/********************************************************************************
 * Copyright (C) 2020 TypeFox and others.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the Eclipse
 * Public License v. 2.0 are satisfied: GNU General Public License, version 2
 * with the GNU Classpath Exception which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 ********************************************************************************/ import { injectable , inject , named , postConstruct } from '@theia/core/shared/inversify' ; import URI from '@theia/core/lib/common/uri' ; import { timeout , Deferred } from '@theia/core/lib/common/promise-util' ; import { CancellationToken , CancellationTokenSource } from '@theia/core/lib/common/cancellation' ; import { Disposable , DisposableCollection } from '@theia/core/lib/common/disposable' ; import { WaitUntilEvent , Emitter , AsyncEmitter , Event } from '@theia/core/lib/common/event' ; import { ContributionProvider } from '@theia/core/lib/common/contribution-provider' ; import { TernarySearchTree } from '@theia/core/lib/common/ternary-search-tree' ; import { ensureFileSystemProviderError , etag , ETAG_DISABLED , FileChangesEvent , FileOperation , FileOperationError , FileOperationEvent , FileOperationResult , FileSystemProviderCapabilities , FileSystemProviderErrorCode , FileType , hasFileFolderCopyCapability , hasOpenReadWriteCloseCapability , hasReadWriteCapability , CreateFileOptions , FileContent , FileStat , FileStatWithMetadata , FileStreamContent , FileSystemProvider , FileSystemProviderWithFileReadWriteCapability , FileSystemProviderWithOpenReadWriteCloseCapability , ReadFileOptions , ResolveFileOptions , ResolveMetadataFileOptions , Stat , WatchOptions , WriteFileOptions , toFileOperationResult , toFileSystemProviderErrorCode , ResolveFileResult , ResolveFileResultWithMetadata , MoveFileOptions , CopyFileOptions , BaseStatWithMetadata , FileDeleteOptions , FileOperationOptions , hasAccessCapability , hasUpdateCapability , hasFileReadStreamCapability , FileSystemProviderWithFileReadStreamCapability } from '../common/files' ; import { BinaryBuffer , BinaryBufferReadable , BinaryBufferReadableStream , BinaryBufferReadableBufferedStream , BinaryBufferWriteableStream } from '@theia/core/lib/common/buffer' ; import { ReadableStream , isReadableStream , isReadableBufferedStream , transform , consumeStream , peekStream , peekReadable , Readable } from '@theia/core/lib/common/stream' ; import { LabelProvider } from '@theia/core/lib/browser/label-provider' ; import { FileSystemPreferences } from './filesystem-preferences' ; import { ProgressService } from '@theia/core/lib/common/progress-service' ; import { DelegatingFileSystemProvider } from '../common/delegating-file-system-provider' ; import type { TextDocumentContentChangeEvent } from '@theia/core/shared/vscode-languageserver-protocol' ; import { EncodingRegistry } from '@theia/core/lib/browser/encoding-registry' ; import { UTF8 , UTF8_with_bom } from '@theia/core/lib/common/encodings' ; import { EncodingService , ResourceEncoding , DecodeStreamResult } from '@theia/core/lib/common/encoding-service' ; import { Mutable } from '@theia/core/lib/common/types' ; import { readFileIntoStream } from '../common/io' ; import { FileSystemWatcherErrorHandler } from './filesystem-watcher-error-handler' ; import { FileSystemUtils } from '../common/filesystem-utils' ; export interface FileOperationParticipant { /**
     * Participate in a file operation of a working copy. Allows to
     * change the working copy before it is being saved to disk.
     */ participate ( target , source , operation , timeout , token ) : Promise < void > ; } export interface ReadEncodingOptions { /**
     * The optional encoding parameter allows to specify the desired encoding when resolving
     * the contents of the file.
     */ encoding ? : string ; /**
     * The optional guessEncoding parameter allows to guess encoding from content of the file.
     */ autoGuessEncoding ? : boolean ; } export interface WriteEncodingOptions { /**
     * The encoding to use when updating a file.
     */ encoding ? : string ; /**
     * If set to true, will enforce the selected encoding and not perform any detection using BOMs.
     */ overwriteEncoding ? : boolean ; } export interface ReadTextFileOptions extends ReadEncodingOptions , ReadFileOptions { /**
     * The optional acceptTextOnly parameter allows to fail this request early if the file
     * contents are not textual.
     */ acceptTextOnly ? : boolean ; } interface BaseTextFileContent extends BaseStatWithMetadata { /**
     * The encoding of the content if known.
     */ encoding : string ; } export interface TextFileContent extends BaseTextFileContent { /**
     * The content of a text file.
     */ value : string ; } export interface TextFileStreamContent extends BaseTextFileContent { /**
     * The line grouped content of a text file.
     */ value : ReadableStream < string > ; } export interface CreateTextFileOptions extends WriteEncodingOptions , CreateFileOptions { } export interface WriteTextFileOptions extends WriteEncodingOptions , WriteFileOptions { } export interface UpdateTextFileOptions extends WriteEncodingOptions , WriteFileOptions { readEncoding : string ; } export interface UserFileOperationEvent extends WaitUntilEvent { /**
     * An identifier to correlate the operation through the
     * different event types (before, after, error).
     */ readonly correlationId : number ; /**
     * The file operation that is taking place.
     */ readonly operation : FileOperation ; /**
     * The resource the event is about.
     */ readonly target : URI ; /**
     * A property that is defined for move operations.
     */ readonly source ? : URI ; } export const FileServiceContribution = Symbol ( 'FileServiceContribution' ) ; /**
 * A  FileServiceContribution can be used to add custom  FileSystemProvider s.
 * For this, the contribution has to listen to the  FileSystemProviderActivationEvent and register
 * the custom  FileSystemProvider s according to the scheme when this event is fired.
 *
 * ### Example usage
 * ```ts
 * export class MyFileServiceContribution implements FileServiceContribution {
 *     registerFileSystemProviders(service: FileService): void {
 *         service.onWillActivateFileSystemProvider(event => {
 *             if (event.scheme === 'mySyncProviderScheme') {
 *                 service.registerProvider('mySyncProviderScheme', this.mySyncProvider);
 *             }
 *             if (event.scheme === 'myAsyncProviderScheme') {
 *                 event.waitUntil((async () => {
 *                     const myAsyncProvider = await this.createAsyncProvider();
 *                     service.registerProvider('myAsyncProviderScheme', myAsyncProvider);
 *                 })());
 *             }
 *         });
 *
 *     }
 *```
  export interface FileServiceContribution { /**
     * Register custom file system providers for the given  FileService .
     *  param service registerFileSystemProviders ( service ) : void ; } /**
 * Represents the `FileSystemProviderRegistration` event.
 * This event is fired by the  FileService if a  FileSystemProvider is
 * registered to or unregistered from the service.
  export interface FileSystemProviderRegistrationEvent { /** `True` if a new provider has been registered, `false` if a provider has been unregistered. */ added : boolean ; /** The (uri) scheme for which the provider was (previously) registered */ scheme : string ; /** The affected file system provider for which this event was fired. */ provider ? : FileSystemProvider ; } /**
 * Represents the `FileSystemProviderCapabilitiesChange` event.
 * This event is fired by the  FileService if the capabilities of one of its managed
 *  FileSystemProvider s have changed.
  export interface FileSystemProviderCapabilitiesChangeEvent { /** The affected file system provider for which this event was fired. */ provider : FileSystemProvider ; /** The (uri) scheme for which the provider is registered */ scheme : string ; } /**
 * Represents the `FileSystemProviderActivation` event.
 * This event is fired by the  FileService if it wants to activate the
 *  FileSystemProvider for a specific scheme.
  export interface FileSystemProviderActivationEvent extends WaitUntilEvent { /** The (uri) scheme for which the provider should be activated */ scheme : string ; } export const enum TextFileOperationResult { FILE_IS_BINARY } export class TextFileOperationError extends FileOperationError { constructor ( message , public textFileOperationResult , public options ? ) { super ( message , FileOperationResult . FILE_OTHER_ERROR ) ; Object . setPrototypeOf ( this , TextFileOperationError . prototype ) ; } } /**
 * The  FileService is the common facade responsible for all interactions with file systems.
 * It manages all registered  FileSystemProvider s and
 *  forwards calls to the responsible  FileSystemProvider , determined by the scheme.
 * For additional documentation regarding the provided functions see also  FileSystemProvider .
  @ injectable ( ) export class FileService { private readonly BUFFER_SIZE = 64 * 1024 ; @ inject ( LabelProvider ) protected readonly labelProvider : LabelProvider ; @ inject ( FileSystemPreferences ) protected readonly preferences : FileSystemPreferences ; @ inject ( ProgressService ) protected readonly progressService : ProgressService ; @ inject ( EncodingRegistry ) protected readonly encodingRegistry : EncodingRegistry ; @ inject ( EncodingService ) protected readonly encodingService : EncodingService ; @ inject ( ContributionProvider ) @ named ( FileServiceContribution ) protected readonly contributions : ContributionProvider < FileServiceContribution > ; @ inject ( FileSystemWatcherErrorHandler ) protected readonly watcherErrorHandler : FileSystemWatcherErrorHandler ; @ postConstruct ( ) protected init ( ) { for ( const contribution of this . contributions . getContributions ( ) ) { contribution . registerFileSystemProviders ( this ) ; } } private correlationIds = 0 ; private readonly onWillRunUserOperationEmitter = new AsyncEmitter < UserFileOperationEvent > ( ) ; /**
     * An event that is emitted when file operation is being performed.
     * This event is triggered by user gestures.
     */ readonly onWillRunUserOperation = this . onWillRunUserOperationEmitter . event ; private readonly onDidFailUserOperationEmitter = new AsyncEmitter < UserFileOperationEvent > ( ) ; /**
     * An event that is emitted when file operation is failed.
     * This event is triggered by user gestures.
     */ readonly onDidFailUserOperation = this . onDidFailUserOperationEmitter . event ; private readonly onDidRunUserOperationEmitter = new AsyncEmitter < UserFileOperationEvent > ( ) ; /**
     * An event that is emitted when file operation is finished.
     * This event is triggered by user gestures.
     */ readonly onDidRunUserOperation = this . onDidRunUserOperationEmitter . event ; private onDidChangeFileSystemProviderRegistrationsEmitter = new Emitter < FileSystemProviderRegistrationEvent > ( ) ; readonly onDidChangeFileSystemProviderRegistrations = this . onDidChangeFileSystemProviderRegistrationsEmitter . event ; private onWillActivateFileSystemProviderEmitter = new Emitter < FileSystemProviderActivationEvent > ( ) ; /**
     * See `FileServiceContribution.registerProviders`.
     */ readonly onWillActivateFileSystemProvider = this . onWillActivateFileSystemProviderEmitter . event ; private onDidChangeFileSystemProviderCapabilitiesEmitter = new Emitter < FileSystemProviderCapabilitiesChangeEvent > ( ) ; readonly onDidChangeFileSystemProviderCapabilities = this . onDidChangeFileSystemProviderCapabilitiesEmitter . event ; private readonly providers = new Map < string , FileSystemProvider > ( ) ; private readonly activations = new Map < string , Promise < FileSystemProvider > > ( ) ; /**
     * Registers a new  FileSystemProvider for the given scheme.
     *  param scheme param provider returns registerProvider ( scheme , provider ) { if ( this . providers . has ( scheme ) ) { throw new Error ( `A filesystem provider for the scheme '${ scheme }' is already registered.` ) ; } this . providers . set ( scheme , provider ) ; this . onDidChangeFileSystemProviderRegistrationsEmitter . fire ( { added : true , scheme , provider } ) ; const providerDisposables = new DisposableCollection ( ) ; providerDisposables . push ( provider . onDidChangeFile ( changes => this . onDidFilesChangeEmitter . fire ( new FileChangesEvent ( changes ) ) ) ) ; providerDisposables . push ( provider . onFileWatchError ( ( ) => this . handleFileWatchError ( ) ) ) ; providerDisposables . push ( provider . onDidChangeCapabilities ( ( ) => this . onDidChangeFileSystemProviderCapabilitiesEmitter . fire ( { provider , scheme } ) ) ) ; return Disposable . create ( ( ) => { this . onDidChangeFileSystemProviderRegistrationsEmitter . fire ( { added : false , scheme , provider } ) ; this . providers . delete ( scheme ) ; providerDisposables . dispose ( ) ; } ) ; } param scheme returns async activateProvider ( scheme ) { let provider = this . providers . get ( scheme ) ; if ( provider ) { return provider ; } let activation = this . activations . get ( scheme ) ; if ( ! activation ) { const deferredActivation = new Deferred < FileSystemProvider > ( ) ; this . activations . set ( scheme , activation = deferredActivation . promise ) ; WaitUntilEvent . fire ( this . onWillActivateFileSystemProviderEmitter , { scheme } ) . then ( ( ) => { provider = this . providers . get ( scheme ) ; if ( ! provider ) { const error = new Error ( ) ; error . name = 'ENOPRO' ; error . message = `No file system provider found for scheme ${ scheme }` ; throw error ; } else { deferredActivation . resolve ( provider ) ; } } ) . catch ( e => deferredActivation . reject ( e ) ) ; } return activation ; } /**
     * Tests if the service (i.e. any of its registered  FileSystemProvider s) can handle the given resource.
     *  param resource returns canHandleResource ( resource ) { return this . providers . has ( resource . scheme ) ; } /**
     * Tests if the service (i.e the  FileSystemProvider registered for the given uri scheme) provides the given capability.
     *  param resource param capability returns hasCapability ( resource , capability ) { const provider = this . providers . get ( resource . scheme ) ; return ! ! ( provider && ( provider . capabilities & capability ) ) ; } protected async withProvider ( resource ) { if ( ! resource . path . isAbsolute ) { throw new FileOperationError ( `Unable to resolve filesystem provider with relative file path ${ this . resourceForError ( resource ) }` , FileOperationResult . FILE_INVALID_PATH ) ; } return this . activateProvider ( resource . scheme ) ; } private async withReadProvider ( resource ) { const provider = await this . withProvider ( resource ) ; if ( hasOpenReadWriteCloseCapability ( provider ) || hasReadWriteCapability ( provider ) ) { return provider ; } throw new Error ( `Filesystem provider for scheme '${ resource . scheme }' neither has FileReadWrite, FileReadStream nor FileOpenReadWriteClose capability which is needed for the read operation.` ) ; } private async withWriteProvider ( resource ) { const provider = await this . withProvider ( resource ) ; if ( hasOpenReadWriteCloseCapability ( provider ) || hasReadWriteCapability ( provider ) ) { return provider ; } throw new Error ( `Filesystem provider for scheme '${ resource . scheme }' neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed for the write operation.` ) ; } private onDidRunOperationEmitter = new Emitter < FileOperationEvent > ( ) ; /**
     * An event that is emitted when operation is finished.
     * This event is triggered by user gestures and programmatically.
     */ readonly onDidRunOperation = this . onDidRunOperationEmitter . event ; param resource param options return resolve ( resource , options ) ; resolve ( resource , options ? ) ; async resolve ( resource , options ? ) { try { return await this . doResolveFile ( resource , options ) ; } catch ( error ) { if ( toFileSystemProviderErrorCode ( error ) === FileSystemProviderErrorCode . FileNotFound ) { throw new FileOperationError ( `Unable to resolve non-existing file '${ this . resourceForError ( resource ) }'` , FileOperationResult . FILE_NOT_FOUND ) ; } throw ensureFileSystemProviderError ( error ) ; } } private async doResolveFile ( resource , options ) ; private async doResolveFile ( resource , options ? ) ; private async doResolveFile ( resource , options ? ) { const provider = await this . withProvider ( resource ) ; const resolveTo = options ?. resolveTo ; const resolveSingleChildDescendants = options ?. resolveSingleChildDescendants ; const resolveMetadata = options ?. resolveMetadata ; const stat = await provider . stat ( resource ) ; let trie ; return this . toFileStat ( provider , resource , stat , undefined , ! ! resolveMetadata , ( stat , siblings ) => { if ( ! trie ) { trie = TernarySearchTree . forUris < true > ( ! ! ( provider . capabilities & FileSystemProviderCapabilities . PathCaseSensitive ) ) ; trie . set ( resource , true ) ; if ( Array . isArray ( resolveTo ) && resolveTo . length ) { resolveTo . forEach ( uri => trie ! . set ( uri , true ) ) ; } } if ( Boolean ( trie . findSuperstr ( stat . resource ) || trie . get ( stat . resource ) ) ) { return true ; } if ( stat . isDirectory && resolveSingleChildDescendants ) { return siblings === 1 ; } return false ; } ) ; } private async toFileStat ( provider , resource , stat , siblings , resolveMetadata , recurse ) ; private async toFileStat ( provider , resource , stat , siblings , resolveMetadata , recurse ) ; private async toFileStat ( provider , resource , stat , siblings , resolveMetadata , recurse ) { const fileStat = FileStat . fromStat ( resource , stat ) ; if ( fileStat . isDirectory && recurse ( fileStat , siblings ) ) { try { const entries = await provider . readdir ( resource ) ; const resolvedEntries = await Promise . all ( entries . map ( async ( [ name , type ] ) => { try { const childResource = resource . resolve ( name ) ; const childStat = resolveMetadata ? await provider . stat ( childResource ) : { type } ; return await this . toFileStat ( provider , childResource , childStat , entries . length , resolveMetadata , recurse ) ; } catch ( error ) { console . trace ( error ) ; return null ; } } ) ) ; fileStat . children = resolvedEntries . filter ( e => ! ! e ) as FileStat [ ] ; } catch ( error ) { console . trace ( error ) ; fileStat . children = [ ] ; } return fileStat ; } return fileStat ; } param toResolve returns A promise of all resolved resources. The promise is not rejected if any of the given resources cannot be resolved.
     * Instead this is reflected with the `success` flag of the corresponding  ResolveFileResult .
      async resolveAll ( toResolve ) ; async resolveAll ( toResolve ) ; async resolveAll ( toResolve ) { return Promise . all ( toResolve . map ( async ( entry ) => { try { return { stat : await this . doResolveFile ( entry . resource , entry . options ) , success : true } ; } catch ( error ) { console . trace ( error ) ; return { stat : undefined , success : false } ; } } ) ) ; } param resource throws Will throw an error if no  FileSystemProvider is registered for the given resource.
     *
     *  returns async exists ( resource ) { const provider = await this . withProvider ( resource ) ; try { const stat = await provider . stat ( resource ) ; return ! ! stat ; } catch ( error ) { return false ; } } /**
     * Tests a user's permissions for the given resource.
     */ async access ( resource , mode ? ) { const provider = await this . withProvider ( resource ) ; if ( ! hasAccessCapability ( provider ) ) { return false ; } try { await provider . access ( resource , mode ) ; return true ; } catch ( error ) { return false ; } } param resource throws Will throw an error if no  FileSystemProvider is registered for the given resource.
     *
     *  returns async fsPath ( resource ) { const provider = await this . withProvider ( resource ) ; if ( ! hasAccessCapability ( provider ) ) { return resource . path . toString ( ) ; } return provider . fsPath ( resource ) ; } async create ( resource , value ? , options ? ) { if ( options ?. fromUserGesture === false ) { return this . doCreate ( resource , value , options ) ; } await this . runFileOperationParticipants ( resource , undefined , FileOperation . CREATE ) ; const event = { correlationId : this . correlationIds ++ , operation : FileOperation . CREATE , target : resource } ; await this . onWillRunUserOperationEmitter . fire ( event ) ; let stat ; try { stat = await this . doCreate ( resource , value , options ) ; } catch ( error ) { await this . onDidFailUserOperationEmitter . fire ( event ) ; throw error ; } await this . onDidRunUserOperationEmitter . fire ( event ) ; return stat ; } protected async doCreate ( resource , value ? , options ? ) { const encoding = await this . getWriteEncoding ( resource , options ) ; const encoded = await this . encodingService . encodeStream ( value , encoding ) ; return this . createFile ( resource , encoded , options ) ; } async write ( resource , value , options ? ) { const encoding = await this . getWriteEncoding ( resource , options ) ; const encoded = await this . encodingService . encodeStream ( value , encoding ) ; return Object . assign ( await this . writeFile ( resource , encoded , options ) , { encoding : encoding . encoding } ) ; } async read ( resource , options ? ) { const [ bufferStream , decoder ] = await this . doRead ( resource , { ... options , preferUnbuffered : true } ) ; return { ... bufferStream , encoding : decoder . detected . encoding || UTF8 , value : await consumeStream ( decoder . stream , strings => strings . join ( '' ) ) } ; } async readStream ( resource , options ? ) { const [ bufferStream , decoder ] = await this . doRead ( resource , options ) ; return { ... bufferStream , encoding : decoder . detected . encoding || UTF8 , value : decoder . stream } ; } private async doRead ( resource , options ? ) { options = this . resolveReadOptions ( options ) ; let bufferStream ; if ( options ?. preferUnbuffered ) { const content = await this . readFile ( resource , options ) ; bufferStream = { ... content , value : BinaryBufferReadableStream . fromBuffer ( content . value ) } ; } else { bufferStream = await this . readFileStream ( resource , options ) ; } const decoder = await this . encodingService . decodeStream ( bufferStream . value , { guessEncoding : options . autoGuessEncoding , overwriteEncoding : detectedEncoding => this . getReadEncoding ( resource , options , detectedEncoding ) } ) ; if ( options ?. acceptTextOnly && decoder . detected . seemsBinary ) { throw new TextFileOperationError ( 'File seems to be binary and cannot be opened as text' , TextFileOperationResult . FILE_IS_BINARY , options ) ; } return [ bufferStream , decoder ] ; } protected resolveReadOptions ( options ? ) { options = { ... options , autoGuessEncoding : typeof options ?. autoGuessEncoding === 'boolean' ? options . autoGuessEncoding : this . preferences [ 'files.autoGuessEncoding' ] } ; const limits = options . limits = options . limits || { } ; if ( typeof limits . size !== 'number' ) { limits . size = this . preferences [ 'files.maxFileSizeMB' ] * 1024 * 1024 ; } return options ; } async update ( resource , changes , options ) { const provider = this . throwIfFileSystemIsReadonly ( await this . withWriteProvider ( resource ) , resource ) ; try { await this . validateWriteFile ( provider , resource , options ) ; if ( hasUpdateCapability ( provider ) ) { const encoding = await this . getEncodingForResource ( resource , options ? options . encoding : undefined ) ; ; const stat = await provider . updateFile ( resource , changes , { readEncoding : options . readEncoding , writeEncoding : encoding , overwriteEncoding : options . overwriteEncoding || false } ) ; return Object . assign ( FileStat . fromStat ( resource , stat ) , { encoding : stat . encoding } ) ; } else { throw new Error ( 'incremental file update is not supported' ) ; } } catch ( error ) { this . rethrowAsFileOperationError ( 'Unable to write file' , resource , error , options ) ; } } async createFile ( resource , bufferOrReadableOrStream = BinaryBuffer . fromString ( '' ) , options ? ) { if ( ! options ?. overwrite && await this . exists ( resource ) ) { throw new FileOperationError ( `Unable to create file '${ this . resourceForError ( resource ) }' that already exists when overwrite flag is not set` , FileOperationResult . FILE_MODIFIED_SINCE , options ) ; } const fileStat = await this . writeFile ( resource , bufferOrReadableOrStream ) ; this . onDidRunOperationEmitter . fire ( new FileOperationEvent ( resource , FileOperation . CREATE , fileStat ) ) ; return fileStat ; } async writeFile ( resource , bufferOrReadableOrStream , options ? ) { const provider = this . throwIfFileSystemIsReadonly ( await this . withWriteProvider ( resource ) , resource ) ; try { const stat = await this . validateWriteFile ( provider , resource , options ) ; if ( ! stat ) { await this . mkdirp ( provider , resource . parent ) ; } let bufferOrReadableOrStreamOrBufferedStream ; if ( hasReadWriteCapability ( provider ) && ! ( bufferOrReadableOrStream instanceof BinaryBuffer ) ) { if ( isReadableStream ( bufferOrReadableOrStream ) ) { const bufferedStream = await peekStream ( bufferOrReadableOrStream , 3 ) ; if ( bufferedStream . ended ) { bufferOrReadableOrStreamOrBufferedStream = BinaryBuffer . concat ( bufferedStream . buffer ) ; } else { bufferOrReadableOrStreamOrBufferedStream = bufferedStream ; } } else { bufferOrReadableOrStreamOrBufferedStream = peekReadable ( bufferOrReadableOrStream , data => BinaryBuffer . concat ( data ) , 3 ) ; } } else { bufferOrReadableOrStreamOrBufferedStream = bufferOrReadableOrStream ; } if ( ! hasOpenReadWriteCloseCapability ( provider ) || ( hasReadWriteCapability ( provider ) && bufferOrReadableOrStreamOrBufferedStream instanceof BinaryBuffer ) ) { await this . doWriteUnbuffered ( provider , resource , bufferOrReadableOrStreamOrBufferedStream ) ; } else { await this . doWriteBuffered ( provider , resource , bufferOrReadableOrStreamOrBufferedStream instanceof BinaryBuffer ? BinaryBufferReadable . fromBuffer ( bufferOrReadableOrStreamOrBufferedStream ) : bufferOrReadableOrStreamOrBufferedStream ) ; } } catch ( error ) { this . rethrowAsFileOperationError ( 'Unable to write file' , resource , error , options ) ; } return this . resolve ( resource , { resolveMetadata : true } ) ; } private async validateWriteFile ( provider , resource , options ? ) { let stat = undefined ; try { stat = await provider . stat ( resource ) ; } catch ( error ) { return undefined ; } if ( ( stat . type & FileType . Directory ) !== 0 ) { throw new FileOperationError ( `Unable to write file ${ this . resourceForError ( resource ) } that is actually a directory` , FileOperationResult . FILE_IS_DIRECTORY , options ) ; } if ( this . modifiedSince ( stat , options ) ) { throw new FileOperationError ( 'File Modified Since' , FileOperationResult . FILE_MODIFIED_SINCE , options ) ; } return stat ; } /**
     * Dirty write prevention: if the file on disk has been changed and does not match our expected
     * mtime and etag, we bail out to prevent dirty writing.
     *
     * First, we check for a mtime that is in the future before we do more checks. The assumption is
     * that only the mtime is an indicator for a file that has changed on disk.
     *
     * Second, if the mtime has advanced, we compare the size of the file on disk with our previous
     * one using the etag() function. Relying only on the mtime check has proven to produce false
     * positives due to file system weirdness (especially around remote file systems). As such, the
     * check for size is a weaker check because it can return a false negative if the file has changed
     * but to the same length. This is a compromise we take to avoid having to produce checksums of
     * the file content for comparison which would be much slower to compute.
     */ protected modifiedSince ( stat , options ? ) { return ! ! options && typeof options . mtime === 'number' && typeof options . etag === 'string' && options . etag !== ETAG_DISABLED && typeof stat . mtime === 'number' && typeof stat . size === 'number' && options . mtime < stat . mtime && options . etag !== etag ( { mtime : options . mtime , size : stat . size } ) ; } async readFile ( resource , options ? ) { const provider = await this . withReadProvider ( resource ) ; const stream = await this . doReadAsFileStream ( provider , resource , { ... options , preferUnbuffered : true } ) ; return { ... stream , value : await BinaryBufferReadableStream . toBuffer ( stream . value ) } ; } async readFileStream ( resource , options ? ) { const provider = await this . withReadProvider ( resource ) ; return this . doReadAsFileStream ( provider , resource , options ) ; } private async doReadAsFileStream ( provider , resource , options ? ) { const cancellableSource = new CancellationTokenSource ( ) ; const statPromise = this . validateReadFile ( resource , options ) . then ( stat => stat , error => { cancellableSource . cancel ( ) ; throw error ; } ) ; try { if ( options && typeof options . etag === 'string' && options . etag !== ETAG_DISABLED ) { await statPromise ; } let fileStreamPromise ; if ( ! ( hasOpenReadWriteCloseCapability ( provider ) || hasFileReadStreamCapability ( provider ) ) || ( hasReadWriteCapability ( provider ) && options ?. preferUnbuffered ) ) { fileStreamPromise = this . readFileUnbuffered ( provider , resource , options ) ; } else if ( hasFileReadStreamCapability ( provider ) ) { fileStreamPromise = Promise . resolve ( this . readFileStreamed ( provider , resource , cancellableSource . token , options ) ) ; } else { fileStreamPromise = Promise . resolve ( this . readFileBuffered ( provider , resource , cancellableSource . token , options ) ) ; } const [ fileStat , fileStream ] = await Promise . all ( [ statPromise , fileStreamPromise ] ) ; return { ... fileStat , value : fileStream } ; } catch ( error ) { this . rethrowAsFileOperationError ( 'Unable to read file' , resource , error , options ) ; } } private readFileStreamed ( provider , resource , token , options = Object . create ( null ) ) { const fileStream = provider . readFileStream ( resource , options , token ) ; return transform ( fileStream , { data : data => data instanceof BinaryBuffer ? data : BinaryBuffer . wrap ( data ) , error : error => this . asFileOperationError ( 'Unable to read file' , resource , error , options ) } , data => BinaryBuffer . concat ( data ) ) ; } private readFileBuffered ( provider , resource , token , options = Object . create ( null ) ) { const stream = BinaryBufferWriteableStream . create ( ) ; readFileIntoStream ( provider , resource , stream , data => data , { ... options , bufferSize : this . BUFFER_SIZE , errorTransformer : error => this . asFileOperationError ( 'Unable to read file' , resource , error , options ) } , token ) ; return stream ; } protected rethrowAsFileOperationError ( message , resource , error , options ? ) { throw this . asFileOperationError ( message , resource , error , options ) ; } protected asFileOperationError ( message , resource , error , options ? ) { const fileOperationError = new FileOperationError ( `${ message } '${ this . resourceForError ( resource ) }' (${ ensureFileSystemProviderError ( error ) . toString ( ) })` , toFileOperationResult ( error ) , options ) ; fileOperationError . stack = `${ fileOperationError . stack }\nCaused by: ${ error . stack }` ; return fileOperationError ; } private async readFileUnbuffered ( provider , resource , options ? ) { let buffer = await provider . readFile ( resource ) ; if ( options && typeof options . position === 'number' ) { buffer = buffer . slice ( options . position ) ; } if ( options && typeof options . length === 'number' ) { buffer = buffer . slice ( 0 , options . length ) ; } this . validateReadFileLimits ( resource , buffer . byteLength , options ) ; return BinaryBufferReadableStream . fromBuffer ( BinaryBuffer . wrap ( buffer ) ) ; } private async validateReadFile ( resource , options ? ) { const stat = await this . resolve ( resource , { resolveMetadata : true } ) ; if ( stat . isDirectory ) { throw new FileOperationError ( `Unable to read file '${ this . resourceForError ( resource ) }' that is actually a directory` , FileOperationResult . FILE_IS_DIRECTORY , options ) ; } if ( options && typeof options . etag === 'string' && options . etag !== ETAG_DISABLED && options . etag === stat . etag ) { throw new FileOperationError ( 'File not modified since' , FileOperationResult . FILE_NOT_MODIFIED_SINCE , options ) ; } this . validateReadFileLimits ( resource , stat . size , options ) ; return stat ; } private validateReadFileLimits ( resource , size , options ? ) { if ( options ?. limits ) { let tooLargeErrorResult = undefined ; if ( typeof options . limits . memory === 'number' && size > options . limits . memory ) { tooLargeErrorResult = FileOperationResult . FILE_EXCEEDS_MEMORY_LIMIT ; } if ( typeof options . limits . size === 'number' && size > options . limits . size ) { tooLargeErrorResult = FileOperationResult . FILE_TOO_LARGE ; } if ( typeof tooLargeErrorResult === 'number' ) { throw new FileOperationError ( `Unable to read file '${ this . resourceForError ( resource ) }' that is too large to open` , tooLargeErrorResult ) ; } } } async move ( source , target , options ? ) { if ( options ?. fromUserGesture === false ) { return this . doMove ( source , target , options . overwrite ) ; } await this . runFileOperationParticipants ( target , source , FileOperation . MOVE ) ; const event = { correlationId : this . correlationIds ++ , operation : FileOperation . MOVE , target , source } ; await this . onWillRunUserOperationEmitter . fire ( event ) ; let stat ; try { stat = await this . doMove ( source , target , options ?. overwrite ) ; } catch ( error ) { await this . onDidFailUserOperationEmitter . fire ( event ) ; throw error ; } await this . onDidRunUserOperationEmitter . fire ( event ) ; return stat ; } protected async doMove ( source , target , overwrite ? ) { const sourceProvider = this . throwIfFileSystemIsReadonly ( await this . withWriteProvider ( source ) , source ) ; const targetProvider = this . throwIfFileSystemIsReadonly ( await this . withWriteProvider ( target ) , target ) ; const mode = await this . doMoveCopy ( sourceProvider , source , targetProvider , target , 'move' , ! ! overwrite ) ; const fileStat = await this . resolve ( target , { resolveMetadata : true } ) ; this . onDidRunOperationEmitter . fire ( new FileOperationEvent ( source , mode === 'move' ? FileOperation . MOVE : FileOperation . COPY , fileStat ) ) ; return fileStat ; } async copy ( source , target , options ? ) { if ( options ?. fromUserGesture === false ) { return this . doCopy ( source , target , options . overwrite ) ; } await this . runFileOperationParticipants ( target , source , FileOperation . COPY ) ; const event = { correlationId : this . correlationIds ++ , operation : FileOperation . COPY , target , source } ; await this . onWillRunUserOperationEmitter . fire ( event ) ; let stat ; try { stat = await this . doCopy ( source , target , options ?. overwrite ) ; } catch ( error ) { await this . onDidFailUserOperationEmitter . fire ( event ) ; throw error ; } await this . onDidRunUserOperationEmitter . fire ( event ) ; return stat ; } protected async doCopy ( source , target , overwrite ? ) { const sourceProvider = await this . withReadProvider ( source ) ; const targetProvider = this . throwIfFileSystemIsReadonly ( await this . withWriteProvider ( target ) , target ) ; const mode = await this . doMoveCopy ( sourceProvider , source , targetProvider , target , 'copy' , ! ! overwrite ) ; const fileStat = await this . resolve ( target , { resolveMetadata : true } ) ; this . onDidRunOperationEmitter . fire ( new FileOperationEvent ( source , mode === 'copy' ? FileOperation . COPY : FileOperation . MOVE , fileStat ) ) ; return fileStat ; } private async doMoveCopy ( sourceProvider , source , targetProvider , target , mode , overwrite ) { if ( source . toString ( ) === target . toString ( ) ) { return mode ; } const { exists , isSameResourceWithDifferentPathCase } = await this . doValidateMoveCopy ( sourceProvider , source , targetProvider , target , mode , overwrite ) ; if ( exists && ! overwrite ) { const parent = await this . resolve ( target . parent ) ; const name = isSameResourceWithDifferentPathCase ? target . path . name : target . path . name + '_copy' ; target = FileSystemUtils . generateUniqueResourceURI ( target . parent , parent , name , target . path . ext ) ; } if ( exists && ! isSameResourceWithDifferentPathCase && overwrite ) { await this . delete ( target , { recursive : true } ) ; } await this . mkdirp ( targetProvider , target . parent ) ; if ( mode === 'copy' ) { if ( sourceProvider === targetProvider && hasFileFolderCopyCapability ( sourceProvider ) ) { await sourceProvider . copy ( source , target , { overwrite } ) ; } else { const sourceFile = await this . resolve ( source ) ; if ( sourceFile . isDirectory ) { await this . doCopyFolder ( sourceProvider , sourceFile , targetProvider , target ) ; } else { await this . doCopyFile ( sourceProvider , source , targetProvider , target ) ; } } return mode ; } else { if ( sourceProvider === targetProvider ) { await sourceProvider . rename ( source , target , { overwrite } ) ; return mode ; } else { await this . doMoveCopy ( sourceProvider , source , targetProvider , target , 'copy' , overwrite ) ; await this . delete ( source , { recursive : true } ) ; return 'copy' ; } } } private async doCopyFile ( sourceProvider , source , targetProvider , target ) { if ( hasOpenReadWriteCloseCapability ( sourceProvider ) && hasOpenReadWriteCloseCapability ( targetProvider ) ) { return this . doPipeBuffered ( sourceProvider , source , targetProvider , target ) ; } if ( hasOpenReadWriteCloseCapability ( sourceProvider ) && hasReadWriteCapability ( targetProvider ) ) { return this . doPipeBufferedToUnbuffered ( sourceProvider , source , targetProvider , target ) ; } if ( hasReadWriteCapability ( sourceProvider ) && hasOpenReadWriteCloseCapability ( targetProvider ) ) { return this . doPipeUnbufferedToBuffered ( sourceProvider , source , targetProvider , target ) ; } if ( hasReadWriteCapability ( sourceProvider ) && hasReadWriteCapability ( targetProvider ) ) { return this . doPipeUnbuffered ( sourceProvider , source , targetProvider , target ) ; } } private async doCopyFolder ( sourceProvider , sourceFolder , targetProvider , targetFolder ) { await targetProvider . mkdir ( targetFolder ) ; if ( Array . isArray ( sourceFolder . children ) ) { await Promise . all ( sourceFolder . children . map ( async ( sourceChild ) => { const targetChild = targetFolder . resolve ( sourceChild . name ) ; if ( sourceChild . isDirectory ) { return this . doCopyFolder ( sourceProvider , await this . resolve ( sourceChild . resource ) , targetProvider , targetChild ) ; } else { return this . doCopyFile ( sourceProvider , sourceChild . resource , targetProvider , targetChild ) ; } } ) ) ; } } private async doValidateMoveCopy ( sourceProvider , source , targetProvider , target , mode , overwrite ? ) { let isSameResourceWithDifferentPathCase = false ; if ( sourceProvider === targetProvider ) { const isPathCaseSensitive = ! ! ( sourceProvider . capabilities & FileSystemProviderCapabilities . PathCaseSensitive ) ; if ( ! isPathCaseSensitive ) { isSameResourceWithDifferentPathCase = source . toString ( ) . toLowerCase ( ) === target . toString ( ) . toLowerCase ( ) ; } if ( isSameResourceWithDifferentPathCase && mode === 'copy' ) { throw new Error ( `Unable to copy when source '${ this . resourceForError ( source ) }' is same as target '${ this . resourceForError ( target ) }' with different path case on a case insensitive file system` ) ; } if ( ! isSameResourceWithDifferentPathCase && target . isEqualOrParent ( source , isPathCaseSensitive ) ) { throw new Error ( `Unable to move/copy when source '${ this . resourceForError ( source ) }' is parent of target '${ this . resourceForError ( target ) }'.` ) ; } } const exists = await this . exists ( target ) ; if ( exists && ! isSameResourceWithDifferentPathCase ) { if ( sourceProvider === targetProvider ) { const isPathCaseSensitive = ! ! ( sourceProvider . capabilities & FileSystemProviderCapabilities . PathCaseSensitive ) ; if ( source . isEqualOrParent ( target , isPathCaseSensitive ) ) { throw new Error ( `Unable to move/copy '${ this . resourceForError ( source ) }' into '${ this . resourceForError ( target ) }' since a file would replace the folder it is contained in.` ) ; } } } return { exists , isSameResourceWithDifferentPathCase } ; } async createFolder ( resource , options = { } ) { const { fromUserGesture = true , } = options ; const provider = this . throwIfFileSystemIsReadonly ( await this . withProvider ( resource ) , resource ) ; await this . mkdirp ( provider , resource ) ; const fileStat = await this . resolve ( resource , { resolveMetadata : true } ) ; if ( fromUserGesture ) { this . onDidRunUserOperationEmitter . fire ( { correlationId : this . correlationIds ++ , operation : FileOperation . CREATE , target : resource } ) ; } else { this . onDidRunOperationEmitter . fire ( new FileOperationEvent ( resource , FileOperation . CREATE , fileStat ) ) ; } return fileStat ; } private async mkdirp ( provider , directory ) { const directoriesToCreate = [ ] ; while ( ! directory . path . isRoot ) { try { const stat = await provider . stat ( directory ) ; if ( ( stat . type & FileType . Directory ) === 0 ) { throw new Error ( `Unable to create folder ${ this . resourceForError ( directory ) } that already exists but is not a directory` ) ; } break ; } catch ( error ) { if ( toFileSystemProviderErrorCode ( error ) !== FileSystemProviderErrorCode . FileNotFound ) { throw error ; } directoriesToCreate . push ( directory . path . base ) ; directory = directory . parent ; } } for ( let i = directoriesToCreate . length - 1 ; i >= 0 ; i -- ) { directory = directory . resolve ( directoriesToCreate [ i ] ) ; try { await provider . mkdir ( directory ) ; } catch ( error ) { if ( toFileSystemProviderErrorCode ( error ) !== FileSystemProviderErrorCode . FileExists ) { throw error ; } } } } async delete ( resource , options ? ) { if ( options ?. fromUserGesture === false ) { return this . doDelete ( resource , options ) ; } await this . runFileOperationParticipants ( resource , undefined , FileOperation . DELETE ) ; const event = { correlationId : this . correlationIds ++ , operation : FileOperation . DELETE , target : resource } ; await this . onWillRunUserOperationEmitter . fire ( event ) ; try { await this . doDelete ( resource , options ) ; } catch ( error ) { await this . onDidFailUserOperationEmitter . fire ( event ) ; throw error ; } await this . onDidRunUserOperationEmitter . fire ( event ) ; } protected async doDelete ( resource , options ? ) { const provider = this . throwIfFileSystemIsReadonly ( await this . withProvider ( resource ) , resource ) ; const useTrash = ! ! options ?. useTrash ; if ( useTrash && ! ( provider . capabilities & FileSystemProviderCapabilities . Trash ) ) { throw new Error ( `Unable to delete file '${ this . resourceForError ( resource ) }' via trash because provider does not support it.` ) ; } const exists = await this . exists ( resource ) ; if ( ! exists ) { throw new FileOperationError ( `Unable to delete non-existing file '${ this . resourceForError ( resource ) }'` , FileOperationResult . FILE_NOT_FOUND ) ; } const recursive = ! ! options ?. recursive ; if ( ! recursive && exists ) { const stat = await this . resolve ( resource ) ; if ( stat . isDirectory && Array . isArray ( stat . children ) && stat . children . length > 0 ) { throw new Error ( `Unable to delete non-empty folder '${ this . resourceForError ( resource ) }'.` ) ; } } await provider . delete ( resource , { recursive , useTrash } ) ; this . onDidRunOperationEmitter . fire ( new FileOperationEvent ( resource , FileOperation . DELETE ) ) ; } private onDidFilesChangeEmitter = new Emitter < FileChangesEvent > ( ) ; /**
     * An event that is emitted when files are changed on the disk.
     */ get onDidFilesChange ( ) : Event < FileChangesEvent > { return this . onDidFilesChangeEmitter . event ; } private activeWatchers = new Map < string , { disposable : Disposable ; count : number ; } > ( ) ; watch ( resource , options = { recursive : false , excludes : [ ] } ) { const resolvedOptions = { ... options , excludes : options . excludes . concat ( '**/theia_upload_*' ) } ; let watchDisposed = false ; let watchDisposable = Disposable . create ( ( ) => watchDisposed = true ) ; this . doWatch ( resource , resolvedOptions ) . then ( disposable => { if ( watchDisposed ) { disposable . dispose ( ) ; } else { watchDisposable = disposable ; } } , error => console . error ( error ) ) ; return Disposable . create ( ( ) => watchDisposable . dispose ( ) ) ; } async doWatch ( resource , options ) { const provider = await this . withProvider ( resource ) ; const key = this . toWatchKey ( provider , resource , options ) ; const watcher = this . activeWatchers . get ( key ) || { count : 0 , disposable : provider . watch ( resource , options ) } ; if ( ! this . activeWatchers . has ( key ) ) { this . activeWatchers . set ( key , watcher ) ; } watcher . count += 1 ; return Disposable . create ( ( ) => { watcher . count -- ; if ( watcher . count === 0 ) { watcher . disposable . dispose ( ) ; this . activeWatchers . delete ( key ) ; } } ) ; } private toWatchKey ( provider , resource , options ) { return [ this . toMapKey ( provider , resource ) , String ( options . recursive ) , options . excludes . join ( ) ] . join ( ) ; } private writeQueues : Map < string , Promise < void > > = new Map ( ) ; private ensureWriteQueue ( provider , resource , task ) { const queueKey = this . toMapKey ( provider , resource ) ; const writeQueue = ( this . writeQueues . get ( queueKey ) || Promise . resolve ( ) ) . then ( task , task ) ; this . writeQueues . set ( queueKey , writeQueue ) ; return writeQueue ; } private toMapKey ( provider , resource ) { const isPathCaseSensitive = ! ! ( provider . capabilities & FileSystemProviderCapabilities . PathCaseSensitive ) ; return isPathCaseSensitive ? resource . toString ( ) : resource . toString ( ) . toLowerCase ( ) ; } private async doWriteBuffered ( provider , resource , readableOrStreamOrBufferedStream ) { return this . ensureWriteQueue ( provider , resource , async ( ) => { const handle = await provider . open ( resource , { create : true } ) ; try { if ( isReadableStream ( readableOrStreamOrBufferedStream ) || isReadableBufferedStream ( readableOrStreamOrBufferedStream ) ) { await this . doWriteStreamBufferedQueued ( provider , handle , readableOrStreamOrBufferedStream ) ; } else { await this . doWriteReadableBufferedQueued ( provider , handle , readableOrStreamOrBufferedStream ) ; } } catch ( error ) { throw ensureFileSystemProviderError ( error ) ; } finally { await provider . close ( handle ) ; } } ) ; } private async doWriteStreamBufferedQueued ( provider , handle , streamOrBufferedStream ) { let posInFile = 0 ; let stream ; if ( isReadableBufferedStream ( streamOrBufferedStream ) ) { if ( streamOrBufferedStream . buffer . length > 0 ) { const chunk = BinaryBuffer . concat ( streamOrBufferedStream . buffer ) ; await this . doWriteBuffer ( provider , handle , chunk , chunk . byteLength , posInFile , 0 ) ; posInFile += chunk . byteLength ; } if ( streamOrBufferedStream . ended ) { return ; } stream = streamOrBufferedStream . stream ; } else { stream = streamOrBufferedStream ; } return new Promise ( async ( resolve , reject ) => { stream . on ( 'data' , async ( chunk ) => { stream . pause ( ) ; try { await this . doWriteBuffer ( provider , handle , chunk , chunk . byteLength , posInFile , 0 ) ; } catch ( error ) { return reject ( error ) ; } posInFile += chunk . byteLength ; setTimeout ( ( ) => stream . resume ( ) ) ; } ) ; stream . on ( 'error' , error => reject ( error ) ) ; stream . on ( 'end' , ( ) => resolve ( ) ) ; } ) ; } private async doWriteReadableBufferedQueued ( provider , handle , readable ) { let posInFile = 0 ; let chunk ; while ( ( chunk = readable . read ( ) ) !== null ) { await this . doWriteBuffer ( provider , handle , chunk , chunk . byteLength , posInFile , 0 ) ; posInFile += chunk . byteLength ; } } private async doWriteBuffer ( provider , handle , buffer , length , posInFile , posInBuffer ) { let totalBytesWritten = 0 ; while ( totalBytesWritten < length ) { const bytesWritten = await provider . write ( handle , posInFile + totalBytesWritten , buffer . buffer , posInBuffer + totalBytesWritten , length - totalBytesWritten ) ; totalBytesWritten += bytesWritten ; } } private async doWriteUnbuffered ( provider , resource , bufferOrReadableOrStreamOrBufferedStream ) { return this . ensureWriteQueue ( provider , resource , ( ) => this . doWriteUnbufferedQueued ( provider , resource , bufferOrReadableOrStreamOrBufferedStream ) ) ; } private async doWriteUnbufferedQueued ( provider , resource , bufferOrReadableOrStreamOrBufferedStream ) { let buffer ; if ( bufferOrReadableOrStreamOrBufferedStream instanceof BinaryBuffer ) { buffer = bufferOrReadableOrStreamOrBufferedStream ; } else if ( isReadableStream ( bufferOrReadableOrStreamOrBufferedStream ) ) { buffer = await BinaryBufferReadableStream . toBuffer ( bufferOrReadableOrStreamOrBufferedStream ) ; } else if ( isReadableBufferedStream ( bufferOrReadableOrStreamOrBufferedStream ) ) { buffer = await BinaryBufferReadableBufferedStream . toBuffer ( bufferOrReadableOrStreamOrBufferedStream ) ; } else { buffer = BinaryBufferReadable . toBuffer ( bufferOrReadableOrStreamOrBufferedStream ) ; } return provider . writeFile ( resource , buffer . buffer , { create : true , overwrite : true } ) ; } private async doPipeBuffered ( sourceProvider , source , targetProvider , target ) { return this . ensureWriteQueue ( targetProvider , target , ( ) => this . doPipeBufferedQueued ( sourceProvider , source , targetProvider , target ) ) ; } private async doPipeBufferedQueued ( sourceProvider , source , targetProvider , target ) { let sourceHandle = undefined ; let targetHandle = undefined ; try { sourceHandle = await sourceProvider . open ( source , { create : false } ) ; targetHandle = await targetProvider . open ( target , { create : true } ) ; const buffer = BinaryBuffer . alloc ( this . BUFFER_SIZE ) ; let posInFile = 0 ; let posInBuffer = 0 ; let bytesRead = 0 ; do { bytesRead = await sourceProvider . read ( sourceHandle , posInFile , buffer . buffer , posInBuffer , buffer . byteLength - posInBuffer ) ; await this . doWriteBuffer ( targetProvider , targetHandle , buffer , bytesRead , posInFile , posInBuffer ) ; posInFile += bytesRead ; posInBuffer += bytesRead ; if ( posInBuffer === buffer . byteLength ) { posInBuffer = 0 ; } } while ( bytesRead > 0 ) ; } catch ( error ) { throw ensureFileSystemProviderError ( error ) ; } finally { await Promise . all ( [ typeof sourceHandle === 'number' ? sourceProvider . close ( sourceHandle ) : Promise . resolve ( ) , typeof targetHandle === 'number' ? targetProvider . close ( targetHandle ) : Promise . resolve ( ) , ] ) ; } } private async doPipeUnbuffered ( sourceProvider , source , targetProvider , target ) { return this . ensureWriteQueue ( targetProvider , target , ( ) => this . doPipeUnbufferedQueued ( sourceProvider , source , targetProvider , target ) ) ; } private async doPipeUnbufferedQueued ( sourceProvider , source , targetProvider , target ) { return targetProvider . writeFile ( target , await sourceProvider . readFile ( source ) , { create : true , overwrite : true } ) ; } private async doPipeUnbufferedToBuffered ( sourceProvider , source , targetProvider , target ) { return this . ensureWriteQueue ( targetProvider , target , ( ) => this . doPipeUnbufferedToBufferedQueued ( sourceProvider , source , targetProvider , target ) ) ; } private async doPipeUnbufferedToBufferedQueued ( sourceProvider , source , targetProvider , target ) { const targetHandle = await targetProvider . open ( target , { create : true } ) ; try { const buffer = await sourceProvider . readFile ( source ) ; await this . doWriteBuffer ( targetProvider , targetHandle , BinaryBuffer . wrap ( buffer ) , buffer . byteLength , 0 , 0 ) ; } catch ( error ) { throw ensureFileSystemProviderError ( error ) ; } finally { await targetProvider . close ( targetHandle ) ; } } private async doPipeBufferedToUnbuffered ( sourceProvider , source , targetProvider , target ) { const buffer = await BinaryBufferReadableStream . toBuffer ( this . readFileBuffered ( sourceProvider , source , CancellationToken . None ) ) ; await this . doWriteUnbuffered ( targetProvider , target , buffer ) ; } protected throwIfFileSystemIsReadonly < T extends FileSystemProvider > ( provider , resource ) { if ( provider . capabilities & FileSystemProviderCapabilities . Readonly ) { throw new FileOperationError ( `Unable to modify readonly file ${ this . resourceForError ( resource ) }` , FileOperationResult . FILE_PERMISSION_DENIED ) ; } return provider ; } private resourceForError ( resource ) { return this . labelProvider . getLongName ( resource ) ; } private readonly participants : FileOperationParticipant [ ] = [ ] ; addFileOperationParticipant ( participant ) { this . participants . push ( participant ) ; return Disposable . create ( ( ) => { const index = this . participants . indexOf ( participant ) ; if ( index > - 1 ) { this . participants . splice ( index , 1 ) ; } } ) ; } async runFileOperationParticipants ( target , source , operation ) { const participantsTimeout = this . preferences [ 'files.participants.timeout' ] ; if ( participantsTimeout <= 0 ) { return ; } const cancellationTokenSource = new CancellationTokenSource ( ) ; return this . progressService . withProgress ( this . progressLabel ( operation ) , 'window' , async ( ) => { for ( const participant of this . participants ) { if ( cancellationTokenSource . token . isCancellationRequested ) { break ; } try { const promise = participant . participate ( target , source , operation , participantsTimeout , cancellationTokenSource . token ) ; await Promise . race ( [ promise , timeout ( participantsTimeout , cancellationTokenSource . token ) . then ( ( ) => cancellationTokenSource . dispose ( ) , ( ) => { } ) ] ) ; } catch ( err ) { console . warn ( err ) ; } } } ) ; } private progressLabel ( operation ) { switch ( operation ) { case FileOperation . CREATE : return "Running 'File Create' participants..." ; case FileOperation . MOVE : return "Running 'File Rename' participants..." ; case FileOperation . COPY : return "Running 'File Copy' participants..." ; case FileOperation . DELETE : return "Running 'File Delete' participants..." ; } } protected async getWriteEncoding ( resource , options ? ) { const encoding = await this . getEncodingForResource ( resource , options ? options . encoding : undefined ) ; return this . encodingService . toResourceEncoding ( encoding , { overwriteEncoding : options ?. overwriteEncoding , read : async ( length ) => { const buffer = await BinaryBufferReadableStream . toBuffer ( ( await this . readFileStream ( resource , { length } ) ) . value ) ; return buffer . buffer ; } } ) ; } protected getReadEncoding ( resource , options ? , detectedEncoding ? ) { let preferredEncoding ; if ( options ?. encoding ) { if ( detectedEncoding === UTF8_with_bom && options . encoding === UTF8 ) { preferredEncoding = UTF8_with_bom ; } else { preferredEncoding = options . encoding ; } } else if ( detectedEncoding ) { preferredEncoding = detectedEncoding ; } return this . getEncodingForResource ( resource , preferredEncoding ) ; } protected async getEncodingForResource ( resource , preferredEncoding ? ) { resource = await this . toUnderlyingResource ( resource ) ; return this . encodingRegistry . getEncodingForResource ( resource , preferredEncoding ) ; } /**
     * Converts to an underlying fs provider resource format.
     *
     * For example converting `user-storage` resources to `file` resources under a user home:
     * user-storage:/user/settings.json => file://home/.theia/settings.json
     */ async toUnderlyingResource ( resource ) { let provider = await this . withProvider ( resource ) ; while ( provider instanceof DelegatingFileSystemProvider ) { resource = provider . toUnderlyingResource ( resource ) ; provider = await this . withProvider ( resource ) ; } return resource ; } protected handleFileWatchError ( ) { this . watcherErrorHandler . handleError ( ) ; } }