tarmap
Version:
221 lines (187 loc) • 6.73 kB
JavaScript
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;