Add aliasing and flushing

pull/117/merge
Lennart kats 2015-06-20 18:52:44 +00:00
rodzic f591d815c8
commit 7545b361f0
15 zmienionych plików z 3446 dodań i 4 usunięć

Wyświetl plik

@ -0,0 +1,101 @@
define(function(require, exports, module) {
"use strict";
main.consumes = ["Plugin", "auth", "ext"];
main.provides = ["api"];
return main;
function main(options, imports, register) {
var Plugin = imports.Plugin;
var auth = imports.auth;
/***** Initialization *****/
var plugin = new Plugin("Ajax.org", main.consumes);
var apiUrl = options.apiUrl || "";
var pid = options.projectId;
var BASICAUTH;
// Set api to ext
imports.ext.api = plugin;
/***** Methods *****/
var REST_METHODS = ["get", "post", "put", "delete", "patch"];
function wrapMethod(urlPrefix, method) {
return function(url, options, callback) {
url = apiUrl + urlPrefix + url;
if (!callback) {
callback = options;
options = {};
}
var headers = options.headers = options.headers || {};
headers.Accept = headers.Accept || "application/json";
options.method = method.toUpperCase();
if (!options.timeout)
options.timeout = 60000;
if (BASICAUTH) {
options.username = BASICAUTH[0];
options.password = BASICAUTH[1];
}
auth.request(url, options, function(err, data, res) {
if (err) {
err = (data && data.error) || err;
err.message = err.message || String(err);
return callback(err, data, res);
}
callback(err, data, res);
});
};
}
function apiWrapper(urlPrefix) {
var wrappers = REST_METHODS.map(wrapMethod.bind(null, urlPrefix));
var wrappedApi = {};
for (var i = 0; i < wrappers.length; i++)
wrappedApi[REST_METHODS[i]] = wrappers[i];
return wrappedApi;
}
var collab = apiWrapper("/collab/" + pid + "/");
var user = apiWrapper("/user/");
var preview = apiWrapper("/preview/");
var project = apiWrapper("/projects/" + pid + "/");
var users = apiWrapper("/users/");
var packages = apiWrapper("/packages/");
var stats = apiWrapper("/stats/");
var settings = apiWrapper("/settings/");
var vfs = apiWrapper("/vfs/");
/***** Register and define API *****/
/**
* Provides C9 API access
* @singleton
**/
plugin.freezePublicAPI({
get apiUrl() { return apiUrl; },
get basicAuth() { throw new Error("Permission Denied"); },
set basicAuth(v) { BASICAUTH = v.split(":"); },
collab: collab,
user: user,
preview: preview,
project: project,
users: users,
packages: packages,
stats: stats,
settings: settings,
vfs: vfs
});
register(null, {
api: plugin
});
}
});

Wyświetl plik

@ -0,0 +1,421 @@
/**
* Provides core functionality to Cloud9. This module sets up the plugin
* system, event system and settings.
*
* @module c9.core
* @main c9.core
*/
define(function(require, module, exports) {
main.consumes = ["Plugin", "ext", "vfs"];
main.provides = ["c9"];
return main;
function main(options, imports, register) {
var Plugin = imports.Plugin;
var vfs = imports.vfs;
/***** Initialization *****/
var plugin = new Plugin("Ajax.org", main.consumes);
var emit = plugin.getEmitter();
emit.setMaxListeners(500);
imports.ext.vfs = imports.vfs;
var loaded = false;
var loggedIn = false;
var isReady = false;
var state = 0;
var STORAGE = 1 << 1;
var NETWORK = 1 << 2;
var PROCESS = 1 << 3;
var LOCAL = 1 << 4;
// Copy configuration settings - To Be Deprecated!
var skipProps = { "consumes": 1, "provides": 1, "install": 1, "name": 1 };
for (var prop in options) {
if (!skipProps[prop])
plugin[prop] = options[prop];
}
var totalLoadTime, startLoadTime;
function load() {
if (loaded) return false;
loaded = true;
loggedIn = parseInt(plugin.uid, 10) > 0;
if (vfs.connection)
setStatus(state | STORAGE | PROCESS);
if (vfs.connected)
setStatus(state | NETWORK);
if (plugin.local)
setStatus(state | LOCAL);
vfs.on("connecting", function() {
emit("connecting");
}, plugin);
vfs.on("disconnect", function(reason) {
setStatus(state & ~STORAGE & ~PROCESS & ~NETWORK);
emit("disconnect");
}, plugin);
vfs.on("connect", function() {
setStatus(state | NETWORK | STORAGE | PROCESS);
emit("connect");
}, plugin);
vfs.on("error", function(message) {
setStatus(state & ~STORAGE & ~PROCESS);
// TODO: Don't display all errors?
if (emit("showerrormessage", message) !== false) {
console.error(
"Error on server",
"Received following error from server:",
JSON.stringify(message.message)
);
}
}, plugin);
vfs.on("message", function(message) {
emit("message", message);
}, plugin);
vfs.on("away", function() {
emit("away");
}, plugin);
vfs.on("back", function() {
emit("back");
}, plugin);
// Before unload
window.addEventListener("beforeunload", beforequit);
// Unload
window.addEventListener("unload", quit);
}
/***** Methods *****/
function setStatus(s) {
state = s;
emit("stateChange", {state: s, last: state});
}
function has(check) {
return (state & check) ? true : false;
}
function ready(){
isReady = true;
emit.sticky("ready");
}
function beforequit(){
emit("beforequit");
}
function quit(){
emit("quit");
}
function toExternalPath(path) {
if (plugin.platform == "win32")
path = path.replace(/^[/]+/, "").replace(/[/]+/g, "\\");
return path;
}
function toInternalPath(path) {
if (plugin.platform == "win32") {
path = path.replace(/[\\/]+/g, "/");
path = path.replace(/^\/*(\w):/, function(_, a) {
return "/" + a.toUpperCase() + ":";
});
}
return path;
}
/***** Lifecycle *****/
plugin.on("load", function(){
load();
});
plugin.on("enable", function(){
});
plugin.on("disable", function(){
});
plugin.on("unload", function(){
loaded = false;
});
/***** Register and define API *****/
/**
* Main c9 object for Cloud9 which holds error handlers
* of the entire application as well as the state for availability of resources
* @singleton
**/
plugin.freezePublicAPI({
/**
* use this constant to see if storage capabilities are currently
* available. This is relevant for {@link fs}.
*
* c9.has(c9.STORAGE); // Will return true if storage is available
*
* @property {Number} STORAGE
* @readonly
*/
STORAGE: STORAGE,
/**
* use this constant to see if network capabilities are currently
* available. This is relevant for {@link net#connect}.
*
* c9.has(c9.NETWORK); // Will return true if network is available
*
* @property {Number} NETWORK
* @readonly
*/
NETWORK: NETWORK,
/**
* use this constant to see if process control capabilities are
* currently available. This is relevant for {@link proc#spawn},
* {@link proc#execFile} and {@link proc#pty}.
*
* c9.has(c9.PROCESS); // Will return true if storage is available
*
* @property {Number} PROCESS
* @readonly
*/
PROCESS: PROCESS,
/**
* use this constant to see if Cloud9 is running locally and the
* local runtime is available.
*
* c9.has(c9.LOCAL); // Will return true if storage is available
*
* @property {Number} LOCAL
* @readonly
*/
LOCAL: LOCAL,
/**
* @property {String} workspaceDir
* @readonly
*/
/**
* @property {Boolean} debug
* @readonly
*/
/**
* @property {Number} sessionId
* @readonly
*/
/**
* @property {String} workspaceId
* @readonly
*/
/**
* @property {Boolean} readonly
* @readonly
*/
/**
* @property {String} projectName
* @readonly
*/
/**
* @property {String} version
* @readonly
*/
/**
* @property {Boolean} hosted
* @readonly
*/
/**
* @property {Boolean} local
* @readonly
*/
/**
* Specifies whether the user is logged in to Cloud9.
* @property {Boolean} loggedIn
* @readonly
*/
get loggedIn(){ return loggedIn; },
/**
* the connection object that manages the connection between Cloud9
* and the workspace server. Cloud9 uses Engine.IO to manage this
* connection.
* @property {Object} connection
* @readonly
*/
get connection(){ return vfs.connection; },
/**
* Specifies whether Cloud9 is connceted to the workspace server
* @property {Boolean} connected
* @readonly
*/
get connected(){ return vfs.connected; },
/**
* a bitmask of the constants {@link c9#NETWORK}, {@link c9#STORAGE},
* {@link c9#PROCESS}, {@link c9#LOCAL}. Use this for complex
* queries.
* See also: {@link c9#has}
*
* @property {Number} status
* @readonly
*/
get status(){ return state; },
/**
* the URL from which Cloud9 is loaded.
* @property {String} location
* @readonly
*/
get location(){ return location && location.href || ""; },
/**
*
*/
get totalLoadTime(){ return totalLoadTime; },
set totalLoadTime(v){ totalLoadTime = v; },
/**
*
*/
get startLoadTime(){ return startLoadTime; },
set startLoadTime(v){ startLoadTime = v; },
/**
*
*/
get isReady(){ return isReady; },
_events: [
/**
* Fires when a javascript exception occurs.
* @event error
* @param {Object} e
* @param {String} e.oldpath
*/
"error",
/**
* Fires when Cloud9 starts connecting to the workspace server.
* @event connecting
*/
"connecting",
/**
* Fires when Cloud9 is connected to the workspace server.
* @event connect
*/
"connect",
/**
* Fires when Cloud9 is permanently disconnected from the
* workspace server.
*
* @event disconnect
*/
"disconnect",
/**
* Fires when Cloud9 receives a message from the workspace server.
* @event message
* @param {String} message the message that is received
*/
"message",
/**
* Fires when Cloud9 is disconnected from the workspace server.
* Cloud9 will try to re-establish the connection with the server
* for a few minutes. When that doesn't happen the disconnect
* event is fired.
* @event away
*/
"away",
/**
* Fires when Cloud9 is reconnected to a pre-existing session
* from which it was temporarily disconnected.
* @event back
*/
"back",
/**
* Fires when there is a connection error
* @event showerrormessage
* @param {String} message the error message to display
*/
"showerrormessage",
/**
* Fires when all plugins have loaded
* @event ready
*/
"ready",
/**
* Fires just before exiting the application.
* @event beforequit
*/
"beforequit"
],
/**
* Send a message to the statefull server
* @param {Object} msg the JSON to send to the client
*/
send: vfs.send,
/**
* Sets the availability of resources. Use bitwise operations to
* set availability of different resources. The default
* resources are {@link c9#NETWORK}, {@link c9#STORAGE},
* {@link c9#PROCESS}, {@link c9#LOCAL}
* @param {Number} status a bitwised & of {@link c9#NETWORK},
* {@link c9#STORAGE}, {@link c9#PROCESS}, {@link c9#LOCAL}
*/
setStatus: setStatus,
/**
* Checks the availability of resources. Use the following constants
* {@link c9#NETWORK}, {@link c9#STORAGE}, {@link c9#PROCESS},
* {@link c9#LOCAL}
* @param {Number} test one of {@link c9#NETWORK}, {@link c9#STORAGE},
* {@link c9#PROCESS}, {@link c9#LOCAL}
*/
has: has,
/**
* This method is called by the boot loader, it triggers the ready
* event.
*
* @private
*/
ready: ready,
/**
* This method is called before exiting cloud9.
*
* @private
*/
beforequit: beforequit,
/**
* This method is called to exit cloud9.
*
* @private
*/
quit: quit,
/**
* Canonicalizes a path to its internal form.
* For example, turns C:\ into /C:/ on Windows.
*/
toInternalPath: toInternalPath,
/**
* Canonicalizes a path to its external form.
* For example, turns /C:/ into C:\ on Windows.
*/
toExternalPath: toExternalPath
});
register(null, {
c9: plugin
});
}
});

Wyświetl plik

@ -0,0 +1,94 @@
/*global describe:false, it:false */
"use client";
require(["lib/architect/architect", "lib/chai/chai"], function (architect, chai) {
var expect = chai.expect;
expect.setupArchitectTest([
{
packagePath: "plugins/c9.core/c9",
startdate: new Date(),
debug: 2,
hosted: true,
local: false
},
"plugins/c9.vfs.client/vfs_client",
"plugins/c9.vfs.client/endpoint",
"plugins/c9.ide.auth/auth",
"plugins/c9.core/api",
"plugins/c9.core/ext",
"plugins/c9.core/http-xhr",
{
consumes: [],
provides: ["auth.bootstrap", "info", "dialog.error"],
setup: expect.html.mocked
},
{
consumes: ["c9", "vfs"],
provides: [],
setup: main
}
], architect);
function main(options, imports, register) {
var c9 = imports.c9;
var vfs = imports.vfs;
describe('c9', function() {
this.timeout(30000);
it('should send proper events during connecting', function(done) {
// var count = 0;
// c9.on("connecting", function c1(){
// count++;
// expect(c9.connecting).to.equal(true);
// expect(c9.connected).to.equal(false);
// expect(c9.has(c9.NETWORK)).to.equal(false);
// c9.off("connecting", c1);
// });
expect(c9.connected).to.equal(false);
c9.once("connect", function c2(){
// expect(count, "Connecting event was not called").to.equal(1);
expect(c9.connected).to.equal(true);
expect(c9.has(c9.NETWORK)).to.equal(true);
done();
});
c9.enable();
});
it('check status settings and getting', function(done) {
c9.setStatus(c9.status & ~c9.STORAGE);
expect(c9.has(c9.STORAGE)).to.equal(false);
c9.setStatus(c9.status | c9.STORAGE);
expect(c9.has(c9.STORAGE)).to.equal(true);
done();
});
it('should send correct events during away', function(done) {
expect(c9.connected).to.equal(true);
expect(c9.has(c9.NETWORK)).to.equal(true);
c9.once("away", function c1(){
expect(c9.connected).to.equal(false);
expect(c9.has(c9.NETWORK)).to.equal(true);
});
c9.once("back", function c1(){
expect(c9.connected).to.equal(true);
expect(c9.has(c9.NETWORK)).to.equal(true);
done();
});
vfs.connection.socket.close();
});
});
onload && onload();
}
});

Wyświetl plik

@ -0,0 +1,30 @@
define(function(require, module, exports) {
"use strict";
plugin.consumes = ["auth"];
plugin.provides = [
"api.client"
];
return plugin;
function plugin(options, imports, register) {
var assert = require("assert");
var createClient = require("frontdoor/lib/api-client");
assert(options.baseUrl, "Option 'baseUrl' is required");
var auth = imports.auth;
var baseUrl = options.baseUrl.replace(/\/$/, "");
var descriptionUrl = options.descriptionUrl || baseUrl + "/api.json";
createClient(descriptionUrl, {
request: auth.request
}, function(err, client) {
register(err, {
"api.client": client
});
});
}
});

Wyświetl plik

@ -0,0 +1,941 @@
define(function(require, exports, module) {
main.consumes = [];
main.provides = ["ext", "Plugin"];
return main;
function main(options, imports, register) {
var Emitter = require("events").EventEmitter;
var plugins = [];
var lut = {};
var manuallyDisabled = {};
var dependencies = {};
var counters = {};
var $id = 1;
/***** Initialization *****/
var plugin = new Plugin("Ajax.org", main.consumes);
var emit = plugin.getEmitter();
var vfs, settings, api;
plugin.__defineSetter__("vfs", function(remote) {
vfs = remote;
delete plugin.vfs;
});
plugin.__defineSetter__("settings", function(remote) {
settings = remote;
settings.on("read", function(){
var s = settings.getNode("state/ext/counters");
for (var type in s) {
counters[type] = s[type.substr(1)];
}
});
delete plugin.settings;
});
plugin.__defineSetter__("api", function(remote) {
api = remote;
delete plugin.api;
});
var eventRegistry = Object.create(null);
/***** Methods *****/
function uid(type, name) {
while (!name || lut[name]) {
if (!counters[type]) counters[type] = 0;
name = type + counters[type]++;
}
if (settings && counters[type])
settings.set("state/ext/counters/@" + type, counters[type]);
return name;
}
function registerPlugin(plugin, loaded) {
if (plugins.indexOf(plugin) == -1)
plugins.push(plugin);
lut[plugin.name] = plugin;
loaded(true);
var deps = plugin.deps;
if (deps) {
deps.forEach(function(dep) {
// if (dep !== plugin.name) throw new Error(dep);
(dependencies[dep]
|| (dependencies[dep] = {}))[plugin.name] = 1;
});
}
emit("register", {plugin: plugin});
}
function unregisterPlugin(plugin, loaded, ignoreDeps, keep) {
if (!plugin.registered)
return;
if (!ignoreDeps && getDependencies(plugin.name).length) {
//@todo this should be moved to whoever is calling this.
// if (!silent)
// util.alert(
// "Could not disable extension",
// "Extension is still in use",
// "This extension cannot be disabled, because it is still in use by the following plugins:<br /><br />"
// + " - " + usedBy.join("<br /> - ")
// + "<br /><br /> Please disable those plugins first.");
return false;
}
if (!keep)
plugins.splice(plugins.indexOf(plugin), 1);
delete lut[plugin.name];
loaded(false, 0);
var deps = plugin.deps;
if (deps && dependencies) {
deps.forEach(function(dep) {
delete dependencies[dep][plugin.name];
});
}
emit("unregister", {plugin: plugin});
}
function getDependencies(pluginName){
var usedBy = [];
// Check for dependencies needing this plugin
if (dependencies) {
var deps = dependencies[pluginName];
if (deps) {
Object.keys(deps).forEach(function(name) {
usedBy.push(name);
});
}
}
return usedBy;
}
function unloadAllPlugins(exclude) {
if (lut.settings)
lut.settings.unload(null, true);
function unload(plugin) {
if (!plugin || exclude && exclude[plugin.name])
return;
var deps = dependencies[plugin.name];
if (deps) {
Object.keys(deps).forEach(function(name) {
unload(lut[name]);
});
}
plugin.unload(null, true);
}
var list = plugins.slice(0);
for (var i = list.length - 1; i >= 0; i--) {
var plugin = list[i];
if (!plugin.loaded) continue;
if (plugin.unload)
unload(plugin);
else
console.warn("Ignoring not a plugin: " + plugin.name);
}
}
function loadRemotePlugin(id, options, callback) {
vfs.extend(id, options, function(err, meta) {
callback(err, meta && meta.api);
});
}
function fetchRemoteApi(id, callback) {
vfs.use(id, {}, function(err, meta) {
callback(err, meta && meta.api);
});
}
function unloadRemotePlugin(id, options, callback) {
if (typeof options == "function") {
callback = options;
options = {};
}
vfs.unextend(id, options, callback);
}
function enablePlugin(name){
if (!lut[name] && !manuallyDisabled[name])
throw new Error("Could not find plugin: " + name);
(lut[name] || manuallyDisabled[name]).load(name);
}
function disablePlugin(name){
if (!lut[name])
throw new Error("Could not find plugin: " + name);
var plugin = lut[name];
if (plugin.unload({ keep: true }) === false)
throw new Error("Failed unloading plugin: " + name);
manuallyDisabled[name] = plugin;
}
/***** Register and define API *****/
/**
* The Cloud9 Extension Manager
* @singleton
*/
plugin.freezePublicAPI({
/**
*
*/
get plugins(){ return plugins.slice(0); },
/**
*
*/
get named(){
var named = Object.create(lut);
for (var name in manuallyDisabled) {
if (!lut[name])
lut[name] = manuallyDisabled[name];
}
return named;
},
_events: [
/**
* Fires when a plugin registers
* @event register
* @param {Object} e
* @param {Plugin} e.plugin the plugin that registers
*/
"register",
/**
* Fires when a plugin unregisters
* @event unregister
* @param {Object} e
* @param {Plugin} e.plugin the plugin that unregisters
*/
"unregister"
],
/**
* Loads a plugin on the remote server. This plugin can be either
* source that we have local, or a path to a file that already
* exists on the server. The plugin provides an api that is returned
* in the callback. The remote plugin format is very simple. Here's
* an example of a Math module:
*
* #### Math Modules
*
* module.exports = function (vfs, options, register) {
* register(null, {
* add: function (a, b, callback) {
* callback(null, a + b);
* },
* multiply: function (a, b, callback) {
* callback(null, a * b);
* }
* });
* };
*
* @param {String} id A unique identifier for this module
* @param {Object} options Options to specify
* @param {String} [options.code] The implementation of a module, e.g. require("text!./my-service.js").
* @param {String} [options.file] An absolute path to a module on the remote disk
* @param {Boolean} [options.redefine] specifying whether to replace an existing module with the same `id`
* @param {Function} callback called when the code has been loaded.
* @param {Error} callback.err The error object if an error has occured.
* @param {Object} callback.api The api the code that loaded defined.
*
*/
loadRemotePlugin: loadRemotePlugin,
/**
*
*/
fetchRemoteApi: fetchRemoteApi,
/**
* Unloads a plugin loaded with loadRemotePlugin
* @param {String} id The unique identifier for this module
* @param {Function} callback
*/
unloadRemotePlugin: unloadRemotePlugin,
/**
*
*/
unloadAllPlugins: unloadAllPlugins,
/**
*
*/
getDependencies: getDependencies,
/**
*
*/
enablePlugin: enablePlugin,
/**
*
*/
disablePlugin: disablePlugin
});
function Plugin(developer, deps) {
var elements = [];
var names = {};
var waiting = {};
var events = [];
var other = [];
var name = "";
var time = 0;
var registered = false;
var loaded = false;
var event = new Emitter();
var disabled = false;
var onNewEvents = {};
var declaredEvents = [];
this.deps = deps;
this.developer = developer;
event.on("newListener", function(type, listener) {
if (!(type in onNewEvents))
return;
var data = onNewEvents[type];
if (data === -1)
event.emit("$event." + type, listener);
else {
listener(onNewEvents[type]);
if (event.listeners(type).indexOf(listener) > -1)
console.trace("Used 'on' instead of 'once' to "
+ "listen to sticky event " + name + "." + type);
}
});
function init(reg) {
registered = reg;
}
/***** Methods *****/
this.getEmitter = function(){
var emit = event.emit.bind(event);
var _self = this;
var sticky = function(name, e, plugin) {
if (plugin) {
_self.on("$event." + name, function(listener){
listener(e);
}, plugin);
onNewEvents[name] = -1;
}
else {
onNewEvents[name] = e;
}
return emit(name, e);
};
function unsticky(name, e) {
delete onNewEvents[name];
}
emit.listeners = event.listeners.bind(event);
emit.setMaxListeners = event.setMaxListeners.bind(event);
emit.sticky = sticky;
emit.unsticky = unsticky;
return emit;
};
this.freezePublicAPI = function(api) {
// Build a list of known events to warn users if they use a
// non-existent event.
if (api._events) {
api._events.forEach(function(name){
declaredEvents[name] = true;
});
delete api._events;
}
// Reverse prototyping of the API
// if (!this.__proto__) {
// modifying __proto__ is very slow on chrome!
Object.keys(api).forEach(function(key) {
var d = Object.getOwnPropertyDescriptor(api, key);
Object.defineProperty(this, key, d);
}, this);
// }
// else {
// api.__proto__ = this.__proto__;
// this.__proto__ = api;
// Object.freeze(api);
// }
if (!baseclass) {
delete this.baseclass;
delete this.freezePublicAPI.baseclass;
delete this.freezePublicAPI;
delete this.setAPIKey;
delete this.getEmitter;
Object.freeze(this);
}
baseclass = false;
return this;
};
var baseclass;
this.baseclass =
this.freezePublicAPI.baseclass = function(){ baseclass = true; };
function getElement(name, callback) {
// remove id's after storing them.
if (!callback) {
// If we run without APF, just return a simple object
if (typeof apf == "undefined")
return {};
if (!names[name]) {
throw new Error("Could not find AML element by name '"
+ name + "'");
}
return names[name];
}
else {
if (names[name]) callback(names[name]);
else {
(waiting[name] || (waiting[name] = [])).push(callback);
}
}
}
function addElement() {
for (var i = 0; i < arguments.length; i++) {
var node = arguments[i];
elements.push(node);
recur(node);
}
function recur(node) {
(node.childNodes || []).forEach(recur);
var id = node.id;
if (!id)
return;
// Delete their global reference
delete window[id];
// delete apf.nameserver.lookup.all[node.id];
// Keep their original name in a lookup table
names[id] = node;
// Set a new unique id
if (node.localName != "page") { // Temp hack, should fix in tabs
node.id = "element" + node.$uniqueId;
apf.nameserver.lookup.all[node.id] = node;
}
// Call all callbacks waiting for this element
if (waiting[id]) {
waiting[id].forEach(function(callback) {
callback(node);
});
delete waiting[id];
}
}
return arguments[0];
}
function addEvent(emitter, type, listener) {
if (!listener.listenerId)
listener.listenerId = $id++;
events.push([emitter.name, type, listener.listenerId]);
}
function addOther(o) {
other.push(o);
}
function initLoad(type, listener) {
if (type == "load") listener();
}
function load(nm, type) {
var dt = Date.now();
if (type) nm = uid(type, nm);
if (nm && !name) name = nm;
event.name = name;
eventRegistry[name] = event;
registerPlugin(this, init);
loaded = true;
event.emit("load");
event.on("newListener", initLoad);
time = Date.now() - dt;
}
function enable() {
emit("enablePlugin", {plugin: this});
event.emit("enable");
disabled = false;
}
function disable() {
emit("disablePlugin", {plugin: this});
event.emit("disable");
disabled = true;
}
function unload(e, ignoreDeps) {
if (!loaded) return;
if (event.emit("beforeUnload", e) === false)
return false;
if (unregisterPlugin(this, init, ignoreDeps, e && e.keep) === false)
return false;
loaded = false;
event.emit("unload", e);
this.cleanUp();
event.off("newListener", initLoad);
setTimeout(function() {
if (eventRegistry[name] == event && !loaded) {
delete eventRegistry[name];
}
});
}
function cleanUp(keepElements) {
if (!keepElements) {
// Loop through elements
elements.forEach(function(element) {
element.destroy(true, true);
});
elements = [];
names = {};
waiting = [];
}
// Loop through events
events.forEach(function(eventRecord) {
var event = eventRegistry[eventRecord[0]];
if (!event) return; // this happens with mock plugins during testing
var type = eventRecord[1];
var id = eventRecord[2];
var _events = event._events;
var eventList = _events && _events[type];
if (typeof eventList == "function") {
if (eventList.listenerId == id)
event.off(type, eventList);
} else if (Array.isArray(eventList)) {
eventList.some(function(listener) {
if (listener.listenerId != id) return;
event.off(type, listener);
return true;
});
}
});
events = [];
// Loop through other
other.forEach(function(o) {
o();
});
other = [];
onNewEvents = {};
}
function setAPIKey(apikey){
// Validate Key
if (!apikey || !apikey.match(/[\w+]{27}=/))
throw new Error("Invalid API key");
return {
getPersistentData: getPersistentData.bind(this, apikey),
setPersistentData: setPersistentData.bind(this, apikey)
};
}
function getPersistentData(apiKey, context, callback){
var type;
if (!apiKey)
throw new Error("API Key not set. Please call plugin.setAPIKey(options.key);");
if (context == "user") type = "user";
else if (context == "workspace") type = "project";
else throw new Error("Unsupported context: " + context);
api[type].get("persistent/" + apiKey, function(err, data){
if (err) return callback(err);
try { callback(null, JSON.stringify(data)); }
catch(e){ return callback(e); }
});
}
function setPersistentData(apiKey, context, data, callback){
var type;
if (!apiKey)
throw new Error("API Key not set. Please call plugin.setAPIKey(options.key);");
if (context == "user") type = "user";
else if (context == "workspace") type = "project";
else throw new Error("Unsupported context: " + context);
api[type].put("persistent/" + apiKey, { data: JSON.stringify(data) }, callback);
}
/***** Register and define API *****/
this.baseclass();
/**
* Base class for all Plugins of Cloud9. A Cloud9 Plugin is
* an instance of the Plugin class. This class offers ways to
* describe the API it offers as well as ways to clean up the
* objects created during the lifetime of the plugin.
*
* Note that everything in Cloud9 is a plugin. This means that
* your plugins have the exact same possibilities as any other part
* of Cloud9. When building plugins you can simply create additional
* features or replace existing functionality, by turning off the
* core plugins and preference of your own.
*
* Check out the [template](http://example.org/template) for the
* recommended way of building a plugin. All Cloud9 Core Plugins
* are written in this way.
*
* Our goal has been to create an extensible system that works both
* in the browser as well as in Node.js. The Cloud9 CLI uses the
* exact same plugin structure as the Cloud9 in the browser. The
* same goes for many of the Cloud9 platform services. We focussed
* on making the system easy to use by making sure you can create
* plugins using simple javascript, html and css. The plugins you
* create will become available as services inside the Cloud9
* plugin system. This means that other plugins can consume your
* functionality and importantly you can replace existing services
* by giving your plugin the same service name.
*
* The plugin class will allow you to specify an API that is
* "frozen" upon definition (See {@link Object#freeze}. This means
* that once your plugin's API is defined the object's interface
* cannot be changed anymore. Property gettters and setters will
* still work and events can still be set/unset. Having immutable
* APIs will prevent users from common hacks that devs often use
* in the javascript community, adding new properties to objects.
* It is our aim that this will increase the stability of the system
* as a whole while introducing foreign plugins to it.
*
* The event flow of a basic plugin is as follows:
*
* * {@link #event-load} - *The plugin is loaded (this can happen multiple times to the same plugin instance)*
* * {@link #event-unload} - *The plugin is unloaded*
*
* #### User Actions:
*
* * {@link #event-disable} - *The plugin is disabled*
* * {@link #event-enable} - *The plugin is enabled*
*
* The following example shows how to implement a basic plugin:
*
* define(function(require, exports, module) {
* main.consumes = ["dependency"];
* main.provides = ["myplugin"];
* return main;
*
* function main(options, imports, register) {
* var dependency = imports.dependency;
*
* var plugin = new Plugin("(Company) Name", main.consumes);
* var emit = plugin.getEmitter();
*
* plugin.on("load", function(e) {
* // Create a command, menu item, etc
* });
* plugin.on("unload", function(e) {
* // Any custom unload code (most things are cleaned up automatically)
* });
*
* function doSomething(){
* }
*
* plugin.freezePublicAPI({
* doSomething : doSomething
* });
* }
* });
*
* @class Plugin
* @extends Object
*/
/**
* @constructor
* Creates a new Plugin instance.
* @param {String} developer The name of the developer of the plugin
* @param {String[]} deps A list of dependencies for this
* plugin. In most cases it's a reference to main.consumes.
*/
this.freezePublicAPI({
_events: [
/**
* Fires when the plugin is loaded
* @event load
*/
"load",
/**
* Fires before the plugin is unloaded
* @event beforeUnload
*/
"beforeUnload",
/**
* Fires when the plugin is unloaded
* @event unload
*/
"unload",
/**
* Fires when the plugin is enabled
* @event enable
*/
"enable",
/**
* Fires when the plugin is disabled
* @event disable
*/
"disable",
/**
* Fires any time a new listener is added.
*
* plugin.on('newListener', function (event, listener) {
* // new listener added
* });
*
* @event newListener
*/
"newListener",
/**
* Fires any time a listener is removed.
*
* plugin.on('removeListener', function (event, listener) {
* // listener is removed
* });
*
* @event removeListener
*/
"removeListener"
],
/**
* @property {Boolean} registered Specifies whether the plugin is registered
* @readonly
*/
get registered(){ return registered; },
/**
* @property {Date} time The time when the plugin was registered
* @readonly
*/
get time(){ return time; },
/**
* @property {Boolean} enabled Specifies whether the plugin is enabled
* @readonly
*/
get enabled(){ return !disabled; },
/**
* @property {Boolean} loaded whether the plugin is loaded.
* This happens by calling load or setting the name.
* @readonly
*/
get loaded(){ return loaded; },
/**
* @property {String} name The name of the plugin
*/
get name(){ return name; },
set name(val) {
if (name == val)
return;
if (!name) {
name = val;
this.load();
}
else
throw new Error("Plugin Name Exception");
},
/**
* Copies all methods and properties from `api` and then freezes
* the plugin to prevent further changes to its API.
*
* @method freezePublicAPI
* @param {Object} api simple object that defines the API
**/
/**
* Fetches the event emitter for this plugin. After freezing the
* public API of this plugin, this method will no longer be
* available.
*
* Note that there is a limit to the amount of event listeners
* that can be placed on a plugin. This is to protect developers
* from leaking event listeners. When this limit is reached
* an error is thrown. Use the emit.setMaxListeners
* to change the amount of listeners a plugin can have:
*
* var emit = plugin.getEmitter();
* emit.setMaxListeners(100);
*
* @method getEmitter
* @return {Function}
* @return {String} return.eventName The name of the event to emit
* @return {Object} return.eventObject The data passed as the first argument to all event listeners
* @return {Boolean} return.immediateEmit Specifies whether
* to emit the event to event handlers that are set after
* emitting this event. This is useful for instance when you
* want to have a "load" event that others add listeners to
* after the load event is called.
**/
/**
* Fetches a UI element. You can use this method both sync and async.
* @param {String} name the id of the element to fetch
* @param {Function} [callback] the function to call when the
* element is available (could be immediately)
**/
getElement: getElement,
/**
* Register an element for destruction during the destroy phase of
* this plugin's lifecycle.
* @param {AMLElement} element the element to register
**/
addElement: addElement,
/**
* Register an event for destruction during the destroy phase of
* this plugin's lifecycle.
* @param {Array} ev Array containing three elements:
* object, event-name, callback
**/
addEvent: addEvent,
/**
* Register a function that is called during the destroy phase of
* this plugin's lifecycle.
* @param {Function} o function called during the destroy phase
* of this plugin's lifecycle
**/
addOther: addOther,
/**
* Loads this plugin into Cloud9
**/
load: load,
/**
* @ignore Enable this plugin
**/
enable: enable,
/**
* @ignore Disable this plugin
**/
disable: disable,
/**
* Unload this plugin from Cloud9
**/
unload: unload,
/**
* Removes all elements, events and other items registered for
* cleanup by this plugin
*/
cleanUp: cleanUp,
/**
*
*/
setAPIKey: setAPIKey,
/**
* Adds an event handler to this plugin. Note that unlike the
* event implementation you know from the browser, you are
* able to add the same listener multiple times to listen to
* the same event.
*
* @param {String} name The name of this event
* @param {Function} callback The function called when the event is fired
* @param {Plugin} plugin The plugin that is responsible
* for the event listener. Make sure to always add a reference
* to a plugin when adding events in order for the listeners
* to be cleaned up when the plugin unloads. If you forget
* this you will leak listeners into Cloud9.
* @fires newListener
**/
on: function(eventName, callback, plugin){
// if (!declaredEvents[eventName])
// console.warn("Missing event description or unknown event '" + eventName + "' for plugin '" + name + "'", new Error().stack);
event.on(eventName, callback, plugin);
},
/**
* Adds an event handler to this plugin and removes it after executing it once
* @param {String} name the name of this event
* @param {Function} callback the function called when the event is fired
**/
once: function(eventName, callback){
// if (!declaredEvents[eventName])
// console.warn("Missing event description or unknown event '" + eventName + "' for plugin '" + name + "'");
event.once(eventName, callback);
},
/**
* Removes an event handler from this plugin
* @param {String} name the name of this event
* @param {Function} callback the function previously registered as event handler
* @fires removeListener
**/
off: event.removeListener.bind(event),
/**
* Returns an array of listeners for an event specified by `name`
* @param {String} name the name of this event
*/
listeners: event.listeners.bind(event)
});
}
register(null, {
ext: plugin,
Plugin: Plugin
});
}
});

Wyświetl plik

@ -0,0 +1,218 @@
/*global describe:false, it:false */
"use client";
require(["lib/architect/architect", "lib/chai/chai"], function (architect, chai) {
var expect = chai.expect;
expect.setupArchitectTest([
"plugins/c9.core/ext",
{
consumes: ["ext", "Plugin"],
provides: [],
setup: main
}
], architect);
function main(options, imports, register) {
var ext = imports.ext;
var Plugin = imports.Plugin;
describe('plugin', function() {
this.timeout(1000);
it('should expose the constructor arguments', function(done) {
var deps = [1,2];
var plugin = new Plugin("Ajax.org", deps);
expect(plugin.developer).to.equal("Ajax.org");
expect(plugin.deps).to.equal(deps);
done();
});
it('should only allow setting the api once', function(done) {
var plugin = new Plugin("Ajax.org", []);
var func = function(a) {};
plugin.freezePublicAPI({
test: func
});
plugin.test = "nothing";
expect(plugin.test).to.equal(func);
done();
});
it('should give access to the event emitter before freezing the api', function(done) {
var plugin = new Plugin("Ajax.org", []);
var emit = plugin.getEmitter();
plugin.freezePublicAPI({});
plugin.on("test", function(){ done(); })
emit("test");
});
it('should not give access to the event emitter after freezing the api', function(done) {
var plugin = new Plugin("Ajax.org", []);
plugin.freezePublicAPI({});
expect(plugin.getEmitter).to.not.ok
done();
});
it('should call load event when name is set', function(done) {
var plugin = new Plugin("Ajax.org", []);
plugin.on("load", function(){ done() });
plugin.name = "test";
});
it('should only allow the name to be set once', function(done) {
var plugin = new Plugin("Ajax.org", []);
plugin.name = "test";
expect(function(){ plugin.name = "test2";}).to.throw("Plugin Name Exception");
done();
});
it('should call sticky event when adding handler later', function(done) {
var plugin = new Plugin("Ajax.org", []);
var emit = plugin.getEmitter();
plugin.name = "test";
emit.sticky("ready");
plugin.on("ready", done);
});
it('should call sticky event for each plugin added', function(done) {
var plugin = new Plugin("Ajax.org", []);
var plugin2 = new Plugin("Ajax.org", []);
var plugin3 = new Plugin("Ajax.org", []);
var emit = plugin.getEmitter();
plugin.name = "test";
plugin2.name = "test2";
plugin3.name = "test3";
emit.sticky("create", {}, plugin2);
emit.sticky("create", {}, plugin3);
var z = 0;
plugin.on("create", function(){
if (++z == 2) done();
else if (z > 2)
throw new Error("Called too often initially");
});
});
it('should call sticky event only for the non-unloaded plugins', function(done) {
var plugin = new Plugin("Ajax.org", []);
var plugin2 = new Plugin("Ajax.org", []);
var plugin3 = new Plugin("Ajax.org", []);
var emit = plugin.getEmitter();
plugin.name = "test";
plugin2.name = "test2";
plugin3.name = "test3";
emit.sticky("create", {}, plugin2);
emit.sticky("create", {}, plugin3);
var z = 0, q = 0, timer;
plugin.on("create", function(){
if (++z == 2) {
plugin3.unload();
plugin.on("create", function(){
++q;
clearTimeout(timer);
timer = setTimeout(function(){
if (q == 1) done();
else throw new Error("Called too often after unload");
})
});
}
else if (z > 2) {
throw new Error("Called too often initially");
}
});
});
it('should call unload event when unload() is called', function(done) {
var plugin = new Plugin("Ajax.org", []);
var loaded = false;
plugin.on("unload", function error(){
if (!loaded)
throw new Error("shouldn't call unload");
done();
});
plugin.unload();
loaded = true;
plugin.load();
plugin.unload();
});
it('should call disable event when disable() is called', function(done) {
var plugin = new Plugin("Ajax.org", []);
plugin.on("disable", function(){ done() });
plugin.enable();
plugin.disable();
});
it('should call enable event when enable() is called', function(done) {
var plugin = new Plugin("Ajax.org", []);
plugin.on("enable", function(){ done() });
plugin.enable();
});
it('should destroy all assets when it\'s unloaded', function(done) {
var plugin = new Plugin("Ajax.org", []);
var count = 0;
function check(){
if (++count == 4)
done();
}
var el1 = {destroy: check, childNodes: []};
var el2 = {destroy: check, childNodes: []};
plugin.load();
plugin.on("load", check);
expect(plugin.listeners("load").length).to.equal(1);
plugin.addElement(el1, el2);
plugin.addEvent(plugin, "load", check);
plugin.addOther(check);
plugin.unload();
if (!plugin.listeners("load").length)
check();
});
//@todo haven't tested getElement
});
describe('ext', function() {
it('should register a plugin when the plugin\'s name is set', function(done) {
var plugin = new Plugin("Ajax.org", []);
expect(plugin.registered).to.equal(false);
ext.on("register", function reg(){
expect(plugin.registered).to.equal(true);
done();
ext.off("register", reg);
})
plugin.name = "test";
});
it('should call the unregister event when the plugin is unloaded', function(done) {
var plugin = new Plugin("Ajax.org", []);
plugin.name = "test";
ext.on("unregister", function unreg(){
expect(plugin.registered).to.equal(false);
done();
ext.off("register", unreg);
})
plugin.unload();
});
it('should return false on unload() when the dependency tree is not in check', function(done) {
var plugin = new Plugin("Ajax.org", []);
plugin.name = "test";
var plugin2 = new Plugin("Ajax.org", ["test"]);
plugin2.name = "test2";
expect(plugin.unload()).to.equal(false);
done();
});
});
onload && onload();
}
});

Wyświetl plik

@ -0,0 +1,55 @@
"use strict";
main.consumes = [];
main.provides = ["http"];
module.exports = main;
function main(options, imports, register) {
var request = require("frontdoor/lib/http_node");
/**
* Simple API for performing HTTP requests.
*
* Example:
*
* http.request("http://www.c9.io", function(err, data){
* if (err) throw err;
* console.log(data);
* });
*
* @singleton
*/
var plugin = {
/**
* Performs an HTTP request
*
* @param {String} url Target URL for the HTTP request
* @param {Object} [options] Request options
* @param {String} [options.method] HTTP method (default=GET)
* @param {Object} [options.query] URL query parameters as an object
* @param {String} [options.body] HTTP body for PUT and POST
* @param {Object} [options.headers] Request headers
* @param {Object} [options.username] Basic auth username
* @param {Object} [options.password] Basic auth password
* @param {Number} [options.timeout] Timeout in ms (default=10000)
* @param {String} [options.contentType='application/x-www-form-urlencoded; charset=UTF-8'] Content type of sent data
* @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server
* @param {Function} [options.progress] Progress event handler
* @param {Function} [options.progress.loaded] The amount of bytes downloaded/uploaded.
* @param {Function} [options.progress.total] The total amount of bytes to download/upload.
* @param {Function} callback Called when the request returns.
* @param {Error} callback.err Error object if an error occured.
* @param {String} callback.data The data received.
* @param {Object} callback.res
* @param {String} callback.res.body The body of the response message.
* @param {Number} callback.res.status The status of the response message.
* @param {Object} callback.res.headers The headers of the response message.
*/
request: request
};
register(null, {
http: plugin
});
}

Wyświetl plik

@ -0,0 +1,268 @@
define(function(require, module, exports) {
"use strict";
var XHR = XMLHttpRequest; // Grab constructor early so it can't be spoofed
main.consumes = ["Plugin"];
main.provides = ["http"];
return main;
function main(options, imports, register) {
var URL = require("url");
var qs = require("querystring");
var plugin = new imports.Plugin("Ajax.org", main.consumes);
var debug = options.debug !== false;
function request(url, options, callback) {
if (!callback)
return request(url, {}, options);
if (typeof options == "string")
return request(url, {method: options}, callback);
var method = options.method || "GET";
var headers = options.headers || {};
var body = options.body || "";
var contentType = options.contentType
|| "application/x-www-form-urlencoded; charset=UTF-8";
var timeout = options.hasOwnProperty("timeout") ? options.timeout : 10000;
var async = options.sync !== true;
var parsedUrl = parseUrl(url, options.query);
if (contentType === "application/json")
headers.Accept = headers.Accept || "application/json";
if (options.username) {
headers.Authorization = "Basic " + btoa(options.username + ":" + options.password);
}
var xhr = new XHR();
if (options.overrideMimeType)
xhr.overrideMimeType = options.overrideMimeType;
// From MDN: Note: You need to add the event listeners before
// calling open() on the request. Otherwise the progress events
// will not fire.
if (options.progress) {
var obj = method == "PUT" ? xhr.upload : xhr;
obj.onprogress = function(e) {
if (e.lengthComputable)
options.progress(e.loaded, e.total);
};
}
xhr.open(method, URL.format(parsedUrl), async);
headers["Content-Type"] = contentType;
for (var header in headers)
xhr.setRequestHeader(header, headers[header]);
// encode body
if (typeof body == "object") {
if (contentType.indexOf("application/json") === 0) {
try {
body = JSON.stringify(body);
} catch (e) {
return done(new Error("Could not serialize body as json"));
}
}
else if (contentType.indexOf("application/x-www-form-urlencoded") === 0) {
body = qs.stringify(body);
}
else if (["[object File]", "[object Blob]"].indexOf(Object.prototype.toString.call(body)) > -1) {
// pass as is
xhr.overrideMimeType = body.type;
}
else {
body = body.toString();
}
}
var timer;
var timedout = false;
if (timeout) {
timer = setTimeout(function() {
timedout = true;
xhr.abort();
var err = new Error("Timeout");
err.code = "ETIMEOUT";
done(err);
}, timeout);
}
xhr.send(body || "");
var abort = xhr.abort;
xhr.abort = function(){
clearTimeout(timer);
abort.call(xhr);
};
xhr.onload = function(e) {
var res = {
body: xhr.responseText,
status: xhr.status,
headers: parseHeaders(xhr.getAllResponseHeaders())
};
var data = xhr.responseText;
var contentType = options.overrideMimeType || res.headers["content-type"] || "";
if (contentType.indexOf("application/json") === 0) {
try {
data = JSON.parse(data);
} catch (e) {
return done(e);
}
}
if (this.status > 299) {
var err = new Error(xhr.responseText);
err.code = xhr.status;
if (debug)
console.error("HTTP error " + this.status + ": " + xhr.responseText);
return done(err, data, res);
}
done(null, data, res);
};
xhr.onerror = function(e) {
if (typeof XMLHttpRequestProgressEvent == "function" && e instanceof XMLHttpRequestProgressEvent) {
// No useful information in this object.
// Possibly CORS error if code was 0.
var err = new Error("Failed to retrieve resource from " + parsedUrl.host);
err.cause = e;
err.code = e.target.status;
return done(err);
}
e.code = e.target.status;
done(e);
};
var called = false;
function done(err, data, res) {
timer && clearTimeout(timer);
if (called) return;
called = true;
callback(err, data, res);
}
return xhr;
}
var callbackId = 1;
function jsonP(url, options, callback) {
if (!callback) return jsonP(url, {}, options);
var cbName = "__josnpcallback" + callbackId++;
var callbackParam = options.callbackParam || "callback";
var parsedUrl = parseUrl(url, options.query);
parsedUrl.query[callbackParam] = cbName;
window[cbName] = function(json) {
delete window.cbName;
callback(json);
};
var head = document.getElementsByTagName("head")[0] || document.documentElement;
var s = document.createElement('script');
s.src = URL.format(parsedUrl);
head.appendChild(s);
s.onload = s.onreadystatechange = function(_, isAbort) {
if (isAbort || !s.readyState || s.readyState == "loaded" || s.readyState == "complete") {
head.removeChild(s);
s = s.onload = s.onreadystatechange = null;
}
};
}
function parseUrl(url, query) {
query = query || {};
var parsedUrl = URL.parse(url, true);
for (var key in query)
parsedUrl.query[key] = query[key];
delete parsedUrl.search;
return parsedUrl;
}
function parseHeaders(headerString) {
return headerString
.split('\u000d\u000a')
.reduce(function(headers, headerPair) {
var index = headerPair.indexOf('\u003a\u0020');
if (index > 0) {
var key = headerPair.substring(0, index).toLowerCase();
var val = headerPair.substring(index + 2);
headers[key] = val;
}
return headers;
}, {});
}
/**
* Simple API for performing HTTP requests.
*
* Example:
*
* http.request("http://www.c9.io", function(err, data) {
* if (err) throw err;
* console.log(data);
* });
*
* @singleton
*/
plugin.freezePublicAPI({
/**
* Performs an HTTP request
*
* @param {String} url Target URL for the HTTP request
* @param {Object} [options] Request options
* @param {String} [options.method] HTTP method (default=GET)
* @param {Object} [options.query] URL query parameters as an object
* @param {String|Object} [options.body] HTTP body for PUT and POST
* @param {Object} [options.headers] Request headers
* @param {Object} [options.username] Basic auth username
* @param {Object} [options.password] Basic auth password
* @param {Number} [options.timeout] Timeout in ms (default=10000)
* @param {String} [options.contentType='application/x-www-form-urlencoded; charset=UTF-8'] Content type of sent data
* @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server
* @param {Function} [options.progress] Progress event handler
* @param {Function} [options.progress.loaded] The amount of bytes downloaded/uploaded.
* @param {Function} [options.progress.total] The total amount of bytes to download/upload.
* @param {Function} callback Called when the request returns.
* @param {Error} callback.err Error object if an error occured.
* @param {String} callback.data The data received.
* @param {Object} callback.res
* @param {String} callback.res.body The body of the response message.
* @param {Number} callback.res.status The status of the response message.
* @param {Object} callback.res.headers The headers of the response message.
*/
request: request,
/**
* Performs a JSONP request
*
* @param {String} url Target URL for the JSONP request
* @param {Object} [options] Request options
* @param {String} [options.callbackParam="callback"] name of the callback query parameter
* @param {Object} [options.query] URL query parameters as an object
* @param {Function} callback Called when the request returns.
* @param {Error} callback.err Error object if an error occured.
* @param {String} callback.data The data received.
* @param {Object} callback.res
* @param {String} callback.res.body The body of the response message.
* @param {Number} callback.res.status The status of the response message.
*/
jsonP: jsonP
});
register(null, {
http: plugin
});
}
});

Wyświetl plik

@ -0,0 +1,36 @@
/*global describe, it */
"use client";
require(["lib/architect/architect", "lib/chai/chai"], function (architect, chai) {
var expect = chai.expect;
expect.setupArchitectTest([
"plugins/c9.core/ext",
"plugins/c9.core/http-xhr",
{
consumes: ["http"],
provides: [],
setup: main
}
], architect);
function main(options, imports, register) {
var http = imports.http;
describe('http', function() {
it('should request a url via XHR', function(done) {
http.request("plugins/c9.core/http-xhr.js", function(err, data, res) {
if (err) throw (err.message || err);
expect(res.status).to.equal(200);
expect(data.indexOf("define(function(require, module, exports) {"))
.to.equal(0);
done();
});
});
});
onload && onload();
}
});

Wyświetl plik

@ -0,0 +1,31 @@
"use strict";
var debug = require("debug")("node:defaults");
main.consumes = [];
main.provides = ["node.defaults"];
module.exports = main;
/**
* initialize gloabl nodejs settings
*/
function main(options, imports, register) {
if ("tls_reject_unauthorized" in options) {
debug("setting NODE_TLS_REJECT_UNAUTHORIZED to %s", options.tls_reject_unauthorized);
process.env.NODE_TLS_REJECT_UNAUTHORIZED = options.tls_reject_unauthorized;
}
if (options.maxSockets) {
debug("setting maxSockets to %s", options.maxSockets);
require("http").globalAgent.maxSockets = options.maxSockets;
require("https").globalAgent.maxSockets = options.maxSockets;
}
register(null, {
"node.defaults": {}
});
}

Wyświetl plik

@ -0,0 +1,693 @@
define(function(require, exports, module) {
main.consumes = [
"c9", "ui", "Plugin", "fs", "proc", "api", "info", "ext", "util"
];
main.provides = ["settings"];
return main;
function main(options, imports, register) {
var c9 = imports.c9;
var ext = imports.ext;
var ui = imports.ui;
var Plugin = imports.Plugin;
var fs = imports.fs;
var proc = imports.proc;
var api = imports.api;
var info = imports.info;
var util = imports.util;
var _ = require("lodash");
var join = require("path").join;
/***** Initialization *****/
var plugin = new Plugin("Ajax.org", main.consumes);
var emit = plugin.getEmitter();
// Give the info, ext plugin a reference to settings
info.settings = plugin;
ext.settings = plugin;
// We'll have a lot of listeners, so upping the limit
emit.setMaxListeners(10000);
var resetSettings = options.reset || c9.location.match(/reset=([\w\|]*)/) && RegExp.$1;
var develMode = c9.location.indexOf("devel=1") > -1;
var debugMode = c9.location.indexOf("debug=2") > -1;
var testing = options.testing;
var debug = options.debug;
// do not leave reset= in url
if (resetSettings && window.history)
window.history.pushState(null, null, location.href.replace(/reset=([\w\|]*)/,""));
var TEMPLATE = options.template || { user: {}, project: {}, state: {} };
var INTERVAL = 1000;
var PATH = {
"project" : c9.toInternalPath(options.projectConfigPath || "/.c9") + "/project.settings",
"user" : c9.toInternalPath(options.userConfigPath || "~/.c9") + "/user.settings",
"state" : c9.toInternalPath(options.stateConfigFilePath || (options.stateConfigPath || "/.c9") + "/state.settings")
};
var KEYS = Object.keys(PATH);
var saveToCloud = {};
var model = {};
var cache = {};
var diff = 0; // TODO should we allow this to be undefined and get NaN in timestamps?
var userData;
var inited = false;
function loadSettings(json) {
if (!json) {
// Load from TEMPLATE
if (options.settings == "defaults" || testing)
json = TEMPLATE;
// Load from parsed settings in the index file
else if (options.settings) {
json = options.settings;
if (debugMode)
json.state = localStorage["debugState" + c9.projectName];
for (var type in json) {
if (typeof json[type] == "string") {
if (json[type].charAt(0) == "<") {
json[type] = TEMPLATE[type];
}
else {
try {
json[type] = JSON.parse(json[type]);
} catch (e) {
json[type] = TEMPLATE[type];
}
}
}
}
}
if (!json) {
var info = {};
var count = KEYS.length;
KEYS.forEach(function(type) {
fs.readFile(PATH[type], function(err, data) {
try {
info[type] = err ? {} : JSON.parse(data);
} catch (e) {
console.error("Invalid Settings Read for ",
type, ": ", data);
info[type] = {};
}
if (--count === 0)
loadSettings(info);
});
});
return;
}
}
read(json);
events();
if (resetSettings)
saveToFile();
KEYS.forEach(function(type) {
var node = model[type];
if (node)
cache[type] = JSON.stringify(node);
});
model.loaded = true;
}
/***** Methods *****/
var dirty, timer;
function checkSave(){
if (dirty)
saveToFile();
}
function startTimer(){
if (c9.readonly) return;
clearInterval(timer);
timer = setInterval(checkSave, INTERVAL);
}
function save(force, sync) {
dirty = true;
if (force) {
saveToFile();
startTimer();
}
}
function saveToFile(sync) {
if (c9.readonly || !plugin.loaded)
return;
if (c9.debug)
console.log("Saving Settings...");
emit("write", { model : model });
model.time = new Date().getTime();
if (develMode) {
dirty = false;
return;
}
saveModel(sync);
}
function saveModel(forceSync) {
if (c9.readonly || !c9.has(c9.NETWORK)) return;
if (model.loaded && !testing) {
KEYS.forEach(function(type) {
var node = model[type];
if (!node) return;
// Get XML string
var json = util.stableStringify(node, 0, " ");
if (cache[type] == json) return; // Ignore if same as cache
// Set Cache
cache[type] = json;
// Debug mode
if (debugMode && type == "state") {
localStorage["debugState" + c9.projectName] = json;
return;
}
// Detect whether we're in standalone mode
var standalone = !options.hosted;
if (standalone || type == "project") {
fs.writeFile(PATH[type], json, forceSync, function(err){});
if (standalone && !saveToCloud[type])
return; // We're done
}
var addPid = type !== "user"
? "/" + info.getWorkspace().id
: "";
// Save settings in persistent API
api.settings.put(type + addPid, {
body: { settings: json },
sync: forceSync
}, function (err) {});
});
}
dirty = false;
}
function read(json, isReset) {
try {
if (testing) throw "testing";
KEYS.forEach(function(type) {
if (json[type])
model[type] = json[type];
});
if (resetSettings) {
var query = (resetSettings == 1
? "user|state" : resetSettings).split("|");
query.forEach(function(type) {
model[type] = TEMPLATE[type];
});
}
} catch (e) {
KEYS.forEach(function(type) {
model[type] = TEMPLATE[type];
});
}
if (!c9.debug) {
try {
emit("read", {
model: model,
ext: plugin,
reset: isReset
});
} catch (e) {
fs.writeFile(PATH.project
+ ".broken", JSON.stringify(json), function(){});
KEYS.forEach(function(type) {
model[type] = TEMPLATE[type];
});
emit("read", {
model: model,
ext: plugin,
reset: isReset
});
}
}
else {
emit("read", {
model: model,
ext: plugin,
reset: isReset
});
}
if (inited)
return;
inited = true;
plugin.on("newListener", function(type, cb) {
if (type != "read") return;
if (c9.debug || debug) {
cb({model : model, ext : plugin});
}
else {
try {
cb({ model : model, ext : plugin });
}
catch (e) {
console.error(e.message, e.stack);
}
}
});
}
var hasSetEvents;
function events() {
if (hasSetEvents) return;
hasSetEvents = true;
startTimer();
c9.on("beforequit", function(){
emit("write", { model: model, unload: true });
saveModel(true); //Forcing sync xhr works in chrome
}, plugin);
c9.on("stateChange", function(e) {
if (e.state | c9.NETWORK && e.last | c9.NETWORK)
saveToFile(); //Save to file
}, plugin);
}
function migrate(pathFrom, pathTo) {
var idx = pathFrom.lastIndexOf("/");
var nodeParent = getNode(pathFrom.substr(0, idx));
var name = pathFrom.substr(idx + 1);
var nodeFrom = nodeParent && nodeParent[name];
if (!nodeFrom) return;
// Remove node
delete nodeParent[name];
// Create new node
var nodeTo = getNode(pathTo) || setNode(pathTo, {}) && getNode(pathTo);
// Move attributes
for (var prop in nodeFrom) {
nodeTo[prop] = nodeFrom[prop];
}
}
function setDefaults(path, attr) {
var node = getNode(path) || set(path, {}, true, true) && getNode(path);
var changed;
attr.forEach(function(a) {
var name = "@" + a[0];
if (!node.hasOwnProperty(name)) {
node[name] = a[1];
emit(path + "/" + name, a[1]);
changed = true;
}
});
if (changed)
emit(path);
}
function update(type, json, ud){
// Do nothing if they are the same
if (_.isEqual(model[type], json))
return;
userData = ud;
// Compare key/values (assume source has same keys as target)
(function recur(source, target, base){
for (var prop in source) {
if (prop == "json()") {
setJson(base, source[prop]);
}
else if (typeof source[prop] == "object") {
if (!target[prop]) target[prop] = {};
recur(source[prop], target[prop], join(base, prop));
}
else if (source[prop] != target[prop]) {
set(join(base, prop), source[prop]);
}
}
})(json, model[type], type);
userData = null;
}
function setNode(query, value) {
return set(query, value, true);
}
function set(query, value, isNode, isDefault, checkDefined) {
if (!inited && !isDefault) return false;
var parts = query.split("/");
var key = parts.pop();
if (!isNode && key.charAt(0) !== "@") {
parts.push(key);
key = "json()";
}
var hash = model;
if (!parts.every(function(part) {
if (!hash[part] && checkDefined) return false;
hash = hash[part] || (hash[part] = {});
return hash;
})) {
console.warn("Setting non defined query: ", query);
return false;
}
if (hash[key] === value)
return;
hash[key] = value;
// Tell everyone this property changed
emit(parts.join("/"));
// Tell everyone it's parent changed
emit(query, value);
// Tell everyone the root type changed (user, project, state)
scheduleAnnounce(parts[0], userData);
dirty = true; //Prevent recursion
return true;
}
var timers = {};
function scheduleAnnounce(type, userData){
clearTimeout(timers[type]);
timers[type] = setTimeout(function(){
emit("change:" + type, { data: model[type], userData: userData });
});
}
function setJson(query, value) {
return set(query, value);
}
function getJson(query) {
var json = get(query, true);
if (query.indexOf("json()") == -1)
json = json["json()"];
if (typeof json === "object")
return JSON.parse(JSON.stringify(json));
if (typeof json === "string") {
try {
return JSON.parse(json);
} catch (e) {}
}
// do not return null or undefined so that getJson(query).foo never throws
return false;
}
function getBool(query) {
var bool = get(query);
return ui.isTrue(bool) || (ui.isFalse(bool) ? false : undefined);
}
function getNumber(query) {
var double = get(query);
return parseFloat(double, 10);
}
function getNode(query) {
return get(query, true);
}
function get(query, isNode) {
var parts = query.split("/");
if (!isNode && parts[parts.length - 1].charAt(0) !== "@")
parts.push("json()");
var hash = model;
parts.every(function(part) {
hash = hash[part];
return hash;
});
return hash === undefined ? "" : hash;
}
function exist(query) {
var parts = query.split("/");
var hash = model;
return parts.every(function(part) {
hash = hash[part];
return hash;
});
}
function reset(query) {
if (!query) query = "user|state";
var info = {};
query.split("|").forEach(function(type) {
info[type] = TEMPLATE[type];
});
read(model, true);
saveToFile();
}
/***** Lifecycle *****/
plugin.on("load", function(){
// Give ui a reference to settings
ui.settings = plugin;
// Get the Time
proc.execFile("node", {
args: ["-e", "console.log(Date.now())"]
}, function(err, stdout, stderr) {
if (err || stderr)
return;
var time = parseInt(stdout, 10);
diff = Date.now() - time;
});
});
plugin.on("enable", function(){
});
plugin.on("disable", function(){
});
plugin.on("unload", function(){
dirty = false;
diff = 0;
userData = null;
inited = false;
clearInterval(timer);
});
/***** Register and define API *****/
/**
* Settings for Cloud9. Settings are stored based on a path pointing
* to leaves. Each leaf can be accessed using the "@" char.
*
* Example:
*
* settings.set("user/tree/@width", "200");
*
* Example:
*
* settings.getNumber("user/tree/@width");
* @singleton
*/
plugin.freezePublicAPI({
/**
* Exposes the model object that stores the XML used to store the
* settings. This property is here for backwards compatibility only
* and will be removed in the next version.
* @property model
* @deprecated
* @private
*/
model: model, //Backwards compatibility, should be removed in a later version
/**
* @property {Boolean} inited whether the settings have been loaded
*/
get inited(){ return inited; },
/**
* The offset between the server time and the client time in
* milliseconds. A positive number means the client is ahead of the
* server.
* @property timeOffset
* @readonly
*/
get timeOffset(){ return diff; },
/**
*
*/
get paths(){ return PATH; },
/**
*
*/
get saveToCloud(){ return saveToCloud; },
_events: [
/**
* @event read Fires when settings are read
*/
"read",
/**
* @event write Fires when settings are written
* @param {Object} e
* @param {Boolean} e.unload specifies whether the application
* is being unloaded. During an unload there is not much time
* and only the highly urgent information should be saved in a
* way that the browser still allows (socket is gone, etc).
**/
"write"
],
/**
* Saves the most current settings after a timeout
* @param {Boolean} force forces the settings to be saved immediately
*/
save: save,
/**
* Loads the xml settings into the application
* @param {XMLElement} xml The settings xml
*/
read: read,
/**
* Sets a value in the settings tree
* @param {String} path the path specifying the key for the value
* @param {String} value the value to store in the specified location
*/
"set" : set,
/**
* Sets a value in the settings tree and serializes it as JSON
* @param {String} path the path specifying the key for the value
* @param {String} value the value to store in the specified location
*/
"setJson" : setJson,
/**
* Gets a value from the settings tree
* @param {String} path the path specifying the key for the value
*/
"get" : get,
/**
* Gets a value from the settings tree and interprets it as JSON
* @param {String} path the path specifying the key for the value
*/
"getJson" : getJson,
/**
* Gets a value from the settings tree and interprets it as Boolean
* @param {String} path the path specifying the key for the value
*/
"getBool" : getBool,
/**
* Gets a value from the settings tree and interprets it as Boolean
* @param {String} path the path specifying the key for the value
*/
"getNumber" : getNumber,
/**
* Gets an object from the settings tree and returns it
* @param {String} path the path specifying the key for the value
*/
"getNode" : getNode,
/**
* Checks to see if a node exists
* @param {String} path the path specifying the key for the value
*/
"exist" : exist,
/**
* Sets the default attributes of a settings tree node.
*
* Example:
*
* settings.setDefaults("user/myplugin", [
* ["width", 200],
* ["show", true]
* ])
*
* @param {String} path the path specifying the key for the value
* @param {Array} attr two dimensional array with name
* values of the attributes for which the defaults are set
*/
setDefaults: setDefaults,
/**
* Moves and renames attributes from one path to another path
* @param {String} fromPath the path specifying where key for the value
* @param {String} toPath the path specifying where key for the value
* @param {Array} attr two dimensional array with name
* values of the attributes for which the defaults are set
*/
migrate: migrate,
/**
* Resets the settings to their defaults
*/
reset: reset,
/**
* Update user, project or state settings incrementally
* @param {String} type
* @param {Object} settings
*/
update: update
});
if (c9.connected || options.settings)
loadSettings();
else
c9.once("connect", loadSettings);
register(null, {
settings: plugin
});
}
});

Wyświetl plik

@ -0,0 +1,119 @@
/*global describe:false, it:false */
"use client";
require(["lib/architect/architect", "lib/chai/chai"], function (architect, chai) {
var expect = chai.expect;
var defSettings = { user: { "@bar": "foo", "bar": {"json()": "test"} }, state: { oi : { hi : 10 } }, project: { hi: 0 } };
var copySettings = JSON.parse(JSON.stringify(defSettings));
expect.setupArchitectTest([
{
packagePath: "plugins/c9.core/c9",
workspaceId: "user/javruben/dev",
env: "test",
},
"plugins/c9.vfs.client/vfs_client",
"plugins/c9.vfs.client/endpoint",
"plugins/c9.ide.auth/auth",
"plugins/c9.core/ext",
"plugins/c9.core/http-xhr",
"plugins/c9.ide.ui/lib_apf",
{
packagePath: "plugins/c9.core/settings",
settings: defSettings,
debug: true
},
"plugins/c9.ide.ui/ui",
"plugins/c9.core/api",
// Mock plugins
{
consumes: [],
provides: ["fs", "auth.bootstrap", "info", "proc", "dialog.error"],
setup: expect.html.mocked
},
{
consumes: ["settings"],
provides: [],
setup: main
}
], architect);
function main(options, imports, register) {
var settings = imports.settings;
describe('settings', function() {
it('should expose the settings in it\'s model', function(done) {
expect(settings.model.project).to.deep.equal(defSettings.project);
expect(settings.model.user).to.deep.equal(defSettings.user);
expect(settings.model.state).to.deep.equal(defSettings.state);
done();
});
it('should expose the tree via the get method', function(done) {
expect(settings.get('user/@bar')).to.equal("foo");
expect(settings.get('user/bar')).to.equal("test");
done();
});
it('should allow altering the tree via the set method', function(done) {
var v = Math.random().toString();
settings.set('user/@bar', v)
expect(settings.get('user/@bar')).to.equal(v);
v = Math.random().toString();
settings.set('user/bar', v)
expect(settings.get('user/bar')).to.equal(v);
done();
});
it('should allow new settings to be read from json', function(done) {
settings.read(copySettings);
settings.once("read", function(){
expect(settings.get("user/@bar")).to.equal("foo");
done();
});
});
it('should call event listener on tree node', function(done) {
settings.once("user", function(){
expect(settings.get("user/@bar")).to.equal("test");
done();
});
settings.set("user/@bar", "test");
});
it('should allow type conversion for JSON and Booleans', function(done) {
settings.set('user/@bar', "true")
expect(settings.getBool('user/@bar')).to.equal(true);
settings.setJson('user/bar', {test:1})
expect(settings.getJson('user/bar')).property("test").to.equal(1);
done();
});
it('should set default values only when they are not set already', function(done) {
settings.setDefaults('user', [
["bar", "10"],
["test", "15"]
]);
expect(settings.exist('user')).to.equal(true);
expect(settings.get('user/@bar')).to.not.equal("10");
expect(settings.get('user/@test')).to.equal("15");
done();
});
it('should set default values the node doesn\'t exist yet', function(done) {
settings.setDefaults('new', [
["bar", "10"],
["test", "15"]
]);
expect(settings.exist('new')).to.equal(true);
expect(settings.get('new/@bar')).to.equal("10");
expect(settings.get('new/@test')).to.equal("15");
done();
});
});
onload && onload();
}
});

Wyświetl plik

@ -0,0 +1,383 @@
/**
* Utilities for the Ajax.org Cloud IDE
*
* @copyright 2013, Ajax.org B.V.
*/
define(function(require, exports, module) {
main.consumes = ["c9", "Plugin"];
main.provides = ["util"];
return main;
function main(options, imports, register) {
var c9 = imports.c9;
var Plugin = imports.Plugin;
var normalize = require("path").normalize;
var plugin = new Plugin("Ajax.org", main.consumes);
plugin.escapeXpathString = function(name) {
if (!name)
return "";
if (name.indexOf('"') > -1) {
var out = [];
var parts = name.split('"');
parts.each(function(part) {
out.push(part === "" ? "'\"'" : '"' + part + '"');
});
return "concat(" + out.join(", ") + ")";
}
return '"' + name + '"';
};
var SupportedIcons = {
"application/xhtml+xml":"html",
"text/css": "css",
"text/x-scss": "css",
"text/x-sass": "css",
"text/html":"html",
"application/pdf":"page_white_acrobat",
"image":"image",
"application/xml":"page_white_code_red",
"image/svg+xml": "page_white_picture",
"text/plain": "page_white_text",
"application/javascript": "page_white_code",
"application/json": "page_white_code",
"text/x-script.python": "page_white_code",
"text/x-script.ocaml": "page_white_code",
"text/x-script.clojure": "page_white_code",
"application/x-httpd-php": "page_white_php",
"application/x-sh": "page_white_wrench",
"text/x-coldfusion": "page_white_coldfusion",
"text/x-script.ruby": "page_white_ruby",
"text/x-script.coffeescript": "page_white_cup",
"text/cpp": "page_white_cplusplus",
"text/x-c": "page_white_c",
"text/x-logiql": "logiql",
"text/x-csharp": "page_white_csharp",
"text/x-java-source": "page_white_cup",
"text/x-markdown": "page_white_text",
"text/x-xquery": "page_white_code"
};
var contentTypes = {
"c9search": "text/x-c9search",
"js": "application/javascript",
"json": "application/json",
"run": "application/javascript",
"build": "application/javascript",
"css": "text/css",
"scss": "text/x-scss",
"sass": "text/x-sass",
"xml": "application/xml",
"rdf": "application/rdf+xml",
"rss": "application/rss+xml",
"svg": "image/svg+xml",
"wsdl": "application/wsdl+xml",
"xslt": "application/xslt+xml",
"atom": "application/atom+xml",
"mathml": "application/mathml+xml",
"mml": "application/mathml+xml",
"php": "application/x-httpd-php",
"phtml": "application/x-httpd-php",
"html": "text/html",
"xhtml": "application/xhtml+xml",
"coffee": "text/x-script.coffeescript",
"py": "text/x-script.python",
"java": "text/x-java-source",
"logic": "text/x-logiql",
"ru": "text/x-script.ruby",
"gemspec": "text/x-script.ruby",
"rake": "text/x-script.ruby",
"rb": "text/x-script.ruby",
"c": "text/x-c",
"cc": "text/x-c",
"cpp": "text/x-c",
"cxx": "text/x-c",
"h": "text/x-c",
"hh": "text/x-c",
"hpp": "text/x-c",
"bmp": "image",
"djv": "image",
"djvu": "image",
"gif": "image",
"ico": "image",
"jpeg": "image",
"jpg": "image",
"pbm": "image",
"pgm": "image",
"png": "image",
"pnm": "image",
"ppm": "image",
"psd": "image",
"svgz": "image",
"tif": "image",
"tiff": "image",
"xbm": "image",
"xpm": "image",
"clj": "text/x-script.clojure",
"ml": "text/x-script.ocaml",
"mli": "text/x-script.ocaml",
"cfm": "text/x-coldfusion",
"sql": "text/x-sql",
"sh": "application/x-sh",
"bash": "application/x-sh",
"xq": "text/x-xquery",
"terminal": "terminal"
};
plugin.getFileIcon = function(name) {
var icon = "page_white_text";
var ext;
if (name) {
ext = name.split(".").pop().toLowerCase();
icon = SupportedIcons[contentTypes[ext]] || "page_white_text";
}
return icon;
};
plugin.getFileIconCss = function(staticPrefix) {
function iconCss(name, icon) {
return ".filetree-icon." + name + "{background-image:"
+"url(\"" + staticPrefix + "/icons/" + (icon || name) + ".png\")}";
}
var css = "";
var added = {};
for (var i in SupportedIcons) {
var icon = SupportedIcons[i];
if (!added[icon]) {
css += iconCss(icon) + "\n";
added[icon] = true;
}
}
return css;
};
plugin.getContentType = function(filename) {
var type = filename.split(".").pop().split("!").pop().toLowerCase() || "";
return contentTypes[type] || "text/plain";
};
// taken from http://xregexp.com/
plugin.escapeRegExp = function(str) {
return str.replace(/[-[\]{}()*+?.,\\^$|#\s"']/g, "\\$&");
};
plugin.escapeXml = window.apf
? apf.escapeXML
: function() { alert("oops! apf needed for this") };
plugin.replaceStaticPrefix = function (string) {
return string.replace(new RegExp("{c9.staticPrefix}", "g"), c9.staticUrl);
};
/*
* JavaScript Linkify - v0.3 - 6/27/2009
* http://benalman.com/projects/javascript-linkify/
*
* Copyright (c) 2009 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*
* Some regexps adapted from http://userscripts.org/scripts/review/7122
*/
plugin.linkify = function(){var k="[a-z\\d.-]+://",h="(?:(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])",c="(?:(?:[^\\s!@#$%^&*()_=+[\\]{}\\\\|;:'\",.<>/?]+)\\.)+",n="(?:ac|ad|aero|ae|af|ag|ai|al|am|an|ao|aq|arpa|ar|asia|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|biz|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|cat|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|coop|com|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|info|int|in|io|iq|ir|is|it|je|jm|jobs|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mobi|mo|mp|mq|mr|ms|mt|museum|mu|mv|mw|mx|my|mz|name|na|nc|net|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pro|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm|tn|to|tp|travel|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g|xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)",f="(?:"+c+n+"|"+h+")",o="(?:[;/][^#?<>\\s]*)?",e="(?:\\?[^#<>\\s]*)?(?:#[^<>\\s]*)?",d="\\b"+k+"[^<>\\s]+",a="\\b"+f+o+e+"(?!\\w)",m="mailto:",j="(?:"+m+")?[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@"+f+e+"(?!\\w)",l=new RegExp("(?:"+d+"|"+a+"|"+j+")","ig"),g=new RegExp("^"+k,"i"),b={"'":"`",">":"<",")":"(","]":"[","}":"{","B;":"B+","b:":"b9"},i={callback:function(q,p){return p?'<a href="'+p+'" title="'+p+'">'+q+"</a>":q},punct_regexp:/(?:[!?.,:;'"]|(?:&|&amp;)(?:lt|gt|quot|apos|raquo|laquo|rsaquo|lsaquo);)$/};return function(u,z){z=z||{};var w,v,A,p,x="",t=[],s,E,C,y,q,D,B,r;for(v in i){if(z[v]===undefined){z[v]=i[v]}}while(w=l.exec(u)){A=w[0];E=l.lastIndex;C=E-A.length;if(/[\/:]/.test(u.charAt(C-1))){continue}do{y=A;r=A.substr(-1);B=b[r];if(B){q=A.match(new RegExp("\\"+B+"(?!$)","g"));D=A.match(new RegExp("\\"+r,"g"));if((q?q.length:0)<(D?D.length:0)){A=A.substr(0,A.length-1);E--}}if(z.punct_regexp){A=A.replace(z.punct_regexp,function(F){E-=F.length;return""})}}while(A.length&&A!==y);p=A;if(!g.test(p)){p=(p.indexOf("@")!==-1?(!p.indexOf(m)?"":m):!p.indexOf("irc.")?"irc://":!p.indexOf("ftp.")?"ftp://":"http://")+p}if(s!=C){t.push([u.slice(s,C)]);s=E}t.push([A,p])}t.push([u.substr(s)]);for(v=0;v<t.length;v++){x+=z.callback.apply(window,t[v])}return x||u}}();
plugin.stableStringify = function(obj, replacer, spaces) {
var sortByKeys = function(obj) {
if (!obj || typeof obj != "object" || Array.isArray(obj)) {
return obj;
}
var sorted = {};
Object.keys(obj).sort().forEach(function(key) {
sorted[key] = sortByKeys(obj[key]);
});
// assert(_.isEqual(obj, sorted));
return sorted;
};
return JSON.stringify(sortByKeys(obj), replacer, spaces);
};
plugin.safeParseJson = function(strJson, cb){
// Remove comments
var data = strJson.replace(/(^|\n)\s*\/\/.*/g, "");
try { return JSON.parse(data); }
catch (e) { cb(e); return false; }
}
/**
*
*/
plugin.extend = function(dest, src) {
var prop, i, x = !dest.notNull;
if (arguments.length == 2) {
for (prop in src) {
if (x || src[prop])
dest[prop] = src[prop];
}
return dest;
}
for (i = 1; i < arguments.length; i++) {
src = arguments[i];
for (prop in src) {
if (x || src[prop])
dest[prop] = src[prop];
}
}
return dest;
};
plugin.isEqual = function(o1, o2) {
for (var prop in o1)
if (o1[prop] != o2[prop])
return false;
for (var prop in o2)
if (o1[prop] != o2[prop])
return false;
return true;
};
/**
* Generate an XML tag that contains properties according to a property-map defined
* in `attrs`.
*
* @param {String} tag Name of the XML tag
* @param {Object} attrs Map of name-value pairs of XML properties
* @param {Boolean} noclose If TRUE, the XML tag will be returned UNclosed. Defaults to FALSE.
* @type {String}
*/
plugin.toXmlTag = function (tag, attrs, noclose) {
return "<" + tag + " " + plugin.toXmlAttributes(attrs) + (noclose ? ">" : " />");
};
/**
* Returns the gravatar url for this user
* @param {Number} size the size of the image
*/
plugin.getGravatarUrl = function getGravatarUrl(email, size, defaultImage) {
var md5Email = apf.crypto.MD5.hex_md5((email || "").trim().toLowerCase());
return "https://secure.gravatar.com/avatar/"
+ md5Email + "?s=" + size + "&d=" + (defaultImage || "retro");
};
var reHome = new RegExp("^" + plugin.escapeRegExp(c9.home || "/home/ubuntu"));
plugin.normalizePath = function(path){
return path && normalize(path.replace(reHome, "~"));
};
/**
* Converts a map of name-value pairs to XML properties.
*
* @param {Object} obj Map of name-value pairs of XML properties
* @type {String}
*/
plugin.toXmlAttributes = function(obj) {
var xml = Object.keys(obj)
.map(function (k) {
return k + '="' + apf.escapeXML(obj[k]) + '"';
})
.join(" ");
return xml;
};
plugin.shadeColor = function(base, factor) {
var m = base.match(/(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
if (!m) {
m = base.match(/(\w\w)(\w\w)(\w\w)/);
if (!m) {
m = base.match(/(\w)(\w)(\w)/);
if (!m)
return base; // not a color
m = [0, m[1] + m[1], m[2] + m[2], m[3] + m[3]];
}
m = [0, parseInt(m[1], 16), parseInt(m[2], 16), parseInt(m[3], 16)];
}
var R = m[1], G = m[2], B = m[3];
return {
isLight: (0.2126 * R + 0.7152 * G + 0.0722 * B) > 150,
color: "rgb(" + parseInt(R * factor, 10) + ", "
+ parseInt(G * factor, 10) + ", "
+ parseInt(B * factor, 10) + ")"
};
};
plugin.getBox = function(value, base) {
if (!base) base = 0;
if (value === null || (!parseInt(value, 10) && parseInt(value, 10) !== 0))
return [0, 0, 0, 0];
var x = String(value).split(/\s* \s*/);
for (var i = 0; i < x.length; i++)
x[i] = parseInt(x[i], 10) || 0;
switch (x.length) {
case 1:
x[1] = x[0];
x[2] = x[0];
x[3] = x[0];
break;
case 2:
x[2] = x[0];
x[3] = x[1];
break;
case 3:
x[3] = x[1];
break;
}
return x;
};
plugin.escapeShell = function(cmd) {
var re = /([\#\&\;\`\|\*\?<>\^\(\)\[\]\{\}\$\,\x0A\xFF\' \"\\])/g;
return cmd.replace(re, "\\$1");//.replace(/^~/, "\\~");
};
var cloneObject = plugin.cloneObject = function(obj) {
if (obj === null || typeof obj !== "object")
return obj;
var copy = Array.isArray(obj) ? [] : {};
Object.keys(obj).forEach(function(k) {
copy[k] = cloneObject(obj[k]);
});
return copy;
};
plugin.nextFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame;
if (plugin.nextFrame)
plugin.nextFrame = plugin.nextFrame.bind(window);
else
plugin.nextFrame = function(callback) {
setTimeout(callback, 17);
};
plugin.freezePublicAPI({});
register(null, {
util: plugin
});
}
});

Wyświetl plik

@ -0,0 +1,40 @@
/*global describe:false, it:false */
"use client";
require(["lib/architect/architect", "lib/chai/chai"], function (architect, chai) {
var expect = chai.expect;
expect.setupArchitectTest([
"plugins/c9.core/ext",
"plugins/c9.core/util",
// Mock plugins
{
consumes: [],
provides: ["c9"],
setup: expect.html.mocked
},
{
consumes: ["util"],
provides: [],
setup: main
}
], architect);
function main(options, imports, register) {
var util = imports.util;
describe('getContentType, getFileIcon', function() {
it('should retrieve the content type based on a filename', function() {
expect(util.getContentType("test.js")).to.equal("application/javascript");
expect(util.getContentType("test.html")).to.equal("text/html");
});
it('should retrieve the icon class name based on a filename', function() {
expect(util.getFileIcon("test.js")).to.equal("page_white_code");
expect(util.getFileIcon("test.html")).to.equal("html");
});
});
onload && onload();
}
});

Wyświetl plik

@ -36,6 +36,7 @@ function plugin(options, imports, register) {
var connect = imports.connect;
var render = imports["connect.render"];
var analytics = imports["analytics"];
var async = require("async");
var Types = require("frontdoor").Types;
var error = require("http-error");
@ -281,7 +282,7 @@ function plugin(options, imports, register) {
}
// TODO: use an interval to make sure this fires
// even when this REST api is not used for a day
trackActivity(entry.user);
trackActivity(entry.user, req.cookies);
entry.vfs.handleRest(scope, path, req, res, next);
}
]);
@ -315,12 +316,23 @@ function plugin(options, imports, register) {
}
]);
function trackActivity(user) {
function trackActivity(user, cookies) {
if (user.id === -1)
return;
if (new Date(user.lastVfsAccess).getDate() != new Date().getDate() ||
Date.now() > user.lastVfsAccess + VFS_ACTIVITY_WINDOW) {
analytics.identifyClean(user);
analytics.trackClean(user, "VFS is active", { uid: user.id });
// Alias anonymous id, identify, and track activity;
// wait for a flush between each step; see
// https://segment.com/docs/integrations/mixpanel/#server-side
async.series([
analytics.aliasClean.bind(null, cookies.mixpanelAnonymousId, user.id),
analytics.identifyClean.bind(user, {}),
analytics.trackClean(user, "VFS is active", { uid: user.id }),
], function(err) {
if (err) return console.log("Error logging activity", err.stack || err);
});
user.lastVfsAccess = Date.now();
user.save(function() {});