11import { ERRORS , StorageBackendError } from '@internal/errors'
2- import { randomUUID } from 'crypto'
2+ import { createHash , randomUUID } from 'crypto'
33import fs from 'fs-extra'
44import fsExtra from 'fs-extra'
55import * as xattr from 'fs-xattr'
6- import fileChecksum from 'md5-file'
7- import MultiStream from 'multistream'
86import path from 'path'
97import stream from 'stream'
108import { promisify } from 'util'
@@ -384,7 +382,7 @@ export class FileBackend implements StorageBackendAdapter {
384382
385383 await pipeline ( body , writeStream )
386384
387- const etag = await fileChecksum ( partPath )
385+ const etag = await this . computeMd5 ( partPath )
388386
389387 const platform = process . platform == 'darwin' ? 'darwin' : 'linux'
390388 await this . setMetadataAttr ( partPath , METADATA_ATTR_KEYS [ platform ] [ 'etag' ] , etag )
@@ -432,11 +430,7 @@ export class FileBackend implements StorageBackendAdapter {
432430 const finalParts = await Promise . all ( partsByEtags )
433431 finalParts . sort ( ( a , b ) => parseInt ( a . split ( '-' ) [ 1 ] ) - parseInt ( b . split ( '-' ) [ 1 ] ) )
434432
435- const fileStreams = finalParts . map ( ( partPath ) => {
436- return fs . createReadStream ( partPath )
437- } )
438-
439- const multistream = new MultiStream ( fileStreams )
433+ const multipartStream = this . mergePartStreams ( finalParts )
440434 const metadataContent = await fsExtra . readFile (
441435 this . resolveSecurePath (
442436 path . join (
@@ -456,7 +450,7 @@ export class FileBackend implements StorageBackendAdapter {
456450 bucketName ,
457451 key ,
458452 version ,
459- multistream ,
453+ multipartStream ,
460454 metadata . contentType ,
461455 metadata . cacheControl
462456 )
@@ -524,7 +518,7 @@ export class FileBackend implements StorageBackendAdapter {
524518 const writePart = fs . createWriteStream ( partFilePath )
525519 await pipeline ( partStream , writePart )
526520
527- const etag = await fileChecksum ( partFilePath )
521+ const etag = await this . computeMd5 ( partFilePath )
528522 await this . setMetadataAttr ( partFilePath , METADATA_ATTR_KEYS [ platform ] [ 'etag' ] , etag )
529523
530524 const fileStat = await fs . lstat ( partFilePath )
@@ -535,6 +529,30 @@ export class FileBackend implements StorageBackendAdapter {
535529 }
536530 }
537531
532+ private mergePartStreams ( partPaths : string [ ] ) : stream . Readable {
533+ return stream . Readable . from ( this . iteratePartChunks ( partPaths ) )
534+ }
535+
536+ private async * iteratePartChunks ( partPaths : string [ ] ) : AsyncGenerator < Buffer > {
537+ for ( const partPath of partPaths ) {
538+ const partStream = fs . createReadStream ( partPath )
539+ for await ( const chunk of partStream ) {
540+ yield chunk as Buffer
541+ }
542+ }
543+ }
544+
545+ private async computeMd5 ( filePath : string ) : Promise < string > {
546+ const hash = createHash ( 'md5' )
547+ const readStream = fs . createReadStream ( filePath )
548+
549+ for await ( const chunk of readStream ) {
550+ hash . update ( chunk )
551+ }
552+
553+ return hash . digest ( 'hex' )
554+ }
555+
538556 /**
539557 * Returns a private url that can only be accessed internally by the system
540558 * @param bucket
@@ -676,7 +694,7 @@ export class FileBackend implements StorageBackendAdapter {
676694
677695 private async etag ( file : string , stats : fs . Stats ) : Promise < string > {
678696 if ( this . etagAlgorithm === 'md5' ) {
679- const checksum = await fileChecksum ( file )
697+ const checksum = await this . computeMd5 ( file )
680698 return `"${ checksum } "`
681699 } else if ( this . etagAlgorithm === 'mtime' ) {
682700 return `"${ stats . mtimeMs . toString ( 16 ) } -${ stats . size . toString ( 16 ) } "`
0 commit comments