Je pense souvent qu'il serait très pratique si les applications Web pouvaient lire et écrire des fichiers et des répertoires. Après être passées du mode hors ligne au mode en ligne, les applications sont devenues plus complexes et le manque d'API dans le système de fichiers a entravé la progression du réseau. Le stockage ou l'interaction avec des données binaires ne doivent pas être limités au bureau. Heureusement, cette situation a finalement changé grâce à l'émergence de FileSystemAPI. Avec FileSystemAPI, les applications Web peuvent créer, lire, naviguer et écrire dans des parties en bac à sable du système de fichiers local de l'utilisateur.
API est divisée en différentes rubriques :
Lecture et traitement des fichiers : File
/Blob
, FileList
, FileReader
Créer et écrire : BlobBuilder
, FileWriter
Accès aux répertoires et au système de fichiers : DirectoryReader
, FileEntry
/DirectoryEntry
, LocalFileSystem
Au moment de la rédaction de cet article, il n'existe que le navigateur GoogleChrome Ce FileSystemAPI peut être implémenté. Il n'existe actuellement aucune interface utilisateur de navigateur dédiée à la gestion des quotas de fichiers /. Pour stocker des données sur le système de l'utilisateur, votre application devra peut-être demander des quotas. Cependant, vous pouvez utiliser la balise --unlimited-quota-for-files
pour exécuter le navigateur Chrome à des fins de test. De plus, si vous développez une application ou une extension pour le Chrome Web Store, vous pouvez utiliser les autorisations du fichier manifeste unlimitedStorage
sans demander de quotas. Enfin, l'utilisateur reçoit une boîte de dialogue d'autorisation pour accorder, refuser ou ajouter du stockage à l'application.
Si vous souhaitez déboguer votre application via file://
, vous aurez peut-être besoin de la balise --allow-file-access-from-files
. La non-utilisation de ces balises entraîne SECURITY_ERR
ou QUOTA_EXCEEDED_ERR
FileError.
Une application Web peut demander l'accès au système de fichiers sandbox en appelant window.requestFileSystem() :
// Note: The file system has been prefixed as of Google Chrome 12: window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; window.requestFileSystem(type, size, successCallback, opt_errorCallback)
type
Indique si le stockage des fichiers doit être persistant. Les valeurs possibles incluent window.TEMPORARY et window.PERSISTENT. Les données stockées via TEMPORARY peuvent être supprimées à la discrétion du navigateur (par exemple si plus d'espace est requis). La suppression du stockage PERSISTANT nécessite une autorisation explicite de l’utilisateur ou de l’application, et nécessite que l’utilisateur accorde un quota à votre application. Voir Demander des quotas.
taille
La taille en octets dont l'application a besoin pour le stockage.
successCallback
Callback appelé lorsque la requête du système de fichiers réussit. Son paramètre est un objet FileSystem.
opt_errorCallback
Un rappel facultatif utilisé pour gérer les erreurs ou lorsque la demande d'obtention du système de fichiers est rejetée. Son paramètre est un objet FileError.
Si vous appelez requestFileSystem() pour la première fois, le système créera un nouveau stockage pour votre application. Notez qu'il s'agit d'un système de fichiers sandbox, ce qui signifie qu'une application Web ne peut pas accéder aux fichiers d'une autre application. Cela signifie également que vous ne pouvez pas lire/écrire des fichiers dans des dossiers arbitraires sur le disque dur de l'utilisateur (par exemple Mes images, Mes documents, etc.).
Exemple d'utilisation :
function onInitFs(fs) { console.log('Opened file system: ' + fs.name);} window.requestFileSystem(window.TEMPORARY, 5*1024*1024 /*5MB*/, onInitFs, errorHandler);
La spécification FileSystem définit également l'interface API de synchronisation (LocalFileSystemSync) prévue pour les WebWorkers. Cependant, ce tutoriel ne couvre pas l'API de synchronisation.
Dans la suite de ce document nous utiliserons le même gestionnaire pour les erreurs générées par les appels asynchrones :
function errorHandler(e) { var msg = ''; switch (e.code) { case FileError.QUOTA_EXCEEDED_ERR: msg = 'QUOTA_EXCEEDED_ERR'; break; case FileError.NOT_FOUND_ERR: msg = 'NOT_FOUND_ERR'; break; case FileError.SECURITY_ERR: msg = 'SECURITY_ERR'; break; case FileError.INVALID_MODIFICATION_ERR: msg = 'INVALID_MODIFICATION_ERR'; break; case FileError.INVALID_STATE_ERR: msg = 'INVALID_STATE_ERR'; break; default: msg = 'Unknown Error'; break; }; console.log('Error: ' + msg); }
解的讯息。请求存储配额要使用 PERSISTENT 存储,您必须向用户取得存储持久数据的许可。由于浏览器可自行决定删除临时存储的数据,因此这一限制不适用于 TEMPORARY 存储。为了将 PERSISTENT 存储与 FileSystem API 配合使用,Chrome 浏览器使用基于 window.webkitStorageInfo 的新 API 以请求存储:
window.webkitStorageInfo.requestQuota(PERSISTENT, 1024*1024, function(grantedBytes) { window.requestFileSystem(PERSISTENT, grantedBytes, onInitFs, errorHandler); }, function(e) { console.log('Error', e); });
用户授予许可后,就不必再调用 requestQuota() 了。后续调用为无操作指令。您还可以使用 API 查询源的当前配额使用情况和分配情况:window.webkitStorageInfo.queryUsageAndQuota()使用文件沙盒环境中的文件通过 FileEntry 接口表示。FileEntry 包含标准文件系统中会有的属性类型(name、isFile...)和方法(remove、moveTo、copyTo...)。FileEntry 的属性和方法:
fileEntry.isFile === true fileEntry.isDirectory === false fileEntry.name fileEntry.fullPath ... fileEntry.getMetadata(successCallback, opt_errorCallback); fileEntry.remove(successCallback, opt_errorCallback); fileEntry.moveTo(dirEntry, opt_newName, opt_successCallback, opt_errorCallback); fileEntry.copyTo(dirEntry, opt_newName, opt_successCallback, opt_errorCallback); fileEntry.getParent(successCallback, opt_errorCallback); fileEntry.toURL(opt_mimeType); fileEntry.file(successCallback, opt_errorCallback); fileEntry.createWriter(successCallback, opt_errorCallback); ...
为了更好地理解 FileEntry,本部分还提供了执行常规任务的众多技巧。创建文件您可以使用文件系统的 getFile()(DirectoryEntry 接口的一种方法)查找或创建文件。请求文件系统后,系统会向成功回调传递FileSystem 对象,其中包含指向该应用相应文件系统的根的 DirectoryEntry (fs.root)。以下代码会在该应用相应文件系统的根中创建名为“log.txt”的空白文件:
function onInitFs(fs) { fs.root.getFile('log.txt', {create: true, exclusive: true}, function(fileEntry) { // fileEntry.isFile === true // fileEntry.name == 'log.txt' // fileEntry.fullPath == '/log.txt' }, errorHandler); } window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);
请求文件系统后,系统会向成功处理程序传递 FileSystem 对象。我们可以将回调中的 fs.root.getFile() 命名为要创建的文件的文件名。您可以传递绝对路径或相对路径,但该路径必须有效。例如,如果您尝试创建一个其直接父级文件不存在的文件,将会导致出错。getFile() 的第二个参数是在文件不存在时从字面上说明函数行为的对象。在此示例中,create: true 会在文件不存在时创建文件,并在文件存在时 (exclusive: true) 引发错误。如果 create: false,系统只会获取并返回文件。无论是哪种情况,系统都不会覆盖文件内容,因为我们只是获取相关文件的引用路径。通过名称读取文件以下代码会检索名为“log.txt”的文件,并使用 FileReader API 读取文件内容,然后将其附加到页面上新的
function onInitFs(fs) { fs.root.getFile('log.txt', {}, function(fileEntry) { // Get a File object representing the file, // then use FileReader to read its contents. fileEntry.file(function(file) { var reader = new FileReader(); reader.onloadend = function(e) { var txtArea = document.createElement('textarea'); txtArea.value = this.result; document.body.appendChild(txtArea); }; reader.readAsText(file); }, errorHandler); }, errorHandler); } window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);
写入到文件以下代码会创建名为“log.txt”的空白文件(如果该文件不存在),并在文件中填入“Lorem Ipsum”文字。
function onInitFs(fs) { fs.root.getFile('log.txt', {create: true}, function(fileEntry) { // Create a FileWriter object for our FileEntry (log.txt). fileEntry.createWriter(function(fileWriter) { fileWriter.onwriteend = function(e) { console.log('Write completed.'); }; fileWriter.onerror = function(e) { console.log('Write failed: ' + e.toString()); }; // Create a new Blob and write it to log.txt. var bb = new BlobBuilder(); // Note: window.WebKitBlobBuilder in Chrome 12. bb.append('Lorem Ipsum'); fileWriter.write(bb.getBlob('text/plain')); }, errorHandler); }, errorHandler); } window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);
此时,我们会调用 FileEntry 的 createWriter() 方法获取 FileWriter 对象。在成功回调中为error 事件和 writeend 事件设置事件处理程序。通过以下操作将文字数据写入文件:创建 Blob,向 Blob 附加文字,然后将 Blob 传递到FileWriter.write()。向文件附加文字以下代码会将“Hello World”文字附加到日志文件结尾。如果该文件不存在,系统将引发错误。
function onInitFs(fs) { fs.root.getFile('log.txt', {create: false}, function(fileEntry) { // Create a FileWriter object for our FileEntry (log.txt). fileEntry.createWriter(function(fileWriter) { fileWriter.seek(fileWriter.length); // Start write position at EOF. // Create a new Blob and write it to log.txt. var bb = new BlobBuilder(); // Note: window.WebKitBlobBuilder in Chrome 12. bb.append('Hello World'); fileWriter.write(bb.getBlob('text/plain')); }, errorHandler); }, errorHandler); } window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);
以下代码可让用户使用 <input type="file" multiple />
选择多个文件,并在应用的沙盒文件系统中复制这些文件。
<input type="file" id="myfile" multiple /> document.querySelector('#myfile').onchange = function(e) { var files = this.files; window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) { // Duplicate each file the user selected to the app's fs. for (var i = 0, file; file = files[i]; ++i) { // Capture current iteration's file in local scope for the getFile() callback. (function(f) { fs.root.getFile(file.name, {create: true, exclusive: true}, function(fileEntry) { fileEntry.createWriter(function(fileWriter) { fileWriter.write(f); // Note: write() can take a File or Blob object. }, errorHandler); }, errorHandler); })(file); } }, errorHandler); };
虽然我们通过输入导入文件,您也可以使用 HTML5 拖放功能轻松实现相同的目标。
正如评论中所说的,FileWriter.write() 可接受 Blob 或 File。这是因为 File 继承自 Blob,所以文件对象也是 Blob。
以下代码会删除“log.txt”文件。
window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) { fs.root.getFile('log.txt', {create: false}, function(fileEntry) { fileEntry.remove(function() { console.log('File removed.'); }, errorHandler); }, errorHandler); }, errorHandler);
沙盒中的目录通过 DirectoryEntry
接口表示,该接口共享了 FileEntry 的大部分属性(继承自常用 Entry
接口)。不过,DirectoryEntry
还可使用其他方法处理目录。
DirectoryEntry
的属性和方法:
dirEntry.isDirectory === true // See the section on FileEntry for other inherited properties/methods. ... var dirReader = dirEntry.createReader(); dirEntry.getFile(path, opt_flags, opt_successCallback, opt_errorCallback); dirEntry.getDirectory(path, opt_flags, opt_successCallback, opt_errorCallback); dirEntry.removeRecursively(successCallback, opt_errorCallback); ...
使用 DirectoryEntry
的 getDirectory()
方法读取或创建目录。您可以递交名称或路径作为查找或创建所用的目录。
例如,以下代码会在根目录中创建名为“MyPictures”的目录:
[html] view plaincopy window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) { fs.root.getDirectory('MyPictures', {create: true}, function(dirEntry) { ... }, errorHandler); }, errorHandler);
创建子目录的方法与创建其他任何目录的方法完全相同。不过,如果您尝试创建其直接父目录不存在的目录,API 将引发错误。相应的解决方法是,依次创建各级目录,而这对异步 API 而言非常麻烦。
以下代码会在系统创建父文件夹后以递归方式添加各个子文件夹,从而在应用相应 FileSystem 的根中创建新的层次结构 (music/genres/jazz)。
[html] view plaincopy var path = 'music/genres/jazz/'; function createDir(rootDirEntry, folders) { // Throw out './' or '/' and move on to prevent something like '/foo/.//bar'. if (folders[0] == '.' || folders[0] == '') { folders = folders.slice(1); } rootDirEntry.getDirectory(folders[0], {create: true}, function(dirEntry) { // Recursively add the new subfolder (if we still have another to create). if (folders.length) { createDir(dirEntry, folders.slice(1)); } }, errorHandler); }; function onInitFs(fs) { createDir(fs.root, path.split('/')); // fs.root is a DirectoryEntry. } window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);
在“music/genres/jazz”处于合适的位置后,我们就可以将完整路径传递到 getDirectory()
,然后在其下方创建新的子文件夹。例如:
window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) { fs.root.getFile('/music/genres/jazz/song.mp3', {create: true}, function(fileEntry) { ... }, errorHandler); }, errorHandler);
要读取目录的内容,可先创建 DirectoryReader
,然后调用 readEntries()
方法。我们不能保证所有目录条目都能在仅调用一次 readEntries()
的情况下同时返回。也就是说,您需要一直调用 DirectoryReader.readEntries()
,直到系统不再返回结果为止。以下代码对此作了说明:
[html] view plaincopy <ul id="filelist"></ul> function toArray(list) { return Array.prototype.slice.call(list || [], 0); } function listResults(entries) { // Document fragments can improve performance since they're only appended // to the DOM once. Only one browser reflow occurs. var fragment = document.createDocumentFragment(); entries.forEach(function(entry, i) { var img = entry.isDirectory ? '<img src="folder-icon.gif">' : '<img src="file-icon.gif">'; var li = document.createElement('li'); li.innerHTML = [img, '<span>', entry.name, '</span>'].join(''); fragment.appendChild(li); }); document.querySelector('#filelist').appendChild(fragment); } function onInitFs(fs) { var dirReader = fs.root.createReader(); var entries = []; // Call the reader.readEntries() until no more results are returned. var readEntries = function() { dirReader.readEntries (function(results) { if (!results.length) { listResults(entries.sort()); } else { entries = entries.concat(toArray(results)); readEntries(); } }, errorHandler); }; readEntries(); // Start reading dirs. } window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);
DirectoryEntry.remove()
方法的行为与 FileEntry
相应方法的行为非常相似。差别在于:尝试删除非空目录时会引发错误。
以下代码会从“/music/genres/”删除空的“jazz”目录:
[html] view plaincopy window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) { fs.root.getDirectory('music/genres/jazz', {}, function(dirEntry) { dirEntry.remove(function() { console.log('Directory removed.'); }, errorHandler); }, errorHandler); }, errorHandler);
如果您不需要某个包含条目的目录,不妨使用 removeRecursively()
。该方法将以递归方式删除目录及其内容。
以下代码会以递归方式删除“music”目录及其包含的所有文件和目录:
[html] view plaincopy window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) { fs.root.getDirectory('/misc/../music', {}, function(dirEntry) { dirEntry.removeRecursively(function() { console.log('Directory removed.'); }, errorHandler); }, errorHandler); }, errorHandler);
FileEntry
和 DirectoryEntry
享有共同的操作。
FileEntry
和 DirectoryEntry
均可使用 copyTo()
复制现有条目。该方法会自动以递归方式复制文件夹。
以下代码示例会将“me.png”文件从一个目录复制到另一个目录:
[html] view plaincopy function copy(cwd, src, dest) { cwd.getFile(src, {}, function(fileEntry) { cwd.getDirectory(dest, {}, function(dirEntry) { fileEntry.copyTo(dirEntry); }, errorHandler); }, errorHandler); } window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) { copy(fs.root, '/folder1/me.png', 'folder2/mypics/'); }, errorHandler);
FileEntry
和 DirectoryEntry
的 moveTo()
方法可让您移动或重命名文件或目录。其第一个参数是文件要移动到的目标父目录,其第二个参数是文件可选的新名称。如未提供新名称,系统将使用文件的原名称。
以下示例将“me.png”重命名为“you.png”,但并不移动该文件:
[html] view plaincopy function rename(cwd, src, newName) { cwd.getFile(src, {}, function(fileEntry) { fileEntry.moveTo(cwd, newName); }, errorHandler); } window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) { rename(fs.root, 'me.png', 'you.png'); }, errorHandler); 以下示例将“me.png”(位于根目录中)移动到名为“newfolder”的文件夹。 function move(src, dirName) { fs.root.getFile(src, {}, function(fileEntry) { fs.root.getDirectory(dirName, {}, function(dirEntry) { fileEntry.moveTo(dirEntry); }, errorHandler); }, errorHandler); } window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) { move('/me.png', 'newfolder/'); }, errorHandler);
FileSystem API 使用新的网址机制,(即 filesystem:),可用于填充 src 或 href 属性。例如,如果您要显示某幅图片且拥有相应的 fileEntry,您可以调用 toURL() 获取该文件的 filesystem: 网址:
var img = document.createElement('img'); img.src = fileEntry.toURL(); // filesystem:http://example.com/temporary/myfile.png document.body.appendChild(img);
另外,如果您已具备 filesystem: 网址,可使用 resolveLocalFileSystemURL() 找回 fileEntry:
window.resolveLocalFileSystemURL = window.resolveLocalFileSystemURL || window.webkitResolveLocalFileSystemURL; var url = 'filesystem:http://example.com/temporary/myfile.png'; window.resolveLocalFileSystemURL(url, function(fileEntry) { ... });
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!