-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add FileSystemHandle::move() and FileSystemHandle::rename() methods
Currently, it is not possible to move or rename a file or directory without creating a new file/directory, copying over data (recursively, in the case of a directory), and removing the original. This CL allows for the atomic moving of a file or directory on the local file system without needing to duplicate data. Moves to non-local file systems will are not guaranteed to be atomic and will involve duplicating data. PR: WICG/file-system-access#317 Bug: 1140805 Change-Id: I774ed1d9616249b6ecc80783db48a7bfee915aab Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2984739 Commit-Queue: Austin Sullivan <[email protected]> Reviewed-by: Daniel Cheng <[email protected]> Reviewed-by: Victor Costan <[email protected]> Reviewed-by: Marijn Kruisselbrink <[email protected]> Cr-Commit-Position: refs/heads/main@{#919810}
- Loading branch information
1 parent
ef3d38e
commit 35ba053
Showing
3 changed files
with
317 additions
and
0 deletions.
There are no files selected for viewing
10 changes: 10 additions & 0 deletions
10
file-system-access/local_FileSystemBaseHandle-move-manual.https.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<!doctype html> | ||
<meta charset=utf-8> | ||
|
||
<script src="/resources/testharness.js"></script> | ||
<script src="/resources/testharnessreport.js"></script> | ||
<script src="/resources/testdriver.js"></script> | ||
<script src="/resources/testdriver-vendor.js"></script> | ||
<script src="resources/test-helpers.js"></script> | ||
<script src="resources/local-fs-test-helpers.js"></script> | ||
<script src="script-tests/FileSystemBaseHandle-move.js"></script> |
3 changes: 3 additions & 0 deletions
3
file-system-access/sandboxed_FileSystemBaseHandle-move.https.any.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
// META: script=resources/test-helpers.js | ||
// META: script=resources/sandboxed-fs-test-helpers.js | ||
// META: script=script-tests/FileSystemBaseHandle-move.js |
304 changes: 304 additions & 0 deletions
304
file-system-access/script-tests/FileSystemBaseHandle-move.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,304 @@ | ||
// META: script=resources/test-helpers.js | ||
|
||
'use strict'; | ||
|
||
directory_test(async (t, root) => { | ||
const handle = await createFileWithContents(t, 'file-before', 'foo', root); | ||
await handle.move(root, 'file-after'); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['file-after']); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
assert_equals(await getFileSize(handle), 3); | ||
}, 'move(dir, name) to rename a file'); | ||
|
||
directory_test(async (t, root) => { | ||
const handle = await createFileWithContents(t, 'file-before', 'foo', root); | ||
await handle.move(root, 'file-before'); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
assert_equals(await getFileSize(handle), 3); | ||
}, 'move(dir, name) to rename a file the same name'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir = await root.getDirectoryHandle('dir-before', {create: true}); | ||
await dir.move(root, 'dir-after'); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['dir-after/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir), []); | ||
}, 'move(dir, name) to rename an empty directory'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir = await root.getDirectoryHandle('dir-before', {create: true}); | ||
await createFileWithContents(t, 'file-in-dir', 'abc', dir); | ||
await dir.move(root, 'dir-after'); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['dir-after/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir), ['file-in-dir']); | ||
}, 'move(dir, name) to rename a non-empty directory'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); | ||
const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); | ||
const file = await createFileWithContents(t, 'file', 'abc', dir_src); | ||
await file.move(dir_dest); | ||
|
||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir_src), []); | ||
assert_array_equals(await getSortedDirectoryEntries(dir_dest), ['file']); | ||
assert_equals(await getFileContents(file), 'abc'); | ||
assert_equals(await getFileSize(file), 3); | ||
}, 'move(dir) to move a file to a new directory'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); | ||
const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); | ||
const file = await createFileWithContents(t, 'file', 'abc', dir_src); | ||
await file.move(dir_dest, ''); | ||
|
||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir_src), []); | ||
assert_array_equals(await getSortedDirectoryEntries(dir_dest), ['file']); | ||
assert_equals(await getFileContents(file), 'abc'); | ||
assert_equals(await getFileSize(file), 3); | ||
}, 'move(dir, "") to move a file to a new directory'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); | ||
const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); | ||
const file = | ||
await createFileWithContents(t, 'file-in-dir-src', 'abc', dir_src); | ||
await file.move(dir_dest, 'file-in-dir-dest'); | ||
|
||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir_src), []); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(dir_dest), ['file-in-dir-dest']); | ||
assert_equals(await getFileContents(file), 'abc'); | ||
assert_equals(await getFileSize(file), 3); | ||
}, 'move(dir, name) to move a file to a new directory'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); | ||
const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); | ||
const dir_in_dir = | ||
await dir_src.getDirectoryHandle('dir-in-dir', {create: true}); | ||
await dir_in_dir.move(dir_dest); | ||
|
||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir_src), []); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(dir_dest), ['dir-in-dir/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir_in_dir), []); | ||
}, 'move(dir) to move an empty directory to a new directory'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); | ||
const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); | ||
const dir_in_dir = | ||
await dir_src.getDirectoryHandle('dir-in-dir', {create: true}); | ||
await dir_in_dir.move(dir_dest, ""); | ||
|
||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir_src), []); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(dir_dest), ['dir-in-dir/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir_in_dir), []); | ||
}, 'move(dir, "") to move an empty directory to a new directory'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); | ||
const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); | ||
const dir_in_dir = | ||
await dir_src.getDirectoryHandle('dir-in-dir', {create: true}); | ||
await dir_in_dir.move(dir_dest, 'dir-in-dir'); | ||
|
||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir_src), []); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(dir_dest), ['dir-in-dir/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir_in_dir), []); | ||
}, 'move(dir, name) to move an empty directory to a new directory'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); | ||
const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); | ||
const dir_in_dir = | ||
await dir_src.getDirectoryHandle('dir-in-dir', {create: true}); | ||
const file = | ||
await createFileWithContents(t, 'file-in-dir', 'abc', dir_in_dir); | ||
await dir_in_dir.move(dir_dest, ""); | ||
|
||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir_src), []); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(dir_dest), ['dir-in-dir/']); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(dir_in_dir), ['file-in-dir']); | ||
// `file` should be invalidated after moving directories. | ||
await promise_rejects_dom(t, 'NotFoundError', getFileContents(file)); | ||
}, 'move(dir, "") to move a non-empty directory to a new directory'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); | ||
const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); | ||
const dir_in_dir = | ||
await dir_src.getDirectoryHandle('dir-in-dir', {create: true}); | ||
const file = | ||
await createFileWithContents(t, 'file-in-dir', 'abc', dir_in_dir); | ||
await dir_in_dir.move(dir_dest, 'dir-in-dir'); | ||
|
||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir_src), []); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(dir_dest), ['dir-in-dir/']); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(dir_in_dir), ['file-in-dir']); | ||
// `file` should be invalidated after moving directories. | ||
await promise_rejects_dom(t, 'NotFoundError', getFileContents(file)); | ||
}, 'move(dir, name) to move a non-empty directory to a new directory'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir1 = await root.getDirectoryHandle('dir1', {create: true}); | ||
const dir2 = await root.getDirectoryHandle('dir2', {create: true}); | ||
const handle = await createFileWithContents(t, 'file', 'foo', root); | ||
|
||
await handle.move(dir1); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir1/', 'dir2/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir1), ['file']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir2), []); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
|
||
await handle.move(dir2); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir1/', 'dir2/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir1), []); | ||
assert_array_equals(await getSortedDirectoryEntries(dir2), ['file']); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
|
||
await handle.move(root); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir1/', 'dir2/', 'file']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir1), []); | ||
assert_array_equals(await getSortedDirectoryEntries(dir2), []); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
}, 'move(dir) can be called multiple times'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir1 = await root.getDirectoryHandle('dir1', {create: true}); | ||
const dir2 = await root.getDirectoryHandle('dir2', {create: true}); | ||
const handle = await createFileWithContents(t, 'file', 'foo', root); | ||
|
||
await handle.move(dir1, ""); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir1/', 'dir2/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir1), ['file']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir2), []); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
|
||
await handle.move(dir2, ""); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir1/', 'dir2/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir1), []); | ||
assert_array_equals(await getSortedDirectoryEntries(dir2), ['file']); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
|
||
await handle.move(root, ""); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir1/', 'dir2/', 'file']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir1), []); | ||
assert_array_equals(await getSortedDirectoryEntries(dir2), []); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
}, 'move(dir, "") can be called multiple times'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir1 = await root.getDirectoryHandle('dir1', {create: true}); | ||
const dir2 = await root.getDirectoryHandle('dir2', {create: true}); | ||
const handle = await createFileWithContents(t, 'file', 'foo', root); | ||
|
||
await handle.move(dir1, 'file-1'); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir1/', 'dir2/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir1), ['file-1']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir2), []); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
|
||
await handle.move(dir2, 'file-2'); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir1/', 'dir2/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir1), []); | ||
assert_array_equals(await getSortedDirectoryEntries(dir2), ['file-2']); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
|
||
await handle.move(root, 'file-3'); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(root), ['dir1/', 'dir2/', 'file-3']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir1), []); | ||
assert_array_equals(await getSortedDirectoryEntries(dir2), []); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
}, 'move(dir, name) can be called multiple times'); | ||
|
||
directory_test(async (t, root) => { | ||
const handle = await createFileWithContents(t, 'file-before', 'foo', root); | ||
await promise_rejects_js( | ||
t, TypeError, handle.move(root, '#$23423@352^*3243')); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
assert_equals(await getFileSize(handle), 3); | ||
}, 'move(dir, name) with a name with invalid characters should fail'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir = await root.getDirectoryHandle('dir', {create: true}); | ||
await promise_rejects_dom( | ||
t, 'InvalidModificationError', dir.move(dir)); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['dir/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir), []); | ||
}, 'move(dir, name) to move a directory within itself fails'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir = await root.getDirectoryHandle('dir', {create: true}); | ||
await promise_rejects_dom( | ||
t, 'InvalidModificationError', dir.move(dir, 'dir-fail')); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['dir/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir), []); | ||
}, 'move(dir, name) to move a directory within itself and rename fails'); | ||
|
||
directory_test(async (t, root) => { | ||
const parent_dir = | ||
await root.getDirectoryHandle('parent-dir', {create: true}); | ||
const child_dir = | ||
await parent_dir.getDirectoryHandle('child-dir', {create: true}); | ||
await promise_rejects_dom( | ||
t, 'InvalidModificationError', parent_dir.move(child_dir)); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['parent-dir/']); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(parent_dir), ['child-dir/']); | ||
assert_array_equals(await getSortedDirectoryEntries(child_dir), []); | ||
}, 'move(dir) to move a directory within a descendent fails'); | ||
|
||
directory_test(async (t, root) => { | ||
const parent_dir = | ||
await root.getDirectoryHandle('parent-dir', {create: true}); | ||
const child_dir = | ||
await parent_dir.getDirectoryHandle('child-dir', {create: true}); | ||
await promise_rejects_dom( | ||
t, 'InvalidModificationError', parent_dir.move(child_dir, 'dir')); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['parent-dir/']); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(parent_dir), ['child-dir/']); | ||
assert_array_equals(await getSortedDirectoryEntries(child_dir), []); | ||
}, 'move(dir, name) to move a directory within a descendent fails'); |