1. Introduction
This section is non-normative.
This
document
defines
fundamental
infrastructure
for
file
system
APIs.
In
addition,
it
defines
an
API
that
makes
it
possible
for
websites
to
get
access
to
a
file
system
directory
without
having
to
first
prompt
the
user
for
access.
This
enables
use
cases
where
a
website
wants
to
save
data
to
disk
before
a
user
has
picked
a
location
to
save
to,
without
forcing
the
website
to
use
a
completely
different
storage
mechanism
with
a
different
API
for
such
files.
The
entry
point
for
this
is
the
navigator.storage.getDirectory()
method.
2. Files and Directories
2.1. Concepts
An entry is either a file entry or a directory entry .
Each
entry
has
an
associated
query
access
algorithm,
which
takes
"
read
"
or
"
readwrite
"
mode
and
returns
a
PermissionState
.
Unless
specified
otherwise
it
returns
"
denied
".
The
algorithm
is
allowed
to
throw.
Each
entry
has
an
associated
request
access
algorithm,
which
takes
"
read
"
or
"
readwrite
"
mode
and
returns
a
PermissionState
.
Unless
specified
otherwise
it
returns
"
denied
".
The
algorithm
is
allowed
to
throw.
Note: Implementations that only implement this specification and not dependent specifications do not need to bother implementing entry 's query access and request access .
Each entry has an associated name (a string ).
A valid file name is a string that is not an empty string, is not equal to "." or "..", and does not contain '/' or any other character used as path separator on the underlying platform.
Note: This means that '\' is not allowed in names on Windows, but might be allowed on other operating systems. Additionally underlying file systems might have further restrictions on what names are or aren’t allowed, so a string merely being a valid file name is not a guarantee that creating a file or directory with that name will succeed.
We should consider having further normative restrictions on file names that will never be allowed using this API, rather than leaving it entirely up to underlying file systems.
A
file
entry
additionally
consists
of
binary
data
(a
byte
sequence
),
a
modification
timestamp
(a
number
representing
the
number
of
milliseconds
since
the
Unix
Epoch
),
a
lock
(a
string
that
may
exclusively
be
"
open
",
"
taken-exclusive
"
or
"
taken-shared
")
and
a
shared
lock
count
(a
number
representing
the
number
shared
locks
that
are
taken
at
a
given
point
in
time).
exclusive
"
or
"
shared
"
on
a
given
file
entry
file
:
-
Let lock be the file ’s lock .
-
Let count be the file ’s shared lock count .
-
If value is "
exclusive
":-
If lock is "
open
":-
Set lock to "
taken-exclusive
". -
Return true.
-
-
-
If value is "
shared
":-
If lock is "
open
":-
Set lock to "
taken-shared
". -
Set count to 1.
-
Return true.
-
-
Otherwise, if lock is "
taken-shared
":-
Increase count by one.
-
Return true.
-
-
-
Return false.
-
Let lock be the file ’s associated lock .
-
Let count be the file ’s shared lock count .
-
If lock is "
taken-shared
":-
Decrease count by one.
-
If count is 0, set lock to "
open
".
-
-
Otherwise, set lock to "
open
".
Note:
Locks
help
prevent
concurrent
modifications
to
a
file.
A
FileSystemWritableFileStream
requires
a
shared
lock,
while
a
FileSystemSyncAccessHandle
requires
an
exclusive
one.
A directory entry additionally consists of a set of children , which are themselves entries . Each member is either a file or a directory .
An entry entry should be contained in the children of at most one directory entry , and that directory entry is also known as entry ’s parent . An entry 's parent is null if no such directory entry exists.
Note: Two different entries can represent the same file or directory on disk, in which case it is possible for both entries to have a different parent, or for one entry to have a parent while the other entry does not have a parent.
Entries can (but don’t have to) be backed by files on the host operating system’s local file system, so it is possible for the binary data , modification timestamp , and children of entries to be modified by applications outside of this specification. Exactly how external changes are reflected in the data structures defined by this specification, as well as how changes made to the data structures defined here are reflected externally is left up to individual user-agent implementations.
An entry a is the same as an entry b if a is equal to b , or if a and b are backed by the same file or directory on the local file system.
TODO: Explain better how entries map to files on disk (multiple entries can map to the same file or directory on disk but an entry doesn’t have to map to any file on disk).
-
Let result be a new promise .
-
Run these steps in parallel :
-
If child is the same as root , resolve result with an empty list, and abort.
-
Let childPromises be « ».
-
Wait for all childPromises , with the these success steps:
-
If result hasn’t been resolved yet, resolve result with
null
.
-
-
-
Return result .
2.2.
The
FileSystemHandle
interface
enum {
FileSystemHandleKind ,
"file" , }; [
"directory" Exposed =(Window ,Worker ),SecureContext ,Serializable ]interface {
FileSystemHandle readonly attribute FileSystemHandleKind kind ;readonly attribute USVString name ;Promise <boolean >isSameEntry (FileSystemHandle ); };
other
A
FileSystemHandle
object
represents
an
entry
.
Each
FileSystemHandle
object
is
associated
with
an
entry
(an
entry
).
Multiple
separate
objects
implementing
the
FileSystemHandle
interface
can
all
be
associated
with
the
same
entry
simultaneously.
FileSystemHandle
objects
are
serializable
objects
.
Their serialization steps , given value , serialized and forStorage are:
-
Set serialized .[[Origin]] to value ’s relevant settings object 's origin .
-
Set serialized .[[Entry]] to value ’s entry .
-
If serialized .[[Origin]] is not same origin with value ’s relevant settings object 's origin , then throw a
DataCloneError
. -
Set value ’s entry to serialized .[[Entry]]
-
handle
.
kind
-
Returns
"file"
if handle is aFileSystemFileHandle
, or"directory"
if handle is aFileSystemDirectoryHandle
.This can be used to distinguish files from directories when iterating over the contents of a directory.
-
handle
.
name
-
Returns the name of the entry represented by handle .
The
kind
getter
steps
are
to
return
"file"
if
this
is
a
file
entry
;
otherwise
"directory"
.
The
name
getter
steps
are
to
return
this
's
name
.
2.2.1.
The
isSameEntry()
method
-
same
=
await
handle1
.
isSameEntry
( handle2 ) -
Returns true if handle1 and handle2 represent the same file or directory.
isSameEntry(
other
)
method
steps
are:
-
Let realm be this 's relevant Realm .
-
Let p be a new promise in realm .
-
Run these steps in parallel :
-
Return p .
2.3.
The
FileSystemFileHandle
interface
dictionary {
FileSystemCreateWritableOptions boolean =
keepExistingData false ; }; [Exposed =(Window ,Worker ),SecureContext ,Serializable ]interface :
FileSystemFileHandle FileSystemHandle {Promise <File >getFile ();Promise <FileSystemWritableFileStream >createWritable (optional FileSystemCreateWritableOptions = {}); [
options Exposed =DedicatedWorker ]Promise <FileSystemSyncAccessHandle >createSyncAccessHandle (); };
A
FileSystemFileHandle
's
associated
entry
must
be
a
file
entry
.
FileSystemFileHandle
objects
are
serializable
objects
.
Their
serialization
steps
and
deserialization
steps
are
the
same
as
those
for
FileSystemHandle
.
2.3.1.
The
getFile()
method
getFile()
method
steps
are:
-
Let result be a new promise .
-
Run these steps in parallel :
-
Let access be the result of running this 's entry 's query access given "
read
". -
If access is not "
granted
", reject result with aNotAllowedError
and abort. -
Let f be a new
File
. -
Set f ’s snapshot state to the current state of entry .
-
Set f ’s underlying byte sequence to a copy of entry ’s binary data .
-
Set f .
lastModified
to entry ’s modification timestamp . -
Set f .
type
to an implementation-defined value, based on for example entry ’s name or its file extension.The reading and snapshotting behavior needs to be better specified in the [FILE-API] spec, for now this is kind of hand-wavy.
-
Resolve result with f .
-
-
Return result .
2.3.2.
The
createWritable()
method
-
stream
=
await
fileHandle
.
createWritable()
- stream = await fileHandle .
createWritable
({keepExistingData
: true/false }) - stream = await fileHandle .
-
Returns a
FileSystemWritableFileStream
that can be used to write to the file. Any changes made through stream won’t be reflected in the file represented by fileHandle until the stream has been closed. User agents try to ensure that no partial writes happen, i.e. the file represented by fileHandle will either contain its old contents or it will contain whatever data was written through stream up until the stream has been closed.This is typically implemented by writing data to a temporary file, and only replacing the file represented by fileHandle with the temporary file when the writable filestream is closed.
If
keepExistingData
is false or not specified, the temporary file starts out empty, otherwise the existing file is first copied to this temporary file.Creating a
FileSystemWritableFileStream
takes a shared lock on the entry associated with fileHandle . This prevents the creation ofFileSystemSyncAccessHandles
for the entry, until the stream is closed.
There has been some discussion around and desire for a "inPlace" mode for createWritable (where changes will be written to the actual underlying file as they are written to the writer, for example to support in-place modification of large files or things like databases). This is not currently implemented in Chrome. Implementing this is currently blocked on figuring out how to combine the desire to run malware checks with the desire to let websites make fast in-place modifications to existing large files.
createWritable(
options
)
method
steps
are:
-
Let result be a new promise .
-
Run these steps in parallel :
-
Let access be the result of running this 's entry 's request access given "
readwrite
". If that throws an exception, reject result with that exception and abort. -
If access is not "
granted
", reject result with aNotAllowedError
and abort. -
Let lockResult be the result of taking a lock with "
shared
" on entry . -
If lockResult is false, reject result with a
NoModificationAllowedError
and abort. -
Let stream be the result of creating a new FileSystemWritableFileStream for entry in this 's relevant realm .
-
If options .
keepExistingData
is true:-
Set stream . [[buffer]] to a copy of entry ’s binary data .
-
-
Resolve result with stream .
-
-
Return result .
2.3.3.
The
createSyncAccessHandle()
method
-
handle
=
await
fileHandle
.
createSyncAccessHandle
() -
Returns a
FileSystemSyncAccessHandle
that can be used to read from/write to the file. Changes made through handle might be immediately reflected in the file represented by fileHandle . To ensure the changes are reflected in this file, the handle can be flushed or closed.Creating a
FileSystemSyncAccessHandle
takes an exclusive lock on the entry associated with fileHandle . This prevents the creation of furtherFileSystemSyncAccessHandle
s orFileSystemWritableFileStream
s for the entry, until the access handle is closed.The returned
FileSystemSyncAccessHandle
offers synchronous methods. This allows for higher performance on contexts where asynchronous operations come with high overhead, e.g., WebAssembly.For the time being, this method will only succeed when the fileHandle belongs to the origin private file system .
createSyncAccessHandle()
method
steps
are:
-
Let result be a new promise .
-
Run these steps in parallel :
-
Let access be the result of running this 's entry 's request access given "
readwrite
". If that throws an exception, reject result with that exception and abort. -
If access is not "
granted
", reject result with aNotAllowedError
and abort. -
If entry does not represent an entry in an origin private file system , reject result with an
InvalidStateError
and abort. -
Let lockResult be the result of taking a lock with "
exclusive
" on entry . -
If lockResult is false, reject result with a
NoModificationAllowedError
and abort. -
Let handle be the result of creating a new FileSystemSyncAccessHandle for entry in this 's relevant realm .
-
Resolve result with handle .
-
-
Return result .
2.4.
The
FileSystemDirectoryHandle
interface
dictionary {
FileSystemGetFileOptions boolean =
create false ; };dictionary {
FileSystemGetDirectoryOptions boolean =
create false ; };dictionary {
FileSystemRemoveOptions boolean =
recursive false ; }; [Exposed =(Window ,Worker ),SecureContext ,Serializable ]interface :
FileSystemDirectoryHandle FileSystemHandle {async iterable <USVString ,FileSystemHandle >;Promise <FileSystemFileHandle >getFileHandle (USVString ,
name optional FileSystemGetFileOptions = {});
options Promise <FileSystemDirectoryHandle >getDirectoryHandle (USVString ,
name optional FileSystemGetDirectoryOptions = {});
options Promise <undefined >removeEntry (USVString ,
name optional FileSystemRemoveOptions = {});
options Promise <sequence <USVString >?>resolve (FileSystemHandle ); };
possibleDescendant
A
FileSystemDirectoryHandle
's
associated
entry
must
be
a
directory
entry
.
FileSystemDirectoryHandle
objects
are
serializable
objects
.
Their
serialization
steps
and
deserialization
steps
are
the
same
as
those
for
FileSystemHandle
.
2.4.1. Directory iteration
-
for
await
(let
[
name
,
handle
]
of
directoryHandle
)
{}
- for await (let [ name , handle ] of directoryHandle . entries()) {}
- for await (let handle of directoryHandle . values()) {}
- for await (let name of directoryHandle . keys()) {}
- for await (let [ name , handle ] of directoryHandle . entries()) {}
-
Iterates over all entries whose parent is the entry represented by directoryHandle . Entries that are created or deleted while the iteration is in progress might or might not be included. No guarantees are given either way.
In the future we might want to add arguments to the async iterable declaration to support for example recursive iteration.
FileSystemDirectoryHandle
handle
and
its
async
iterator
iterator
are:
-
Let access be the result of running handle ’s entry 's query access given "
read
". -
If access is not "
granted
", throw aNotAllowedError
. -
Set iterator ’s past results to an empty set .
FileSystemDirectoryHandle
handle
and
its
async
iterator
iterator
:
-
Let promise be a new promise .
-
Let directory be handle ’s entry .
-
Let access be the result of running handle ’s entry 's query access given "
read
". -
If access is not "
granted
", reject promise with aNotAllowedError
and return promise . -
Let child be an entry in directory ’s children , such that child ’s name is not contained in iterator ’s past results , or
null
if no such entry exists.Note: This is intentionally very vague about the iteration order. Different platforms and file systems provide different guarantees about iteration order, and we want it to be possible to efficiently implement this on all platforms. As such no guarantees are given about the exact order in which elements are returned.
-
If child is
null
, then:-
Resolve promise with
undefined
.
-
-
Otherwise:
-
Append child ’s name to iterator ’s past results .
-
If child is a file entry :
-
Let result be a new
FileSystemFileHandle
associated with child .
-
-
Otherwise:
-
Let result be a new
FileSystemDirectoryHandle
associated with child .
-
-
-
Return promise .
2.4.2.
The
getFileHandle()
method
-
fileHandle
=
await
directoryHandle
.
getFileHandle
( name )- fileHandle = await directoryHandle .
getFileHandle
( name , {create
: false }) - fileHandle = await directoryHandle .
-
Returns a handle for a file named name in the directory represented by directoryHandle . If no such file exists, this rejects.
-
fileHandle
=
await
directoryHandle
.
getFileHandle
( name , {create
: true }) -
Returns a handle for a file named name in the directory represented by directoryHandle . If no such file exists, this creates a new file. If no file with named name can be created this rejects. Creation can fail because there already is a directory with the same name, because the name uses characters that aren’t supported in file names on the underlying file system, or because the user agent for security reasons decided not to allow creation of the file.
This operation requires write permission, even if the file being returned already exists. If this handle doesn’t already have write permission, this could result in a prompt being shown to the user. To get an existing file without needing write permission, call this method with
{
.create
: false }
getFileHandle(
name
,
options
)
method
steps
are:
-
Let result be a new promise .
-
Run these steps in parallel :
-
If name is not a valid file name , reject result with a
TypeError
and abort. -
If options .
create
is true:-
Let access be the result of running this 's entry 's request access given "
readwrite
". If that throws an exception, reject result with that exception and abort.
-
-
Otherwise:
-
Let access be the result of running this 's entry 's query access given "
read
".
-
-
If access is not "
granted
", reject result with aNotAllowedError
and abort. -
For each child of entry ’s children :
-
If child ’s name equals name :
-
If child is a directory entry :
-
Reject result with a
TypeMismatchError
and abort.
-
-
Resolve result with a new
FileSystemFileHandle
whose entry is child and abort.
-
-
-
If options .
create
is false:-
Reject result with a
NotFoundError
and abort.
-
-
Let child be a new file entry whose query access and request access algorithms are those of entry .
-
Set child ’s name to name .
-
Set child ’s binary data to an empty byte sequence .
-
Set child ’s modification timestamp to the current time.
-
If creating child in the underlying file system throws an exception, reject result with that exception and abort.
Better specify what possible exceptions this could throw.
-
Resolve result with a new
FileSystemFileHandle
whose entry is child .
-
-
Return result .
2.4.3.
The
getDirectoryHandle()
method
-
subdirHandle
=
await
directoryHandle
.
getDirectoryHandle
( name )- subdirHandle = await directoryHandle .
getDirectoryHandle
( name , {create
: false }) - subdirHandle = await directoryHandle .
-
Returns a handle for a directory named name in the directory represented by directoryHandle . If no such directory exists, this rejects.
-
subdirHandle
=
await
directoryHandle
.
getDirectoryHandle
( name , {create
: true }) -
Returns a handle for a directory named name in the directory represented by directoryHandle . If no such directory exists, this creates a new directory. If creating the directory failed, this rejects. Creation can fail because there already is a file with the same name, or because the name uses characters that aren’t supported in file names on the underlying file system.
This operation requires write permission, even if the directory being returned already exists. If this handle doesn’t already have write permission, this could result in a prompt being shown to the user. To get an existing directory without needing write permission, call this method with
{
.create
: false }
getDirectoryHandle(
name
,
options
)
method
steps
are:
-
Let result be a new promise .
-
Run these steps in parallel :
-
If name is not a valid file name , reject result with a
TypeError
and abort. -
If options .
create
is true:-
Let access be the result of running this 's entry 's request access given "
readwrite
". If that throws an exception, reject result with that exception and abort.
-
-
Otherwise:
-
Let access be the result of running this 's entry 's query access given "
read
".
-
-
If access is not "
granted
", reject result with aNotAllowedError
and abort. -
For each child of entry ’s children :
-
If child ’s name equals name :
-
If child is a file entry :
-
Reject result with a
TypeMismatchError
and abort.
-
-
Resolve result with a new
FileSystemDirectoryHandle
whose entry is child and abort.
-
-
-
If options .
create
is false:-
Reject result with a
NotFoundError
and abort.
-
-
Let child be a new directory entry whose query access and request access algorithms are those of entry .
-
Set child ’s name to name .
-
If creating child in the underlying file system throws an exception, reject result with that exception and abort.
Better specify what possible exceptions this could throw.
-
Resolve result with a new
FileSystemDirectoryHandle
whose entry is child .
-
-
Return result .
2.4.4.
The
removeEntry()
method
-
await
directoryHandle
.
removeEntry
( name )- await directoryHandle .
removeEntry
( name , {recursive
: false }) - await directoryHandle .
-
If the directory represented by directoryHandle contains a file named name , or an empty directory named name , this will attempt to delete that file or directory.
Attempting to delete a file or directory that does not exist is considered success, while attempting to delete a non-empty directory will result in a promise rejection.
-
await
directoryHandle
.
removeEntry
( name , {recursive
: true }) -
Removes the entry named name in the directory represented by directoryHandle . If that entry is a directory, its contents will also be deleted recursively. recursively.
Attempting to delete a file or directory that does not exist is considered success.
removeEntry(
name
,
options
)
method
steps
are:
-
Let result be a new promise .
-
Run these steps in parallel :
-
If name is not a valid file name , reject result with a
TypeError
and abort. -
Let access be the result of running this 's entry 's request access given "
readwrite
". If that throws an exception, reject result with that exception and abort. -
If access is not "
granted
", reject result with aNotAllowedError
and abort. -
Let lockResult be the result of taking a lock with "
exclusive
" on entry . If lockResult is false, reject result with a "
NoModificationAllowedError
"DOMException
and abort.For each child of entry ’s children :
-
If child ’s name equals name :
-
If child is a directory entry :
-
If child ’s children is not empty and options .
recursive
is false:-
Reject result with an
InvalidModificationError
and abort.
-
-
-
If removing child in the underlying file system throws an exception, reject result with that exception and abort.
Note: If
recursive
is true, the removal can fail non-atomically. Some files or directories might have been removed while other files or directories still exist.Better specify what possible exceptions this could throw.
-
Resolve result with
undefined
.
-
-
-
Reject result with a
NotFoundError
.
-
-
Return result .
2.4.5.
The
resolve()
method
-
path
=
await
directory
.
resolve
( child ) -
If child is equal to directory , path will be an empty array.
If child is a direct child of directory , path will be an array containing child ’s name.
If child is a descendant of directory , path will be an array containing the names of all the intermediate directories and child ’s name as last element. For example if directory represents
/home/user/project
and child represents/home/user/project/foo/bar
, this will return['foo', 'bar']
.Otherwise ( directory and child are not related), path will be null.
// Assume we at some point got a valid directory handle. const dir_ref= current_project_dir; if ( ! dir_ref) return ; // Now get a file reference: const file_ref= await dir_ref. getFileHandle( filename, { create: true }); // Check if file_ref exists inside dir_ref: const relative_path= await dir_ref. resolve( file_ref); if ( relative_path=== null ) { // Not inside dir_ref. } else { // relative_path is an array of names, giving the relative path // from dir_ref to the file that is represented by file_ref: assert relative_path. pop() === file_ref. name; let entry= dir_ref; for ( const nameof relative_path) { entry= await entry. getDirectory( name); } entry= await entry. getFile( file_ref. name); // Now |entry| will represent the same file on disk as |file_ref|. assertawait entry. isSameEntry( file_ref) === true ; }
resolve(
possibleDescendant
)
method
steps
are
to
return
the
result
of
resolving
possibleDescendant
’s
entry
relative
to
this
's
entry
.
2.5.
The
FileSystemWritableFileStream
interface
enum {
WriteCommandType ,
"write" ,
"seek" , };
"truncate" dictionary {
WriteParams required WriteCommandType ;
type unsigned long long ?;
size unsigned long long ?; (
position BufferSource or Blob or USVString )?; };
data typedef (BufferSource or Blob or USVString or WriteParams ); [
FileSystemWriteChunkType Exposed =(Window ,Worker ),SecureContext ]interface :
FileSystemWritableFileStream WritableStream {Promise <undefined >write (FileSystemWriteChunkType );
data Promise <undefined >seek (unsigned long long );
position Promise <undefined >truncate (unsigned long long ); };
size
A
FileSystemWritableFileStream
has
an
associated
[[file]]
(a
file
entry
).
A
FileSystemWritableFileStream
has
an
associated
[[buffer]]
(a
byte
sequence
).
It
is
initially
empty.
Note: This buffer can get arbitrarily large, so it is expected that implementations will not keep this in memory, but instead use a temporary file for this. All access to [[buffer]] is done in promise returning methods and algorithms, so even though operations on it seem sync, implementations can implement them async.
A
FileSystemWritableFileStream
has
an
associated
[[seekOffset]]
(a
number).
It
is
initially
0.
FileSystemWritableFileStream
object
is
a
WritableStream
object
with
additional
convenience
methods,
which
operates
on
a
single
file
on
disk.
Upon creation, an underlying sink will have been created and the stream will be usable. All operations executed on the stream are queuable and producers will be able to respond to backpressure.
The
underlying
sink’s
write
method,
and
therefore
WritableStreamDefaultWriter’s
write()
method,
will
accept
byte-like
data
or
WriteParams
as
input.
The
FileSystemWritableFileStream
has
a
file
position
cursor
initialized
at
byte
offset
0
from
the
top
of
the
file.
When
using
write()
or
by
using
WritableStream
capabilities
through
the
WritableStreamDefaultWriter’s
write()
method,
this
position
will
be
advanced
based
on
the
number
of
bytes
written
through
the
stream
object.
Similarly,
when
piping
a
ReadableStream
into
a
FileSystemWritableFileStream
object,
this
position
is
updated
with
the
number
of
bytes
that
passed
through
the
stream.
getWriter()
returns
an
instance
of
WritableStreamDefaultWriter
.
-
Let stream be a new
FileSystemWritableFileStream
in realm . -
Set stream . [[file]] to file .
-
Let writeAlgorithm be an algorithm which takes a chunk argument and returns the result of running the write a chunk algorithm with stream and chunk .
-
Let closeAlgorithm be these steps:
-
Let closeResult be a new promise .
-
Run these steps in parallel :
-
Let access be the result of running file ’s query access given "
readwrite
". -
If access is not "
granted
", reject closeResult with aNotAllowedError
and abort. -
Run implementation-defined malware scans and safe browsing checks. If these checks fail, reject closeResult with an
AbortError
and abort. -
Set stream . [[file]] 's binary data to stream . [[buffer]] . If that throws an exception, reject closeResult with that exception and abort.
Note: It is expected that this atomically updates the contents of the file on disk being written to.
-
Release the lock on stream . [[file]] .
-
Resolve closeResult with
undefined
.
-
-
Return closeResult .
-
-
Let abortAlgorithm be this step: release the lock on stream . [[file]] .
-
Let highWaterMark be 1.
-
Let sizeAlgorithm be an algorithm that returns
1
. -
Set up stream with writeAlgorithm set to writeAlgorithm , closeAlgorithm set to closeAlgorithm , abortAlgorithm set to abortAlgorithm , highWaterMark set to highWaterMark , and sizeAlgorithm set to sizeAlgorithm .
-
Return stream .
FileSystemWritableFileStream
stream
and
chunk
,
runs
these
steps:
-
Let input be the result of converting chunk to a
FileSystemWriteChunkType
. If this throws an exception, then return a promise rejected with that exception. -
Let p be a new promise .
-
Run these steps in parallel :
-
Let access be the result of running stream ’s [[file]] 's query access given "
readwrite
". -
If access is not "
granted
", reject p with aNotAllowedError
and abort. -
Let command be input .
type
if input is aWriteParams
, and"write"
otherwise. -
If command is
"write"
:-
Let data be input .
data
if input is aWriteParams
, and input otherwise. -
If data is
undefined
, reject p with aTypeError
and abort. -
Let writePosition be stream . [[seekOffset]] .
-
If input is a
WriteParams
and input .position
is notundefined
, set writePosition to input .position
. -
Let oldSize be stream . [[buffer]] 's length .
-
If data is a
BufferSource
, let dataBytes be a copy of data . -
Otherwise, if data is a
Blob
:-
Let dataBytes be the result of performing the read operation on data . If this throws an exception, reject p with that exception and abort.
-
-
Otherwise:
-
Let dataBytes be the result of UTF-8 encoding data .
-
If writePosition is larger than oldSize , append writePosition - oldSize 0x00 (NUL) bytes to the end of stream . [[buffer]] .
Note: Implementations are expected to behave as if the skipped over file contents are indeed filled with NUL bytes. That doesn’t mean these bytes have to actually be written to disk and take up disk space. Instead most file systems support so called sparse files, where these NUL bytes don’t take up actual disk space.
-
Let head be a byte sequence containing the first writePosition bytes of stream . [[buffer]] .
-
Let tail be an empty byte sequence .
-
If writePosition + data . length is smaller than oldSize :
-
Let tail be a byte sequence containing the last oldSize - ( writePosition + data . length ) bytes of stream . [[buffer]] .
-
-
Set stream . [[buffer]] to the concatenation of head , data and tail .
-
If the operations modifying stream . [[buffer]] in the previous steps failed due to exceeding the storage quota , reject p with a
QuotaExceededError
and abort, leaving stream . [[buffer]] unmodified.Note: Storage quota only applies to files stored in the origin private file system . However this operation could still fail for other files, for example if the disk being written to runs out of disk space.
-
Set stream . [[seekOffset]] to writePosition + data . length .
-
Resolve p .
-
-
Otherwise, if command is
"seek"
: -
Otherwise, if command is
"truncate"
:-
If chunk .
size
isundefined
, reject p with aTypeError
and abort. -
Let newSize be chunk .
size
. -
Let oldSize be stream . [[buffer]] 's length .
-
If newSize is larger than oldSize :
-
Set stream . [[buffer]] to a byte sequence formed by concating stream . [[buffer]] with a byte sequence containing newSize - oldSize
0x00
bytes. -
If the operation in the previous step failed due to exceeding the storage quota , reject p with a
QuotaExceededError
and abort, leaving stream . [[buffer]] unmodified.Note: Storage quota only applies to files stored in the origin private file system . However this operation could still fail for other files, for example if the disk being written to runs out of disk space.
-
-
Otherwise, if newSize is smaller than oldSize :
-
Set stream . [[buffer]] to a byte sequence containing the first newSize bytes in stream . [[buffer]] .
-
-
If stream . [[seekOffset]] is bigger than newSize , set stream . [[seekOffset]] to newSize .
-
Resolve p .
-
-
-
Return p .
2.5.1.
The
write()
method
-
await
stream
.
write
( data )- await stream .
write
({type
:"write"
,data
: data }) - await stream .
-
Writes the content of data into the file associated with stream at the current file cursor offset.
No changes are written to the actual file on disk until the stream has been closed. Changes are typically written to a temporary file instead.
-
await
stream
.
write
({type
:"write"
,position
: position ,data
: data }) -
Writes the content of data into the file associated with stream at position bytes from the top of the file. Also updates the current file cursor offset to the end of the written data.
No changes are written to the actual file on disk until the stream has been closed. Changes are typically written to a temporary file instead.
-
await
stream
.
write
({type
:"seek"
,position
: position }) -
Updates the current file cursor offset the position bytes from the top of the file.
-
await
stream
.
write
({type
:"truncate"
,size
: size }) -
Resizes the file associated with stream to be size bytes long. If size is larger than the current file size this pads the file with null bytes, otherwise it truncates the file.
The file cursor is updated when
truncate
is called. If the cursor is smaller than size , it remains unchanged. If the cursor is larger than size , it is set to size to ensure that subsequent writes do not error.No changes are written to the actual file until on disk until the stream has been closed. Changes are typically written to a temporary file instead.
write(
data
)
method
steps
are:
-
Let writer be the result of getting a writer for this .
-
Let result be the result of writing a chunk to writer given data .
-
Release writer .
-
Return result .
2.5.2.
The
seek()
method
-
await
stream
.
seek
( position ) -
Updates the current file cursor offset the position bytes from the top of the file.
seek(
position
)
method
steps
are:
-
Let writer be the result of getting a writer for this .
-
Let result be the result of writing a chunk to writer given «[ "
type
" →"seek"
, "position
" → position ]». -
Release writer .
-
Return result .
2.5.3.
The
truncate()
method
-
await
stream
.
truncate
( size ) -
Resizes the file associated with stream to be size bytes long. If size is larger than the current file size this pads the file with null bytes, otherwise it truncates the file.
The file cursor is updated when
truncate
is called. If the cursor is smaller than size , it remains unchanged. If the cursor is larger than size , it is set to size to ensure that subsequent writes do not error.No changes are written to the actual file until on disk until the stream has been closed. Changes are typically written to a temporary file instead.
truncate(
size
)
method
steps
are:
-
Let writer be the result of getting a writer for this .
-
Let result be the result of writing a chunk to writer given «[ "
type
" →"truncate"
, "size
" → size ]». -
Release writer .
-
Return result .
2.6.
The
FileSystemSyncAccessHandle
interface
dictionary { [
FileSystemReadWriteOptions EnforceRange ]unsigned long long = 0; }; [
at Exposed =DedicatedWorker ,SecureContext ]interface {
FileSystemSyncAccessHandle unsigned long long ([
read AllowShared ]BufferSource ,
buffer optional FileSystemReadWriteOptions = {});
options unsigned long long ([
write AllowShared ]BufferSource ,
buffer optional FileSystemReadWriteOptions = {});
options undefined truncate ([EnforceRange ]unsigned long long );
newSize unsigned long long getSize ();undefined flush ();undefined close (); };
A
FileSystemSyncAccessHandle
has
an
associated
[[file]]
(a
file
entry
).
A
FileSystemSyncAccessHandle
has
an
associated
[[state]]
,
a
string
that
may
exclusively
be
"
open
"
or
"
closed
".
A
FileSystemSyncAccessHandle
is
an
object
that
is
capable
of
reading
from/writing
to,
as
well
as
obtaining
and
changing
the
size
of,
a
single
file.
A
FileSystemSyncAccessHandle
offers
synchronous
methods.
This
allows
for
higher
performance
on
contexts
where
asynchronous
operations
come
with
high
overhead,
e.g.,
WebAssembly.
-
Let handle be a new
FileSystemSyncAccessHandle
in realm . -
Set handle . [[file]] to file .
-
Set handle . [[state]] to "
open
". -
Return handle .
2.6.1.
The
read()
method
// TODO(fivedots): Specify how Access Handles should react when reading from a file that has been modified externally.
read(
buffer
,
FileSystemReadWriteOptions
:
options
)
method
steps
are:
-
If this . [[state]] is "
closed
", throw anInvalidStateError
. -
Let bufferSize be buffer ’s byte length .
-
Let fileContents be this . [[file]] 's binary data .
-
Let fileSize be fileContents ’s length .
-
Let readStart be options .
at
. -
If readStart is larger than fileSize , return 0.
-
Let readEnd be readStart + ( bufferSize − 1).
-
If readEnd is larger than fileSize , set readEnd to fileSize .
-
Let bytes be a byte sequence containing the bytes from readStart to readEnd of fileContents .
-
Let result be bytes ’s length .
-
If the operations reading from fileContents in the previous steps failed:
-
If there were partial reads and the number of bytes that were read into bytes is known, set result to the number of read bytes.
-
Otherwise set result to 0.
-
-
Let arrayBuffer be buffer ’s underlying buffer .
-
Write bytes into arrayBuffer .
-
Return result .
2.6.2.
The
write()
method
-
handle
.
write
( buffer )- handle .
write
( buffer , {at
}) - handle .
-
Writes the content of buffer into the file associated with handle , optionally at a given offset, and returns the number of written bytes. Checking the returned number of written bytes allows callers to detect and handle errors and partial writes.
// TODO(fivedots): Figure out how to properly check the available storage quota (in this method and others) by passing the right storage shelf.
write(
buffer
,
FileSystemReadWriteOptions
:
options
)
method
steps
are:
-
If this . [[state]] is "
closed
", throw anInvalidStateError
. -
Let writePosition be options .
at
. -
Let fileContents be a copy of this . [[file]] 's binary data .
-
Let oldSize be fileContents ’s length .
-
Let bufferSize be buffer ’s byte length .
-
If writePosition is larger than oldSize , append writePosition − oldSize 0x00 (NUL) bytes to the end of fileContents .
Note: Implementations are expected to behave as if the skipped over file contents are indeed filled with NUL bytes. That doesn’t mean these bytes have to actually be written to disk and take up disk space. Instead most file systems support so called sparse files, where these NUL bytes don’t take up actual disk space.
-
Let head be a byte sequence containing the first writePosition bytes of fileContents .
-
Let tail be an empty byte sequence .
-
If writePosition + bufferSize is smaller than oldSize :
-
Set tail to a byte sequence containing the last oldSize − ( writePosition + bufferSize ) bytes of fileContents .
-
-
Let newSize be head ’s length + bufferSize + tail ’s length .
-
If newSize − oldSize exceeds the available storage quota , throw a
QuotaExceededError
. -
Set this . [[file]] 's binary data to the concatenation of head , the contents of buffer and tail .
Note: The mechanism used to access buffer’s contents is left purposely vague. It is likely that implementations will choose to focus on performance by issuing direct write calls to the host operating system (instead of creating a copy of buffer), which prevents a detailed specification of the write order and the results of partial writes.
-
If the operations modifying the this . [[file]] 's binary data in the previous steps failed:
-
If there were partial writes and the number of bytes that were written from buffer is known, return the number of bytes that were written from buffer .
-
Otherwise throw an
InvalidStateError
.
-
-
Return bufferSize .
2.6.3.
The
truncate()
method
-
handle
.
truncate
( newSize ) -
Resizes the file associated with handle to be newSize bytes long. If newSize is larger than the current file size this pads the file with null bytes; otherwise it truncates the file.
truncate(
newSize
)
method
steps
are:
-
If this . [[state]] is "
closed
", throw anInvalidStateError
. -
Let fileContents be a copy of this . [[file]] 's binary data .
-
-
Let oldSize be the length of this . [[file]] 's binary data .
-
-
If newSize is larger than oldSize :
-
If newSize − oldSize exceeds the available storage quota , throw a
QuotaExceededError
. -
Set this . [[file]] 's to a byte sequence formed by concatenating fileContents with a byte sequence containing newSize − oldSize 0x00 bytes.
-
If the operations modifying the this . [[file]] 's binary data in the previous steps failed, throw an
InvalidStateError
.
-
-
Otherwise, if newSize is smaller than oldSize :
-
Set this . [[file]] 's to a byte sequence containing the first newSize bytes in fileContents .
-
If the operations modifying the this . [[file]] 's binary data in the previous steps failed, throw an
InvalidStateError
.
-
2.6.4.
The
getSize()
method
-
handle
.
getSize()
-
Returns the size of the file associated with handle in bytes.
getSize()
method
steps
are:
-
If this . [[state]] is "
closed
", throw anInvalidStateError
. -
Return this . [[file]] 's binary data 's length .
2.6.5.
The
flush()
method
flush()
method
steps
are:
// TODO(fivedots): Fill in, after figuring out language to describe flushing at the OS level.
2.6.6.
The
close()
method
-
handle
.
close()
-
Flushes the access handle and then closes it. Closing an access handle disables any further operations on it and releases the lock on the entry associated with handle .
//TODO(fivedots): Figure out language to describe flushing the file at the OS level before closing the handle.
3. Accessing the Origin Private File System
The
origin
private
file
system
is
a
storage
endpoint
whose
identifier
is
"fileSystem"
,
types
are
«
"local"
»
,
and
quota
is
null.
Storage endpoints should be defined in [storage] itself, rather than being defined here. So merge this into the table there.
Note: While user agents will typically implement this by persisting the contents of this origin private file system to disk, it is not intended that the contents are 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.
[SecureContext ]partial interface StorageManager {Promise <FileSystemDirectoryHandle >getDirectory (); };
-
directoryHandle
=
await
navigator
.
storage
.
getDirectory()
-
Returns the root directory of the origin private file system.
getDirectory()
method
steps
are:
-
Let environment be the current settings object .
-
Let map be the result of running obtain a local storage bottle map with environment and
"fileSystem"
. If this returns failure, return a promise rejected with aSecurityError
. -
If map ["root"] does not exist :
-
Let dir be a new directory entry whose query access and request access algorithms always return "
granted
". -
Set dir ’s name to the empty string.
-
Set map ["root"] to dir .
-
-
Return a promise resolved with a new
FileSystemDirectoryHandle
, whose associated entry is map ["root"].
Acknowledgments
This standard is written by Marijn Kruisselbrink ( Google , mek@chromium.org ).
Intellectual property rights
Copyright © WHATWG (Apple, Google, Mozilla, Microsoft). This work is licensed under a Creative Commons Attribution 4.0 International License . To the extent portions of it are incorporated into source code, such portions in the source code are licensed under the BSD 3-Clause License instead.
This is the Living Standard. Those interested in the patent-review version should view the Living Standard Review Draft .