Files
smartarchive/ts/classes.tartools.ts

209 lines
6.5 KiB
TypeScript
Raw Permalink Normal View History

2023-11-06 18:14:21 +01:00
import * as plugins from './plugins.js';
import type { IArchiveEntry, TCompressionLevel } from './interfaces.js';
import { GzipTools } from './classes.gziptools.js';
2023-11-06 18:14:21 +01:00
/**
* TAR archive creation and extraction utilities
*/
2023-11-06 18:14:21 +01:00
export class TarTools {
/**
* Add a file to a TAR pack stream
*/
2024-06-08 10:30:03 +02:00
public async addFileToPack(
pack: plugins.tarStream.Pack,
optionsArg: {
fileName?: string;
content?:
| string
| Buffer
| plugins.stream.Readable
2024-06-08 10:30:03 +02:00
| plugins.smartfile.SmartFile
| plugins.smartfile.StreamFile;
byteLength?: number;
filePath?: string;
}
2024-06-08 10:30:03 +02:00
): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
2024-06-08 14:00:55 +02:00
let fileName: string | null = null;
if (optionsArg.fileName) {
fileName = optionsArg.fileName;
} else if (optionsArg.content instanceof plugins.smartfile.SmartFile) {
fileName = optionsArg.content.relative;
2024-06-08 14:00:55 +02:00
} else if (optionsArg.content instanceof plugins.smartfile.StreamFile) {
fileName = optionsArg.content.relativeFilePath;
2024-06-08 14:00:55 +02:00
} else if (optionsArg.filePath) {
fileName = optionsArg.filePath;
}
2024-06-08 10:30:03 +02:00
if (!fileName) {
reject(new Error('No filename specified for TAR entry'));
return;
}
// Determine content byte length
let contentByteLength: number | undefined;
2024-06-08 10:30:03 +02:00
if (optionsArg.byteLength) {
contentByteLength = optionsArg.byteLength;
} else if (typeof optionsArg.content === 'string') {
contentByteLength = Buffer.byteLength(optionsArg.content, 'utf8');
} else if (Buffer.isBuffer(optionsArg.content)) {
contentByteLength = optionsArg.content.length;
} else if (optionsArg.content instanceof plugins.smartfile.SmartFile) {
contentByteLength = await optionsArg.content.getSize();
2024-06-08 10:30:03 +02:00
} else if (optionsArg.content instanceof plugins.smartfile.StreamFile) {
contentByteLength = await optionsArg.content.getSize();
2024-06-08 10:30:03 +02:00
} else if (optionsArg.filePath) {
const fileStat = await plugins.fsPromises.stat(optionsArg.filePath);
2024-06-08 10:30:03 +02:00
contentByteLength = fileStat.size;
}
// Convert all content types to Readable stream
let content: plugins.stream.Readable;
2024-06-08 11:01:56 +02:00
if (Buffer.isBuffer(optionsArg.content)) {
content = plugins.stream.Readable.from(optionsArg.content);
2024-06-08 11:01:56 +02:00
} else if (typeof optionsArg.content === 'string') {
content = plugins.stream.Readable.from(Buffer.from(optionsArg.content));
2024-06-08 11:01:56 +02:00
} else if (optionsArg.content instanceof plugins.smartfile.SmartFile) {
content = plugins.stream.Readable.from(optionsArg.content.contents);
2024-06-08 11:01:56 +02:00
} else if (optionsArg.content instanceof plugins.smartfile.StreamFile) {
content = await optionsArg.content.createReadStream();
} else if (optionsArg.content instanceof plugins.stream.Readable) {
2024-06-08 11:01:56 +02:00
content = optionsArg.content;
} else if (optionsArg.filePath) {
content = plugins.fs.createReadStream(optionsArg.filePath);
} else {
reject(new Error('No content or filePath specified for TAR entry'));
return;
2024-06-08 11:01:56 +02:00
}
2024-06-08 10:30:03 +02:00
const entry = pack.entry(
{
name: fileName,
...(contentByteLength !== undefined ? { size: contentByteLength } : {}),
2024-06-08 10:30:03 +02:00
},
(err: Error | null) => {
2024-06-08 10:30:03 +02:00
if (err) {
reject(err);
} else {
resolve();
}
}
2024-06-08 10:30:03 +02:00
);
content.pipe(entry);
// Note: resolve() is called in the callback above when pipe completes
2023-11-06 18:14:21 +01:00
});
}
2024-06-08 10:30:03 +02:00
/**
* Pack a directory into a TAR stream
2024-06-08 10:30:03 +02:00
*/
public async packDirectory(directoryPath: string): Promise<plugins.tarStream.Pack> {
const fileTree = await plugins.listFileTree(directoryPath, '**/*');
2024-06-08 10:30:03 +02:00
const pack = await this.getPackStream();
2024-06-08 10:30:03 +02:00
for (const filePath of fileTree) {
const absolutePath = plugins.path.join(directoryPath, filePath);
const fileStat = await plugins.fsPromises.stat(absolutePath);
2024-06-08 10:30:03 +02:00
await this.addFileToPack(pack, {
byteLength: fileStat.size,
2024-06-08 13:47:38 +02:00
filePath: absolutePath,
2024-06-08 10:30:03 +02:00
fileName: filePath,
content: plugins.fs.createReadStream(absolutePath),
2024-06-08 10:30:03 +02:00
});
}
2024-06-08 11:03:37 +02:00
return pack;
2024-06-08 10:30:03 +02:00
}
/**
* Get a new TAR pack stream
*/
public async getPackStream(): Promise<plugins.tarStream.Pack> {
return plugins.tarStream.pack();
2023-11-06 18:14:21 +01:00
}
/**
* Get a TAR extraction stream
*/
public getDecompressionStream(): plugins.tarStream.Extract {
2023-11-06 18:14:21 +01:00
return plugins.tarStream.extract();
}
/**
* Pack files into a TAR buffer
*/
public async packFiles(files: IArchiveEntry[]): Promise<Buffer> {
const pack = await this.getPackStream();
for (const file of files) {
await this.addFileToPack(pack, {
fileName: file.archivePath,
content: file.content as string | Buffer | plugins.stream.Readable | plugins.smartfile.SmartFile | plugins.smartfile.StreamFile,
byteLength: file.size,
});
}
pack.finalize();
const chunks: Buffer[] = [];
return new Promise((resolve, reject) => {
pack.on('data', (chunk: Buffer) => chunks.push(chunk));
pack.on('end', () => resolve(Buffer.concat(chunks)));
pack.on('error', reject);
});
}
/**
* Pack a directory into a TAR.GZ buffer
*/
public async packDirectoryToTarGz(
directoryPath: string,
compressionLevel?: TCompressionLevel
): Promise<Buffer> {
const pack = await this.packDirectory(directoryPath);
pack.finalize();
const gzipTools = new GzipTools();
const gzipStream = gzipTools.getCompressionStream(compressionLevel);
const chunks: Buffer[] = [];
return new Promise((resolve, reject) => {
pack
.pipe(gzipStream)
.on('data', (chunk: Buffer) => chunks.push(chunk))
.on('end', () => resolve(Buffer.concat(chunks)))
.on('error', reject);
});
}
/**
* Pack a directory into a TAR.GZ stream
*/
public async packDirectoryToTarGzStream(
directoryPath: string,
compressionLevel?: TCompressionLevel
): Promise<plugins.stream.Readable> {
const pack = await this.packDirectory(directoryPath);
pack.finalize();
const gzipTools = new GzipTools();
const gzipStream = gzipTools.getCompressionStream(compressionLevel);
return pack.pipe(gzipStream);
}
/**
* Pack files into a TAR.GZ buffer
*/
public async packFilesToTarGz(
files: IArchiveEntry[],
compressionLevel?: TCompressionLevel
): Promise<Buffer> {
const tarBuffer = await this.packFiles(files);
const gzipTools = new GzipTools();
return gzipTools.compress(tarBuffer, compressionLevel);
}
2023-11-06 18:14:21 +01:00
}