44 *--------------------------------------------------------------------------------------------*/
55
66import { MainContext , MainThreadFileSystemShape } from './extHost.protocol' ;
7- import * as vscode from 'vscode' ;
7+ import type * as vscode from 'vscode' ;
88import * as files from 'vs/platform/files/common/files' ;
99import { FileSystemError } from 'vs/workbench/api/common/extHostTypes' ;
1010import { VSBuffer } from 'vs/base/common/buffer' ;
1111import { createDecorator } from 'vs/platform/instantiation/common/instantiation' ;
1212import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService' ;
1313import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo' ;
1414import { IDisposable , toDisposable } from 'vs/base/common/lifecycle' ;
15+ import { ResourceQueue } from 'vs/base/common/async' ;
16+ import { IExtUri , extUri , extUriIgnorePathCase } from 'vs/base/common/resources' ;
17+ import { Schemas } from 'vs/base/common/network' ;
18+ import { IMarkdownString } from 'vs/base/common/htmlContent' ;
1519
1620export class ExtHostConsumerFileSystem {
1721
@@ -20,7 +24,9 @@ export class ExtHostConsumerFileSystem {
2024 readonly value : vscode . FileSystem ;
2125
2226 private readonly _proxy : MainThreadFileSystemShape ;
23- private readonly _fileSystemProvider = new Map < string , vscode . FileSystemProvider > ( ) ;
27+ private readonly _fileSystemProvider = new Map < string , { impl : vscode . FileSystemProvider ; extUri : IExtUri ; isReadonly : boolean } > ( ) ;
28+
29+ private readonly writeQueue = new ResourceQueue ( ) ;
2430
2531 constructor (
2632 @IExtHostRpcService extHostRpc : IExtHostRpcService ,
@@ -38,7 +44,7 @@ export class ExtHostConsumerFileSystem {
3844 if ( provider ) {
3945 // use shortcut
4046 await that . _proxy . $ensureActivation ( uri . scheme ) ;
41- stat = await provider . stat ( uri ) ;
47+ stat = await provider . impl . stat ( uri ) ;
4248 } else {
4349 stat = await that . _proxy . $stat ( uri ) ;
4450 }
@@ -60,7 +66,7 @@ export class ExtHostConsumerFileSystem {
6066 if ( provider ) {
6167 // use shortcut
6268 await that . _proxy . $ensureActivation ( uri . scheme ) ;
63- return ( await provider . readDirectory ( uri ) ) . slice ( ) ; // safe-copy
69+ return ( await provider . impl . readDirectory ( uri ) ) . slice ( ) ; // safe-copy
6470 } else {
6571 return await that . _proxy . $readdir ( uri ) ;
6672 }
@@ -70,8 +76,14 @@ export class ExtHostConsumerFileSystem {
7076 } ,
7177 async createDirectory ( uri : vscode . Uri ) : Promise < void > {
7278 try {
73- // no shortcut: does mkdirp
74- return await that . _proxy . $mkdir ( uri ) ;
79+ const provider = that . _fileSystemProvider . get ( uri . scheme ) ;
80+ if ( provider && ! provider . isReadonly ) {
81+ // use shortcut
82+ await that . _proxy . $ensureActivation ( uri . scheme ) ;
83+ return await that . mkdirp ( provider . impl , provider . extUri , uri ) ;
84+ } else {
85+ return await that . _proxy . $mkdir ( uri ) ;
86+ }
7587 } catch ( err ) {
7688 return ExtHostConsumerFileSystem . _handleError ( err ) ;
7789 }
@@ -82,7 +94,7 @@ export class ExtHostConsumerFileSystem {
8294 if ( provider ) {
8395 // use shortcut
8496 await that . _proxy . $ensureActivation ( uri . scheme ) ;
85- return ( await provider . readFile ( uri ) ) . slice ( ) ; // safe-copy
97+ return ( await provider . impl . readFile ( uri ) ) . slice ( ) ; // safe-copy
8698 } else {
8799 const buff = await that . _proxy . $readFile ( uri ) ;
88100 return buff . buffer ;
@@ -93,19 +105,26 @@ export class ExtHostConsumerFileSystem {
93105 } ,
94106 async writeFile ( uri : vscode . Uri , content : Uint8Array ) : Promise < void > {
95107 try {
96- // no shortcut: does mkdirp
97- return await that . _proxy . $writeFile ( uri , VSBuffer . wrap ( content ) ) ;
108+ const provider = that . _fileSystemProvider . get ( uri . scheme ) ;
109+ if ( provider && ! provider . isReadonly ) {
110+ // use shortcut
111+ await that . _proxy . $ensureActivation ( uri . scheme ) ;
112+ await that . mkdirp ( provider . impl , provider . extUri , provider . extUri . dirname ( uri ) ) ;
113+ return await that . writeQueue . queueFor ( uri ) . queue ( ( ) => Promise . resolve ( provider . impl . writeFile ( uri , content , { create : true , overwrite : true } ) ) ) ;
114+ } else {
115+ return await that . _proxy . $writeFile ( uri , VSBuffer . wrap ( content ) ) ;
116+ }
98117 } catch ( err ) {
99118 return ExtHostConsumerFileSystem . _handleError ( err ) ;
100119 }
101120 } ,
102121 async delete ( uri : vscode . Uri , options ?: { recursive ?: boolean ; useTrash ?: boolean } ) : Promise < void > {
103122 try {
104123 const provider = that . _fileSystemProvider . get ( uri . scheme ) ;
105- if ( provider ) {
124+ if ( provider && ! provider . isReadonly ) {
106125 // use shortcut
107126 await that . _proxy . $ensureActivation ( uri . scheme ) ;
108- return await provider . delete ( uri , { recursive : false , ...options } ) ;
127+ return await provider . impl . delete ( uri , { recursive : false , ...options } ) ;
109128 } else {
110129 return await that . _proxy . $delete ( uri , { recursive : false , useTrash : false , atomic : false , ...options } ) ;
111130 }
@@ -139,6 +158,49 @@ export class ExtHostConsumerFileSystem {
139158 } ) ;
140159 }
141160
161+ private async mkdirp ( provider : vscode . FileSystemProvider , providerExtUri : IExtUri , directory : vscode . Uri ) : Promise < void > {
162+ const directoriesToCreate : string [ ] = [ ] ;
163+
164+ while ( ! providerExtUri . isEqual ( directory , providerExtUri . dirname ( directory ) ) ) {
165+ try {
166+ const stat = await provider . stat ( directory ) ;
167+ if ( ( stat . type & files . FileType . Directory ) === 0 ) {
168+ throw FileSystemError . FileExists ( `Unable to create folder '${ directory . scheme === Schemas . file ? directory . fsPath : directory . toString ( true ) } ' that already exists but is not a directory` ) ;
169+ }
170+
171+ break ; // we have hit a directory that exists -> good
172+ } catch ( error ) {
173+ if ( files . toFileSystemProviderErrorCode ( error ) !== files . FileSystemProviderErrorCode . FileNotFound ) {
174+ throw error ;
175+ }
176+
177+ // further go up and remember to create this directory
178+ directoriesToCreate . push ( providerExtUri . basename ( directory ) ) ;
179+ directory = providerExtUri . dirname ( directory ) ;
180+ }
181+ }
182+
183+ for ( let i = directoriesToCreate . length - 1 ; i >= 0 ; i -- ) {
184+ directory = providerExtUri . joinPath ( directory , directoriesToCreate [ i ] ) ;
185+
186+ try {
187+ await provider . createDirectory ( directory ) ;
188+ } catch ( error ) {
189+ if ( files . toFileSystemProviderErrorCode ( error ) !== files . FileSystemProviderErrorCode . FileExists ) {
190+ // For mkdirp() we tolerate that the mkdir() call fails
191+ // in case the folder already exists. This follows node.js
192+ // own implementation of fs.mkdir({ recursive: true }) and
193+ // reduces the chances of race conditions leading to errors
194+ // if multiple calls try to create the same folders
195+ // As such, we only throw an error here if it is other than
196+ // the fact that the file already exists.
197+ // (see also https://github.com/microsoft/vscode/issues/89834)
198+ throw error ;
199+ }
200+ }
201+ }
202+ }
203+
142204 private static _handleError ( err : any ) : never {
143205 // desired error type
144206 if ( err instanceof FileSystemError ) {
@@ -184,8 +246,8 @@ export class ExtHostConsumerFileSystem {
184246
185247 // ---
186248
187- addFileSystemProvider ( scheme : string , provider : vscode . FileSystemProvider ) : IDisposable {
188- this . _fileSystemProvider . set ( scheme , provider ) ;
249+ addFileSystemProvider ( scheme : string , provider : vscode . FileSystemProvider , options ?: { isCaseSensitive ?: boolean ; isReadonly ?: boolean | IMarkdownString } ) : IDisposable {
250+ this . _fileSystemProvider . set ( scheme , { impl : provider , extUri : options ?. isCaseSensitive ? extUri : extUriIgnorePathCase , isReadonly : ! ! options ?. isReadonly } ) ;
189251 return toDisposable ( ( ) => this . _fileSystemProvider . delete ( scheme ) ) ;
190252 }
191253}
0 commit comments