API Quyền truy cập vào hệ thống tệp cho phép các ứng dụng web đọc hoặc lưu trực tiếp các thay đổi vào tệp và thư mục trên thiết bị của người dùng.
API Truy cập hệ thống tệp là gì?
File System Access API (API truy cập hệ thống tệp) cho phép nhà phát triển xây dựng các ứng dụng web mạnh mẽ tương tác với các tệp trên thiết bị cục bộ của người dùng, chẳng hạn như IDE, trình chỉnh sửa ảnh và video, trình chỉnh sửa văn bản, v.v. Sau khi người dùng cấp quyền truy cập cho một ứng dụng web, API này cho phép họ đọc hoặc lưu trực tiếp các thay đổi vào tệp và thư mục trên thiết bị của người dùng. Ngoài việc đọc và ghi tệp, API Truy cập hệ thống tệp còn cung cấp khả năng mở thư mục và liệt kê nội dung của thư mục đó.
Nếu đã từng làm việc với việc đọc và ghi tệp, bạn sẽ quen thuộc với nhiều nội dung mà tôi sắp chia sẻ. Tuy nhiên, bạn vẫn nên đọc tài liệu này vì không phải hệ thống nào cũng giống nhau.
API Truy cập hệ thống tệp được hỗ trợ trên hầu hết các trình duyệt Chromium trên Windows, macOS, ChromeOS và Linux. Một ngoại lệ đáng chú ý là Brave, trong đó tính năng này hiện chỉ có sau một cờ. Chúng tôi đang nỗ lực hỗ trợ Android trong bối cảnh crbug.com/1011535.
Sử dụng API Truy cập hệ thống tệp
Để thể hiện sức mạnh và tính hữu ích của API Truy cập hệ thống tệp, tôi đã viết một trình chỉnh sửa văn bản tệp duy nhất. Tệp này cho phép bạn mở một tệp văn bản, chỉnh sửa tệp đó, lưu các thay đổi trở lại ổ đĩa hoặc bắt đầu một tệp mới và lưu các thay đổi vào ổ đĩa. Ứng dụng này không có gì đặc biệt nhưng cung cấp đủ thông tin để giúp bạn hiểu các khái niệm.
Hỗ trợ trình duyệt
Phát hiện tính năng
Để tìm hiểu xem API Quyền truy cập vào hệ thống tệp có được hỗ trợ hay không, hãy kiểm tra xem phương thức bộ chọn mà bạn quan tâm có tồn tại hay không.
if ('showOpenFilePicker' in self) {
// The `showOpenFilePicker()` method of the File System Access API is supported.
}
Dùng thử
Xem API Truy cập hệ thống tệp trong thực tế trong bản minh hoạ trình soạn thảo văn bản.
Đọc tệp từ hệ thống tệp cục bộ
Trường hợp sử dụng đầu tiên mà tôi muốn giải quyết là yêu cầu người dùng chọn một tệp, sau đó mở và đọc tệp đó từ ổ đĩa.
Yêu cầu người dùng chọn một tệp để đọc
Điểm truy cập vào API Truy cập hệ thống tệp là window.showOpenFilePicker()
. Khi được gọi, phương thức này sẽ hiển thị hộp thoại bộ chọn tệp và nhắc người dùng chọn một tệp. Sau khi người dùng chọn một tệp, API sẽ trả về một mảng các handle tệp. Thông số options
không bắt buộc cho phép bạn tác động đến hành vi của bộ chọn tệp, ví dụ: bằng cách cho phép người dùng chọn nhiều tệp, thư mục hoặc các loại tệp khác nhau.
Nếu không có tuỳ chọn nào được chỉ định, công cụ chọn tệp sẽ cho phép người dùng chọn một tệp. Đây là cách hoàn hảo để tạo trình soạn thảo văn bản.
Giống như nhiều API mạnh mẽ khác, bạn phải gọi showOpenFilePicker()
trong một ngữ cảnh bảo mật và phải gọi từ trong một cử chỉ của người dùng.
let fileHandle;
butOpenFile.addEventListener('click', async () => {
// Destructure the one-element array.
[fileHandle] = await window.showOpenFilePicker();
// Do something with the file handle.
});
Sau khi người dùng chọn một tệp, showOpenFilePicker()
sẽ trả về một mảng các handle, trong trường hợp này là một mảng một phần tử có một FileSystemFileHandle
chứa các thuộc tính và phương thức cần thiết để tương tác với tệp.
Bạn nên lưu giữ thông tin tham chiếu đến handle tệp để có thể sử dụng sau. Bạn sẽ cần đến quyền này để lưu các thay đổi đối với tệp hoặc để thực hiện bất kỳ thao tác nào khác đối với tệp.
Đọc tệp từ hệ thống tệp
Giờ đây, bạn đã có một handle (tên nhận dạng) cho một tệp, bạn có thể lấy các thuộc tính của tệp hoặc truy cập vào chính tệp đó.
Bây giờ, tôi sẽ đọc nội dung của thư. Việc gọi handle.getFile()
sẽ trả về một đối tượng File
chứa một blob. Để lấy dữ liệu từ blob, hãy gọi một trong các phương thức của blob (slice()
, stream()
, text()
hoặc arrayBuffer()
).
const file = await fileHandle.getFile();
const contents = await file.text();
Đối tượng File
do FileSystemFileHandle.getFile()
trả về chỉ có thể đọc được miễn là tệp cơ bản trên ổ đĩa không thay đổi. Nếu tệp trên ổ đĩa bị sửa đổi, đối tượng File
sẽ không đọc được và bạn cần gọi lại getFile()
để lấy đối tượng File
mới nhằm đọc dữ liệu đã thay đổi.
Kết hợp kiến thức đã học
Khi người dùng nhấp vào nút Open (Mở), trình duyệt sẽ hiển thị một bộ chọn tệp. Sau khi người dùng chọn một tệp, ứng dụng sẽ đọc nội dung và đặt nội dung đó vào <textarea>
.
let fileHandle;
butOpenFile.addEventListener('click', async () => {
[fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();
const contents = await file.text();
textArea.value = contents;
});
Ghi tệp vào hệ thống tệp cục bộ
Trong trình soạn thảo văn bản, có hai cách để lưu tệp: Lưu và Lưu dưới dạng. Lưu sẽ ghi lại các thay đổi vào tệp gốc bằng cách sử dụng handle tệp được truy xuất trước đó. Tuy nhiên, thao tác Save As (Lưu dưới dạng) sẽ tạo một tệp mới và do đó cần có một handle tệp mới.
Tạo tệp mới
Để lưu tệp, hãy gọi showSaveFilePicker()
. Thao tác này sẽ hiển thị bộ chọn tệp ở chế độ "lưu", cho phép người dùng chọn một tệp mới mà họ muốn dùng để lưu. Đối với trình chỉnh sửa văn bản, tôi cũng muốn trình chỉnh sửa này tự động thêm phần mở rộng .txt
, vì vậy, tôi đã cung cấp một số tham số bổ sung.
async function getNewFileHandle() {
const options = {
types: [
{
description: 'Text Files',
accept: {
'text/plain': ['.txt'],
},
},
],
};
const handle = await window.showSaveFilePicker(options);
return handle;
}
Lưu các thay đổi vào ổ đĩa
Bạn có thể tìm thấy tất cả mã để lưu các thay đổi đối với một tệp trong bản minh hoạ trình soạn thảo văn bản của tôi trên GitHub. Các hoạt động tương tác với hệ thống tệp cốt lõi nằm trong fs-helpers.js
. Đơn giản nhất, quy trình này sẽ có dạng như mã sau.
Tôi sẽ hướng dẫn bạn từng bước và giải thích từng bước.
// fileHandle is an instance of FileSystemFileHandle..
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();
}
Việc ghi dữ liệu vào ổ đĩa sử dụng đối tượng FileSystemWritableFileStream
, một lớp con của WritableStream
. Tạo luồng bằng cách gọi createWritable()
trên đối tượng handle (trình xử lý) của tệp. Khi createWritable()
được gọi, trước tiên, trình duyệt sẽ kiểm tra xem người dùng đã cấp quyền ghi vào tệp hay chưa. Nếu quyền ghi chưa được cấp, trình duyệt sẽ nhắc người dùng cấp quyền. Nếu không được cấp quyền, createWritable()
sẽ gửi một DOMException
và ứng dụng sẽ không thể ghi vào tệp. Trong trình soạn thảo văn bản, các đối tượng DOMException
được xử lý trong phương thức saveFile()
.
Phương thức write()
sẽ lấy một chuỗi, đây là điều cần thiết cho trình soạn thảo văn bản. Nhưng nó cũng có thể lấy một BufferSource hoặc một Blob. Ví dụ: bạn có thể chuyển trực tiếp luồng đến trình xử lý:
async function writeURLToFile(fileHandle, url) {
// Create a FileSystemWritableFileStream to write to.
const writable = await fileHandle.createWritable();
// Make an HTTP request for the contents.
const response = await fetch(url);
// Stream the response into the file.
await response.body.pipeTo(writable);
// pipeTo() closes the destination pipe by default, no need to close it.
}
Bạn cũng có thể seek()
hoặc truncate()
trong luồng để cập nhật tệp ở một vị trí cụ thể hoặc đổi kích thước tệp.
Chỉ định tên tệp và thư mục bắt đầu được đề xuất
Trong nhiều trường hợp, bạn có thể muốn ứng dụng đề xuất tên tệp hoặc vị trí mặc định. Ví dụ: trình chỉnh sửa văn bản có thể đề xuất tên tệp mặc định là Untitled Text.txt
thay vì Untitled
. Bạn có thể thực hiện việc này bằng cách truyền một thuộc tính suggestedName
trong các tuỳ chọn showSaveFilePicker
.
const fileHandle = await self.showSaveFilePicker({
suggestedName: 'Untitled Text.txt',
types: [{
description: 'Text documents',
accept: {
'text/plain': ['.txt'],
},
}],
});
Điều này cũng áp dụng cho thư mục khởi động mặc định. Nếu đang tạo trình chỉnh sửa văn bản, bạn nên bắt đầu hộp thoại lưu tệp hoặc mở tệp trong thư mục documents
mặc định, còn đối với trình chỉnh sửa hình ảnh, bạn nên bắt đầu trong thư mục pictures
mặc định. Bạn có thể đề xuất thư mục khởi động mặc định bằng cách truyền thuộc tính startIn
đến các phương thức showSaveFilePicker
, showDirectoryPicker()
hoặc showOpenFilePicker
như sau.
const fileHandle = await self.showOpenFilePicker({
startIn: 'pictures'
});
Danh sách các thư mục hệ thống phổ biến là:
desktop
: Thư mục trên máy tính của người dùng, nếu có.documents
: Thư mục thường lưu trữ các tài liệu do người dùng tạo.downloads
: Thư mục thường lưu trữ các tệp đã tải xuống.music
: Thư mục thường lưu trữ tệp âm thanh.pictures
: Thư mục thường lưu trữ ảnh và các hình ảnh tĩnh khác.videos
: Thư mục thường lưu trữ video hoặc phim.
Ngoài các thư mục hệ thống phổ biến, bạn cũng có thể truyền một tệp hoặc tay điều khiển thư mục hiện có dưới dạng giá trị cho startIn
. Sau đó, hộp thoại sẽ mở trong cùng một thư mục.
// Assume `directoryHandle` is a handle to a previously opened directory.
const fileHandle = await self.showOpenFilePicker({
startIn: directoryHandle
});
Chỉ định mục đích của các bộ chọn tệp khác nhau
Đôi khi, các ứng dụng có bộ chọn khác nhau cho các mục đích khác nhau. Ví dụ: trình chỉnh sửa văn bản đa dạng thức có thể cho phép người dùng mở tệp văn bản, nhưng cũng có thể nhập hình ảnh. Theo mặc định, mỗi bộ chọn tệp sẽ mở ở vị trí được ghi nhớ gần đây nhất. Bạn có thể tránh vấn đề này bằng cách lưu trữ các giá trị id
cho từng loại bộ chọn. Nếu bạn chỉ định một id
, thì quá trình triển khai bộ chọn tệp sẽ ghi nhớ một thư mục riêng biệt được sử dụng gần đây nhất cho id
đó.
const fileHandle1 = await self.showSaveFilePicker({
id: 'openText',
});
const fileHandle2 = await self.showSaveFilePicker({
id: 'importImage',
});
Lưu trữ handle tệp hoặc handle thư mục trong IndexedDB
Tên tệp và tên thư mục có thể chuyển đổi tuần tự, nghĩa là bạn có thể lưu tên tệp hoặc tên thư mục vào IndexedDB hoặc gọi postMessage()
để gửi các tên đó giữa cùng một nguồn cấp cao nhất.
Việc lưu tên tệp hoặc tên thư mục vào IndexedDB có nghĩa là bạn có thể lưu trữ trạng thái hoặc ghi nhớ tệp hoặc thư mục mà người dùng đang xử lý. Điều này cho phép bạn giữ lại danh sách các tệp đã mở hoặc chỉnh sửa gần đây, đề nghị mở lại tệp gần đây nhất khi mở ứng dụng, khôi phục thư mục đang hoạt động trước đó, v.v. Trong trình soạn thảo văn bản, tôi lưu trữ danh sách 5 tệp gần đây nhất mà người dùng đã mở, cho phép truy cập lại các tệp đó.
Ví dụ về mã sau đây cho thấy cách lưu trữ và truy xuất một handle tệp và một handle thư mục. Bạn có thể xem ví dụ thực tế trên Glitch. (Tôi sử dụng thư viện idb-keyval để viết ngắn gọn.)
import { get, set } from 'https://unpkg.com/idb-keyval@5.0.2/dist/esm/index.js';
const pre1 = document.querySelector('pre.file');
const pre2 = document.querySelector('pre.directory');
const button1 = document.querySelector('button.file');
const button2 = document.querySelector('button.directory');
// File handle
button1.addEventListener('click', async () => {
try {
const fileHandleOrUndefined = await get('file');
if (fileHandleOrUndefined) {
pre1.textContent = `Retrieved file handle "${fileHandleOrUndefined.name}" from IndexedDB.`;
return;
}
const [fileHandle] = await window.showOpenFilePicker();
await set('file', fileHandle);
pre1.textContent = `Stored file handle for "${fileHandle.name}" in IndexedDB.`;
} catch (error) {
alert(error.name, error.message);
}
});
// Directory handle
button2.addEventListener('click', async () => {
try {
const directoryHandleOrUndefined = await get('directory');
if (directoryHandleOrUndefined) {
pre2.textContent = `Retrieved directroy handle "${directoryHandleOrUndefined.name}" from IndexedDB.`;
return;
}
const directoryHandle = await window.showDirectoryPicker();
await set('directory', directoryHandle);
pre2.textContent = `Stored directory handle for "${directoryHandle.name}" in IndexedDB.`;
} catch (error) {
alert(error.name, error.message);
}
});
Các quyền và tên xử lý tệp hoặc thư mục được lưu trữ
Vì các quyền không phải lúc nào cũng tồn tại giữa các phiên, nên bạn nên xác minh xem người dùng có cấp quyền cho tệp hoặc thư mục bằng queryPermission()
hay không. Nếu chưa, hãy gọi requestPermission()
để yêu cầu (lại). Điều này cũng áp dụng cho các tay cầm tệp và thư mục. Bạn cần chạy fileOrDirectoryHandle.requestPermission(descriptor)
hoặc fileOrDirectoryHandle.queryPermission(descriptor)
tương ứng.
Trong trình soạn thảo văn bản, tôi đã tạo một phương thức verifyPermission()
để kiểm tra xem người dùng đã cấp quyền hay chưa và đưa ra yêu cầu nếu cần.
async function verifyPermission(fileHandle, readWrite) {
const options = {};
if (readWrite) {
options.mode = 'readwrite';
}
// Check if permission was already granted. If so, return true.
if ((await fileHandle.queryPermission(options)) === 'granted') {
return true;
}
// Request permission. If the user grants permission, return true.
if ((await fileHandle.requestPermission(options)) === 'granted') {
return true;
}
// The user didn't grant permission, so return false.
return false;
}
Bằng cách yêu cầu quyền ghi cùng với yêu cầu đọc, tôi đã giảm số lượng lời nhắc cấp quyền; người dùng sẽ thấy một lời nhắc khi mở tệp và cấp quyền cho cả việc đọc và ghi vào tệp đó.
Mở một thư mục và liệt kê nội dung của thư mục đó
Để liệt kê tất cả tệp trong một thư mục, hãy gọi showDirectoryPicker()
. Người dùng chọn một thư mục trong bộ chọn, sau đó FileSystemDirectoryHandle
sẽ được trả về, cho phép bạn liệt kê và truy cập vào các tệp của thư mục. Theo mặc định, bạn sẽ có quyền đọc các tệp trong thư mục, nhưng nếu cần quyền ghi, bạn có thể truyền { mode: 'readwrite' }
vào phương thức.
butDir.addEventListener('click', async () => {
const dirHandle = await window.showDirectoryPicker();
for await (const entry of dirHandle.values()) {
console.log(entry.kind, entry.name);
}
});
Ngoài ra, nếu bạn cần truy cập vào từng tệp bằng getFile()
để lấy kích thước tệp riêng lẻ, chẳng hạn như, đừng sử dụng await
trên từng kết quả theo tuần tự, mà hãy xử lý tất cả tệp song song, chẳng hạn như sử dụng Promise.all()
.
butDir.addEventListener('click', async () => {
const dirHandle = await window.showDirectoryPicker();
const promises = [];
for await (const entry of dirHandle.values()) {
if (entry.kind !== 'file') {
continue;
}
promises.push(entry.getFile().then((file) => `${file.name} (${file.size})`));
}
console.log(await Promise.all(promises));
});
Tạo hoặc truy cập vào tệp và thư mục trong thư mục
Từ một thư mục, bạn có thể tạo hoặc truy cập vào các tệp và thư mục bằng cách sử dụng phương thức getFileHandle()
hoặc tương ứng là phương thức getDirectoryHandle()
. Bằng cách truyền vào một đối tượng options
không bắt buộc có khoá là create
và giá trị boolean là true
hoặc false
, bạn có thể xác định xem có tạo tệp hoặc thư mục mới hay không nếu không có tệp hoặc thư mục đó.
// In an existing directory, create a new directory named "My Documents".
const newDirectoryHandle = await existingDirectoryHandle.getDirectoryHandle('My Documents', {
create: true,
});
// In this new directory, create a file named "My Notes.txt".
const newFileHandle = await newDirectoryHandle.getFileHandle('My Notes.txt', { create: true });
Giải quyết đường dẫn của một mục trong thư mục
Khi làm việc với các tệp hoặc thư mục trong một thư mục, bạn có thể giải quyết đường dẫn của mục cần xử lý. Bạn có thể thực hiện việc này bằng phương thức resolve()
được đặt tên phù hợp. Để phân giải, mục có thể là mục con trực tiếp hoặc gián tiếp của thư mục.
// Resolve the path of the previously created file called "My Notes.txt".
const path = await newDirectoryHandle.resolve(newFileHandle);
// `path` is now ["My Documents", "My Notes.txt"]
Xoá tệp và thư mục trong thư mục
Nếu đã có quyền truy cập vào một thư mục, bạn có thể xoá các tệp và thư mục có trong thư mục đó bằng phương thức removeEntry()
. Đối với thư mục, bạn có thể tuỳ ý xoá đệ quy và bao gồm tất cả thư mục con cũng như các tệp có trong đó.
// Delete a file.
await directoryHandle.removeEntry('Abandoned Projects.txt');
// Recursively delete a folder.
await directoryHandle.removeEntry('Old Stuff', { recursive: true });
Xoá trực tiếp tệp hoặc thư mục
Nếu bạn có quyền truy cập vào một tệp hoặc tay cầm thư mục, hãy gọi remove()
trên FileSystemFileHandle
hoặc
FileSystemDirectoryHandle
để xoá tệp hoặc tay cầm thư mục đó.
// Delete a file.
await fileHandle.remove();
// Delete a directory.
await directoryHandle.remove();
Đổi tên và di chuyển tệp và thư mục
Bạn có thể đổi tên hoặc di chuyển tệp và thư mục sang một vị trí mới bằng cách gọi move()
trên giao diện FileSystemHandle
. FileSystemHandle
có các giao diện con FileSystemFileHandle
và FileSystemDirectoryHandle
. Phương thức move()
nhận một hoặc hai tham số. Giá trị đầu tiên có thể là một chuỗi có tên mới hoặc FileSystemDirectoryHandle
đến thư mục đích. Trong trường hợp thứ hai, thông số thứ hai (không bắt buộc) là một chuỗi có tên mới, vì vậy, việc di chuyển và đổi tên có thể diễn ra trong một bước.
// Rename the file.
await file.move('new_name');
// Move the file to a new directory.
await file.move(directory);
// Move the file to a new directory and rename it.
await file.move(directory, 'newer_name');
Tích hợp tính năng kéo và thả
Giao diện Kéo và thả HTML cho phép các ứng dụng web chấp nhận các tệp được kéo và thả trên một trang web. Trong quá trình kéo và thả, các mục tệp và thư mục được kéo sẽ được liên kết với các mục nhập tệp và mục nhập thư mục tương ứng. Phương thức DataTransferItem.getAsFileSystemHandle()
trả về một lời hứa có đối tượng FileSystemFileHandle
nếu mục được kéo là tệp và một lời hứa có đối tượng FileSystemDirectoryHandle
nếu mục được kéo là thư mục. Trang thông tin sau đây cho thấy cách thực hiện. Xin lưu ý rằng DataTransferItem.kind
của giao diện Kéo và thả là "file"
cho cả tệp và thư mục, trong khi FileSystemHandle.kind
của API Truy cập hệ thống tệp là "file"
cho tệp và "directory"
cho thư mục.
elem.addEventListener('dragover', (e) => {
// Prevent navigation.
e.preventDefault();
});
elem.addEventListener('drop', async (e) => {
e.preventDefault();
const fileHandlesPromises = [...e.dataTransfer.items]
.filter((item) => item.kind === 'file')
.map((item) => item.getAsFileSystemHandle());
for await (const handle of fileHandlesPromises) {
if (handle.kind === 'directory') {
console.log(`Directory: ${handle.name}`);
} else {
console.log(`File: ${handle.name}`);
}
}
});
Truy cập vào hệ thống tệp riêng tư gốc
Hệ thống tệp riêng tư gốc là một điểm cuối bộ nhớ, như tên gọi cho thấy, là riêng tư đối với nguồn gốc của trang. Mặc dù các trình duyệt thường triển khai việc này bằng cách lưu trữ nội dung của hệ thống tệp riêng tư gốc này vào ổ đĩa ở đâu đó, nhưng không có ý định cho phép người dùng truy cập vào nội dung đó. Tương tự, không có tệp hoặc thư mục nào có tên khớp với tên của các thư mục con trong hệ thống tệp riêng tư gốc. Mặc dù trình duyệt có thể khiến bạn có cảm giác rằng có các tệp, nhưng nội bộ – vì đây là hệ thống tệp riêng tư gốc – trình duyệt có thể lưu trữ "các tệp" này trong cơ sở dữ liệu hoặc bất kỳ cấu trúc dữ liệu nào khác. Về cơ bản, nếu bạn sử dụng API này, hãy không mong đợi tìm thấy các tệp đã tạo được so khớp một với một ở đâu đó trên ổ đĩa cứng. Bạn có thể hoạt động như bình thường trên hệ thống tệp riêng tư gốc sau khi có quyền truy cập vào FileSystemDirectoryHandle
gốc.
const root = await navigator.storage.getDirectory();
// Create a new file handle.
const fileHandle = await root.getFileHandle('Untitled.txt', { create: true });
// Create a new directory handle.
const dirHandle = await root.getDirectoryHandle('New Folder', { create: true });
// Recursively remove a directory.
await root.removeEntry('Old Stuff', { recursive: true });
Truy cập vào các tệp được tối ưu hoá cho hiệu suất từ hệ thống tệp riêng tư gốc
Hệ thống tệp riêng gốc cung cấp quyền truy cập không bắt buộc vào một loại tệp đặc biệt được tối ưu hoá cao cho hiệu suất, chẳng hạn như bằng cách cung cấp quyền ghi tại chỗ và độc quyền vào nội dung của tệp. Trong Chromium 102 trở lên, có một phương thức bổ sung trên hệ thống tệp riêng tư gốc để đơn giản hoá việc truy cập tệp: createSyncAccessHandle()
(dành cho các thao tác đọc và ghi đồng bộ).
Phương thức này được hiển thị trên FileSystemFileHandle
, nhưng chỉ dành riêng cho Trình chạy web.
// (Read and write operations are synchronous,
// but obtaining the handle is asynchronous.)
// Synchronous access exclusively in Worker contexts.
const accessHandle = await fileHandle.createSyncAccessHandle();
const writtenBytes = accessHandle.write(buffer);
const readBytes = accessHandle.read(buffer, { at: 1 });
Tự động bổ sung tính năng
Không thể hoàn toàn polyfill các phương thức API Truy cập hệ thống tệp.
- Bạn có thể ước chừng phương thức
showOpenFilePicker()
bằng phần tử<input type="file">
. - Bạn có thể mô phỏng phương thức
showSaveFilePicker()
bằng phần tử<a download="file_name">
, mặc dù điều này sẽ kích hoạt một lượt tải xuống theo phương thức lập trình và không cho phép ghi đè các tệp hiện có. - Phương thức
showDirectoryPicker()
có thể được mô phỏng một phần bằng phần tử<input type="file" webkitdirectory>
không chuẩn.
Chúng tôi đã phát triển một thư viện có tên là browser-fs-access. Thư viện này sử dụng API Truy cập hệ thống tệp bất cứ khi nào có thể và quay lại các tuỳ chọn tốt nhất tiếp theo trong mọi trường hợp khác.
Tính bảo mật và quyền truy cập
Nhóm Chrome đã thiết kế và triển khai API Truy cập hệ thống tệp bằng các nguyên tắc cốt lõi được xác định trong bài viết Kiểm soát quyền truy cập vào các tính năng mạnh mẽ của nền tảng web, bao gồm cả quyền kiểm soát và tính minh bạch của người dùng cũng như tính công thái học của người dùng.
Mở tệp hoặc lưu tệp mới
Khi mở tệp, người dùng cấp quyền đọc tệp hoặc thư mục bằng bộ chọn tệp.
Bạn chỉ có thể hiển thị bộ chọn tệp đang mở bằng cử chỉ của người dùng khi được phân phát từ một bối cảnh bảo mật. Nếu thay đổi ý định, người dùng có thể huỷ lựa chọn trong bộ chọn tệp và trang web sẽ không có quyền truy cập vào bất kỳ nội dung nào. Hành vi này giống với hành vi của phần tử <input type="file">
.
Tương tự, khi một ứng dụng web muốn lưu tệp mới, trình duyệt sẽ hiển thị bộ chọn tệp lưu, cho phép người dùng chỉ định tên và vị trí của tệp mới. Vì họ đang lưu một tệp mới vào thiết bị (thay vì ghi đè tệp hiện có), nên bộ chọn tệp sẽ cấp cho ứng dụng quyền ghi vào tệp.
Thư mục bị hạn chế
Để giúp bảo vệ người dùng và dữ liệu của họ, trình duyệt có thể hạn chế khả năng người dùng lưu vào một số thư mục nhất định, chẳng hạn như các thư mục hệ điều hành cốt lõi như Windows, thư mục Thư viện macOS. Khi điều này xảy ra, trình duyệt sẽ hiển thị lời nhắc và yêu cầu người dùng chọn một thư mục khác.
Sửa đổi tệp hoặc thư mục hiện có
Ứng dụng web không thể sửa đổi tệp trên ổ đĩa nếu không có sự cho phép rõ ràng của người dùng.
Lời nhắc cấp quyền
Nếu người dùng muốn lưu các thay đổi đối với một tệp mà trước đó họ đã cấp quyền đọc, thì trình duyệt sẽ hiển thị lời nhắc cấp quyền, yêu cầu trang web được ghi các thay đổi vào ổ đĩa. Yêu cầu cấp quyền chỉ có thể được kích hoạt bằng cử chỉ của người dùng, chẳng hạn như bằng cách nhấp vào nút Lưu.
Ngoài ra, một ứng dụng web chỉnh sửa nhiều tệp, chẳng hạn như IDE, cũng có thể yêu cầu quyền lưu các thay đổi tại thời điểm mở.
Nếu người dùng chọn Huỷ và không cấp quyền ghi, thì ứng dụng web không thể lưu các thay đổi vào tệp cục bộ. Ứng dụng phải cung cấp một phương thức thay thế để người dùng lưu dữ liệu của họ, chẳng hạn như bằng cách cung cấp cách "tải tệp xuống" hoặc lưu dữ liệu vào đám mây.
Sự minh bạch
Sau khi người dùng cấp quyền cho một ứng dụng web để lưu tệp cục bộ, trình duyệt sẽ hiển thị một biểu tượng trong thanh địa chỉ. Khi nhấp vào biểu tượng này, một cửa sổ bật lên sẽ hiển thị danh sách các tệp mà người dùng đã cấp quyền truy cập. Người dùng có thể thu hồi quyền truy cập đó bất cứ lúc nào nếu họ muốn.
Quyền ổn định
Ứng dụng web có thể tiếp tục lưu các thay đổi đối với tệp mà không cần nhắc cho đến khi tất cả các thẻ cho nguồn gốc của tệp đó đã đóng. Sau khi một thẻ bị đóng, trang web sẽ mất tất cả quyền truy cập. Vào lần tiếp theo người dùng sử dụng ứng dụng web, họ sẽ được nhắc lại để truy cập vào các tệp.
Phản hồi
Chúng tôi muốn biết trải nghiệm của bạn với API Truy cập hệ thống tệp.
Giới thiệu cho chúng tôi về thiết kế API
API có hoạt động như mong đợi không? Hay có phương thức hoặc thuộc tính nào bị thiếu mà bạn cần để triển khai ý tưởng của mình không? Bạn có câu hỏi hoặc nhận xét về mô hình bảo mật không?
- Gửi vấn đề về thông số kỹ thuật trên kho lưu trữ GitHub về Quyền truy cập vào hệ thống tệp WICG hoặc thêm ý kiến của bạn vào một vấn đề hiện có.
Bạn gặp vấn đề khi triển khai?
Bạn có phát hiện lỗi khi triển khai Chrome không? Hay cách triển khai có khác với thông số kỹ thuật không?
- Gửi lỗi tại https://new.crbug.com. Hãy nhớ cung cấp càng nhiều thông tin chi tiết càng tốt, hướng dẫn cách tái tạo và đặt Components (Thành phần) thành
Blink>Storage>FileSystem
. Glitch rất hữu ích để chia sẻ các bản tái hiện nhanh.
Bạn có dự định sử dụng API này không?
Bạn có dự định sử dụng API Truy cập hệ thống tệp trên trang web của mình không? Sự ủng hộ công khai của bạn giúp chúng tôi ưu tiên các tính năng và cho các nhà cung cấp trình duyệt khác thấy tầm quan trọng của việc hỗ trợ các tính năng đó.
- Hãy chia sẻ cách bạn dự định sử dụng công cụ này trên luồng thảo luận Discourse của WICG.
- Gửi một tweet đến @ChromiumDev bằng hashtag
#FileSystemAccess
và cho chúng tôi biết bạn đang sử dụng ở đâu và như thế nào.
Đường liên kết hữu ích
- Video giải thích công khai
- Thông số kỹ thuật về quyền truy cập vào hệ thống tệp và Thông số kỹ thuật về tệp
- Theo dõi lỗi
- Mục nhập trên ChromeStatus.com
- Định nghĩa TypeScript
- File System Access API – Mô hình bảo mật Chromium
- Thành phần Blink:
Blink>Storage>FileSystem
Lời cảm ơn
Thông số kỹ thuật của API Truy cập hệ thống tệp do Marijn Kruisselbrink viết.