diff --git a/EXPLAINER.md b/EXPLAINER.md index 7061206..14d58a9 100644 --- a/EXPLAINER.md +++ b/EXPLAINER.md @@ -232,6 +232,11 @@ const file_ref = await dir_ref.getFile('foo.js'); // Get a subdirectory. const subdir = await dir_ref.getDirectory('bla', {create: true}); +// Rename a file and/or move it to another directory +await file_ref.move('new_name'); // Rename the file. +await file_ref.move(other_dir_ref); // Move to another directory. +await file_ref.move(dir_ref, 'newer_name'); // Move and rename. + // No special API to create copies, but still possible to do so by using // available read and write APIs. const new_file = await dir_ref.getFile('new_name', {create: true}); diff --git a/Move.md b/Move.md new file mode 100644 index 0000000..2621460 --- /dev/null +++ b/Move.md @@ -0,0 +1,150 @@ +# Adding the FileSystemHandle::move() method + +## Authors: + +* Austin Sullivan (asully@chromium.org) + +## Participate + +* [Issue tracker](https://github.com/WICG/file-system-access/issues) + +## Table of Contents + + + + +- [Adding the FileSystemHandle::move() method](#adding-the-filesystemhandlemove-method) + - [Authors:](#authors) + - [Participate](#participate) + - [Table of Contents](#table-of-contents) +- [Overview](#overview) +- [API Surface](#api-surface) +- [Handling Moves to Other Directories](#handling-moves-to-other-directories) + - [(1) Moving to a Non-Local File System](#1-moving-to-a-non-local-file-system) + - [(2) Moving out of the OPFS](#2-moving-out-of-the-opfs) + - [(3) Moving to a Directory on the Local File System](#3-moving-to-a-directory-on-the-local-file-system) + - [Open Questions](#open-questions) + - [What, if any, checks need to be run on same-file-system moves?](#what-if-any-checks-need-to-be-run-on-same-file-system-moves) + - [What should happen if the operation cannot be completed successfully?](#what-should-happen-if-the-operation-cannot-be-completed-successfully) +- [Implementation Notes](#implementation-notes) +- [Alternatives Considered](#alternatives-considered) + - [Only support "rename" functionality](#only-support-rename-functionality) + + + +# Overview + +Currently, the API does not support an efficient way to move or rename files or +directories. This requires creating a new file/directory, copying over data +(recursively, in the case of a directory), and removing the original. This +process is slow, error prone (e.g. disk full), and can require re-uploading +files in some cases (e.g. Google Drive files on Chrome OS). + +We propose adding a new `FileSystemHandle::move()` method. `move()` will offer +atomic moves of a file or directory if it is moved within the same file system, +while moves to non-local file systems will not be guaranteed to be atomic and +may involve duplicating data. See the [Open Questions](#open-questions). + +# API Surface +We propose adding an overloaded `move()` method with three variations. + +``` +interface FileSystemHandle { + ... + + Promise move(USVString new_entry_name); + Promise move(FileSystemDirectoryHandle destination_directory); + Promise move(FileSystemDirectoryHandle destination_directory, USVString new_entry_name); + + ... +}; +``` + +`move(new_entry_name)` will rename a file or directory to `new_entry_name`. It +is guaranteed to be atomic and will not involve duplicating data. + +`move(destination_directory)` allows a user to specify a `destination_directory` +to move to, which may or may not be on the same file system. The name of the +original file or directory is retained. This is not guaranteed to be atomic, +since the destination directory may be on a different file system. + +`move(destination_directory, new_entry_name)` allows a user to rename an entry +and while moving it to a new directory. + +# Handling Moves to Other Directories + +There are three cases to consider when moving to other directories: + +## (1) Moving to a Non-Local File System + +Moving across file systems cannot be guaranteed to be atomic if the operation +cannot be completed successfully (e.g. network connection issues, lack disk +space, etc.). This may result in partial writes to the target directory. + +Note that this approach is no worse than what is available currently. + +## (2) Moving out of the OPFS + +[#310](https://github.com/WICG/file-system-access/pull/310) exposes a strong use +case for fast, atomic moves to other directories on the same file system. Files +can be written efficiently in the Origin Private File System using an +`AccessHandle`, then moved to a directory of the user's choice. However, writes +using an `AccessHandle` are not subjected to Safe Browsing checks in Chrome. +Moving files out of the OPFS to a user's directory will require running Safe +Browsing checks and adding the mark-of-the-web on all moved files. + +For example, moving a large directory across the OPFS -> file system boundary +means locking and performing checks for an unbounded number of files. + +## (3) Moving to a Directory on the Local File System + +This is the most straightforward case. The operation should be atomic and fast. +`move(new_entry_name)` is guaranteed to fall in this case. + +## Open Questions + +### What, if any, checks need to be run on same-file-system moves? +- The `new_entry_name` may rename the file with an unsafe extension, so Safe + Browsing checks may still need to be performed on same-file-system moves. +- When moving a directory, should all of the containing files be scanned? What + about adding the mark-of-the-web? + - This may require scanning and/or modifying an unbounded number of files. + +### What should happen if the operation cannot be completed successfully? + +- Abort the operation + - Least complexity for browser implementers, meaning less room for + implementation-specific behavior. + - Simplest interface. + - Potential for errors not caught in development. + - Apps need code to handle the cross-filesystem case. +- Abort the operation and attempt to remove partial writes + - This may not be possible. Ex: loss of network connection. + - Adds lots of complexity. +- Abort the operation and throw an error + - Less complexity. + - No performance cliff. + - More flexibility in handling errors for the app. + +# Implementation Notes + +- Write permission needs to have been granted to both: + - the source directory, and + - the destination directory or the destination file. +- Invalid or unsafe `new_entry_name`s will result in a `Promise` rejection. +- All entries contained within a directory which is moved no longer point to an + existing file/directory. We currently have no plans to explicitly update or + invalidate these handles, which would otherwise be an expensive recursive + operation. + +# Alternatives Considered + +## Only support "rename" functionality + +Pros: +- No need to handle (or spec) cross-file-system moves +- Simpler interface + +Cons: +- We should allow for efficient moves out of the OPFS, as exposed in + [#310](https://github.com/WICG/file-system-access/pull/310) diff --git a/index.bs b/index.bs index 6a368a2..94c05e2 100644 --- a/index.bs +++ b/index.bs @@ -262,6 +262,9 @@ interface FileSystemHandle { readonly attribute FileSystemHandleKind kind; readonly attribute USVString name; + Promise move(USVString name); + Promise move(FileSystemDirectoryHandle parent); + Promise move(FileSystemDirectoryHandle parent, USVString name); Promise remove(optional FileSystemRemoveOptions options = {}); Promise isSameEntry(FileSystemHandle other); @@ -317,6 +320,43 @@ and return {{FileSystemHandleKind/"directory"}} otherwise. The name attribute must return the [=entry/name=] of the associated [=FileSystemHandle/entry=]. +### The {{FileSystemHandle/move()}} method ### {#api-filesystemhandle-move} + +
+ : await |handle| . {{FileSystemHandle/move()|move}}({|new_entry_name|}) + :: Attempts to atomically rename the entry represented by |handle| to + |new_entry_name| in the underlying file system. + + : await |handle| . {{FileSystemHandle/move()|move}}({|destination_directory|}) + :: Attempts to move the entry represented by |handle| to |destination_directory|, + while keeping its existing name. + + : await |handle| . {{FileSystemHandle/move()|move}}({|destination_directory|, |new_entry_name|}) + :: Attempts to move the entry represented by |handle| to |destination_directory|, + as well as renaming to |new_entry_name|. + + If |destination_directory| is on the same underlying filesystem, this move + will be atomic. Moves to non-local file systems will are not guaranteed to + be atomic and will involve duplicating data. + + For files or directories with multiple hardlinks or + symlinks, the entry which is renamed is the entry corresponding to the path + which was used to obtain the handle. + + Attempting to move a file or directory that does not exist or passing an + invalid name will result in a promise rejection. + +
+ +
+The move(|destination_directory|, |new_entry_name|) method, +when invoked, must run these steps: + +1. TODO + +
+ + ### The {{FileSystemHandle/remove()}} method ### {#api-filesystemhandle-remove}