HTML5 文件操作API


簡介

我常常想,如果網絡應用能夠讀取和寫入文件與目錄,將會非常方便。從離線轉移到在線后,應用變得更加復雜,而文件系統方面的API的缺乏也一直阻礙着網絡前進。存儲二進制數據或與其進行交互不應局限於桌面。令人欣慰的是,由於FileSystemAPI的出現,這一現狀終於得到了改變。有了FileSystemAPI,網絡應用就可以創建、讀取、導航用戶本地文件系統中的沙盒部分以及向其中寫入數據。

API 被分為以下不同的主題:

  • 讀取和處理文件:File/BlobFileListFileReader

  • 創建和寫入:BlobBuilderFileWriter

  • 目錄和文件系統訪問:DirectoryReaderFileEntry/DirectoryEntryLocalFileSystem

瀏覽器支持與存儲限制

在寫這篇文章時,只有 GoogleChrome 瀏覽器可以實施此FileSystemAPI。目前尚不存在專門用於文件/配額管理的瀏覽器用戶界面。要在用戶的系統上存儲數據,您的應用可能需要請求配額。不過,可使用--unlimited-quota-for-files標記運行Chrome瀏覽器進行測試。此外,如果您要開發的是用於Chrome網上應用店的應用或擴展程序,可使用unlimitedStorage清單文件權限,而無需請求配額。最后,用戶會收到授予、拒絕或為應用增加存儲的權限對話框。

如果您要通過 file://調試您的應用,可能需要--allow-file-access-from-files標記。不使用這些標記會導致SECURITY_ERRQUOTA_EXCEEDED_ERRFileError

請求文件系統

網絡應用可通過調用 window.requestFileSystem()請求對沙盒文件系統的訪問權限:

[html] view plain copy
  1. // Note: The file system has been prefixed as of Google Chrome 12:  
  2. window.requestFileSystem  = window.requestFileSystem || window.webkitRequestFileSystem;  
  3.   
  4. window.requestFileSystem(type, size, successCallback, opt_errorCallback)  
type
文件存儲是否應該是持久的。可能的值包括 window.TEMPORARYwindow.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

在本文檔的其余部分中,我們將使用相同的處理程序處理異步調用引發的錯誤:

[html] view plain copy
  1. function errorHandler(e) {  
  2.   var msg = '';  
  3.   
  4.   switch (e.code) {  
  5.     case FileError.QUOTA_EXCEEDED_ERR:  
  6.       msg = 'QUOTA_EXCEEDED_ERR';  
  7.       break;  
  8.     case FileError.NOT_FOUND_ERR:  
  9.       msg = 'NOT_FOUND_ERR';  
  10.       break;  
  11.     case FileError.SECURITY_ERR:  
  12.       msg = 'SECURITY_ERR';  
  13.       break;  
  14.     case FileError.INVALID_MODIFICATION_ERR:  
  15.       msg = 'INVALID_MODIFICATION_ERR';  
  16.       break;  
  17.     case FileError.INVALID_STATE_ERR:  
  18.       msg = 'INVALID_STATE_ERR';  
  19.       break;  
  20.     default:  
  21.       msg = 'Unknown Error';  
  22.       break;  
  23.   };  
  24.   
  25.   console.log('Error: ' + msg);  
  26. }  
當然,這種錯誤回調非常通用,能讓您充分理解,但您提供給用戶的應是易於一般人理解的訊息。請求存儲配額要使用 PERSISTENT 存儲,您必須向用戶取得存儲持久數據的許可。由於瀏覽器可自行決定刪除臨時存儲的數據,因此這一限制不適用於 TEMPORARY 存儲。為了將 PERSISTENT 存儲與 FileSystem API 配合使用,Chrome 瀏覽器使用基於 window.webkitStorageInfo 的新 API 以請求存儲:
[html] view plain copy
  1. window.webkitStorageInfo.requestQuota(PERSISTENT, 1024*1024, function(grantedBytes) {  
  2.   window.requestFileSystem(PERSISTENT, grantedBytes, onInitFs, errorHandler);  
  3. }, function(e) {  
  4.   console.log('Error', e);  
  5. });  
用戶授予許可后,就不必再調用 requestQuota() 了。后續調用為無操作指令。您還可以使用 API 查詢源的當前配額使用情況和分配情況:window.webkitStorageInfo.queryUsageAndQuota()使用文件沙盒環境中的文件通過 FileEntry 接口表示。FileEntry 包含標准文件系統中會有的屬性類型(nameisFile...)和方法(removemoveTocopyTo...)。FileEntry 的屬性和方法:
[html] view plain copy
  1. fileEntry.isFile === true  
  2. fileEntry.isDirectory === false  
  3. fileEntry.name  
  4. fileEntry.fullPath  
  5. ...  
  6.   
  7. fileEntry.getMetadata(successCallback, opt_errorCallback);  
  8. fileEntry.remove(successCallback, opt_errorCallback);  
  9. fileEntry.moveTo(dirEntry, opt_newName, opt_successCallback, opt_errorCallback);  
  10. fileEntry.copyTo(dirEntry, opt_newName, opt_successCallback, opt_errorCallback);  
  11. fileEntry.getParent(successCallback, opt_errorCallback);  
  12. fileEntry.toURL(opt_mimeType);  
  13.   
  14. fileEntry.file(successCallback, opt_errorCallback);  
  15. fileEntry.createWriter(successCallback, opt_errorCallback);  
  16. ...  
為了更好地理解 FileEntry,本部分還提供了執行常規任務的眾多技巧。創建文件您可以使用文件系統的 getFile()DirectoryEntry 接口的一種方法)查找或創建文件。請求文件系統后,系統會向成功回調傳遞FileSystem 對象,其中包含指向該應用相應文件系統的根的 DirectoryEntry (fs.root)。以下代碼會在該應用相應文件系統的根中創建名為“log.txt”的空白文件:
[html] view plain copy
  1. function onInitFs(fs) {  
  2.   
  3.   fs.root.getFile('log.txt', {create: true, exclusive: true}, function(fileEntry) {  
  4.   
  5.     // fileEntry.isFile === true  
  6.     // fileEntry.name == 'log.txt'  
  7.     // fileEntry.fullPath == '/log.txt'  
  8.   
  9.   }, errorHandler);  
  10.   
  11. }  
  12.   
  13. window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);  
請求文件系統后,系統會向成功處理程序傳遞 FileSystem 對象。我們可以將回調中的 fs.root.getFile() 命名為要創建的文件的文件名。您可以傳遞絕對路徑或相對路徑,但該路徑必須有效。例如,如果您嘗試創建一個其直接父級文件不存在的文件,將會導致出錯。getFile() 的第二個參數是在文件不存在時從字面上說明函數行為的對象。在此示例中,create: true 會在文件不存在時創建文件,並在文件存在時 (exclusive: true) 引發錯誤。如果 create: false,系統只會獲取並返回文件。無論是哪種情況,系統都不會覆蓋文件內容,因為我們只是獲取相關文件的引用路徑。通過名稱讀取文件以下代碼會檢索名為“log.txt”的文件,並使用 FileReader API 讀取文件內容,然后將其附加到頁面上新的 <textarea>。如果 log.txt 不存在,系統將引發錯誤。
[html] view plain copy
  1. function onInitFs(fs) {  
  2.   
  3.   fs.root.getFile('log.txt', {}, function(fileEntry) {  
  4.   
  5.     // Get a File object representing the file,  
  6.     // then use FileReader to read its contents.  
  7.     fileEntry.file(function(file) {  
  8.        var reader = new FileReader();  
  9.   
  10.        reader.onloadend = function(e) {  
  11.          var txtArea = document.createElement('textarea');  
  12.          txtArea.value = this.result;  
  13.          document.body.appendChild(txtArea);  
  14.        };  
  15.   
  16.        reader.readAsText(file);  
  17.     }, errorHandler);  
  18.   
  19.   }, errorHandler);  
  20.   
  21. }  
  22.   
  23. window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);  
寫入到文件以下代碼會創建名為“log.txt”的空白文件(如果該文件不存在),並在文件中填入“Lorem Ipsum”文字。
[html] view plain copy
  1. function onInitFs(fs) {  
  2.   
  3.   fs.root.getFile('log.txt', {create: true}, function(fileEntry) {  
  4.   
  5.     // Create a FileWriter object for our FileEntry (log.txt).  
  6.     fileEntry.createWriter(function(fileWriter) {  
  7.   
  8.       fileWriter.onwriteend = function(e) {  
  9.         console.log('Write completed.');  
  10.       };  
  11.   
  12.       fileWriter.onerror = function(e) {  
  13.         console.log('Write failed: ' + e.toString());  
  14.       };  
  15.   
  16.       // Create a new Blob and write it to log.txt.  
  17.       var bb = new BlobBuilder(); // Note: window.WebKitBlobBuilder in Chrome 12.  
  18.       bb.append('Lorem Ipsum');  
  19.       fileWriter.write(bb.getBlob('text/plain'));  
  20.   
  21.     }, errorHandler);  
  22.   
  23.   }, errorHandler);  
  24.   
  25. }  
  26.   
  27. window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);  
此時,我們會調用 FileEntry createWriter() 方法獲取 FileWriter 對象。在成功回調中為error 事件和 writeend 事件設置事件處理程序。通過以下操作將文字數據寫入文件:創建 Blob,向 Blob 附加文字,然后將 Blob 傳遞到FileWriter.write()向文件附加文字以下代碼會將“Hello World”文字附加到日志文件結尾。如果該文件不存在,系統將引發錯誤。
[html] view plain copy
  1. function onInitFs(fs) {  
  2.   
  3.   fs.root.getFile('log.txt', {create: false}, function(fileEntry) {  
  4.   
  5.     // Create a FileWriter object for our FileEntry (log.txt).  
  6.     fileEntry.createWriter(function(fileWriter) {  
  7.   
  8.       fileWriter.seek(fileWriter.length); // Start write position at EOF.  
  9.   
  10.       // Create a new Blob and write it to log.txt.  
  11.       var bb = new BlobBuilder(); // Note: window.WebKitBlobBuilder in Chrome 12.  
  12.       bb.append('Hello World');  
  13.       fileWriter.write(bb.getBlob('text/plain'));  
  14.   
  15.     }, errorHandler);  
  16.   
  17.   }, errorHandler);  
  18.   
  19. }  
  20.   
  21. window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);  
 
        

復制用戶選定的文件

以下代碼可讓用戶使用 <input type="file" multiple /> 選擇多個文件,並在應用的沙盒文件系統中復制這些文件。

[html] view plain copy
  1. <input type="file" id="myfile" multiple />  
  2.   
  3. document.querySelector('#myfile').onchange = function(e) {  
  4.   var files = this.files;  
  5.   
  6.   window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {  
  7.     // Duplicate each file the user selected to the app's fs.  
  8.     for (var i = 0, file; file = files[i]; ++i) {  
  9.   
  10.       // Capture current iteration's file in local scope for the getFile() callback.  
  11.       (function(f) {  
  12.         fs.root.getFile(file.name, {create: true, exclusive: true}, function(fileEntry) {  
  13.           fileEntry.createWriter(function(fileWriter) {  
  14.             fileWriter.write(f); // Note: write() can take a File or Blob object.  
  15.           }, errorHandler);  
  16.         }, errorHandler);  
  17.       })(file);  
  18.   
  19.     }  
  20.   }, errorHandler);  
  21.   
  22. };  

雖然我們通過輸入導入文件,您也可以使用 HTML5 拖放功能輕松實現相同的目標。

正如評論中所說的,FileWriter.write() 可接受 BlobFile。這是因為 File 繼承自 Blob,所以文件對象也是 Blob。

刪除文件

以下代碼會刪除“log.txt”文件。

[html] view plain copy
  1. window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {  
  2.   fs.root.getFile('log.txt', {create: false}, function(fileEntry) {  
  3.   
  4.     fileEntry.remove(function() {  
  5.       console.log('File removed.');  
  6.     }, errorHandler);  
  7.   
  8.   }, errorHandler);  
  9. }, errorHandler);  

使用目錄

沙盒中的目錄通過 DirectoryEntry 接口表示,該接口共享了 FileEntry 的大部分屬性(繼承自常用 Entry 接口)。不過,DirectoryEntry 還可使用其他方法處理目錄。

DirectoryEntry 的屬性和方法:

[html] view plain copy
  1. dirEntry.isDirectory === true  
  2. // See the section on FileEntry for other inherited properties/methods.  
  3. ...  
  4.   
  5. var dirReader = dirEntry.createReader();  
  6. dirEntry.getFile(path, opt_flags, opt_successCallback, opt_errorCallback);  
  7. dirEntry.getDirectory(path, opt_flags, opt_successCallback, opt_errorCallback);  
  8. dirEntry.removeRecursively(successCallback, opt_errorCallback);  
  9. ...  

創建目錄

使用 DirectoryEntrygetDirectory() 方法讀取或創建目錄。您可以遞交名稱或路徑作為查找或創建所用的目錄。

例如,以下代碼會在根目錄中創建名為“MyPictures”的目錄:

[html] view plain copy
  1. window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {  
  2.   fs.root.getDirectory('MyPictures', {create: true}, function(dirEntry) {  
  3.     ...  
  4.   }, errorHandler);  
  5. }, errorHandler);  

子目錄

創建子目錄的方法與創建其他任何目錄的方法完全相同。不過,如果您嘗試創建其直接父目錄不存在的目錄,API 將引發錯誤。相應的解決方法是,依次創建各級目錄,而這對異步 API 而言非常麻煩。

以下代碼會在系統創建父文件夾后以遞歸方式添加各個子文件夾,從而在應用相應 FileSystem 的根中創建新的層次結構 (music/genres/jazz)。

[html] view plain copy
  1. var path = 'music/genres/jazz/';  
  2.   
  3. function createDir(rootDirEntry, folders) {  
  4.   // Throw out './' or '/' and move on to prevent something like '/foo/.//bar'.  
  5.   if (folders[0] == '.' || folders[0] == '') {  
  6.     folders = folders.slice(1);  
  7.   }  
  8.   rootDirEntry.getDirectory(folders[0], {create: true}, function(dirEntry) {  
  9.     // Recursively add the new subfolder (if we still have another to create).  
  10.     if (folders.length) {  
  11.       createDir(dirEntry, folders.slice(1));  
  12.     }  
  13.   }, errorHandler);  
  14. };  
  15.   
  16. function onInitFs(fs) {  
  17.   createDir(fs.root, path.split('/')); // fs.root is a DirectoryEntry.  
  18. }  
  19.   
  20. 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 plain copy
  1. <ul id="filelist"></ul>  
  2.   
  3. function toArray(list) {  
  4.   return Array.prototype.slice.call(list || [], 0);  
  5. }  
  6.   
  7. function listResults(entries) {  
  8.   // Document fragments can improve performance since they're only appended  
  9.   // to the DOM once. Only one browser reflow occurs.  
  10.   var fragment = document.createDocumentFragment();  
  11.   
  12.   entries.forEach(function(entry, i) {  
  13.     var img = entry.isDirectory ? '<img src="folder-icon.gif">' :  
  14.                                   '<img src="file-icon.gif">';  
  15.     var li = document.createElement('li');  
  16.     li.innerHTML = [img, '<span>', entry.name, '</span>'].join('');  
  17.     fragment.appendChild(li);  
  18.   });  
  19.   
  20.   document.querySelector('#filelist').appendChild(fragment);  
  21. }  
  22.   
  23. function onInitFs(fs) {  
  24.   
  25.   var dirReader = fs.root.createReader();  
  26.   var entries = [];  
  27.   
  28.   // Call the reader.readEntries() until no more results are returned.  
  29.   var readEntries = function() {  
  30.      dirReader.readEntries (function(results) {  
  31.       if (!results.length) {  
  32.         listResults(entries.sort());  
  33.       } else {  
  34.         entries = entries.concat(toArray(results));  
  35.         readEntries();  
  36.       }  
  37.     }, errorHandler);  
  38.   };  
  39.   
  40.   readEntries(); // Start reading dirs.  
  41.   
  42. }  
  43.   
  44. window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);  

刪除目錄

DirectoryEntry.remove() 方法的行為與 FileEntry 相應方法的行為非常相似。差別在於:嘗試刪除非空目錄時會引發錯誤。

以下代碼會從“/music/genres/”刪除空的“jazz”目錄:

[html] view plain copy
  1. window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {  
  2.   fs.root.getDirectory('music/genres/jazz', {}, function(dirEntry) {  
  3.   
  4.     dirEntry.remove(function() {  
  5.       console.log('Directory removed.');  
  6.     }, errorHandler);  
  7.   
  8.   }, errorHandler);  
  9. }, errorHandler);  

以遞歸方式刪除目錄

如果您不需要某個包含條目的目錄,不妨使用 removeRecursively()。該方法將以遞歸方式刪除目錄及其內容。

以下代碼會以遞歸方式刪除“music”目錄及其包含的所有文件和目錄:

[html] view plain copy
  1. window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {  
  2.   fs.root.getDirectory('/misc/../music', {}, function(dirEntry) {  
  3.   
  4.     dirEntry.removeRecursively(function() {  
  5.       console.log('Directory removed.');  
  6.     }, errorHandler);  
  7.   
  8.   }, errorHandler);  
  9. }, errorHandler);  

復制、重命名和移動

FileEntryDirectoryEntry 享有共同的操作。

復制條目

FileEntryDirectoryEntry 均可使用 copyTo() 復制現有條目。該方法會自動以遞歸方式復制文件夾。

以下代碼示例會將“me.png”文件從一個目錄復制到另一個目錄:

[html] view plain copy
  1. function copy(cwd, src, dest) {  
  2.   cwd.getFile(src, {}, function(fileEntry) {  
  3.   
  4.     cwd.getDirectory(dest, {}, function(dirEntry) {  
  5.       fileEntry.copyTo(dirEntry);  
  6.     }, errorHandler);  
  7.   
  8.   }, errorHandler);  
  9. }  
  10.   
  11. window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {  
  12.   copy(fs.root, '/folder1/me.png', 'folder2/mypics/');  
  13. }, errorHandler);  

移動或重命名條目

FileEntryDirectoryEntrymoveTo() 方法可讓您移動或重命名文件或目錄。其第一個參數是文件要移動到的目標父目錄,其第二個參數是文件可選的新名稱。如未提供新名稱,系統將使用文件的原名稱。

以下示例將“me.png”重命名為“you.png”,但並不移動該文件:

[html] view plain copy
  1. function rename(cwd, src, newName) {  
  2.   cwd.getFile(src, {}, function(fileEntry) {  
  3.     fileEntry.moveTo(cwd, newName);  
  4.   }, errorHandler);  
  5. }  
  6.   
  7. window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {  
  8.   rename(fs.root, 'me.png', 'you.png');  
  9. }, errorHandler);  
  10.   
  11. 以下示例將“me.png”(位於根目錄中)移動到名為“newfolder”的文件夾。  
  12.   
  13. function move(src, dirName) {  
  14.   fs.root.getFile(src, {}, function(fileEntry) {  
  15.   
  16.     fs.root.getDirectory(dirName, {}, function(dirEntry) {  
  17.       fileEntry.moveTo(dirEntry);  
  18.     }, errorHandler);  
  19.   
  20.   }, errorHandler);  
  21. }  
  22.   
  23. window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {  
  24.   move('/me.png', 'newfolder/');  
  25. }, errorHandler);  

filesystem: 網址

FileSystem API 使用新的網址機制,(即 filesystem:),可用於填充 src 或 href 屬性。例如,如果您要顯示某幅圖片且擁有相應的 fileEntry,您可以調用 toURL() 獲取該文件的 filesystem: 網址:

[html] view plain copy
  1. var img = document.createElement('img');  
  2. img.src = fileEntry.toURL(); // filesystem:http://example.com/temporary/myfile.png  
  3. document.body.appendChild(img);  

另外,如果您已具備 filesystem: 網址,可使用 resolveLocalFileSystemURL() 找回 fileEntry:

[html] view plain copy
  1. window.resolveLocalFileSystemURL = window.resolveLocalFileSystemURL ||  
  2.                                    window.webkitResolveLocalFileSystemURL;  
  3.   
  4. var url = 'filesystem:http://example.com/temporary/myfile.png';  
  5. window.resolveLocalFileSystemURL(url, function(fileEntry) {  
  6.   ...  
  7. });  

支持的瀏覽器:

Opera僅支持FileAPI  IE不支持   Safari不支持   Firefox僅支持FileAPI   Chrome/Chromium瀏覽器支持。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM