Skripte mit Hilfe von HTTP und ZIP updaten

Die Bibliothek Extendables von Stijn Debrouwere ist inzwischen schon fast ein Jahr im Orbit. Die Idee ist ExtendScript, also die JavaScript Implementierung fast aller Adobe Anwendungen, mit nützlichen Funktionen zu erweitern. Die Klassiker sind die Erweiterungen der Klassen String und Array, wie z.B. die Methoden "Zeichenkette".trim() oder "Zeichenkette„.contains().

Neben vielen Erweiterungen und Gimmicks fällt vor allem das HTTP-Modul auf. Die bekannten Verfahren über das Socket-Objekt sind eher aufwändig und man braucht ein gesundes Verständnis des Protokolls HTTP. Mit Extendables kann man sich die Programmierung auf dieser Ebene sparen und direkt mit Webdiensten kommunizieren. Das Minimalbeispiel aus der Dokumentation zeigt das Vorgehen recht gut:

#include "extendables/extendables.jsx";
var http = require("http");
var response = http.get("http://www.w3c.org");
if (response.status == 200) {
    $.writeln(response.body);
} else {
    $.writeln("Connection failed");
}

Mit Hilfe der #include Direktive wird die Bibliothek extendables.jsx eingebunden. Das http-Package wird über die Funktion require() aktiviert und kann dann über die Variable http angesprochen werden. Mit get() kann eine Webadresse abgerufen werden, das Ergebnis der Abfrage wird hier in der Variable response gespeichert, sie enthält in der Eigenschaft body die Website. In der hier nicht verwendeten Eigenschaft headers sind weitere Statusinformationen enthalten. Weitere Infos zum Thema HTTP spukt die gute alte Wikipedia recht übersichtlich aus.

Ich möchte hier ein Praxisbeispiel vorstellen. Gerade komplexe Skripte laufen manchmal nicht ganz fehlerfrei und brauchen ab und zu ein Update. Wenn diese Skripte dann noch auf verschiedenen Rechnern verteilt sind, wäre es praktisch, diese über ein Webupdate zu aktualisieren – ganz so wie das inzwischen jede moderne Software macht.

Für das Skript braucht man natürlich einen Webserver. Das Beispiel arbeitet mit einem Skript, das die Uhrzeit bei Erstellung anzeigt. Es wird minütlich auf meinem Server per Cronjob aktualisiert und bietet somit ein ideales Beispiel für das Update-Skript.

Das eigentliche Skript ist in der Funktion checkForUpdates() enthalten, die mit den Parametern _updateServerURL und _updateFile gesteuert werden kann.

checkForUpdates ("http://www.publishingx.de/software/test/", "package.zip");

Der aktuelle Pfad wird ebenfalls mit Hilfe der Extendables Erweiterung component()ermittelt.

var _path = Folder(File($.fileName).component('path'));

Die Versionsprüfung wird anhand des Inhalts der Textdatei etag.txt vorgenommen. Im sogenannten ETag speichert der Webserver die Versionsinformation einer Datei, dies wird z.B. beim Caching von Webbrowsern eingesetzt. Später beim Update speichert das Skript die Information, hier wird sie zunächst ausgelesen, um sie später mit der Version auf dem Server zu vergleichen. Das ist nicht der eigentliche Umgang mit dem HTTP Etag (304 Modified). Laut HTTP Standard sollte der Client anfragen, ob es überhaupt notwendig ist die neue Datei zu laden. Da dies in Extendables aber nicht implementiert ist, werde ich diesen Vergleich lokal vornehmen (und muss deswegen in jedem Fall den Header herunterladen).

var _etagFile =  File (_path + "/etag.txt");
if (_etagFile.open("r")) {
	var _localEtag = _etagFile.read();
	_etagFile.close();
}

Zunächst wird geprüft, ob eine Internet-Verbindung besteht:

if( http.has_internet_access()) { /* ... */ }

Da eine ZIP-Datei heruntergeladen werden soll, muss die Anfrage im Binary-Mode vorgenommen werden. Deswegen muss der HTTPRequest selber gebaut werden, was aber kein Problem ist. Wichtig ist, das encoding des Requests auf BINARY zu stellen.

var _updateURL = _updateServerURL + _updateFile;
var _req = new http.HTTPRequest("GET", _updateURL);
_req.encoding("BINARY");
var _resp = _req.do();

In _resp ist nun die Antwort enthalten. Zunächst wird anhand der Eigenschaft status geprüft, ob die Daten vorhanden sind.

if( _resp.status == 200 ) { /* ... */ }

Wenn wir Daten erhalten haben, wird das ETag aus dem Header geladen und mit dem lokalen ETag verglichen.

var _remoteETag = _resp.headers["ETag"]; // == _resp.headers.ETag;
if (_remoteETag != _localEtag) { { /* ... */ }

Sollte eine veränderte Version vorliegen, muss diese gespeichert und entpackt werden. Dazu wird zunächst die ZIP-Datei in den TEMP-Folder geschrieben und dann mit app.unpackageUCF() entpackt. Beim Schreiben muss man natürlich wieder das richtige encoding einstellen.

var _zipFile = new File (Folder.temp + "/temp.zip");
_zipFile.encoding = "BINARY";
if (_zipFile.open("w")) {
	_zipFile.write(_resp.body);
	_zipFile.close();
}
// ...
app.unpackageUCF(_zipFile, _path);

Im Zip-Archiv befindet sich die Datei scriptToUpdate.jsx die dann automatisch auf dem lokalen Rechner überschrieben wird. Achten Sie darauf, dass InDesign unter Windows7 im Anwedungsordner normalerweise keine Schreibrechte hat, das Skript empfiehlt sich also für den Benutzer-Ordner.

Das Update Skript inklusive Extendables kann natürlich auch vollständig heruntergeladen werden: checkForUpdates.zip Leider ist in der aktuellen Version von Extendables ein bei er Verarbeitung von Binary-Daten enthalten. Stijn hat zugesagt diesen bald zu beheben, solange muss man leider noch die im Download-Archiv enthaltene und von mir gepatchte Version verwenden!