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

add content for FileSystemSyncAccessHandle #21815

Merged
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
97fca21
add content for FileSystemSyncAccessHandle
chrisdavidmills Oct 25, 2022
13b8e5e
Update files/en-us/web/api/file_system_access_api/index.md
chrisdavidmills Oct 26, 2022
1e5a833
Update files/en-us/web/api/file_system_access_api/index.md
chrisdavidmills Oct 26, 2022
97e4ae4
Update files/en-us/web/api/file_system_access_api/index.md
chrisdavidmills Oct 26, 2022
161b3d6
Making fix according to tomayac comment
chrisdavidmills Oct 26, 2022
cea2ec7
Merge branch 'add-content-for-FileSystemSyncAccessHandle' of github.c…
chrisdavidmills Oct 26, 2022
a52a0c6
fixing code examples according to tomayac comments
chrisdavidmills Oct 26, 2022
0418aa8
Merge branch 'main' into add-content-for-FileSystemSyncAccessHandle
chrisdavidmills Oct 26, 2022
705ab9c
Update files/en-us/web/api/filesystemfilehandle/createsyncaccesshandl…
chrisdavidmills Oct 26, 2022
d2b8610
Update files/en-us/web/api/filesystemfilehandle/createsyncaccesshandl…
chrisdavidmills Oct 26, 2022
9ac69ce
Update files/en-us/web/api/filesystemfilehandle/index.md
chrisdavidmills Oct 26, 2022
f9d12bf
Update files/en-us/web/api/filesystemsyncaccesshandle/close/index.md
chrisdavidmills Oct 26, 2022
5dd5ac6
Update files/en-us/web/api/filesystemsyncaccesshandle/flush/index.md
chrisdavidmills Oct 26, 2022
514c0fd
Update files/en-us/web/api/filesystemsyncaccesshandle/getsize/index.md
chrisdavidmills Oct 26, 2022
1685336
Update files/en-us/web/api/filesystemsyncaccesshandle/index.md
chrisdavidmills Oct 26, 2022
8bdd5fa
Update files/en-us/web/api/filesystemsyncaccesshandle/read/index.md
chrisdavidmills Oct 26, 2022
b9b16df
Update files/en-us/web/api/filesystemsyncaccesshandle/read/index.md
chrisdavidmills Oct 26, 2022
6c518da
Update files/en-us/web/api/filesystemsyncaccesshandle/write/index.md
chrisdavidmills Oct 26, 2022
4551547
Respond to a-sully review comments and make some other fixes
chrisdavidmills Nov 18, 2022
2a359bd
more fixes for a-sully comments
chrisdavidmills Nov 22, 2022
66e92ef
Update files/en-us/web/api/file_system_access_api/index.md
teoli2003 Dec 5, 2022
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
54 changes: 50 additions & 4 deletions files/en-us/web/api/file_system_access_api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ browser-compat:
- api.FileSystemHandle
- api.FileSystemFileHandle
- api.FileSystemDirectoryHandle
- api.FileSystemSyncAccessHandle
- api.FileSystemWritableFileStream
- api.Window.showOpenFilePicker
---
Expand All @@ -28,13 +29,16 @@ The File System Access API allows read, write and file management capabilities.

This API allows interaction with files on a user's local device, or on a user-accessible network file system. Core functionality of this API includes reading files, writing or saving files, and access to directory structure.

Most of the interaction with files and directories is accomplished through handles. A parent {{domxref('FileSystemHandle')}} class helps define two child classes: {{domxref('FileSystemFileHandle')}} and {{domxref('FileSystemDirectoryHandle')}}, for files and directories respectively.
Most of the interaction with files and directories is accomplished through handles. A parent {{domxref('FileSystemHandle')}} class helps define two child classes: {{domxref('FileSystemFileHandle')}} and {{domxref('FileSystemDirectoryHandle')}}, for files and directories respectively. In addition, a third class—{{domxref('FileSystemSyncAccessHandle')}}—defines a handle for synchronous read/write operations. The synchronous nature of this class brings performance advantages intended for use in contexts where asynchronous operations come with high overhead (e.g., [WebAssembly](/en-US/docs/WebAssembly)), but it is only usable inside dedicated [Web Workers](/en-US/docs/Web/API/Web_Workers_API).
chrisdavidmills marked this conversation as resolved.
Show resolved Hide resolved

These handles represent the file or directory on the user's system. You must first gain access to them by showing the user a file or directory picker. The methods which allow this are {{domxref('window.showOpenFilePicker')}} and {{domxref('window.showDirectoryPicker')}}. Once these are called, the file picker presents itself and the user selects either a file or directory. Once this happens successfully, a handle is returned. You can also gain access to file handles via the {{domxref('DataTransferItem.getAsFileSystemHandle()')}} method of the {{domxref('HTML Drag and Drop API')}}.
The handles represent a file or directory on the user's system. You can first gain access to them by showing the user a file or directory picker. The methods which allow this are {{domxref('window.showOpenFilePicker')}} and {{domxref('window.showDirectoryPicker')}}. Once these are called, the file picker presents itself and the user selects either a file or directory. Once this happens successfully, a handle is returned. You can also gain access to file handles via the {{domxref('DataTransferItem.getAsFileSystemHandle()')}} method of the {{domxref('HTML Drag and Drop API')}}. In the case of {{domxref('FileSystemSyncAccessHandle')}}, the handle is accessed via the {{domxref('FileSystemFileHandle.createSyncAccessHandle', 'createSyncAccessHandle()')}} method.
chrisdavidmills marked this conversation as resolved.
Show resolved Hide resolved

The handle provides its own functionality and there are a few differences depending on whether a file or directory was selected (see the [interfaces](#interfaces) section for specific details). You then can access file data, or information (including children) of the directory selected.
Each handle provides its own functionality and there are a few differences depending on which one you are using (see the [interfaces](#interfaces) section for specific details). You then can access file data, or information (including children) of the directory selected.

There is also "save" functionality, using the {{domxref('FileSystemWritableFileStream')}} interface. Once the data you'd like to save is in a format of {{domxref('Blob')}}, {{jsxref("String")}} object, string literal or {{jsxref('ArrayBuffer', 'buffer')}}, you can open a stream and save the data to a file. This can be the existing file or a new file.
There is also "save" functionality:

- In the case of the asynchronous handles, use the {{domxref('FileSystemWritableFileStream')}} interface. Once the data you'd like to save is in a format of {{domxref('Blob')}}, {{jsxref("String")}} object, string literal or {{jsxref('ArrayBuffer', 'buffer')}}, you can open a stream and save the data to a file. This can be the existing file or a new file.
- In the case of the synchronous {{domxref('FileSystemSyncAccessHandle')}}, you write changes to a file using the {{domxref('FileSystemSyncAccessHandle.write', 'write()')}} method, then commit the changes to disk using {{domxref('FileSystemSyncAccessHandle.flush', 'flush()')}}.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then commit the changes to disk using {{domxref('FileSystemSyncAccessHandle.flush', 'flush()')}}

This makes it sound like it's a mandatory two-step process, whereas you should really only call flush() if you need the previous writes to be flushed to disk. Otherwise the underlying OS will flush the writes whenever it sees fit (which should be good enough in most cases...)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it seems a bit odd to include FileSystemSyncAccessHandle under "save" functionality since it provides so much more than just saves (primarily in-place editing, random-access reads and writes). It might be worth explicitly mentioning that writes using this API are in-place

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to answer any clarifying questions if you need help wording this!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I've updated the text to

In the case of the synchronous FileSystemSyncAccessHandle, you write changes to a file using the write() method. You can optionally also call flush() if you need the changes committed to disk at a specific time (otherwise you can leave the underlying operating system to handle this when it sees fit, which should be OK in most cases).

I'll also make sure that this is reflected on the specific flush() reference page.

As for including "FileSystemSyncAccessHandle under "save" functionality", I think this is OK — I think it is clear that we are just talking about the save functionality available on this class, rather than presenting it as the whole point of FileSystemSyncAccessHandle. I'll make sure that the write-in-place nature is reflected on the actual class ref pages, and earlier on in the main intro.


This API opens up potential functionality the web has been lacking. Still, security has been of utmost concern when designing the API, and access to file/directory data is disallowed unless the user specifically permits it.

Expand All @@ -46,6 +50,8 @@ This API opens up potential functionality the web has been lacking. Still, secur
- : Provides a handle to a file system entry.
- {{domxref("FileSystemDirectoryHandle")}}
- : provides a handle to a file system directory.
- {{domxref("FileSystemSyncAccessHandle")}}
- : Provides a synchronous handle to a file system entry. The synchronous nature of the file reads and writes allows for higher performance for critical methods in contexts where asynchronous operations come with high overhead, e.g., [WebAssembly](/en-US/docs/WebAssembly). This class is only accessible inside dedicated [Web Workers](/en-US/docs/Web/API/Web_Workers_API).
chrisdavidmills marked this conversation as resolved.
Show resolved Hide resolved
- {{domxref("FileSystemWritableFileStream")}}
- : is a {{domxref('WritableStream')}} object with additional convenience methods, which operates on a single file on disk.

Expand Down Expand Up @@ -175,6 +181,46 @@ writableStream.write({ type: "seek", position });
writableStream.write({ type: "truncate", size });
```

### Synchronously reading and writing a file

The following asynchronous event handler function is contained inside a Web Worker. On receiving a message from the main thread it:

- Creates a synchronous file access handle.
- Gets the size of the file and creates an {{jsxref("ArrayBuffer")}} to contain it.
- Reads the file contents into the buffer.
- Encodes the message and writes it to the end of the file.
- Persists the changes to disk and closes the access handle.

```js
onmessage = async (e) => {
// retrieve message sent to work from main script
const message = e.data;

// Get handle to draft file
const root = await navigator.storage.getDirectory();
const draftHandle = await root.getFileHandle('draft.txt', { create: true });
// Get sync access handle
const accessHandle = await draftHandle.createSyncAccessHandle();

// Get size of the file.
const fileSize = await accessHandle.getSize();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const fileSize = await accessHandle.getSize();
const fileSize = accessHandle.getSize();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thing about this one is that I'm presenting the async versions of these methods first, kind of as the "default", and then mentioning that the sync ones are available later on. So I'd rather leave this as is

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd really prefer we match the spec and have all the code snippets say that these methods are sync, perhaps with a note somewhere that they were async on earlier browser versions (you can link whatwg/fs#7). Part of the reason we were okay with this change is that we weren't expecting too many people to be using this API yet. Putting out documentation encouraging use of the async API is counterproductive in that sense.

We see the async version of this interface as a mistake and do not want to further confuse users with async methods on a _Sync_AccessHandle :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I think you've made some fair points here, plus the spec is now updated. So, for each affected page, which discusses those features and includes related code examples, I've updated the code examples to use the sync methods and included a note warning people that some browsers will still implement the incorrect async versions. Let me know you think of the wording.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wording looks good to me! 👍

Also don't forget to update this await of getSize() :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@a-sully Darnit, I've missed some; I'll check all the pages I've touched again, to make sure. Thanks for the heads-up!

// Read file content to a buffer.
const buffer = new DataView(new ArrayBuffer(fileSize));
const readBuffer = accessHandle.read(buffer, { at: 0 });

// Write the message to the end of the file.
const encoder = new TextEncoder();
const encodedMessage = encoder.encode(message);
const writeBuffer = accessHandle.write(encodedMessage, { at: readBuffer });

// Persist changes to disk.
await accessHandle.flush();
chrisdavidmills marked this conversation as resolved.
Show resolved Hide resolved

// Always close FileSystemSyncAccessHandle if done.
await accessHandle.close();
chrisdavidmills marked this conversation as resolved.
Show resolved Hide resolved
}
```

## Specifications

{{Specifications}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
title: FileSystemFileHandle.createSyncAccessHandle()
slug: Web/API/FileSystemFileHandle/createSyncAccessHandle
page-type: web-api-instance-method
tags:
- Directory
- File
- File System Access API
- FileSystemFileHandle
- Method
- createSyncAccessHandle
- working with files
browser-compat: api.FileSystemFileHandle.createSyncAccessHandle
---

{{securecontext_header}}{{DefaultAPISidebar("File System Access API")}}

The **`createSyncAccessHandle()`** method of the
{{domxref("FileSystemFileHandle")}} interface returns a {{jsxref('Promise')}} which resolves to a {{domxref('FileSystemSyncAccessHandle')}} object
that can be used to synchronously read from and write to a file. The synchronous nature of this method brings performance advantages,
but it is only usable inside dedicated [Web Workers](/en-US/docs/Web/API/Web_Workers_API).

Creating a {{domxref('FileSystemSyncAccessHandle')}} takes an exclusive lock on the file associated with the file handle. This prevents the creation of further {{domxref('FileSystemSyncAccessHandle')}}s or {{domxref('FileSystemWritableFileStream')}}s for the file, until the existing access handle is closed.

If the file on disk changes or is removed after this method is called, the returned
{{domxref('File')}} object will likely be no longer readable.
chrisdavidmills marked this conversation as resolved.
Show resolved Hide resolved

## Syntax

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

### Parameters

None.

### Return value

A {{jsxref('Promise')}} which resolves to a {{domxref('FileSystemSyncAccessHandle')}} object.

### Exceptions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put up whatwg/fs#57 a while back to try to better specify the exceptions this API throws. You're welcome to use that as a guide to expand this section

That being said, we can't quite guarantee that a particular error code maps to a specific action, since if the underlying OS provides an error when performing a file operation we often just map that to the closest DOMException (which may be NoModificationAllowedError, for example, which we'd otherwise prefer to have reserved for file locking errors)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comments @a-sully !

So for this one, I've made a couple of changes to these descriptions based on your linked descriptions (see next commit). That's a really useful issue text! Let me know what you think.

I think defining what exceptions a feature may throw on MDN has always been tricky, especially if, as in this case, the OS may muddy the waters with its own exceptions. To figure out what exceptions to write about, I tend to just read the algorithm for the feature in the spec to see what exceptions are fired at each stage, do a bit of testing to try to observe behavior, and then write about what I find. Is there a better way, or have you got any tips for making this better?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understandable! Unfortunately I don't have any other suggestions... Is it reasonable to link to whatwg/fs#57?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is useful to link it, but I'm not sure where for best exposure. For now, I've added a note to the main API landing page "Concepts and usage" section, which links to it and explains what the issue is:

Note: The different exceptions that can be thrown when using the features of this API are listed on relevant pages as defined in the spec. However, the situation is made more complex by the interaction of the API and the underlying operating system. A proposal has been made to list the error mappings in the spec, which includes useful related information.


- `InvalidStateError` {{domxref("DOMException")}}
- : Thrown if the {{domxref('FileSystemSyncAccessHandle')}} object does not represent an file in the [origin private file system](https://fs.spec.whatwg.org/#origin-private-file-system).
- `NoModificationAllowedError` {{domxref("DOMException")}}
- : Thrown if the browser is not able to take an exclusive lock on the file associated with the file handle.
chrisdavidmills marked this conversation as resolved.
Show resolved Hide resolved
- `NotAllowedError` {{domxref("DOMException")}}
- : Thrown if the {{domxref('PermissionStatus.state')}} is not `granted` in
read mode.

## Examples

The following asynchronous event handler function is contained inside a Web Worker. The snippet inside it creates a synchronous file access handle.

```js
onmessage = async (e) => {
// Retrieve message sent to work from main script
const message = e.data;

// Get handle to draft file
const root = await navigator.storage.getDirectory();
const draftHandle = await root.getFileHandle('draft.txt', { create: true });
// Get sync access handle
const accessHandle = await draftHandle.createSyncAccessHandle();

// …

// Always close FileSystemSyncAccessHandle if done.
await accessHandle.close();
chrisdavidmills marked this conversation as resolved.
Show resolved Hide resolved
}
```

## Specifications

{{Specifications}}

## Browser compatibility

{{Compat}}

## See also

- [File System Access API](/en-US/docs/Web/API/File_System_Access_API)
- [The File System Access API: simplifying access to local files](https://web.dev/file-system-access/)
44 changes: 44 additions & 0 deletions files/en-us/web/api/filesystemfilehandle/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ _Inherits methods from its parent, {{DOMxRef("FileSystemHandle")}}._
- {{domxref('FileSystemFileHandle.getFile', 'getFile()')}}
- : Returns a {{jsxref('Promise')}} which resolves to a {{domxref('File')}} object
representing the state on disk of the entry represented by the handle.
- {{domxref('FileSystemFileHandle.createSyncAccessHandle', 'createSyncAccessHandle()')}}
- : Returns a {{jsxref('Promise')}} which resolves to a {{domxref('FileSystemSyncAccessHandle')}} object
that can be used to synchronously read from and write to a file. The synchronous nature of this method brings performance advantages,
but it is only usable inside dedicated [Web Workers](/en-US/docs/Web/API/Web_Workers_API).
- {{domxref('FileSystemFileHandle.createWritable', 'createWritable()')}}
- : Returns a {{jsxref('Promise')}} which resolves to a newly created {{domxref('FileSystemWritableFileStream')}}
object that can be used to write to a file.
Expand Down Expand Up @@ -81,6 +85,46 @@ async function writeFile(fileHandle, contents) {
}
```

### Synchronously reading and writing a file

The following asynchronous event handler function is contained inside a Web Worker. On receiving a message from the main thread it:

- Creates a synchronous file access handle.
- Gets the size of the file and creates an {{jsxref("ArrayBuffer")}} to contain it.
- Reads the file contents into the buffer.
- Encodes the message and writes it to the end of the file.
- Persists the changes to disk and closes the access handle.

```js
onmessage = async (e) => {
// Retrieve message sent to work from main script
const message = e.data;

// Get handle to draft file
const root = await navigator.storage.getDirectory();
const draftHandle = await root.getFileHandle('draft.txt', { create: true });
// Get sync access handle
const accessHandle = await draftHandle.createSyncAccessHandle();

// Get size of the file.
const fileSize = await accessHandle.getSize();
// Read file content to a buffer.
const buffer = new DataView(new ArrayBuffer(fileSize));
const readBuffer = accessHandle.read(buffer, { at: 0 });

// Write the message to the end of the file.
const encoder = new TextEncoder();
const encodedMessage = encoder.encode(message);
const writeBuffer = accessHandle.write(encodedMessage, { at: readBuffer });

// Persist changes to disk.
await accessHandle.flush();

// Always close FileSystemSyncAccessHandle if done.
await accessHandle.close();
}
```

## Specifications

{{Specifications}}
Expand Down
97 changes: 97 additions & 0 deletions files/en-us/web/api/filesystemsyncaccesshandle/close/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
title: FileSystemSyncAccessHandle.close()
slug: Web/API/FileSystemSyncAccessHandle/close
page-type: web-api-instance-method
tags:
- close
- Directory
- File
- File System Access API
- FileSystemSyncAccessHandle
- Method
- stream
- working with files
browser-compat: api.FileSystemSyncAccessHandle.close
---

{{securecontext_header}}{{DefaultAPISidebar("File System Access API")}}

The **`close()`** method of the
{{domxref("FileSystemSyncAccessHandle")}} interface closes an open synchronous file handle, disabling any further operations on it and releasing the exclusive lock previously put on the file associated with the file handle.

## Syntax

```js-nolint
close()
```

### Parameters

None.

### Return value

A {{jsxref('Promise')}} which resolves to undefined.
chrisdavidmills marked this conversation as resolved.
Show resolved Hide resolved

### Exceptions

None.

## Examples

The following asynchronous event handler function is contained inside a Web Worker. On receiving a message from the main thread it:

- Creates a synchronous file access handle.
- Gets the size of the file and creates an {{jsxref("ArrayBuffer")}} to contain it.
- Reads the file contents into the buffer.
- Encodes the message and writes it to the end of the file.
- Persists the changes to disk and closes the access handle.

```js
onmessage = async (e) => {
// Retrieve message sent to work from main script
const message = e.data;

// Get handle to draft file
const root = await navigator.storage.getDirectory();
const draftHandle = await root.getFileHandle('draft.txt', { create: true });
// Get sync access handle
const accessHandle = await draftHandle.createSyncAccessHandle();

// Get size of the file.
const fileSize = await accessHandle.getSize();
// Read file content to a buffer.
const buffer = new DataView(new ArrayBuffer(fileSize));
const readBuffer = accessHandle.read(buffer, { at: 0 });

// Write the message to the end of the file.
const encoder = new TextEncoder();
const encodedMessage = encoder.encode(message);
const writeBuffer = accessHandle.write(encodedMessage, { at: readBuffer });

// Persist changes to disk.
await accessHandle.flush();

// Always close FileSystemSyncAccessHandle if done.
await accessHandle.close();
}
```

Note that some browsers feature an experimental synchronous version of `close()` that provides further improved performance:
chrisdavidmills marked this conversation as resolved.
Show resolved Hide resolved

```js
accessHandle.close();
```

## Specifications

{{Specifications}}

## Browser compatibility

{{Compat}}

## See also

- [File System Access API](/en-US/docs/Web/API/File_System_Access_API)
- [The File System Access API: simplifying access to local files](https://web.dev/file-system-access/)
Loading