tar-async
Version:
Asynchronous tar and untar
245 lines (197 loc) • 5.25 kB
JavaScript
(function () {
"use strict";
var Stream = require('stream').Stream,
headerFormat = require('./header').structure,
buffer,
totalRead = 0,
recordSize = 512,
fileStream,
leftToRead,
fileTypes = [
'normal', 'hard-link', 'symbolic-link', 'character-special', 'block-special', 'directory', 'fifo', 'contiguous-file'
];
function filterDecoder(input) {
var filter = [];
if (!input) {
return [0, 7];
}
if (typeof input === 'string') {
input = [].push(input);
}
if (!(input instanceof Array)) {
console.error('Invalid fileType. Only Arrays or strings are accepted');
return;
}
input.forEach(function (i) {
var index = fileTypes.indexOf(i);
if (index < 0) {
console.error('Filetype not valid. Ignoring input:', i);
return;
}
filter.push(i);
});
return filter;
}
function readInt(value) {
return parseInt(value.replace(/^0*/, ''), 8) || 0;
}
function readString(buf) {
var i, length;
for (i = 0, length = buf.length; i < buf.length; i += 1) {
if (buf[i] === 0) {
return buf.toString('utf8', 0, i);
}
}
}
function doHeader(buf, cb) {
var data = {}, offset = 0, checksum = 0;
function updateChecksum(value) {
var i, length;
for (i = 0, length = value.length; i < length; i += 1) {
checksum += value.charCodeAt(i);
}
}
headerFormat.some(function (field) {
var tBuf = buf.slice(offset, offset + field.length),
tString = tBuf.toString();
offset += field.length;
if (field.field === 'ustar' && !/ustar/.test(tString)) {
// end the loop if not using the extended header
return true;
} else if (field.field === 'checksum') {
updateChecksum(' ');
} else {
updateChecksum(tString);
}
if (field.type === 'string') {
data[field.field] = readString(tBuf);
} else if (field.type === 'number') {
data[field.field] = readInt(tString);
}
});
if (typeof cb === 'function') {
if (checksum !== data.checksum) {
return cb.call(this, 'Checksum not equal', checksum, data.checksum);
}
cb.call(this, null, data, recordSize);
}
}
/*
* Extract data from an input.
*
* @param opts- object of options
* @param cb- callback for each file
*/
function Untar(opts, cb) {
if (typeof opts === 'function') {
cb = opts;
opts = {};
}
// I should probably actually add some options...
opts = opts || {};
this.fileTypes = filterDecoder(opts.filter);
this.cb = cb;
}
Untar.prototype = Object.create(Stream.prototype, {
constructor: {
value: Untar
}
});
Untar.prototype.end = function (data, encoding) {
if (data) {
this.write(data, encoding);
}
};
Untar.prototype.write = function write(data, encoding) {
var buf, tBuf, bytesBuffer;
// get a Buffer object
if (typeof data === 'string') {
buf = new Buffer(data, encoding);
} else if (typeof data === 'array') {
buf = new Buffer(data);
} else {
buf = data;
}
if (!buf) {
tBuf = buffer;
} else if (buffer) {
// create new buffer with old and new data
tBuf = new Buffer(buffer.length + buf.length);
buffer.copy(tBuf);
buf.copy(tBuf, buffer.length);
} else {
tBuf = buf;
}
// clear old buffer
buffer = undefined;
// nothing to do, just give up ='(
if (!tBuf || tBuf.length === 0) {
return;
}
// stream the file
if (fileStream) {
if (tBuf.length >= leftToRead) {
fileStream.emit('data', tBuf.slice(0, leftToRead));
fileStream.emit('end');
fileStream = undefined;
buffer = tBuf.slice(leftToRead);
totalRead += leftToRead;
leftToRead = 0;
return;
}
fileStream.emit('data', tBuf);
leftToRead -= tBuf.length;
totalRead += tBuf.length;
return;
}
// no file to read, so let's try reading a header
// if we're not an even multiple, account for trailing nulls
if (totalRead % recordSize) {
bytesBuffer = recordSize - (totalRead % recordSize);
// if we don't have enough bytes to account for the nulls
if (tBuf.length < bytesBuffer) {
totalRead += bytesBuffer;
return;
}
// throw away trailing nulls
tBuf = tBuf.slice(bytesBuffer);
totalRead += bytesBuffer;
}
// if we don't have enough for a full header, wait 'til we do...
if (tBuf.length < recordSize) {
buffer = tBuf;
return;
}
doHeader.call(this, tBuf, function (err, data, rOffset) {
if (err) {
if (rOffset === 0) {
return;
}
return this.cb(err);
}
// update total; rOffset should always be 512
totalRead += rOffset;
buffer = tBuf.slice(rOffset);
fileStream = new Stream();
if (this.fileTypes.indexOf(data.type) >= 0) {
// we'll let the user know if they want this type of file
this.cb(err, data, fileStream);
}
if (buffer.length >= data.size) {
fileStream.emit('data', buffer.slice(0, data.size));
fileStream.emit('end');
totalRead += data.size;
buffer = buffer.slice(data.size);
fileStream = undefined;
// recurse, we still have data
return write.call(this);
}
leftToRead = data.size - buffer.length;
fileStream.emit('data', buffer);
totalRead += buffer.length;
buffer = undefined;
});
};
Untar.prototype.writable = true;
module.exports = Untar;
}());