我常常想,如果網路應用程式能夠讀取和寫入檔案與目錄,將會非常方便。從離線轉移到線上後,應用程式變得更加複雜,而檔案系統方面的API的缺乏也一直阻礙著網路前進。儲存二進位資料或與其進行互動不應局限於桌面。令人欣慰的是,由於FileSystemAPI的出現,這一現狀終於改變了。有了FileSystemAPI,網路應用程式就可以建立、讀取、導航使用者本機檔案系統中的沙盒部分以及向其中寫入資料。
API 被分成以下不同的主題:
#讀取和處理檔案:File
/ Blob
、FileList
、FileReader
建立和寫入:BlobBuilder
##、FileWriter
DirectoryReader、FileEntry
/
DirectoryEntry、##LocalFileSystem
瀏覽器可以實作此FileSystemAPI 。目前尚不存在專門用於檔案/配額管理的瀏覽器使用者介面。要在使用者的系統上儲存數據,您的應用程式可能需要請求配額。不過,可使用--unlimited-quota-for-files標記執行
Chrome瀏覽器進行測試。此外,如果您要開發的是用於Chrome線上應用程式商店的應用程式或擴充程序,可使用unlimitedStorage清單檔案權限,而無需請求配額。最後,使用者會收到授予、拒絕或為應用程式增加儲存的權限對話框。
如果您要透過
file://#調試您的應用程式,可能需要
--allow-file-access-from-files 標記。不使用這些標記會導致
SECURITY_ERR或
QUOTA_EXCEEDED_ERRFileError。
請求檔案系統
// 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
#文件儲存是否應該是持久的。可能的值包括window.TEMPORARY和window.PERSISTENT。透過TEMPORARY儲存的資料可由瀏覽器自行決定刪除(例如在需要更多空間的情況下)。要清除PERSISTENT存儲,必須獲得用戶或應用的明確授權,並且需要用戶向您的應用授予配額。請參閱請求配額。
size
應用需要用於儲存的大小(以位元組為單位)。
successCallback
檔案系統請求成功時呼叫的回呼。其參數為 FileSystem物件。
opt_errorCallback
用於處理錯誤或取得檔案系統的請求遭到拒絕時可選的回呼。其參數為FileError物件。
如果您是首次呼叫requestFileSystem(),系統會為您的應用程式建立新的儲存體。請注意,這是沙箱檔案系統,也就是說,一個網路應用程式無法存取另一個應用程式的檔案。這也意味著您無法在使用者硬碟上的任意資料夾(例如「我的圖片」、「我的文件」等)中讀取/寫入檔案。
用法範例:
function onInitFs(fs) { console.log('Opened file system: ' + fs.name);} window.requestFileSystem(window.TEMPORARY, 5*1024*1024 /*5MB*/, onInitFs, errorHandler);
FileSystem 規格也定義了計畫用於WebWorkers的同步API(LocalFileSystemSync)介面。不過,本教學不涉及該同步API。
在本文檔的其餘部分中,我們將使用相同的處理程序處理非同步呼叫引發的錯誤:
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 读取文件内容,然后将其附加到页面上新的 4750256ae76b6b9d804861d8f69e79d3。如果 log.txt 不存在,系统将引发错误。
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);
以下代码可让用户使用 724acc485e2bb01e9bb3d8b9d88e9aff
选择多个文件,并在应用的沙盒文件系统中复制这些文件。
<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) { ... });
以上是HTML5檔案操作API的程式碼案例詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!