4
4
*--------------------------------------------------------------------------------------------*/
5
5
6
6
import { MainContext , MainThreadFileSystemShape } from './extHost.protocol' ;
7
- import * as vscode from 'vscode' ;
7
+ import type * as vscode from 'vscode' ;
8
8
import * as files from 'vs/platform/files/common/files' ;
9
9
import { FileSystemError } from 'vs/workbench/api/common/extHostTypes' ;
10
10
import { VSBuffer } from 'vs/base/common/buffer' ;
11
11
import { createDecorator } from 'vs/platform/instantiation/common/instantiation' ;
12
12
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService' ;
13
13
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo' ;
14
14
import { 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' ;
15
19
16
20
export class ExtHostConsumerFileSystem {
17
21
@@ -20,7 +24,9 @@ export class ExtHostConsumerFileSystem {
20
24
readonly value : vscode . FileSystem ;
21
25
22
26
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 ( ) ;
24
30
25
31
constructor (
26
32
@IExtHostRpcService extHostRpc : IExtHostRpcService ,
@@ -38,7 +44,7 @@ export class ExtHostConsumerFileSystem {
38
44
if ( provider ) {
39
45
// use shortcut
40
46
await that . _proxy . $ensureActivation ( uri . scheme ) ;
41
- stat = await provider . stat ( uri ) ;
47
+ stat = await provider . impl . stat ( uri ) ;
42
48
} else {
43
49
stat = await that . _proxy . $stat ( uri ) ;
44
50
}
@@ -60,7 +66,7 @@ export class ExtHostConsumerFileSystem {
60
66
if ( provider ) {
61
67
// use shortcut
62
68
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
64
70
} else {
65
71
return await that . _proxy . $readdir ( uri ) ;
66
72
}
@@ -70,8 +76,14 @@ export class ExtHostConsumerFileSystem {
70
76
} ,
71
77
async createDirectory ( uri : vscode . Uri ) : Promise < void > {
72
78
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
+ }
75
87
} catch ( err ) {
76
88
return ExtHostConsumerFileSystem . _handleError ( err ) ;
77
89
}
@@ -82,7 +94,7 @@ export class ExtHostConsumerFileSystem {
82
94
if ( provider ) {
83
95
// use shortcut
84
96
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
86
98
} else {
87
99
const buff = await that . _proxy . $readFile ( uri ) ;
88
100
return buff . buffer ;
@@ -93,19 +105,26 @@ export class ExtHostConsumerFileSystem {
93
105
} ,
94
106
async writeFile ( uri : vscode . Uri , content : Uint8Array ) : Promise < void > {
95
107
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
+ }
98
117
} catch ( err ) {
99
118
return ExtHostConsumerFileSystem . _handleError ( err ) ;
100
119
}
101
120
} ,
102
121
async delete ( uri : vscode . Uri , options ?: { recursive ?: boolean ; useTrash ?: boolean } ) : Promise < void > {
103
122
try {
104
123
const provider = that . _fileSystemProvider . get ( uri . scheme ) ;
105
- if ( provider ) {
124
+ if ( provider && ! provider . isReadonly ) {
106
125
// use shortcut
107
126
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 } ) ;
109
128
} else {
110
129
return await that . _proxy . $delete ( uri , { recursive : false , useTrash : false , atomic : false , ...options } ) ;
111
130
}
@@ -139,6 +158,49 @@ export class ExtHostConsumerFileSystem {
139
158
} ) ;
140
159
}
141
160
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
+
142
204
private static _handleError ( err : any ) : never {
143
205
// desired error type
144
206
if ( err instanceof FileSystemError ) {
@@ -184,8 +246,8 @@ export class ExtHostConsumerFileSystem {
184
246
185
247
// ---
186
248
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 } ) ;
189
251
return toDisposable ( ( ) => this . _fileSystemProvider . delete ( scheme ) ) ;
190
252
}
191
253
}
0 commit comments