-
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.
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
- Loading branch information
1 parent
37558f1
commit 59a7cf4
Showing
3 changed files
with
373 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 |
360 changes: 360 additions & 0 deletions
360
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,360 @@ | ||
// 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('file-after'); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['file-after']); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
assert_equals(await getFileSize(handle), 3); | ||
}, 'move(name) to rename a file'); | ||
|
||
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('file-before'); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
assert_equals(await getFileSize(handle), 3); | ||
}, 'move(name) to rename a file the same name'); | ||
|
||
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 handle = await createFileWithContents(t, 'file-before', 'foo', root); | ||
await promise_rejects_js(t, TypeError, handle.move('')); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
assert_equals(await getFileSize(handle), 3); | ||
}, 'move("") to rename a file fails'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir = await root.getDirectoryHandle('dir-before', {create: true}); | ||
await dir.move('dir-after'); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['dir-after/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir), []); | ||
}, 'move(name) to rename an empty directory'); | ||
|
||
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 promise_rejects_js(t, TypeError, dir.move('')); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['dir-before/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir), []); | ||
}, 'move("") to rename an empty directory fails'); | ||
|
||
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('dir-after'); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['dir-after/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir), ['file-in-dir']); | ||
}, 'move(name) to rename a non-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, null); | ||
|
||
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, null) 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, null); | ||
|
||
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, null) 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, null); | ||
|
||
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, null) 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 handle = await createFileWithContents(t, 'file-1', 'foo', root); | ||
|
||
await handle.move('file-2'); | ||
assert_array_equals(await getSortedDirectoryEntries(root), ['file-2']); | ||
|
||
await handle.move('file-3'); | ||
assert_array_equals(await getSortedDirectoryEntries(root), ['file-3']); | ||
|
||
await handle.move('file-1'); | ||
assert_array_equals(await getSortedDirectoryEntries(root), ['file-1']); | ||
}, 'move(name) 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, null); | ||
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, null); | ||
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, null); | ||
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, null) 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 dir = await root.getDirectoryHandle('dir', {create: true}); | ||
const handle = await createFileWithContents(t, 'file-before', 'foo', dir); | ||
await handle.move(root); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['dir/']); | ||
assert_array_equals( | ||
await getSortedDirectoryEntries(dir), | ||
['[object FileSystemDirectoryHandle]']); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
assert_equals(await getFileSize(handle), 3); | ||
}, 'move(dir) should rename to stringified dir object'); | ||
|
||
directory_test(async (t, root) => { | ||
const dir = await root.getDirectoryHandle('dir', {create: true}); | ||
const handle = await createFileWithContents(t, 'file-before', 'foo', dir); | ||
await promise_rejects_js(t, TypeError, handle.move('Lorem.')); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['dir/']); | ||
assert_array_equals(await getSortedDirectoryEntries(dir), ['file-before']); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
assert_equals(await getFileSize(handle), 3); | ||
}, 'move(name) with a name with a trailing period should fail'); | ||
|
||
directory_test(async (t, root) => { | ||
const handle = await createFileWithContents(t, 'file-before', 'foo', root); | ||
await promise_rejects_js(t, TypeError, handle.move('#$23423@352^*3243')); | ||
|
||
assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']); | ||
assert_equals(await getFileContents(handle), 'foo'); | ||
assert_equals(await getFileSize(handle), 3); | ||
}, 'move(name) with a name with invalid characters should fail'); | ||
|
||
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, '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, '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'); | ||
|
||
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-fail')); | ||
|
||
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 and rename fails'); |