-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[WasmFS] Initial OPFS/AccessHandles backend (#16813)
Add a backend that stores its underlying files in the Origin Private File System (OPFS) and reads and writes the files synchronously using `FileSystemSyncAccessHandle`. This initial implementation works correctly as long as there are no errors; better error handling and more robust edge case testing will come in a future PR.
- Loading branch information
Showing
14 changed files
with
744 additions
and
13 deletions.
There are no files selected for viewing
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
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,260 @@ | ||
/** | ||
* @license | ||
* Copyright 2022 The Emscripten Authors | ||
* SPDX-License-Identifier: MIT | ||
*/ | ||
|
||
mergeInto(LibraryManager.library, { | ||
// TODO: Generate these ID pools from a common utility. | ||
$wasmfsOPFSDirectories: { | ||
allocated: [], | ||
free: [], | ||
get: function(i) { | ||
assert(this.allocated[i] !== undefined); | ||
return this.allocated[i]; | ||
} | ||
}, | ||
|
||
$wasmfsOPFSFiles: { | ||
allocated: [], | ||
free: [], | ||
get: function(i) { | ||
assert(this.allocated[i] !== undefined); | ||
return this.allocated[i]; | ||
} | ||
}, | ||
|
||
$wasmfsOPFSAccesses: { | ||
allocated: [], | ||
free: [], | ||
get: function(i) { | ||
assert(this.allocated[i] !== undefined); | ||
return this.allocated[i]; | ||
} | ||
}, | ||
|
||
$wasmfsOPFSAllocate: function(ids, handle) { | ||
let id; | ||
if (ids.free.length > 0) { | ||
id = ids.free.pop(); | ||
ids.allocated[id] = handle; | ||
} else { | ||
id = ids.allocated.length; | ||
ids.allocated.push(handle); | ||
} | ||
return id; | ||
}, | ||
|
||
$wasmfsOPFSFree: function(ids, id) { | ||
delete ids.allocated[id]; | ||
ids.free.push(id); | ||
}, | ||
|
||
_wasmfs_opfs_init_root_directory__deps: ['$wasmfsOPFSDirectories'], | ||
_wasmfs_opfs_init_root_directory: async function(ctx) { | ||
if (wasmfsOPFSDirectories.allocated.length == 0) { | ||
// Directory 0 is reserved as the root | ||
let root = await navigator.storage.getDirectory(); | ||
wasmfsOPFSDirectories.allocated.push(root); | ||
} | ||
_emscripten_proxy_finish(ctx); | ||
}, | ||
|
||
// Return the file ID for the file with `name` under `parent`, creating it if | ||
// it doesn't exist and `create` or otherwise return one of the following | ||
// error codes: | ||
// | ||
// -1: file does not exist. | ||
// -2: file exists but it is actually a directory. | ||
// -3: file exists but an access handle cannot be created for it. | ||
$wasmfsOPFSGetOrCreateFile__deps: ['$wasmfsOPFSAllocate', | ||
'$wasmfsOPFSDirectories', | ||
'$wasmfsOPFSFiles'], | ||
$wasmfsOPFSGetOrCreateFile: async function(parent, name, create) { | ||
let parentHandle = wasmfsOPFSDirectories.get(parent); | ||
let fileHandle; | ||
try { | ||
fileHandle = await parentHandle.getFileHandle(name, {create: create}); | ||
} catch (e) { | ||
if (e.name === "NotFoundError") { | ||
return -1; | ||
} | ||
if (e.name === "TypeMismatchError") { | ||
return -2; | ||
} | ||
throw e; | ||
} | ||
return wasmfsOPFSAllocate(wasmfsOPFSFiles, fileHandle); | ||
}, | ||
|
||
// Return the file ID for the directory with `name` under `parent`, creating | ||
// it if it doesn't exist and `create` or otherwise one of the following error | ||
// codes: | ||
// | ||
// -1: directory does not exist. | ||
// -2: directory exists but is actually a data file. | ||
$wasmfsOPFSGetOrCreateDir__deps: ['$wasmfsOPFSAllocate', | ||
'$wasmfsOPFSDirectories'], | ||
$wasmfsOPFSGetOrCreateDir: async function(parent, name, create) { | ||
let parentHandle = wasmfsOPFSDirectories.get(parent); | ||
let childHandle; | ||
try { | ||
childHandle = | ||
await parentHandle.getDirectoryHandle(name, {create: create}); | ||
} catch (e) { | ||
if (e.name === "NotFoundError") { | ||
return -1; | ||
} | ||
if (e.name === "TypeMismatchError") { | ||
return -2; | ||
} | ||
throw e; | ||
} | ||
return wasmfsOPFSAllocate(wasmfsOPFSDirectories, childHandle); | ||
}, | ||
|
||
_wasmfs_opfs_get_child__deps: ['$wasmfsOPFSGetOrCreateFile', | ||
'$wasmfsOPFSGetOrCreateDir'], | ||
_wasmfs_opfs_get_child: | ||
async function(ctx, parent, namePtr, childTypePtr, childIDPtr) { | ||
let name = UTF8ToString(namePtr); | ||
let childType = 1; | ||
let childID = await wasmfsOPFSGetOrCreateFile(parent, name, false); | ||
if (childID == -2) { | ||
childType = 2; | ||
childID = await wasmfsOPFSGetOrCreateDir(parent, name, false); | ||
} | ||
{{{ makeSetValue('childTypePtr', 0, 'childType', 'i32') }}}; | ||
{{{ makeSetValue('childIDPtr', 0, 'childID', 'i32') }}}; | ||
_emscripten_proxy_finish(ctx); | ||
}, | ||
|
||
_wasmfs_opfs_get_entries__deps: [], | ||
_wasmfs_opfs_get_entries: async function(ctx, dirID, entries) { | ||
let dirHandle = wasmfsOPFSDirectories.get(dirID); | ||
for await (let [name, child] of dirHandle.entries()) { | ||
withStackSave(() => { | ||
let namePtr = allocateUTF8OnStack(name); | ||
let type = child.kind == "file" ? | ||
{{{ cDefine('File::DataFileKind') }}} : | ||
{{{ cDefine('File::DirectoryKind') }}}; | ||
__wasmfs_opfs_record_entry(entries, namePtr, type) | ||
}); | ||
} | ||
_emscripten_proxy_finish(ctx); | ||
}, | ||
|
||
_wasmfs_opfs_insert_file__deps: ['$wasmfsOPFSGetOrCreateFile'], | ||
_wasmfs_opfs_insert_file: async function(ctx, parent, namePtr, childIDPtr) { | ||
let name = UTF8ToString(namePtr); | ||
let childID = await wasmfsOPFSGetOrCreateFile(parent, name, true); | ||
{{{ makeSetValue('childIDPtr', 0, 'childID', 'i32') }}}; | ||
_emscripten_proxy_finish(ctx); | ||
}, | ||
|
||
_wasmfs_opfs_insert_directory__deps: ['$wasmfsOPFSGetOrCreateDir'], | ||
_wasmfs_opfs_insert_directory: async function(ctx, parent, namePtr, childIDPtr) { | ||
let name = UTF8ToString(namePtr); | ||
let childID = await wasmfsOPFSGetOrCreateDir(parent, name, true); | ||
{{{ makeSetValue('childIDPtr', 0, 'childID', 'i32') }}}; | ||
_emscripten_proxy_finish(ctx); | ||
}, | ||
|
||
_wasmfs_opfs_move__deps: ['$wasmfsOPFSFiles', '$wasmfsOPFSDirectories'], | ||
_wasmfs_opfs_move: async function(ctx, fileID, newDirID, namePtr) { | ||
let name = UTF8ToString(namePtr); | ||
let fileHandle = wasmfsOPFSFiles.get(fileID); | ||
let newDirHandle = wasmfsOPFSDirectories.get(newDirID); | ||
await fileHandle.move(newDirHandle, name); | ||
_emscripten_proxy_finish(ctx); | ||
}, | ||
|
||
_wasmfs_opfs_remove_child__deps: ['$wasmfsOPFSFree', '$wasmfsOPFSDirectories'], | ||
_wasmfs_opfs_remove_child: async function(ctx, dirID, namePtr) { | ||
let name = UTF8ToString(namePtr); | ||
let dirHandle = wasmfsOPFSDirectories.get(dirID); | ||
await dirHandle.removeEntry(name); | ||
_emscripten_proxy_finish(ctx); | ||
}, | ||
|
||
_wasmfs_opfs_free_file__deps: ['$wasmfsOPFSFree', '$wasmfsOPFSFiles'], | ||
_wasmfs_opfs_free_file: function(fileID) { | ||
wasmfsOPFSFree(wasmfsOPFSFiles, fileID); | ||
}, | ||
|
||
_wasmfs_opfs_free_directory__deps: ['$wasmfsOPFSFree', | ||
'$wasmfsOPFSDirectories'], | ||
_wasmfs_opfs_free_directory: function(dirID) { | ||
wasmfsOPFSFree(wasmfsOPFSDirectories, dirID); | ||
}, | ||
|
||
_wasmfs_opfs_open__deps: ['$wasmfsOPFSAllocate', | ||
'$wasmfsOPFSFiles', | ||
'$wasmfsOPFSAccesses'], | ||
_wasmfs_opfs_open: async function(ctx, fileID, accessIDPtr) { | ||
let fileHandle = wasmfsOPFSFiles.get(fileID); | ||
let accessID; | ||
try { | ||
let accessHandle; | ||
// TODO: Remove this once the Access Handles API has settled. | ||
if (FileSystemFileHandle.prototype.createSyncAccessHandle.length == 0) { | ||
accessHandle = await fileHandle.createSyncAccessHandle(); | ||
} else { | ||
accessHandle = await fileHandle.createSyncAccessHandle( | ||
{mode: "in-place"}); | ||
} | ||
accessID = wasmfsOPFSAllocate(wasmfsOPFSAccesses, accessHandle); | ||
} catch (e) { | ||
if (e.name === "InvalidStateError") { | ||
accessID = -1; | ||
} | ||
throw e; | ||
} | ||
{{{ makeSetValue('accessIDPtr', 0, 'accessID', 'i32') }}}; | ||
_emscripten_proxy_finish(ctx); | ||
}, | ||
|
||
_wasmfs_opfs_close__deps: ['$wasmfsOPFSFree', '$wasmfsOPFSAccesses'], | ||
_wasmfs_opfs_close: async function(ctx, accessID) { | ||
let accessHandle = wasmfsOPFSAccesses.get(accessID); | ||
await accessHandle.close(); | ||
wasmfsOPFSFree(wasmfsOPFSAccesses, accessID); | ||
_emscripten_proxy_finish(ctx); | ||
}, | ||
|
||
_wasmfs_opfs_read__deps: ['$wasmfsOPFSAccesses'], | ||
_wasmfs_opfs_read: function(accessID, bufPtr, len, pos) { | ||
let accessHandle = wasmfsOPFSAccesses.get(accessID); | ||
let data = HEAPU8.subarray(bufPtr, bufPtr + len); | ||
return accessHandle.read(data, {at: pos}); | ||
}, | ||
|
||
_wasmfs_opfs_write__deps: ['$wasmfsOPFSAccesses'], | ||
_wasmfs_opfs_write: function(accessID, bufPtr, len, pos, nwrittenPtr) { | ||
let accessHandle = wasmfsOPFSAccesses.get(accessID); | ||
let data = HEAPU8.subarray(bufPtr, bufPtr + len); | ||
return accessHandle.write(data, {at: pos}); | ||
}, | ||
|
||
_wasmfs_opfs_get_size__deps: ['$wasmfsOPFSAccesses'], | ||
_wasmfs_opfs_get_size: async function(ctx, accessID, sizePtr) { | ||
let accessHandle = wasmfsOPFSAccesses.get(accessID); | ||
let size = await accessHandle.getSize(); | ||
{{{ makeSetValue('sizePtr', 0, 'size', 'i32') }}}; | ||
_emscripten_proxy_finish(ctx); | ||
}, | ||
|
||
_wasmfs_opfs_set_size__deps: ['$wasmfsOPFSAccesses'], | ||
_wasmfs_opfs_set_size: async function(ctx, accessID, size) { | ||
let accessHandle = wasmfsOPFSAccesses.get(accessID); | ||
await accessHandle.truncate(size); | ||
_emscripten_proxy_finish(ctx); | ||
}, | ||
|
||
_wasmfs_opfs_flush__deps: ['$wasmfsOPFSAccesses'], | ||
_wasmfs_opfs_flush: async function(ctx, accessID) { | ||
let accessHandle = wasmfsOPFSAccesses.get(accessID); | ||
await accessHandle.flush(); | ||
_emscripten_proxy_finish(ctx); | ||
} | ||
}); |
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
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
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
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
Oops, something went wrong.