From: stephan Date: Sat, 17 Sep 2022 23:29:27 +0000 (+0000) Subject: Implement OPFS xAccess(), albeit with more limited semantics than the VFS API calls... X-Git-Tag: version-3.40.0~169^2~105 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=8200a6d8d7685ab252667768a4c291f1f0e9993c;p=thirdparty%2Fsqlite.git Implement OPFS xAccess(), albeit with more limited semantics than the VFS API calls for. Add a way for OPFS xDelete() to optionally recursively remove empty dirs left over after deleting a file. FossilOrigin-Name: c342b5d745f301104c59851c753287ebbbe95a69a56cb522d376d0f3e352c30f --- diff --git a/ext/wasm/sqlite3-opfs-async-proxy.js b/ext/wasm/sqlite3-opfs-async-proxy.js index 1ff9397027..4a60e8aa1a 100644 --- a/ext/wasm/sqlite3-opfs-async-proxy.js +++ b/ext/wasm/sqlite3-opfs-async-proxy.js @@ -72,9 +72,17 @@ warn("This file is very much experimental and under construction.",self.location const __openFiles = Object.create(null); /** - Map of dir names to FileSystemDirectoryHandle objects. + Expects an OPFS file path. It gets resolved, such that ".." + components are properly expanded, and returned. If the 2nd + are is true, it's returned as an array of path elements, + else it's returned as an absolute path string. */ -const __dirCache = new Map; +const getResolvedPath = function(filename,splitIt){ + const p = new URL( + filename, 'file://irrelevant' + ).pathname; + return splitIt ? p.split('/').filter((v)=>!!v) : p; +} /** Takes the absolute path to a filesystem element. Returns an array @@ -83,21 +91,13 @@ const __dirCache = new Map; along the way. Throws if any creation or resolution fails. */ const getDirForPath = async function f(absFilename, createDirs = false){ - const url = new URL( - absFilename, 'file://xyz' - ) /* use URL to resolve path pieces such as a/../b */; - const path = url.pathname.split('/').filter((v)=>!!v); + const path = getResolvedPath(absFilename, true); const filename = path.pop(); - const allDirs = '/'+path.join('/'); - let dh = __dirCache.get(allDirs); - if(!dh){ - dh = state.rootDir; - for(const dirName of path){ - if(dirName){ - dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs}); - } + let dh = state.rootDir; + for(const dirName of path){ + if(dirName){ + dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs}); } - __dirCache.set(allDirs, dh); } return [dh, filename]; }; @@ -113,14 +113,6 @@ const storeAndNotify = (opName, value)=>{ Atomics.notify(state.opSABView, state.opIds[opName]); }; -const isInt32 = function(n){ - return ('bigint'!==typeof n /*TypeError: can't convert BigInt to number*/) - && !!(n===(n|0) && n<=2147483647 && n>=-2147483648); -}; -const affirm32Bits = function(n){ - return isInt32(n) || toss("Number is too large (>31 bits) (FIXME!):",n); -}; - /** Throws if fh is a file-holding object which is flagged as read-only. */ @@ -134,9 +126,26 @@ const affirmNotRO = function(opName,fh){ to simplify finding them. */ const vfsAsyncImpls = { - xAccess: async function({filename, exists, readWrite}){ - warn("xAccess(",arguments[0],") is TODO"); - const rc = state.sq3Codes.SQLITE_IOERR; + xAccess: async function(filename){ + log("xAccess(",arguments[0],")"); + /* OPFS cannot support the full range of xAccess() queries sqlite3 + calls for. We can essentially just tell if the file is + accessible, but if it is it's automatically writable (unless + it's locked, which we cannot(?) know without trying to open + it). OPFS does not have the notion of read-only. + + The return semantics of this function differ from sqlite3's + xAccess semantics because we are limited in what we can + communicate back to our synchronous communication partner: 0 = + accessible, non-0 means not accessible. + */ + let rc = 0; + try{ + const [dh, fn] = await getDirForPath(filename); + await dh.getFileHandle(fn); + }catch(e){ + rc = state.sq3Codes.SQLITE_IOERR; + } storeAndNotify('xAccess', rc); }, xClose: async function(fid){ @@ -156,12 +165,34 @@ const vfsAsyncImpls = { } }, xDelete: async function({filename, syncDir/*ignored*/}){ + /* The syncDir flag is, for purposes of the VFS API's semantics, + ignored here. However, if it has the value 0x1234 then: after + deleting the given file, recursively try to delete any empty + directories left behind in its wake (ignoring any errors and + stopping at the first failure). + + That said: we don't know for sure that removeEntry() fails if + the dir is not empty because the API is not documented. It has, + however, a "recursive" flag which defaults to false, so + presumably it will fail if the dir is not empty and that flag + is false. + */ log("xDelete(",arguments[0],")"); try { - const [hDir, filenamePart] = await getDirForPath(filename, false); - await hDir.removeEntry(filenamePart); + while(filename){ + const [hDir, filenamePart] = await getDirForPath(filename, false); + //log("Removing:",hDir, filenamePart); + if(!filenamePart) break; + await hDir.removeEntry(filenamePart); + if(0x1234 !== syncDir) break; + filename = getResolvedPath(filename, true); + filename.pop(); + filename = filename.join('/'); + } }catch(e){ - /* Ignoring: _presumably_ the file can't be found. */ + /* Ignoring: _presumably_ the file can't be found or a dir is + not empty. */ + //error("Delete failed",filename, e.message); } storeAndNotify('xDelete', 0); }, @@ -268,8 +299,8 @@ const vfsAsyncImpls = { xWrite: async function({fid,src,n,offset}){ log("xWrite(",arguments[0],")"); let rc; + const fh = __openFiles[fid]; try{ - const fh = __openFiles[fid]; affirmNotRO('xWrite', fh); const nOut = fh.accessHandle.write(new UInt8Array(fh.sab, 0, n), {at: offset}); rc = (nOut===n) ? 0 : state.sq3Codes.SQLITE_IOERR_WRITE; @@ -324,4 +355,4 @@ navigator.storage.getDirectory().then(function(d){ } }; wMsg('loaded'); -}); +}).catch((e)=>error(e)); diff --git a/ext/wasm/x-sync-async.js b/ext/wasm/x-sync-async.js index 9329e765ac..250fde349f 100644 --- a/ext/wasm/x-sync-async.js +++ b/ext/wasm/x-sync-async.js @@ -47,13 +47,14 @@ const initOpfsVfs = function(sqlite3){ warn("This file is very much experimental and under construction.",self.location.pathname); if(self.window===self || - !self.SharedBufferArray || + !self.SharedArrayBuffer || !self.FileSystemHandle || !self.FileSystemDirectoryHandle || !self.FileSystemFileHandle || !self.FileSystemFileHandle.prototype.createSyncAccessHandle || !navigator.storage.getDirectory){ warn("This environment does not have OPFS support."); + return; } const capi = sqlite3.capi; @@ -282,7 +283,11 @@ const initOpfsVfs = function(sqlite3){ are in alphabetical order to simplify finding them. */ const vfsSyncWrappers = { - // TODO: xAccess + xAccess: function(pVfs,zName,flags,pOut){ + const rc = opRun('xAccess', wasm.cstringToJs(zName)); + wasm.setMemValue(pOut, rc ? 0 : 1, 'i32'); + return 0; + }, xCurrentTime: function(pVfs,pOut){ /* If it turns out that we need to adjust for timezone, see: https://stackoverflow.com/a/11760121/1458521 */ @@ -397,17 +402,25 @@ const initOpfsVfs = function(sqlite3){ const fid = sq3File.pointer; const openFlags = capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE - | capi.SQLITE_OPEN_DELETEONCLOSE + //| capi.SQLITE_OPEN_DELETEONCLOSE | capi.SQLITE_OPEN_MAIN_DB; const pOut = wasm.scopedAlloc(8); const dbFile = "/sanity/check/file"; - let rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, dbFile, - fid, openFlags, pOut); + const zDbFile = wasm.scopedAllocCString(dbFile); + let rc; + vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut); + rc = wasm.getMemValue(pOut,'i32'); + log("xAccess(",dbFile,") exists ?=",rc); + rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile, + fid, openFlags, pOut); log("open rc =",rc,"state.opSABView[xOpen] =",state.opSABView[state.opIds.xOpen]); if(isWorkerErrCode(rc)){ error("open failed with code",rc); return; } + vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut); + rc = wasm.getMemValue(pOut,'i32'); + if(!rc) toss("xAccess() failed to detect file."); rc = ioSyncWrappers.xSync(sq3File.pointer, 0); if(rc) toss('sync failed w/ rc',rc); rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024); @@ -417,11 +430,14 @@ const initOpfsVfs = function(sqlite3){ if(rc) toss('xFileSize failed w/ rc',rc); log("xFileSize says:",wasm.getMemValue(pOut, 'i64')); log("xSleep()ing before close()ing..."); - opRun('xSleep',1500); + opRun('xSleep',1000); rc = ioSyncWrappers.xClose(fid); log("xClose rc =",rc,"opSABView =",state.opSABView); log("Deleting file:",dbFile); - opRun('xDelete', dbFile); + vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234); + vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut); + rc = wasm.getMemValue(pOut,'i32'); + if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete()."); }finally{ sq3File.dispose(); wasm.scopedAllocPop(scope); diff --git a/manifest b/manifest index bef8fab86f..503b895473 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Generic\scleanups\sin\sthe\sOPFS\ssync/async\sproxy. -D 2022-09-17T21:13:26.228 +C Implement\sOPFS\sxAccess(),\salbeit\swith\smore\slimited\ssemantics\sthan\sthe\sVFS\sAPI\scalls\sfor.\sAdd\sa\sway\sfor\sOPFS\sxDelete()\sto\soptionally\srecursively\sremove\sempty\sdirs\sleft\sover\safter\sdeleting\sa\sfile. +D 2022-09-17T23:29:27.832 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -523,7 +523,7 @@ F ext/wasm/speedtest1.html fbb8e4d1639028443f3687a683be660beca6927920545cf6b1fdf F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0 F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5 -F ext/wasm/sqlite3-opfs-async-proxy.js ceb5e3a190bfd42d25fc137b3aaf09adf6469b5f1e33528a64ce7ff74e5ef5b1 +F ext/wasm/sqlite3-opfs-async-proxy.js c4be9e614abea5f237564757141e3e128f0cff4b4cd8350f68e17347230fd34e F ext/wasm/sqlite3-worker1-promiser.js 92b8da5f38439ffec459a8215775d30fa498bc0f1ab929ff341fc3dd479660b9 F ext/wasm/sqlite3-worker1.js 0c1e7626304543969c3846573e080c082bf43bcaa47e87d416458af84f340a9e F ext/wasm/testing-worker1-promiser.html 6eaec6e04a56cf24cf4fa8ef49d78ce8905dde1354235c9125dca6885f7ce893 @@ -534,7 +534,7 @@ F ext/wasm/testing2.html a66951c38137ff1d687df79466351f3c734fa9c6d9cce71d3cf97c2 F ext/wasm/testing2.js 25584bcc30f19673ce13a6f301f89f8820a59dfe044e0c4f2913941f4097fe3c F ext/wasm/wasmfs.make 21a5cf297954a689e0dc2a95299ae158f681cae5e90c10b99d986097815fd42d F ext/wasm/x-sync-async.html 717b0d3bee96e49cbd36731bead497ab27a8bf3a3b23dd11e40e61d4ac9e8b80 -F ext/wasm/x-sync-async.js c99a6f98a8b0ccb43b2883a1bc1da42d0d84b26b5d4fd99d2f053ffe209a0a1f +F ext/wasm/x-sync-async.js a95e8acb04fd69526317ee63bf25fa478a20e223d5dc16f7526baf5d81e07d1e F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 @@ -2030,8 +2030,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 44db9132145b3072488ea91db53f6c06be74544beccad5fd07efd22c0f03dc04 -R 75c9aa6717389505aa78f157abdf6d44 +P f36bddbe54c3acbfaa958042e4d24724f130bdca551401033f9bc63f3da73492 +R 5aad77d6a3a0d677e9c84e63e20c4294 U stephan -Z 6a0a0bcb470dbb44e9d0c87c09f1fedd +Z 31fefe04f3f471aad99d738829acf9a8 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 4f626ec440..6035cd7f00 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f36bddbe54c3acbfaa958042e4d24724f130bdca551401033f9bc63f3da73492 \ No newline at end of file +c342b5d745f301104c59851c753287ebbbe95a69a56cb522d376d0f3e352c30f \ No newline at end of file