Files
smartarchive/ts_shared/classes.ziptools.ts

108 lines
3.6 KiB
TypeScript
Raw Normal View History

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;
}
}