kopia lustrzana https://github.com/c9/core
1271 wiersze
38 KiB
JavaScript
1271 wiersze
38 KiB
JavaScript
var fs = require("fs");
|
|
var join = require("path").join;
|
|
var proc = require("child_process");
|
|
var path = require("path");
|
|
|
|
// set up env variables for windows
|
|
if (process.platform == "win32") {
|
|
// HOME usually isn't defined on windows
|
|
if (!process.env.HOME)
|
|
process.env.HOME = process.env.HOMEDRIVE + process.env.HOMEPATH;
|
|
// add cloud9 cygwin to path
|
|
var msysBin = join(process.env.HOME, ".c9", "msys/bin");
|
|
process.env.Path = msysBin + ";" + process.env.path;
|
|
process.env.C9_BASH_BIN = msysBin + "/bash.exe";
|
|
process.env.CYGWIN = "nodosfilewarning " + (process.env.CYGWIN || "");
|
|
process.env.CHERE_INVOKING = 1; // prevent cygwin from changing bash path
|
|
}
|
|
|
|
// Ports on which we'd like to run preview (see http://saucelabs.com/docs/connect#localhost)
|
|
var SAFE_PORTS = [2222, 2310, 3000, 3001, 3030, 3210, 3333, 4000, 4001,
|
|
4040, 4321, 4502, 4503, 4567, 5000, 5001, 5050, 5555,
|
|
5432, 6000, 6001, 6060, 6666, 6543, 7000, 7070, 7774,
|
|
7777, 8000, 8001, 8003, 8031, 8080, 8081, 8765, 8777,
|
|
8888, 9000, 9001, 9080, 9090, 9876, 9877, 9999, 49221];
|
|
|
|
var installPath = process.platform == "dar-win" // disabled for sdk
|
|
? "/Library/Application Support/Cloud9"
|
|
: join(process.env.HOME, ".c9");
|
|
|
|
// Support legacy installations
|
|
if (installPath === "/Library/Application Support/Cloud9"
|
|
&& !fs.existsSync(join(installPath, "version"))
|
|
&& fs.existsSync(join(process.env.HOME, installPath, "version")))
|
|
installPath = join(process.env.HOME, installPath);
|
|
|
|
var nodePath = process.platform == "win32"
|
|
? join(installPath, "node.exe")
|
|
: installPath + "/node/bin/node";
|
|
|
|
var logStream;
|
|
|
|
// this seems to be needed in both window and server
|
|
process.on("uncaughtException", function(err) {
|
|
console.error(err);
|
|
});
|
|
|
|
function toInternalPath(path) {
|
|
if (process.platform == "win32")
|
|
path = path.replace(/^[/]*/, "/").replace(/[\\/]+/g, "/");
|
|
return path;
|
|
}
|
|
|
|
var App = window.nwGui.App;
|
|
var argv = App.argv;
|
|
argv.parsed = parseArgs(argv);
|
|
|
|
var settingDir = argv.parsed["--setting-path"]
|
|
? path.resolve(argv.parsed["--setting-path"])
|
|
: (installPath === "/Library/Application Support/Cloud9"
|
|
? path.join(process.env.HOME, installPath)
|
|
: installPath);
|
|
|
|
function parseArgs(argv) {
|
|
if (typeof argv == "string")
|
|
argv = argv.match(/(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)'|((?:[^ \\]|\\.)+))/g)
|
|
.map(function(x) { return x.replace(/^["']|["']$/g, ""); });
|
|
var parsed = {
|
|
_: [],
|
|
"--setting-path": "",
|
|
"-w": ""
|
|
};
|
|
var argname;
|
|
for (var i = 0; i < argv.length; i++) {
|
|
var arg = argv[i];
|
|
if (arg[0] === "-") {
|
|
argname = arg;
|
|
if (!(argname in parsed)) {
|
|
parsed[argname] = true;
|
|
argname = "";
|
|
}
|
|
} else if (argname) {
|
|
parsed[argname] = arg;
|
|
argname = "";
|
|
} else {
|
|
parsed._.push(arg);
|
|
}
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
|
|
var server = {
|
|
writePIDFile : function(path){
|
|
function write(pid){
|
|
fs.writeFile(process.env.HOME + "/.c9/pid", pid + ":" + path,
|
|
function(err){});
|
|
}
|
|
|
|
// In OSX the pid is not the nw process, but a child
|
|
// We'll look up the parent
|
|
if (process.platform == "darwin") {
|
|
proc.execFile("ps", ["-axf"], function(err, stdout, stderr){
|
|
if (err) return console.log("Could not write PID file: ",
|
|
(err.message || err) );
|
|
|
|
var re = new RegExp("[ \\d]*?\\s" + process.pid
|
|
+ "\\s+(\\d+)\\s+.*Contents\\/Frameworks\\/node\\-webkit");
|
|
|
|
var m = stdout.match(re);
|
|
if (!m) return console.log("Could not write PID file");
|
|
|
|
write(m[1]);
|
|
});
|
|
}
|
|
else
|
|
write(process.pid);
|
|
},
|
|
|
|
start : function(options, callback){
|
|
if (nwProcessId) {
|
|
return windowManager.connection.call(0, {
|
|
type: "serverStart",
|
|
options: options,
|
|
}, callback);
|
|
}
|
|
|
|
var self = this;
|
|
if (this.process) {
|
|
return process.nextTick(function() {
|
|
callback(null, self.options.port);
|
|
});
|
|
}
|
|
// console.log("Starting Cloud9...", port);
|
|
var allowExit;
|
|
var path = options.path;
|
|
if (!path)
|
|
path = process.env.HOME || "/";
|
|
|
|
// Write PID file
|
|
this.writePIDFile(path);
|
|
|
|
// Listen for exit signals
|
|
process.on("exit", function(){
|
|
if (p) p.kill();
|
|
|
|
fs.unlink(process.env.HOME + "/.c9/pid", function(){});
|
|
});
|
|
|
|
var args = [
|
|
join(__dirname, "../server.js"),
|
|
"local",
|
|
"--setting-path", settingDir,
|
|
"-s", "local",
|
|
"--listen", options.host,
|
|
"-w", path,
|
|
"-p", options.port,
|
|
"--collab", options.collab
|
|
];
|
|
|
|
if (options.wsType)
|
|
args.push("--workspacetype", options.wsType);
|
|
|
|
if (options.inProcess) {
|
|
var server = require("../server");
|
|
args.shift();
|
|
return server(args, "local", function() {
|
|
callback(null, options.port);
|
|
});
|
|
}
|
|
|
|
var env = {};
|
|
Object.keys(process.env).forEach(function(name){
|
|
env[name] = process.env[name];
|
|
});
|
|
env["NODE_PATH"] = (process.platform == "win32"
|
|
? join(nodePath, "../deps")
|
|
: installPath) + "/node_modules";
|
|
|
|
var p = proc.spawn(nodePath, args, { env: env });
|
|
|
|
// Do Nothing
|
|
function done(){
|
|
p.stderr.removeListener("data", waitErr);
|
|
p.stdout.removeListener("data", waitOut);
|
|
}
|
|
function waitErr(chunk){
|
|
chunk = chunk.toString();
|
|
if (chunk.indexOf("EADDRINUSE") > -1) {
|
|
done();
|
|
p.kill();
|
|
|
|
var err = new Error(chunk);
|
|
err.code = "EADDRINUSE";
|
|
err.options = options;
|
|
allowExit = true;
|
|
callback(err);
|
|
}
|
|
}
|
|
function waitOut(chunk){
|
|
chunk = chunk.toString();
|
|
console.warn("SERVER:", chunk);
|
|
if (chunk.indexOf("Started") > -1) {
|
|
done();
|
|
callback(null, options.port);
|
|
}
|
|
}
|
|
p.stderr.on("data", waitErr);
|
|
p.stdout.on("data", waitOut);
|
|
|
|
p.stderr.on("data", function(e) {
|
|
console.error(e+"");
|
|
});
|
|
p.stdout.on("data", function(e) {
|
|
console.log(e+"");
|
|
});
|
|
p.on("exit", function() {
|
|
self.process = null;
|
|
setTimeout(function() {
|
|
if (p.exitCode && !allowExit && !self.process)
|
|
self.start(options, function(){});
|
|
}, 1000);
|
|
});
|
|
self.options = options;
|
|
self.process = p;
|
|
},
|
|
|
|
findFreePort : function(host, callback) {
|
|
var ports = SAFE_PORTS.slice();
|
|
find();
|
|
|
|
function find() {
|
|
var port = ports.pop();
|
|
if (!port)
|
|
return callback(new Error("Could not find an available port"));
|
|
require("netutil").isPortOpen(host, port, 2000, function(open) {
|
|
if (!open)
|
|
return callback(null, port);
|
|
find();
|
|
});
|
|
}
|
|
},
|
|
|
|
stop : function() {
|
|
this.process.kill();
|
|
},
|
|
|
|
restart : function() {
|
|
this.stop();
|
|
this.start(this.options);
|
|
},
|
|
|
|
getPlugins : function(options, cb, restoreWindow) {
|
|
var windowConfig = options.windowConfig || {};
|
|
var configPath = join(__dirname, "../configs/client-default-local.js");
|
|
var settingsPath = join(__dirname, "../settings/local.js");
|
|
var themeDir = join(__dirname, "../build/standalone/skin/" +
|
|
(windowConfig.isRemote ? "full" : "default-local"));
|
|
|
|
var stateConfigFilePath = windowConfig.id
|
|
? join(settingDir, "windows/" + windowConfig.id + "-state.settings")
|
|
: join(settingDir, "state.settings");
|
|
|
|
function getConfig(options) {
|
|
options.installed = true;
|
|
options.settingDir = settingDir;
|
|
var settings = {
|
|
"user": join(settingDir, "user.settings"),
|
|
"profile": join(settingDir, "profile.settings"),
|
|
"project": join(settingDir, "project.settings"),
|
|
"state": stateConfigFilePath
|
|
};
|
|
|
|
for (var type in settings) {
|
|
settings[type] = readJSONFile(settings[type]);
|
|
}
|
|
|
|
if (windowConfig.stateSettings) {
|
|
settings.state = windowConfig.stateSettings;
|
|
delete windowConfig.stateSettings;
|
|
}
|
|
|
|
options.settings = settings;
|
|
|
|
if (settings.profile.id) {
|
|
options.user = settings.profile;
|
|
if (settings.profile.saucelabs) {
|
|
options.saucelabs.account = settings.profile.saucelabs;
|
|
}
|
|
}
|
|
else {
|
|
options.user = {
|
|
id: -1,
|
|
name: "Anonymous",
|
|
fullname: "Anonymous",
|
|
email: "",
|
|
pubkey: null
|
|
};
|
|
}
|
|
|
|
if (process.platform == "win32")
|
|
options.sauceConnectPath = join(nodePath, "../deps");
|
|
|
|
|
|
return require(configPath)(options);
|
|
}
|
|
|
|
function updateFilePaths(plugins, cb) {
|
|
var stateSettings, userSettings, preloadPlugin;
|
|
|
|
function fixCssStaticPath(str) {
|
|
if (options.windowLocation && str)
|
|
return str.replace(/(url\(["']?)\/(?:standalone\/)?static/g, "$1" + options.windowLocation);
|
|
return str;
|
|
}
|
|
function loadTheme(name, cb) {
|
|
if (preloadPlugin[name])
|
|
return cb(null, preloadPlugin[name]);
|
|
fs.readFile(themeDir + "/" + name + ".css", "utf8", function(err, data) {
|
|
// todo rebuild missing themes
|
|
data = fixCssStaticPath(data);
|
|
preloadPlugin[name] = data;
|
|
cb && cb(err, data);
|
|
});
|
|
}
|
|
var isRemote = windowConfig.isRemote;
|
|
for (var i = 0; i < plugins.length; i++) {
|
|
var p = plugins[i];
|
|
if (p.packagePath === "plugins/c9.ide.layout.classic/preload") {
|
|
preloadPlugin = p;
|
|
if (options.packed || isRemote)
|
|
p.loadTheme = loadTheme;
|
|
else
|
|
p.themePrefix = "/static/standalone/skin/default-local";
|
|
}
|
|
else if (p.packagePath === "plugins/c9.vfs.client/endpoint") {
|
|
p.getServers = options.getServers;
|
|
}
|
|
else if (p.packagePath == "plugins/c9.ide.language/language") {
|
|
p.useUIWorker = options.noWorker;
|
|
if (options.packed) {
|
|
p.staticPrefix = options.windowLocation;
|
|
p.workerPrefix = settings.CORSWorkerPrefix;
|
|
} else {
|
|
p.staticPrefix = p.workerPrefix = null;
|
|
}
|
|
}
|
|
else if (p.packagePath == "plugins/c9.core/settings") {
|
|
if (!isRemote)
|
|
p.stateConfigFilePath = stateConfigFilePath;
|
|
stateSettings = p.settings.state;
|
|
if (typeof stateSettings == "string") {
|
|
try {
|
|
stateSettings = JSON.parse(stateSettings);
|
|
} catch(e) {}
|
|
}
|
|
userSettings = p.settings.user;
|
|
if (typeof userSettings == "string") {
|
|
try {
|
|
userSettings = JSON.parse(userSettings);
|
|
} catch(e) {}
|
|
}
|
|
}
|
|
|
|
if (p.staticPrefix && options.windowLocation && options.packed) {
|
|
p.staticPrefix = p.staticPrefix.replace(/^\/static/, options.windowLocation);
|
|
}
|
|
}
|
|
var themeName = userSettings && userSettings.general && userSettings.general["@skin"] || "dark";
|
|
restoreWindow && restoreWindow(stateSettings);
|
|
loadTheme(themeName, cb);
|
|
}
|
|
|
|
var settings = require(settingsPath)(null, null, settingDir);
|
|
settings.packed = options.packed;
|
|
|
|
settings.vfsServers = options.vfsServers;
|
|
settings.workspaceDir = options.workspaceDir;
|
|
settings.CORSWorkerPrefix = (
|
|
options.windowLocation && options.packed ? options.windowLocation + "/build/" : "/static/"
|
|
) + "standalone/worker";
|
|
|
|
if (windowConfig.isRemote)
|
|
settings.remoteWorkspace = windowConfig.name;
|
|
|
|
if (settings.remoteWorkspace) {
|
|
getRemoteWorkspaceConfig(windowConfig.name, function(err, config) {
|
|
if (err) {
|
|
plugins = getConfig(settings);
|
|
addErrorHandlerPlugin(plugins, windowConfig);
|
|
}
|
|
else {
|
|
plugins = require(configPath).makeLocal(config.plugins, settings);
|
|
settings.url = config.url;
|
|
}
|
|
updateFilePaths(plugins, function(){
|
|
cb(plugins, settings);
|
|
});
|
|
});
|
|
} else {
|
|
var plugins = getConfig(settings);
|
|
updateFilePaths(plugins, function(){
|
|
cb(plugins, settings);
|
|
});
|
|
}
|
|
},
|
|
|
|
appendLog : function(message) {
|
|
logStream && logStream.write(message);
|
|
},
|
|
|
|
__dirname : __dirname,
|
|
installPath : installPath,
|
|
argv: argv,
|
|
|
|
openWindow : openWindow,
|
|
parseArgs : parseArgs,
|
|
getRecentWindows: getRecentWindows,
|
|
listC9Projects: listC9Projects,
|
|
getRemoteWorkspaceConfig: getRemoteWorkspaceConfig
|
|
};
|
|
|
|
function addErrorHandlerPlugin(plugins, windowConfig) {
|
|
plugins.push({
|
|
provides: [],
|
|
consumes: ["c9", "auth", "restore", "dialog.alert", "commands"],
|
|
setup: function(options, imports, register) {
|
|
imports.auth.on("login", function() {
|
|
getRemoteWorkspaceConfig(windowConfig.name, function(err, config) {
|
|
if (err) {
|
|
|
|
return imports["dialog.alert"].alert(
|
|
"Error loading remote workspace",
|
|
"Couldn't load config for " + windowConfig.name
|
|
);
|
|
}
|
|
imports.commands.exec("restartc9");
|
|
});
|
|
});
|
|
|
|
register(null, {});
|
|
}
|
|
});
|
|
}
|
|
|
|
/*****************************************************************/
|
|
/* windowManager: move to own file once build tool supports that */
|
|
/*****************************************************************/
|
|
|
|
var MAX_RECENT_WINDOWS = 20;
|
|
var MAX_SAME_PROCESS = 1;
|
|
var processIdCounter = 1;
|
|
var activeWindowId = null;
|
|
var nwProcessId = 0;
|
|
var allWindows = Object.create(null);
|
|
var windowData = Object.create(null);
|
|
var connection = null;
|
|
|
|
var windowManager = server.windowManager = {
|
|
quit: quit,
|
|
quitAll: quitAll,
|
|
unquit: unquit,
|
|
onClose: onClose,
|
|
save: scheduleSave,
|
|
statePath: join(settingDir, "window.settings"),
|
|
openWindow: openWindow,
|
|
$allWindows: allWindows,
|
|
$windowData: windowData,
|
|
onShowWindow: onShowWindow,
|
|
restoreSession: restoreSession,
|
|
registerWindow: registerWindow,
|
|
getRecentWindows: getRecentWindows,
|
|
findWindowForPath: findWindowForPath,
|
|
registerSharedModule: registerSharedModule,
|
|
signalToAll: signalToAll,
|
|
setFavorites: setFavorites,
|
|
forEachWindow: forEachWindow,
|
|
isPrimaryWindow: isPrimaryWindow,
|
|
get connection() { return connection },
|
|
get activeWindow() { return allWindows[activeWindowId] },
|
|
get activeWindowId() { return activeWindowId },
|
|
};
|
|
|
|
function forEachWindow(fn) {
|
|
getRecentWindows().forEach(function(data) {
|
|
fn(data, allWindows[data.id]);
|
|
});
|
|
}
|
|
|
|
|
|
function signalToAll(name, e) {
|
|
if (nwProcessId)
|
|
return connection.call(0, {type: "signalToAll", arg: [name, e]});
|
|
|
|
forEachWindow(function(data, win) {
|
|
if (win && win.emit) win.emit(name, e);
|
|
});
|
|
}
|
|
|
|
function isPrimaryWindow(window) {
|
|
if (nwProcessId) return false;
|
|
var win = window.win;
|
|
if (win.options.nwProcessId)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
function findWindowForPath(path) {
|
|
var matches = [];
|
|
path = toInternalPath(path);
|
|
for (var id in windowData) {
|
|
var win = windowData[id];
|
|
win.favorites && win.favorites.forEach(function(p, i) {
|
|
if (path.indexOf(p) === 0 && (path[p.length] == "/" || !path[p.length])) {
|
|
matches.push({
|
|
win: win,
|
|
dist: path.substr(p.length).split("/").length + (i ? 1 : 0)
|
|
});
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
matches.sort(function(a, b) {
|
|
return a.dist - b.dist;
|
|
});
|
|
|
|
return matches[0] && matches[0].win;
|
|
}
|
|
|
|
function restoreSession(win) {
|
|
var state = readJSONFile(this.statePath);
|
|
var toOpen = [];
|
|
Object.keys(state).forEach(function(id) {
|
|
var data = state[id];
|
|
if (data.isOpen) {
|
|
data.isOpen = false;
|
|
toOpen.push(data);
|
|
}
|
|
if (!data.favorites)
|
|
data.favorites = [];
|
|
windowData[id] = data;
|
|
});
|
|
|
|
// Deal with user reopening app on osx
|
|
App.on("reopen", function(){
|
|
openWindow({id: activeWindowId || "latest", focus: true});
|
|
});
|
|
// Event to open additional files (I hope)
|
|
App.on("open", function(cmdLine) {
|
|
// console.log(cmdLine);
|
|
var parsed = parseArgs(cmdLine);
|
|
var path = parsed._.pop();
|
|
openWindowForPath(path);
|
|
});
|
|
|
|
var path = argv.parsed._.pop();
|
|
if (path) {
|
|
// todo handle folders?
|
|
openWindowForPath(path);
|
|
} else {
|
|
if (!toOpen.length)
|
|
toOpen.push({id: "latest"});
|
|
toOpen.sort(function(a, b) {
|
|
return b.time - a.time;
|
|
});
|
|
toOpen[0].focus = true;
|
|
openWindow(toOpen[0]);
|
|
}
|
|
}
|
|
|
|
function setFavorites(id, favorites) {
|
|
if (windowData[id]) {
|
|
windowData[id].favorites = favorites;
|
|
scheduleSave();
|
|
}
|
|
updateTitle(id);
|
|
if (nwProcessId)
|
|
connection.call(0, {id: id, favorites: favorites, type: "setFavorites"});
|
|
}
|
|
|
|
function updateTitle(id) {
|
|
var winData = windowData[id];
|
|
var win = allWindows[id];
|
|
if (win && winData) {
|
|
var favs = winData.favorites;
|
|
if (winData.isRemote) {
|
|
win.displayName = "Remote " + /[^\/]*\/?$/.exec(winData.name || "")[0];
|
|
} else {
|
|
win.displayName = /[^\/]*\/?$/.exec(favs[0] || "")[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
function openWindowForPath(path, forceNew) {
|
|
var data = windowManager.findWindowForPath(path);
|
|
if (!data && !forceNew)
|
|
data = {id : "latest", isRemote: false, isEmpty: true};
|
|
|
|
data.focus = true;
|
|
data.filePath = path;
|
|
openWindow(data);
|
|
}
|
|
|
|
function registerWindow(win, id) {
|
|
if (typeof id == "string") {
|
|
if (id[0] == "{") {
|
|
win.options = JSON.parse(id);
|
|
id = win.options.id;
|
|
}
|
|
}
|
|
|
|
if (!win.options)
|
|
win.options = {id: id};
|
|
|
|
allWindows[id] = win;
|
|
activeWindowId = id;
|
|
function mainCloseHandler() {
|
|
if (win.listeners("close").length == 1) {
|
|
onClose(id);
|
|
win.close(true);
|
|
}
|
|
}
|
|
// make sure only one mainCloseHandler is attached even after calling win.reload()
|
|
win.listeners("close").forEach(function(f) {
|
|
if (f.name == "mainCloseHandler")
|
|
win.removeListener("close", f);
|
|
});
|
|
win.on("close", mainCloseHandler);
|
|
win.on("focus", function() {
|
|
onFocus(id);
|
|
});
|
|
|
|
win.on("savePosition", function() {
|
|
savePosition(id);
|
|
});
|
|
|
|
if (id != "root")
|
|
scheduleSave();
|
|
|
|
if (win.options.nwProcessId && !allWindows.root)
|
|
nwProcessId = win.options.nwProcessId;
|
|
|
|
if (nwProcessId)
|
|
updateWindowData(win.options);
|
|
|
|
if (!connection)
|
|
connection = new MsgChannel();
|
|
|
|
if (!logStream) {
|
|
try {
|
|
logStream = fs.createWriteStream(settingDir + '/log.txt', nwProcessId ? {flags: 'a'} : undefined);
|
|
} catch (e) {}
|
|
}
|
|
|
|
// if (id != "root") {
|
|
// setTimeout(function() {
|
|
// windowManager.registerWindow();
|
|
// });
|
|
// }
|
|
}
|
|
|
|
function onClose(id) {
|
|
delete allWindows[id];
|
|
var winState = windowData[id];
|
|
|
|
if (nwProcessId) {
|
|
return connection.call(0, {type: "onClose", id: id});
|
|
}
|
|
|
|
if (Object.keys(allWindows).length <= 1 && allWindows.root) {
|
|
if (process.platform != "darwin" || allWindows.root.quitting) {
|
|
allWindows.root.close(true);
|
|
allWindows.root.quitting = true;
|
|
}
|
|
}
|
|
|
|
// Only save state when not quitting the application
|
|
if (allWindows.root && !allWindows.root.quitting) {
|
|
winState.isOpen = false;
|
|
savestate();
|
|
}
|
|
|
|
if (activeWindowId == id)
|
|
activeWindowId = 0;
|
|
}
|
|
|
|
function quit() {
|
|
if (nwProcessId)
|
|
return connection.call(0, {type: "quit"});
|
|
|
|
var active = allWindows[activeWindowId];
|
|
|
|
forEachWindow(function(data, win) {
|
|
if (win && win != active && win.hide)
|
|
win.hide();
|
|
});
|
|
|
|
if (active) {
|
|
active.emit("askForQuit", true);
|
|
}
|
|
}
|
|
|
|
function quitAll(){
|
|
if (nwProcessId)
|
|
return connection.call(0, {type: "quitAll"});
|
|
|
|
if (allWindows.root)
|
|
allWindows.root.quitting = true;
|
|
|
|
forEachWindow(function(data, win) {
|
|
if (win && win.emit) win.emit("saveAndQuit");
|
|
});
|
|
}
|
|
|
|
function unquit(){
|
|
if (nwProcessId)
|
|
return connection.call(0, {type: "unquit"});
|
|
|
|
var active = allWindows[activeWindowId];
|
|
forEachWindow(function(data, win) {
|
|
if (win && win != active && win.show)
|
|
win.show();
|
|
});
|
|
|
|
active && active.show();
|
|
}
|
|
|
|
function onFocus(id) {
|
|
if (nwProcessId)
|
|
return connection.call(0, {type: "onFocus", id: id});
|
|
activeWindowId = id;
|
|
if (windowData[id])
|
|
windowData[id].time = Date.now();
|
|
}
|
|
|
|
function onShowWindow(win) {
|
|
if (win.options && win.options.focus) {
|
|
delete win.options.focus;
|
|
win.focus();
|
|
}
|
|
if (win.$onShow) {
|
|
win.$onShow(null, win);
|
|
win.$onShow = undefined;
|
|
}
|
|
}
|
|
|
|
function savePosition(id, position) {
|
|
var win = allWindows[id];
|
|
if (!position) {
|
|
if (!win || win.isMinimized)
|
|
return;
|
|
position = [win.x, win.y, win.width, win.height];
|
|
}
|
|
|
|
if (nwProcessId)
|
|
return connection.call(0, {type: "savePosition", id: id, position: position});
|
|
var winData = windowData[id];
|
|
if (winData) {
|
|
winData.position = position;
|
|
scheduleSave();
|
|
}
|
|
}
|
|
|
|
function openQuitSure(callback){
|
|
|
|
}
|
|
|
|
function openWindow(options, callback) {
|
|
if (nwProcessId)
|
|
return connection.call(0, {type: "openWindow", options: options}, callback);
|
|
|
|
options = validateWindowOptions(options);
|
|
var id = options.id;
|
|
|
|
if (allWindows[id]) {
|
|
if (options.duplicate && id == activeWindowId) {
|
|
id = options.id = options.id + ".1";
|
|
} else {
|
|
win = allWindows[id];
|
|
win.emit("focusWindow");
|
|
if (options.filePath)
|
|
win.emit("openFile", {path: options.filePath});
|
|
return callback && callback();
|
|
}
|
|
}
|
|
|
|
var window = allWindows.root.window;
|
|
var nwGui = window.require("nw.gui");
|
|
|
|
var isNewInstance = false;
|
|
var inProcessWindows = 0;
|
|
for (var i in allWindows) {
|
|
if (allWindows[i].options && allWindows[i].options.nwProcessId === 0) {
|
|
if (++inProcessWindows >= MAX_SAME_PROCESS) {
|
|
isNewInstance = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
options.nwProcessId = isNewInstance ? processIdCounter++ : 0;
|
|
|
|
var url = "index.html?id=" + (isNewInstance ? escape(JSON.stringify(options)) : id);
|
|
if (options.reset) {
|
|
url += "&reset=state";
|
|
delete options.reset;
|
|
}
|
|
var sc = window.screen;
|
|
var ah = sc.availHeight, aw = sc.availWidth, ax = sc.availLeft, ay = sc.availTop;
|
|
var h, w, x, y;
|
|
var position = options.position;
|
|
|
|
if (!position) {
|
|
w = Math.min(1200, Math.round(0.7 * aw));
|
|
h = Math.min(960, Math.round(0.8 * ah));
|
|
x = Math.round(ax + (aw - w)/2);
|
|
y = Math.round(ay + (ah - h)/2);
|
|
} else {
|
|
x = Math.max(ax - 10, position[0]);
|
|
y = Math.max(ay - 10, position[1]);
|
|
w = Math.min(aw, position[2]);
|
|
h = Math.min(ah, position[3]);
|
|
}
|
|
|
|
var win = nwGui.Window.open(url, {
|
|
"new-instance": !!isNewInstance,
|
|
height: h,
|
|
width: w,
|
|
left: x,
|
|
top: y,
|
|
show: false,
|
|
frame: false,
|
|
toolbar: false,
|
|
title: "Cloud9",
|
|
icon: "icon.png",
|
|
"win_bg": "#000000"
|
|
});
|
|
win.moveTo(x, y);
|
|
win.options = options;
|
|
allWindows[id] = win;
|
|
win.$onShow = callback;
|
|
|
|
if (isNewInstance) {
|
|
win.emit = function() {
|
|
connection.call(win.options.nwProcessId, {
|
|
type: "winEvent",
|
|
id: id,
|
|
arguments: Array.apply(null, arguments)
|
|
});
|
|
};
|
|
}
|
|
updateWindowData(options);
|
|
}
|
|
|
|
function updateWindowData(options) {
|
|
var id = options.id;
|
|
var winState = windowData[id] || {};
|
|
winState.id = id;
|
|
if (!winState.favorites)
|
|
winState.favorites = [];
|
|
|
|
winState.isRemote = options.isRemote;
|
|
winState.time = Date.now();
|
|
winState.isOpen = true;
|
|
|
|
if (winState.isRemote) {
|
|
winState.name = options.name;
|
|
}
|
|
|
|
windowData[id] = winState;
|
|
}
|
|
|
|
function validateWindowOptions(options) {
|
|
options = options || {};
|
|
if (options.isRemote) {
|
|
options.id = "remote/" + options.name;
|
|
} else {
|
|
var windows = Object.keys(windowData).map(function(id) {
|
|
return windowData[id];
|
|
}).filter(function(x) {
|
|
return !x.isRemote;
|
|
}).sort(function(a, b) {
|
|
if (Boolean(b.isOpen) !== Boolean(a.isOpen))
|
|
return b.isOpen ? 1 : -1;
|
|
if (b.isOpen || (a.favorites.length && b.favorites.length))
|
|
return b.time - a.time;
|
|
return b.favorites.length ? 1 : -1;
|
|
});
|
|
|
|
if (options.id == "latest") {
|
|
for (var i = 0; i < windows.length; i++) {
|
|
var d = windows[i];
|
|
if (options.isOpen === false && d.isOpen)
|
|
continue;
|
|
if (options.isRemote === false && d.isRemote)
|
|
continue;
|
|
if (options.isEmpty && d.favorites.length)
|
|
continue;
|
|
|
|
d.filePath = options.filePath;
|
|
options = d;
|
|
break;
|
|
}
|
|
if (options.id == "latest")
|
|
options.id = 0;
|
|
}
|
|
if (!options.id) {
|
|
var id = 0;
|
|
if (windows.length > MAX_RECENT_WINDOWS) {
|
|
var last = windows[windows.length - 1];
|
|
id = last.isOpen ? 0 : last.id;
|
|
}
|
|
|
|
if (!id) {
|
|
do id++;
|
|
while (windowData[id]);
|
|
}
|
|
|
|
if (!options.stateSettings)
|
|
options.reset = true;
|
|
options.id = id;
|
|
delete windowData[id];
|
|
}
|
|
}
|
|
|
|
if (windowData[options.id] && !options.position)
|
|
options.position = windowData[options.id].position;
|
|
|
|
return options;
|
|
}
|
|
|
|
function readJSONFile(path) {
|
|
try {
|
|
var data = fs.readFileSync(path, "utf8");
|
|
return JSON.parse(data) || {};
|
|
}
|
|
catch(e){
|
|
console.log("Could not parse JSON", e);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function getRecentWindows(callback) {
|
|
if (nwProcessId && callback)
|
|
return connection.call(0, {type: "getRecentWindows"}, callback);
|
|
var names = Object.create(null);
|
|
var recentWindows = Object.keys(windowData).map(function(id) {
|
|
var data = windowData[id];
|
|
var name = data.name;
|
|
if (!name && data.favorites[0])
|
|
name = data.favorites[0].match(/[^\/]*\/?$/)[0];
|
|
if (!name)
|
|
name = "[Untitled" + (id < 10 ? " " : "") + id + "]";
|
|
// todo better handling for duplicate windows
|
|
if (names[name]) {
|
|
name += " (" + names[name] + ")";
|
|
names[name]++;
|
|
} else {
|
|
names[name] = 1;
|
|
}
|
|
return {
|
|
name: name,
|
|
id: id,
|
|
time: data.time,
|
|
isRemote: !!data.isRemote,
|
|
isOpen: !!data.isOpen,
|
|
isEmpty: !data.isRemote && !data.favorites.length,
|
|
favorites: data.favorites.slice()
|
|
};
|
|
});
|
|
callback && callback(null, recentWindows);
|
|
return recentWindows;
|
|
}
|
|
|
|
function registerSharedModule(name, construct) {
|
|
if (nwProcessId) return;
|
|
var root = allWindows.root;
|
|
if (!root.modules)
|
|
root.modules = Object.create(null);
|
|
if (!root.modules[name]) {
|
|
root.modules[name] = construct(windowManager, connection);
|
|
//@nightwing why eval?
|
|
// root.modules[name] = "(" + global.eval("(" + construct + ")")(windowManager, connection);
|
|
}
|
|
}
|
|
|
|
var saveTimer, saving;
|
|
function savestate() {
|
|
if (nwProcessId)
|
|
return connection.call(0, {type: "windowManagerSave"});
|
|
if (saving)
|
|
return scheduleSave();
|
|
var content = JSON.stringify(windowData, function(x,y) { return y || undefined});
|
|
saving = true;
|
|
var tmp = windowManager.statePath + "~";
|
|
fs.writeFile(tmp, content, "utf8", function(err, result) {
|
|
saving = false;
|
|
fs.rename(tmp, windowManager.statePath, function(err, result) {
|
|
saving = false;
|
|
});
|
|
});
|
|
}
|
|
function scheduleSave() {
|
|
if (nwProcessId)
|
|
return connection.call(0, {type: "windowManagerSave"});
|
|
if (!saveTimer) {
|
|
saveTimer = setTimeout(function() {
|
|
saveTimer = null;
|
|
savestate();
|
|
}, 500);
|
|
}
|
|
}
|
|
|
|
// remote projects
|
|
function loadData(url, callback) {
|
|
var xhr = new window.XMLHttpRequest();
|
|
xhr.open("GET", url + "?access_token=fake_token", true);
|
|
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
|
xhr.onload = function(e) {
|
|
callback(null, xhr.responseText);
|
|
};
|
|
xhr.onabort = xhr.onerror = function(e) {
|
|
callback(e);
|
|
};
|
|
xhr.send("");
|
|
}
|
|
|
|
// TODO add proper api to c9 server
|
|
function listC9Projects(user, callback) {
|
|
if (!user)
|
|
return callback(null, []);
|
|
|
|
var url = "https://c9.io/" + user.name;
|
|
loadData(url, function(err, result) {
|
|
if (err) return callback(err);
|
|
var ownProjects = [];
|
|
var sharedProjects = [];
|
|
try {
|
|
var pids = Object.create(null);
|
|
JSON.parse(result.match(/projects:\s*(.*),/)[1]).forEach(function(x) {
|
|
var userName = x.owner_username || user.name;
|
|
|
|
var project = {
|
|
name: userName + "/" + x.name,
|
|
projectName: x.name,
|
|
pid: x.pid,
|
|
isRemote: true,
|
|
};
|
|
|
|
if (pids[project.pid])
|
|
return;
|
|
|
|
pids[project.pid] = project;
|
|
|
|
if (userName == user.name)
|
|
ownProjects.push(project);
|
|
else
|
|
sharedProjects.push(project);
|
|
});
|
|
} catch(e) {
|
|
console.error(e);
|
|
return callback(e);
|
|
}
|
|
callback(null, {
|
|
shared: sharedProjects,
|
|
own: ownProjects
|
|
});
|
|
});
|
|
}
|
|
|
|
function getRemoteWorkspaceConfig(projectName, callback) {
|
|
var url = "https://ide.c9.io/" + projectName + "?config=1";
|
|
loadData(url, function(err, result, xhr) {
|
|
try {
|
|
var config = JSON.parse(result);
|
|
} catch(e) {
|
|
err = err || e;
|
|
}
|
|
callback(err, {
|
|
url: url,
|
|
plugins: config && config.architectConfig,
|
|
raw: config
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
var net = require('net');
|
|
var projectWD = window.nwGui.App.dataPath;
|
|
var sockPath = process.platform == "win32"
|
|
? "\\\\.\\pipe\\"+ projectWD +"\\c9.windowManager.socket"
|
|
: join(projectWD, "c9.windowManager.socket");
|
|
|
|
function MsgChannel() {
|
|
this.callbacks = {};
|
|
this.callbackId = 0;
|
|
this.sockets = {};
|
|
this.handlers = {};
|
|
var self = this;
|
|
var buffer = "";
|
|
function read(e, fn) {
|
|
e = e + "";
|
|
var i;
|
|
while ((i = e.indexOf("\x01")) != -1) {
|
|
buffer += e.substring(0, i);
|
|
e = e.substr(i + 1);
|
|
try {
|
|
var msg = JSON.parse(buffer);
|
|
fn(msg);
|
|
} catch(e) {
|
|
console.error(buffer);
|
|
console.error(e);
|
|
}
|
|
buffer = "";
|
|
}
|
|
buffer += e;
|
|
}
|
|
|
|
if (nwProcessId) {
|
|
var client = net.connect(sockPath, function () {});
|
|
this.client = client;
|
|
this.send(0, {type: "init"});
|
|
client.on("data", function(e) {
|
|
read(e, function(msg) {self.onMessage(msg)});
|
|
});
|
|
client.on("error", function(e){
|
|
console.error(e);
|
|
});
|
|
} else {
|
|
var server = net.createServer(function(socket) {
|
|
socket.on("data", function(e){
|
|
read(e, function(msg) {
|
|
if (msg.type == "init")
|
|
self.sockets[msg.$from] = socket;
|
|
else
|
|
self.onMessage(msg);
|
|
});
|
|
});
|
|
});
|
|
|
|
var startServer = function() {
|
|
try {
|
|
server.listen(sockPath);
|
|
} catch(e) {
|
|
console.error(e);
|
|
}
|
|
};
|
|
if (process.platform == "win32") {
|
|
startServer();
|
|
} else {
|
|
// Remove the stale socket, if it exists at sockPath
|
|
fs.unlink(sockPath, startServer);
|
|
}
|
|
}
|
|
}
|
|
(function(){
|
|
this.send = function(processId, msg) {
|
|
msg.$from = nwProcessId;
|
|
if (nwProcessId) {
|
|
this.client.write(JSON.stringify(msg) + "\x01", "utf8");
|
|
} else if (processId) {
|
|
this.sockets[processId].write(JSON.stringify(msg) + "\x01", "utf8");
|
|
} else {
|
|
this.onMessage(msg);
|
|
}
|
|
};
|
|
|
|
this.call = function(processId, args, callback) {
|
|
if (callback) {
|
|
var id = this.callbackId++;
|
|
this.callbacks[id] = callback;
|
|
args.callbackId = id;
|
|
}
|
|
this.send(processId, args);
|
|
};
|
|
|
|
this.onMessage = function(msg) {
|
|
switch(msg.type) {
|
|
case "callback":
|
|
var callback = this.callbacks[msg.callbackId];
|
|
if (callback) {
|
|
callback(msg.err, msg.data);
|
|
delete this.callbacks[msg.callbackId];
|
|
}
|
|
break;
|
|
case "serverStart":
|
|
server.start(msg.options, function(err, data) {
|
|
connection.call(msg.$from, {
|
|
err: err,
|
|
data: data,
|
|
type: "callback",
|
|
callbackId: msg.callbackId
|
|
});
|
|
});
|
|
break;
|
|
case "openWindow":
|
|
openWindow(msg.options, function() {
|
|
connection.call(msg.$from, {
|
|
type: "callback",
|
|
callbackId: msg.callbackId
|
|
});
|
|
});
|
|
break;
|
|
case "windowManagerSave":
|
|
scheduleSave();
|
|
break;
|
|
|
|
case "quit":
|
|
quit();
|
|
break;
|
|
case "unquit":
|
|
unquit();
|
|
break;
|
|
case "quitAll":
|
|
quitAll();
|
|
break;
|
|
case "onClose":
|
|
onClose(msg.id);
|
|
break;
|
|
case "onFocus":
|
|
onFocus(msg.id);
|
|
break;
|
|
case "savePosition":
|
|
savePosition(msg.id, msg.position);
|
|
return;
|
|
case "getRecentWindows":
|
|
getRecentWindows(function(err, data) {
|
|
connection.call(msg.$from, {
|
|
err: err,
|
|
data: data,
|
|
type: "callback",
|
|
callbackId: msg.callbackId
|
|
});
|
|
});
|
|
break;
|
|
case "setFavorites":
|
|
setFavorites(msg.id, msg.favorites);
|
|
break;
|
|
case "winEvent":
|
|
var win = allWindows[msg.id];
|
|
if (win) {
|
|
win.emit.apply(win, msg.arguments);
|
|
}
|
|
break;
|
|
case "signalToAll":
|
|
signalToAll.apply(null, msg.arg);
|
|
break;
|
|
default:
|
|
if (this.handlers[msg.type])
|
|
this.handlers[msg.type](msg);
|
|
}
|
|
};
|
|
|
|
this.sendToWindow = function(id, type, msg) {
|
|
var win = allWindows[id];
|
|
var processId = win.options.nwProcessId;
|
|
this.send(processId, {
|
|
id: id,
|
|
type: "winEvent",
|
|
arguments: [type, msg]
|
|
});
|
|
};
|
|
|
|
this.on = function(type, handler) {
|
|
this.handlers[type] = handler;
|
|
};
|
|
|
|
}).call(MsgChannel.prototype);
|
|
|
|
|
|
//
|
|
module.exports = server;
|