import B2 from "backblaze-b2" ; import { Readable } from "stream" ; import { AbstractAdapter } from "./AbstractAdapter" ; import { StorageType , ConfigBackblazeB2 , BackblazeB2Bucket , BackblazeB2File } from "./types" ; import { parseUrl } from "./util" ; require ( "@gideo-llc/backblaze-b2-upload-any" ) . install ( B2 ) ; export class AdapterBackblazeB2 extends AbstractAdapter { protected type = StorageType . B2 ; private bucketId : string ; private storage : any ; private buckets : BackblazeB2Bucket [ ] = [ ] ; private files : BackblazeB2File [ ] = [ ] ; private nextFileName : string ; constructor ( config ) { super ( ) ; const cfg = this . parseConfig ( config ) ; this . config = { ... cfg } ; if ( cfg . slug ) { this . slug = cfg . slug ; delete cfg . slug ; } if ( cfg . bucketName ) { this . bucketName = this . generateSlug ( cfg . bucketName , this . slug ) ; delete cfg . bucketName ; } delete cfg . type ; this . storage = new B2 ( cfg ) ; } private parseConfig ( config ) { let cfg ; if ( typeof config === "string" ) { const { type , part1 : applicationKeyId , part2 : applicationKey , bucketName , queryString , } = parseUrl ( config ) ; cfg = { type , applicationKeyId , applicationKey , bucketName , ... queryString , } ; } else { cfg = { ... config } ; } if ( ! cfg . applicationKey || ! cfg . applicationKeyId ) { throw new Error ( "You must specify a value for both 'applicationKeyId' and  'applicationKey' for storage type 'b2'" ) ; } return cfg ; } public async init ( ) { if ( this . initialized ) { return Promise . resolve ( true ) ; } try { await this . storage . authorize ( ) ; } catch ( e ) { throw new Error ( e . message ) ; } if ( this . bucketName ) { await this . createBucket ( this . bucketName ) ; this . bucketId = this . getBucketId ( ) ; } this . initialized = true ; return true ; } private getBucketId ( ) { const index = this . buckets . findIndex ( ( b ) => b . bucketName === this . bucketName ) ; if ( index !== - 1 ) { return this . buckets [ index ] . bucketId ; } } async getFileAsReadable ( name , options = { start : 0 } ) { const file = await this . findFile ( name ) ; if ( file === null ) { throw new Error ( "file not found" ) ; } const d = await this . storage . downloadFileById ( { fileId : file . fileId , responseType : "stream" , axios : { headers : { "Content-Type" : file . contentType , Range : `bytes=${ options . start }-${ options . end }` , } , } , } ) ; return d . data ; } async removeFile ( name ) { if ( ! this . bucketName ) { throw new Error ( "no bucket selected" ) ; } const n = this . generateSlug ( name ) ; const file = await this . findFile ( n ) ; if ( file === null ) { return "file not found" ; } const { data : { files } , } = await this . storage . listFileVersions ( { bucketId : this . bucketId , } ) ; Promise . all ( files . filter ( ( f ) => f . fileName === n ) . map ( ( { fileId , fileName } ) => this . storage . deleteFileVersion ( { fileId , fileName , } ) ) ) ; this . files = this . files . filter ( file => file . fileName !== name ) ; return "file removed" ; } private findBucketLocal ( name ) { if ( this . buckets . length === 0 ) { return null ; } const index = this . buckets . findIndex ( b => b . bucketName === name ) ; if ( index !== - 1 ) { return this . buckets [ index ] ; } return null ; } private async findBucket ( name ) { const b = this . findBucketLocal ( name ) ; if ( b !== null ) { return b ; } await this . listBuckets ( ) ; return this . findBucketLocal ( name ) ; } public getSelectedBucket ( ) { return this . bucketName ; } protected async store ( buffer , targetPath ) ; protected async store ( stream , targetPath ) ; protected async store ( origPath , targetPath ) ; protected async store ( arg , targetPath ) { if ( ! this . bucketName ) { throw new Error ( "no bucket selected" ) ; } await this . createBucket ( this . bucketName ) ; try { const file = await this . storage . uploadAny ( { bucketId : this . bucketId , fileName : targetPath , data : arg , } ) ; this . files . push ( file ) ; return ; } catch ( e ) { return Promise . reject ( ) ; } } async createBucket ( name ) { const msg = this . validateName ( name ) ; if ( msg !== null ) { return Promise . reject ( msg ) ; } const n = this . generateSlug ( name ) ; const b = await this . findBucket ( n ) ; if ( b !== null ) { return ; } const d = await this . storage . createBucket ( { bucketName : n , bucketType : "allPrivate" , } ) . catch ( e => { throw new Error ( e . response . data . message ) ; } ) ; this . buckets . push ( d . data ) ; return "bucket created" ; } async selectBucket ( name ) { if ( ! name ) { this . bucketName = "" ; return "bucket deselected" ; } if ( name === this . bucketName ) { return "bucket selected" ; } const b = await this . findBucket ( name ) ; if ( b !== null ) { this . bucketName = name ; this . bucketId = b . bucketId ; this . files = [ ] ; return "bucket selected" ; } await this . createBucket ( name ) ; this . bucketName = name ; this . bucketId = this . getBucketId ( ) ; this . files = [ ] ; return "bucket selected" ; } async clearBucket ( name ? ) { let n = name || this . bucketName ; n = super . generateSlug ( n ) ; const b = await this . findBucket ( n ) ; if ( b === null ) { throw new Error ( "bucket not found" ) ; } const { data : { files } , } = await this . storage . listFileVersions ( { bucketId : b . bucketId , } ) ; await Promise . all ( files . map ( ( file ) => this . storage . deleteFileVersion ( { fileId : file . fileId , fileName : file . fileName , } ) ) ) ; return "bucket cleared" ; } async deleteBucket ( name ? ) { let n = name || this . bucketName ; n = super . generateSlug ( n ) ; const b = await this . findBucket ( n ) ; if ( b === null ) { throw new Error ( "bucket not found" ) ; } try { await this . clearBucket ( n ) ; } catch ( e ) { return e . response . data . message ; } const { bucketId } = b ; try { await this . storage . deleteBucket ( { bucketId } ) ; } catch ( e ) { return e . response . data . message ; } this . buckets = this . buckets . filter ( b => b . bucketName !== n ) ; this . bucketId = "" ; this . bucketName = "" ; return "bucket deleted" ; } async listBuckets ( ) { const { data : { buckets } , } = await this . storage . listBuckets ( ) ; this . buckets = buckets ; const names = this . buckets . map ( b => b . bucketName ) ; return names ; } async listFiles ( numFiles = 1000 ) { if ( ! this . bucketName ) { throw new Error ( "no bucket selected" ) ; } const { data : { files , nextFileName } , } = await this . storage . listFileNames ( { bucketId : this . bucketId , maxFileCount : numFiles , } ) ; this . files = [ ... files ] ; if ( nextFileName !== null ) { this . nextFileName = nextFileName ; } return this . files . map ( f => [ f . fileName , f . contentLength ] ) ; } private async findFile ( name ) { let i = this . files . findIndex ( ( file ) => file . fileName === name ) ; if ( i > - 1 ) { return this . files [ i ] ; } const { data : { files } , } = await this . storage . listFileNames ( { bucketId : this . bucketId } ) ; this . files = files ; i = this . files . findIndex ( ( file ) => file . fileName === name ) ; if ( i > - 1 ) { return this . files [ i ] ; } return null ; } async sizeOf ( name ) { if ( ! this . bucketName ) { throw new Error ( "no bucket selected" ) ; } const file = await this . findFile ( name ) ; if ( file === null ) { throw new Error ( "File not found" ) ; } return file . contentLength ; } async fileExists ( name ) { if ( ! this . bucketName ) { throw new Error ( "no bucket selected" ) ; } const file = await this . findFile ( name ) ; if ( file === null ) { return false ; } return true ; } }