@@ -21,6 +21,7 @@ import * as http from 'http';
2121import request from 'node-fetch' ;
2222import { authScopes , BUCKET_NAME , storageAPI , uploadAPI } from './constants' ;
2323import { GCSMetaStorage , GCSMetaStorageOptions } from './gcs-meta-storage' ;
24+ import { resolve } from 'url' ;
2425
2526export interface ClientError extends Error {
2627 code : string ;
@@ -72,9 +73,13 @@ export interface GCStorageOptions extends BaseStorageOptions<GCSFile>, GoogleAut
7273 metaStorageConfig ?: LocalMetaStorageOptions | GCSMetaStorageOptions ;
7374}
7475
75- export class GCSFile extends File {
76+ export interface GCSFile extends File {
7677 GCSUploadURI ?: string ;
77- uri = '' ;
78+ uri : string ;
79+ move : ( dest : string ) => Promise < Record < string , string > > ;
80+ copy : ( dest : string ) => Promise < Record < string , string > > ;
81+ get : ( ) => Promise < Record < string , string > > ;
82+ delete : ( ) => Promise < any > ;
7883}
7984
8085/**
@@ -95,6 +100,7 @@ export class GCStorage extends BaseStorage<GCSFile> {
95100 storageBaseURI : string ;
96101 uploadBaseURI : string ;
97102 meta : MetaStorage < GCSFile > ;
103+ private readonly bucket : string ;
98104
99105 constructor ( public config : GCStorageOptions = { } ) {
100106 super ( config ) ;
@@ -109,11 +115,11 @@ export class GCStorage extends BaseStorage<GCSFile> {
109115 }
110116 config . scopes ||= authScopes ;
111117 config . keyFile ||= process . env . GCS_KEYFILE ;
112- const bucketName = config . bucket || process . env . GCS_BUCKET || BUCKET_NAME ;
113- this . storageBaseURI = [ storageAPI , bucketName , 'o' ] . join ( '/' ) ;
114- this . uploadBaseURI = [ uploadAPI , bucketName , 'o' ] . join ( '/' ) ;
118+ this . bucket = config . bucket || process . env . GCS_BUCKET || BUCKET_NAME ;
119+ this . storageBaseURI = [ storageAPI , this . bucket , 'o' ] . join ( '/' ) ;
120+ this . uploadBaseURI = [ uploadAPI , this . bucket , 'o' ] . join ( '/' ) ;
115121 this . authClient = new GoogleAuth ( config ) ;
116- this . _checkBucket ( bucketName ) ;
122+ this . _checkBucket ( ) ;
117123 }
118124
119125 normalizeError ( error : ClientError ) : HttpError {
@@ -131,7 +137,7 @@ export class GCStorage extends BaseStorage<GCSFile> {
131137 }
132138
133139 async create ( req : http . IncomingMessage , config : FileInit ) : Promise < GCSFile > {
134- const file = new GCSFile ( config ) ;
140+ const file = new File ( config ) as GCSFile ;
135141 file . name = this . namingFunction ( file ) ;
136142 await this . validate ( file ) ;
137143 try {
@@ -173,6 +179,7 @@ export class GCStorage extends BaseStorage<GCSFile> {
173179 if ( isCompleted ( file ) ) {
174180 file . uri = `${ this . storageBaseURI } /${ file . name } ` ;
175181 await this . _onComplete ( file ) ;
182+ return this . buildCompletedFile ( file ) ;
176183 }
177184 return file ;
178185 }
@@ -190,6 +197,61 @@ export class GCStorage extends BaseStorage<GCSFile> {
190197 return [ { name } as GCSFile ] ;
191198 }
192199
200+ async copy ( name : string , dest : string ) : Promise < Record < string , string > > {
201+ type CopyProgress = {
202+ rewriteToken ?: string ;
203+ kind : string ;
204+ objectSize : number ;
205+ totalBytesRewritten : number ;
206+ done : boolean ;
207+ resource : Record < string , any > ;
208+ } ;
209+ const newPath = resolve ( `/${ this . bucket } /${ name } ` , encodeURI ( dest ) ) ;
210+ const [ , bucket , ...pathSegments ] = newPath . split ( '/' ) ;
211+ const filename = pathSegments . join ( '/' ) ;
212+ const url = `${ this . storageBaseURI } /${ name } /rewriteTo/b/${ bucket } /o/${ filename } ` ;
213+ let progress = { } as CopyProgress ;
214+ const opts = {
215+ body : '' ,
216+ headers : { 'Content-Type' : 'application/json' } ,
217+ method : 'POST' as const ,
218+ url
219+ } ;
220+ do {
221+ opts . body = progress . rewriteToken
222+ ? JSON . stringify ( { rewriteToken : progress . rewriteToken } )
223+ : '' ;
224+ progress = ( await this . authClient . request < CopyProgress > ( opts ) ) . data ;
225+ } while ( progress . rewriteToken ) ;
226+ return progress . resource ;
227+ }
228+
229+ async move ( name : string , dest : string ) : Promise < Record < string , string > > {
230+ const resource = await this . copy ( name , dest ) ;
231+ const url = `${ this . storageBaseURI } /${ name } ` ;
232+ await this . authClient . request ( { method : 'DELETE' as const , url } ) ;
233+ return resource ;
234+ }
235+
236+ async _get ( name : string ) : Promise < Record < string , string > > {
237+ const url = `${ this . storageBaseURI } /${ name } ` ;
238+ return ( await this . authClient . request < Record < string , string > > ( { url } ) ) . data ;
239+ }
240+
241+ buildCompletedFile ( file : GCSFile ) : GCSFile {
242+ const completed = { ...file } ;
243+ completed . lock = async lockFn => {
244+ completed . lockedBy = lockFn ;
245+ return Promise . resolve ( completed . lockedBy ) ;
246+ } ;
247+ completed . get = ( ) => this . _get ( file . name ) ;
248+ completed . delete = ( ) => this . delete ( file . name ) ;
249+ completed . copy = async ( dest : string ) => this . copy ( file . name , dest ) ;
250+ completed . move = async ( dest : string ) => this . move ( file . name , dest ) ;
251+
252+ return completed ;
253+ }
254+
193255 protected async _write ( part : FilePart & GCSFile ) : Promise < number > {
194256 const { size, uri, body } = part ;
195257 const contentRange = buildContentRange ( part ) ;
@@ -228,9 +290,9 @@ export class GCStorage extends BaseStorage<GCSFile> {
228290 return this . deleteMeta ( file . name ) ;
229291 } ;
230292
231- private _checkBucket ( bucketName : string ) : void {
293+ private _checkBucket ( ) : void {
232294 this . authClient
233- . request ( { url : ` ${ storageAPI } / ${ bucketName } ` } )
295+ . request ( { url : this . storageBaseURI } )
234296 . then ( ( ) => ( this . isReady = true ) )
235297 . catch ( ( err : ClientError ) => {
236298 // eslint-disable-next-line no-console
0 commit comments