tar-iterator
Version:
Extract contents from tar archive type using an iterator API using streams or paths. Use stream interface and pipe transforms to add decompression algorithms
76 lines (75 loc) • 3.14 kB
JavaScript
import once from 'call-once-fn';
import { DirectoryEntry, LinkEntry, normalizePath, SymbolicLinkEntry } from 'extract-base-iterator';
import FileEntry from './FileEntry.js';
export default function nextEntry(next, iterator, callback) {
// Guard: bail early if iterator already ended
if (!iterator.lock || iterator.isDone()) {
return callback(null, {
done: true,
value: null
});
}
const extract = iterator.extract;
if (!extract) return callback(new Error('Extract missing'));
const nextCallback = once((err, entry, next)=>{
extract.removeListener('entry', onEntry);
extract.removeListener('error', onError);
extract.removeListener('finish', onEnd);
// keep processing
if (entry) iterator.push(nextEntry.bind(null, next));
// Avoid Zalgo: ensure callback is always async even when entry is available synchronously
process.nextTick(()=>{
err ? callback(err) : callback(null, entry ? {
done: false,
value: entry
} : {
done: true,
value: null
});
});
});
// Use nextCallback for all events to ensure once() wrapper is respected
const onError = (err)=>nextCallback(err);
const onEnd = ()=>nextCallback();
const onEntry = function onEntry(header, stream, next) {
// Guard: skip if iterator already ended (stale lock)
if (!iterator.lock || iterator.isDone()) {
stream.resume(); // drain stream
return nextCallback(null, null, next);
}
const attributes = {
...header
};
attributes.path = normalizePath(header.name);
attributes.mtime = new Date(attributes.mtime);
switch(attributes.type){
case 'directory':
stream.resume(); // drain stream
return nextCallback(null, new DirectoryEntry(attributes), next);
case 'symlink':
stream.resume(); // drain stream
attributes.linkpath = header.linkname;
return nextCallback(null, new SymbolicLinkEntry(attributes), next);
case 'link':
stream.resume(); // drain stream
attributes.linkpath = header.linkname;
return nextCallback(null, new LinkEntry(attributes), next);
case 'file':
return nextCallback(null, new FileEntry(attributes, stream, iterator.lock), next);
}
stream.resume(); // drain stream
return nextCallback(new Error(`Unrecognized entry type: ${attributes.type}`), null, next);
};
extract.on('entry', onEntry);
extract.on('error', onError);
extract.on('finish', onEnd);
// Resume parsing to emit any pending entry
// For first call (next is null), this triggers the first entry emission
// For subsequent calls, next() unlocks the parser which then processes the next header
if (next) {
next();
} else {
// First call - resume to emit any pending entry
extract.resume();
}
}