@componentor/fs 1.2.2 → 1.2.4
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/dist/index.js +260 -192
- package/dist/index.js.map +1 -1
- package/dist/opfs-hybrid.js +260 -192
- package/dist/opfs-hybrid.js.map +1 -1
- package/dist/opfs-worker.js +260 -192
- package/dist/opfs-worker.js.map +1 -1
- package/package.json +1 -1
- package/src/packed-storage.ts +309 -241
package/src/packed-storage.ts
CHANGED
|
@@ -128,6 +128,7 @@ export class PackedStorage {
|
|
|
128
128
|
private useChecksum: boolean
|
|
129
129
|
private index: PackIndex | null = null
|
|
130
130
|
private indexLoaded = false
|
|
131
|
+
private lockPromise: Promise<void> | null = null
|
|
131
132
|
|
|
132
133
|
constructor(handleManager: HandleManager, useSync: boolean, useCompression = false, useChecksum = true) {
|
|
133
134
|
this.handleManager = handleManager
|
|
@@ -137,6 +138,23 @@ export class PackedStorage {
|
|
|
137
138
|
this.useChecksum = useChecksum
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Acquire lock for pack file access (prevents concurrent handle conflicts)
|
|
143
|
+
*/
|
|
144
|
+
private async acquireLock(): Promise<() => void> {
|
|
145
|
+
while (this.lockPromise) {
|
|
146
|
+
await this.lockPromise
|
|
147
|
+
}
|
|
148
|
+
let release: () => void
|
|
149
|
+
this.lockPromise = new Promise(resolve => {
|
|
150
|
+
release = () => {
|
|
151
|
+
this.lockPromise = null
|
|
152
|
+
resolve()
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
return release!
|
|
156
|
+
}
|
|
157
|
+
|
|
140
158
|
/**
|
|
141
159
|
* Reset pack storage state (memory only)
|
|
142
160
|
*/
|
|
@@ -163,9 +181,9 @@ export class PackedStorage {
|
|
|
163
181
|
/**
|
|
164
182
|
* Load pack index from disk (always reloads to support hybrid mode)
|
|
165
183
|
* Verifies CRC32 checksum for integrity
|
|
184
|
+
* Note: Caller must hold the lock
|
|
166
185
|
*/
|
|
167
186
|
private async loadIndex(): Promise<PackIndex> {
|
|
168
|
-
// Always reload from disk to ensure we see writes from other threads/workers
|
|
169
187
|
try {
|
|
170
188
|
const { fileHandle } = await this.handleManager.getHandle(PACK_FILE)
|
|
171
189
|
if (!fileHandle) {
|
|
@@ -174,36 +192,38 @@ export class PackedStorage {
|
|
|
174
192
|
|
|
175
193
|
if (this.useSync) {
|
|
176
194
|
const access = await fileHandle.createSyncAccessHandle()
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// Read header: index length + CRC32
|
|
184
|
-
const header = new Uint8Array(8)
|
|
185
|
-
access.read(header, { at: 0 })
|
|
186
|
-
const view = new DataView(header.buffer)
|
|
187
|
-
const indexLen = view.getUint32(0, true)
|
|
188
|
-
const storedCrc = view.getUint32(4, true)
|
|
189
|
-
|
|
190
|
-
// Read everything after header (index + data) for CRC verification
|
|
191
|
-
const contentSize = size - 8
|
|
192
|
-
const content = new Uint8Array(contentSize)
|
|
193
|
-
access.read(content, { at: 8 })
|
|
194
|
-
access.close()
|
|
195
|
+
try {
|
|
196
|
+
const size = access.getSize()
|
|
197
|
+
if (size < 8) {
|
|
198
|
+
return {}
|
|
199
|
+
}
|
|
195
200
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
+
// Read header: index length + CRC32
|
|
202
|
+
const header = new Uint8Array(8)
|
|
203
|
+
access.read(header, { at: 0 })
|
|
204
|
+
const view = new DataView(header.buffer)
|
|
205
|
+
const indexLen = view.getUint32(0, true)
|
|
206
|
+
const storedCrc = view.getUint32(4, true)
|
|
207
|
+
|
|
208
|
+
// Read everything after header (index + data) for CRC verification
|
|
209
|
+
const contentSize = size - 8
|
|
210
|
+
const content = new Uint8Array(contentSize)
|
|
211
|
+
access.read(content, { at: 8 })
|
|
212
|
+
|
|
213
|
+
// Verify CRC32 if enabled
|
|
214
|
+
if (this.useChecksum && storedCrc !== 0) {
|
|
215
|
+
const calculatedCrc = crc32(content)
|
|
216
|
+
if (calculatedCrc !== storedCrc) {
|
|
217
|
+
throw createECORRUPTED(PACK_FILE)
|
|
218
|
+
}
|
|
201
219
|
}
|
|
202
|
-
}
|
|
203
220
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
221
|
+
// Parse index from content
|
|
222
|
+
const indexJson = new TextDecoder().decode(content.subarray(0, indexLen))
|
|
223
|
+
return JSON.parse(indexJson)
|
|
224
|
+
} finally {
|
|
225
|
+
access.close()
|
|
226
|
+
}
|
|
207
227
|
} else {
|
|
208
228
|
const file = await fileHandle.getFile()
|
|
209
229
|
const data = new Uint8Array(await file.arrayBuffer())
|
|
@@ -236,8 +256,13 @@ export class PackedStorage {
|
|
|
236
256
|
* Check if a path exists in the pack
|
|
237
257
|
*/
|
|
238
258
|
async has(path: string): Promise<boolean> {
|
|
239
|
-
const
|
|
240
|
-
|
|
259
|
+
const release = await this.acquireLock()
|
|
260
|
+
try {
|
|
261
|
+
const index = await this.loadIndex()
|
|
262
|
+
return path in index
|
|
263
|
+
} finally {
|
|
264
|
+
release()
|
|
265
|
+
}
|
|
241
266
|
}
|
|
242
267
|
|
|
243
268
|
/**
|
|
@@ -245,10 +270,15 @@ export class PackedStorage {
|
|
|
245
270
|
* Returns originalSize if compressed, otherwise size
|
|
246
271
|
*/
|
|
247
272
|
async getSize(path: string): Promise<number | null> {
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
273
|
+
const release = await this.acquireLock()
|
|
274
|
+
try {
|
|
275
|
+
const index = await this.loadIndex()
|
|
276
|
+
const entry = index[path]
|
|
277
|
+
if (!entry) return null
|
|
278
|
+
return entry.originalSize ?? entry.size
|
|
279
|
+
} finally {
|
|
280
|
+
release()
|
|
281
|
+
}
|
|
252
282
|
}
|
|
253
283
|
|
|
254
284
|
/**
|
|
@@ -256,32 +286,40 @@ export class PackedStorage {
|
|
|
256
286
|
* Handles decompression if file was stored compressed
|
|
257
287
|
*/
|
|
258
288
|
async read(path: string): Promise<Uint8Array | null> {
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
289
|
+
const release = await this.acquireLock()
|
|
290
|
+
try {
|
|
291
|
+
const index = await this.loadIndex()
|
|
292
|
+
const entry = index[path]
|
|
293
|
+
if (!entry) return null
|
|
262
294
|
|
|
263
|
-
|
|
264
|
-
|
|
295
|
+
const { fileHandle } = await this.handleManager.getHandle(PACK_FILE)
|
|
296
|
+
if (!fileHandle) return null
|
|
265
297
|
|
|
266
|
-
|
|
298
|
+
let buffer: Uint8Array
|
|
267
299
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
300
|
+
if (this.useSync) {
|
|
301
|
+
const access = await fileHandle.createSyncAccessHandle()
|
|
302
|
+
try {
|
|
303
|
+
buffer = new Uint8Array(entry.size)
|
|
304
|
+
access.read(buffer, { at: entry.offset })
|
|
305
|
+
} finally {
|
|
306
|
+
access.close()
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
const file = await fileHandle.getFile()
|
|
310
|
+
const data = new Uint8Array(await file.arrayBuffer())
|
|
311
|
+
buffer = data.slice(entry.offset, entry.offset + entry.size)
|
|
312
|
+
}
|
|
278
313
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
314
|
+
// Decompress if needed
|
|
315
|
+
if (entry.originalSize !== undefined) {
|
|
316
|
+
return decompress(buffer)
|
|
317
|
+
}
|
|
283
318
|
|
|
284
|
-
|
|
319
|
+
return buffer
|
|
320
|
+
} finally {
|
|
321
|
+
release()
|
|
322
|
+
}
|
|
285
323
|
}
|
|
286
324
|
|
|
287
325
|
/**
|
|
@@ -293,66 +331,74 @@ export class PackedStorage {
|
|
|
293
331
|
const results = new Map<string, Uint8Array | null>()
|
|
294
332
|
if (paths.length === 0) return results
|
|
295
333
|
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
334
|
+
const release = await this.acquireLock()
|
|
335
|
+
try {
|
|
336
|
+
const index = await this.loadIndex()
|
|
337
|
+
|
|
338
|
+
// Find which paths are in the pack
|
|
339
|
+
const toRead: Array<{ path: string; offset: number; size: number; originalSize?: number }> = []
|
|
340
|
+
for (const path of paths) {
|
|
341
|
+
const entry = index[path]
|
|
342
|
+
if (entry) {
|
|
343
|
+
toRead.push({ path, offset: entry.offset, size: entry.size, originalSize: entry.originalSize })
|
|
344
|
+
} else {
|
|
345
|
+
results.set(path, null)
|
|
346
|
+
}
|
|
306
347
|
}
|
|
307
|
-
}
|
|
308
348
|
|
|
309
|
-
|
|
349
|
+
if (toRead.length === 0) return results
|
|
310
350
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
351
|
+
const { fileHandle } = await this.handleManager.getHandle(PACK_FILE)
|
|
352
|
+
if (!fileHandle) {
|
|
353
|
+
for (const { path } of toRead) {
|
|
354
|
+
results.set(path, null)
|
|
355
|
+
}
|
|
356
|
+
return results
|
|
315
357
|
}
|
|
316
|
-
return results
|
|
317
|
-
}
|
|
318
358
|
|
|
319
|
-
|
|
320
|
-
|
|
359
|
+
// Read all files
|
|
360
|
+
const decompressPromises: Array<{ path: string; promise: Promise<Uint8Array> }> = []
|
|
321
361
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
362
|
+
if (this.useSync) {
|
|
363
|
+
const access = await fileHandle.createSyncAccessHandle()
|
|
364
|
+
try {
|
|
365
|
+
for (const { path, offset, size, originalSize } of toRead) {
|
|
366
|
+
const buffer = new Uint8Array(size)
|
|
367
|
+
access.read(buffer, { at: offset })
|
|
368
|
+
|
|
369
|
+
if (originalSize !== undefined) {
|
|
370
|
+
// Queue for decompression
|
|
371
|
+
decompressPromises.push({ path, promise: decompress(buffer) })
|
|
372
|
+
} else {
|
|
373
|
+
results.set(path, buffer)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
} finally {
|
|
377
|
+
access.close()
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
const file = await fileHandle.getFile()
|
|
381
|
+
const data = new Uint8Array(await file.arrayBuffer())
|
|
382
|
+
for (const { path, offset, size, originalSize } of toRead) {
|
|
383
|
+
const buffer = data.slice(offset, offset + size)
|
|
327
384
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
385
|
+
if (originalSize !== undefined) {
|
|
386
|
+
decompressPromises.push({ path, promise: decompress(buffer) })
|
|
387
|
+
} else {
|
|
388
|
+
results.set(path, buffer)
|
|
389
|
+
}
|
|
333
390
|
}
|
|
334
391
|
}
|
|
335
|
-
access.close()
|
|
336
|
-
} else {
|
|
337
|
-
const file = await fileHandle.getFile()
|
|
338
|
-
const data = new Uint8Array(await file.arrayBuffer())
|
|
339
|
-
for (const { path, offset, size, originalSize } of toRead) {
|
|
340
|
-
const buffer = data.slice(offset, offset + size)
|
|
341
392
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
results.set(path, buffer)
|
|
346
|
-
}
|
|
393
|
+
// Wait for all decompressions
|
|
394
|
+
for (const { path, promise } of decompressPromises) {
|
|
395
|
+
results.set(path, await promise)
|
|
347
396
|
}
|
|
348
|
-
}
|
|
349
397
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
398
|
+
return results
|
|
399
|
+
} finally {
|
|
400
|
+
release()
|
|
353
401
|
}
|
|
354
|
-
|
|
355
|
-
return results
|
|
356
402
|
}
|
|
357
403
|
|
|
358
404
|
/**
|
|
@@ -365,85 +411,91 @@ export class PackedStorage {
|
|
|
365
411
|
async writeBatch(entries: Array<{ path: string; data: Uint8Array }>): Promise<void> {
|
|
366
412
|
if (entries.length === 0) return
|
|
367
413
|
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
processedEntries
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
414
|
+
const release = await this.acquireLock()
|
|
415
|
+
try {
|
|
416
|
+
const encoder = new TextEncoder()
|
|
417
|
+
|
|
418
|
+
// Compress data if enabled
|
|
419
|
+
let processedEntries: Array<{ path: string; data: Uint8Array; originalSize?: number }>
|
|
420
|
+
if (this.useCompression) {
|
|
421
|
+
processedEntries = await Promise.all(
|
|
422
|
+
entries.map(async ({ path, data }) => {
|
|
423
|
+
const compressed = await compress(data)
|
|
424
|
+
// Only use compressed if it's actually smaller
|
|
425
|
+
if (compressed.length < data.length) {
|
|
426
|
+
return { path, data: compressed, originalSize: data.length }
|
|
427
|
+
}
|
|
428
|
+
return { path, data }
|
|
429
|
+
})
|
|
430
|
+
)
|
|
431
|
+
} else {
|
|
432
|
+
processedEntries = entries
|
|
433
|
+
}
|
|
386
434
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
435
|
+
// Calculate total data size (using compressed sizes where applicable)
|
|
436
|
+
let totalDataSize = 0
|
|
437
|
+
for (const { data } of processedEntries) {
|
|
438
|
+
totalDataSize += data.length
|
|
439
|
+
}
|
|
392
440
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
441
|
+
// Build index - iterate until offsets stabilize
|
|
442
|
+
// (offset changes -> JSON length changes -> header size changes -> offset changes)
|
|
443
|
+
// Header format: [index length: 4][CRC32: 4][JSON index][file data...]
|
|
444
|
+
const newIndex: PackIndex = {}
|
|
445
|
+
let headerSize = 8 // 4 bytes index length + 4 bytes CRC32
|
|
446
|
+
let prevHeaderSize = 0
|
|
447
|
+
|
|
448
|
+
// Iterate until stable (usually 2-3 iterations)
|
|
449
|
+
while (headerSize !== prevHeaderSize) {
|
|
450
|
+
prevHeaderSize = headerSize
|
|
451
|
+
|
|
452
|
+
let currentOffset = headerSize
|
|
453
|
+
for (const { path, data, originalSize } of processedEntries) {
|
|
454
|
+
const entry: PackIndexEntry = { offset: currentOffset, size: data.length }
|
|
455
|
+
if (originalSize !== undefined) {
|
|
456
|
+
entry.originalSize = originalSize
|
|
457
|
+
}
|
|
458
|
+
newIndex[path] = entry
|
|
459
|
+
currentOffset += data.length
|
|
409
460
|
}
|
|
410
|
-
newIndex[path] = entry
|
|
411
|
-
currentOffset += data.length
|
|
412
|
-
}
|
|
413
461
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
462
|
+
const indexBuf = encoder.encode(JSON.stringify(newIndex))
|
|
463
|
+
headerSize = 8 + indexBuf.length
|
|
464
|
+
}
|
|
417
465
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
466
|
+
// Build the complete pack file
|
|
467
|
+
const finalIndexBuf = encoder.encode(JSON.stringify(newIndex))
|
|
468
|
+
const totalSize = headerSize + totalDataSize
|
|
469
|
+
const packBuffer = new Uint8Array(totalSize)
|
|
470
|
+
const view = new DataView(packBuffer.buffer)
|
|
423
471
|
|
|
424
|
-
|
|
425
|
-
|
|
472
|
+
// Write index JSON at offset 8
|
|
473
|
+
packBuffer.set(finalIndexBuf, 8)
|
|
426
474
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
475
|
+
// Write data at correct offsets
|
|
476
|
+
for (const { path, data } of processedEntries) {
|
|
477
|
+
const entry = newIndex[path]
|
|
478
|
+
packBuffer.set(data, entry.offset)
|
|
479
|
+
}
|
|
432
480
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
481
|
+
// Calculate CRC32 over content (index + data, everything after header) if enabled
|
|
482
|
+
const content = packBuffer.subarray(8)
|
|
483
|
+
const checksum = this.useChecksum ? crc32(content) : 0
|
|
436
484
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
485
|
+
// Write header (index length + CRC32)
|
|
486
|
+
view.setUint32(0, finalIndexBuf.length, true)
|
|
487
|
+
view.setUint32(4, checksum, true)
|
|
440
488
|
|
|
441
|
-
|
|
442
|
-
|
|
489
|
+
await this.writePackFile(packBuffer)
|
|
490
|
+
this.index = newIndex
|
|
491
|
+
} finally {
|
|
492
|
+
release()
|
|
493
|
+
}
|
|
443
494
|
}
|
|
444
495
|
|
|
445
496
|
/**
|
|
446
497
|
* Write the pack file to OPFS
|
|
498
|
+
* Note: Caller must hold the lock
|
|
447
499
|
*/
|
|
448
500
|
private async writePackFile(data: Uint8Array): Promise<void> {
|
|
449
501
|
const { fileHandle } = await this.handleManager.getHandle(PACK_FILE, { create: true })
|
|
@@ -451,9 +503,12 @@ export class PackedStorage {
|
|
|
451
503
|
|
|
452
504
|
if (this.useSync) {
|
|
453
505
|
const access = await fileHandle.createSyncAccessHandle()
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
506
|
+
try {
|
|
507
|
+
access.truncate(data.length)
|
|
508
|
+
access.write(data, { at: 0 })
|
|
509
|
+
} finally {
|
|
510
|
+
access.close()
|
|
511
|
+
}
|
|
457
512
|
} else {
|
|
458
513
|
const writable = await fileHandle.createWritable()
|
|
459
514
|
await writable.write(data)
|
|
@@ -466,98 +521,111 @@ export class PackedStorage {
|
|
|
466
521
|
* Note: Doesn't reclaim space, just removes from index and recalculates CRC32
|
|
467
522
|
*/
|
|
468
523
|
async remove(path: string): Promise<boolean> {
|
|
469
|
-
const
|
|
470
|
-
|
|
524
|
+
const release = await this.acquireLock()
|
|
525
|
+
try {
|
|
526
|
+
const index = await this.loadIndex()
|
|
527
|
+
if (!(path in index)) return false
|
|
471
528
|
|
|
472
|
-
|
|
529
|
+
delete index[path]
|
|
473
530
|
|
|
474
|
-
|
|
475
|
-
|
|
531
|
+
const { fileHandle } = await this.handleManager.getHandle(PACK_FILE)
|
|
532
|
+
if (!fileHandle) return true
|
|
476
533
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
534
|
+
// Need to read existing file to recalculate CRC32
|
|
535
|
+
const encoder = new TextEncoder()
|
|
536
|
+
const newIndexBuf = encoder.encode(JSON.stringify(index))
|
|
480
537
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
538
|
+
if (this.useSync) {
|
|
539
|
+
const access = await fileHandle.createSyncAccessHandle()
|
|
540
|
+
try {
|
|
541
|
+
const size = access.getSize()
|
|
542
|
+
|
|
543
|
+
// Read old header to get old index length
|
|
544
|
+
const oldHeader = new Uint8Array(8)
|
|
545
|
+
access.read(oldHeader, { at: 0 })
|
|
546
|
+
const oldIndexLen = new DataView(oldHeader.buffer).getUint32(0, true)
|
|
547
|
+
|
|
548
|
+
// Read data portion (after old index)
|
|
549
|
+
const dataStart = 8 + oldIndexLen
|
|
550
|
+
const dataSize = size - dataStart
|
|
551
|
+
const dataPortion = new Uint8Array(dataSize)
|
|
552
|
+
if (dataSize > 0) {
|
|
553
|
+
access.read(dataPortion, { at: dataStart })
|
|
554
|
+
}
|
|
497
555
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
556
|
+
// Build new content (new index + data)
|
|
557
|
+
const newContent = new Uint8Array(newIndexBuf.length + dataSize)
|
|
558
|
+
newContent.set(newIndexBuf, 0)
|
|
559
|
+
if (dataSize > 0) {
|
|
560
|
+
newContent.set(dataPortion, newIndexBuf.length)
|
|
561
|
+
}
|
|
504
562
|
|
|
505
|
-
|
|
506
|
-
|
|
563
|
+
// Calculate new CRC32 if enabled
|
|
564
|
+
const checksum = this.useChecksum ? crc32(newContent) : 0
|
|
507
565
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
566
|
+
// Build new header
|
|
567
|
+
const newHeader = new Uint8Array(8)
|
|
568
|
+
const view = new DataView(newHeader.buffer)
|
|
569
|
+
view.setUint32(0, newIndexBuf.length, true)
|
|
570
|
+
view.setUint32(4, checksum, true)
|
|
513
571
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
572
|
+
// Write new file
|
|
573
|
+
const newFile = new Uint8Array(8 + newContent.length)
|
|
574
|
+
newFile.set(newHeader, 0)
|
|
575
|
+
newFile.set(newContent, 8)
|
|
518
576
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
577
|
+
access.truncate(newFile.length)
|
|
578
|
+
access.write(newFile, { at: 0 })
|
|
579
|
+
} finally {
|
|
580
|
+
access.close()
|
|
581
|
+
}
|
|
582
|
+
} else {
|
|
583
|
+
// For non-sync, rewrite the whole file
|
|
584
|
+
const file = await fileHandle.getFile()
|
|
585
|
+
const oldData = new Uint8Array(await file.arrayBuffer())
|
|
526
586
|
|
|
527
|
-
|
|
587
|
+
if (oldData.length < 8) return true
|
|
528
588
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
589
|
+
const oldIndexLen = new DataView(oldData.buffer).getUint32(0, true)
|
|
590
|
+
const dataStart = 8 + oldIndexLen
|
|
591
|
+
const dataPortion = oldData.subarray(dataStart)
|
|
532
592
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
593
|
+
// Build new content
|
|
594
|
+
const newContent = new Uint8Array(newIndexBuf.length + dataPortion.length)
|
|
595
|
+
newContent.set(newIndexBuf, 0)
|
|
596
|
+
newContent.set(dataPortion, newIndexBuf.length)
|
|
537
597
|
|
|
538
|
-
|
|
539
|
-
|
|
598
|
+
// Calculate CRC32 if enabled
|
|
599
|
+
const checksum = this.useChecksum ? crc32(newContent) : 0
|
|
540
600
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
601
|
+
// Build new file
|
|
602
|
+
const newFile = new Uint8Array(8 + newContent.length)
|
|
603
|
+
const view = new DataView(newFile.buffer)
|
|
604
|
+
view.setUint32(0, newIndexBuf.length, true)
|
|
605
|
+
view.setUint32(4, checksum, true)
|
|
606
|
+
newFile.set(newContent, 8)
|
|
547
607
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
608
|
+
const writable = await fileHandle.createWritable()
|
|
609
|
+
await writable.write(newFile)
|
|
610
|
+
await writable.close()
|
|
611
|
+
}
|
|
552
612
|
|
|
553
|
-
|
|
613
|
+
return true
|
|
614
|
+
} finally {
|
|
615
|
+
release()
|
|
616
|
+
}
|
|
554
617
|
}
|
|
555
618
|
|
|
556
619
|
/**
|
|
557
620
|
* Check if pack file is being used (has entries)
|
|
558
621
|
*/
|
|
559
622
|
async isEmpty(): Promise<boolean> {
|
|
560
|
-
const
|
|
561
|
-
|
|
623
|
+
const release = await this.acquireLock()
|
|
624
|
+
try {
|
|
625
|
+
const index = await this.loadIndex()
|
|
626
|
+
return Object.keys(index).length === 0
|
|
627
|
+
} finally {
|
|
628
|
+
release()
|
|
629
|
+
}
|
|
562
630
|
}
|
|
563
631
|
}
|