Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[zh-cn]: sync translation for File System API #23425

Merged
merged 7 commits into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions files/zh-cn/web/api/file_system_api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: 文件系统 API
slug: Web/API/File_System_API
l10n:
sourceCommit: 2b6bddfe281c0179fbde9c870f9de7c0dc3829e8
sourceCommit: 0c3f18aca2c8a93d3982183f64bf7762c2c310b0
---

{{securecontext_header}}{{DefaultAPISidebar("File System API")}}{{AvailableInWorkers}}
Expand All @@ -20,7 +20,7 @@ l10n:
你还可以从以下途径获得句柄:

- {{domxref('HTML Drag and Drop API', 'HTML 拖放 API', '', 'nocode')}} 的 {{domxref('DataTransferItem.getAsFileSystemHandle()')}} 方法。
- [文件处理 API](https://developer.chrome.com/en/articles/file-handling/)。
- [文件处理 API](https://developer.chrome.com/docs/capabilities/web-apis/file-handling)。

每种句柄都提供了其独有的功能,取决于你使用的种类,会有些许差异(详见[接口](#接口)部分)。在获得句柄后,你便可以访问文件的数据或是被选中的目录的信息(包含子目录)。此 API 开辟了 web 此前一直缺乏的潜在功能。但不论如何,安全性是设计 API 时的首要考量,除非用户明确授权,否则就不允许访问文件和目录的数据(注意:[源私有文件系统](#源私有文件系统)并非如此,因为其对用户不可见)。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: 源私有文件系统
slug: Web/API/File_System_API/Origin_private_file_system
l10n:
sourceCommit: 2b6bddfe281c0179fbde9c870f9de7c0dc3829e8
sourceCommit: 2cba64f68aab9e233fecfc2bab8bea4118716c14
---

{{securecontext_header}}{{DefaultAPISidebar("File System API")}}{{AvailableInWorkers}}
Expand Down Expand Up @@ -102,7 +102,7 @@ await (await navigator.storage.getDirectory()).remove({ recursive: true });

### 列出文件夹中的内容

{{domxref("FileSystemDirectoryHandle")}} 是一个[异步迭代器](/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#异步迭代器和异步可迭代协议)。所以,你可以用 [`for awaitof`](/zh-CN/docs/Web/JavaScript/Reference/Statements/for-await...of) 循环和诸如 [`entries()`](/zh-CN/docs/Web/API/FileSystemDirectoryHandle/entries)、[`values()`](/zh-CN/docs/Web/API/FileSystemDirectoryHandle/entries) 和 [`keys()`](/zh-CN/docs/Web/API/FileSystemDirectoryHandle/entries) 这样的标准方法对其进行迭代。
{{domxref("FileSystemDirectoryHandle")}} 是一个[异步迭代器](/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#异步迭代器和异步可迭代协议)。所以,你可以用 [`for await...of`](/zh-CN/docs/Web/JavaScript/Reference/Statements/for-await...of) 循环和诸如 [`entries()`](/zh-CN/docs/Web/API/FileSystemDirectoryHandle/entries)、[`values()`](/zh-CN/docs/Web/API/FileSystemDirectoryHandle/entries) 和 [`keys()`](/zh-CN/docs/Web/API/FileSystemDirectoryHandle/entries) 这样的标准方法对其进行迭代。

例如:

Expand Down Expand Up @@ -179,7 +179,7 @@ size = accessHandle.getSize();
const dataView = new DataView(new ArrayBuffer(size));

// 将整个文件读取到数据视图。
accessHandle.read(dataView);
accessHandle.read(dataView, { at: 0 });
// 打印 `"Some textMore content"`。
console.log(textDecoder.decode(dataView));

Expand Down
2 changes: 1 addition & 1 deletion files/zh-cn/web/api/filesystemdirectoryhandle/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: FileSystemDirectoryHandle
slug: Web/API/FileSystemDirectoryHandle
l10n:
sourceCommit: be3c45cd7a4d5c04139eceae10f7368251cdca64
sourceCommit: e92950d09467164afc9dfd8b35be9c909b63a8ab
---

{{securecontext_header}}{{APIRef("File System API")}}{{AvailableInWorkers}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: FileSystemFileHandle:createSyncAccessHandle() 方法
slug: Web/API/FileSystemFileHandle/createSyncAccessHandle
l10n:
sourceCommit: f10fbe2d2dc4857bf29ce955689a7ba7c1ffac8b
sourceCommit: 2b6f99e45534ce662f842d8b4d2f7845492e353c
---

{{securecontext_header}}{{APIRef("File System API")}}{{AvailableInWorkers("dedicated")}}
Expand All @@ -15,11 +15,23 @@ l10n:

```js-nolint
createSyncAccessHandle()
createSyncAccessHandle(options)
```

### 参数

无。
- `options` {{optional_inline}}

- : 一个具有以下属性的对象:

- `mode` {{optional_inline}} {{non-standard_inline}}
- : 指定访问句柄的锁定模式的字符串。默认值为 `"readwrite"`。可能的值包括:
- `"read-only"`
- : 可以同时在一个文件上打开多个 `FileSystemSyncAccessHandle` 对象(例如,在多个选项卡中使用同一个应用时),前提是它们都以 `"read-only"` 模式打开。打开后,可以在句柄上调用类似读取的方法——{{domxref("FileSystemSyncAccessHandle.read", "read()")}}、{{domxref("FileSystemSyncAccessHandle.getSize", "getSize()")}} 和 {{domxref("FileSystemSyncAccessHandle.close", "close()")}}。
skyclouds2001 marked this conversation as resolved.
Show resolved Hide resolved
- `"readwrite"`
- : 每个文件只能打开一个 `FileSystemSyncAccessHandle` 对象。如果在第一个句柄关闭之前尝试打开后续句柄,则会导致抛出 `NoModificationAllowedError` 异常。打开后,可以调用句柄上的任何可用方法。
- `"readwrite-unsafe"`
- : 可以同时在一个文件上打开多个 `FileSystemSyncAccessHandle` 对象,前提是它们都以 `"readwrite-unsafe"` 模式打开。打开后,可以在句柄上调用任何可用的方法。

### 返回值

Expand All @@ -34,10 +46,12 @@ createSyncAccessHandle()
- `NotFoundError` {{domxref("DOMException")}}
- : 如果未找到当前条目,则抛出该异常。
- `NoModificationAllowedError` {{domxref("DOMException")}}
- : 如果浏览器无法获得文件句柄所关联的文件的锁定,抛出此异常。
- : 如果浏览器无法获得文件句柄所关联的文件的锁定,抛出此异常。这可能是因为 `mode` 被设置为 `readwrite`,并尝试同时打开多个句柄。

## 示例

### 基本用法

以下异步事件处理函数处于 Web Worker 上下文。其中的代码片段创建了一个同步文件访问句柄。

```js
Expand All @@ -58,6 +72,137 @@ onmessage = async (e) => {
};
```

### 带有 `mode` 选项的完整示例

我们的 [`createSyncAccessHandle()` 模式测试](https://createsyncaccesshandle-mode-test.glitch.me/) 示例提供了一个 {{htmlelement("input")}} 字段来输入文本,以及两个按钮——一个用于将输入的文本写入原始私有文件系统中的文件末尾,另一个用于在文件太满时清空文件。
skyclouds2001 marked this conversation as resolved.
Show resolved Hide resolved

尝试探索上面的演示,打开浏览器开发者控制台,以便你可以看到正在发生的事情。如果你尝试在多个浏览器选项卡中打开演示,你会发现可以同时打开多个句柄以同时写入文件。这是因为在 `createSyncAccessHandle()` 调用上设置了 `mode: "readwrite-unsafe"`。
skyclouds2001 marked this conversation as resolved.
Show resolved Hide resolved

下面我们将探索代码。

#### HTML

两个 {{htmlelement("button")}} 元素和文本 {{htmlelement("input")}} 字段如下所示:

```html
<ol>
<li>
<label for="filetext">输入要写入文件的文本:</label>
<input type="text" id="filetext" name="filetext" />
</li>
<li>将你的文本写入文件:<button class="write">撰写文本</button></li>
skyclouds2001 marked this conversation as resolved.
Show resolved Hide resolved
<li>
如果文件太满,则清空该文件:
<button class="empty">空文件</button>
skyclouds2001 marked this conversation as resolved.
Show resolved Hide resolved
</li>
</ol>
```

#### 主线程 JavaScript

HTML 文件中的主线程 JavaScript 如下所示。我们获取对写入文本按钮、清空文件按钮和文本输入字段的引用,然后使用 {{domxref("Worker.Worker", "Worker()")}} 构造函数创建一个新的 Web Worker。然后我们定义两个函数并将它们设置为按钮上的事件处理器:

- 单击写入文本按钮时运行 `writeToOPFS()`。此函数使用 {{domxref("Worker.postMessage()")}} 方法将文本字段的输入值发布到对象内的 Worker,然后清空文本字段,为下一次添加做好准备。请注意传递的对象还包含 `command: "write"` 属性,以指定我们想要使用此消息触发写入操作。
- 单击清空文件按钮时运行 `emptyOPFS()`。这会将包含 `command: "empty"` 属性的对象发布到 Worker,指定要清空文件。

```js
const writeBtn = document.querySelector(".write");
const emptyBtn = document.querySelector(".empty");
const fileText = document.querySelector("#filetext");

const opfsWorker = new Worker("worker.js");

function writeToOPFS() {
opfsWorker.postMessage({
command: "write",
content: fileText.value,
});
console.log("主线程脚本:发送给 worker 的文本");
fileText.value = "";
}

function emptyOPFS() {
opfsWorker.postMessage({
command: "empty",
});
}

writeBtn.addEventListener("click", writeToOPFS);
emptyBtn.addEventListener("click", emptyOPFS);
```

#### Worker 线程 JavaScript

worker JavaScript 如下所示。

首先,我们运行一个名为 `initOPFS()` 的函数,该函数使用 {{domxref("StorageManager.getDirectory()")}} 获取对 OPFS 根的引用,使用 {{domxref("FileSystemDirectoryHandle.getFileHandle()")}} 创建文件并返回其句柄,然后使用 `createSyncAccessHandle()` 返回 {{domxref("FileSystemSyncAccessHandle")}}。此调用包括 `mode: "readwrite-unsafe"` 属性,允许多个句柄同时访问同一文件。

```js
let accessHandle;

async function initOPFS() {
const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle("file.txt", { create: true });
accessHandle = await fileHandle.createSyncAccessHandle({
mode: "readwrite-unsafe",
});
}

initOPFS();
```

在 worker 的 [message 事件](/zh-CN/docs/Web/API/Worker/message_event)处理器中,我们首先使用 {{domxref("FileSystemSyncAccessHandle.getSize", "getSize()")}} 获取文件的大小。然后,我们检查消息中发送的数据是否包含 `command` 属性值 `"empty"`。如果是,我们使用 {{domxref("FileSystemSyncAccessHandle.truncate", "truncate()")}} 清空文件,值为 `0`,并更新 `size` 变量中包含的文件大小。

如果消息数据是其他内容,我们:

- 创建新的 {{domxref("TextEncoder")}} 和 {{domxref("TextDecoder")}} 来处理稍后对文本内容的编码和解码。
- 使用 {{domxref("FileSystemSyncAccessHandle.write", "write()")}} 对消息数据进行编码并将结果写入文件末尾,然后更新 `size` 变量中包含的文件大小。
- 创建一个 {{jsxref("DataView")}} 来包含文件内容,并使用 {{domxref("FileSystemSyncAccessHandle.read", "read()")}} 将内容读入其中。
- 解码 `DataView` 内容并将其记录到控制台。

```js
onmessage = function (e) {
console.log("Worker:从主线程收到消息");

// 获取文件当前大小
let size = accessHandle.getSize();

if (e.data.command === "empty") {
// 将文件截断为 0 字节
accessHandle.truncate(0);

// 获取文件当前大小
size = accessHandle.getSize();
} else {
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();

// 对要写入文件的内容进行编码
const content = textEncoder.encode(e.data.content);
// 在文件末尾写入内容
accessHandle.write(content, { at: size });

// 获取文件当前大小
size = accessHandle.getSize();

// 准备文件长度的数据视图
const dataView = new DataView(new ArrayBuffer(size));

// 将整个文件读入数据视图
accessHandle.read(dataView, { at: 0 });

// 将当前文件内容记录到控制台
console.log("File contents: " + textDecoder.decode(dataView));
skyclouds2001 marked this conversation as resolved.
Show resolved Hide resolved

// 刷新更改
accessHandle.flush();
}

// 将文件的大小记录到控制台
console.log("大小:" + size);
};
```

## 规范

{{Specifications}}
Expand Down
124 changes: 122 additions & 2 deletions files/zh-cn/web/api/filesystemfilehandle/createwritable/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: FileSystemFileHandle:createWritable() 方法
slug: Web/API/FileSystemFileHandle/createWritable
l10n:
sourceCommit: f10fbe2d2dc4857bf29ce955689a7ba7c1ffac8b
sourceCommit: 1a7695e13c51d85a81e3e5d85feedbc5dbd2a379
---

{{securecontext_header}}{{APIRef("File System API")}}{{AvailableInWorkers}}
Expand All @@ -26,6 +26,12 @@ createWritable(options)

- `keepExistingData` {{optional_inline}}
- : {{jsxref('Boolean', '布尔值', '', 'nocode')}},默认为 `false`。当设为 `true` 时,如果文件存在,则现将现有文件的内容复制到临时文件,否则临时文件初始时内容为空。
- `mode` {{optional_inline}} {{non-standard_inline}}
- : 指定可写文件流的锁定模式的字符串。默认值为 `"siloed"`。可能的值包括:
- `"exclusive"`
- : 只能打开一个 `FileSystemWritableFileStream` 写入器。在第一个写入器关闭之前尝试打开后续写入器会导致抛出 `NoModificationAllowedError` 异常。
- `"siloed"`
- : 可以同时打开多个 `FileSystemWritableFileStream` 写入器,每个写入器都有自己的交换文件,例如在多个选项卡中使用同一个应用时。最后打开的写入器会写入其数据,因为每个写入器关闭时都会刷新数据。
skyclouds2001 marked this conversation as resolved.
Show resolved Hide resolved

### 返回值

Expand All @@ -38,12 +44,14 @@ createWritable(options)
- `NotFoundError` {{domxref("DOMException")}}
- : 如果未找到当前条目,则抛出该异常。
- `NoModificationAllowedError` {{domxref("DOMException")}}
- : 如果浏览器无法获取与文件句柄关联的文件的锁,则抛出该异常。
- : 如果浏览器无法获取与文件句柄关联的文件的锁,则抛出该异常。这可能是因为 `mode` 设置为 `exclusive`,并且尝试同时打开多个写入器。
- `AbortError` {{domxref("DOMException")}}
- : 如果实现定义的恶意软件扫描和安全浏览检查失败,则抛出此异常。

## 示例

### 基本用法

以下异步函数用于将给定内容写入文件句柄,从而写入磁盘。

```js
Expand All @@ -59,6 +67,118 @@ async function writeFile(fileHandle, contents) {
}
```

### 通过选项扩展用途

我们的 [`createWritable()` 模式测试](https://createwritable-mode-test.glitch.me/)示例提供了一个 {{htmlelement("button")}} 来选择要写入的文件,一个文本 {{htmlelement("input")}} 字段,你可以在其中输入一些要写入文件的文本,以及第二个 `<button>` 来将文本写入文件。

在上面的演示中,尝试选择文件系统上的文本文件(或输入新文件名),在输入字段中输入一些文本,然后将文本写入文件。打开文件系统上的文件以检查写入是否成功。

此外,尝试同时在两个浏览器选项卡中打开页面。在第一个标签页中选择要写入的文件,然后立即尝试在第二个标签页中选择要写入的同一文件。你应该会收到一条错误消息,因为我们在 `createWritable()` 调用中设置了 `mode: "exclusive"`。
skyclouds2001 marked this conversation as resolved.
Show resolved Hide resolved

下面我们将探索代码。

#### HTML

The two {{htmlelement("button")}} elements and text {{htmlelement("input")}} field look like this:
skyclouds2001 marked this conversation as resolved.
Show resolved Hide resolved

```html
<ol>
<li>选择要写入的文件:<button class="select">选择文件</button></li>
<li>
<label for="filetext">输入要写入文件的文本:</label>
<input type="text" id="filetext" name="filetext" disabled />
</li>
<li>
将你的文本写入文件:
<button class="write" disabled>撰写文本</button>
skyclouds2001 marked this conversation as resolved.
Show resolved Hide resolved
</li>
</ol>
```

文本输入字段和写入文本按钮最初通过 [`disabled`](/zh-CN/docs/Web/HTML/Attributes/disabled) 属性设置为禁用——直到用户选择要写入的文件时,它们才应被使用。

```css hidden
li {
margin-bottom: 10px;
}
```

#### JavaScript

我们首先获取对选择文件按钮、写入文本按钮和文本输入字段的引用。我们还声明一个全局变量 `writableStream`,它将存储对可写流的引用,用于在创建后将文本写入文件。我们最初将其设置为 `null`。

```js
const selectBtn = document.querySelector(".select");
const writeBtn = document.querySelector(".write");
const fileText = document.querySelector("#filetext");

let writableStream = null;
```

接下来,我们创建一个名为 `selectFile()` 的异步函数,当按下选择按钮时,我们将调用该函数。它使用 {{domxref("Window.showSaveFilePicker()")}} 方法向用户显示文件选择器对话框,并为他们选择的文件创建文件句柄。在该句柄上,我们调用 `createWritable()` 方法来创建一个流,以将文本写入所选文件。如果调用失败,我们会将错误记录到控制台。

我们向 `createWritable()` 传递一个包含以下选项的选项对象:

- `keepExistingData: true`:如果所选文件已存在,则在开始写入之前,将其中包含的数据复制到临时文件中。

- `mode: "exclusive"`:表示只能同时在文件句柄上打开一个写入器。如果第二个用户加载示例并尝试选择文件,他们将收到错误。

最后,我们启用输入字段和写文本按钮,因为它们是下一步所需要的,并禁用选择文件按钮(目前不需要)。

```js
async function selectFile() {
// 创建新句柄
const handle = await window.showSaveFilePicker();

// 创建 FileSystemWritableFileStream 来写入
try {
writableStream = await handle.createWritable({
keepExistingData: true,
mode: "exclusive",
});
} catch (e) {
if (e.name === "NoModificationAllowedError") {
console.log(`你现在无法访问该文件;其他人正在尝试修改它。请稍后重试。`);
} else {
console.log(e.message);
}
}

// 启用文本字段和写入按钮,禁用选择按钮
fileText.disabled = false;
writeBtn.disabled = false;
selectBtn.disabled = true;
}
```

我们的下一个函数 `writeFile()` 使用 {{domxref("FileSystemWritableFileStream.write()")}} 将输入字段中输入的文本写入所选文件,然后清空输入字段。然后我们使用 {{domxref("WritableStream.close()")}} 关闭可写流,并重置演示以便再次运行——控件的 `disable` 状态切换回其原始状态,并且 `writableStream` 变量设置回 `null`。

```js
async function writeFile() {
// 将文本写入我们的文件并清空文本字段
await writableStream.write(fileText.value);
fileText.value = "";

// 关闭文件并将内容写入磁盘。
await writableStream.close();

// 禁用文本字段和写入按钮,启用选择按钮
fileText.disabled = true;
writeBtn.disabled = true;
selectBtn.disabled = false;

// 将 writeableStream 重新设置为 null
writableStream = null;
}
```

为了使演示运行,我们在按钮上设置了事件监听器,以便在每个按钮被点击时运行相关的函数。

```js
selectBtn.addEventListener("click", selectFile);
writeBtn.addEventListener("click", writeFile);
```

## 规范

{{Specifications}}
Expand Down
Loading