Source: sources/converters/TAbstractFileConverter.js

/**
 * @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
}