Spielen mit der HTML5-Dateisystem-API
German (Deutsch) translation by Katharina Grigorovich-Nevolina (you can also view the original English article)
HTML5 bietet uns eine ganze Reihe neuer Möglichkeiten, z. B. Zeichnen mit Leinwand, Implementieren von Multimedia mit den Audio- und Video-APIs usw. Eines dieser noch relativ neuen Werkzeuge ist die Dateisystem-API. Dadurch erhalten wir Zugriff auf einen Sandbox-Bereich des lokalen Dateisystems des Benutzers, wodurch die Lücke zwischen Desktop- und Webanwendungen noch weiter geschlossen wird! Im heutigen Tutorial werden wir die Grundlagen dieser neuen und aufregenden API durchgehen und die häufigsten Dateisystemaufgaben untersuchen. Lassen Sie uns anfangen!
Einführung
Wir müssen eine bestimmte Software nicht mehr herunterladen und installieren, um sie verwenden zu können. Mit einem Webbrowser und einer Internetverbindung können wir jede Webanwendung jederzeit, überall und auf jeder Plattform verwenden.
Kurz gesagt, Web-Apps sind cool. Im Vergleich zu Desktop-Apps weisen sie jedoch immer noch eine erhebliche Schwäche auf: Sie können nicht interagieren und Daten in einer strukturierten Hierarchie von Ordnern organisieren - einem echten Dateisystem. Glücklicherweise kann dies mit der neuen Dateisystem-API geändert werden. Diese API ermöglicht Webanwendungen den kontrollierten Zugriff auf eine "Sandbox" eines privaten lokalen Dateisystems, in der sie Dateien schreiben und lesen, Verzeichnisse erstellen und auflisten usw. können. Obwohl zum Zeitpunkt dieses Schreibens nur der Chrome-Browser von Google die "vollständige" Implementierung der Dateisystem-API unterstützt, verdient es dennoch, als leistungsstarke und bequeme Form des lokalen Speichers untersucht zu werden.
Die Dateisystem-API gibt es in zwei verschiedenen Versionen. Die asynchrone API, die für normale Anwendungen nützlich ist, und die synchrone API, die für die Verwendung mit Web-Workern reserviert ist. In diesem Tutorial wird ausschließlich die asynchrone Version der API untersucht.
Schritt 1 - Erste Schritte
Der erste Schritt besteht darin, Zugriff auf das HTML5-Dateisystem zu erhalten, indem Sie ein LocalFile System-Objekt mit der globalen Methode window.requestFileSystem() anfordern:
1 |
window.requestFileSystem(type, size, successCallback, opt_errorCallback) |
Es gibt keine Möglichkeit für eine Webanwendung, über das lokale Stammverzeichnis hinaus "auszubrechen".
Als erste beiden Parameter geben Sie die Lebensdauer und Größe des gewünschten Dateisystems an. Ein PERSISTENT-Dateisystem eignet sich für Web-Apps, die Benutzerdaten dauerhaft speichern möchten. Der Browser löscht es nur auf ausdrückliche Anfrage des Benutzers. Ein TEMPORARY-Dateisystem eignet sich für Webanwendungen, die Daten zwischenspeichern möchten, aber dennoch funktionieren können, wenn der Webbrowser das Dateisystem löscht. Die Größe des Dateisystems wird in Byte angegeben und sollte eine angemessene Obergrenze für die zu speichernde Datenmenge sein.
Der dritte Parameter ist eine Rückruffunktion, die ausgelöst wird, wenn der Benutzeragent erfolgreich ein Dateisystem bereitstellt. Das Argument ist ein FileSystem-Objekt. Zuletzt können wir eine optionale Rückruffunktion hinzufügen, die aufgerufen wird, wenn ein Fehler auftritt oder die Anforderung eines Dateisystems abgelehnt wird. Das Argument ist ein FileError-Objekt. Obwohl dieser Parameter optional ist, ist es immer eine gute Idee, Fehler für Benutzer abzufangen, da es eine Reihe von Stellen gibt, an denen Fehler auftreten können.
Das mit diesen Funktionen erhaltene Dateisystem hängt von der Herkunft des enthaltenen Dokuments ab. Alle Dokumente oder Web-Apps desselben Ursprungs (Host, Port und Protokoll) teilen sich ein Dateisystem. Zwei Dokumente oder Anwendungen unterschiedlicher Herkunft haben völlig unterschiedliche und nicht zusammenhängende Dateisysteme. Ein Dateisystem ist auf eine einzelne Anwendung beschränkt und kann nicht auf die gespeicherten Daten einer anderen Anwendung zugreifen. Es ist auch von den restlichen Dateien auf der Festplatte des Benutzers isoliert, was eine gute Sache ist: Es gibt keine Möglichkeit für eine Webanwendung, über das lokale Stammverzeichnis hinaus "auszubrechen" oder auf andere Weise auf beliebige Dateien zuzugreifen.
Sehen wir uns ein Beispiel an:
1 |
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; |
2 |
|
3 |
window.requestFileSystem(window.TEMPORARY, 5*1024*1024, initFS, errorHandler); |
4 |
|
5 |
function initFS(fs){
|
6 |
alert("Welcome to Filesystem! It's showtime :)"); // Just to check if everything is OK :)
|
7 |
// place the functions you will learn bellow here |
8 |
} |
9 |
|
10 |
function errorHandler(){
|
11 |
console.log('An error occured');
|
12 |
} |
Dadurch wird ein temporäres Dateisystem mit 5 MB Speicher erstellt. Es bietet dann eine erfolgreiche Rückruffunktion, mit der wir unser Dateisystem betreiben. Natürlich wird auch ein Fehlerbehandler hinzugefügt - für den Fall, dass etwas schief geht. Hier ist die Funktion errorHandler() zu allgemein. Wenn Sie möchten, können Sie eine leicht optimierte Version erstellen, die dem Leser eine aussagekräftigere Fehlermeldung gibt:
1 |
function errorHandler(err){
|
2 |
var msg = 'An error occured: '; |
3 |
|
4 |
switch (err.code) {
|
5 |
case FileError.NOT_FOUND_ERR: |
6 |
msg += 'File or directory not found'; |
7 |
break; |
8 |
|
9 |
case FileError.NOT_READABLE_ERR: |
10 |
msg += 'File or directory not readable'; |
11 |
break; |
12 |
|
13 |
case FileError.PATH_EXISTS_ERR: |
14 |
msg += 'File or directory already exists'; |
15 |
break; |
16 |
|
17 |
case FileError.TYPE_MISMATCH_ERR: |
18 |
msg += 'Invalid filetype'; |
19 |
break; |
20 |
|
21 |
default: |
22 |
msg += 'Unknown Error'; |
23 |
break; |
24 |
}; |
25 |
|
26 |
console.log(msg); |
27 |
}; |
Das Dateisystemobjekt, das Sie erhalten, hat einen name (einen eindeutigen Namen für das vom Browser zugewiesene Dateisystem) und eine root-Eigenschaft, die auf das Stammverzeichnis des Dateisystems verweist. Dies ist ein DirectoryEntry-Objekt, und es kann verschachtelte Verzeichnisse enthalten, die selbst durch DirectoryEntry-Objekte dargestellt werden. Jedes Verzeichnis im Dateisystem kann Dateien enthalten, die durch FileEntry-Objekte dargestellt werden. Das DirectoryEntry-Objekt definiert Methoden zum Abrufen von DirectoryEntry- und FileEntry-Objekten anhand des Pfadnamens (sie erstellen optional neue Verzeichnisse oder Dateien, wenn Sie einen Namen angeben, der nicht vorhanden ist). DirectoryEntry definiert auch eine Factory-Methode createReader(), die ein DirectoryReader-Objekt zum Auflisten des Inhalts eines Verzeichnisses zurückgibt. Die FileEntry-Klasse definiert eine Methode zum Abrufen des File-Objekts (eines Blobs), das den Inhalt einer Datei darstellt. Sie können dann ein FileReader-Objekt verwenden, um die Datei zu lesen. FileEntry definiert eine andere Methode zum Zurückgeben eines FileWriter-Objekts, mit dem Sie Inhalte in eine Datei schreiben können.
Phuh ... klingt kompliziert? Mach dir keine Sorgen. Alles wird klarer, wenn wir die folgenden Beispiele durchgehen.
Schritt 2 - Arbeiten mit Verzeichnissen
Das erste, was Sie in einem Dateisystem erstellen müssen, sind natürlich einige Buckets oder Verzeichnisse. Obwohl das Stammverzeichnis bereits vorhanden ist, möchten Sie nicht alle Ihre Dateien dort ablegen. Verzeichnisse werden vom DirectoryEntry-Objekt erstellt. Im folgenden Beispiel erstellen wir ein Verzeichnis mit dem Namen Documents im Stammverzeichnis:
1 |
fs.root.getDirectory('Documents', {create: true}, function(dirEntry) {
|
2 |
alert('You have just created the ' + dirEntry.name + ' directory.');
|
3 |
}, errorHandler); |
Die Methode getDirectory() wird zum Lesen und Erstellen von Verzeichnissen verwendet. Als ersten Parameter können Sie entweder einen Namen oder einen Pfad als Verzeichnis zum Nachschlagen oder Erstellen übergeben. Wir setzen das zweite Argument auf true, weil wir versuchen, ein Verzeichnis zu erstellen - kein vorhandenes zu lesen. Und am Ende fügen wir einen Fehlerrückruf hinzu.
So weit, ist es gut. Wir haben ein Verzeichnis; Fügen wir nun ein Unterverzeichnis hinzu. Die Funktion ist mit einem Unterschied genau gleich: Wir ändern das erste Argument von 'Dokumente' in 'Dokumente/Musik'. Leicht genug; Was aber, wenn Sie einen Unterordner, Sky, mit zwei übergeordneten Ordnern, Images und Nature, im Ordner Documents erstellen möchten? Wenn Sie für das Pfadargument 'Documents/Images/Nature/Sky' eingeben, wird eine Fehlermeldung angezeigt, da Sie kein Verzeichnis erstellen können, wenn das unmittelbare übergeordnete Verzeichnis nicht vorhanden ist. Eine Lösung hierfür besteht darin, jeden Ordner einzeln zu erstellen: Images in Documents, Nature in Images und dann Sky in Nature. Dies ist jedoch ein sehr langsamer und unpraktischer Prozess. Es gibt eine bessere Lösung: eine Funktion zu erstellen, die alle erforderlichen Ordner automatisch erstellt.
1 |
function createDir(rootDir, folders) {
|
2 |
rootDir.getDirectory(folders[0], {create: true}, function(dirEntry) {
|
3 |
if (folders.length) {
|
4 |
createDir(dirEntry, folders.slice(1)); |
5 |
} |
6 |
}, errorHandler); |
7 |
}; |
8 |
|
9 |
createDir(fs.root, 'Documents/Images/Nature/Sky/'.split('/'));
|
Mit diesem kleinen Trick müssen wir lediglich einen vollständigen Pfad für die Ordner bereitstellen, die wir erstellen möchten. Jetzt wurde das Sky-Verzeichnis erfolgreich erstellt, und Sie können andere Dateien oder Verzeichnisse darin erstellen.
Jetzt ist es Zeit zu überprüfen, was wir in unserem Dateisystem haben. Wir erstellen ein DirectoryReader-Objekt und verwenden die readEntries()-Methode, um den Inhalt des Verzeichnisses zu lesen.
1 |
fs.root.getDirectory('Documents', {}, function(dirEntry){<br>
|
2 |
var dirReader = dirEntry.createReader(); |
3 |
dirReader.readEntries(function(entries) {<br>
|
4 |
for(var i = 0; i < entries.length; i++) {
|
5 |
var entry = entries[i]; |
6 |
if (entry.isDirectory){
|
7 |
console.log('Directory: ' + entry.fullPath);
|
8 |
} |
9 |
else if (entry.isFile){
|
10 |
console.log('File: ' + entry.fullPath);
|
11 |
} |
12 |
} |
13 |
|
14 |
}, errorHandler); |
15 |
}, errorHandler); |
Im obigen Code werden die Eigenschaften isDirectory und isFile verwendet, um eine unterschiedliche Ausgabe für Verzeichnisse bzw. Dateien zu erhalten. Darüber hinaus verwenden wir die Eigenschaft fullPath, um den vollständigen Pfad des Eintrags anstelle seines Namens abzurufen.
Es gibt zwei Möglichkeiten, einen DirectoryEntry aus dem Dateisystem zu entfernen: remove() und removeRecursively(). Das erste entfernt ein bestimmtes Verzeichnis nur, wenn es leer ist. Andernfalls erhalten Sie eine Fehlermeldung.
1 |
fs.root.getDirectory('Documents/Music', {}, function(dirEntry) {
|
2 |
dirEntry.remove(function(){
|
3 |
console.log('Directory successfully removed.');
|
4 |
}, errorHandler); |
5 |
}, errorHandler); |
Wenn der Music-Ordner Dateien enthält, müssen Sie die zweite Methode verwenden, mit der das Verzeichnis und der gesamte Inhalt rekursiv gelöscht werden.
1 |
fs.root.getDirectory('Documents/Music', {}, function(dirEntry) {
|
2 |
dirEntry.removeRecursively(function(){
|
3 |
console.log('Directory successufully removed.');
|
4 |
}, errorHandler); |
5 |
}, errorHandler); |
Schritt 3 - Arbeiten mit Dateien
Jetzt, da wir wissen, wie man Verzeichnisse erstellt, ist es Zeit, sie mit Dateien zu füllen!
Im folgenden Beispiel wird eine leere test.txt im Stammverzeichnis erstellt:
1 |
fs.root.getFile('test.txt', {create: true, exclusive: true}, function(fileEntry) {
|
2 |
alert('A file ' + fileEntry.name + ' was created successfully.');
|
3 |
}, errorHandler); |
Das erste Argument für getFile() kann ein absoluter oder relativer Pfad sein, muss jedoch gültig sein. Beispielsweise ist es ein Fehler, zu versuchen, eine Datei zu erstellen, wenn das unmittelbare übergeordnete Element nicht vorhanden ist. Das zweite Argument ist ein Objektliteral, das das Verhalten der Funktion beschreibt, wenn die Datei nicht vorhanden ist. In diesem Beispiel erstellt create: true die Datei, wenn sie nicht vorhanden ist, und gibt in diesem Fall einen Fehler aus (exklusiv: true). Andernfalls wird bei create: false die Datei einfach abgerufen und zurückgegeben.
Eine leere Datei zu haben ist jedoch nicht sehr nützlich; Fügen wir also einige Inhalte hinzu. Wir können das FileWriter-Objekt dafür verwenden.
1 |
fs.root.getFile('test.txt', {create: false}, function(fileEntry) {
|
2 |
fileEntry.createWriter(function(fileWriter) {
|
3 |
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder; |
4 |
var bb = new BlobBuilder(); |
5 |
bb.append('Filesystem API is awesome!');
|
6 |
fileWriter.write(bb.getBlob('text/plain'));
|
7 |
}, errorHandler); |
8 |
}, errorHandler); |
Oben rufen wir die Datei test.txt ab und erstellen ein FileWriter-Objekt dafür. Anschließend hängen wir Inhalte an, indem wir ein neues BlobBuilder-Objekt erstellen und die write()-Methode von FileWriter verwenden.
Durch Aufrufen von getFile() wird nur ein FileEntry abgerufen. Der Inhalt der Datei wird nicht zurückgegeben. Wenn wir also den Inhalt der Datei lesen möchten, müssen wir das File-Objekt und das FileReader-Objekt verwenden.
1 |
fs.root.getFile('test.txt', {}, function(fileEntry) {
|
2 |
fileEntry.file(function(file) {
|
3 |
var reader = new FileReader(); |
4 |
reader.onloadend = function(e) {
|
5 |
alert(this.result); |
6 |
}; |
7 |
reader.readAsText(file); |
8 |
}, errorHandler); |
9 |
}, errorHandler); |
Wir haben einige Inhalte in unsere Datei geschrieben, aber was ist, wenn Sie zu einem späteren Zeitpunkt weitere hinzufügen möchten? Um Daten an eine vorhandene Datei anzuhängen, wird der FileWriter erneut verwendet. Wir können den Writer mithilfe der seek()-Methode am Ende der Datei neu positionieren. seek akzeptiert einen Byte-Offset als Argument und setzt die Position des Dateischreibers auf diesen Offset.
1 |
fs.root.getFile('test.txt', {create: false}, function(fileEntry) {
|
2 |
fileEntry.createWriter(function(fileWriter) {
|
3 |
fileWriter.seek(fileWriter.length); |
4 |
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder; |
5 |
var bb = new BlobBuilder(); |
6 |
bb.append('Yes, it is!');
|
7 |
fileWriter.write(bb.getBlob('text/plain'));
|
8 |
}, errorHandler); |
9 |
}, errorHandler); |
Um eine Datei aus dem Dateisystem zu entfernen, rufen Sie einfach entry.remove() auf. Das erste Argument für diese Methode ist eine Nullparameter-Rückruffunktion, die aufgerufen wird, wenn die Datei erfolgreich gelöscht wurde. Der zweite ist ein optionaler Fehlerrückruf, wenn Fehler auftreten.
1 |
fs.root.getFile('test.txt', {create: false}, function(fileEntry) {
|
2 |
fileEntry.remove(function() {
|
3 |
console.log('File successufully removed.');
|
4 |
}, errorHandler); |
5 |
}, errorHandler); |
Schritt 4 - Bearbeiten von Dateien und Verzeichnissen
FileEntry und DirectoryEntry verwenden dieselben API-Methoden zum Kopieren, Verschieben und Umbenennen von Einträgen. Es gibt zwei Methoden, die Sie für diese Vorgänge verwenden können: copyTo() und moveTo(). Beide akzeptieren genau die gleichen Parameter:
1 |
copyTo(parentDirEntry, opt_newName, opt_successCallback, opt_errorCallback); |
2 |
|
3 |
moveTo(parentDirEntry, opt_newName, opt_successCallback, opt_errorCallback); |
Der erste Parameter ist der übergeordnete Ordner, in den der Eintrag verschoben/kopiert werden soll. Der zweite ist ein optionaler neuer Name für den verschobenen/kopierten Eintrag, der tatsächlich erforderlich ist, wenn Sie einen Eintrag in denselben Ordner kopieren. Andernfalls wird eine Fehlermeldung angezeigt. Der dritte und vierte Parameter wurden zuvor erläutert.
Sehen wir uns einige einfache Beispiele an. Im Folgenden kopieren wir die Datei test.txt vom root-Verzeichnis in das Verzeichnis Documents.
1 |
function copy(currDir, srcEntry, destDir) {
|
2 |
currDir.getFile(srcEntry, {}, function(fileEntry) {
|
3 |
currDir.getDirectory(destDir, {}, function(dirEntry) {
|
4 |
fileEntry.copyTo(dirEntry); |
5 |
}, errorHandler); |
6 |
}, errorHandler); |
7 |
} |
8 |
|
9 |
copy(fs.root, 'test.txt', 'Documents/'); |
In diesem nächsten Beispiel wird test.txt in Documents verschoben, anstatt es zu kopieren:
1 |
function move(currDir, srcEntry, dirName) {
|
2 |
currDir.getFile(srcEntry, {}, function(fileEntry) {
|
3 |
currDir.getDirectory(dirName, {}, function(dirEntry) {
|
4 |
fileEntry.moveTo(dirEntry); |
5 |
}, errorHandler); |
6 |
}, errorHandler); |
7 |
} |
8 |
|
9 |
move(fs.root, 'test.txt', 'Documents/'); |
Im folgenden Beispiel wird test.txt in text.txt umbenannt:
1 |
function rename(currDir, srcEntry, newName) {
|
2 |
currDir.getFile(srcEntry, {}, function(fileEntry) {
|
3 |
fileEntry.moveTo(currDir, newName); |
4 |
}, errorHandler); |
5 |
} |
6 |
|
7 |
rename(fs.root, 'test.txt', 'text.txt'); |
Erfahren Sie mehr
In diesem Einführungs-Tutorial haben wir nur die Oberfläche der verschiedenen Dateisystemschnittstellen zerkratzt. Wenn Sie mehr erfahren und sich eingehender mit der Dateisystem-API befassen möchten, lesen Sie die W3C-Spezifikationen:
Nachdem Sie nun ein grundlegendes Verständnis dafür haben, was die Dateisystem-API ist und wie sie verwendet werden kann, sollte es wesentlich einfacher sein, die API-Dokumentation zu verstehen, was auf den ersten Blick etwas verwirrend sein kann.
Schlussfolgerung
Die Dateisystem-API ist eine leistungsstarke und benutzerfreundliche Technologie, die Webentwicklern beim Erstellen von Webanwendungen eine ganze Reihe neuer Möglichkeiten bietet. Zugegeben, es ist noch recht neu und wird nicht von allen gängigen Browsern unterstützt, aber dies wird sich sicherlich in Zukunft ändern. Sie könnten genauso gut einen Vorsprung bekommen!





