Progressive Web App 存取本機檔案 File System Access API 簡介

me
林彥成
2021-09-18 | 2 min.
文章目錄
  1. 1. 什麼是 File System Access API?
  2. 2. 透過瀏覽器讀取本機檔案
  3. 3. 透過瀏覽器寫入本機檔案
  4. 4. 透過瀏覽器刪除本機檔案
  5. 5. 透過瀏覽器開啟目錄
  6. 6. File System Access API 權限

什麼是 File System Access API?

透過這個 File System Access API 就能透過程式操作本機上的檔案,舉例來說像是開啟或儲存檔案等,部分瀏覽器會需要給予相關的權限才能進行操作,除了開啟檔案 API 本身也提供了開啟目錄並列舉檔案列表的功能。

對 Progressive Web App 也會在某些情境需要可以存取和操作本機端的檔案,Google 的 Lab 提供了底下這個編輯器可以簡單試玩:

https://googlechromelabs.github.io/text-editor/

browser-fs-access library:
https://github.com/GoogleChromeLabs/browser-fs-access

小編在幾年前剛學習 SPA 時也寫了個單頁筆記的應用,能夠快速的寫一些筆記,也附上原始碼和 Demo 連結。

原始碼: https://github.com/LinYenCheng/vue-note
Demo 連結: https://linyencheng.github.io/vue-note/

透過瀏覽器讀取本機檔案

前幾年小編其實沒有使用過 File System Access API,所以顯然在操作上其實有兩種方式,那第一種是透過原生的 input 然後指定 typefile 就可以達到讀取的效果,第二種是透過 File System Access API 的 showOpenFilePicker()

  1. 直接在 html 中加入 <input type="file" id="fileInput"> 並且簡單撰寫相關事件就能夠使用,程式碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

var fileInput = document.getElementById('fileInput');

fileInput.addEventListener('change', function(e) {
var file = fileInput.files[0];
var textType = /text.*/;

if (file.type.match(textType)) {
var reader = new FileReader();

reader.onload = function(e) {
maindata.gridData = [];
var objText = JSON.parse(reader.result.toString());
objText.gridData.forEach(function(element, index) {
maindata.gridData.push(element);
});
console.log(objText);
};

reader.readAsText(file);
} else {
fileDisplayArea.innerText = "File not supported!";
}
});
  1. 使用 File System Access API 中的 showOpenFilePicker(),最後取得的 File object 包含會一個 blob 可以透過以下的方法去取得相關的值:
  • slice()
  • stream()
  • text()
  • arrayBuffer()
1
2
3
4
5
6
7
8
9
10
11
let fileHandle;
butOpenFile.addEventListener('click', async () => {
[fileHandle] = await window.showOpenFilePicker({
// 可以提供預設
startIn: 'pictures'
});
const file = await fileHandle.getFile();
const contents = await file.text();
textArea.value = contents;
});

showOpenFilePicker 中的 startIn 提供的預設值是常見的幾個資料夾,能夠提供指定

  • desktop
  • documents
  • downloads
  • music
  • pictures
  • videos

透過瀏覽器寫入本機檔案

操作上一樣會有兩種方式,第一種是產生出一個下載連結,將 Blob 檔案透過下載的方式寫入,第二種是透過 File System Access API 的 showSaveFilePicker()

  1. 將需要寫入的內容先產生成 Blob 然後產生成下載連結,最後透過程式去點擊連結去觸發開啟選擇寫入資料夾的視窗。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

function saveFile(text) {
var url = null;
var data = new Blob([text], {
type: 'text/plain;'
});
// If we are replacing a previously generated file we need to
// manually revoke the object URL to avoid memory leaks.
if (url !== null) {
window.URL.revokeObjectURL(url);
}

url = window.URL.createObjectURL(data);

var link = document.getElementById('exportText');
link.href = url;
var filename = window.prompt("輸入檔名") || 'export';
link.download = filename + '.txt';
link.click();
}

  1. 透過 showSaveFilePicker() 開啟選擇寫入資料夾的視窗。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

const fileHandle = await self.showSaveFilePicker({
suggestedName: 'Untitled Text.txt',
types: [{
description: 'Text documents',
accept: {
'text/plain': ['.txt'],
},
}],
});

async function writeFile(fileHandle, contents) {
// Create a FileSystemWritableFileStream to write to.
const writable = await fileHandle.createWritable();
// Write the contents of the file to the stream.
await writable.write(contents);
// Close the file and write the contents to disk.
await writable.close();
}

透過瀏覽器刪除本機檔案

這個之前小編就沒有試過其他方式,那透過 File System Access API 的 removeEntry() 是可以做到刪除檔案和刪除資料夾所有內容的效果。

1
2
3
4
// 刪除檔案
await directoryHandle.removeEntry('Abandoned Projects.txt');
// 刪除資料夾所有內容
await directoryHandle.removeEntry('Old Stuff', { recursive: true });

透過瀏覽器開啟目錄

File System Access API 的 showDirectoryPicker() 提供了我們操作目錄並列舉內容的功能。

1
2
3
4
5
const dirHandle = await window.showDirectoryPicker();
for await (const entry of dirHandle.values()) {
console.log(entry.kind, entry.name);
}

File System Access API 權限

部分瀏覽器環境還是會有權限的問題,所以偵測權限相關就變成也是要寫在程式碼裡,邏輯如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

async function verifyPermission(fileHandle, readWrite) {
const options = {};
if (readWrite) {
options.mode = 'readwrite';
}
// 看是否已同意
if ((await fileHandle.queryPermission(options)) === 'granted') {
return true;
}
// 看這次有沒有要成功
if ((await fileHandle.requestPermission(options)) === 'granted') {
return true;
}
// 被拒絕
return false;
}


喜歡這篇文章,請幫忙拍拍手喔 🤣

share