/** * @author [Tristan Valcke]{@link https://github.com/Itee} * @license [BSD-3-Clause]{@link https://opensource.org/licenses/BSD-3-Clause} * * @file Todo * * @example Todo * */ import * as globalBuffer from 'buffer' import fs from 'fs' import { isNull, isString, isUndefined } from 'itee-validators' import { Writable } from 'stream' /* Writable memory stream */ class MemoryWriteStream extends Writable { constructor ( options ) { super( options ) const bufferSize = options.bufferSize || globalBuffer.kStringMaxLength this.memoryBuffer = Buffer.alloc( bufferSize ) this.offset = 0 } _final ( callback ) { callback() } _write ( chunk, encoding, callback ) { // our memory store stores things in buffers const buffer = ( Buffer.isBuffer( chunk ) ) ? chunk : new Buffer( chunk, encoding ) // concat to the buffer already there for ( let byteIndex = 0, numberOfByte = buffer.length ; byteIndex < numberOfByte ; byteIndex++ ) { this.memoryBuffer[ this.offset ] = buffer[ byteIndex ] this.offset++ } // Next callback() } _writev ( chunks, callback ) { for ( let chunkIndex = 0, numberOfChunks = chunks.length ; chunkIndex < numberOfChunks ; chunkIndex++ ) { this.memoryBuffer = Buffer.concat( [ this.memoryBuffer, chunks[ chunkIndex ] ] ) } // Next callback() } _releaseMemory () { this.memoryBuffer = null } toArrayBuffer () { const buffer = this.memoryBuffer const arrayBuffer = new ArrayBuffer( buffer.length ) const view = new Uint8Array( arrayBuffer ) for ( let i = 0 ; i < buffer.length ; ++i ) { view[ i ] = buffer[ i ] } this._releaseMemory() return arrayBuffer } toJSON () { return JSON.parse( this.toString() ) } toString () { const string = this.memoryBuffer.toString() this._releaseMemory() return string } } //////// class TAbstractFileConverter { constructor ( parameters = {} ) { const _parameters = { ...{ dumpType: TAbstractFileConverter.DumpType.ArrayBuffer }, ...parameters } this.dumpType = _parameters.dumpType this._isProcessing = false this._queue = [] } get dumpType () { return this._dumpType } set dumpType ( value ) { if ( isNull( value ) ) { throw new TypeError( 'Dump type cannot be null ! Expect a non empty string.' ) } if ( isUndefined( value ) ) { throw new TypeError( 'Dump type cannot be undefined ! Expect a non empty string.' ) } this._dumpType = value } setDumpType ( value ) { this.dumpType = value return this } convert ( file, parameters, onSuccess, onProgress, onError ) { if ( !file ) { onError( 'File cannot be null or empty, aborting file convertion !!!' ) return } this._queue.push( { file, parameters, onSuccess, onProgress, onError } ) this._processQueue() } _processQueue () { if ( this._queue.length === 0 || this._isProcessing ) { return } this._isProcessing = true const self = this const dataBloc = this._queue.shift() const file = dataBloc.file const parameters = dataBloc.parameters const onSuccess = dataBloc.onSuccess const onProgress = dataBloc.onProgress const onError = dataBloc.onError if ( isString( file ) ) { self._dumpFileInMemoryAs( self._dumpType, file, parameters, _onDumpSuccess, _onProcessProgress, _onProcessError ) } else { const data = file.data switch ( self._dumpType ) { case TAbstractFileConverter.DumpType.ArrayBuffer: { const bufferSize = data.length const arrayBuffer = new ArrayBuffer( bufferSize ) const view = new Uint8Array( arrayBuffer ) for ( let i = 0 ; i < bufferSize ; ++i ) { view[ i ] = data[ i ] } _onDumpSuccess( arrayBuffer ) } break case TAbstractFileConverter.DumpType.JSON: _onDumpSuccess( JSON.parse( data.toString() ) ) break case TAbstractFileConverter.DumpType.String: _onDumpSuccess( data.toString() ) break default: throw new RangeError( `Invalid switch parameter: ${ self._dumpType }` ) } } function _onDumpSuccess ( data ) { self._convert( data, parameters, _onProcessSuccess, _onProcessProgress, _onProcessError ) } function _onProcessSuccess ( threeData ) { onSuccess( threeData ) self._isProcessing = false self._processQueue() } function _onProcessProgress ( progress ) { onProgress( progress ) } function _onProcessError ( error ) { onError( error ) self._isProcessing = false self._processQueue() } } _dumpFileInMemoryAs ( dumpType, file, parameters, onSuccess, onProgress, onError ) { let isOnError = false const fileReadStream = fs.createReadStream( file ) fileReadStream.on( 'error', ( error ) => { isOnError = true onError( error ) } ) const fileSize = parseInt( parameters.fileSize ) const memoryWriteStream = new MemoryWriteStream( { bufferSize: fileSize } ) memoryWriteStream.on( 'error', ( error ) => { isOnError = true onError( error ) } ) memoryWriteStream.on( 'finish', () => { if ( isOnError ) { return } switch ( dumpType ) { case TAbstractFileConverter.DumpType.ArrayBuffer: onSuccess( memoryWriteStream.toArrayBuffer() ) break case TAbstractFileConverter.DumpType.String: onSuccess( memoryWriteStream.toString() ) break case TAbstractFileConverter.DumpType.JSON: onSuccess( memoryWriteStream.toJSON() ) break default: throw new RangeError( `Invalid switch parameter: ${ dumpType }` ) } fileReadStream.unpipe() fileReadStream.close() memoryWriteStream.end() } ) fileReadStream.pipe( memoryWriteStream ) } _convert ( /*data, parameters, onSuccess, onProgress, onError*/ ) {} } TAbstractFileConverter.MAX_FILE_SIZE = 67108864 TAbstractFileConverter.DumpType = Object.freeze( { ArrayBuffer: 0, String: 1, JSON: 2 } ) export { TAbstractFileConverter, MemoryWriteStream }