It is very common for an application to interact with local files. For example, a general workflow is opening a file, making some changes, and saving the file. For web apps, this might be hard to implement. It is possible to simulate the file operations using IndexedDB API, an HTML input element with the file
type, an HTML anchor element with the download
attribute, etc, but that would require a good understanding of these standards and careful design for a good user experience. Also, the performance may not be satisfactory for frequent operations and large files.
The File System Access API makes it possible for web apps to have easy and efficient file access. It provides a way to create, open, read, and write files directly. It also allows apps to create directories and enumerate their contents.
Origin Private File System
WebKit has added support for the File System Access API with the origin private file system — a private storage endpoint to some origin. Conceptually, every origin owns an independent directory, and a page can only access files or directories in its origin’s directory. For example, https://webkit.org cannot read files created by https://apple.com.
Based on the implementation of different browsers, one entry in the origin private file system does not necessarily map to an entry in the user’s local filesystem — it can be an object stored in some database. That means a file or directory created via the File System Access API may not be easily retrieved from outside of the browser.
Persistence
The API is currently unavailable for Safari windows in Private Browsing mode. For where is it available, its storage lifetime is the same as other persistent storage types like IndexedDB and LocalStorage. The storage policy will conform to the Storage Standard. Safari users can view and delete file system storage for a site via Preferences on macOS or Settings on iOS.
Browser Support
The File System Access API with origin private file system is enabled in WebKit from r284131. It is available in Safari on:
- macOS 12.2 and above
- iOS 15.2 and above
In Safari on macOS 12.4 and iOS 15.4, we introduced the getFile()
method of FileSystemFileHandle
.
The API
WebKit currently supports four interfaces of the File System Access API:
FileSystemHandle
, which represents an entry in the file system. It is available in Worker andFileSystemFileHandle
, which inherits from FileSystemHandle and represents a file entry.FileSystemDirectoryHandle
, which inherits from FileSystemHandle and represents a directory entry.FileSystemSyncAccessHandle
, which provides an exclusive duplex stream for synchronous read and write on an entry. Unlike the interfaces above, which exist in both Window and Worker contexts,FileSystemSyncAccessHandle
is only available in Worker.
With these basic interfaces in mind, let’s look at how to use them by diving into some examples.
Examples
Accessing the Origin Private File System
In the origin private file system, a FileSystemHandle
represents either the root directory of the origin’s space, or a descendant of the root directory. Therefore, the first step is to get the root FileSystemDirectoryHandle
. It is done via StorageManager
interface.
const root = await navigator.storage.getDirectory();
Creating a directory or a file
With a FileSystemDirectoryHandle
object like root, you can get access to its child with some specific name using getDirectoryHandle()
and getFileHandle()
methods.
const untitledFile = await root.getFileHandle("Untitled.txt", { "create" : true });
const existingUntitledFile = await root.getFileHandle("Untitled.txt");
const diaryDirectory = await root.getDirectoryHandle("Diary Folder", { "create" : true });
Moving or Renaming a Directory or a File
To move around the file or directory a FileSystemHandle
represents, you can use the move()
method. The first parameter is a FileSystemDirectoryHandle
representing the target parent directory, and the second parameter is a USVString
representing the target file name. The string must be a valid file name.
await untitledFile.move(diaryDirectory, untitledFile.name);
await untitledFile.move(diaryDirectory, "Feb_01.txt");
Resolving the Path from a Directory Entry to its Descendant
To find out if a FileSystemHandle
is a descendant of an existing FileSystemDirectoryHandle
, and to get their relative path, you can use the resolve()
method. The result is an array of component names that forms the path.
const diaryFile = await diaryDirectory.getFileHandle("Feb_01.txt");
const relativePath = await root.resolve(diaryFile);
Enumerating Contents in a Directory
The methods introduced above require you to know the name of target, but if you don’t know the name, you can still get it by enumerating the contents of an existing directory with async
iterators returned by the keys()
, values()
, and entries()
methods.
const trashDirectory = await root.getDirectoryHandle("Trash", { "create" : true });
const directoryNames = [];
for await (const handle of root.values()) {
if (handle.kind == "directory") {
directoryNames.push(handle.name);
}
}
Deleting a Directory or a File
With a FileSystemDirectoryHandle
object, you can delete its child entries by name with the removeEntry()
method.
await diaryDirectory.removeEntry(diaryFile.name);
await root.removeEntry(trashDirectory.name, { "recursive" : true });
Reading a File
Once you have the FileSystemFileHandle
representing the target file, you can read its properties and content by converting it to a File
object using the getFile()
method. You can get file information and content using interfaces of File
.
const fileHandle = await root.getFileHandle("Draft.txt", { "create" : true });
const file = await fileHandle.getFile();
Reading and Writing a File in a Worker Thread
Another way to read a file is to use the read()
method of the FileSystemSyncAccessHandle
interface. You can create a FileSystemSyncAccessHandle
from a FileSystemFileHandle
object using the createSyncAccessHandle()
method. Since FileSystemSyncAccessHandle
is only available in Worker contexts, you will need to create a dedicated Worker first.
Unlike getFile()
that returns a Promise, read()
is synchronous, and thus provides better performance. If you’re aiming for the most efficient file access, FileSystemSyncAccessHandle
is the way to go.
To write a file, you can use the synchronous write()
method of FileSystemSyncAccessHandle
. In the current implementation, this is the only way to write a file in WebKit.
To implement synchronous read and write operations, a FileSystemSyncAccessHandle
must have exclusive access to a file entry. Therefore, the attempt to create a second FileSystemSyncAccessHandle
on an entry will fail, if the previous FileSystemSyncAccessHandle
is not closed properly.
const root = await navigator.storage.getDirectory();
const draftFile = await root.getFileHandle("Draft.txt");
const accessHandle = await draftFile.createSyncAccessHandle();
const fileSize = await accessHandle.getSize();
const readBuffer = new ArrayBuffer(fileSize);
const readSize = accessHandle.read(readBuffer, { "at": 0 });
const encoder = new TextEncoder();
const writeBuffer = encoder.encode("Thank you for reading this.");
const writeSize = accessHandle.write(writeBuffer, { "at" : readSize });
await accessHandle.truncate(1);
await accessHandle.flush();
await accessHandle.close();
Summary
If your web app needs to interact with files, you should try the new File System Access API. It provides interfaces that are similar to the native file system API, with optimized performance.
As the standard evolves and development goes on, we will keep adding or updating interfaces and methods according to the File System Access API spec. If you encounter any issue when using this API, please file a bug on bugs.webkit.org under the “Website Storage” component. You may also create a new bug report for feature requests, describing your use case and why the feature is important. If you have any question or suggestion about the API itself, you can file a spec issue in the WICG repo. Your feedback is very important to us.
from Hacker News https://ift.tt/5v6jU2A
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.