diff options
-rw-r--r-- | ext/wasm/api/sqlite3-api-opfs.js | 136 | ||||
-rw-r--r-- | ext/wasm/sqlite3-opfs-async-proxy.js | 104 | ||||
-rw-r--r-- | manifest | 14 | ||||
-rw-r--r-- | manifest.uuid | 2 |
4 files changed, 184 insertions, 72 deletions
diff --git a/ext/wasm/api/sqlite3-api-opfs.js b/ext/wasm/api/sqlite3-api-opfs.js index ffc7669a7..c6b38fa93 100644 --- a/ext/wasm/api/sqlite3-api-opfs.js +++ b/ext/wasm/api/sqlite3-api-opfs.js @@ -128,7 +128,7 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri) // failure is, e.g., that the remote script is 404. promiseReject(new Error("Loading OPFS async Worker failed for unknown reasons.")); }; - const wMsg = (type,payload)=>W.postMessage({type,payload}); + const wMsg = (type,args)=>W.postMessage({type,args}); /** Generic utilities for working with OPFS. This will get filled out by the Promise setup and, on success, installed as sqlite3.opfs. @@ -186,36 +186,48 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri) require a bit of acrobatics but should be feasible. */ const state = Object.create(null); + state.littleEndian = true; state.verbose = options.verbose; state.fileBufferSize = 1024 * 64 /* size of aFileHandle.sab. 64k = max sqlite3 page size. */; - state.sabOffsetS11n = state.fileBufferSize; + state.sabS11nOffset = state.fileBufferSize; + state.sabS11nSize = 2048; state.sabIO = new SharedArrayBuffer( state.fileBufferSize - + 4096/* arg/result serialization */ - + 8 /* to be removed - porting crutch */ + + state.sabS11nSize/* arg/result serialization block */ ); - state.fbInt64Offset = - state.sabIO.byteLength - 8 /*spot in fileHandle.sab to store an int64 result. - to be removed. Porting crutch. */; state.opIds = Object.create(null); + state.rcIds = Object.create(null); const metrics = Object.create(null); { let i = 0; + state.opIds.whichOp = i++; state.opIds.nothing = i++; state.opIds.xAccess = i++; + state.rcIds.xAccess = i++; state.opIds.xClose = i++; + state.rcIds.xClose = i++; state.opIds.xDelete = i++; + state.rcIds.xDelete = i++; state.opIds.xDeleteNoWait = i++; + state.rcIds.xDeleteNoWait = i++; state.opIds.xFileSize = i++; + state.rcIds.xFileSize = i++; state.opIds.xOpen = i++; + state.rcIds.xOpen = i++; state.opIds.xRead = i++; + state.rcIds.xRead = i++; state.opIds.xSleep = i++; + state.rcIds.xSleep = i++; state.opIds.xSync = i++; + state.rcIds.xSync = i++; state.opIds.xTruncate = i++; + state.rcIds.xTruncate = i++; state.opIds.xWrite = i++; + state.rcIds.xWrite = i++; state.opIds.mkdir = i++; + state.rcIds.mkdir = i++; state.sabOP = new SharedArrayBuffer(i * 4/*sizeof int32*/); state.opIds.xFileControl = state.opIds.xSync /* special case */; opfsUtil.metrics.reset(); @@ -247,7 +259,7 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri) must be a single object or primitive value, depending on the given operation's signature in the async API counterpart. */ - const opRun = (op,args)=>{ + const opRun = (op,...args)=>{ const t = performance.now(); Atomics.store(state.sabOPView, state.opIds[op], -1); wMsg(op, args); @@ -256,6 +268,44 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri) return Atomics.load(state.sabOPView, state.opIds[op]); }; + const initS11n = ()=>{ + // Achtung: this code is 100% duplicated in the other half of this proxy! + if(state.s11n) return state.s11n; + const jsonDecoder = new TextDecoder(), + jsonEncoder = new TextEncoder('utf-8'), + viewSz = new DataView(state.sabIO, state.sabS11nOffset, 4), + viewJson = new Uint8Array(state.sabIO, state.sabS11nOffset+4, state.sabS11nSize-4); + state.s11n = Object.create(null); + /** + Returns an array of the state serialized by the most recent + serialize() operation (here or in the counterpart thread), or + null if the serialization buffer is empty. + */ + state.s11n.deserialize = function(){ + const sz = viewSz.getInt32(0, state.littleEndian); + const json = sz ? jsonDecoder.decode( + viewJson.slice(0, sz) + /* slice() (copy) needed, instead of subarray() (reference), + because TextDecoder throws if asked to decode from an + SAB. */ + ) : null; + return JSON.parse(json); + } + /** + Serializes all arguments to the shared buffer for consumption + by the counterpart thread. This impl currently uses JSON for + serialization for simplicy of implementation, but if that + proves imperformant then a lower-level approach will be + created. + */ + state.s11n.serialize = function(...args){ + const json = jsonEncoder.encode(JSON.stringify(args)); + viewSz.setInt32(0, json.byteLength, state.littleEndian); + viewJson.set(json); + }; + return state.s11n; + }; + /** Generates a random ASCII string len characters long, intended for use as a temporary file name. @@ -425,7 +475,7 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri) xFileControl: function(pFile, opId, pArg){ mTimeStart('xFileControl'); const rc = (capi.SQLITE_FCNTL_SYNC===opId) - ? opRun('xSync', {fid:pFile, flags:0}) + ? opRun('xSync', pFile, 0) : capi.SQLITE_NOTFOUND; mTimeEnd(); return rc; @@ -434,10 +484,8 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri) mTimeStart('xFileSize'); const rc = opRun('xFileSize', pFile); if(!isWorkerErrCode(rc)){ - wasm.setMemValue( - pSz64, __openFiles[pFile].sabViewFileSize.getBigInt64(0,true), - 'i64' - ); + const sz = state.s11n.deserialize()[0]; + wasm.setMemValue(pSz64, BigInt(sz), 'i64'); } mTimeEnd(); return rc; @@ -447,14 +495,13 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri) //warn("xLock(",arguments,") is a no-op"); return 0; }, - xRead: function(pFile,pDest,n,offset){ + xRead: function(pFile,pDest,n,offset64){ /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */ mTimeStart('xRead'); const f = __openFiles[pFile]; let rc; try { - // FIXME(?): block until we finish copying the xRead result buffer. How? - rc = opRun('xRead',{fid:pFile, n, offset}); + rc = opRun('xRead',pFile, n, Number(offset64)); if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){ // set() seems to be the fastest way to copy this... wasm.heap8u().set(f.sabView.subarray(0, n), pDest); @@ -468,11 +515,11 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri) }, xSync: function(pFile,flags){ ++metrics.xSync.count; - return 0; // impl'd in xFileControl(). opRun('xSync', {fid:pFile, flags}); + return 0; // impl'd in xFileControl() }, xTruncate: function(pFile,sz64){ mTimeStart('xTruncate'); - const rc = opRun('xTruncate', {fid:pFile, size: sz64}); + const rc = opRun('xTruncate', pFile, Number(sz64)); mTimeEnd(); return rc; }, @@ -481,15 +528,14 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri) //warn("xUnlock(",arguments,") is a no-op"); return 0; }, - xWrite: function(pFile,pSrc,n,offset){ + xWrite: function(pFile,pSrc,n,offset64){ /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */ mTimeStart('xWrite'); const f = __openFiles[pFile]; let rc; try { - // FIXME(?): block from here until we finish the xWrite. How? f.sabView.set(wasm.heap8u().subarray(pSrc, pSrc+n)); - rc = opRun('xWrite',{fid:pFile, n, offset}); + rc = opRun('xWrite', pFile, n, Number(offset64)); }catch(e){ error("xWrite(",arguments,") failed:",e,f); rc = capi.SQLITE_IOERR_WRITE; @@ -527,7 +573,7 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri) }, xDelete: function(pVfs, zName, doSyncDir){ mTimeStart('xDelete'); - opRun('xDelete', {filename: wasm.cstringToJs(zName), syncDir: doSyncDir}); + opRun('xDelete', wasm.cstringToJs(zName), doSyncDir, false); /* We're ignoring errors because we cannot yet differentiate between harmless and non-harmless failures. */ mTimeEnd(); @@ -577,24 +623,22 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri) }else if('number'===typeof zName){ zName = wasm.cstringToJs(zName); } - const args = Object.create(null); - args.fid = pFile; - args.filename = zName; - args.sab = new SharedArrayBuffer(state.fileBufferSize); - args.flags = flags; - const rc = opRun('xOpen', args); + const fh = Object.create(null); + fh.fid = pFile; + fh.filename = zName; + fh.sab = new SharedArrayBuffer(state.fileBufferSize); + fh.flags = flags; + const rc = opRun('xOpen', pFile, zName, flags); if(!rc){ /* Recall that sqlite3_vfs::xClose() will be called, even on error, unless pFile->pMethods is NULL. */ - if(args.readOnly){ + if(fh.readOnly){ wasm.setMemValue(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32'); } - __openFiles[pFile] = args; - args.sabView = new Uint8Array(state.sabIO, 0, state.fileBufferSize); - args.sabViewFileSize = new DataView(state.sabIO, state.fbInt64Offset, 8); - args.sq3File = new sqlite3_file(pFile); - args.sq3File.$pMethods = opfsIoMethods.pointer; - args.ba = new Uint8Array(args.sab); + __openFiles[pFile] = fh; + fh.sabView = state.sabFileBufView; + fh.sq3File = new sqlite3_file(pFile); + fh.sq3File.$pMethods = opfsIoMethods.pointer; } mTimeEnd(); return rc; @@ -640,13 +684,17 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri) but cannot report the nature of the failure. */ opfsUtil.deleteEntry = function(fsEntryName,recursive=false){ - return 0===opRun('xDelete', {filename:fsEntryName, recursive}); + return 0===opRun('xDelete', fsEntryName, 0, recursive); }; /** - Exactly like deleteEntry() but runs asynchronously. + Exactly like deleteEntry() but runs asynchronously. This is a + "fire and forget" operation: it does not return a promise + because the counterpart operation happens in another thread and + waiting on that result in a Promise would block the OPFS VFS + from acting until it completed. */ - opfsUtil.deleteEntryAsync = async function(fsEntryName,recursive=false){ - wMsg('xDeleteNoWait', {filename: fsEntryName, recursive}); + opfsUtil.deleteEntryAsync = function(fsEntryName,recursive=false){ + wMsg('xDeleteNoWait', [fsEntryName, 0, recursive]); }; /** Synchronously creates the given directory name, recursively, in @@ -753,11 +801,11 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri) W.onmessage = function({data}){ //log("Worker.onmessage:",data); switch(data.type){ - case 'loaded': + case 'opfs-async-loaded': /*Pass our config and shared state on to the async worker.*/ - wMsg('init',state); + wMsg('opfs-async-init',state); break; - case 'inited':{ + case 'opfs-async-inited':{ /*Indicates that the async partner has received the 'init', so we now know that the state object is no longer subject to being copied by a pending postMessage() call.*/ @@ -772,7 +820,9 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri) } capi.sqlite3_vfs_register.addReference(opfsVfs, opfsIoMethods); state.sabOPView = new Int32Array(state.sabOP); - state.sabFileBufView = new Uint8Array(state.sabFileBufView, 0, state.fileBufferSize); + state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize); + state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize); + initS11n(); if(options.sanityChecks){ warn("Running sanity checks because of opfs-sanity-check URL arg..."); sanityCheck(); diff --git a/ext/wasm/sqlite3-opfs-async-proxy.js b/ext/wasm/sqlite3-opfs-async-proxy.js index a1ed4362d..19d71512c 100644 --- a/ext/wasm/sqlite3-opfs-async-proxy.js +++ b/ext/wasm/sqlite3-opfs-async-proxy.js @@ -220,7 +220,7 @@ const vfsAsyncImpls = { storeAndNotify('xDelete', rc); mTimeEnd(); }, - xDeleteNoWait: async function({filename, syncDir, recursive = false}){ + xDeleteNoWait: async function(filename, syncDir = 0, recursive = false){ /* 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 @@ -261,10 +261,7 @@ const vfsAsyncImpls = { let sz; try{ sz = await fh.accessHandle.getSize(); - if(!fh.sabViewFileSize){ - fh.sabViewFileSize = new DataView(state.sabIO,state.fbInt64Offset,8); - } - fh.sabViewFileSize.setBigInt64(0, BigInt(sz), true); + state.s11n.serialize(Number(sz)); sz = 0; }catch(e){ error("xFileSize():",e, fh); @@ -273,11 +270,7 @@ const vfsAsyncImpls = { storeAndNotify('xFileSize', sz); mTimeEnd(); }, - xOpen: async function({ - fid/*sqlite3_file pointer*/, - filename, - flags - }){ + xOpen: async function(fid/*sqlite3_file pointer*/, filename, flags){ const opName = 'xOpen'; mTimeStart(opName); log(opName+"(",arguments[0],")"); @@ -316,7 +309,7 @@ const vfsAsyncImpls = { } mTimeEnd(); }, - xRead: async function({fid,n,offset}){ + xRead: async function(fid,n,offset){ mTimeStart('xRead'); log("xRead(",arguments[0],")"); let rc = 0; @@ -337,7 +330,7 @@ const vfsAsyncImpls = { storeAndNotify('xRead',rc); mTimeEnd(); }, - xSync: async function({fid,flags/*ignored*/}){ + xSync: async function(fid,flags/*ignored*/){ mTimeStart('xSync'); log("xSync(",arguments[0],")"); const fh = __openFiles[fid]; @@ -345,14 +338,14 @@ const vfsAsyncImpls = { storeAndNotify('xSync',0); mTimeEnd(); }, - xTruncate: async function({fid,size}){ + xTruncate: async function(fid,size){ mTimeStart('xTruncate'); log("xTruncate(",arguments[0],")"); let rc = 0; const fh = __openFiles[fid]; try{ affirmNotRO('xTruncate', fh); - await fh.accessHandle.truncate(Number(size)); + await fh.accessHandle.truncate(size); }catch(e){ error("xTruncate():",e,fh); rc = state.sq3Codes.SQLITE_IOERR_TRUNCATE; @@ -360,7 +353,7 @@ const vfsAsyncImpls = { storeAndNotify('xTruncate',rc); mTimeEnd(); }, - xWrite: async function({fid,n,offset}){ + xWrite: async function(fid,n,offset){ mTimeStart('xWrite'); log("xWrite(",arguments[0],")"); let rc; @@ -380,6 +373,70 @@ const vfsAsyncImpls = { } }; + +const initS11n = ()=>{ + // Achtung: this code is 100% duplicated in the other half of this proxy! + if(state.s11n) return state.s11n; + const jsonDecoder = new TextDecoder(), + jsonEncoder = new TextEncoder('utf-8'), + viewSz = new DataView(state.sabIO, state.sabS11nOffset, 4), + viewJson = new Uint8Array(state.sabIO, state.sabS11nOffset+4, state.sabS11nSize-4); + state.s11n = Object.create(null); + /** + Returns an array of the state serialized by the most recent + serialize() operation (here or in the counterpart thread), or + null if the serialization buffer is empty. + */ + state.s11n.deserialize = function(){ + const sz = viewSz.getInt32(0, state.littleEndian); + const json = sz ? jsonDecoder.decode( + viewJson.slice(0, sz) + /* slice() (copy) needed, instead of subarray() (reference), + because TextDecoder throws if asked to decode from an + SAB. */ + ) : null; + return JSON.parse(json); + } + /** + Serializes all arguments to the shared buffer for consumption + by the counterpart thread. This impl currently uses JSON for + serialization for simplicy of implementation, but if that + proves imperformant then a lower-level approach will be + created. + */ + state.s11n.serialize = function(...args){ + const json = jsonEncoder.encode(JSON.stringify(args)); + viewSz.setInt32(0, json.byteLength, state.littleEndian); + viewJson.set(json); + }; + return state.s11n; +}; + +const waitLoop = function(){ + const opHandlers = Object.create(null); + for(let k of Object.keys(state.opIds)){ + const o = Object.create(null); + opHandlers[state.opIds[k]] = o; + o.key = k; + } + const sabOP = state.sabOP; + for(;;){ + try { + Atomics.store(sabOP, state.opIds.whichOp, 0); + Atomic.wait(sabOP, state.opIds.whichOp); + const opId = Atomics.load(sabOP, state.opIds.whichOp); + const hnd = opHandlers[opId] ?? toss("No waitLoop handler for whichOp #",opId); + const args = state.s11n.deserialize(); + log("whichOp =",opId,hnd,args); + const rc = 0/*TODO: run op*/; + Atomics.store(sabOP, state.rcIds[hnd.key], rc); + Atomics.notify(sabOP, state.rcIds[hnd.key]); + }catch(e){ + error('in waitLoop():',e.message); + } + } +}; + navigator.storage.getDirectory().then(function(d){ const wMsg = (type)=>postMessage({type}); state.rootDir = d; @@ -387,33 +444,38 @@ navigator.storage.getDirectory().then(function(d){ self.onmessage = async function({data}){ log("self.onmessage()",data); switch(data.type){ - case 'init':{ + case 'opfs-async-init':{ /* Receive shared state from synchronous partner */ - const opt = data.payload; + const opt = data.args; + state.littleEndian = opt.littleEndian; state.verbose = opt.verbose ?? 2; state.fileBufferSize = opt.fileBufferSize; - state.fbInt64Offset = opt.fbInt64Offset; + state.sabS11nOffset = opt.sabS11nOffset; + state.sabS11nSize = opt.sabS11nSize; state.sabOP = opt.sabOP; state.sabOPView = new Int32Array(state.sabOP); state.sabIO = opt.sabIO; state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize); + state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize); state.opIds = opt.opIds; + state.rcIds = opt.rcIds; state.sq3Codes = opt.sq3Codes; Object.keys(vfsAsyncImpls).forEach((k)=>{ if(!Number.isFinite(state.opIds[k])){ toss("Maintenance required: missing state.opIds[",k,"]"); } }); + initS11n(); metrics.reset(); log("init state",state); - wMsg('inited'); + wMsg('opfs-async-inited'); break; } default:{ let err; const m = vfsAsyncImpls[data.type] || toss("Unknown message type:",data.type); try { - await m(data.payload).catch((e)=>err=e); + await m(...data.args).catch((e)=>err=e); }catch(e){ err = e; } @@ -425,5 +487,5 @@ navigator.storage.getDirectory().then(function(d){ } } }; - wMsg('loaded'); + wMsg('opfs-async-loaded'); }).catch((e)=>error(e)); @@ -1,5 +1,5 @@ -C OPFS\sVFS:\smoved\si/o\sbuffer\sfrom\sper-file\sto\sthe\sVFS,\sand\srelated\srefactoring,\sin\sprep\sfor\sexperimentation\swith\sa\snew\sinter-worker\scomms\smodel. -D 2022-09-20T01:28:47.162 +C OPFS\sVFS:\sfurther\sinternal\srefactoring\stowards\sexperimenting\swith\sa\snew\scomms\smodel. +D 2022-09-20T03:31:02.139 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -484,7 +484,7 @@ F ext/wasm/api/post-js-header.js 0e853b78db83cb1c06b01663549e0e8b4f377f12f5a2d9a F ext/wasm/api/sqlite3-api-cleanup.js 8564a6077cdcaea9a9f428a019af8a05887f0131e6a2a1e72a7ff1145fadfe77 F ext/wasm/api/sqlite3-api-glue.js 366d580c8e5bf7fcf4c6dee6f646c31f5549bd417ea03a59a0acca00e8ecce30 F ext/wasm/api/sqlite3-api-oo1.js 2d13dddf0d2b4168a9249f124134d37924331e5b55e05dba18b6d661fbeefe48 -F ext/wasm/api/sqlite3-api-opfs.js f8027fb4af1c24fcfad31889f8a5ccfa3b96d6e812d3495b13833bef57034046 +F ext/wasm/api/sqlite3-api-opfs.js 351459d571166ff4cebaccd6b8aad2b0fe5eac54a8c777ba52c31c931a3eb2e2 F ext/wasm/api/sqlite3-api-prologue.js 0d2639387b94c30f492d4aea6e44fb7b16720808678464559458fd2ae3759655 F ext/wasm/api/sqlite3-api-worker1.js ee4cf149cbacb63d06b536674f822aa5088b7e022cdffc69f1f36cebe2f9fea0 F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9 @@ -519,7 +519,7 @@ F ext/wasm/speedtest1.html 512addeb3c27c94901178b7bcbde83a6f95c093f9ebe16a2959a0 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 7ebc36915cd61bd4f067a4823307f4d4eb2678a173aaae470c534e8fe9cda650 +F ext/wasm/sqlite3-opfs-async-proxy.js 462081970a6a46d9b2c386474aacad2d81e6629bb554d6cad5c58515f08c8a38 F ext/wasm/sqlite3-worker1-promiser.js 4fd0465688a28a75f1d4ee4406540ba494f49844e3cad0670d0437a001943365 F ext/wasm/sqlite3-worker1.js 0c1e7626304543969c3846573e080c082bf43bcaa47e87d416458af84f340a9e F ext/wasm/test-opfs-vfs.html eb69dda21eb414b8f5e3f7c1cc0f774103cc9c0f87b2d28a33419e778abfbab5 @@ -2026,8 +2026,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 d1f1fe6f1c60640f7770dfb9245c459a09b8d24ec2ddf664dff77c810bd51f96 -R e5a1ff53ebe902985ab8e87bba2c71fb +P d4d63e4580ad8d497310608175308c03c517e051d7865cb66aa0b10356612d7d +R 7fef73d3d6085edd0d87db08b80a36c1 U stephan -Z d05962442428c5dae722ab00dca47e48 +Z d9dc6ab88cc2471cfc1f140af8fec063 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 8d4f71349..b2152187c 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d4d63e4580ad8d497310608175308c03c517e051d7865cb66aa0b10356612d7d
\ No newline at end of file +5ca412ced24b4e3af5f467e710a597ed440badf7b8335346aade11d3cad3d1a1
\ No newline at end of file |