WsgiDAV ist ein in Python geschriebener WebDAV-Server, der sowohl stand-alone über HTTP oder über WSGI in Kombination mit einem Webserver verwendet werden kann. Die Feature-Liste beinhaltet:
- Vollständige WebDAV-Implementierung inklusive Support für Locking und Properties
- Authentifizierung über PAM, AD oder über einfach in der Konfigurationsdatei hinterlegte Benutzer
- Extrabehandlungen für einige Anwendungen, z.B. MS Office
- Erweiterbarkeit/Plugin-Schnittstelle
Was mich natürlich interessiert ist, ob Dead Properties richtig funktionieren und XML-Namespaces korrekt behandelt werden (nicht so wie bei anderen). Dazu habe ich aus dem WebDAV-RFC den Beispiel-Request aus Section 9.2.2 ausgeführt.
<?xml version="1.0" encoding="utf-8" ?>
<D:propertyupdate xmlns:D="DAV:"
xmlns:Z="http://ns.example.com/standards/z39.50/">
<D:set>
<D:prop>
<Z:Authors>
<Z:Author>Jim Whitehead</Z:Author>
<Z:Author>Roy Fielding</Z:Author>
</Z:Authors>
</D:prop>
</D:set>
<D:remove>
<D:prop><Z:Copyright-Owner/></D:prop>
</D:remove>
</D:propertyupdate>
Dieser speichert eine Property mit XML-Werten, die Namespace-Definition dazu ist allerdings im Root-Element, nicht im Property-Element Z:Authors
. Einige Server-Implementierungen haben mit dieser Art Request Probleme. WsgiDAV hingegen verarbeitet dies problemlos. Man muss allerdings dazu sicherstellen, dass Dead Properties aktiviert sind und persistent gespeichert werden. Dazu muss der property_manager
konfiguriert sein:
# Property Manager
# null: (default) no support for dead properties
# true: Use wsgidav.prop_man.property_manager.PropertyManager
# which is an in-memory property manager (NOT persistent)
#
# Example: Use persistent shelve based property manager
property_manager:
class: wsgidav.prop_man.property_manager.ShelvePropertyManager
kwargs:
storage_path: 'wsgidav-props.shelve'
Ein anderer Test von mir war dav-sync. Auch dies funktioniert problemlos. Bisher war meine Empfehlung für einen Server für dav-sync in der Regel Apache. Wer einen anderen Webserver benutzt, dem kann ich hiermit jetzt WsgiDAV empfehlen.
Die Entwicklung von dav 1.3 ist ganz und gar nicht so verlaufen wie ich mir das vorgestellt hatte. Über ein Jahr später als geplant, konnte ich das Release dann vor kurzem endlich fertig stellen. Ich entwickel das jetzt allerdings im meiner Freizeit, daher variiert es natürlich, wie viel Zeit ich in die Entwicklung stecke. Trotzdem muss ich zugeben, dass die Zeit, die ich investiert habe, nicht unbedingt optimal genutzt wurde.
Schauen wir uns zunächst ein paar Zahlen der Entwicklung an.
version date loc new-loc n-month lines/month
---------------------------------------------------------------
begin 2012-11-30 0 lines 0 0 0
1.0.0 2017-08-13 14633 lines 14633 57 256
1.1.0 2017-10-07 15416 lines 783 2 391
1.2.0 2018-06-26 20465 lines 5049 8 631
1.3.0 2019-12-15 29743 lines 9278 18 515
Für die erste Version von dav habe ich natürlich relativ lange gebraucht. Es ist vermutlich deutlich einfacher, an eine bestehende Software, die man auch noch gut kennt, neues Zeug ranzubasteln, als etwas komplett neues aus dem Boden zu stampfen.
Bei der Entwicklung von dav 1.2 war ich am produktivsten. Aber wenn man nur nach lines/month geht, dann war dav 1.3 eigentlich kein Desaster (im Vergleich zu den anderen Releases). Was man aber sieht ist, dass sich die Code-Basis drastisch vergrößert hat. Viel zu viele neue Features haben den Weg in das Release gefunden. Genau das ist eines der Probleme.
Das größte Ärgernis dabei war, dass ich zwar relativ zügig neue Features implementiert hatte, aber sobald die minimal irgendwie funktioniert haben, bin ich zum nächsten neuen Feature übergegangen. Am Ende hatte ich haufen Neues, das aber nicht gut funktionierte.
Dazu kam auch ein größeres Refactoring der dav-sync Codebasis. Dieses hat natürlich bereits bestehende Funktionalität, die fehlerfrei lief, auch etwas zerstört.
Am Ende war ich Monatelang damit beschäftig, massenweise Tests zu schreiben, um überhaupt wieder Vertrauen in den Code zu entwickeln. Dies war jedoch recht erfolgreich, die Grundfunktionalität dürfte stabiler als vorher sein.
Was ich hoffentlich daraus gelernt habe: Dinge zuende entwickeln. Keine halben Sachen liegen lassen. Und am besten schließt man die Entwicklung eines neuen Features ab, in dem man diverse Tests dafür geschrieben hat, die alle problemlos durchlaufen.
Auch sollte man sich nicht zu viel auf einmal vornehmen. Ich hoffe ich kann den Spruch Release early, release often in Zukunft mehr anwenden. Das hat durchaus seine Vorteile.
Mit dav-sync ist es nun möglich, Symlinks zu synchronisieren. Eine Besonderheit dabei ist, dass unter Windows Verknüpfungen von dav-sync als Symlink interpretiert werden. Windows hat zwar auch richtige Symlinks, die sind aber Mist, da normale Benutzer beispielsweise keine anlegen dürfen.
Beim Synchronisieren wird dann für die Verknüpfung oder Symlink eine leere Resource angelegt und die relative Adresse des Targets in einer WebDAV-Property gespeichert. Damit hat man ein automatisches Mapping zwischen Symlinks und Windows-Verknüpfungen. Wer also gerne über Links seine Dateien organisiert, kann dies nun auch plattformübergreifend synchronisieren. Das gibt es sonst nirgends.
Konfigurieren muss man dafür nicht viel. In die sync.xml muss innerhalb des <directory>
-Elements nur folgendes eingefügt werden:
<directory>
...
<symlink-intern>sync</symlink-intern>
</directory>
Synchronisiert werden können dabei nur Links, die ein Target innerhalb des Sync-Directory haben. Links mit externen Targets können aus Sicherheitsgründen nicht synchronisiert werden.
Neben sync
gibt es für das <symlink-intern>
-Element noch zwei Werte, die angegeben werden können. Zum einen follow
, womit angegeben wird, dass Symlinks gefolgt werden soll. Das Ziel wird als normale Datei hochgeladen. Dies ist auch der Default. Die andere Möglichkeit ist ignore
, womit dav-sync dann Symlinks nicht mehr folgt, sondern ignoriert.
Es gibt auch noch das Element <symlink-extern>
für Links mit einem externen Target. Hier sind die Werte follow
und ignore
möglich. Wie erwähnt ist sync
, also das Synchronisieren von externen Links, nicht möglich.
Seit dav-sync 1.3 gibt es das Feature, dass Dateien nicht nur als Ganzes synchronisiert werden können, sondern auch nur Teile einer Datei. Hierfür wird für die Datei eine feste Block-Größe definiert und die Datei wird dann in mehrere Blöcke mit dieser Größe eingeteilt. Lokal wird dann pro Block ein Hash gespeichert, so dass erkannt werden kann, welcher Block geändert wurde und synchronisiert werden muss. Auf dem Server wird statt der Datei eine Collection angelegt, in der die einzelnen Blöcke abgelegt werden.
Das Ganze funktioniert natürlich auch Problemlos mit Verschlüsselung. Jeder Block wird als eigenständige Datei hochgeladen und dabei wie sonst auch mit AES im CBC-Mode verschlüsselt.
Natürlich muss man, um das Feature zu nutzen, mal wieder etwas konfigurieren. Zunächst sollte man sich überlegen, welche Dateien überhaupt zerlegt werden sollen. Mögliche Konfigurationsmöglichkeiten sind zum einen, Dateien mit bestimmten Namen oder Pfaden auszuwählen, zum anderen kann auch eine Mindestdateigröße angegeben werden, ab der Dateien aufgeteilt werden sollen.
Die andere Sache, die man sich überlegen muss, ist, welche Größe die einzelnen Blöcke haben sollen. Diese sollte nicht zu klein sein, da sonst viele tausende kleine Dateien auf dem Server abgelegt werden könnten.
Die Konfiguration erfolgt dann in der Datei sync.xml. Hier muss innerhalb des Sync-Directory folgendes eingefügt werden:
<directory>
...
<splitconfig>
<!-- split all files with .vmdk file extension that are bigger than 100mb -->
<split>
<blocksize>10m</blocksize>
<filter>
<include>\.vmdk$</include>
</filter>
<minsize>100m</minsize>
</split>
</splitconfig>
</directory>
Pflichtangabe ist das Element <blocksize>
. Damit wird die Blockgröße in Bytes, alternativ auch mit einem Suffix wie k, m oder g, angegeben. Dazu muss entweder <minsize>
oder ein <filter>
definiert sein. Beides zusammen geht natürlich auch.
Mit <filter>
können Regex-Filter, die auf den Dateipfad angewendet werden, konfiguriert werden. Möglich sind zum einen <include>
-Filter, die zunächst definieren, welche Dateien ausgewählt werden sollen. Dies kann dann noch mit <exclude>
eingeschränkt werden. Die Doku enthält dazu mehr Details.
Mit <minsize>
gibt man die Größe an, die eine Datei mindestens haben muss, damit sie aufgeteilt wird. Gibt man nur dieses Element an, ohne <filter>
, kann man also pauschal alle Dateien ab einer bestimmten Größe splitten.
Es können auch mehrere split-Regeln definiert werden. Dazu muss nur innerhalb des Elements <splitconfig>
ein weiteres <split>
-Element eingefügt werden.
Mehr als die Konfiguration muss man dann auch nicht tun. An der Benutzung von dav-sync ändert sich nichts. Wenn alles korrekt konfiguriert ist, werden dann die entsprechenden Dateien aufgeteilt und nur geänderte Blöcke der Datei werden übertragen. Der Benutzer kriegt davon erstmal nichts mit, außer dass es vielleicht schneller geht.
Eine Einschränkung gibt es allerdings. Datei-Versionierung funktioniert nicht mit gesplitteten Dateien. Genauso funktioniert es nicht, wenn <backup-on-pull>true</backup-on-pull>
für das Sync-Directory konfiguriert ist, dass bei jeder Dateiänderung ein Backup der Datei in das Trash-Verzeichnis verschoben wird. Der Grund ist, dass nicht mehr kostengünstig über ein move die Datei gesichert werden kann, weil keine komplette Datei übertragen wird.
Erwähnenswert wäre auch noch, dass Datei-Splitting ohne Konfiguration der sync.xml möglich wäre, denn es kann im Prinzip pro Datei einzelnd festgelegt werden, ob diese gesplittet werden soll. Einfache dav-sync-Befehle, um das festzulegen, haben es jedoch nicht in das Release geschafft und ohne Befehle wäre das zu Hacky für diesen Artikel.
Ich habe in dav-sync von Anfang an die Datensicherheit an erste Stelle gestellt. Geprägt von Horrorgeschichten über Synchronisations-Programme, die massenweise Daten niedergemetzelt haben, wollte ich mir und allen dav-sync Usern dies ersparen. Daher gab es schon von Anfang an als zusätzliche Sicherung das Trash-Verzeichnis, in das Dateien verschoben wurden, statt sie zu löschen.
Neu ist jetzt das Versioning-Feature, womit auf dem Server mehrere Versionen von Dateien gespeichert werden können. Dies ist in zwei Varianten implementiert. Einmal über einfache Kopien von den Dateien, die in einer versteckten Collection abgelegt werden. Die andere Variante nutzt die WebDAV-Erweiterung DeltaV, wofür es jedoch praktisch keine Server-Implementierungen gibt. Noch nicht ;-)
Im Folgenden möchte ich die Konfiguration und die Benutzung der dav-sync Versionierung erläutern.
Zunächst benötigt man natürlich erst einmal eine Konfiguration für ein Verzeichnis, welches synchronisiert werden soll. In diesem Artikel habe ich die Einrichtung und grundlengende Benutzung von dav-sync erklärt. Für die Versionierung muss nur noch die Konfiguration (sync.xml) etwas angepasst werden. Hierfür muss nur innerhalb des <directory>
-Elements folgendes ergänzt werden:
<versioning type="simple" always="true" />
Das type="simple"
steht für die Versionierung über einfache Kopien der Dateien. Alternativ könnte hier statt simple
auch deltav
stehen. Hierfür benötigt man natürlich einen DeltaV-kompatiblen Server. Apache, Nextcloud oder praktisch alle anderen WebDAV-Server unterstützten dies jedoch nicht.
Das Attribut always="true"
gibt an, dass bei jedem Hochladen einer Datei eine neue Version angelegt werden soll. Hier könnte natürlich auch false
angegeben werden, dann wird nur eine neue Version gespeichert, wenn dies manuell angefordert wird. Hierfür gibt es für die dav-sync Kommandos push und archive die Option -S
. Wird diese angegeben, wird für jede hochgeladene Datei die vorherige Version gespeichert.
Damit ist die Konfiguration auch schon abgeschlossen. Alte Versionen von Dateien werden standardmäßig in der Collection .dav-version-history (unterhalb des Sync-Directory) abgelegt. Wer will könnte noch den Speicherort der Versions-History mit dem <history>
-Element anpassen.
<versioning type="simple" always="true">
<history>/.backup/</history>
</versioning>
Jetzt werden immer alte Versionen von Dateien gespeichert. Wer hingegen always="false"
benutzt, kann beim Uploaden von Dateien folgendermaßen die Version speichern:
$ dav push -S mydir
Um jetzt alte Versionen von Dateien wiederherzustellen, benötigt man zwei dav-sync Befehle: list-versions und restore. Beide Befehle arbeiten mit lokalen Dateipfaden, man kann daher einen absoluten oder relativen Pfad zu der Datei angeben und dav-sync bestimmt dann automatisch das passende Sync-Directory dazu (wenn es eindeutig ist, ansonsten muss man es mit der Option -s
angeben). Als Beispiel gehen wir davon aus, dass wir uns in der Shell im Verzeichnis befinden, in dem die Datei liegt.
$ dav-sync list-versions test.txt
name: 1576420562-69ba29c77659c2db
lastmodified: Sun, 15 Dec 2019 10:39:30 GMT
url: https://example.com/webdav/.history/157/1576420562-69ba29c77659c2db
name: 1576420572-b6a1862b0b1c04e6
lastmodified: Sun, 15 Dec 2019 11:12:28 GMT
url: https://example.com/webdav/.history/157/1576420572-b6a1862b0b1c04e6
name: 1576420613-8b1260a7a6943580
lastmodified: Sun, 15 Dec 2019 15:33:06 GMT
url: https://example.com/webdav/.history/157/1576420613-8b1260a7a6943580
list-versions zeigt alle verfügbaren vorherigen Versionen einer Datei an. Was wir aus der Ausgabe benötigen ist das Feld name. Den Namen können wir bei dem Befehl restore benutzen, um diese Version wiederherzustellen.
$ dav-sync restore -V 576420572-b6a1862b0b1c04e6 test.txt
get: /test.txt
Result: 1 file pulled, 0 errors
Dies downloaded die angegebene Version und ersetzt damit die lokale Datei.
Eine Einschränkung ist noch, dass es keine einfache Möglichkeit gibt, gelöschte Dateien wiederherzustellen. Wenn man versehentlich eine Datei löscht, kann man unmittelbar danach zwar mit dav-sync restore -s <syncdir>
alle gelöschten Dateien wiederherstellen. Dies funktioniert jedoch nicht mehr, sobald man nach dem Löschen den Befehl push ausführt und somit auf dem Server die Datei gelöscht wird. Dann ist die Datei zwar noch in der History vorhanden, es gibt jedoch keinen Befehl, der die Datei wiederherstellen kann. Da bleibt einem nur übrig, die gesamte History zu durchsuchen.