UNPKG

tarmap

Version:
221 lines (187 loc) 6.73 kB
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.TarMap = void 0; var _zlib = require("zlib"); var _pifs = _interopRequireDefault(require("pifs")); const HEADER_SIZE = 512; const chksum = block => { let sum = 8 * 32; for (let i = 0; i < 148; i++) { sum += block[i]; } for (let j = 156; j < HEADER_SIZE; j++) { sum += block[j]; } return sum; }; const trimBufferString = str => { for (let i = 0; i < str.length; i++) { if (str.charCodeAt(i) === 0) { return str.substr(0, i); } } return str; }; const encodeOct = (val, n) => { const valStr = val.toString(8); if (valStr.length > n) { throw new Error('Encoded value length is greater than requested space'); } return `${valStr.padStart(n, '0')} `; }; const generateHeader = (name, size) => { const buf = Buffer.alloc(HEADER_SIZE); buf.write(name); buf.write(encodeOct(0o644, 6), 100); buf.write(encodeOct(0, 6), 108); buf.write(encodeOct(0, 6), 116); buf.write(encodeOct(size, 11), 124); buf.write(encodeOct(Math.floor(Date.now() / 1000), 11), 136); buf.write('0', 156); buf.write('ustar', 257); buf.write(encodeOct(0, 2), 263); buf.write(encodeOct(0, 6), 329); buf.write(encodeOct(0, 6), 337); buf.write(encodeOct(chksum(buf), 6), 148); return buf; }; const TarMap = async tarFilePath => { let fd = null; const files = new Map(); const filesToWrite = new Map(); const filesToDelete = new Set(); let pos = 0; const nameBuffer = Buffer.alloc(100); const sizeBuffer = Buffer.alloc(12); let tempFilePath = tarFilePath; try { await _pifs.default.access(tarFilePath); tempFilePath = tarFilePath.replace('.gz', ''); await new Promise((resolve, reject) => { _pifs.default.createReadStream(tarFilePath).on('error', reject).pipe((0, _zlib.createGunzip)()).on('error', reject).pipe(_pifs.default.createWriteStream(tempFilePath)).on('error', reject).on('finish', resolve); }); fd = await _pifs.default.open(tempFilePath, 'r'); while (true) { await _pifs.default.read(fd, nameBuffer, 0, 100, pos); const hashedFileName = trimBufferString(nameBuffer.toString()); if (hashedFileName.length === 0) { break; } await _pifs.default.read(fd, sizeBuffer, 0, 12, pos + 124); const dataSize = parseInt(sizeBuffer.toString(), 8); files.set(hashedFileName, { position: pos + HEADER_SIZE, size: dataSize }); pos += HEADER_SIZE + dataSize + (HEADER_SIZE - dataSize % HEADER_SIZE); } } catch {} return { has: fileName => files.has(fileName), list: () => { const tempSet = new Set([...files.keys(), ...filesToWrite.keys()]); for (const fileToDelete of filesToDelete) { tempSet.delete(fileToDelete); } return Array.from(tempSet); }, read: async fileName => { if (filesToWrite.has(fileName)) { return filesToWrite.get(fileName); } if (filesToDelete.has(fileName)) { return null; } if (fd === null) { return null; } const { position, size } = files.get(fileName); const dataBuffer = Buffer.alloc(size); await _pifs.default.read(fd, dataBuffer, 0, size, position); return dataBuffer; }, delete: fileName => { filesToDelete.add(fileName); }, write: (fileName, data) => { filesToWrite.set(fileName, data); }, save: async () => { if (filesToWrite.size === 0 && filesToDelete.size === 0) { if (fd !== null) { await _pifs.default.close(fd); } return; } const tempSaveFilePath = `${tempFilePath}.tmp`; const tempFd = await _pifs.default.open(tempSaveFilePath, 'w'); let tempPos = 0; if (fd !== null) { for (const fileName of files.keys()) { if (filesToWrite.has(fileName)) { const data = filesToWrite.get(fileName); const dataBufferSize = data.byteLength; const headerBuffer = generateHeader(fileName, dataBufferSize); await _pifs.default.write(tempFd, headerBuffer, 0, HEADER_SIZE, tempPos); await _pifs.default.write(tempFd, data, 0, dataBufferSize, tempPos + HEADER_SIZE); tempPos += HEADER_SIZE + dataBufferSize + (HEADER_SIZE - dataBufferSize % HEADER_SIZE); } else if (!filesToDelete.has(fileName)) { const { position, size } = files.get(fileName); const headerBuffer = Buffer.alloc(HEADER_SIZE); const dataBuffer = Buffer.alloc(size); await _pifs.default.read(fd, headerBuffer, 0, HEADER_SIZE, position - HEADER_SIZE); await _pifs.default.read(fd, dataBuffer, 0, size, position); await _pifs.default.write(tempFd, headerBuffer, 0, HEADER_SIZE, tempPos); await _pifs.default.write(tempFd, dataBuffer, 0, size, tempPos + HEADER_SIZE); tempPos += HEADER_SIZE + size + (HEADER_SIZE - size % HEADER_SIZE); } } } for (const filePath of filesToWrite.keys()) { if (!files.has(filePath)) { const data = filesToWrite.get(filePath); const size = data.byteLength; const headerBuffer = generateHeader(filePath, size); await _pifs.default.write(tempFd, headerBuffer, 0, HEADER_SIZE, tempPos); await _pifs.default.write(tempFd, data, 0, size, tempPos + HEADER_SIZE); tempPos += HEADER_SIZE + size + (HEADER_SIZE - size % HEADER_SIZE); } } files.clear(); filesToWrite.clear(); filesToDelete.clear(); await _pifs.default.write(tempFd, Buffer.alloc(1024), 0, 1024, tempPos); if (fd !== null) { await _pifs.default.close(fd); fd = null; await _pifs.default.unlink(tempFilePath); } await _pifs.default.close(tempFd); await new Promise((resolve, reject) => { _pifs.default.createReadStream(tempSaveFilePath).on('error', reject).pipe((0, _zlib.createGzip)({ level: _zlib.constants.Z_BEST_COMPRESSION })).on('error', reject).pipe(_pifs.default.createWriteStream(tarFilePath)).on('error', reject).on('finish', resolve); }); await _pifs.default.unlink(tempSaveFilePath); }, close: async () => { files.clear(); filesToWrite.clear(); filesToDelete.clear(); if (fd !== null) { await _pifs.default.close(fd); fd = null; await _pifs.default.unlink(tempFilePath); } } }; }; exports.TarMap = TarMap;