import { Storage , FilesReadable , ListResult , PathAbs } from 'storage-core' ; import { S3 , AWSError } from 'aws-sdk' ; import pathModule from 'path' ; import { Readable , PassThrough , Writable } from 'stream' ; import { AwsS3StorageOptions } from './AwsS3StorageOptions' ; import { PromiseResult } from 'aws-sdk/lib/request' ; const formatContent = ( f ) => ( { path : f . Key || '' , name : pathModule . basename ( f . Key || '' ) , size : f . Size , updatedAt : f . LastModified , md5Hash : f . ETag , isFile : true , raw : f as object } ) ; export class AwsS3Storage extends Storage < AwsS3StorageOptions > { s3 : S3 ; constructor ( options ) { super ( options ) ; this . s3 = new S3 ( { ... options } ) ; } @ PathAbs ( ) async getTopLevel ( path ? ) { const request = await this . s3 . listObjectsV2 ( { Bucket : this . options . bucket , Prefix : path ? `${ path }/` : '' , Delimiter : '/' } ) . promise ( ) ; const { CommonPrefixes , Contents } = request ; const dirs = ( CommonPrefixes && CommonPrefixes . map ( p => ( { name : pathModule . basename ( p . Prefix || '' ) , path : ( p . Prefix || '' ) . slice ( 0 , - 1 ) , isFile : false , raw : p } ) ) ) || [ ] ; const files = ( Contents && Contents . filter ( c => c . Key && c . Key [ c . Key . length - 1 ] !== '/' ) . map ( c => formatContent ( c ) ) ) || [ ] ; return dirs . concat ( files ) ; } @ PathAbs ( ) getFilesStream ( path ? ) { let request ; let ContinuationToken ; return new FilesReadable ( async ( ) => { if ( request && ! request . IsTruncated ) return null ; request = await this . s3 . listObjectsV2 ( { Bucket : this . options . bucket , Prefix : path , ContinuationToken } ) . promise ( ) ; ContinuationToken = request . NextContinuationToken ; const contents = request . Contents ; return ( contents && contents . filter ( c => c . Key && c . Key [ c . Key . length - 1 ] !== '/' ) . map ( f => formatContent ( f ) ) ) ; } ) ; } @ PathAbs ( ) async readFile ( filePath ) { const r = await this . s3 . getObject ( { Bucket : this . options . bucket , Key : filePath } ) . promise ( ) ; let buffer = undefined ; if ( r . Body instanceof Buffer ) buffer = r . Body ; if ( typeof r . Body === 'string' ) buffer = Buffer . from ( r . Body , 'utf8' ) ; if ( r . Body instanceof Readable ) { const chunks = [ ] ; for await ( let chunk of r . Body ) { chunks . push ( chunk ) ; } buffer = Buffer . concat ( chunks ) ; } if ( ! buffer || ! r . Body ) throw new Error ( 'Could not convert return body to buffer.' ) ; return buffer ; } @ PathAbs ( ) async writeFile ( filePath , data ) { await this . s3 . putObject ( { Bucket : this . options . bucket , Key : filePath , Body : data } ) . promise ( ) ; } @ PathAbs ( ) async deleteFile ( filePath ) { await this . s3 . deleteObject ( { Bucket : this . options . bucket , Key : filePath } ) . promise ( ) ; } @ PathAbs ( ) async getFileSize ( filePath ) { const r = await this . s3 . headObject ( { Bucket : this . options . bucket , Key : filePath } ) . promise ( ) ; return r . ContentLength || 0 ; } @ PathAbs ( ) async createWriteStream ( filePath ) { const pass = new PassThrough ( ) ; this . s3 . upload ( { Bucket : this . options . bucket , Key : filePath , Body : pass } ) . promise ( ) ; return pass ; } @ PathAbs ( ) async createReadStream ( filePath ) { return this . s3 . getObject ( { Bucket : this . options . bucket , Key : filePath } ) . createReadStream ( ) ; } async makeDir ( _ ) { } }