serialize async operations

This commit is contained in:
Simon Knott 2024-12-12 10:48:47 +01:00
parent a526cb9c81
commit c6e8d209fb
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC

View file

@ -58,6 +58,7 @@ function parseHeader(buffer: Buffer) {
type TarHeader = NonNullable<ReturnType<typeof parseHeader>>;
export class TarExtractor extends Writable {
private queue = Promise.resolve();
private buffer = Buffer.alloc(0);
private currentHeader: TarHeader | null = null;
private remainingBytes = 0;
@ -67,49 +68,11 @@ export class TarExtractor extends Writable {
super();
}
async mkdir(dir: string) {
try {
await fs.promises.mkdir(dir, { recursive: true });
// Set proper permissions for directories
await fs.promises.chmod(dir, 0o755);
} catch (err) {
if (err.code !== 'EEXIST')
throw err;
}
}
async processHeader(header: TarHeader) {
const fullPath = this.outputPath(header.name);
await this.mkdir(path.dirname(fullPath));
if (header.type === 'directory') {
await this.mkdir(fullPath);
return null;
}
if (header.type === 'symlink') {
await this.createSymlink(fullPath, header.linkname);
return null;
}
// TODO: track chmod maybe
return fs.createWriteStream(fullPath, { mode: header.mode });
}
async createSymlink(symlinkPath: string, targetPath: string) {
try {
await fs.promises.unlink(symlinkPath);
} catch (err) {
if (err.code !== 'ENOENT')
throw err;
}
await fs.promises.symlink(targetPath, symlinkPath);
}
override async _write(chunk: Buffer, _encoding: string, callback: (err?: Error) => void) {
try {
this.queue = this.queue.then(() => this._writeImpl(chunk)).then(callback).catch(callback);
}
private async _writeImpl(chunk: Buffer): Promise<undefined> {
this.buffer = Buffer.concat([this.buffer, chunk]);
while (this.buffer.length >= 512) {
@ -163,9 +126,46 @@ export class TarExtractor extends Writable {
}
}
}
callback();
}
async mkdir(dir: string) {
try {
await fs.promises.mkdir(dir, { recursive: true });
// Set proper permissions for directories
await fs.promises.chmod(dir, 0o755);
} catch (err) {
callback(err);
if (err.code !== 'EEXIST')
throw err;
}
}
async processHeader(header: TarHeader) {
const fullPath = this.outputPath(header.name);
await this.mkdir(path.dirname(fullPath));
if (header.type === 'directory') {
await this.mkdir(fullPath);
return null;
}
if (header.type === 'symlink') {
await this.createSymlink(fullPath, header.linkname);
return null;
}
// TODO: track chmod maybe
return fs.createWriteStream(fullPath, { mode: header.mode });
}
async createSymlink(symlinkPath: string, targetPath: string) {
try {
await fs.promises.unlink(symlinkPath);
} catch (err) {
if (err.code !== 'ENOENT')
throw err;
}
await fs.promises.symlink(targetPath, symlinkPath);
}
}