aboutsummaryrefslogtreecommitdiffstats
path: root/trunk
diff options
context:
space:
mode:
Diffstat (limited to 'trunk')
-rw-r--r--trunk/etherpad/src/etherpad/admin/plugins.js182
-rw-r--r--trunk/etherpad/src/etherpad/control/admin/pluginmanager.js65
-rw-r--r--trunk/etherpad/src/main.js8
-rw-r--r--trunk/etherpad/src/plugins/kafoo/main.js8
-rw-r--r--trunk/etherpad/src/plugins/testplugin/main.js27
-rw-r--r--trunk/etherpad/src/templates/admin/pluginmanager.ejs126
-rw-r--r--trunk/infrastructure/framework-src/modules/execution.js5
7 files changed, 420 insertions, 1 deletions
diff --git a/trunk/etherpad/src/etherpad/admin/plugins.js b/trunk/etherpad/src/etherpad/admin/plugins.js
new file mode 100644
index 0000000..f48dbb5
--- /dev/null
+++ b/trunk/etherpad/src/etherpad/admin/plugins.js
@@ -0,0 +1,182 @@
+/**
+ * Copyright 2009 RedHog, Egil Möller <egil.moller@piratpartiet.se>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import("faststatic");
+import("dispatch.{Dispatcher,PrefixMatcher,forward}");
+
+import("etherpad.utils.*");
+import("etherpad.collab.server_utils");
+import("etherpad.globals.*");
+import("etherpad.log");
+import("etherpad.pad.padusers");
+import("etherpad.pro.pro_utils");
+import("etherpad.helpers");
+import("etherpad.pro.pro_accounts.getSessionProAccount");
+import("sqlbase.sqlbase");
+import("sqlbase.sqlcommon");
+import("sqlbase.sqlobj");
+import("exceptionutils");
+import("execution");
+
+jimport("java.io.File",
+ "java.io.DataInputStream",
+ "java.io.FileInputStream",
+ "java.lang.Byte",
+ "java.io.FileReader",
+ "java.io.BufferedReader",
+ "net.appjet.oui.JarVirtualFile");
+
+pluginsLoaded = false;
+pluginModules = {};
+plugins = {};
+hooks = {};
+
+function loadAvailablePlugin(pluginName) {
+ if (plugins[pluginName] != undefined)
+ return plugins[pluginName];
+
+ var pluginsDir = new Packages.java.io.File("src/plugins");
+
+ var pluginFile = new Packages.java.io.File(pluginsDir, pluginName + '/main.js');
+ if (pluginFile.exists()) {
+ var pluginModulePath = pluginFile.getPath().replace(new RegExp("src/\(.*\)\.js"), "$1").replace("/", ".", "g");
+ var importStmt = "import('" + pluginModulePath + "')";
+ try {
+ return execution.fancyAssEval(importStmt, "main;");
+ } catch (e) {
+ log.info({errorLoadingPlugin:exceptionutils.getStackTracePlain(e)});
+ }
+ }
+ return null;
+}
+
+function loadAvailablePlugins() {
+ var pluginsDir = new Packages.java.io.File("src/plugins");
+
+ var pluginNames = pluginsDir.list();
+
+ for (i = 0; i < pluginNames.length; i++) {
+ var plugin = loadAvailablePlugin(pluginNames[i]);
+ if (plugin != null)
+ pluginModules[pluginNames[i]] = plugin
+ }
+}
+
+function loadInstalledHooks() {
+ var sql = '' +
+ 'select ' +
+ ' hook.name as hook, ' +
+ ' plugin.name as plugin, ' +
+ ' plugin_hook.original_name as original_hook ' +
+ 'from ' +
+ ' plugin ' +
+ ' left outer join plugin_hook on ' +
+ ' plugin.id = plugin_hook.plugin_id ' +
+ ' left outer join hook on ' +
+ ' plugin_hook.hook_id = hook.id ' +
+ 'order by hook.name, plugin.name';
+
+ var rows = sqlobj.executeRaw(sql, {});
+ for (var i = 0; i < rows.length; i++) {
+ if (hooks[rows[i].hook] == undefined)
+ hooks[rows[i].hook] = [];0
+ if (plugins[rows[i].plugin] == undefined)
+ plugins[rows[i].plugin] = [];
+ plugins[rows[i].plugin].push({hookName:rows[i].hook, originalHook:rows[i].originalName});
+ if (rows[i].hook != 'null')
+ hooks[rows[i].hook].push({pluginName:rows[i].plugin, originalHook:rows[i].originalName});
+ }
+}
+
+function loadPlugins() {
+ if (pluginsLoaded) return;
+ pluginsLoaded = true;
+ loadAvailablePlugins();
+ loadInstalledHooks();
+}
+
+function registerHook(pluginName, hookName, originalHook) {
+ if (originalHook == undefined) originalHook = null;
+ plugins[pluginName].push({hookName:hookName, originalHook:originalHook});
+ if (hooks[hookName] === undefined) hooks[hookName] = [];
+ hooks[hookName].push({pluginName:pluginName, originalHook:originalHook});
+
+ var plugin = sqlobj.selectSingle('plugin', {name:pluginName});
+ var hook = sqlobj.selectSingle('hook', {name:hookName});
+ if (hook == null) {
+ sqlobj.insert('hook', {name:hookName});
+ hook = sqlobj.selectSingle('hook', {name:hookName});
+ }
+ sqlobj.insert("plugin_hook", {plugin_id:plugin.id, hook_id:hook.id, original_name:originalHook});
+}
+
+function unregisterHook(pluginName, hookName) {
+ plugins[pluginName] = plugins[pluginName].filter(function (hook) { return hook.hookName != hookName; });
+ hooks[hookName] = hooks[hookName].filter(function (plugin) { return plugin.pluginName != pluginName; });
+ if (hooks[hookName].length == 0)
+ delete hooks[hookName];
+
+ var conditions = {};
+ if (pluginName != undefined) {
+ var plugin = sqlobj.selectSingle('plugin', {name:pluginName});
+ conditions['plugin_id'] = plugin.id;
+ }
+ if (hookName != undefined) {
+ var hook = sqlobj.selectSingle('hook', {name:hookName});
+ conditions['hook_id'] = hook.id;
+ }
+ sqlobj.deleteRows('plugin_hook', conditions);
+}
+
+/* User API */
+function enablePlugin(pluginName) {
+ loadPlugins();
+ if (pluginModules[pluginName] === undefined)
+ throw new Error ("Unable to find a plugin named " + pluginName);
+ if (plugins[pluginName] !== undefined)
+ throw new Error ("Atempting to reenable the already enabled plugin " + pluginName);
+ sqlobj.insert("plugin", {name:pluginName});
+ plugins[pluginName] = [];
+ for (var i = 0; i < pluginModules[pluginName].hooks.length; i++)
+ registerHook(pluginName, pluginModules[pluginName].hooks[i]);
+ pluginModules[pluginName].install();
+}
+
+function disablePlugin(pluginName) {
+ loadPlugins();
+ pluginModules[pluginName].uninstall();
+ var pluginHooks = plugins[pluginName].map(function (x) { return x; }); // copy array
+
+ for (pluginHook in pluginHooks)
+ unregisterHook(pluginName, pluginHooks[pluginHook].hookName);
+ delete plugins[pluginName];
+ sqlobj.deleteRows("plugin", {name:pluginName});
+}
+
+function callHook(hookName, args) {
+ loadPlugins();
+log.info({XYZZZZ:hooks, NANANA:hookName});
+ if (hooks[hookName] === undefined)
+ return [];
+ return hooks[hookName].map(
+ function (plugin) {
+ return pluginModules[plugin.pluginName][plugin.originalHook || hookName](args);
+ });
+}
+
+function callHookStr(hookName, args, sep, pre, post) {
+ return callHook(hookName, args).map(function (x) { return pre + x + post}).join(sep || "");
+}
diff --git a/trunk/etherpad/src/etherpad/control/admin/pluginmanager.js b/trunk/etherpad/src/etherpad/control/admin/pluginmanager.js
new file mode 100644
index 0000000..3fb017c
--- /dev/null
+++ b/trunk/etherpad/src/etherpad/control/admin/pluginmanager.js
@@ -0,0 +1,65 @@
+/**
+ * Copyright 2009 RedHog, Egil Möller <egil.moller@piratpartiet.se>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import("faststatic");
+import("dispatch.{Dispatcher,PrefixMatcher,forward}");
+
+import("etherpad.utils.*");
+import("etherpad.collab.server_utils");
+import("etherpad.globals.*");
+import("etherpad.log");
+import("etherpad.pad.padusers");
+import("etherpad.pro.pro_utils");
+import("etherpad.helpers");
+import("etherpad.pro.pro_accounts.getSessionProAccount");
+import("etherpad.admin.plugins");
+
+
+function onRequest() {
+ plugins.loadPlugins();
+
+ if (request.params.action == 'install') {
+ plugins.enablePlugin(request.params.plugin);
+ } else if (request.params.action == 'uninstall') {
+ plugins.disablePlugin(request.params.plugin);
+ } else if (request.params.action == 'reinstall') {
+ plugins.disablePlugin(request.params.plugin);
+ plugins.enablePlugin(request.params.plugin);
+ }
+
+ helpers.addClientVars({
+ userAgent: request.headers["User-Agent"],
+ debugEnabled: request.params.djs,
+ clientIp: request.clientAddr,
+ colorPalette: COLOR_PALETTE,
+ serverTimestamp: +(new Date),
+ isProPad: pro_utils.isProDomainRequest(),
+ userIsGuest: padusers.isGuest(padusers.getUserId()),
+ userId: padusers.getUserId(),
+ });
+
+ renderHtml("admin/pluginmanager.ejs",
+ {
+ pluginModules: plugins.pluginModules,
+ plugins: plugins.plugins,
+ config: appjet.config,
+ bodyClass: 'nonpropad',
+ isPro: pro_utils.isProDomainRequest(),
+ isProAccountHolder: pro_utils.isProDomainRequest() && ! padusers.isGuest(padusers.getUserId()),
+ account: getSessionProAccount(), // may be falsy
+ });
+ return true;
+}
diff --git a/trunk/etherpad/src/main.js b/trunk/etherpad/src/main.js
index 9cc1db2..cf07829 100644
--- a/trunk/etherpad/src/main.js
+++ b/trunk/etherpad/src/main.js
@@ -34,6 +34,7 @@ import("etherpad.importexport.importexport");
import("etherpad.legacy_urls");
import("etherpad.control.aboutcontrol");
+import("etherpad.control.admin.pluginmanager");
import("etherpad.control.admincontrol");
import("etherpad.control.blogcontrol");
import("etherpad.control.connection_diagnostics_control");
@@ -68,6 +69,8 @@ import("etherpad.pad.dbwriter");
import("etherpad.pad.pad_migrations");
import("etherpad.pad.noprowatcher");
+import("etherpad.admin.plugins");
+
jimport("java.lang.System.out.println");
serverhandlers.startupHandler = function() {
@@ -92,6 +95,8 @@ serverhandlers.startupHandler = function() {
team_billing.onStartup();
collabroom_server.onStartup();
readLatestSessionsFromDisk();
+
+ plugins.callHook('serverStartup');
};
serverhandlers.resetHandler = function() {
@@ -99,6 +104,8 @@ serverhandlers.resetHandler = function() {
}
serverhandlers.shutdownHandler = function() {
+ plugins.callHook('serverShutdown');
+
appjet.cache.shutdownHandlerIsRunning = true;
log.callCatchingExceptions(writeSessionsToDisk);
@@ -375,6 +382,7 @@ function handlePath() {
[DirMatcher('/ep/beta-account/'), forward(pro_beta_control)],
[DirMatcher('/ep/pro-signup/'), forward(pro_signup_control)],
[DirMatcher('/ep/about/'), forward(aboutcontrol)],
+ [DirMatcher('/ep/admin/pluginmanager'), forward(pluginmanager)],
[DirMatcher('/ep/admin/'), forward(admincontrol)],
[DirMatcher('/ep/blog/posts/'), blogcontrol.render_post],
[DirMatcher('/ep/blog/'), forward(blogcontrol)],
diff --git a/trunk/etherpad/src/plugins/kafoo/main.js b/trunk/etherpad/src/plugins/kafoo/main.js
new file mode 100644
index 0000000..d0ef442
--- /dev/null
+++ b/trunk/etherpad/src/plugins/kafoo/main.js
@@ -0,0 +1,8 @@
+hooks = ['nahook', 'fiehook'];
+description = 'Kabar';
+
+function install() {
+}
+
+function uninstall() {
+}
diff --git a/trunk/etherpad/src/plugins/testplugin/main.js b/trunk/etherpad/src/plugins/testplugin/main.js
new file mode 100644
index 0000000..589c60c
--- /dev/null
+++ b/trunk/etherpad/src/plugins/testplugin/main.js
@@ -0,0 +1,27 @@
+import("etherpad.log");
+import("etherpad.admin.plugins");
+
+hooks = ['testhook', 'nahook', 'serverStartup', 'serverShutdown'];
+description = 'Test Plugin';
+
+function install() {
+ log.info("Installing testplugin");
+}
+
+function uninstall() {
+ log.info("Uninstalling testplugin");
+}
+
+function testhook () {
+}
+
+function nahook() {
+}
+
+function serverStartup() {
+ log.info("Server startup for testplugin");
+}
+
+function serverShutdown() {
+ log.info("Server shutdown for testplugin");
+}
diff --git a/trunk/etherpad/src/templates/admin/pluginmanager.ejs b/trunk/etherpad/src/templates/admin/pluginmanager.ejs
new file mode 100644
index 0000000..4e08fc9
--- /dev/null
+++ b/trunk/etherpad/src/templates/admin/pluginmanager.ejs
@@ -0,0 +1,126 @@
+<% /* Copyright 2009 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS-IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License. */ %>
+<%
+ helpers.setHtmlTitle("Browse tags");
+ helpers.setBodyId("padbody");
+ helpers.addBodyClass("limwidth nonpropad nonprouser");
+ helpers.includeCss("pad2_ejs.css");
+ helpers.setRobotsPolicy({index: false, follow: false})
+ helpers.includeJQuery();
+ helpers.includeCometJs();
+ helpers.includeJs("json2.js");
+ helpers.addToHead('\n<style type="text/css" title="dynamicsyntax"></style>\n');
+
+ function inArray(item, arr) {
+ for (var i = 0; i < arr.length; i++)
+ if (arr[i] == item)
+ return true;
+ return false;
+ }
+%>
+
+<div id="padpage">
+ <div id="padtop">
+ <div id="topbar" style="margin: 7px; margin-top: 0px;">
+ <div id="topbarleft"><!-- --></div>
+ <div id="topbarright"><!-- --></div>
+ <div id="topbarcenter"><a href="/" id="topbaretherpad">EtherPad</a></div>
+ <% if (isProAccountHolder) { %>
+ <div id="accountnav"><%= toHTML(account.email) %><a href="/ep/account/sign-out">(sign out)</a></div>
+ <% } else if (isPro) { %>
+ <div id="accountnav"><a href="<%= signinUrl %>">sign in</a></div>
+ <% } %>
+ </div>
+ </div>
+ <div id="docbar" class="docbar-public">
+ <div id="docbarleft"><!-- --></div>
+ <div title="Browse pads by tag" id="docbarpadtitle"><span>Browse tags</span></div>
+
+ <div id="docbaroptions-outer"><a href="javascript:void(0)" id="docbaroptions">Pad Options</a></div>
+ <div id="docbarsavedrevs-outer"><a href="javascript:void(0)" id="docbarsavedrevs">Saved revisions</a></div>
+ <div id="docbarimpexp-outer"><a href="javascript:void(0)" id="docbarimpexp">Import/Export</a></div>
+ <div id="docbarslider-outer"><a target="_blank" href="/ep/pad/view/xx/latest" id="docbarslider">Time Slider</a></div>
+
+ </div>
+ </div>
+ <div id="padmain">
+
+ <div id="padsidebar">
+ <div id="padusers">
+ </div>
+
+ <div id="hdraggie"><!-- --></div>
+
+ <div id="padchat"></div>
+ </div> <!-- /padsidebar -->
+
+ <div id="padeditor">
+ <div id="editbar" class="enabledtoolbar">
+ <div id="editbarleft"><!-- --></div>
+ <div id="editbarright"><!-- --></div>
+
+ <div id="editbarinner">
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('bold'));" class="editbarbutton bold" title="Bold (ctrl-B)">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('italic'));" class="editbarbutton italic" title="Italics (ctrl-I)">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('underline'));" class="editbarbutton underline" title="Underline (ctrl-U)">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('strikethrough'));" class="editbarbutton strikethrough" title="Strikethrough">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('clearauthorship'));" class="editbarbutton clearauthorship" title="Clear Authorship Colors">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('undo'));" class="editbarbutton undo" title="Undo (ctrl-Z)">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('redo'));" class="editbarbutton redo" title="Redo (ctrl-Y)">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('insertunorderedlist'));" class="editbarbutton insertunorderedlist" title="Toggle Bullet List">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('indent'));" class="editbarbutton indent" title="Indent List">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('outdent'));" class="editbarbutton outdent" title="Unindent List">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('save'));" class="editbarbutton save" title="Save Revision">&nbsp;</a>
+ </div>
+ </div>
+ <div style="height: 268px;" id="editorcontainerbox">
+ <div id="editorcontainer" style="padding:5pt; height: 600pt;">
+ <h1>Plugin manager</h1>
+ <table>
+ <tr>
+ <th>Module name</th>
+ <th>Status</th>
+ <th></th>
+ </tr>
+ <% for (var plugin in pluginModules) { %>
+ <tr>
+ <td><%= pluginModules[plugin].description %></td>
+ <td>
+ <% if (plugins[plugin] !== undefined) { %>
+ Installed
+ <% } else { %>
+ Not installed
+ <% } %>
+ </td>
+ <td>
+ <% if (plugins[plugin] !== undefined) { %>
+ <a href="/ep/admin/pluginmanager/?plugin=<%= plugin %>&action=uninstall">Uninstall</a>
+ <a href="/ep/admin/pluginmanager/?plugin=<%= plugin %>&action=reinstall">Reinstall</a>
+ <% } else { %>
+ <a href="/ep/admin/pluginmanager/?plugin=<%= plugin %>&action=install">Install</a>
+ <% } %>
+ </td>
+ </tr>
+ <% } %>
+ </table>
+ </div>
+ </div>
+ </div><!-- /padeditor -->
+
+ <div id="bottomarea">
+ <div id="widthprefcheck" class="widthprefunchecked"><!-- --></div>
+ <div id="sidebarcheck" class="sidebarchecked"><!-- --></div>
+ </div>
+ </div>
+</div>
diff --git a/trunk/infrastructure/framework-src/modules/execution.js b/trunk/infrastructure/framework-src/modules/execution.js
index 1cec418..2f9d933 100644
--- a/trunk/infrastructure/framework-src/modules/execution.js
+++ b/trunk/infrastructure/framework-src/modules/execution.js
@@ -44,8 +44,11 @@ function fancyAssEval(initCode, mainCode) {
1);
}
var runner = Packages.net.appjet.oui.ScopeReuseManager.getEmpty(scalaF1(init));
+ var requestWrapper = null;
+ if (request.underlying !== undefined)
+ requestWrapper = new Packages.net.appjet.oui.RequestWrapper(request.underlying);
var ec = new Packages.net.appjet.oui.ExecutionContext(
- new Packages.net.appjet.oui.RequestWrapper(request.underlying),
+ requestWrapper,
null, runner);
return Packages.net.appjet.oui.ExecutionContextUtils.withContext(ec,
scalaF0(function() {