Die Etherpad Plugin-Infrastruktur

2 Jul

Die folgenden Ausführungen basieren auf dem Dokument README.plugins und der Analyse des Etherpad Plugins heading1.

Plugin-Infrastruktur und Hooks

Bei der Plugin-Infrastruktur von Etherpad handelt es sich um ein sog. Hook-Infrastruktur, d.h. dass an geeignetter Stelle im Etherpad Quellcode sog. Hook-Calls ausgelöst werden, für welche sich ein oder mehrere Plugins registrieren können.

Ein Beispiel hierfür sind die Hook-Calls serverStartup und serverShutdown in der Datei etherpad/src/main.js in den Funktionen serverhandlers.startupHandler und serverhandlers.shutdownHandler.

/*
 * etherpad/src/main.js
 */
...
serverhandlers.startupHandler = function() {
  // Order matters.
  checkSystemRequirements();
  ...
  log.onStartup();
  statistics.onStartup();
  migration_runner.onStartup();
  pad_migrations.onStartup();
  model.onStartup();
  collab_server.onStartup();
  pad_control.onStartup();
  dbwriter.onStartup();
  blogcontrol.onStartup();
  importexport.onStartup();
  pro_pad_editors.onStartup();
  noprowatcher.onStartup();
  team_billing.onStartup();
  collabroom_server.onStartup();
  readLatestSessionsFromDisk();

  plugins.callHook('serverStartup');
};
…
serverhandlers.shutdownHandler = function() {
  plugins.callHook('serverShutdown');

  appjet.cache.shutdownHandlerIsRunning = true;
  log.callCatchingExceptions(writeSessionsToDisk);
  log.callCatchingExceptions(dbwriter.onShutdown);
  log.callCatchingExceptions(sqlcommon.onShutdown);
  log.callCatchingExceptions(pro_pad_editors.onShutdown);
};
...

Dabei kann grundsätzlich jeder in JavaScript zulässige Funktionsname als Hook-Name verwendet werden. Die Datei README.plugins führt eine (unvollständige) Liste von zur Verfügung stehenden Hooks auf. Ein Beispiel für einen wichtigen Hook, welcher u.a. nicht aufgeführt wird, ist aceCreateDomLine, doch dazu später mehr.

Serverseitige vs. clientseitige Plugin-Teile

Etherpad führt Code auf dem Server, als auch auf dem Client aus. In beiden Fällen kommt JavaScript zum Einsatz. Auf der Serverseite bildet dabei ein Gespann aus dem von den ehemaligen Etherpadentwicklern konzipierten AppJet und einem eigenen JavaScript Micro-Framework die Basis für serverseitiges JavaScript.

Ein Plugin besteht immer wenigstens aus der Datei main.js, im Falle des Plugins heading1 also bsp. etherpad/src/plugins/heading1/main.js. Diese wird auf der Serverseite geladen und ausgeführt. Diese Datei muss einen Objektkonstruktur enthalten, welcher der folgenden Benennungsvorschrift entsprechen muss

PluginNameInit

Im Falle des Plugins heading1 heißt der Objektkonstruktur als heading1Init und ist im folgenden als Bestandteil der Datei heading1/main.js dargestellt.

import("etherpad.log");
import("plugins.heading1.hooks");
import("plugins.heading1.static.js.main");

function heading1Init() {
  this.hooks = ['editBarItemsLeftPad', 'aceAttribsToClasses',
  'aceCreateDomLine'];
  this.description = 'heading1';
  this.client = new main.heading1Init();
  this.editBarItemsLeftPad = hooks.editBarItemsLeftPad;
  this.aceAttribsToClasses = main.aceAttribsToClasses;
  this.aceCreateDomLine = main.aceCreateDomLine;
  this.install = install;
  this.uninstall = uninstall;
}

function install() {
  log.info("Installing heading1");
}

function uninstall() {
  log.info("Uninstalling heading1");
}

Die Aufgaben des Objektkonstrukturs sind u.a.

  • Registrierung für bestehende Hook-Calls (this.hooks = …)
  • Definitionen der Hook-Funktionen, welche bei einem entsprechen Hook-Call aufgerufen werden (z.B. this.editBarItemsLeftPad = … oder this.aceAttribsToClasses = …)

An dieser Stelle kann man bereits die ersten Unterschiede zwischen serverseitigem und clientseitigem JavaScript Code erkennen. Vergleicht man einmal die Definitionen der beiden Hooks editBarItemsLeftPad und aceAttribsToClasses

  • this.editBarItemsLeftPad = hooks.editBarItemsLeftPad;
  • this.aceAttribsToClasses = main.aceAttribsToClasses;

so fällt einem auf, dass die eine Funktion unterhalb von hooks und die andere unterhalb von main definiert wird. Die entsprechenden Funktionsdefinitionen finden sich in den Dateien heading1/hooks.js und heading1/static/js/main.js.

Bei editBarItemsLeftPad handelt es sich um serverseitigen JavaScript Code, bei aceAttribsToClasses handelt es sich um Code, welcher sowohl client-, als auch serverseitig ausgeführt wird.

An dieser Stelle wird es zugegebener Maßen etwas unübersichtlich, dennoch ist es die gängige Praxis bei den bestehenden Etherpad-Plugins. Die Hauptplugindatei main.js inkludiert sowohl die Datei hooks.js mit dem serverseitigen Code, als auch die Datei static/js/main.js, welche den clientseitgen JavaScript Code beinhaltet. Dazu der entsprechende Abschnitt aus der Datei README.plugins, welcher diesen Sachverhalt relativ präzise auf den Punkt bringt.

The hook system is replicated on the client side - there is a
plugins.callHook() and plugins can register client side functions to
be called when that hook is called for.

This registration is done from a client side java script file called
static/js/main.js. There is one major catch with this file: It runs
both on the server and client! Beware!

static/js/main.js is modelled after the main main.js, and imported by
that one on the server. The descriptor object created by
pluginNameInit() in static/js/main.js is also included as a property
on the main plugin descriptor object.

Beispiel: Aufruf von heading1 auf der Serverseite

  1. src/ethpad/plugins/headin1/main.js wird aufgerufen
  2. src/ethpad/plugins/headin1/hooks.js wird inkludiert
  3. src/ethpad/plugins/headin1/static/js/main.js wird inkludiert
  4. Mit Hilfe des Objektkonstruktors heading1Init wird ein neues Objekt mit den folgenden Eigenschaften erstellt
    1. hooks = [‚editBarItemsLeftPad‘, ‚aceAttribsToClasses‘, ‚aceCreateDomLine‘];
    2. description = ‚heading1‘;
    3. client = new main.heading1Init();
    4. editBarItemsLeftPad = hooks.editBarItemsLeftPad;
    5. aceAttribsToClasses = main.aceAttribsToClasses;
    6. aceCreateDomLine = main.aceCreateDomLine;
    7. install = install;
    8. uninstall = uninstall;

Beispiel: Aufruf von heading1 auf der Clientseite

  1. src/etherpad/plugins/heading1/static/js/main.js wird aufgerufen
  2. Mit Hilfe des Objektkonstrukturs in DIESER Datei wird ein neues Objekt mit den folgenden Eigenschaften erstellt
    1. hooks = [‚aceAttribsToClasses‘, ‚aceCreateDomLine‘, ‚collectContentPre‘, ‚collectContentPost‘];
    2. aceAttribsToClasses = aceAttribsToClasses;
    3. aceCreateDomLine = aceCreateDomLine;
    4. collectContentPre = collectContentPre;
    5. collectContentPost = collectContentPost;

Vergleicht man nun einmal die Eigenschaft hooks der beiden Objekte, so fällt auf, dass es genau drei verschiedene Arten von Plugincode gibt

  1. Code welcher nur auf der Serverseite ausgeführt wird
    • z.B. editBarItemsLeftPad
  2. Code, welcher nur auf der Clientseite ausgeführt wird
    • z.B. collectContentPre und collectContentPost
  3. Code, welcher sowohl auf der Server- also auch auf der Clientseite ausgeführt wird
    • z.B. aceAttribsToClasses

Man erkennt ebenfalls gut, wie der clientseitge Code serverseitig über die Eigenschaft client eingebunden wird, nämlich via client = new main.heading1Init().

Es gilt nun, anhand der bestehenden Plugins die Verwendung der bestehenden Hook-Calls, wie etwa editBarItemsLeftPad, aceAttribsToClasses oder aceCreateDomLine zu analysieren.

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: