API Truy cập hệ thống tệp: đơn giản hoá quyền truy cập vào các tệp cục bộ

API Truy cập 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 đối với 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 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 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 người dùng cấp quyền truy cập vào ứ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à các 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ở một thư mục và liệt kê nội dung của thư mục đó.

Nếu bạn đã từng đọc và ghi tệp trước đây, phần lớn những gì tôi sắp chia sẻ sẽ là quen thuộc với bạn. Bạn nên đọc hướng dẫn này vì không phải mọi hệ thống đều 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 hiện chỉ hoạt động sau một cờ. Chúng tôi đang nỗ lực hỗ trợ Android theo ngữ 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 tệp văn bản trình chỉnh sửa. Cửa sổ này cho phép bạn mở, chỉnh sửa tệp văn bản, lưu thay đổi trở lại đĩa hoặc bắt đầu tệp mới và lưu thay đổi vào đĩa. Không có gì cầu kỳ nhưng cung cấp đủ để giúp bạn hiểu các khái niệm.

Hỗ trợ trình duyệt

Hỗ trợ trình duyệt

  • Chrome: 86.
  • Cạnh: 86.
  • Firefox: không được hỗ trợ.
  • Safari: không được hỗ trợ.

Nguồn

Phát hiện tính năng

Để tìm hiểu xem API Truy cập 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 có được hỗ trợ hay không mà bạn quan tâm đã tồn tại.

if ('showOpenFilePicker' in self) {
  // The `showOpenFilePicker()` method of the File System Access API is supported.
}

Dùng thử

Xem cách API Truy cập hệ thống tệp hoạt động trong bản minh hoạ về trình chỉnh sửa văn bản.

Đọc một 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 xử lý là yêu cầu người dùng chọn một tệp, sau đó mở và đọc tệp đó khỏi đĩ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, ứng dụng này sẽ hiện hộp thoại 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 tệp tên người dùng. Tham 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, đối với ví dụ: bằng cách cho phép người dùng chọn nhiều tệp hoặc thư mục hoặc nhiều loại tệp. Nếu không chỉ định bất kỳ tuỳ chọn nào, bộ chọn tệp sẽ cho phép người dùng chọn một tệp. Đây là hoàn hảo cho một trình soạn thảo văn bản.

Giống như nhiều API mạnh mẽ khác, việc gọi showOpenFilePicker() phải được thực hiện trong một bảo mật ngữ cảnh và phải được 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 tên người dùng, trong trường hợp này là mảng một phần tử với một FileSystemFileHandle chứa các thuộc tính và cần thiết để tương tác với tệp đó.

Bạn nên giữ lại một tham chiếu đến tên người dùng tệp để có thể sử dụng sau này. Sẽ là để lưu những 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 một tệp từ hệ thống tệp

Giờ đây, khi đã có tên người 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 email. Việc gọi handle.getFile() sẽ trả về 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, (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 được sửa đổi, đối tượng File sẽ trở thành không thể đọc được và bạn phải gọi lại getFile() để lấy đối tượng File mới nhằm đọc nội dung đã 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 chọn một tệp, ứng dụng đọc nội dung và đặt chúng 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 chỉnh sửa văn bản, có hai cách để lưu tệp: LưuLưu dưới dạng. Lưu ghi các thay đổi trở lại tệp gốc bằng cách sử dụng tên người dùng tệp được truy xuất trước đó. Nhưng Lưu Khi tạo một tệp mới nên yêu cầu một tên người dùng mới cho tệp.

Tạo tệp mới

Để lưu một tệp, hãy gọi hàm showSaveFilePicker() để cho thấy bộ chọn tệp trong "lưu" chế độ này, cho phép người dùng chọn tệp mới họ muốn sử dụng để lưu. Đối với văn bản trình chỉnh sửa của mình, tôi cũng muốn tiện ích này tự động thêm một tiện ích .txt, vì vậy, tôi đã cung cấp thêm tham số.

async function getNewFileHandle() {
  const options = {
    types: [
      {
        description: 'Text Files',
        accept: {
          'text/plain': ['.txt'],
        },
      },
    ],
  };
  const handle = await window.showSaveFilePicker(options);
  return handle;
}

Lưu thay đổi vào đĩa

Bạn có thể tìm thấy tất cả mã để lưu thay đổi cho tệp trong bản minh hoạ trình chỉnh sửa văn bản của tôi trên GitHub. Các hoạt động tương tác chính với hệ thống tệp nằm trong fs-helpers.js. Cách đơn giản nhất là quá trình này có dạng như mã sau. Tôi sẽ giải thích 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 bằng đối tượng FileSystemWritableFileStream là một lớp con trong tổng số WritableStream. Tạo luồng bằng cách gọi createWritable() trên tệp xử lý. 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 hay chưa quyền ghi vào tệp. Nếu bạn chưa cấp quyền ghi, trình duyệt sẽ nhắc để người dùng cấp quyền. Nếu chưa được cấp quyền, createWritable() sẽ gửi ra một DOMException và ứng dụng sẽ không thể ghi vào tệp. Trong trình chỉnh sửa văn bản, Các đối tượng DOMException được xử lý trong phương thức saveFile().

Phương thức write() nhận một chuỗi. Đây là chuỗi cần thiết cho một trình chỉnh sửa văn bản. Tuy nhiên, quá trình này cũng có thể mất nhiều thời gian BufferSource hoặc Blob. Ví dụ: bạn có thể chuyển luồng trực tiếp đến nó:

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ại một vị trí cụ thể hoặc đổi kích thước tệp.

Chỉ định tên tệp được đề xuất và thư mục bắt đầu

Trong nhiều trường hợp, bạn có thể muốn ứng dụng của mình đề xuất tên tệp hoặc vị trí mặc định. Ví dụ: văn bản trình chỉnh sửa nên đề xuất tên tệp mặc định là Untitled Text.txt thay vì Untitled. Bạn bạn có thể thực hiện điều này bằng cách truyền thuộc tính suggestedName như một phần của các tuỳ chọn showSaveFilePicker.

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

Tương tự như vậy đối với thư mục khởi động mặc định. Nếu đang xây dựng một trình chỉnh sửa văn bản, có thể bạn cầ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 hình ảnh trình chỉnh sửa của bạn, bạn nên bắt đầu trong thư mục pictures mặc định. Bạn có thể đề xuất thời điểm bắt đầu mặc định thư mục bằng cách truyền thuộc tính startIn đến showSaveFilePicker, showDirectoryPicker() hoặc showOpenFilePicker như vậy.

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 máy tính của người dùng, nếu có.
  • documents: Thư mục lưu trữ các tài liệu do người dùng tạo.
  • downloads: Thư mục nơi thường lưu trữ các tệp được tải xuống.
  • music: Thư mục nơi thường lưu trữ các 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 nơi video hoặc phim thường được lưu trữ.

Ngoài các thư mục hệ thống phổ biến, bạn cũng có thể truyền một tên người dùng tệp hoặc thư mục hiện có dưới dạng một giá trị cho startIn. Sau đó, hộp thoại sẽ mở ra trong cùng 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 những mục đích khác nhau. Ví dụ: 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 tệp sẽ mở tại vị trí được nhớ cuối cùng. Bạn có thể tránh né điều này bằng cách lưu trữ các giá trị id cho mỗi loại bộ chọn. Nếu bạn chỉ định id, thì quá trình triển khai bộ chọn tệp sẽ ghi nhớ một riêng thư mục 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ữ các tên người dùng tệp hoặc tên người dùng thư mục trong IndexedDB

Tên người dùng tệp và tên người dùng thư mục có thể chuyển đổi tuần tự, tức là bạn có thể lưu tệp hoặc xử lý thư mục đến IndexedDB hoặc gọi postMessage() để gửi chúng giữa cùng một cấp cao nhất máy chủ gốc.

Lưu tệp hoặc tên người dùng thư mục vào IndexedDB có nghĩa là bạn có thể lưu trữ trạng thái hoặc nhớ trạng thái nào các tệp hoặc thư mục mà người dùng đang thao tác. Thao tác này giúp bạn có thể giữ lại danh sách các sự kiện đã mở gần đây hoặc đã chỉnh sửa, đề nghị mở lại tệp gần đây nhất khi ứng dụng được mở, khôi phục lại trạng thái làm việc trước đó và nhiều thứ khác. Trong trình chỉnh sửa 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 đã truy cập đã mở để có thể truy cập lại vào những tệp đó.

Ví dụ về mã sau đây cho thấy việc lưu trữ và truy xuất một tên người dùng tệp và một tên người dùng thư mục. Bạn có thể xem điều này trong thực tế trên Glitch. (Tôi sử dụng thư viện idb-keyval cho 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à trình xử lý tệp hoặc thư mục được lưu trữ

quyền không phải lúc nào cũng được duy trì giữa các phiên, bạn cần xác minh xem người dùng đã cấp quyền cho tệp hoặc thư mục bằng queryPermission(). Nếu họ chưa nhận được, hãy gọi requestPermission() để yêu cầu (lại). Cách hoạt động tương tự cũng áp dụng cho tên người dùng tệp và thư mục. Bạn cần chạy fileOrDirectoryHandle.requestPermission(descriptor) hoặc fileOrDirectoryHandle.queryPermission(descriptor).

Trong trình chỉnh sửa văn bản, tôi đã tạo một phương thức verifyPermission() để kiểm tra xem người dùng đã thực hiện đã cấp quyền và nếu cần, đưa ra yêu cầu.

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 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 thấy một lời nhắc khi mở tệp, đồng thời cấp quyền đọc và ghi vào tệp.

Mở một thư mục và liệt kê nội dung trong đó

Để liệt kê tất cả cá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 là trả về, cho phép bạn liệt kê và truy cập các tệp của thư mục. Theo mặc định, bạn sẽ đọc quyền truy cập vào các tệp trong thư mục, nhưng nếu cần quyền ghi, bạn có thể chuyể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);
  }
});

Ví dụ: nếu bạn cần truy cập vào từng tệp bằng getFile() để lấy từng tệp không sử dụng await trên từng kết quả theo trình tự, mà hãy xử lý tất cả các tệp trong song song, ví dụ: 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 getFileHandle() hoặc tương ứng với getDirectoryHandle() . Bằng cách truyền vào một đối tượng options (không bắt buộc) có khoá create và giá trị boolean là true hoặc false, bạn có thể xác định xem có nên tạo tệp hoặc thư mục mới hay không nếu chưa 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 nên phân giải đường dẫn của mục liên quan. 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. Để giải quyết vấn đề này, mục có thể là phần tử 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 một thư mục

Nếu đã có đượ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 chứa bằng Phương thức removeEntry(). Đối với các thư mục, thao tác xoá có thể là đệ quy và bao gồm tất cả thư mục con và các tệp 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ên người dùng tệp hoặc thư mục, hãy gọi remove() trên FileSystemFileHandle hoặc FileSystemDirectoryHandle để xoá.

// 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 đến 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 FileSystemFileHandleFileSystemDirectoryHandle. Phương thức move() nhận một hoặc hai tham số. Hộp thoại đầu tiên có thể: là một chuỗi có tên mới hoặc FileSystemDirectoryHandle vào thư mục đích. Trong trường hợp sau, tham số thứ hai tùy chọn là một chuỗi có tên mới, vì vậy việc di chuyển và đổi tên có thể xảy 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ả

Chiến lược phát hành đĩa đơn Giao diện Kéo và thả HTML cho phép ứng dụng web chấp nhận tệp được kéo và thả trên một trang web. Trong thao tác kéo và thả, tệp đã kéo và các mục trong thư mục 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. DataTransferItem.getAsFileSystemHandle() phương thức trả về một lời hứa có đối tượng FileSystemFileHandle nếu mục được kéo là một tệp và hứa hẹn với đối tượng FileSystemDirectoryHandle nếu mục được kéo là một thư mục. Danh sách sau đây cho thấy điều này trong thực tế. Lưu ý rằng giao diện Kéo và thả DataTransferItem.kind"file" cho cả tệp thư mục, trong khi FileSystemHandle.kind của API Truy cập hệ thống tệp là "file" cho các 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ư của máy chủ gốc

Hệ thống tệp riêng tư gốc là một điểm cuối lưu trữ, đúng như tên gọi, là điểm cuối dành riêng cho nguồn gốc của trang. Mặc dù các trình duyệt thường triển khai chế độ này bằng cách duy trì nội dung của thuộc tính này hệ thống tệp riêng tư gốc sang ổ đĩa nào đó, thì nội dung không phải là của người dùng dễ sử dụng. Tương tự, không có kỳ vọng rằng các tệp hoặc thư mục có tên khớp với tên con của hệ thống tệp riêng tư gốc. Mặc dù trình duyệt có vẻ như có một số tệp trong nội bộ—vì đây là hệ thống tệp riêng tư gốc — nên trình duyệt có thể lưu trữ những "tệp" này trong cơ sở dữ liệu hay 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, không muốn tìm các tệp đã tạo được so khớp từng tệp 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 bạn 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 });

Hỗ trợ trình duyệt

  • Chrome: 86.
  • Cạnh: 86.
  • Firefox: 111.
  • Safari: 15.2.

Nguồn

Truy cập vào các tệp được tối ưu hoá để cải thiện hiệu suất qua hệ thống tệp riêng tư của nguồn gốc

Hệ thống tệp riêng tư gốc cung cấp quyền truy cập tuỳ chọn vào một loại tệp đặc biệt được tối ưu hoá cho hiệu suất, ví dụ: bằng cách cung cấp quyền ghi tại chỗ và độc quyền đối với các dữ liệu nội dung. Trong Chromium 102 trở lên, có một phương thức khác được áp dụng cho hệ thống tệp riêng tư gốc dành cho đơn giản hoá quyền truy cập tệp: createSyncAccessHandle() (dành cho các thao tác đọc và ghi đồng bộ). Nội dung này được hiển thị vào FileSystemFileHandle, nhưng độc quyền ở 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 });

Polyfilling

Không thể điền hoàn toàn các phương thức API Truy cập hệ thống tệp.

  • Phương thức showOpenFilePicker() có thể được ước chừng bằng phần tử <input type="file">.
  • Phương thức showSaveFilePicker() có thể được mô phỏng bằng phần tử <a download="file_name">, mặc dù điều này kích hoạt quá trình tải xuống 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 chút bằng phương thức không chuẩn Phần tử <input type="file" webkitdirectory>.

Chúng tôi đã phát triển một thư viện có tên browser-fs-access sử dụng tệp System Access API (API Truy cập hệ thống) bất cứ khi nào có thể và tương thích với các lựa chọn tốt nhất tiếp theo sau đây trong tất cả các trường hợp.

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 theo các nguyên tắc cốt lõi được xác định trong Kiểm soát quyền truy cập vào các tính năng nền tảng web mạnh mẽ, bao gồm cả người dùng khả năng kiểm soát và tính minh bạch cũng như công thái học người dùng.

Mở tệp hoặc lưu tệp mới

Bộ chọn tệp để mở tệp và đọc
Bộ chọn tệp dùng để mở một tệp hiện có để đọc.

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ộ chọn tệp đang mở chỉ có thể hiển thị bằng cử chỉ của người dùng khi được cung cấp từ một chế độ bảo mật ngữ cảnh. Nếu người dùng đổi ý, họ có thể huỷ lựa chọn trong tệp và trang web không có quyền truy cập vào bất kỳ nội dung nào. Đây là hành vi tương tự như của Phần tử <input type="file">.

Bộ chọn tệp để lưu tệp vào đĩa.
Bộ chọn tệp dùng để lưu tệp vào đĩa.

Tương tự, khi một ứng dụng web muốn lưu một tệp mới, trình duyệt sẽ hiện bộ chọn lưu tệp, 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 cho thiết bị (so với việc ghi đè một tệp hiện có), bộ chọn tệp sẽ cấp quyền cho ứng dụng 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ể giới hạn khả năng lưu của người dùng vào một số các thư mục, ví dụ: 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 .

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 khi chưa nhận được sự cho phép rõ ràng của người dùng.

Lời nhắc cấp quyền

Nếu một người muốn lưu các thay đổi vào một tệp mà trước đó họ đã cấp quyền đọc, trình duyệt hiển thị lời nhắc cấp quyền, yêu cầu cấp quyền cho trang web để ghi thay đổi vào đĩa. Yêu cầu cấp quyền chỉ có thể được kích hoạt bằng một cử chỉ của người dùng, ví dụ: bằng cách nhấp vào Lưu .

Thông báo cấp quyền xuất hiện trước khi lưu tệp.
Người dùng sẽ thấy lời nhắc trước khi trình duyệt được cấp quyền ghi quyền đối với tệp hiện có.

Ngoài ra, một ứng dụng web chỉnh sửa nhiều tệp (chẳng hạn như một IDE) cũng có thể yêu cầu quyền lưu 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, ứng dụng web sẽ không thể lưu các thay đổi đối với tệp cục bộ. Phải cung cấp cho người dùng một phương thức thay thế để lưu dữ liệu của họ, cho bằng cách cung cấp cách "tải xuống" tệp hoặc lưu dữ liệu lên đám mây.

Sự minh bạch

Biểu tượng thanh địa chỉ
Biểu tượng trên thanh địa chỉ cho biết người dùng đã cấp quyền cho trang web lưu vào tệp cục bộ.

Sau khi người dùng cấp quyền cho một ứng dụng web để lưu tệp trên máy, trình duyệt sẽ hiển thị biểu tượng vào thanh địa chỉ. Khi bạn nhấp vào biểu tượng này, một cửa sổ bật lên sẽ mở ra và hiển thị danh sách các tệp mà người dùng đã cung cấp truy cập vào. Người dùng có thể thu hồi quyền truy cập đó bất cứ lúc nào nếu muốn.

Khả năng lưu trữ cố định quyền

Ứng dụng web có thể tiếp tục lưu các thay đổi đối với tệp mà không nhắc cho đến khi tất cả các thẻ cho máy chủ gốc đã đóng. Khi một thẻ bị đóng, trang web sẽ mất tất cả quyền truy cập. Lần tiếp theo khi 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.

Cho chúng tôi biết về thiết kế API

Có điều gì về API không hoạt động như bạn mong đợi không? Hoặc có phương thức nào bị thiếu hoặc thuộc tính nào bạn cần để triển khai ý tưởng của mình? Có câu hỏi hoặc nhận xét về bảo mật mẫu?

Bạn gặp vấn đề trong quá trình triển khai?

Bạn có phát hiện lỗi trong quá trình 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?

  • Báo cáo 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 tái tạo và đặt Thành phần thành Blink>Storage>FileSystem. Glitch rất hữu ích khi chia sẻ các bản trình bày nhanh.

Dự định sử dụng API?

Bạn đang định sử dụng API Truy cập hệ thống tệp trên trang web của mình? Sự hỗ trợ công khai của bạn giúp chúng tôi sắp xếp thứ tự ưu tiên đồng thời cho thấy các nhà cung cấp trình duyệt khác tầm quan trọng của việc hỗ trợ các nhà cung cấp này.

Các đường liên kết hữu ích

Xác nhận

Thông số API Truy cập hệ thống tệp được viết bởi Marijn Kruisselbrink.