108 lines
3.6 KiB
TypeScript
108 lines
3.6 KiB
TypeScript
|
|
import * as plugins from './plugins.js';
|
||
|
|
import type { IArchiveEntry, TCompressionLevel } from './interfaces.js';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Transform stream for ZIP decompression using fflate
|
||
|
|
* Emits StreamFile objects for each file in the archive
|
||
|
|
*/
|
||
|
|
export class ZipDecompressionTransform extends plugins.smartstream.SmartDuplex<Uint8Array, plugins.smartfile.StreamFile> {
|
||
|
|
private streamtools!: plugins.smartstream.IStreamTools;
|
||
|
|
private unzipper = new plugins.fflate.Unzip(async (fileArg) => {
|
||
|
|
let resultBuffer: Uint8Array;
|
||
|
|
fileArg.ondata = async (_flateError, dat, final) => {
|
||
|
|
if (resultBuffer) {
|
||
|
|
const combined = new Uint8Array(resultBuffer.length + dat.length);
|
||
|
|
combined.set(resultBuffer);
|
||
|
|
combined.set(dat, resultBuffer.length);
|
||
|
|
resultBuffer = combined;
|
||
|
|
} else {
|
||
|
|
resultBuffer = new Uint8Array(dat);
|
||
|
|
}
|
||
|
|
if (final) {
|
||
|
|
const streamFile = plugins.smartfile.StreamFile.fromBuffer(Buffer.from(resultBuffer));
|
||
|
|
streamFile.relativeFilePath = fileArg.name;
|
||
|
|
this.streamtools.push(streamFile);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
fileArg.start();
|
||
|
|
});
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
super({
|
||
|
|
objectMode: true,
|
||
|
|
writeFunction: async (chunkArg, streamtoolsArg) => {
|
||
|
|
this.streamtools ? null : (this.streamtools = streamtoolsArg);
|
||
|
|
const chunk = chunkArg instanceof Uint8Array ? chunkArg : new Uint8Array(chunkArg);
|
||
|
|
this.unzipper.push(chunk, false);
|
||
|
|
return null;
|
||
|
|
},
|
||
|
|
finalFunction: async () => {
|
||
|
|
this.unzipper.push(new Uint8Array(0), true);
|
||
|
|
await plugins.smartdelay.delayFor(0);
|
||
|
|
await this.streamtools.push(null);
|
||
|
|
return null;
|
||
|
|
},
|
||
|
|
});
|
||
|
|
this.unzipper.register(plugins.fflate.UnzipInflate);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* ZIP compression and decompression utilities
|
||
|
|
*/
|
||
|
|
export class ZipTools {
|
||
|
|
/**
|
||
|
|
* Get a streaming decompression transform for extracting ZIP archives
|
||
|
|
*/
|
||
|
|
public getDecompressionStream(): ZipDecompressionTransform {
|
||
|
|
return new ZipDecompressionTransform();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create a ZIP archive from an array of entries
|
||
|
|
*/
|
||
|
|
public async createZip(entries: IArchiveEntry[], compressionLevel?: TCompressionLevel): Promise<Uint8Array> {
|
||
|
|
const filesObj: plugins.fflate.Zippable = {};
|
||
|
|
|
||
|
|
for (const entry of entries) {
|
||
|
|
let data: Uint8Array;
|
||
|
|
|
||
|
|
if (typeof entry.content === 'string') {
|
||
|
|
data = new TextEncoder().encode(entry.content);
|
||
|
|
} else if (entry.content instanceof Uint8Array) {
|
||
|
|
data = entry.content;
|
||
|
|
} else if (entry.content instanceof plugins.smartfile.SmartFile) {
|
||
|
|
data = new Uint8Array(entry.content.contents);
|
||
|
|
} else if (entry.content instanceof plugins.smartfile.StreamFile) {
|
||
|
|
const buffer = await entry.content.getContentAsBuffer();
|
||
|
|
data = new Uint8Array(buffer);
|
||
|
|
} else {
|
||
|
|
throw new Error('Unsupported content type for ZIP entry');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (compressionLevel !== undefined) {
|
||
|
|
filesObj[entry.archivePath] = [data, { level: compressionLevel }];
|
||
|
|
} else {
|
||
|
|
filesObj[entry.archivePath] = data;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Use sync version for Deno compatibility (fflate async uses Web Workers)
|
||
|
|
const result = plugins.fflate.zipSync(filesObj);
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Extract a ZIP buffer to an array of entries
|
||
|
|
*/
|
||
|
|
public async extractZip(data: Uint8Array): Promise<Array<{ path: string; content: Uint8Array }>> {
|
||
|
|
// Use sync version for Deno compatibility (fflate async uses Web Workers)
|
||
|
|
const result = plugins.fflate.unzipSync(data);
|
||
|
|
const entries: Array<{ path: string; content: Uint8Array }> = [];
|
||
|
|
for (const [path, content] of Object.entries(result)) {
|
||
|
|
entries.push({ path, content });
|
||
|
|
}
|
||
|
|
return entries;
|
||
|
|
}
|
||
|
|
}
|