44 *--------------------------------------------------------------------------------------------*/
55
66import { Disposable , IDisposable , toDisposable , combinedDisposable } from 'vs/base/common/lifecycle' ;
7- import { IFileService , IResolveFileOptions , IResourceEncodings , FileChangesEvent , FileOperationEvent , IFileSystemProviderRegistrationEvent , IFileSystemProvider , IFileStat , IResolveFileResult , IResolveContentOptions , IContent , IStreamContent , ITextSnapshot , IUpdateContentOptions , ICreateFileOptions , IFileSystemProviderActivationEvent , FileOperationError , FileOperationResult , FileOperation , FileSystemProviderCapabilities , FileType , toFileSystemProviderErrorCode , FileSystemProviderErrorCode , IStat , IFileStatWithMetadata , IResolveMetadataFileOptions , etag } from 'vs/platform/files/common/files' ;
7+ import { IFileService , IResolveFileOptions , IResourceEncodings , FileChangesEvent , FileOperationEvent , IFileSystemProviderRegistrationEvent , IFileSystemProvider , IFileStat , IResolveFileResult , IResolveContentOptions , IContent , IStreamContent , ITextSnapshot , IUpdateContentOptions , ICreateFileOptions , IFileSystemProviderActivationEvent , FileOperationError , FileOperationResult , FileOperation , FileSystemProviderCapabilities , FileType , toFileSystemProviderErrorCode , FileSystemProviderErrorCode , IStat , IFileStatWithMetadata , IResolveMetadataFileOptions , etag , hasReadWriteCapability , hasFileFolderCopyCapability , hasOpenReadWriteCloseCapability } from 'vs/platform/files/common/files' ;
88import { URI } from 'vs/base/common/uri' ;
99import { Event , Emitter } from 'vs/base/common/event' ;
1010import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation' ;
@@ -223,7 +223,7 @@ export class FileService2 extends Disposable implements IFileService {
223223 const childResource = joinPath ( resource , name ) ;
224224 const childStat = resolveMetadata ? await provider . stat ( childResource ) : { type } ;
225225
226- return this . toFileStat ( provider , childResource , childStat , entries . length , resolveMetadata , recurse ) ;
226+ return await this . toFileStat ( provider , childResource , childStat , entries . length , resolveMetadata , recurse ) ;
227227 } catch ( error ) {
228228 this . logService . trace ( error ) ;
229229
@@ -322,21 +322,19 @@ export class FileService2 extends Disposable implements IFileService {
322322
323323 //#region Move/Copy/Delete/Create Folder
324324
325- moveFile ( source : URI , target : URI , overwrite ?: boolean ) : Promise < IFileStatWithMetadata > {
326- if ( source . scheme === target . scheme ) {
327- return this . doMoveCopyWithSameProvider ( source , target , false /* just move */ , overwrite ) ;
328- }
329-
330- return this . doMoveWithDifferentProvider ( source , target ) ;
331- }
325+ async moveFile ( source : URI , target : URI , overwrite ?: boolean ) : Promise < IFileStatWithMetadata > {
332326
333- private async doMoveWithDifferentProvider ( source : URI , target : URI , overwrite ?: boolean ) : Promise < IFileStatWithMetadata > {
327+ // same provider
328+ if ( source . scheme === target . scheme ) {
329+ const provider = this . throwIfFileSystemIsReadonly ( await this . withProvider ( source ) ) ;
334330
335- // copy file source => target
336- await this . copyFile ( source , target , overwrite ) ;
331+ await this . doMoveCopy ( provider , source , target , false /* just move */ , overwrite ) ;
332+ }
337333
338- // delete source
339- await this . del ( source , { recursive : true } ) ;
334+ // across providers
335+ else {
336+ await this . doMoveAcrossProviders ( source , target ) ;
337+ }
340338
341339 // resolve and send events
342340 const fileStat = await this . resolveFile ( target , { resolveMetadata : true } ) ;
@@ -345,33 +343,47 @@ export class FileService2 extends Disposable implements IFileService {
345343 return fileStat ;
346344 }
347345
348- async copyFile ( source : URI , target : URI , overwrite ?: boolean ) : Promise < IFileStatWithMetadata > {
349- if ( source . scheme === target . scheme ) {
350- return this . doCopyWithSameProvider ( source , target , overwrite ) ;
346+ private async doMoveCopy ( provider : IFileSystemProvider , source : URI , target : URI , keepCopy : boolean , overwrite ?: boolean ) : Promise < void > {
347+
348+ // validation
349+ const { exists, isCaseChange } = await this . doValidateMoveCopy ( provider , source , target , keepCopy , overwrite ) ;
350+
351+ // delete as needed
352+ if ( exists && ! isCaseChange ) {
353+ await this . del ( target , { recursive : true } ) ;
351354 }
352355
353- return this . doCopyWithDifferentProvider ( source , target ) ;
354- }
356+ // create parent folders
357+ await this . mkdirp ( provider , dirname ( target ) ) ;
358+
359+ // rename/copy source => target
360+ if ( keepCopy ) {
355361
356- private async doCopyWithSameProvider ( source : URI , target : URI , overwrite : boolean = false ) : Promise < IFileStatWithMetadata > {
357- const provider = this . throwIfFileSystemIsReadonly ( await this . withProvider ( source ) ) ;
362+ // check if provider supports fast file/folder copy
363+ if ( hasFileFolderCopyCapability ( provider ) ) {
364+ return provider . copy ( source , target , { overwrite : ! ! overwrite } ) ;
365+ }
358366
359- // check if provider supports fast file/folder copy
360- if ( provider . capabilities & FileSystemProviderCapabilities . FileFolderCopy && typeof provider . copy === 'function' ) {
361- return this . doMoveCopyWithSameProvider ( source , target , true /* keep copy */ , overwrite ) ;
362- }
367+ // otherwise we need to manually copy: via read/write
368+ if ( hasOpenReadWriteCloseCapability ( provider ) ) {
369+ return this . joinOnLegacy . then ( legacy => legacy . copyFile ( source , target , overwrite ) ) . then ( ( ) => undefined ) ;
370+ }
363371
364- return this . joinOnLegacy . then ( legacy => legacy . copyFile ( source , target , overwrite ) ) ;
365- }
372+ // otherwise we need to manually copy: via readFile/writeFile
373+ if ( hasReadWriteCapability ( provider ) ) {
374+ return provider . writeFile ( target , await provider . readFile ( source ) , { create : true , overwrite : ! ! overwrite } ) ;
375+ }
366376
367- private async doCopyWithDifferentProvider ( source : URI , target : URI , overwrite ?: boolean ) : Promise < IFileStatWithMetadata > {
368- return this . joinOnLegacy . then ( legacy => legacy . copyFile ( source , target , overwrite ) ) ;
377+ // give up if provider has insufficient capabilities
378+ return Promise . reject ( 'Provider neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed to support copy.' ) ;
379+ } else {
380+ return provider . rename ( source , target , { overwrite : ! ! overwrite } ) ;
381+ }
369382 }
370383
371- private async doMoveCopyWithSameProvider ( source : URI , target : URI , keepCopy : boolean , overwrite ?: boolean ) : Promise < IFileStatWithMetadata > {
372- const provider = this . throwIfFileSystemIsReadonly ( await this . withProvider ( source ) ) ;
384+ private async doValidateMoveCopy ( provider : IFileSystemProvider , source : URI , target : URI , keepCopy : boolean , overwrite ?: boolean ) : Promise < { exists : boolean , isCaseChange : boolean } > {
373385
374- // validation
386+ // Check if source is equal or parent to target
375387 const isPathCaseSensitive = ! ! ( provider . capabilities & FileSystemProviderCapabilities . PathCaseSensitive ) ;
376388 const isCaseChange = isPathCaseSensitive ? false : isEqual ( source , target , true /* ignore case */ ) ;
377389 if ( ! isCaseChange && isEqualOrParent ( target , source , ! isPathCaseSensitive ) ) {
@@ -380,6 +392,8 @@ export class FileService2 extends Disposable implements IFileService {
380392
381393 const exists = await this . existsFile ( target ) ;
382394 if ( exists && ! isCaseChange ) {
395+
396+ // Bail out if target exists and we are not about to overwrite
383397 if ( ! overwrite ) {
384398 throw new FileOperationError ( localize ( 'unableToMoveCopyError2' , "Unable to move/copy. File already exists at destination." ) , FileOperationResult . FILE_MOVE_CONFLICT ) ;
385399 }
@@ -389,27 +403,45 @@ export class FileService2 extends Disposable implements IFileService {
389403 if ( isEqualOrParent ( source , target , ! isPathCaseSensitive ) ) {
390404 return Promise . reject ( new Error ( localize ( 'unableToMoveCopyError3' , "Unable to move/copy. File would replace folder it is contained in." ) ) ) ;
391405 }
392-
393- await this . del ( target , { recursive : true } ) ;
394406 }
395407
396- // create parent folders
397- await this . mkdirp ( provider , dirname ( target ) ) ;
408+ return { exists , isCaseChange } ;
409+ }
398410
399- // rename/copy source => target
400- if ( keepCopy ) {
401- await provider . copy ! ( source , target , { overwrite : ! ! overwrite } ) ;
402- } else {
403- await provider . rename ( source , target , { overwrite : ! ! overwrite } ) ;
411+ private async doMoveAcrossProviders ( source : URI , target : URI , overwrite ?: boolean ) : Promise < void > {
412+
413+ // copy file source => target
414+ await this . copyFile ( source , target , overwrite ) ;
415+
416+ // delete source
417+ await this . del ( source , { recursive : true } ) ;
418+ }
419+
420+ async copyFile ( source : URI , target : URI , overwrite ?: boolean ) : Promise < IFileStatWithMetadata > {
421+
422+ // same provider
423+ if ( source . scheme === target . scheme ) {
424+ const provider = this . throwIfFileSystemIsReadonly ( await this . withProvider ( source ) ) ;
425+
426+ await this . doMoveCopy ( provider , source , target , true /* mode: copy */ , overwrite ) ;
427+ }
428+
429+ // across providers
430+ else {
431+ await this . doCopyAcrossProviders ( source , target ) ;
404432 }
405433
406434 // resolve and send events
407435 const fileStat = await this . resolveFile ( target , { resolveMetadata : true } ) ;
408- this . _onAfterOperation . fire ( new FileOperationEvent ( source , keepCopy ? FileOperation . COPY : FileOperation . MOVE , fileStat ) ) ;
436+ this . _onAfterOperation . fire ( new FileOperationEvent ( source , FileOperation . COPY , fileStat ) ) ;
409437
410438 return fileStat ;
411439 }
412440
441+ private async doCopyAcrossProviders ( source : URI , target : URI , overwrite ?: boolean ) : Promise < void > {
442+ return this . joinOnLegacy . then ( legacy => legacy . copyFile ( source , target , overwrite ) ) . then ( ( ) => undefined ) ;
443+ }
444+
413445 async createFolder ( resource : URI ) : Promise < IFileStatWithMetadata > {
414446 const provider = this . throwIfFileSystemIsReadonly ( await this . withProvider ( resource ) ) ;
415447
0 commit comments