diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index e55835f..e6e4fa9 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartfile', - version: '10.0.38', + version: '10.0.39', description: 'offers smart ways to work with files in nodejs' } diff --git a/ts/classes.streamfile.ts b/ts/classes.streamfile.ts index c7765cd..e5f9bc9 100644 --- a/ts/classes.streamfile.ts +++ b/ts/classes.streamfile.ts @@ -14,6 +14,11 @@ export class StreamFile { relativeFilePath?: string; private streamSource: StreamSource; + // enable stream based multi use + private cachedStreamBuffer?: Buffer; + public multiUse: boolean; + public used: boolean = false; + private constructor(streamSource: StreamSource, relativeFilePath?: string) { this.streamSource = streamSource; this.relativeFilePath = relativeFilePath; @@ -23,12 +28,16 @@ export class StreamFile { public static async fromPath(filePath: string): Promise { const streamSource = () => Promise.resolve(smartfileFsStream.createReadStream(filePath)); - return new StreamFile(streamSource, filePath); + const streamFile = new StreamFile(streamSource, filePath); + streamFile.multiUse = true; + return streamFile; } public static async fromUrl(url: string): Promise { const streamSource = async () => plugins.smartrequest.getStream(url); // Replace with actual plugin method - return new StreamFile(streamSource); + const streamFile = new StreamFile(streamSource); + streamFile.multiUse = true; + return streamFile; } public static fromBuffer(buffer: Buffer, relativeFilePath?: string): StreamFile { @@ -38,11 +47,59 @@ export class StreamFile { stream.push(null); // End of stream return Promise.resolve(stream); }; - return new StreamFile(streamSource, relativeFilePath); + const streamFile = new StreamFile(streamSource, relativeFilePath); + streamFile.multiUse = true; + return streamFile; + } + + /** + * Creates a StreamFile from an existing Readable stream with an option for multiple uses. + * @param stream A Node.js Readable stream. + * @param relativeFilePath Optional file path for the stream. + * @param multiUse If true, the stream can be read multiple times, caching its content. + * @returns A StreamFile instance. + */ + public static fromStream(stream: Readable, relativeFilePath?: string, multiUse: boolean = false): StreamFile { + const streamSource = function() { + if (this.multiUse) { + // If multi-use is enabled and we have cached content, create a new readable stream from the buffer + const bufferedStream = new Readable(); + bufferedStream.push(this.cachedStreamBuffer); + bufferedStream.push(null); // No more data to push + return Promise.resolve(bufferedStream); + } else { + return Promise.resolve(stream); + } + }; + + const streamFile = new StreamFile(streamSource, relativeFilePath); + streamFile.multiUse = multiUse; + + // If multi-use is enabled, cache the stream when it's first read + if (multiUse) { + const chunks: Buffer[] = []; + stream.on('data', (chunk) => chunks.push(Buffer.from(chunk))); + stream.on('end', () => { + streamFile.cachedStreamBuffer = Buffer.concat(chunks); + }); + // It's important to handle errors that may occur during streaming + stream.on('error', (err) => { + console.error('Error while caching stream:', err); + }); + } + + return streamFile; } // METHODS + private checkMultiUse() { + if (!this.multiUse && this.used) { + throw new Error('This stream can only be used once.'); + } + this.used = true; + } + /** * Creates a new readable stream from the source. */ @@ -55,6 +112,7 @@ export class StreamFile { * @param filePathArg The file path where the stream should be written. */ public async writeToDisk(filePathArg: string): Promise { + this.checkMultiUse(); const readStream = await this.createReadStream(); const writeStream = smartfileFsStream.createWriteStream(filePathArg); @@ -67,12 +125,14 @@ export class StreamFile { } public async writeToDir(dirPathArg: string) { + this.checkMultiUse(); const filePath = plugins.path.join(dirPathArg, this.relativeFilePath); await smartfileFs.ensureDir(plugins.path.parse(filePath).dir); return this.writeToDisk(filePath); } public async getContentAsBuffer() { + this.checkMultiUse(); const done = plugins.smartpromise.defer(); const readStream = await this.createReadStream(); const chunks: Buffer[] = [];