define(function(require, exports, module) { main.consumes = ["Plugin", "automate", "vfs", "c9", "proc", "fs", "error_handler"]; main.provides = ["installer"]; return main; function main(options, imports, register) { var Plugin = imports.Plugin; var automate = imports.automate; var c9 = imports.c9; var fs = imports.fs; var proc = imports.proc; var errorHandler = imports.error_handler; var DEBUG = c9.location.indexOf("debug=3") != -1; /***** Initialization *****/ var plugin = new Plugin("Ajax.org", main.consumes); var emit = plugin.getEmitter(); var NAMESPACE = "installer"; var installSelfCheck = options.installSelfCheck; var installChecked = false; var packages = {}; var sessions = []; var installed = false; var waitForSuccess = false; var installCb, arch, platform, parentPty; // Check that all the dependencies are installed var VERSION = 1; if (!options.cli) { createSession("Cloud9 IDE", VERSION, require("./install/install")); // createSession("Cloud9 CLI", VERSION, require("./install/install.cli")); } function load() { if (c9.readonly) return; if (options.cli) return simpleInstallRead(); imports.vfs.on("beforeConnect", function(e) { if (installChecked) return e.done(false); if (!installSelfCheck) { installChecked = true; e.done(false); simpleInstallRead(); return; } installCb = e.done; if (!proc.installMode) readFromDisk(e.vfs); else proc.installMode = e.vfs; return false; }); } /***** Methods *****/ function simpleInstallRead() { var path = options.installPath.replace(c9.home, "~") + "/installed"; fs.readFile(path, function(err, data) { if (err) { if (err.code == "ENOENT") data = ""; else { c9.once("connect", simpleInstallRead); return; } } parse(data); emit.sticky("ready", installed); }); } function parse(data) { if (data.match(/^1[\r\n]*$/)) // Backwards compatibility data = "Cloud9 IDE@1\nc9.ide.collab@1\nc9.ide.find@1\nCloud9 CLI@1"; installed = {}; data.split("\n").forEach(function(line) { if (!line) return; var p = line.split("@"); installed[p[0]] = parseInt(p[1], 10) || 0; }); if (!installSelfCheck) { installed["Cloud9 IDE"] = 1; installed["c9.ide.collab"] = 1; installed["c9.ide.find"] = 1; installed["Cloud9 CLI"] = 1; } } function readFromDisk(vfs) { function done(err) { if (!installed) installed = {}; if (err && err.code == "ENOENT" || installed["Cloud9 IDE"] !== VERSION) { // Tmux and pty.js are probably not installed. Lets switch // to a special mode of proc proc.installMode = vfs; plugin.once("success", function() { proc.installMode = false; installChecked = true; installCb(true); installCb = null; }); // Wait until installer is done plugin.on("stop", function listen(e) { if (e.session.package.name == "Cloud9 IDE" && (!e.error || !waitForSuccess)) { plugin.waitForSuccess = false; plugin.off("stop", listen); } }, plugin); } else { installChecked = true; installCb(); installCb = null; } emit.sticky("ready", installed); } vfs.readfile(options.installPath.replace(c9.home, "~") + "/installed", { encoding: "utf8" }, function(err, meta) { if (err) { if (err.code == "ENOENT") done(err); return; // Wait for reconnect to try again } var data = ""; var stream = meta.stream; stream.on("data", function(chunk) { data += chunk; }); stream.on("end", function() { parse(data); done(); }); }); } function addPackageManager(name, implementation) { automate.addCommand(NAMESPACE, name, implementation); } function removePackageManager(name) { automate.removeCommand(NAMESPACE, name); } // Add aliases to support a broader range of platforms function addPackageManagerAlias() { var args = [NAMESPACE]; for (var i = 0; i < arguments.length; i++) { args.push(arguments[i]); } automate.addCommandAlias.apply(this, args); } function isInstalled(pkgName, pkgVersion, callback) { if (!installSelfCheck) return true; if (typeof pkgVersion == "function") callback = pkgVersion, pkgVersion = undefined; if (!installed && callback) { return plugin.once("ready", function() { if (isInstalled(pkgName, pkgVersion, callback)) callback(); }); } var boolInstalled = installed[pkgName] && (pkgVersion ? installed[pkgName] == pkgVersion : true); if (!boolInstalled && callback) { plugin.on("stop", function listen() { if (isInstalled(pkgName, pkgVersion)) { callback(); plugin.off("stop", listen); } }); } return boolInstalled; } function reinstall(packageName, silent) { if (packages[packageName]) { if (!silent) emit("reinstall", { name: packageName }); createSession(packageName, packages[packageName].version, packages[packageName].populate, null, true); return true; } return false; } function createSession(packageName, packageVersion, populateSession, callback, force) { if (!installed) { return plugin.on("ready", createSession.bind(this, packageName, packageVersion, populateSession, callback, force)); } if (!options.cli && !c9.isReady) { return c9.on("ready", createSession.bind(this, packageName, packageVersion, populateSession, callback, force)); } if (typeof packageVersion == "function") { force = callback; callback = populateSession; populateSession = packageVersion; packageVersion = populateSession.version; } packageVersion = populateSession.version || parseInt(packageVersion, 10) || 0; packages[packageName] = { version: packageVersion, populate: populateSession }; if (!force && installed[packageName] == packageVersion) return callback && callback(); var session = automate.createSession(NAMESPACE); var add = session.task; delete session.task; function install(options, task, validate) { if (!task || typeof task == "function") { if (typeof task == "function") validate = task; task = options; options = {}; } add(task, options, validate); } function start(callback, force) { if (force || emit("beforeStart", { session: session }) !== false) { // Pre script if (pre) session.tasks.unshift({ "bash": pre }); // Post script if (post) session.tasks.push({ "bash": post }); // Start installation session.run(callback); } } session.on("run", function() { emit("start", { session: session }); }); session.on("stop", function(err) { sessions.splice(sessions.indexOf(session), 1); emit("stop", { session: session, error: err }); // Update installed file if (!err && force !== 2) { installed[packageName] = packageVersion; var contents = Object.keys(installed).map(function(item) { return item + "@" + installed[item]; }).join("\n"); fs.writeFile("~/.c9/installed", contents, function() { callback && callback(err); }); } else { callback && callback(err); } }); session.on("each", function(e) { emit("each", e); }); var intro, pre, post; session.freezePublicAPI({ /** * */ package: { name: packageName, version: packageVersion }, /** * */ get introduction() { return intro; }, set introduction(value) { intro = value; }, /** * */ get preInstallScript() { return pre; }, set preInstallScript(value) { pre = value; }, /** * */ get postInstallScript() { return post; }, set postInstallScript(value) { post = value; }, /** * */ install: install, /** * */ start: start }); session.on("unload", function() { sessions.splice(sessions.indexOf(session), 1); }, plugin); sessions.push(session); if (arch === undefined) { arch = null; proc.execFile("uname", { args: ["-ms"]}, function(e, p) { var parts = p.trim().split(/\s+/); platform = parts[0].toLowerCase(); if (/MINGW|MSYS|CYGWIN/i.test(platform)) platform = c9.platform; // windows only supports local version arch = parts[1]; if (/x86_64/i.test(arch)) arch = "x64"; else if (/armv6l/i.test(arch)) arch = "armv6l"; else if (/armv7l/i.test(arch)) arch = "armv7l"; else if (/i.*86/i.test(arch)) arch = "x86"; if (!arch) arch = undefined; emit.sticky("arch", arch); }); } plugin.once("arch", function() { populateSession(session, { platform: platform, arch: arch }); }); return session; } function ptyExec(options, onData, callback) { if (parentPty) { return parentPty(options, callback); } if (c9.platform == "win32") { options.code = options.code.replace(/\r\n/g, "\n"); return proc.spawn("bash.exe", { args: ["-c", options.code].concat(options.args || []), cwd: options.cwd || null }, function(err, pty) { if (err) return callback(err); pty.stderr.on("data", function(chunk) { onData(chunk, pty); }); pty.stdout.on("data", function(chunk) { onData(chunk, pty); }); pty.on("exit", function(code) { if (!code) callback(); else callback(new Error("Failed " + options.name + ". Exit code " + code)); }); }); } // Working around PTY.js not having an exit code // Until https://github.com/chjj/pty.js/pull/110#issuecomment-93573223 is merged // wrap script in a function and use subshell to prevent exit 0 skipping echo ß // make sure sudo is called with correct passwd and var script = (DEBUG ? "set -x\n" : "") + 'export TERM=xterm\n' // helps with debian dialog error on apt-get install + 'fcn() {\n' + options.code + '\n}\n' + 'sudo(){ /usr/bin/sudo -S -p "###[sudo] password for %p: " "$@" ; }\n' + 'exit() { if [ "$1" == "0" ]; then echo ß; else echo "exiting with $1"; fi; command exit $1; }\n' + 'fcn "$@" && echo ß\n'; proc.pty(options.bash || "bash", { args: ["-c", script].concat(options.args || []), cwd: options.cwd || null }, function(err, pty) { if (err) return callback(err); var done = false; var buffer = ""; // Pipe the data to the onData function pty.on("data", function(chunk) { buffer += chunk; if (chunk.indexOf("ß") > -1) { done = true; chunk = chunk.replace("ß", ""); } onData(chunk, pty); }); // When process exits call callback pty.on("exit", function(code) { if (!done && !code) code = "E_MISSING_END_MARKER"; if (code) { errorHandler.log("install error", { output: buffer, script: script, name: options.name, args: options.args, code: code }); } if (!code) callback(); else callback(new Error("Failed " + options.name + ". Exit code " + code)); }); }); } /***** Lifecycle *****/ plugin.on("load", function() { load(); }); plugin.on("unload", function() { installChecked = false; installed = false; installCb = arch = platform = undefined; waitForSuccess = false; parentPty = null; }); /***** Register and define API *****/ /** * **/ plugin.freezePublicAPI({ /** * */ get packages() { return packages; }, /** * */ get sessions() { return sessions; }, /** * */ get installed() { return installed; }, /** * */ get checked() { return installChecked; }, /** * */ get ptyEnabled() { return installChecked && c9.platform != "win32"; }, /** * @ignore */ get waitForSuccess() { return waitForSuccess; }, set waitForSuccess(v) { waitForSuccess = v; if (!v) emit("success"); }, _events: [ /** * @event beforeStart */ "beforeStart", /** * @event start */ "start", /** * @event stop */ "stop", /** * @event each */ "each" ], /** * */ isInstalled: isInstalled, /** * */ reinstall: reinstall, /** * */ createSession: createSession, /** * */ addPackageManager: addPackageManager, /** * */ removePackageManager: removePackageManager, /** * */ addPackageManagerAlias: addPackageManagerAlias, /** * */ ptyExec: ptyExec, /** * @ignore */ $setPtyExec: function(v) { if (options.cli) parentPty = v; } }); register(null, { installer: plugin }); } });