aboutsummaryrefslogtreecommitdiff
path: root/ext/wasm/api
diff options
context:
space:
mode:
Diffstat (limited to 'ext/wasm/api')
-rw-r--r--ext/wasm/api/sqlite3-api-oo1.js25
-rw-r--r--ext/wasm/api/sqlite3-api-opfs.js238
-rw-r--r--ext/wasm/api/sqlite3-api-prologue.js21
-rw-r--r--ext/wasm/api/sqlite3-api-worker1.js23
-rw-r--r--ext/wasm/api/sqlite3-opfs-async-proxy.js1
5 files changed, 257 insertions, 51 deletions
diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js
index e318b7200..aaaeaa3d5 100644
--- a/ext/wasm/api/sqlite3-api-oo1.js
+++ b/ext/wasm/api/sqlite3-api-oo1.js
@@ -258,9 +258,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
in the form of a single configuration object with the following
properties:
- - `.filename`: database file name
- - `.flags`: open-mode flags
- - `.vfs`: the VFS fname
+ - `filename`: database file name
+ - `flags`: open-mode flags
+ - `vfs`: the VFS fname
The `filename` and `vfs` arguments may be either JS strings or
C-strings allocated via WASM. `flags` is required to be a JS
@@ -561,6 +561,25 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return capi.sqlite3_db_name(affirmDbOpen(this).pointer, dbNumber);
},
/**
+ Returns the name of the sqlite3_vfs used by the given database
+ of this connection (defaulting to 'main'). The argument may be
+ either a JS string or a WASM C-string. Returns undefined if the
+ given db name is invalid. Throws if this object has been
+ close()d.
+ */
+ dbVfsName: function(dbName=0){
+ let rc;
+ const pVfs = capi.sqlite3_js_db_vfs(
+ affirmDbOpen(this).pointer, dbName
+ );
+ if(pVfs){
+ const v = new capi.sqlite3_vfs(pVfs);
+ try{ rc = wasm.cstringToJs(v.$zName) }
+ finally { v.dispose() }
+ }
+ return rc;
+ },
+ /**
Compiles the given SQL and returns a prepared Stmt. This is
the only way to create new Stmt objects. Throws on error.
diff --git a/ext/wasm/api/sqlite3-api-opfs.js b/ext/wasm/api/sqlite3-api-opfs.js
index 1a89432c5..90337d527 100644
--- a/ext/wasm/api/sqlite3-api-opfs.js
+++ b/ext/wasm/api/sqlite3-api-opfs.js
@@ -371,6 +371,20 @@ const installOpfsVfs = function callee(options){
return rc;
};
+ /**
+ Not part of the public API. Only for test/development use.
+ */
+ opfsUtil.debug = {
+ asyncShutdown: ()=>{
+ warn("Shutting down OPFS async listener. The OPFS VFS will no longer work.");
+ opRun('opfs-async-shutdown');
+ },
+ asyncRestart: ()=>{
+ warn("Attempting to restart OPFS VFS async listener. Might work, might not.");
+ W.postMessage({type: 'opfs-async-restart'});
+ }
+ };
+
const initS11n = ()=>{
/**
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@@ -876,37 +890,61 @@ const installOpfsVfs = function callee(options){
}
/**
- Syncronously deletes the given OPFS filesystem entry, ignoring
- any errors. As this environment has no notion of "current
- directory", the given name must be an absolute path. If the 2nd
- argument is truthy, deletion is recursive (use with caution!).
+ Expects an OPFS file path. It gets resolved, such that ".."
+ components are properly expanded, and returned. If the 2nd arg
+ is true, the result is returned as an array of path elements,
+ else an absolute path string is returned.
+ */
+ opfsUtil.getResolvedPath = function(filename,splitIt){
+ const p = new URL(filename, "file://irrelevant").pathname;
+ return splitIt ? p.split('/').filter((v)=>!!v) : p;
+ };
- Returns true if the deletion succeeded and false if it fails,
- but cannot report the nature of the failure.
+ /**
+ Takes the absolute path to a filesystem element. Returns an
+ array of [handleOfContainingDir, filename]. If the 2nd argument
+ is truthy then each directory element leading to the file is
+ created along the way. Throws if any creation or resolution
+ fails.
*/
- opfsUtil.deleteEntry = function(fsEntryName,recursive=false){
- mTimeStart('xDelete');
- const rc = opRun('xDelete', fsEntryName, 0, recursive);
- mTimeEnd();
- return 0===rc;
+ opfsUtil.getDirForFilename = async function f(absFilename, createDirs = false){
+ const path = opfsUtil.getResolvedPath(absFilename, true);
+ const filename = path.pop();
+ let dh = opfsUtil.rootDirectory;
+ for(const dirName of path){
+ if(dirName){
+ dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs});
+ }
+ }
+ return [dh, filename];
};
+
/**
- Synchronously creates the given directory name, recursively, in
+ Creates the given directory name, recursively, in
the OPFS filesystem. Returns true if it succeeds or the
directory already exists, else false.
*/
- opfsUtil.mkdir = function(absDirName){
- mTimeStart('mkdir');
- const rc = opRun('mkdir', absDirName);
- mTimeEnd();
- return 0===rc;
+ opfsUtil.mkdir = async function(absDirName){
+ try {
+ await opfsUtil.getDirForFilename(absDirName+"/filepart", true);
+ return true;
+ }catch(e){
+ //console.warn("mkdir(",absDirName,") failed:",e);
+ return false;
+ }
};
/**
- Synchronously checks whether the given OPFS filesystem exists,
+ Checks whether the given OPFS filesystem entry exists,
returning true if it does, false if it doesn't.
*/
- opfsUtil.entryExists = function(fsEntryName){
- return 0===opRun('xAccess', fsEntryName);
+ opfsUtil.entryExists = async function(fsEntryName){
+ try {
+ const [dh, fn] = await opfsUtil.getDirForFilename(filename);
+ await dh.getFileHandle(fn);
+ return true;
+ }catch(e){
+ return false;
+ }
};
/**
@@ -938,20 +976,160 @@ const installOpfsVfs = function callee(options){
};
/**
- Only for test/development use.
+ Returns a promise which resolves to an object which represents
+ all files and directories in the OPFS tree. The top-most object
+ has two properties: `dirs` is an array of directory entries
+ (described below) and `files` is a list of file names for all
+ files in that directory.
+
+ Traversal starts at sqlite3.opfs.rootDirectory.
+
+ Each `dirs` entry is an object in this form:
+
+ ```
+ { name: directoryName,
+ dirs: [...subdirs],
+ files: [...file names]
+ }
+ ```
+
+ The `files` and `subdirs` entries are always set but may be
+ empty arrays.
+
+ The returned object has the same structure but its `name` is
+ an empty string. All returned objects are created with
+ Object.create(null), so have no prototype.
+
+ Design note: the entries do not contain more information,
+ e.g. file sizes, because getting such info is not only
+ expensive but is subject to locking-related errors.
*/
- opfsUtil.debug = {
- asyncShutdown: ()=>{
- warn("Shutting down OPFS async listener. OPFS will no longer work.");
- opRun('opfs-async-shutdown');
- },
- asyncRestart: ()=>{
- warn("Attempting to restart OPFS async listener. Might work, might not.");
- W.postMessage({type: 'opfs-async-restart'});
+ opfsUtil.treeList = async function(){
+ const doDir = async function callee(dirHandle,tgt){
+ tgt.name = dirHandle.name;
+ tgt.dirs = [];
+ tgt.files = [];
+ for await (const handle of dirHandle.values()){
+ if('directory' === handle.kind){
+ const subDir = Object.create(null);
+ tgt.dirs.push(subDir);
+ await callee(handle, subDir);
+ }else{
+ tgt.files.push(handle.name);
+ }
+ }
+ };
+ const root = Object.create(null);
+ await doDir(opfsUtil.rootDirectory, root);
+ return root;
+ };
+
+ /**
+ Irrevocably deletes _all_ files in the current origin's OPFS.
+ Obviously, this must be used with great caution. It may throw
+ an exception if removal of anything fails (e.g. a file is
+ locked), but the precise conditions under which it will throw
+ are not documented (so we cannot tell you what they are).
+ */
+ opfsUtil.rmfr = async function(){
+ const dir = opfsUtil.rootDirectory, opt = {recurse: true};
+ for await (const handle of dir.values()){
+ dir.removeEntry(handle.name, opt);
+ }
+ };
+
+ /**
+ Deletes the given OPFS filesystem entry. As this environment
+ has no notion of "current directory", the given name must be an
+ absolute path. If the 2nd argument is truthy, deletion is
+ recursive (use with caution!).
+
+ The returned Promise resolves to true if the deletion was
+ successful, else false (but...). The OPFS API reports the
+ reason for the failure only in human-readable form, not
+ exceptions which can be type-checked to determine the
+ failure. Because of that...
+
+ If the final argument is truthy then this function will
+ propagate any exception on error, rather than returning false.
+ */
+ opfsUtil.unlink = async function(fsEntryName, recursive = false,
+ throwOnError = false){
+ try {
+ const [hDir, filenamePart] =
+ await opfsUtil.getDirForFilename(fsEntryName, false);
+ await hDir.removeEntry(filenamePart, {recursive});
+ return true;
+ }catch(e){
+ if(throwOnError){
+ throw new Error("unlink(",arguments[0],") failed: "+e.message,{
+ cause: e
+ });
+ }
+ return false;
}
};
- //TODO to support fiddle db upload:
+ /**
+ Traverses the OPFS filesystem, calling a callback for each one.
+ The argument may be either a callback function or an options object
+ with any of the following properties:
+
+ - `callback`: function which gets called for each filesystem
+ entry. It gets passed 3 arguments: 1) the
+ FileSystemFileHandle or FileSystemDirectoryHandle of each
+ entry (noting that both are instanceof FileSystemHandle). 2)
+ the FileSystemDirectoryHandle of the parent directory. 3) the
+ current depth level, with 0 being at the top of the tree
+ relative to the starting directory. If the callback returns a
+ literal false, as opposed to any other falsy value, traversal
+ stops without an error. Any exceptions it throws are
+ propagated. Results are undefined if the callback manipulate
+ the filesystem (e.g. removing or adding entries) because the
+ how OPFS iterators behave in the face of such changes is
+ undocumented.
+
+ - `recursive` [bool=true]: specifies whether to recurse into
+ subdirectories or not. Whether recursion is depth-first or
+ breadth-first is unspecified!
+
+ - `directory` [FileSystemDirectoryEntry=sqlite3.opfs.rootDirectory]
+ specifies the starting directory.
+
+ If this function is passed a function, it is assumed to be the
+ callback.
+
+ Returns a promise because it has to (by virtue of being async)
+ but that promise has no specific meaning: the traversal it
+ performs is synchronous. The promise must be used to catch any
+ exceptions propagated by the callback, however.
+
+ TODO: add an option which specifies whether to traverse
+ depth-first or breadth-first. We currently do depth-first but
+ an incremental file browsing widget would benefit more from
+ breadth-first.
+ */
+ opfsUtil.traverse = async function(opt){
+ const defaultOpt = {
+ recursive: true,
+ directory: opfsUtil.rootDirectory
+ };
+ if('function'===typeof opt){
+ opt = {callback:opt};
+ }
+ opt = Object.assign(defaultOpt, opt||{});
+ const doDir = async function callee(dirHandle, depth){
+ for await (const handle of dirHandle.values()){
+ if(false === opt.callback(handle, dirHandle, depth)) return false;
+ else if(opt.recursive && 'directory' === handle.kind){
+ if(false === await callee(handle, depth + 1)) break;
+ }
+ }
+ };
+ doDir(opt.directory, 0);
+ };
+
+ //TODO to support fiddle and worker1 db upload:
//opfsUtil.createFile = function(absName, content=undefined){...}
if(sqlite3.oo1){
diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js
index a5ff0c40a..ca6fad8f8 100644
--- a/ext/wasm/api/sqlite3-api-prologue.js
+++ b/ext/wasm/api/sqlite3-api-prologue.js
@@ -1187,14 +1187,15 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
}
/**
- Given an `sqlite3*`, an sqlite3_vfs name, and an optional db
- name, returns a truthy value (see below) if that db handle uses
- that VFS, else returns false. If pDb is falsy then the 3rd
- argument is ignored and this function returns a truthy value if
- the default VFS name matches that of the 2nd argument. Results
- are undefined if pDb is truthy but refers to an invalid
- pointer. The 3rd argument specifies the database name of the
- given database connection to check, defaulting to the main db.
+ Given an `sqlite3*`, an sqlite3_vfs name, and an optional db name
+ (defaulting to "main"), returns a truthy value (see below) if
+ that db uses that VFS, else returns false. If pDb is falsy then
+ the 3rd argument is ignored and this function returns a truthy
+ value if the default VFS name matches that of the 2nd
+ argument. Results are undefined if pDb is truthy but refers to an
+ invalid pointer. The 3rd argument specifies the database name of
+ the given database connection to check, defaulting to the main
+ db.
The 2nd and 3rd arguments may either be a JS string or a WASM
C-string. If the 2nd argument is a NULL WASM pointer, the default
@@ -1209,14 +1210,14 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
bad arguments cause a conversion error when passing into
wasm-space, false is returned.
*/
- capi.sqlite3_js_db_uses_vfs = function(pDb,vfsName,dbName="main"){
+ capi.sqlite3_js_db_uses_vfs = function(pDb,vfsName,dbName=0){
try{
const pK = capi.sqlite3_vfs_find(vfsName);
if(!pK) return false;
else if(!pDb){
return pK===capi.sqlite3_vfs_find(0) ? pK : false;
}else{
- return pK===capi.sqlite3_js_db_vfs(pDb) ? pK : false;
+ return pK===capi.sqlite3_js_db_vfs(pDb,dbName) ? pK : false;
}
}catch(e){
/* Ignore - probably bad args to a wasm-bound function. */
diff --git a/ext/wasm/api/sqlite3-api-worker1.js b/ext/wasm/api/sqlite3-api-worker1.js
index aa438a52d..532d61b67 100644
--- a/ext/wasm/api/sqlite3-api-worker1.js
+++ b/ext/wasm/api/sqlite3-api-worker1.js
@@ -188,6 +188,8 @@
See the sqlite3.oo1.DB constructor for peculiarities and
transformations,
+ vfs: sqlite3_vfs name. Ignored if filename is ":memory:" or "".
+ This may change how the given filename is resolved.
}
}
```
@@ -212,6 +214,7 @@
persistent: true if the given filename resides in the
known-persistent storage, else false.
+ vfs: name of the VFS the "main" db is using.
}
}
```
@@ -362,7 +365,7 @@ sqlite3.initWorker1API = function(){
/** Temp holder for "transferable" postMessage() state. */
xfer: [],
open: function(opt){
- const db = new DB(opt.filename);
+ const db = new DB(opt);
this.dbs[getDbId(db)] = db;
if(this.dbList.indexOf(db)<0) this.dbList.push(db);
return db;
@@ -442,12 +445,14 @@ sqlite3.initWorker1API = function(){
oargs.filename = args.filename || '';
}else{
oargs.filename = args.filename;
+ oargs.vfs = args.vfs;
}
const db = wState.open(oargs);
rc.filename = db.filename;
rc.persistent = (!!pDir && db.filename.startsWith(pDir+'/'))
|| !!sqlite3.capi.sqlite3_js_db_uses_vfs(db.pointer, "opfs");
rc.dbId = getDbId(db);
+ rc.vfs = db.dbVfsName();
return rc;
},
@@ -528,6 +533,7 @@ sqlite3.initWorker1API = function(){
rc.wasmfsOpfsEnabled = !!sqlite3.capi.sqlite3_wasmfs_opfs_dir();
rc.version = sqlite3.version;
rc.vfsList = sqlite3.capi.sqlite3_js_vfs_list();
+ rc.opfsEnabled = !!sqlite3.opfs;
return rc;
},
@@ -542,11 +548,6 @@ sqlite3.initWorker1API = function(){
}
*/
export: function(ev){
- /**
- We need to reimplement this to use the Emscripten FS
- interface. That part used to be in the OO#1 API but that
- dependency was removed from that level of the API.
- */
const db = getMsgDb(ev);
const response = {
bytearray: sqlite3.capi.sqlite3_js_db_export(db.pointer),
@@ -559,17 +560,23 @@ sqlite3.initWorker1API = function(){
toss: function(ev){
toss("Testing worker exception");
+ },
+
+ 'opfs-tree': async function(ev){
+ if(!sqlite3.opfs) toss("OPFS support is unavailable.");
+ const response = await sqlite3.opfs.treeList();
+ return response;
}
}/*wMsgHandler*/;
- self.onmessage = function(ev){
+ self.onmessage = async function(ev){
ev = ev.data;
let result, dbId = ev.dbId, evType = ev.type;
const arrivalTime = performance.now();
try {
if(wMsgHandler.hasOwnProperty(evType) &&
wMsgHandler[evType] instanceof Function){
- result = wMsgHandler[evType](ev);
+ result = await wMsgHandler[evType](ev);
}else{
toss("Unknown db worker message type:",ev.type);
}
diff --git a/ext/wasm/api/sqlite3-opfs-async-proxy.js b/ext/wasm/api/sqlite3-opfs-async-proxy.js
index 71c822c1f..3aa20fa63 100644
--- a/ext/wasm/api/sqlite3-opfs-async-proxy.js
+++ b/ext/wasm/api/sqlite3-opfs-async-proxy.js
@@ -377,6 +377,7 @@ const vfsAsyncImpls = {
if(!filenamePart) break;
await hDir.removeEntry(filenamePart, {recursive});
if(0x1234 !== syncDir) break;
+ recursive = false;
filename = getResolvedPath(filename, true);
filename.pop();
filename = filename.join('/');