Add sqlite3_wasm_vfs_create_file() to replace Emscripten's FS.createDataFile() in a (mostly) VFS-agnostic way. Add a test for worker1's export (to bytearray) support. Re-add worker1 open-from-bytearray using sqlite3_wasm_vfs_create_file() but it's untested (requires a new interactive test app or maybe reconsideration).

FossilOrigin-Name: b35e1225c91a3cadc0d25af1e4e790237256d194990faa13190e343ed03e11c5
This commit is contained in:
stephan 2022-11-02 11:53:31 +00:00
parent faff0410dc
commit f45c33701d
10 changed files with 264 additions and 66 deletions

View File

@ -316,6 +316,8 @@ const installOpfsVfs = function callee(options){
*/
state.sq3Codes = Object.create(null);
[
'SQLITE_ACCESS_EXISTS',
'SQLITE_ACCESS_READWRITE',
'SQLITE_ERROR',
'SQLITE_IOERR',
'SQLITE_IOERR_ACCESS',
@ -939,7 +941,7 @@ const installOpfsVfs = function callee(options){
*/
opfsUtil.entryExists = async function(fsEntryName){
try {
const [dh, fn] = await opfsUtil.getDirForFilename(filename);
const [dh, fn] = await opfsUtil.getDirForFilename(fsEntryName);
await dh.getFileHandle(fn);
return true;
}catch(e){

View File

@ -162,33 +162,6 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
}
});
/**
An Error subclass specifically for reporting DB-level errors and
enabling clients to unambiguously identify such exceptions.
The C-level APIs never throw, but some of the higher-level
C-style APIs do and the object-oriented APIs use exceptions
exclusively to report errors.
*/
class SQLite3Error extends Error {
/**
Constructs this object with a message equal to all arguments
concatenated with a space between each one. As a special case,
if it's passed only a single integer argument, the string form
of that argument is the result of
sqlite3.capi.sqlite3_js_rc_str() or (if that returns falsy), a
synthesized string which contains that integer.
*/
constructor(...args){
if(1===args.length && 'number'===typeof args[0] && args[0]===(args[0] | 0)){
super((capi.sqlite3_js_rc_str && capi.sqlite3_js_rc_str(args[0]))
|| ("Unknown result code #"+args[0]));
}else{
super(args.join(' '));
}
this.name = 'SQLite3Error';
}
};
/**
The main sqlite3 binding API gets installed into this object,
mimicking the C API as closely as we can. The numerous members
@ -219,6 +192,52 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
*/
const wasm = Object.create(null);
/** Internal helper for SQLite3Error ctor. */
const __rcStr = (rc)=>{
return (capi.sqlite3_js_rc_str && capi.sqlite3_js_rc_str(rc))
|| ("Unknown result code #"+rc);
};
/** Internal helper for SQLite3Error ctor. */
const __isInt = (n)=>'number'===typeof n && n===(n | 0);
/**
An Error subclass specifically for reporting DB-level errors and
enabling clients to unambiguously identify such exceptions.
The C-level APIs never throw, but some of the higher-level
C-style APIs do and the object-oriented APIs use exceptions
exclusively to report errors.
*/
class SQLite3Error extends Error {
/**
Constructs this object with a message depending on its arguments:
- If it's passed only a single integer argument, it is assumed
to be an sqlite3 C API result code. The message becomes the
result of sqlite3.capi.sqlite3_js_rc_str() or (if that returns
falsy) a synthesized string which contains that integer.
- If passed 2 arguments and the 2nd is a object, it bevaves
like the Error(string,object) constructor except that the first
argument is subject to the is-integer semantics from the
previous point.
- Else all arguments are concatenated with a space between each
one, using args.join(' '), to create the error message.
*/
constructor(...args){
if(1===args.length && __isInt(args[0])){
super(__rcStr(args[0]));
}else if(2===args.length && 'object'===typeof args){
if(__isInt(args[0])) super(__rcStr(args[0]), args[1]);
else super(...args);
}else{
super(args.join(' '));
}
this.name = 'SQLite3Error';
}
};
/**
Functionally equivalent to the SQLite3Error constructor but may
be used as part of an expression, e.g.:
@ -959,6 +978,8 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
wasm.bindingSignatures.wasm = [
["sqlite3_wasm_db_reset", "int", "sqlite3*"],
["sqlite3_wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"],
["sqlite3_wasm_vfs_create_file", "int",
"sqlite3_vfs*","string","*", "int"],
["sqlite3_wasm_vfs_unlink", "int", "sqlite3_vfs*","string"]
];

View File

@ -423,6 +423,15 @@ sqlite3.initWorker1API = function(){
return wState.dbList[0] && getDbId(wState.dbList[0]);
};
const guessVfs = function(filename){
const m = /^file:.+(vfs=(\w+))/.exec(filename);
return sqlite3.capi.sqlite3_vfs_find(m ? m[2] : 0);
};
const isSpecialDbFilename = (n)=>{
return ''===n || ':'===n[0];
};
/**
A level of "organizational abstraction" for the Worker1
API. Each method in this object must map directly to a Worker1
@ -441,11 +450,35 @@ sqlite3.initWorker1API = function(){
}
const rc = Object.create(null);
const pDir = sqlite3.capi.sqlite3_wasmfs_opfs_dir();
if(!args.filename || ':memory:'===args.filename){
let byteArray, pVfs;
oargs.vfs = args.vfs;
if(isSpecialDbFilename(args.filename)){
oargs.filename = args.filename || '';
}else{
oargs.filename = args.filename;
oargs.vfs = args.vfs;
byteArray = args.byteArray;
if(byteArray) pVfs = guessVfs(args.filename);
}
if(pVfs){
/* 2022-11-02: this feature is as-yet untested except that
sqlite3_wasm_vfs_create_file() has been tested from the
browser dev console. */
let pMem;
try{
pMem = sqlite3.wasm.allocFromTypedArray(byteArray);
const rc = sqlite3.wasm.sqlite3_wasm_vfs_create_file(
pVfs, oargs.filename, pMem, byteArray.byteLength
);
if(rc) sqlite3.SQLite3Error.toss(rc);
}catch(e){
throw new sqlite3.SQLite3Error(
e.name+' creating '+args.filename+": "+e.message, {
cause: e
}
);
}finally{
if(pMem) sqlite3.wasm.dealloc(pMem);
}
}
const db = wState.open(oargs);
rc.filename = db.filename;
@ -462,10 +495,9 @@ sqlite3.initWorker1API = function(){
filename: db && db.filename
};
if(db){
// Keep the "unlink" flag undocumented until we figure out how
// to apply it consistently, independent of the db storage.
wState.close(db, ((ev.args && 'object'===typeof ev.args)
? !!ev.args.unlink : false));
const doUnlink = ((ev.args && 'object'===typeof ev.args)
? !!ev.args.unlink : false);
wState.close(db, doUnlink);
}
return response;
},
@ -542,7 +574,7 @@ sqlite3.initWorker1API = function(){
sqlite3_serialize(). Response is an object:
{
bytearray: Uint8Array (db file contents),
byteArray: Uint8Array (db file contents),
filename: the current db filename,
mimetype: 'application/x-sqlite3'
}
@ -550,11 +582,11 @@ sqlite3.initWorker1API = function(){
export: function(ev){
const db = getMsgDb(ev);
const response = {
bytearray: sqlite3.capi.sqlite3_js_db_export(db.pointer),
byteArray: sqlite3.capi.sqlite3_js_db_export(db.pointer),
filename: db.filename,
mimetype: 'application/x-sqlite3'
};
wState.xfer.push(response.bytearray.buffer);
wState.xfer.push(response.byteArray.buffer);
return response;
}/*export()*/,

View File

@ -230,6 +230,16 @@ const storeAndNotify = (opName, value)=>{
const affirmNotRO = function(opName,fh){
if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs);
};
const affirmLocked = function(opName,fh){
//if(!fh.syncHandle) toss(opName+"(): File does not have a lock: "+fh.filenameAbs);
/**
Currently a no-op, as speedtest1 triggers xRead() without a
lock (that seems like a bug but it's currently uninvestigated).
This means, however, that some OPFS VFS routines may trigger
acquisition of a lock but never let it go until xUnlock() is
called (which it likely won't be if xLock() was not called).
*/
};
/**
We track 2 different timers: the "metrics" timer records how much
@ -395,6 +405,7 @@ const vfsAsyncImpls = {
let rc;
wTimeStart('xFileSize');
try{
affirmLocked('xFileSize',fh);
rc = await (await getSyncHandle(fh)).getSize();
state.s11n.serialize(Number(rc));
rc = 0;
@ -473,6 +484,7 @@ const vfsAsyncImpls = {
let rc = 0, nRead;
const fh = __openFiles[fid];
try{
affirmLocked('xRead',fh);
wTimeStart('xRead');
nRead = (await getSyncHandle(fh)).read(
fh.sabView.subarray(0, n),
@ -515,6 +527,7 @@ const vfsAsyncImpls = {
const fh = __openFiles[fid];
wTimeStart('xTruncate');
try{
affirmLocked('xTruncate',fh);
affirmNotRO('xTruncate', fh);
await (await getSyncHandle(fh)).truncate(size);
}catch(e){
@ -547,9 +560,10 @@ const vfsAsyncImpls = {
xWrite: async function(fid/*sqlite3_file pointer*/,n,offset64){
mTimeStart('xWrite');
let rc;
const fh = __openFiles[fid];
wTimeStart('xWrite');
try{
const fh = __openFiles[fid];
affirmLocked('xWrite',fh);
affirmNotRO('xWrite', fh);
rc = (
n === (await getSyncHandle(fh))

View File

@ -12,7 +12,6 @@
**
** emcc -o sqlite3.wasm ... -I/path/to/sqlite3-c-and-h sqlite3-wasm.c
*/
#define SQLITE_WASM
#ifdef SQLITE_WASM_ENABLE_C_TESTS
/*
@ -928,8 +927,8 @@ int sqlite3_wasm_db_export_chunked( sqlite3* pDb,
** sqlite3_free() to free it.
*/
SQLITE_WASM_KEEP
int sqlite3_wasm_db_serialize( sqlite3* pDb, unsigned char **pOut,
sqlite3_int64 * nOut, unsigned int mFlags ){
int sqlite3_wasm_db_serialize( sqlite3 *pDb, unsigned char **pOut,
sqlite3_int64 *nOut, unsigned int mFlags ){
unsigned char * z;
if( !pDb || !pOut ) return SQLITE_MISUSE;
if(nOut) *nOut = 0;
@ -942,6 +941,103 @@ int sqlite3_wasm_db_serialize( sqlite3* pDb, unsigned char **pOut,
}
}
/*
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own JS/WASM bindings.
**
** Creates a new file using the I/O API of the given VFS, containing
** the given number of bytes of the given data. If the file exists,
** it is truncated to the given length and populated with the given
** data.
**
** This function exists so that we can implement the equivalent of
** Emscripten's FS.createDataFile() in a VFS-agnostic way. This
** functionality is intended for use in uploading database files.
**
** If pVfs is NULL, sqlite3_vfs_find(0) is used.
**
** If zFile is NULL, pVfs is NULL (and sqlite3_vfs_find(0) returns
** NULL), or nData is negative, SQLITE_MISUSE are returned.
**
** On success, it creates a new file with the given name, populated
** with the fist nData bytes of pData. If pData is NULL, the file is
** created and/or truncated to nData bytes.
**
** Whether or not directory components of zFilename are created
** automatically or not is unspecified: that detail is left to the
** VFS. The "opfs" VFS, for example, create them.
**
** Not all VFSes support this functionality, e.g. the "kvvfs" does
** not.
**
** If an error happens while populating or truncating the file, the
** target file will be deleted (if needed) if this function created
** it. If this function did not create it, it is not deleted but may
** be left in an undefined state.
**
** Returns 0 on success. On error, it returns a code described above
** or propagates a code from one of the I/O methods.
**
** Design note: nData is an integer, instead of int64, for WASM
** portability, so that the API can still work in builds where BigInt
** support is disabled or unavailable.
*/
SQLITE_WASM_KEEP
int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs,
const char *zFilename,
const unsigned char * pData,
int nData ){
int rc;
sqlite3_file *pFile = 0;
sqlite3_io_methods const *pIo;
const int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
int flagsOut = 0;
int fileExisted = 0;
int doUnlock = 0;
const unsigned char *pPos = pData;
const int blockSize = 512
/* Because we are using pFile->pMethods->xWrite() for writing, and
** it may have a buffer limit related to sqlite3's pager size, we
** conservatively write in 512-byte blocks (smallest page
** size). */;
if( !pVfs ) pVfs = sqlite3_vfs_find(0);
if( !pVfs || !zFilename || nData<0 ) return SQLITE_MISUSE;
pVfs->xAccess(pVfs, zFilename, SQLITE_ACCESS_EXISTS, &fileExisted);
rc = sqlite3OsOpenMalloc(pVfs, zFilename, &pFile, openFlags, &flagsOut);
if(rc) return rc;
pIo = pFile->pMethods;
if( pIo->xLock ) {
/* We need xLock() in order to accommodate the OPFS VFS, as it
** obtains a writeable handle via the lock operation and releases
** it in xUnlock(). If we don't do those here, we have to add code
** to the VFS to account check whether it was locked before
** xFileSize(), xTruncate(), and the like, and release the lock
** only if it was unlocked when the op was started. */
rc = pIo->xLock(pFile, SQLITE_LOCK_EXCLUSIVE);
doUnlock = 0==rc;
}
if( 0==rc) rc = pIo->xTruncate(pFile, nData);
if( 0==rc && 0!=pData && nData>0 ){
while( 0==rc && nData>0 ){
const int n = nData>=blockSize ? blockSize : nData;
rc = pIo->xWrite(pFile, pPos, n, (sqlite3_int64)(pPos - pData));
nData -= n;
pPos += n;
}
if( 0==rc && nData>0 ){
assert(nData<512);
rc = pIo->xWrite(pFile, pPos, nData, (sqlite3_int64)(pPos - pData));
}
}
if( pIo->xUnlock && doUnlock!=0 ) pIo->xUnlock(pFile, SQLITE_LOCK_NONE);
pIo->xClose(pFile);
if( rc!=0 && 0==fileExisted ){
pVfs->xDelete(pVfs, zFilename, 1);
}
return rc;
}
/*
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own JS/WASM bindings.

View File

@ -248,6 +248,14 @@
.assert(2===ev.resultRows[0][0]);
});
await wtest('export', function(ev){
ev = ev.result;
T.assert('string' === typeof ev.filename)
.assert(ev.byteArray instanceof Uint8Array)
.assert(ev.byteArray.length > 1024)
.assert('application/x-sqlite3' === ev.mimetype);
});
/***** close() tests must come last. *****/
await wtest('close',{},function(ev){
T.assert('string' === typeof ev.result.filename);

View File

@ -229,16 +229,14 @@
T.assert(1===ev.resultRows.length)
.assert(2===ev.resultRows[0][0]);
});
if(0){
// export requires reimpl. for portability reasons.
runOneTest('export',{}, function(ev){
ev = ev.result;
T.assert('string' === typeof ev.filename)
.assert(ev.buffer instanceof Uint8Array)
.assert(ev.buffer.length > 1024)
.assert('application/x-sqlite3' === ev.mimetype);
});
}
runOneTest('export',{}, function(ev){
ev = ev.result;
log("export result:",ev);
T.assert('string' === typeof ev.filename)
.assert(ev.byteArray instanceof Uint8Array)
.assert(ev.byteArray.length > 1024)
.assert('application/x-sqlite3' === ev.mimetype);
});
/***** close() tests must come last. *****/
runOneTest('close',{unlink:true},function(ev){
ev = ev.result;
@ -342,4 +340,6 @@
}
};
log("Init complete, but async init bits may still be running.");
log("Installing Worker into global scope SW for dev purposes.");
self.SW = SW;
})();

View File

@ -346,6 +346,11 @@
}
try{ throw new sqlite3.SQLite3Error(capi.SQLITE_SCHEMA) }
catch(e){ T.assert('SQLITE_SCHEMA' === e.message) }
try{ sqlite3.SQLite3Error.toss(capi.SQLITE_CORRUPT,{cause: true}) }
catch(e){
T.assert('SQLITE_CORRUPT'===e.message)
.assert(true===e.cause);
}
})
////////////////////////////////////////////////////////////////////
.t('strglob/strlike', function(sqlite3){
@ -1762,6 +1767,26 @@
unlink();
}
if(1){
// Sanity-test sqlite3_wasm_vfs_create_file()...
const fSize = 1379;
let sh;
try{
T.assert(!(await opfs.entryExists(filename)));
let rc = wasm.sqlite3_wasm_vfs_create_file(
pVfs, filename, null, fSize
);
T.assert(0===rc)
.assert(await opfs.entryExists(filename));
const fh = await opfs.rootDirectory.getFileHandle(filename);
sh = await fh.createSyncAccessHandle();
T.assert(fSize === await sh.getSize());
}finally{
if(sh) sh.close();
unlink();
}
}
// Some sanity checks of the opfs utility functions...
const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12);
const aDir = testDir+'/test/dir';

View File

@ -1,5 +1,5 @@
C Emcc\sseems\sconfused\sby\sSQLITE_DEBUG,\sfor\sreasons\sunknown.\s\sUse\sNDEBUG\sinstead\nto\ssimplify\sthe\s#ifdef\slogic\sin\ssqlite3recover.c.
D 2022-11-02T11:25:33.199
C Add\ssqlite3_wasm_vfs_create_file()\sto\sreplace\sEmscripten's\sFS.createDataFile()\sin\sa\s(mostly)\sVFS-agnostic\sway.\sAdd\sa\stest\sfor\sworker1's\sexport\s(to\sbytearray)\ssupport.\sRe-add\sworker1\sopen-from-bytearray\susing\ssqlite3_wasm_vfs_create_file()\sbut\sit's\suntested\s(requires\sa\snew\sinteractive\stest\sapp\sor\smaybe\sreconsideration).
D 2022-11-02T11:53:31.000
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -501,13 +501,13 @@ F ext/wasm/api/pre-js.js 287e462f969342b032c03900e668099fa1471d852df7a472de5bc34
F ext/wasm/api/sqlite3-api-cleanup.js ecdc69dbfccfe26146f04799fcfd4a6f5790d46e7e3b9b6e9b0491f92ed8ae34
F ext/wasm/api/sqlite3-api-glue.js 9cfa26a9818532c80c2555bc98615de3b170d5db0cf4b141cc3aa83c33c8758f
F ext/wasm/api/sqlite3-api-oo1.js e9a83489bbb4838ce0aee46eaaa9350e0e25a5b926b565e4f5ae8e840e4fbaed
F ext/wasm/api/sqlite3-api-opfs.js 59b278ed00764fc47ba88be0582ab3fc3ce725e02b6d86459464cc029b9ac356
F ext/wasm/api/sqlite3-api-prologue.js 201b9ab9d101fdefa3175b02748ef39cba43bc7abe87a69b6faac24e58cd70f0
F ext/wasm/api/sqlite3-api-worker1.js 4f920a54fb97d4ca50632d45bd7d011a55016eb5a5883725033abb450903bc6f
F ext/wasm/api/sqlite3-api-opfs.js cdcbb57acc66f4569ac9e18f9d13d5a3657d8aae195725c6324943da56c1005d
F ext/wasm/api/sqlite3-api-prologue.js 1f97261b4d8a60a48f30ee41261e1c4a0c3efff35ae08d7ece243fcf49b3eee3
F ext/wasm/api/sqlite3-api-worker1.js cac2f5c63f950f69b5249c9880d4cd385e914c354c459d4096ed5dbb1248de76
F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3
F ext/wasm/api/sqlite3-opfs-async-proxy.js 9815bd045b638af06350bfe04eaaae17ddf33c3593a9ca2d3f7fdbfd3c6a2205
F ext/wasm/api/sqlite3-opfs-async-proxy.js 936f57737eb65afc0f4c3494b93f7b02208055226a7b3cb58f551c38b03ab083
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c 14ac9c03f6585332f882703f3427f11ffe8ffe8b6c0e252be2c518f7aac6ab6a
F ext/wasm/api/sqlite3-wasm.c 41f4c807d5e027d5dd61d507cb0a78d0cee5618220e100860ff4c45ed1bd7c78
F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b
F ext/wasm/api/sqlite3-worker1.js 1e54ea3d540161bcfb2100368a2fc0cad871a207b8336afee1c445715851ec54
F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8
@ -522,9 +522,9 @@ F ext/wasm/demo-123.js ebae30756585bca655b4ab2553ec9236a87c23ad24fc8652115dcedb0
F ext/wasm/demo-jsstorage.html 409c4be4af5f207fb2877160724b91b33ea36a3cd8c204e8da1acb828ffe588e
F ext/wasm/demo-jsstorage.js 44e3ae7ec2483b6c511384c3c290beb6f305c721186bcf5398ca4e00004a06b8
F ext/wasm/demo-worker1-promiser.html 1de7c248c7c2cfd4a5783d2aa154bce62d74c6de98ab22f5786620b3354ed15f
F ext/wasm/demo-worker1-promiser.js 988ce92220c1cf1dd95fcb6b59734f0ae677942469390ddd8a64f4bbb5f99821
F ext/wasm/demo-worker1-promiser.js b85a2bb1b918db4f09dfa24419241cb3edad7791389425c2505092e9b715017d
F ext/wasm/demo-worker1.html 2c178c1890a2beb5a5fecb1453e796d067a4b8d3d2a04d65ca2eb1ab2c68ef5d
F ext/wasm/demo-worker1.js 117e4eedc62e103e287f0e4a694add7e13a200a4d7056e718645032288c4a8ab
F ext/wasm/demo-worker1.js a619adffc98b75b66c633b00f747b856449a134a9a0357909287d80a182d70fa
F ext/wasm/dist.make 481289899a07958439d07ee4302ff86235fa0fbb72f17ea05db2be90a94abf90
F ext/wasm/fiddle.make e570ec1bfc7d803507a2e514fe32f673fe001b2114b85c73c3964a462ba8bcfc
F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
@ -549,7 +549,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
F ext/wasm/test-opfs-vfs.js 44363db07b2a20e73b0eb1808de4400ca71b703af718d0fa6d962f15e73bf2ac
F ext/wasm/tester1-worker.html 51bf39e2b87f974ae3d5bc3086e2fb36d258f3698c54f6e21ba4b3b99636fa27
F ext/wasm/tester1.html 624ec41cd9f78a1f2b6d7df70aaa7a6394396b1f2455ecbd6de5775c1275b121
F ext/wasm/tester1.js 157eb499aad3365e33a7d95d6847c1d58d533335bc19d79bd3bc700b6350d1a5
F ext/wasm/tester1.js db50ca105d683d1089f540dae4c2baf89a3c101704a05f22cefdcb517587ae8c
F ext/wasm/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd72273503ae7d5
F ext/wasm/wasmfs.make fb2d3c4a298b12cf1ec994ad1d0f1d027ae297449b364cde43d2eb807d68048f
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
@ -2054,8 +2054,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 5bc83d569594e104e90b1acef1a5fd23655b2089de393a6776e799fdef2082f5
R 9c17a1415bba7769c29ae72a7a64647a
U drh
Z c15361b7db363b8dd30434361c36431b
P 2610779ac84ac4a1a6901b6244653faf0c49ac6f0a4710a19aaf2a13106ae742
R 54447cdf301faadeadecf9cb178cd6b7
U stephan
Z 0f92ea26c92634fd686db2350d9751fe
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
2610779ac84ac4a1a6901b6244653faf0c49ac6f0a4710a19aaf2a13106ae742
b35e1225c91a3cadc0d25af1e4e790237256d194990faa13190e343ed03e11c5