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