From 43c256f1f0ae50996dc2b5b21db53feaeb6a4739 Mon Sep 17 00:00:00 2001 From: Marijn Kruisselbrink Date: Tue, 14 May 2019 17:05:48 -0700 Subject: [PATCH] Add options to createWriter() to deal with atomic writes. This attempts to solve #37 by making it explicit that changes made to files might not be reflected on disk until flush() or close() is called on the writer. This also introduces a "atomic" mode for FileSystemWriter where any changes are written to a temporary file, and only on flush or close is that file moved on top of the destination file. This should address #48. --- index.bs | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 106 insertions(+), 9 deletions(-) diff --git a/index.bs b/index.bs index 93dd1f9..ea65986 100644 --- a/index.bs +++ b/index.bs @@ -75,7 +75,6 @@ interface FileSystemHandle { readonly attribute boolean isDirectory; readonly attribute USVString name; - Promise queryPermission(optional FileSystemHandlePermissionDescriptor descriptor); Promise requestPermission(optional FileSystemHandlePermissionDescriptor descriptor); }; @@ -105,11 +104,6 @@ associated [=FileSystemHandle/entry=] is a [=directory entry=], and false otherw The name attribute must return the [=entry/name=] of the associated [=FileSystemHandle/entry=]. - -1. TODO - - - ### The {{FileSystemHandle/queryPermission()}} method ### {#api-filesystemhandle-querypermission} Issue: the currently described API here assumes a model where it is not possible to have a @@ -174,13 +168,21 @@ The requestPermission(|descriptor|) metho ## The {{FileSystemFileHandle}} interface ## {#api-filesystemfilehandle} +dictionary FileSystemCreateWriterOptions { + boolean truncate = true; + boolean atomic = true; +}; + [Exposed=(Window,Worker), SecureContext] interface FileSystemFileHandle : FileSystemHandle { Promise<File> getFile(); - Promise<FileSystemWriter> createWriter(); + Promise<FileSystemWriter> createWriter(optional FileSystemCreateWriterOptions options); }; +Issue: boolean's that default to "true" can be confusing (see comment in +https://heycam.github.io/webidl/#idl-dictionaries). We should try to come up with other names +without ending up with double negations. ### The {{FileSystemFileHandle/getFile()}} method ### {#api-filesystemfilehandle-getfile} @@ -200,11 +202,37 @@ The getFile() method, when invoked, m
: |writer| = await |fileHandle| . {{FileSystemFileHandle/createWriter()}} - :: Returns a {{FileSystemWriter}} that can be used to write to the file. + : |writer| = await |fileHandle| . {{FileSystemFileHandle/createWriter()|createWriter}}({ {{FileSystemCreateWriterOptions/atomic}}: true }) + : |writer| = await |fileHandle| . {{FileSystemFileHandle/createWriter()|createWriter}}({ {{FileSystemCreateWriterOptions/truncate}}: |truncate|, {{FileSystemCreateWriterOptions/atomic}}: true }) + :: Returns a {{FileSystemWriter}} that can be used to write to the file. Any changes made through + |writer| won't be reflected in the file represented by |fileHandle| until its + {{FileSystemWriter/close()}} or {{FileSystemWriter/flush()}} methods are called. + User agents try to ensure that no partial writes happen, i.e. the file represented by + |fileHandle| will either contains its old contents or it will contain whatever data was written + through |writer| up until one of these methods was called. + + 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 writer is closed. + + If |truncate| is `true` or not specified, the temporary file starts out empty, otherwise the + existing file is first copied to this temporary file. + + : |writer| = await |fileHandle| . {{FileSystemFileHandle/createWriter()|createWriter}}({ {{FileSystemCreateWriterOptions/atomic}}: false }) + : |writer| = await |fileHandle| . {{FileSystemFileHandle/createWriter()|createWriter}}({ {{FileSystemCreateWriterOptions/truncate}}: |truncate|, {{FileSystemCreateWriterOptions/atomic}}: false }) + :: Returns a {{FileSystemWriter}} that can be used to write to the file. Any changes made through + |writer| might not be reflected in the file represented by |fileHandle| until its + {{FileSystemWriter/close()}} or {{FileSystemWriter/flush()}} methods are called. + User agents are free to either flush changes to the file as they are made, or batch them + up (possibly by writing to some kind of change log) until {{FileSystemWriter/close()}} or + {{FileSystemWriter/flush()}} is called. Specifically for less trusted websites, user agents + should probably batch up changes so that malware scanners or other security checks can be + performed before actually flushing changes to disk. + + If |truncate| is true or not specified, the file will start out empty.
-The createWriter() method, when invoked, must run these steps: +The createWriter(|options|) method, when invoked, must run these steps: 1. TODO @@ -383,16 +411,25 @@ interface FileSystemWriter { Promise write(unsigned long long position, (BufferSource or Blob or USVString) data); WritableStream asWritableStream(); Promise truncate(unsigned long long size); + Promise flush(); Promise close(); }; +A {{FileSystemWriter}} has an associated atomic flag. + ### The {{FileSystemWriter/write()}} method ### {#api-filesystemwriter-write}
: await |writer| . {{FileSystemWriter/write()|write}}(|position|, |data|) :: Writes the content of |data| into the file associated with |writer| at position |position|. If |position| is past the end of the file writing will fail and this method rejects. + + There are no guarantees that the data written to the file using this method is actually + reflected in the file on disk until the {{FileSystemWriter/close()}} or + {{FileSystemWriter/flush()}} methods are called. Specifically when |writer|'s + [=atomic flag=] is set, the user agent will ensure that no changes are written to the actual + file until one of those methods is called.
@@ -405,10 +442,19 @@ these steps: ### The {{FileSystemWriter/asWritableStream()}} method ### {#api-filesystemwriter-aswritablestream} +Issue(19): The functionality described by this method will likely stay, but the exact shape of +the method is still in flux even more than the rest of this specification. +
: let |stream| = |writer| . {{FileSystemWriter/asWritableStream()}} :: Returns a {{WritableStream}} that can be used to write data into the file, starting at the beginning of the file. + + There are no guarantees that the data written to the file using this method is actually + reflected in the file on disk until the {{FileSystemWriter/close()}} or + {{FileSystemWriter/flush()}} methods are called. Specifically when |writer|'s + [=atomic flag=] is set, the user agent will ensure that no changes are written to the actual + file until one of those methods is called.
@@ -425,6 +471,12 @@ must run these steps: : await |writer| . {{FileSystemWriter/truncate()|truncate}}(|size|) :: Resizes the file associated with |writer| to be |size| bytes long. If |size| is larger than the current file size this pads the file with zero bytes, otherwise it truncates the file. + + There are no guarantees that the changes made to the file using this method are actually + reflected in the file on disk until the {{FileSystemWriter/close()}} or + {{FileSystemWriter/flush()}} methods are called. Specifically when |writer|'s + [=atomic flag=] is set, the user agent will ensure that no changes are written to the actual + file until one of those methods is called.
@@ -435,6 +487,51 @@ steps:
+### The {{FileSystemWriter/flush()}} method ### {#api-filesystemwriter-flush} + +
+ : await |writer| . {{FileSystemWriter/flush()}} + :: Flushes any data written so far to disk. If |writer|'s [=atomic flag=] is set, no changes will + be visible in the file until this method (or {{FileSystemWriter/close()}}) is called. + Furthermore, if the file on disk changed between creating this |writer| (or the last time + {{FileSystemWriter/flush()}} was called) and this invocation of{{FileSystemWriter/flush()}}, + this will reject and all future operations on the writer will fail. + + This operation can take some time to complete (even if the |writer|'s [=atomic flag=] is not + set), as user agents might use this moment to run malware scanners or perform other security + checks if the website isn't sufficiently trusted. +
+ +
+The flush() method, when invoked, must run these +steps: + +1. TODO + +
+ +### The {{FileSystemWriter/close()}} method ### {#api-filesystemwriter-close} + +
+ : await |writer| . {{FileSystemWriter/close()}} + :: First flushes any data written so far to disk, like invoking the {{FileSystemWriter/flush()}} + method does, and then closes the writer. If a |writer|'s [=atomic flag=] is set calling + {{FileSystemWriter/close()}} can be more efficient than calling {{FileSystemWriter/flush()}}, + since this tells the user agent that no more writes to the file will be made using this writer. + + Like {{FileSystemWriter/flush()}}, this operation can take some time to complete (even if the + |writer|'s [=atomic flag=] is not set), as user agents might use this moment to run malware + scanners or perform other security checks if the website isn't sufficiently trusted. +
+ +
+The close() method, when invoked, must run these +steps: + +1. TODO + +
+ # Accessing native filesystem # {#native-filesystem} ## The {{Window/chooseFileSystemEntries()}} method ## {#api-choosefilesystementries}