( originalPack: Pack, tempPath: string, renameMap: Map<string, PackedFile>, )
| 3408 | } |
| 3409 | }; |
| 3410 | const createPackWithRenamedFilesWithOptions = async ( |
| 3411 | originalPack: Pack, |
| 3412 | tempPath: string, |
| 3413 | renameMap: Map<string, PackedFile>, |
| 3414 | ): Promise<void> => { |
| 3415 | let outFile: BinaryFile | undefined; |
| 3416 | let sourceFileId = -1; |
| 3417 | try { |
| 3418 | // Create the final list of packed files |
| 3419 | const finalPackedFiles: PackedFile[] = []; |
| 3420 | // Add all files, using renamed versions where applicable |
| 3421 | for (const originalFile of originalPack.packedFiles) { |
| 3422 | const renamedFile = renameMap.get(originalFile.name); |
| 3423 | if (renamedFile) { |
| 3424 | finalPackedFiles.push(renamedFile); |
| 3425 | } else { |
| 3426 | finalPackedFiles.push(originalFile); |
| 3427 | } |
| 3428 | } |
| 3429 | // Write the new pack file |
| 3430 | outFile = new BinaryFile(tempPath, "w", true); |
| 3431 | sourceFileId = fs.openSync(originalPack.path, "r"); |
| 3432 | await outFile.open(); |
| 3433 | // Write header |
| 3434 | await writePackHeader( |
| 3435 | outFile, |
| 3436 | originalPack.packHeader, |
| 3437 | finalPackedFiles, |
| 3438 | originalPack.dependencyPacks || [], |
| 3439 | ); |
| 3440 | // Pre-build file lookup map for O(1) instead of O(N) lookups |
| 3441 | const originalFileMap = new Map<string, PackedFile>(); |
| 3442 | for (const pf of originalPack.packedFiles) { |
| 3443 | originalFileMap.set(pf.name, pf); |
| 3444 | } |
| 3445 | // Build reverse lookup for renamed files |
| 3446 | const renamedToOriginalMap = new Map<string, string>(); |
| 3447 | for (const [originalName, renamedFile] of renameMap.entries()) { |
| 3448 | renamedToOriginalMap.set(renamedFile.name, originalName); |
| 3449 | } |
| 3450 | // Sort files by original pack position for sequential disk reads (much faster I/O) |
| 3451 | finalPackedFiles.sort((a, b) => { |
| 3452 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
| 3453 | const origA = originalFileMap.get(a.name) || originalFileMap.get(renamedToOriginalMap.get(a.name)!); |
| 3454 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
| 3455 | const origB = originalFileMap.get(b.name) || originalFileMap.get(renamedToOriginalMap.get(b.name)!); |
| 3456 | return (origA?.start_pos || 0) - (origB?.start_pos || 0); |
| 3457 | }); |
| 3458 | // Create buffer pool for efficient memory reuse |
| 3459 | const bufferPool = new BufferPool(); |
| 3460 | // Optimized batching for files mostly <1MB |
| 3461 | const SMALL_FILE_BATCH_SIZE = 2 * 1024 * 1024; // 2MB batches for small files |
| 3462 | const LARGE_FILE_THRESHOLD = 1024 * 1024; // 1MB threshold |
| 3463 | let currentBatchSize = 0; |
| 3464 | let batchBuffers: { buffer: Buffer; actualSize: number; poolBuffer: Buffer }[] = []; |
| 3465 | const flushBatch = async () => { |
| 3466 | if (batchBuffers.length > 0) { |
| 3467 | // Create array of buffer slices for concatenation |
no test coverage detected