Before a Sequential Access File Can Be Written to or Read From, It Must Be Created and __________.

The File System Admission API: simplifying access to local files

The File System Access API allows web apps to read or save changes directly to files and folders on the user's device.

— Updated

Thomas Steiner

What is the File System Access API? #

The File Arrangement Access API (formerly known equally Native File System API and prior to that it was called Writeable Files API) enables developers to build powerful spider web apps that interact with files on the user's local device, like IDEs, photo and video editors, text editors, and more. After a user grants a web app access, this API allows them to read or salvage changes direct to files and folders on the user'southward device. Beyond reading and writing files, the File Organisation Access API provides the ability to open up a directory and enumerate its contents.

If you've worked with reading and writing files before, much of what I'g about to share will be familiar to you. I encourage y'all to read it anyway, because not all systems are alike.

Electric current status #

Browser support #

Browser support: chrome 86, Supported 86 firefox, Not supported × edge 86, Supported 86 safari, Not supported × Source

The File System Access API is currently supported on most Chromium browsers on Windows, macOS, Chrome Bone, and Linux. A notable exception is Brave (brave/brave-browser#11407). Android support is planned; you can runway progress by starring crbug.com/1011535.

Using the File System Access API #

To testify off the power and usefulness of the File System Access API, I wrote a single file text editor. It lets you open a text file, edit information technology, save the changes back to disk, or offset a new file and relieve the changes to disk. It's nothing fancy, only provides plenty to help yous understand the concepts.

Effort it #

Meet the File System Access API in action in the text editor demo.

Read a file from the local file system #

The first use example I wanted to tackle was to enquire the user to choose a file, and so open and read that file from deejay.

Enquire the user to pick a file to read #

The entry point to the File Organization Admission API is window.showOpenFilePicker(). When called, it shows a file picker dialog box, and prompts the user to select a file. After they select a file, the API returns an array of file handles. An optional options parameter lets you lot influence the beliefs of the file picker, for instance, by allowing the user to select multiple files, or directories, or different file types. Without any options specified, the file picker allows the user to select a single file. This is perfect for a text editor.

Similar many other powerful APIs, calling showOpenFilePicker() must be done in a secure context, and must exist called from within a user gesture.

                                          permit                fileHandle;                            
butOpenFile. addEventListener ( 'click' , async ( ) => {
// Destructure the one-chemical element array.
[fileHandle] = expect window. showOpenFilePicker ( ) ;
// Do something with the file handle.
} ) ;

Once the user selects a file, showOpenFilePicker() returns an array of handles, in this case a i-chemical element array with one FileSystemFileHandle that contains the properties and methods needed to interact with the file.

Information technology'due south helpful to go along a reference to the file handle around and so that it can be used later on. Information technology'll be needed to save changes dorsum to the file, or to perform whatever other file operations.

Read a file from the file arrangement #

Now that you have a handle to a file, you tin get the file'southward backdrop, or access the file itself. For at present, I'll simply read its contents. Calling handle.getFile() returns a File object, which contains a blob. To get the data from the blob, call one of its methods, (slice(), stream(), text(), or arrayBuffer()).

                          const              file              =              expect              fileHandle.              getFile              (              )              ;              
const contents = wait file. text ( ) ;

The File object returned past FileSystemFileHandle.getFile() is only readable as long every bit the underlying file on deejay hasn't changed. If the file on deejay is modified, the File object becomes unreadable and you'll demand to phone call getFile() over again to get a new File object to read the inverse data.

Putting it all together #

When users click the Open button, the browser shows a file picker. Once they've selected a file, the app reads the contents and puts them into a <textarea>.

                                          allow                fileHandle;                            
butOpenFile. addEventListener ( 'click' , async ( ) => {
[fileHandle] = expect window. showOpenFilePicker ( ) ;
const file = await fileHandle. getFile ( ) ;
const contents = wait file. text ( ) ;
textArea.value = contents;
} ) ;

Write the file to the local file organisation #

In the text editor, there are 2 means to save a file: Salvage, and Salve Every bit. Save but writes the changes back to the original file using the file handle retrieved earlier. Simply Save As creates a new file, and thus requires a new file handle.

Create a new file #

To save a file, phone call showSaveFilePicker(), which will show the file picker in "save" mode, allowing the user to pick a new file they want to employ for saving. For the text editor, I also wanted it to automatically add a .txt extension, so I provided some additional parameters.

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

Save changes to disk #

You tin observe all the code for saving changes to a file in my text editor demo on GitHub. The core file system interactions are in fs-helpers.js. At its simplest, the process looks like the code below. I'll walk through each step and explain it.

                          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) ;
// Shut the file and write the contents to disk.
wait writable. close ( ) ;
}

Writing data to disk uses a FileSystemWritableFileStream object, essentially a WritableStream. Create the stream by calling createWritable() on the file handle object. When createWritable() is called, the browser commencement checks if the user has granted write permission to the file. If permission to write hasn't been granted, the browser will prompt the user for permission. If permission isn't granted, createWritable() will throw a DOMException, and the app will not exist able to write to the file. In the text editor, these DOMExceptions are handled in the saveFile() method.

The write() method takes a string, which is what'due south needed for a text editor. But it can also accept a BufferSource, or a Blob. For example, you can pipage a stream direct to it:

                                          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 = wait fetch (url) ;
// Stream the response into the file.
wait response.body. pipeTo (writable) ;
// pipeTo() closes the destination pipage by default, no demand to close information technology.
}

You can too seek(), or truncate() within the stream to update the file at a specific position, or resize the file.

Specifying a suggested file name and start directory #

In many cases y'all may want your app to advise a default file name or location. For instance, a text editor might want to suggest a default file name of Untitled Text.txt rather than Untitled. You lot can achieve this by passing a suggestedName belongings every bit role of the showSaveFilePicker options.

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

The same goes for the default start directory. If you're building a text editor, you may want to start the file save or file open dialog in the default documents binder, whereas for an image editor, may want to start in the default pictures folder. You tin propose a default get-go directory by passing a startIn property to the showSaveFilePicker, showDirectoryPicker(), or showOpenFilePicker methods like so.

                                          const                fileHandle                =                await                self.                showOpenFilePicker                (                {                            
startIn: 'pictures'
} ) ;

The list of the well-known arrangement directories is:

  • desktop: The user's desktop directory, if such a affair exists.
  • documents: Directory in which documents created by the user would typically be stored.
  • downloads: Directory where downloaded files would typically be stored.
  • music: Directory where sound files would typically be stored.
  • pictures: Directory where photos and other withal images would typically exist stored.
  • videos: Directory where videos/movies would typically be stored.

Apart from well-known system directories, yous tin can also pass an existing file or directory handle as a value for startIn. The dialog would so open up in the same directory.

                                          // Presume `directoryHandle` were a handle to a previously opened directory.                            
const fileHandle = await cocky. showOpenFilePicker ( {
startIn: directoryHandle
} ) ;

Specifying the purpose of different file pickers #

Sometimes applications have different pickers for different purposes. For example, a rich text editor may permit the user to open up text files, but also to import images. Past default, each file picker would open at the last-remembered location. Yous can circumvent this past storing id values for each blazon of picker. If an id is specified, the file picker implementation volition remember a separate last-used directory for pickers with that same id.

                          const              fileHandle1              =              await              cocky.              showSaveFilePicker              (              {              
id: 'openText' ,
} ) ;

const fileHandle2 = await cocky. showSaveFilePicker ( {
id: 'importImage' ,
} ) ;

Storing file handles or directory handles in IndexedDB #

File handles and directory handles are serializable, which ways that you lot can save a file or directory handle to IndexedDB, or call postMessage() to send them between the same top-level origin.

Saving file or directory handles to IndexedDB ways that yous can shop state, or remember which files or directories a user was working on. This makes it possible to keep a list of recently opened or edited files, offer to re-open up the last file when the app is opened, restore the previous working directory, and more. In the text editor, I shop a list of the five most recent files the user has opened, making information technology like shooting fish in a barrel to access those files again.

The lawmaking example below shows storing and retrieving a file handle and a directory handle. You can come across this in action over on Glitch (I use the idb-keyval library for brevity).

                          import              {              become,              set              }              from              'https://unpkg.com/idb-keyval@5.0.ii/dist/esm/index.js'              ;              

const pre1 = certificate. querySelector ( 'pre.file' ) ;
const pre2 = document. querySelector ( 'pre.directory' ) ;
const button1 = document. querySelector ( 'button.file' ) ;
const button2 = document. querySelector ( 'push.directory' ) ;

// File handle
button1. addEventListener ( 'click' , async ( ) => {
try {
const fileHandleOrUndefined = await get ( 'file' ) ;
if (fileHandleOrUndefined) {
pre1.textContent = ` Retrieved file handle " ${fileHandleOrUndefined.proper noun} " from IndexedDB. ` ;
render ;
}
const [fileHandle] = await window. showOpenFilePicker ( ) ;
expect set ( 'file' , fileHandle) ;
pre1.textContent = ` Stored file handle for " ${fileHandle.name} " in IndexedDB. ` ;
} catch (error) {
alert (fault.name, error.message) ;
}
} ) ;

// Directory handle
button2. addEventListener ( 'click' , async ( ) => {
try {
const directoryHandleOrUndefined = wait 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. ` ;
} grab (fault) {
warning (mistake.name, error.bulletin) ;
}
} ) ;

Stored file or directory handles and permissions #

Since permissions currently are not persisted between sessions, you should verify whether the user has granted permission to the file or directory using queryPermission(). If they oasis't, use requestPermission() to (re-)asking it. This works the same for file and directory handles. You need to run fileOrDirectoryHandle.requestPermission(descriptor) or fileOrDirectoryHandle.queryPermission(descriptor) respectively.

In the text editor, I created a verifyPermission() method that checks if the user has already granted permission, and if required, makes the asking.

                                          async                office                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, render true.
if ( ( await fileHandle. requestPermission (options) ) === 'granted' ) {
return true ;
}
// The user didn't grant permission, so render simulated.
return false ;
}

Past requesting write permission with the read request, I reduced the number of permission prompts: the user sees one prompt when opening the file, and grants permission to both read and write to information technology.

Opening a directory and enumerating its contents #

To enumerate all files in a directory, call showDirectoryPicker(). The user selects a directory in a picker, after which a FileSystemDirectoryHandle is returned, which lets you enumerate and admission the directory'south files.

                          const              butDir              =              certificate.              getElementById              (              'butDirectory'              )              ;              
butDir. addEventListener ( 'click' , async ( ) => {
const dirHandle = await window. showDirectoryPicker ( ) ;
for wait ( const entry of dirHandle. values ( ) ) {
console. log (entry.kind, entry.name) ;
}
} ) ;

If you additionally need to access each file via getFile() to, for example, obtain the individual file sizes, do not use wait on each result sequentially, but rather process all files in parallel, for example, via Promise.all().

                          const              butDir              =              certificate.              getElementById              (              'butDirectory'              )              ;              
butDir. addEventListener ( 'click' , async ( ) => {
const dirHandle = await window. showDirectoryPicker ( ) ;
const promises = [ ] ;
for wait ( const entry of dirHandle. values ( ) ) {
if (entry.kind !== 'file' ) {
break ;
}
promises. push (entry. getFile ( ) . so ( ( file ) => ` ${file.name} ( ${file.size} ) ` ) ) ;
}
console. log ( await Hope. all (promises) ) ;
} ) ;

Creating or accessing files and folders in a directory #

From a directory, y'all can create or access files and folders using the getFileHandle() or respectively the getDirectoryHandle() method. By passing in an optional options object with a key of create and a boolean value of true or false, yous tin can determine if a new file or folder should exist created if it doesn't exist.

                          // In an existing directory, create a new directory named "My Documents".              
const newDirectoryHandle = wait existingDirectoryHandle. getDirectoryHandle ( 'My Documents' , {
create: truthful ,
} ) ;
// In this new directory, create a file named "My Notes.txt".
const newFileHandle = await newDirectoryHandle. getFileHandle ( 'My Notes.txt' , { create: truthful } ) ;

Resolving the path of an item in a directory #

When working with files or folders in a directory, it can exist useful to resolve the path of the item in question. This can exist washed with the aptly named resolve() method. For resolving, the particular can be a direct or indirect kid of the directory.

                          // 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"]

Deleting files and folders in a directory #

If you take obtained admission to a directory, you can delete the independent files and folders with the removeEntry() method. For folders, deletion tin can optionally exist recursive and include all subfolders and the therein independent files.

                          // Delete a file.              
await directoryHandle. removeEntry ( 'Abased Projects.txt' ) ;
// Recursively delete a folder.
await directoryHandle. removeEntry ( 'Old Stuff' , { recursive: true } ) ;

Deleting a file or folder directly #

If you have access to a file or directory handle, call remove() on a FileSystemFileHandle or FileSystemDirectoryHandle to remove it.

                          // Delete a file.              
expect fileHandle. remove ( ) ;
// Delete a directory.
wait directoryHandle. remove ( ) ;

Renaming and moving files and folders #

Files and folders can be renamed or moved to a new location by calling move() on the FileSystemHandle interface. FileSystemHandle has the child interfaces FileSystemFileHandle and FileSystemDirectoryHandle. The move() method takes i or ii parameters. The commencement can either be a string with the new proper name or a FileSystemDirectoryHandle to the destination folder. In the latter case, the optional 2d parameter is a string with the new proper name, so moving and renaming tin happen in ane stride.

                          // Rename the file.              
expect file. move ( 'new_name' ) ;
// Move the file to a new directory.
await file. motion (directory) ;
// Move the file to a new directory and rename information technology.
await file. motion (directory, 'newer_name' ) ;

Drag and drop integration #

The HTML Drag and Driblet interfaces enable web applications to accept dragged and dropped files on a web folio. During a drag and drop operation, dragged file and directory items are associated with file entries and directory entries respectively. The DataTransferItem.getAsFileSystemHandle() method returns a hope with a FileSystemFileHandle object if the dragged item is a file, and a promise with a FileSystemDirectoryHandle object if the dragged item is a directory. The listing below shows this in action. Notation that the Elevate and Drib interface's DataTransferItem.kind volition be "file" for both files and directories, whereas the File System Access API'south FileSystemHandle.kind will be "file" for files and "directory" for directories.

            elem.              addEventListener              (              'dragover'              ,              (              east              )              =>              {              
// Preclude navigation.
due east. preventDefault ( ) ;
} ) ;

elem. addEventListener ( 'drop' , async ( eastward ) => {
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} ` ) ;
}
}
} ) ;

Accessing the origin private file arrangement #

The origin private file organisation is a storage endpoint that, every bit the name suggests, is individual to the origin of the page. While browsers volition typically implement this by persisting the contents of this origin private file system to disk somewhere, it is not intended that the contents be easily user accessible. Similarly, there is no expectation that files or directories with names matching the names of children of the origin private file system exist. While the browser might make it seem that there are files, internally—since this is an origin private file system—the browser might store these "files" in a database or any other information construction. Essentially: what you create with this API, practice not expect to notice it 1:1 somewhere on the hard disk. You tin operate as usual on the origin private file arrangement in one case you have admission to the root FileSystemDirectoryHandle.

                          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 } ) ;

Accessing files optimized for performance from the origin private file organisation #

The origin private file system provides optional access to a special kind of file that is highly optimized for performance, for example, by offering in-place and exclusive write access to a file's content. In that location is an origin trial starting in Chromium 95 and ending in Chromium 98 (February 23, 2022) for simplifying how such files can exist accessed by exposing ii new methods as part of the origin private file system: createAccessHandle() (asynchronous read and write operations) and createSyncAccessHandle() (synchronous read and write operations) that are both exposed on FileSystemFileHandle.

                          // Asynchronous access in all contexts:              
const handle = look file. createAccessHandle ( { style: 'in-place' } ) ;
await handle.writable. getWriter ( ) . write (buffer) ;
const reader = handle.readable. getReader ( { mode: 'byob' } ) ;
// Assumes seekable streams, and SharedArrayBuffer support are available
look reader. read (buffer, { at: one } ) ;
                          // (Read and write operations are synchronous,              
// but obtaining the handle is asynchronous.)
// Synchronous admission exclusively in Worker contexts
const handle = await file. createSyncAccessHandle ( ) ;
const writtenBytes = handle. write (buffer) ;
const readBytes = handle. read (buffer, { at: 1 } ) ;

Polyfilling #

It is not possible to completely polyfill the File System Access API methods.

  • The showOpenFilePicker() method can be approximated with an <input type="file"> element.
  • The showSaveFilePicker() method can exist simulated with a <a download="file_name"> element, albeit this volition trigger a programmatic download and non permit for overwriting existing files.
  • The showDirectoryPicker() method tin be somewhat emulated with the not-standard <input type="file" webkitdirectory> chemical element.

We accept developed a library called browser-fs-access that uses the File Organization Admission API wherever possible and that falls back to these next best options in all other cases.

Security and permissions #

The Chrome squad has designed and implemented the File Organization Access API using the core principles defined in Decision-making Admission to Powerful Web Platform Features, including user control and transparency, and user ergonomics.

Opening a file or saving a new file #

File picker to open a file for reading
A file picker used to open an existing file for reading.

When opening a file, the user provides permission to read a file or directory via the file picker. The open up file picker tin merely exist shown via a user gesture when served from a secure context. If users alter their minds, they tin cancel the selection in the file picker and the site does not get admission to anything. This is the same behavior as that of the <input blazon="file"> chemical element.

File picker to save a file to disk.
A file picker used to save a file to disk.

Similarly, when a web app wants to save a new file, the browser volition bear witness the salvage file picker, allowing the user to specify the name and location of the new file. Since they are saving a new file to the device (versus overwriting an existing file), the file picker grants the app permission to write to the file.

Restricted folders #

To help protect users and their data, the browser may limit the user'south ability to relieve to sure folders, for example, core operating organisation folders like Windows, the macOS Library folders, etc. When this happens, the browser will show a modal prompt and ask the user to choose a different folder.

Modifying an existing file or directory #

A web app cannot modify a file on disk without getting explicit permission from the user.

Permission prompt #

Permission prompt shown prior to saving a file.
Prompt shown to users before the browser is granted write permission on an existing file.

If a person wants to save changes to a file that they previously granted read access to, the browser will show a modal permission prompt, requesting permission for the site to write changes to disk. The permission request tin only be triggered past a user gesture, for example, by clicking a Relieve button.

Alternatively, a web app that edits multiple files, like an IDE, tin can also enquire for permission to save changes at the time of opening.

If the user chooses Cancel, and does not grant write access, the web app cannot salvage changes to the local file. It should provide an alternative method to allow the user to salvage their data, for example by providing a way to "download" the file, saving data to the deject, etc.

Transparency #

Omnibox icon
Omnibox icon indicating the user has granted the website permission to save to a local file.

Once a user has granted permission to a web app to relieve a local file, the browser will show an icon in the URL bar. Clicking on the icon opens a pop-over showing the listing of files the user has given admission to. The user tin can hands revoke that admission if they choose.

Permission persistence #

The web app tin continue to salvage changes to the file without prompting until all tabs for that origin are closed. Once a tab is closed, the site loses all access. The next fourth dimension the user uses the web app, they will be re-prompted for access to the files.

Feedback #

We want to hear about your experiences with the File System Access API.

Tell us about the API design #

Is there something about the API that doesn't work like you lot expected? Or are there missing methods or properties that you need to implement your idea? Take a question or comment on the security model?

  • File a spec issue on the WICG File System Access GitHub repo, or add together your thoughts to an existing event.

Trouble with the implementation? #

Did you discover a bug with Chrome'due south implementation? Or is the implementation unlike from the spec?

  • File a bug at https://new.crbug.com. Be certain to include as much particular equally y'all can, simple instructions for reproducing, and set up Components to Blink>Storage>FileSystem. Glitch works great for sharing quick and easy repros.

Planning to use the API? #

Planning to employ the File System Access API on your site? Your public support helps us to prioritize features, and shows other browser vendors how disquisitional it is to support them.

  • Share how you plan to apply it on the WICG Soapbox thread.
  • Send a tweet to @ChromiumDev using the hashtag #FileSystemAccess and permit us know where and how you're using information technology.

Helpful links #

  • Public explainer
  • File System Access specification & File specification
  • Tracking bug
  • ChromeStatus.com entry
  • Request an origin trial token
  • TypeScript definitions
  • File Arrangement Admission API - Chromium Security Model
  • Glimmer Component: Blink>Storage>FileSystem

Acknowledgements #

The File Organization Access API spec was written by Marijn Kruisselbrink.

Concluding updated: — Ameliorate commodity

Return to all manufactures

thomsurprood.blogspot.com

Source: https://web.dev/file-system-access/

0 Response to "Before a Sequential Access File Can Be Written to or Read From, It Must Be Created and __________."

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel