@livestore/wa-sqlite 0.4.0-dev.22 → 0.4.0-dev.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -36
- package/dist/README.md +13 -13
- package/dist/fts5/wa-sqlite.mjs +1 -1
- package/dist/fts5/wa-sqlite.node.mjs +1 -1
- package/dist/fts5/wa-sqlite.node.wasm +0 -0
- package/dist/fts5/wa-sqlite.wasm +0 -0
- package/dist/wa-sqlite-async.mjs +1 -1
- package/dist/wa-sqlite-async.wasm +0 -0
- package/dist/wa-sqlite-jspi.mjs +1 -1
- package/dist/wa-sqlite-jspi.wasm +0 -0
- package/dist/wa-sqlite.mjs +1 -1
- package/dist/wa-sqlite.node.mjs +1 -1
- package/dist/wa-sqlite.node.wasm +0 -0
- package/dist/wa-sqlite.wasm +0 -0
- package/package.json +40 -29
- package/src/FacadeVFS.js +252 -261
- package/src/VFS.js +84 -85
- package/src/WebLocksMixin.js +357 -351
- package/src/examples/AccessHandlePoolVFS.js +185 -194
- package/src/examples/IDBBatchAtomicVFS.js +429 -409
- package/src/examples/IDBMirrorVFS.js +402 -409
- package/src/examples/MemoryAsyncVFS.js +32 -37
- package/src/examples/MemoryVFS.js +71 -75
- package/src/examples/OPFSAdaptiveVFS.js +206 -206
- package/src/examples/OPFSAnyContextVFS.js +141 -140
- package/src/examples/OPFSCoopSyncVFS.js +297 -299
- package/src/examples/OPFSPermutedVFS.js +529 -540
- package/src/examples/README.md +27 -15
- package/src/examples/tag.js +27 -27
- package/src/sqlite-api.js +910 -941
- package/src/sqlite-constants.js +246 -232
- package/src/types/globals.d.ts +52 -52
- package/src/types/index.d.ts +586 -576
- package/test/AccessHandlePoolVFS.test.js +21 -21
- package/test/IDBBatchAtomicVFS.test.js +69 -69
- package/test/IDBMirrorVFS.test.js +21 -21
- package/test/MemoryAsyncVFS.test.js +21 -21
- package/test/MemoryVFS.test.js +21 -21
- package/test/OPFSAdaptiveVFS.test.js +21 -21
- package/test/OPFSAnyContextVFS.test.js +21 -21
- package/test/OPFSCoopSyncVFS.test.js +21 -21
- package/test/OPFSPermutedVFS.test.js +21 -21
- package/test/TestContext.js +44 -41
- package/test/WebLocksMixin.test.js +369 -360
- package/test/api.test.js +23 -23
- package/test/api_exec.js +72 -61
- package/test/api_misc.js +53 -54
- package/test/api_statements.js +271 -279
- package/test/callbacks.test.js +492 -478
- package/test/data/idbv5.json +1135 -1
- package/test/sql.test.js +30 -30
- package/test/sql_0001.js +49 -33
- package/test/sql_0002.js +55 -34
- package/test/sql_0003.js +85 -49
- package/test/sql_0004.js +76 -47
- package/test/sql_0005.js +60 -44
- package/test/test-worker.js +171 -163
- package/test/vfs_xAccess.js +1 -2
- package/test/vfs_xClose.js +50 -49
- package/test/vfs_xOpen.js +73 -72
- package/test/vfs_xRead.js +31 -31
- package/test/vfs_xWrite.js +30 -29
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
// Copyright 2024 Roy T. Hashimoto. All Rights Reserved.
|
|
2
|
-
import { FacadeVFS } from '../FacadeVFS.js'
|
|
3
|
-
import * as VFS from '../VFS.js'
|
|
2
|
+
import { FacadeVFS } from '../FacadeVFS.js'
|
|
3
|
+
import * as VFS from '../VFS.js'
|
|
4
4
|
|
|
5
|
-
const DEFAULT_TEMPORARY_FILES = 10
|
|
6
|
-
const LOCK_NOTIFY_INTERVAL = 1000
|
|
5
|
+
const DEFAULT_TEMPORARY_FILES = 10
|
|
6
|
+
const LOCK_NOTIFY_INTERVAL = 1000
|
|
7
7
|
|
|
8
|
-
const DB_RELATED_FILE_SUFFIXES = ['', '-journal', '-wal']
|
|
8
|
+
const DB_RELATED_FILE_SUFFIXES = ['', '-journal', '-wal']
|
|
9
9
|
|
|
10
|
-
const finalizationRegistry = new FinalizationRegistry(releaser => releaser())
|
|
10
|
+
const finalizationRegistry = new FinalizationRegistry((releaser) => releaser())
|
|
11
11
|
|
|
12
12
|
class File {
|
|
13
13
|
/** @type {string} */ path
|
|
14
|
-
/** @type {number} */ flags
|
|
15
|
-
/** @type {FileSystemSyncAccessHandle} */ accessHandle
|
|
14
|
+
/** @type {number} */ flags
|
|
15
|
+
/** @type {FileSystemSyncAccessHandle} */ accessHandle
|
|
16
16
|
|
|
17
|
-
/** @type {PersistentFile?} */ persistentFile
|
|
17
|
+
/** @type {PersistentFile?} */ persistentFile
|
|
18
18
|
|
|
19
19
|
constructor(path, flags) {
|
|
20
|
-
this.path = path
|
|
21
|
-
this.flags = flags
|
|
20
|
+
this.path = path
|
|
21
|
+
this.flags = flags
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -28,423 +28,419 @@ class PersistentFile {
|
|
|
28
28
|
|
|
29
29
|
// The following properties are for main database files.
|
|
30
30
|
|
|
31
|
-
/** @type {boolean} */ isLockBusy = false
|
|
32
|
-
/** @type {boolean} */ isFileLocked = false
|
|
33
|
-
/** @type {boolean} */ isRequestInProgress = false
|
|
34
|
-
/** @type {function} */ handleLockReleaser = null
|
|
31
|
+
/** @type {boolean} */ isLockBusy = false
|
|
32
|
+
/** @type {boolean} */ isFileLocked = false
|
|
33
|
+
/** @type {boolean} */ isRequestInProgress = false
|
|
34
|
+
/** @type {function} */ handleLockReleaser = null
|
|
35
35
|
|
|
36
|
-
/** @type {BroadcastChannel} */ handleRequestChannel
|
|
37
|
-
/** @type {boolean} */ isHandleRequested = false
|
|
36
|
+
/** @type {BroadcastChannel} */ handleRequestChannel
|
|
37
|
+
/** @type {boolean} */ isHandleRequested = false
|
|
38
38
|
|
|
39
39
|
constructor(fileHandle) {
|
|
40
|
-
this.fileHandle = fileHandle
|
|
40
|
+
this.fileHandle = fileHandle
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export class OPFSCoopSyncVFS extends FacadeVFS {
|
|
45
|
-
/** @type {Map<number, File>} */ mapIdToFile = new Map()
|
|
45
|
+
/** @type {Map<number, File>} */ mapIdToFile = new Map()
|
|
46
46
|
|
|
47
|
-
lastError = null
|
|
48
|
-
log = null
|
|
49
|
-
|
|
50
|
-
/** @type {Map<string, PersistentFile>} */ persistentFiles = new Map();
|
|
51
|
-
/** @type {Map<string, FileSystemSyncAccessHandle>} */ boundAccessHandles = new Map();
|
|
52
|
-
/** @type {Set<FileSystemSyncAccessHandle>} */ unboundAccessHandles = new Set();
|
|
53
|
-
/** @type {Set<string>} */ accessiblePaths = new Set();
|
|
54
|
-
releaser = null;
|
|
47
|
+
lastError = null
|
|
48
|
+
log = null //function(...args) { console.log(`[${contextName}]`, ...args) };
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
]);
|
|
62
|
-
return vfs;
|
|
63
|
-
}
|
|
50
|
+
/** @type {Map<string, PersistentFile>} */ persistentFiles = new Map()
|
|
51
|
+
/** @type {Map<string, FileSystemSyncAccessHandle>} */ boundAccessHandles = new Map()
|
|
52
|
+
/** @type {Set<FileSystemSyncAccessHandle>} */ unboundAccessHandles = new Set()
|
|
53
|
+
/** @type {Set<string>} */ accessiblePaths = new Set()
|
|
54
|
+
releaser = null
|
|
64
55
|
|
|
65
|
-
|
|
66
|
-
|
|
56
|
+
static async create(name, module) {
|
|
57
|
+
const vfs = new OPFSCoopSyncVFS(name, module)
|
|
58
|
+
await Promise.all([vfs.isReady(), vfs.#initialize(DEFAULT_TEMPORARY_FILES)])
|
|
59
|
+
return vfs
|
|
67
60
|
}
|
|
68
61
|
|
|
69
62
|
async #initialize(nTemporaryFiles) {
|
|
70
63
|
// Delete temporary directories no longer in use.
|
|
71
|
-
const root = await navigator.storage.getDirectory()
|
|
64
|
+
const root = await navigator.storage.getDirectory()
|
|
72
65
|
// @ts-ignore
|
|
73
66
|
for await (const entry of root.values()) {
|
|
74
67
|
if (entry.kind === 'directory' && entry.name.startsWith('.ahp-')) {
|
|
75
68
|
// A lock with the same name as the directory protects it from
|
|
76
69
|
// being deleted.
|
|
77
|
-
await navigator.locks.request(entry.name, { ifAvailable: true }, async lock => {
|
|
70
|
+
await navigator.locks.request(entry.name, { ifAvailable: true }, async (lock) => {
|
|
78
71
|
if (lock) {
|
|
79
|
-
this.log?.(`Deleting temporary directory ${entry.name}`)
|
|
80
|
-
await root.removeEntry(entry.name, { recursive: true })
|
|
72
|
+
this.log?.(`Deleting temporary directory ${entry.name}`)
|
|
73
|
+
await root.removeEntry(entry.name, { recursive: true })
|
|
81
74
|
} else {
|
|
82
|
-
this.log?.(`Temporary directory ${entry.name} is in use`)
|
|
75
|
+
this.log?.(`Temporary directory ${entry.name} is in use`)
|
|
83
76
|
}
|
|
84
|
-
})
|
|
77
|
+
})
|
|
85
78
|
}
|
|
86
79
|
}
|
|
87
80
|
|
|
88
81
|
// Create our temporary directory.
|
|
89
|
-
const tmpDirName = `.ahp-${Math.random().toString(36).slice(2)}
|
|
90
|
-
this.releaser = await new Promise(resolve => {
|
|
82
|
+
const tmpDirName = `.ahp-${Math.random().toString(36).slice(2)}`
|
|
83
|
+
this.releaser = await new Promise((resolve) => {
|
|
91
84
|
navigator.locks.request(tmpDirName, () => {
|
|
92
|
-
return new Promise(release => {
|
|
93
|
-
resolve(release)
|
|
94
|
-
})
|
|
95
|
-
})
|
|
96
|
-
})
|
|
97
|
-
finalizationRegistry.register(this, this.releaser)
|
|
98
|
-
const tmpDir = await root.getDirectoryHandle(tmpDirName, { create: true })
|
|
85
|
+
return new Promise((release) => {
|
|
86
|
+
resolve(release)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
finalizationRegistry.register(this, this.releaser)
|
|
91
|
+
const tmpDir = await root.getDirectoryHandle(tmpDirName, { create: true })
|
|
99
92
|
|
|
100
93
|
// Populate temporary directory.
|
|
101
94
|
for (let i = 0; i < nTemporaryFiles; i++) {
|
|
102
|
-
const tmpFile = await tmpDir.getFileHandle(`${i}.tmp`, { create: true })
|
|
103
|
-
const tmpAccessHandle = await tmpFile.createSyncAccessHandle()
|
|
104
|
-
this.unboundAccessHandles.add(tmpAccessHandle)
|
|
95
|
+
const tmpFile = await tmpDir.getFileHandle(`${i}.tmp`, { create: true })
|
|
96
|
+
const tmpAccessHandle = await tmpFile.createSyncAccessHandle()
|
|
97
|
+
this.unboundAccessHandles.add(tmpAccessHandle)
|
|
105
98
|
}
|
|
106
99
|
}
|
|
107
100
|
|
|
108
101
|
/**
|
|
109
|
-
* @param {string?} zName
|
|
110
|
-
* @param {number} fileId
|
|
111
|
-
* @param {number} flags
|
|
112
|
-
* @param {DataView} pOutFlags
|
|
102
|
+
* @param {string?} zName
|
|
103
|
+
* @param {number} fileId
|
|
104
|
+
* @param {number} flags
|
|
105
|
+
* @param {DataView} pOutFlags
|
|
113
106
|
* @returns {number}
|
|
114
107
|
*/
|
|
115
108
|
jOpen(zName, fileId, flags, pOutFlags) {
|
|
116
109
|
try {
|
|
117
|
-
const url = new URL(zName || Math.random().toString(36).slice(2), 'file://')
|
|
118
|
-
const path = url.pathname
|
|
110
|
+
const url = new URL(zName || Math.random().toString(36).slice(2), 'file://')
|
|
111
|
+
const path = url.pathname
|
|
119
112
|
|
|
120
113
|
if (flags & VFS.SQLITE_OPEN_MAIN_DB) {
|
|
121
|
-
const persistentFile = this.persistentFiles.get(path)
|
|
114
|
+
const persistentFile = this.persistentFiles.get(path)
|
|
122
115
|
if (persistentFile?.isRequestInProgress) {
|
|
123
116
|
// Should not reach here unless SQLite itself retries an open.
|
|
124
117
|
// Otherwise, asynchronous operations started on a previous
|
|
125
118
|
// open try should have completed.
|
|
126
|
-
return VFS.SQLITE_BUSY
|
|
119
|
+
return VFS.SQLITE_BUSY
|
|
127
120
|
} else if (!persistentFile) {
|
|
128
121
|
// This is the usual starting point for opening a database.
|
|
129
122
|
// Register a Promise that resolves when the database and related
|
|
130
123
|
// files are ready to be used.
|
|
131
|
-
this.log?.(`creating persistent file for ${path}`)
|
|
132
|
-
const create = !!(flags & VFS.SQLITE_OPEN_CREATE)
|
|
133
|
-
this._module.retryOps.push(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
124
|
+
this.log?.(`creating persistent file for ${path}`)
|
|
125
|
+
const create = !!(flags & VFS.SQLITE_OPEN_CREATE)
|
|
126
|
+
this._module.retryOps.push(
|
|
127
|
+
(async () => {
|
|
128
|
+
try {
|
|
129
|
+
// Get the path directory handle.
|
|
130
|
+
let dirHandle = await navigator.storage.getDirectory()
|
|
131
|
+
const directories = path.split('/').filter((d) => d)
|
|
132
|
+
const filename = directories.pop()
|
|
133
|
+
for (const directory of directories) {
|
|
134
|
+
dirHandle = await dirHandle.getDirectoryHandle(directory, { create })
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Get file handles for the database and related files,
|
|
138
|
+
// and create persistent file instances.
|
|
139
|
+
for (const suffix of DB_RELATED_FILE_SUFFIXES) {
|
|
140
|
+
const fileHandle = await dirHandle.getFileHandle(filename + suffix, { create })
|
|
141
|
+
await this.#createPersistentFile(fileHandle)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Get access handles for the files.
|
|
145
|
+
const file = new File(path, flags)
|
|
146
|
+
file.persistentFile = this.persistentFiles.get(path)
|
|
147
|
+
await this.#requestAccessHandle(file)
|
|
148
|
+
} catch (e) {
|
|
149
|
+
// Use an invalid persistent file to signal this error
|
|
150
|
+
// for the retried open.
|
|
151
|
+
const persistentFile = new PersistentFile(null)
|
|
152
|
+
this.persistentFiles.set(path, persistentFile)
|
|
153
|
+
console.error(e)
|
|
148
154
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
file.persistentFile = this.persistentFiles.get(path);
|
|
153
|
-
await this.#requestAccessHandle(file);
|
|
154
|
-
} catch (e) {
|
|
155
|
-
// Use an invalid persistent file to signal this error
|
|
156
|
-
// for the retried open.
|
|
157
|
-
const persistentFile = new PersistentFile(null);
|
|
158
|
-
this.persistentFiles.set(path, persistentFile);
|
|
159
|
-
console.error(e);
|
|
160
|
-
}
|
|
161
|
-
})());
|
|
162
|
-
return VFS.SQLITE_BUSY;
|
|
155
|
+
})(),
|
|
156
|
+
)
|
|
157
|
+
return VFS.SQLITE_BUSY
|
|
163
158
|
} else if (!persistentFile.fileHandle) {
|
|
164
159
|
// The asynchronous open operation failed.
|
|
165
|
-
this.persistentFiles.delete(path)
|
|
166
|
-
return VFS.SQLITE_CANTOPEN
|
|
160
|
+
this.persistentFiles.delete(path)
|
|
161
|
+
return VFS.SQLITE_CANTOPEN
|
|
167
162
|
} else if (!persistentFile.accessHandle) {
|
|
168
163
|
// This branch is reached if the database was previously opened
|
|
169
164
|
// and closed.
|
|
170
|
-
this._module.retryOps.push(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
165
|
+
this._module.retryOps.push(
|
|
166
|
+
(async () => {
|
|
167
|
+
const file = new File(path, flags)
|
|
168
|
+
file.persistentFile = this.persistentFiles.get(path)
|
|
169
|
+
await this.#requestAccessHandle(file)
|
|
170
|
+
})(),
|
|
171
|
+
)
|
|
172
|
+
return VFS.SQLITE_BUSY
|
|
176
173
|
}
|
|
177
174
|
}
|
|
178
175
|
|
|
179
|
-
if (!this.accessiblePaths.has(path) &&
|
|
180
|
-
|
|
181
|
-
throw new Error(`File ${path} not found`);
|
|
176
|
+
if (!this.accessiblePaths.has(path) && !(flags & VFS.SQLITE_OPEN_CREATE)) {
|
|
177
|
+
throw new Error(`File ${path} not found`)
|
|
182
178
|
}
|
|
183
179
|
|
|
184
|
-
const file = new File(path, flags)
|
|
185
|
-
this.mapIdToFile.set(fileId, file)
|
|
180
|
+
const file = new File(path, flags)
|
|
181
|
+
this.mapIdToFile.set(fileId, file)
|
|
186
182
|
|
|
187
183
|
if (this.persistentFiles.has(path)) {
|
|
188
|
-
file.persistentFile = this.persistentFiles.get(path)
|
|
184
|
+
file.persistentFile = this.persistentFiles.get(path)
|
|
189
185
|
} else if (this.boundAccessHandles.has(path)) {
|
|
190
186
|
// This temporary file was previously created and closed. Reopen
|
|
191
187
|
// the same access handle.
|
|
192
|
-
file.accessHandle = this.boundAccessHandles.get(path)
|
|
188
|
+
file.accessHandle = this.boundAccessHandles.get(path)
|
|
193
189
|
} else if (this.unboundAccessHandles.size) {
|
|
194
190
|
// Associate an unbound access handle to this file.
|
|
195
|
-
file.accessHandle = this.unboundAccessHandles.values().next().value
|
|
196
|
-
file.accessHandle.truncate(0)
|
|
197
|
-
this.unboundAccessHandles.delete(file.accessHandle)
|
|
198
|
-
this.boundAccessHandles.set(path, file.accessHandle)
|
|
191
|
+
file.accessHandle = this.unboundAccessHandles.values().next().value
|
|
192
|
+
file.accessHandle.truncate(0)
|
|
193
|
+
this.unboundAccessHandles.delete(file.accessHandle)
|
|
194
|
+
this.boundAccessHandles.set(path, file.accessHandle)
|
|
199
195
|
}
|
|
200
|
-
this.accessiblePaths.add(path)
|
|
201
|
-
|
|
202
|
-
pOutFlags.setInt32(0, flags, true)
|
|
203
|
-
return VFS.SQLITE_OK
|
|
196
|
+
this.accessiblePaths.add(path)
|
|
197
|
+
|
|
198
|
+
pOutFlags.setInt32(0, flags, true)
|
|
199
|
+
return VFS.SQLITE_OK
|
|
204
200
|
} catch (e) {
|
|
205
|
-
this.lastError = e
|
|
206
|
-
return VFS.SQLITE_CANTOPEN
|
|
201
|
+
this.lastError = e
|
|
202
|
+
return VFS.SQLITE_CANTOPEN
|
|
207
203
|
}
|
|
208
204
|
}
|
|
209
205
|
|
|
210
206
|
/**
|
|
211
|
-
* @param {string} zName
|
|
212
|
-
* @param {number} syncDir
|
|
207
|
+
* @param {string} zName
|
|
208
|
+
* @param {number} syncDir
|
|
213
209
|
* @returns {number}
|
|
214
210
|
*/
|
|
215
211
|
jDelete(zName, syncDir) {
|
|
216
212
|
try {
|
|
217
|
-
const url = new URL(zName, 'file://')
|
|
218
|
-
const path = url.pathname
|
|
213
|
+
const url = new URL(zName, 'file://')
|
|
214
|
+
const path = url.pathname
|
|
219
215
|
if (this.persistentFiles.has(path)) {
|
|
220
|
-
const persistentFile = this.persistentFiles.get(path)
|
|
221
|
-
persistentFile.accessHandle.truncate(0)
|
|
216
|
+
const persistentFile = this.persistentFiles.get(path)
|
|
217
|
+
persistentFile.accessHandle.truncate(0)
|
|
222
218
|
} else {
|
|
223
|
-
this.boundAccessHandles.get(path)?.truncate(0)
|
|
219
|
+
this.boundAccessHandles.get(path)?.truncate(0)
|
|
224
220
|
}
|
|
225
|
-
this.accessiblePaths.delete(path)
|
|
226
|
-
return VFS.SQLITE_OK
|
|
221
|
+
this.accessiblePaths.delete(path)
|
|
222
|
+
return VFS.SQLITE_OK
|
|
227
223
|
} catch (e) {
|
|
228
|
-
this.lastError = e
|
|
229
|
-
return VFS.SQLITE_IOERR_DELETE
|
|
224
|
+
this.lastError = e
|
|
225
|
+
return VFS.SQLITE_IOERR_DELETE
|
|
230
226
|
}
|
|
231
227
|
}
|
|
232
228
|
|
|
233
229
|
/**
|
|
234
|
-
* @param {string} zName
|
|
235
|
-
* @param {number} flags
|
|
236
|
-
* @param {DataView} pResOut
|
|
230
|
+
* @param {string} zName
|
|
231
|
+
* @param {number} flags
|
|
232
|
+
* @param {DataView} pResOut
|
|
237
233
|
* @returns {number}
|
|
238
234
|
*/
|
|
239
235
|
jAccess(zName, flags, pResOut) {
|
|
240
236
|
try {
|
|
241
|
-
const url = new URL(zName, 'file://')
|
|
242
|
-
const path = url.pathname
|
|
243
|
-
pResOut.setInt32(0, this.accessiblePaths.has(path) ? 1 : 0, true)
|
|
244
|
-
return VFS.SQLITE_OK
|
|
237
|
+
const url = new URL(zName, 'file://')
|
|
238
|
+
const path = url.pathname
|
|
239
|
+
pResOut.setInt32(0, this.accessiblePaths.has(path) ? 1 : 0, true)
|
|
240
|
+
return VFS.SQLITE_OK
|
|
245
241
|
} catch (e) {
|
|
246
|
-
this.lastError = e
|
|
247
|
-
return VFS.SQLITE_IOERR_ACCESS
|
|
248
|
-
}
|
|
242
|
+
this.lastError = e
|
|
243
|
+
return VFS.SQLITE_IOERR_ACCESS
|
|
244
|
+
}
|
|
249
245
|
}
|
|
250
246
|
|
|
251
247
|
/**
|
|
252
|
-
* @param {number} fileId
|
|
248
|
+
* @param {number} fileId
|
|
253
249
|
* @returns {number}
|
|
254
250
|
*/
|
|
255
251
|
jClose(fileId) {
|
|
256
252
|
try {
|
|
257
|
-
const file = this.mapIdToFile.get(fileId)
|
|
258
|
-
this.mapIdToFile.delete(fileId)
|
|
253
|
+
const file = this.mapIdToFile.get(fileId)
|
|
254
|
+
this.mapIdToFile.delete(fileId)
|
|
259
255
|
|
|
260
256
|
if (file?.flags & VFS.SQLITE_OPEN_MAIN_DB) {
|
|
261
257
|
if (file.persistentFile?.handleLockReleaser) {
|
|
262
|
-
this.#releaseAccessHandle(file)
|
|
258
|
+
this.#releaseAccessHandle(file)
|
|
263
259
|
}
|
|
264
260
|
} else if (file?.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {
|
|
265
|
-
file.accessHandle.truncate(0)
|
|
266
|
-
this.accessiblePaths.delete(file.path)
|
|
261
|
+
file.accessHandle.truncate(0)
|
|
262
|
+
this.accessiblePaths.delete(file.path)
|
|
267
263
|
if (!this.persistentFiles.has(file.path)) {
|
|
268
|
-
this.boundAccessHandles.delete(file.path)
|
|
269
|
-
this.unboundAccessHandles.add(file.accessHandle)
|
|
264
|
+
this.boundAccessHandles.delete(file.path)
|
|
265
|
+
this.unboundAccessHandles.add(file.accessHandle)
|
|
270
266
|
}
|
|
271
267
|
}
|
|
272
|
-
return VFS.SQLITE_OK
|
|
268
|
+
return VFS.SQLITE_OK
|
|
273
269
|
} catch (e) {
|
|
274
|
-
this.lastError = e
|
|
275
|
-
return VFS.SQLITE_IOERR_CLOSE
|
|
270
|
+
this.lastError = e
|
|
271
|
+
return VFS.SQLITE_IOERR_CLOSE
|
|
276
272
|
}
|
|
277
273
|
}
|
|
278
274
|
|
|
279
275
|
/**
|
|
280
|
-
* @param {number} fileId
|
|
281
|
-
* @param {Uint8Array} pData
|
|
276
|
+
* @param {number} fileId
|
|
277
|
+
* @param {Uint8Array} pData
|
|
282
278
|
* @param {number} iOffset
|
|
283
279
|
* @returns {number}
|
|
284
280
|
*/
|
|
285
281
|
jRead(fileId, pData, iOffset) {
|
|
286
282
|
try {
|
|
287
|
-
const file = this.mapIdToFile.get(fileId)
|
|
283
|
+
const file = this.mapIdToFile.get(fileId)
|
|
288
284
|
|
|
289
285
|
// On Chrome (at least), passing pData to accessHandle.read() is
|
|
290
286
|
// an error because pData is a Proxy of a Uint8Array. Calling
|
|
291
287
|
// subarray() produces a real Uint8Array and that works.
|
|
292
|
-
const accessHandle = file.accessHandle || file.persistentFile.accessHandle
|
|
293
|
-
const bytesRead = accessHandle.read(pData.subarray(), { at: iOffset })
|
|
288
|
+
const accessHandle = file.accessHandle || file.persistentFile.accessHandle
|
|
289
|
+
const bytesRead = accessHandle.read(pData.subarray(), { at: iOffset })
|
|
294
290
|
|
|
295
291
|
// Opening a database file performs one read without a xLock call.
|
|
296
|
-
if (
|
|
297
|
-
this.#releaseAccessHandle(file)
|
|
292
|
+
if (file.flags & VFS.SQLITE_OPEN_MAIN_DB && !file.persistentFile.isFileLocked) {
|
|
293
|
+
this.#releaseAccessHandle(file)
|
|
298
294
|
}
|
|
299
295
|
|
|
300
296
|
if (bytesRead < pData.byteLength) {
|
|
301
|
-
pData.fill(0, bytesRead)
|
|
302
|
-
return VFS.SQLITE_IOERR_SHORT_READ
|
|
297
|
+
pData.fill(0, bytesRead)
|
|
298
|
+
return VFS.SQLITE_IOERR_SHORT_READ
|
|
303
299
|
}
|
|
304
|
-
return VFS.SQLITE_OK
|
|
300
|
+
return VFS.SQLITE_OK
|
|
305
301
|
} catch (e) {
|
|
306
|
-
this.lastError = e
|
|
307
|
-
return VFS.SQLITE_IOERR_READ
|
|
302
|
+
this.lastError = e
|
|
303
|
+
return VFS.SQLITE_IOERR_READ
|
|
308
304
|
}
|
|
309
305
|
}
|
|
310
306
|
|
|
311
307
|
/**
|
|
312
|
-
* @param {number} fileId
|
|
313
|
-
* @param {Uint8Array} pData
|
|
308
|
+
* @param {number} fileId
|
|
309
|
+
* @param {Uint8Array} pData
|
|
314
310
|
* @param {number} iOffset
|
|
315
311
|
* @returns {number}
|
|
316
312
|
*/
|
|
317
313
|
jWrite(fileId, pData, iOffset) {
|
|
318
314
|
try {
|
|
319
|
-
const file = this.mapIdToFile.get(fileId)
|
|
315
|
+
const file = this.mapIdToFile.get(fileId)
|
|
320
316
|
|
|
321
317
|
// On Chrome (at least), passing pData to accessHandle.write() is
|
|
322
318
|
// an error because pData is a Proxy of a Uint8Array. Calling
|
|
323
319
|
// subarray() produces a real Uint8Array and that works.
|
|
324
|
-
const accessHandle = file.accessHandle || file.persistentFile.accessHandle
|
|
325
|
-
const nBytes = accessHandle.write(pData.subarray(), { at: iOffset })
|
|
326
|
-
if (nBytes !== pData.byteLength) throw new Error('short write')
|
|
327
|
-
return VFS.SQLITE_OK
|
|
320
|
+
const accessHandle = file.accessHandle || file.persistentFile.accessHandle
|
|
321
|
+
const nBytes = accessHandle.write(pData.subarray(), { at: iOffset })
|
|
322
|
+
if (nBytes !== pData.byteLength) throw new Error('short write')
|
|
323
|
+
return VFS.SQLITE_OK
|
|
328
324
|
} catch (e) {
|
|
329
|
-
this.lastError = e
|
|
330
|
-
return VFS.SQLITE_IOERR_WRITE
|
|
325
|
+
this.lastError = e
|
|
326
|
+
return VFS.SQLITE_IOERR_WRITE
|
|
331
327
|
}
|
|
332
328
|
}
|
|
333
329
|
|
|
334
330
|
/**
|
|
335
|
-
* @param {number} fileId
|
|
336
|
-
* @param {number} iSize
|
|
331
|
+
* @param {number} fileId
|
|
332
|
+
* @param {number} iSize
|
|
337
333
|
* @returns {number}
|
|
338
334
|
*/
|
|
339
335
|
jTruncate(fileId, iSize) {
|
|
340
336
|
try {
|
|
341
|
-
const file = this.mapIdToFile.get(fileId)
|
|
342
|
-
const accessHandle = file.accessHandle || file.persistentFile.accessHandle
|
|
343
|
-
accessHandle.truncate(iSize)
|
|
344
|
-
return VFS.SQLITE_OK
|
|
337
|
+
const file = this.mapIdToFile.get(fileId)
|
|
338
|
+
const accessHandle = file.accessHandle || file.persistentFile.accessHandle
|
|
339
|
+
accessHandle.truncate(iSize)
|
|
340
|
+
return VFS.SQLITE_OK
|
|
345
341
|
} catch (e) {
|
|
346
|
-
this.lastError = e
|
|
347
|
-
return VFS.SQLITE_IOERR_TRUNCATE
|
|
342
|
+
this.lastError = e
|
|
343
|
+
return VFS.SQLITE_IOERR_TRUNCATE
|
|
348
344
|
}
|
|
349
345
|
}
|
|
350
346
|
|
|
351
347
|
/**
|
|
352
|
-
* @param {number} fileId
|
|
353
|
-
* @param {number} flags
|
|
348
|
+
* @param {number} fileId
|
|
349
|
+
* @param {number} flags
|
|
354
350
|
* @returns {number}
|
|
355
351
|
*/
|
|
356
352
|
jSync(fileId, flags) {
|
|
357
353
|
try {
|
|
358
|
-
const file = this.mapIdToFile.get(fileId)
|
|
359
|
-
const accessHandle = file.accessHandle || file.persistentFile.accessHandle
|
|
360
|
-
accessHandle.flush()
|
|
361
|
-
return VFS.SQLITE_OK
|
|
354
|
+
const file = this.mapIdToFile.get(fileId)
|
|
355
|
+
const accessHandle = file.accessHandle || file.persistentFile.accessHandle
|
|
356
|
+
accessHandle.flush()
|
|
357
|
+
return VFS.SQLITE_OK
|
|
362
358
|
} catch (e) {
|
|
363
|
-
this.lastError = e
|
|
364
|
-
return VFS.SQLITE_IOERR_FSYNC
|
|
359
|
+
this.lastError = e
|
|
360
|
+
return VFS.SQLITE_IOERR_FSYNC
|
|
365
361
|
}
|
|
366
362
|
}
|
|
367
363
|
|
|
368
364
|
/**
|
|
369
|
-
* @param {number} fileId
|
|
370
|
-
* @param {DataView} pSize64
|
|
365
|
+
* @param {number} fileId
|
|
366
|
+
* @param {DataView} pSize64
|
|
371
367
|
* @returns {number}
|
|
372
368
|
*/
|
|
373
369
|
jFileSize(fileId, pSize64) {
|
|
374
370
|
try {
|
|
375
|
-
const file = this.mapIdToFile.get(fileId)
|
|
376
|
-
const accessHandle = file.accessHandle || file.persistentFile.accessHandle
|
|
377
|
-
const size = accessHandle.getSize()
|
|
378
|
-
pSize64.setBigInt64(0, BigInt(size), true)
|
|
379
|
-
return VFS.SQLITE_OK
|
|
371
|
+
const file = this.mapIdToFile.get(fileId)
|
|
372
|
+
const accessHandle = file.accessHandle || file.persistentFile.accessHandle
|
|
373
|
+
const size = accessHandle.getSize()
|
|
374
|
+
pSize64.setBigInt64(0, BigInt(size), true)
|
|
375
|
+
return VFS.SQLITE_OK
|
|
380
376
|
} catch (e) {
|
|
381
|
-
this.lastError = e
|
|
382
|
-
return VFS.SQLITE_IOERR_FSTAT
|
|
377
|
+
this.lastError = e
|
|
378
|
+
return VFS.SQLITE_IOERR_FSTAT
|
|
383
379
|
}
|
|
384
380
|
}
|
|
385
381
|
|
|
386
382
|
/**
|
|
387
|
-
* @param {number} fileId
|
|
388
|
-
* @param {number} lockType
|
|
383
|
+
* @param {number} fileId
|
|
384
|
+
* @param {number} lockType
|
|
389
385
|
* @returns {number}
|
|
390
386
|
*/
|
|
391
387
|
jLock(fileId, lockType) {
|
|
392
|
-
const file = this.mapIdToFile.get(fileId)
|
|
388
|
+
const file = this.mapIdToFile.get(fileId)
|
|
393
389
|
if (file.persistentFile.isRequestInProgress) {
|
|
394
|
-
file.persistentFile.isLockBusy = true
|
|
395
|
-
return VFS.SQLITE_BUSY
|
|
390
|
+
file.persistentFile.isLockBusy = true
|
|
391
|
+
return VFS.SQLITE_BUSY
|
|
396
392
|
}
|
|
397
393
|
|
|
398
|
-
file.persistentFile.isFileLocked = true
|
|
394
|
+
file.persistentFile.isFileLocked = true
|
|
399
395
|
if (!file.persistentFile.handleLockReleaser) {
|
|
400
396
|
// Start listening for notifications from other connections.
|
|
401
397
|
// This is before we actually get access handles, but waiting to
|
|
402
398
|
// listen until then allows a race condition where notifications
|
|
403
|
-
// are missed.
|
|
399
|
+
// are missed.
|
|
404
400
|
file.persistentFile.handleRequestChannel.onmessage = () => {
|
|
405
|
-
this.log?.(`received notification for ${file.path}`)
|
|
401
|
+
this.log?.(`received notification for ${file.path}`)
|
|
406
402
|
if (file.persistentFile.isFileLocked) {
|
|
407
403
|
// We're still using the access handle, so mark it to be
|
|
408
404
|
// released when we're done.
|
|
409
|
-
file.persistentFile.isHandleRequested = true
|
|
405
|
+
file.persistentFile.isHandleRequested = true
|
|
410
406
|
} else {
|
|
411
407
|
// Release the access handles immediately.
|
|
412
|
-
this.#releaseAccessHandle(file)
|
|
408
|
+
this.#releaseAccessHandle(file)
|
|
413
409
|
}
|
|
414
|
-
file.persistentFile.handleRequestChannel.onmessage = null
|
|
415
|
-
}
|
|
410
|
+
file.persistentFile.handleRequestChannel.onmessage = null
|
|
411
|
+
}
|
|
416
412
|
|
|
417
|
-
this.#requestAccessHandle(file)
|
|
418
|
-
this.log?.('returning SQLITE_BUSY')
|
|
419
|
-
file.persistentFile.isLockBusy = true
|
|
420
|
-
return VFS.SQLITE_BUSY
|
|
413
|
+
this.#requestAccessHandle(file)
|
|
414
|
+
this.log?.('returning SQLITE_BUSY')
|
|
415
|
+
file.persistentFile.isLockBusy = true
|
|
416
|
+
return VFS.SQLITE_BUSY
|
|
421
417
|
}
|
|
422
|
-
file.persistentFile.isLockBusy = false
|
|
423
|
-
return VFS.SQLITE_OK
|
|
418
|
+
file.persistentFile.isLockBusy = false
|
|
419
|
+
return VFS.SQLITE_OK
|
|
424
420
|
}
|
|
425
421
|
|
|
426
422
|
/**
|
|
427
|
-
* @param {number} fileId
|
|
428
|
-
* @param {number} lockType
|
|
423
|
+
* @param {number} fileId
|
|
424
|
+
* @param {number} lockType
|
|
429
425
|
* @returns {number}
|
|
430
426
|
*/
|
|
431
427
|
jUnlock(fileId, lockType) {
|
|
432
|
-
const file = this.mapIdToFile.get(fileId)
|
|
428
|
+
const file = this.mapIdToFile.get(fileId)
|
|
433
429
|
if (lockType === VFS.SQLITE_LOCK_NONE) {
|
|
434
430
|
// Don't change any state if this unlock is because xLock returned
|
|
435
431
|
// SQLITE_BUSY.
|
|
436
432
|
if (!file.persistentFile.isLockBusy) {
|
|
437
433
|
if (file.persistentFile.isHandleRequested) {
|
|
438
|
-
|
|
439
|
-
this.#releaseAccessHandle(file)
|
|
440
|
-
file.persistentFile.isHandleRequested = false
|
|
434
|
+
// Another connection wants the access handle.
|
|
435
|
+
this.#releaseAccessHandle(file)
|
|
436
|
+
file.persistentFile.isHandleRequested = false
|
|
441
437
|
}
|
|
442
|
-
file.persistentFile.isFileLocked = false
|
|
438
|
+
file.persistentFile.isFileLocked = false
|
|
443
439
|
}
|
|
444
440
|
}
|
|
445
|
-
return VFS.SQLITE_OK
|
|
441
|
+
return VFS.SQLITE_OK
|
|
446
442
|
}
|
|
447
|
-
|
|
443
|
+
|
|
448
444
|
/**
|
|
449
445
|
* @param {number} fileId
|
|
450
446
|
* @param {number} op
|
|
@@ -453,138 +449,140 @@ export class OPFSCoopSyncVFS extends FacadeVFS {
|
|
|
453
449
|
*/
|
|
454
450
|
jFileControl(fileId, op, pArg) {
|
|
455
451
|
try {
|
|
456
|
-
const file = this.mapIdToFile.get(fileId)
|
|
452
|
+
const file = this.mapIdToFile.get(fileId)
|
|
457
453
|
switch (op) {
|
|
458
454
|
case VFS.SQLITE_FCNTL_PRAGMA:
|
|
459
|
-
const key = extractString(pArg, 4)
|
|
460
|
-
const value = extractString(pArg, 8)
|
|
461
|
-
this.log?.('xFileControl', file.path, 'PRAGMA', key, value)
|
|
455
|
+
const key = extractString(pArg, 4)
|
|
456
|
+
const value = extractString(pArg, 8)
|
|
457
|
+
this.log?.('xFileControl', file.path, 'PRAGMA', key, value)
|
|
462
458
|
switch (key.toLowerCase()) {
|
|
463
459
|
case 'journal_mode':
|
|
464
|
-
if (value &&
|
|
465
|
-
|
|
466
|
-
throw new Error('journal_mode must be "off", "memory", "delete", or "wal"');
|
|
460
|
+
if (value && !['off', 'memory', 'delete', 'wal'].includes(value.toLowerCase())) {
|
|
461
|
+
throw new Error('journal_mode must be "off", "memory", "delete", or "wal"')
|
|
467
462
|
}
|
|
468
|
-
break
|
|
463
|
+
break
|
|
469
464
|
}
|
|
470
|
-
break
|
|
465
|
+
break
|
|
471
466
|
}
|
|
472
467
|
} catch (e) {
|
|
473
|
-
this.lastError = e
|
|
474
|
-
return VFS.SQLITE_IOERR
|
|
468
|
+
this.lastError = e
|
|
469
|
+
return VFS.SQLITE_IOERR
|
|
475
470
|
}
|
|
476
|
-
return VFS.SQLITE_NOTFOUND
|
|
471
|
+
return VFS.SQLITE_NOTFOUND
|
|
477
472
|
}
|
|
478
473
|
|
|
479
474
|
/**
|
|
480
|
-
* @param {Uint8Array} zBuf
|
|
481
|
-
* @returns
|
|
475
|
+
* @param {Uint8Array} zBuf
|
|
476
|
+
* @returns
|
|
482
477
|
*/
|
|
483
478
|
jGetLastError(zBuf) {
|
|
484
479
|
if (this.lastError) {
|
|
485
|
-
console.error(this.lastError)
|
|
486
|
-
const outputArray = zBuf.subarray(0, zBuf.byteLength - 1)
|
|
487
|
-
const { written } = new TextEncoder().encodeInto(this.lastError.message, outputArray)
|
|
488
|
-
zBuf[written] = 0
|
|
480
|
+
console.error(this.lastError)
|
|
481
|
+
const outputArray = zBuf.subarray(0, zBuf.byteLength - 1)
|
|
482
|
+
const { written } = new TextEncoder().encodeInto(this.lastError.message, outputArray)
|
|
483
|
+
zBuf[written] = 0
|
|
489
484
|
}
|
|
490
485
|
return VFS.SQLITE_OK
|
|
491
486
|
}
|
|
492
487
|
|
|
493
488
|
/**
|
|
494
|
-
* @param {FileSystemFileHandle} fileHandle
|
|
489
|
+
* @param {FileSystemFileHandle} fileHandle
|
|
495
490
|
* @returns {Promise<PersistentFile>}
|
|
496
491
|
*/
|
|
497
492
|
async #createPersistentFile(fileHandle) {
|
|
498
|
-
const persistentFile = new PersistentFile(fileHandle)
|
|
499
|
-
const root = await navigator.storage.getDirectory()
|
|
500
|
-
const relativePath = await root.resolve(fileHandle)
|
|
501
|
-
const path = `/${relativePath.join('/')}
|
|
502
|
-
persistentFile.handleRequestChannel = new BroadcastChannel(`ahp:${path}`)
|
|
503
|
-
this.persistentFiles.set(path, persistentFile)
|
|
504
|
-
|
|
505
|
-
const f = await fileHandle.getFile()
|
|
493
|
+
const persistentFile = new PersistentFile(fileHandle)
|
|
494
|
+
const root = await navigator.storage.getDirectory()
|
|
495
|
+
const relativePath = await root.resolve(fileHandle)
|
|
496
|
+
const path = `/${relativePath.join('/')}`
|
|
497
|
+
persistentFile.handleRequestChannel = new BroadcastChannel(`ahp:${path}`)
|
|
498
|
+
this.persistentFiles.set(path, persistentFile)
|
|
499
|
+
|
|
500
|
+
const f = await fileHandle.getFile()
|
|
506
501
|
if (f.size) {
|
|
507
|
-
this.accessiblePaths.add(path)
|
|
502
|
+
this.accessiblePaths.add(path)
|
|
508
503
|
}
|
|
509
|
-
return persistentFile
|
|
504
|
+
return persistentFile
|
|
510
505
|
}
|
|
511
506
|
|
|
512
507
|
/**
|
|
513
|
-
* @param {File} file
|
|
508
|
+
* @param {File} file
|
|
514
509
|
*/
|
|
515
510
|
#requestAccessHandle(file) {
|
|
516
|
-
console.assert(!file.persistentFile.handleLockReleaser)
|
|
511
|
+
console.assert(!file.persistentFile.handleLockReleaser)
|
|
517
512
|
if (!file.persistentFile.isRequestInProgress) {
|
|
518
|
-
file.persistentFile.isRequestInProgress = true
|
|
519
|
-
this._module.retryOps.push(
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
513
|
+
file.persistentFile.isRequestInProgress = true
|
|
514
|
+
this._module.retryOps.push(
|
|
515
|
+
(async () => {
|
|
516
|
+
// Acquire the Web Lock.
|
|
517
|
+
file.persistentFile.handleLockReleaser = await this.#acquireLock(file.persistentFile)
|
|
518
|
+
|
|
519
|
+
// Get access handles for the database and releated files in parallel.
|
|
520
|
+
this.log?.(`creating access handles for ${file.path}`)
|
|
521
|
+
await Promise.all(
|
|
522
|
+
DB_RELATED_FILE_SUFFIXES.map(async (suffix) => {
|
|
523
|
+
const persistentFile = this.persistentFiles.get(file.path + suffix)
|
|
524
|
+
if (persistentFile) {
|
|
525
|
+
persistentFile.accessHandle = await persistentFile.fileHandle.createSyncAccessHandle()
|
|
526
|
+
}
|
|
527
|
+
}),
|
|
528
|
+
)
|
|
529
|
+
file.persistentFile.isRequestInProgress = false
|
|
530
|
+
})(),
|
|
531
|
+
)
|
|
532
|
+
return this._module.retryOps.at(-1)
|
|
535
533
|
}
|
|
536
|
-
return Promise.resolve()
|
|
534
|
+
return Promise.resolve()
|
|
537
535
|
}
|
|
538
536
|
|
|
539
537
|
/**
|
|
540
|
-
* @param {File} file
|
|
538
|
+
* @param {File} file
|
|
541
539
|
*/
|
|
542
540
|
async #releaseAccessHandle(file) {
|
|
543
|
-
DB_RELATED_FILE_SUFFIXES.forEach(async suffix => {
|
|
544
|
-
const persistentFile = this.persistentFiles.get(file.path + suffix)
|
|
541
|
+
DB_RELATED_FILE_SUFFIXES.forEach(async (suffix) => {
|
|
542
|
+
const persistentFile = this.persistentFiles.get(file.path + suffix)
|
|
545
543
|
if (persistentFile) {
|
|
546
|
-
persistentFile.accessHandle?.close()
|
|
547
|
-
persistentFile.accessHandle = null
|
|
544
|
+
persistentFile.accessHandle?.close()
|
|
545
|
+
persistentFile.accessHandle = null
|
|
548
546
|
}
|
|
549
|
-
})
|
|
547
|
+
})
|
|
550
548
|
this.log?.(`access handles closed for ${file.path}`)
|
|
551
549
|
|
|
552
|
-
file.persistentFile.handleLockReleaser?.()
|
|
553
|
-
file.persistentFile.handleLockReleaser = null
|
|
550
|
+
file.persistentFile.handleLockReleaser?.()
|
|
551
|
+
file.persistentFile.handleLockReleaser = null
|
|
554
552
|
this.log?.(`lock released for ${file.path}`)
|
|
555
553
|
}
|
|
556
554
|
|
|
557
555
|
/**
|
|
558
|
-
* @param {PersistentFile} persistentFile
|
|
556
|
+
* @param {PersistentFile} persistentFile
|
|
559
557
|
* @returns {Promise<function>} lock releaser
|
|
560
558
|
*/
|
|
561
559
|
#acquireLock(persistentFile) {
|
|
562
|
-
return new Promise(resolve => {
|
|
560
|
+
return new Promise((resolve) => {
|
|
563
561
|
// Tell other connections we want the access handle.
|
|
564
|
-
const lockName = persistentFile.handleRequestChannel.name
|
|
562
|
+
const lockName = persistentFile.handleRequestChannel.name
|
|
565
563
|
const notify = () => {
|
|
566
|
-
this.log?.(`notifying for ${lockName}`)
|
|
567
|
-
persistentFile.handleRequestChannel.postMessage(null)
|
|
564
|
+
this.log?.(`notifying for ${lockName}`)
|
|
565
|
+
persistentFile.handleRequestChannel.postMessage(null)
|
|
568
566
|
}
|
|
569
|
-
const notifyId = setInterval(notify, LOCK_NOTIFY_INTERVAL)
|
|
570
|
-
setTimeout(notify)
|
|
567
|
+
const notifyId = setInterval(notify, LOCK_NOTIFY_INTERVAL)
|
|
568
|
+
setTimeout(notify)
|
|
571
569
|
|
|
572
570
|
this.log?.(`lock requested: ${lockName}`)
|
|
573
|
-
navigator.locks.request(lockName, lock => {
|
|
571
|
+
navigator.locks.request(lockName, (lock) => {
|
|
574
572
|
// We have the lock. Stop asking other connections for it.
|
|
575
|
-
this.log?.(`lock acquired: ${lockName}`, lock)
|
|
576
|
-
clearInterval(notifyId)
|
|
577
|
-
return new Promise(resolve)
|
|
578
|
-
})
|
|
579
|
-
})
|
|
573
|
+
this.log?.(`lock acquired: ${lockName}`, lock)
|
|
574
|
+
clearInterval(notifyId)
|
|
575
|
+
return new Promise(resolve)
|
|
576
|
+
})
|
|
577
|
+
})
|
|
580
578
|
}
|
|
581
579
|
}
|
|
582
580
|
|
|
583
581
|
function extractString(dataView, offset) {
|
|
584
|
-
const p = dataView.getUint32(offset, true)
|
|
582
|
+
const p = dataView.getUint32(offset, true)
|
|
585
583
|
if (p) {
|
|
586
|
-
const chars = new Uint8Array(dataView.buffer, p)
|
|
587
|
-
return new TextDecoder().decode(chars.subarray(0, chars.indexOf(0)))
|
|
584
|
+
const chars = new Uint8Array(dataView.buffer, p)
|
|
585
|
+
return new TextDecoder().decode(chars.subarray(0, chars.indexOf(0)))
|
|
588
586
|
}
|
|
589
|
-
return null
|
|
590
|
-
}
|
|
587
|
+
return null
|
|
588
|
+
}
|