Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/api/browser/mainThreadFileSystem.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Emitter, Event } from '../../../base/common/event.js';
7
import { IDisposable, toDisposable, DisposableStore, DisposableMap } from '../../../base/common/lifecycle.js';
8
import { URI, UriComponents } from '../../../base/common/uri.js';
9
import { IFileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IStat, IWatchOptions, FileType, IFileOverwriteOptions, IFileDeleteOptions, IFileOpenOptions, FileOperationError, FileOperationResult, FileSystemProviderErrorCode, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithFileFolderCopyCapability, FilePermission, toFileSystemProviderErrorCode, IFileStatWithPartialMetadata, IFileStat } from '../../../platform/files/common/files.js';
10
import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js';
11
import { ExtHostContext, ExtHostFileSystemShape, IFileChangeDto, MainContext, MainThreadFileSystemShape } from '../common/extHost.protocol.js';
12
import { VSBuffer } from '../../../base/common/buffer.js';
13
import { IMarkdownString } from '../../../base/common/htmlContent.js';
14
15
@extHostNamedCustomer(MainContext.MainThreadFileSystem)
16
export class MainThreadFileSystem implements MainThreadFileSystemShape {
17
18
private readonly _proxy: ExtHostFileSystemShape;
19
private readonly _fileProvider = new DisposableMap<number, RemoteFileSystemProvider>();
20
private readonly _disposables = new DisposableStore();
21
22
constructor(
23
extHostContext: IExtHostContext,
24
@IFileService private readonly _fileService: IFileService
25
) {
26
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystem);
27
28
const infoProxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemInfo);
29
30
for (const entry of _fileService.listCapabilities()) {
31
infoProxy.$acceptProviderInfos(URI.from({ scheme: entry.scheme, path: '/dummy' }), entry.capabilities);
32
}
33
this._disposables.add(_fileService.onDidChangeFileSystemProviderRegistrations(e => infoProxy.$acceptProviderInfos(URI.from({ scheme: e.scheme, path: '/dummy' }), e.provider?.capabilities ?? null)));
34
this._disposables.add(_fileService.onDidChangeFileSystemProviderCapabilities(e => infoProxy.$acceptProviderInfos(URI.from({ scheme: e.scheme, path: '/dummy' }), e.provider.capabilities)));
35
}
36
37
dispose(): void {
38
this._disposables.dispose();
39
this._fileProvider.dispose();
40
}
41
42
async $registerFileSystemProvider(handle: number, scheme: string, capabilities: FileSystemProviderCapabilities, readonlyMessage?: IMarkdownString): Promise<void> {
43
this._fileProvider.set(handle, new RemoteFileSystemProvider(this._fileService, scheme, capabilities, readonlyMessage, handle, this._proxy));
44
}
45
46
$unregisterProvider(handle: number): void {
47
this._fileProvider.deleteAndDispose(handle);
48
}
49
50
$onFileSystemChange(handle: number, changes: IFileChangeDto[]): void {
51
const fileProvider = this._fileProvider.get(handle);
52
if (!fileProvider) {
53
throw new Error('Unknown file provider');
54
}
55
fileProvider.$onFileSystemChange(changes);
56
}
57
58
59
// --- consumer fs, vscode.workspace.fs
60
61
async $stat(uri: UriComponents): Promise<IStat> {
62
try {
63
const stat = await this._fileService.stat(URI.revive(uri));
64
return {
65
ctime: stat.ctime,
66
mtime: stat.mtime,
67
size: stat.size,
68
permissions: stat.readonly ? FilePermission.Readonly : undefined,
69
type: MainThreadFileSystem._asFileType(stat)
70
};
71
} catch (err) {
72
return MainThreadFileSystem._handleError(err);
73
}
74
}
75
76
async $readdir(uri: UriComponents): Promise<[string, FileType][]> {
77
try {
78
const stat = await this._fileService.resolve(URI.revive(uri), { resolveMetadata: false });
79
if (!stat.isDirectory) {
80
const err = new Error(stat.name);
81
err.name = FileSystemProviderErrorCode.FileNotADirectory;
82
throw err;
83
}
84
return !stat.children ? [] : stat.children.map(child => [child.name, MainThreadFileSystem._asFileType(child)] as [string, FileType]);
85
} catch (err) {
86
return MainThreadFileSystem._handleError(err);
87
}
88
}
89
90
private static _asFileType(stat: IFileStat | IFileStatWithPartialMetadata): FileType {
91
let res = 0;
92
if (stat.isFile) {
93
res += FileType.File;
94
95
} else if (stat.isDirectory) {
96
res += FileType.Directory;
97
}
98
if (stat.isSymbolicLink) {
99
res += FileType.SymbolicLink;
100
}
101
return res;
102
}
103
104
async $readFile(uri: UriComponents): Promise<VSBuffer> {
105
try {
106
const file = await this._fileService.readFile(URI.revive(uri));
107
return file.value;
108
} catch (err) {
109
return MainThreadFileSystem._handleError(err);
110
}
111
}
112
113
async $writeFile(uri: UriComponents, content: VSBuffer): Promise<void> {
114
try {
115
await this._fileService.writeFile(URI.revive(uri), content);
116
} catch (err) {
117
return MainThreadFileSystem._handleError(err);
118
}
119
}
120
121
async $rename(source: UriComponents, target: UriComponents, opts: IFileOverwriteOptions): Promise<void> {
122
try {
123
await this._fileService.move(URI.revive(source), URI.revive(target), opts.overwrite);
124
} catch (err) {
125
return MainThreadFileSystem._handleError(err);
126
}
127
}
128
129
async $copy(source: UriComponents, target: UriComponents, opts: IFileOverwriteOptions): Promise<void> {
130
try {
131
await this._fileService.copy(URI.revive(source), URI.revive(target), opts.overwrite);
132
} catch (err) {
133
return MainThreadFileSystem._handleError(err);
134
}
135
}
136
137
async $mkdir(uri: UriComponents): Promise<void> {
138
try {
139
await this._fileService.createFolder(URI.revive(uri));
140
} catch (err) {
141
return MainThreadFileSystem._handleError(err);
142
}
143
}
144
145
async $delete(uri: UriComponents, opts: IFileDeleteOptions): Promise<void> {
146
try {
147
return await this._fileService.del(URI.revive(uri), opts);
148
} catch (err) {
149
return MainThreadFileSystem._handleError(err);
150
}
151
}
152
153
private static _handleError(err: any): never {
154
if (err instanceof FileOperationError) {
155
switch (err.fileOperationResult) {
156
case FileOperationResult.FILE_NOT_FOUND:
157
err.name = FileSystemProviderErrorCode.FileNotFound;
158
break;
159
case FileOperationResult.FILE_IS_DIRECTORY:
160
err.name = FileSystemProviderErrorCode.FileIsADirectory;
161
break;
162
case FileOperationResult.FILE_PERMISSION_DENIED:
163
err.name = FileSystemProviderErrorCode.NoPermissions;
164
break;
165
case FileOperationResult.FILE_MOVE_CONFLICT:
166
err.name = FileSystemProviderErrorCode.FileExists;
167
break;
168
}
169
} else if (err instanceof Error) {
170
const code = toFileSystemProviderErrorCode(err);
171
if (code !== FileSystemProviderErrorCode.Unknown) {
172
err.name = code;
173
}
174
}
175
176
throw err;
177
}
178
179
$ensureActivation(scheme: string): Promise<void> {
180
return this._fileService.activateProvider(scheme);
181
}
182
}
183
184
class RemoteFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileFolderCopyCapability {
185
186
private readonly _onDidChange = new Emitter<readonly IFileChange[]>();
187
private readonly _registration: IDisposable;
188
189
readonly onDidChangeFile: Event<readonly IFileChange[]> = this._onDidChange.event;
190
191
readonly capabilities: FileSystemProviderCapabilities;
192
readonly onDidChangeCapabilities: Event<void> = Event.None;
193
194
constructor(
195
fileService: IFileService,
196
scheme: string,
197
capabilities: FileSystemProviderCapabilities,
198
public readonly readOnlyMessage: IMarkdownString | undefined,
199
private readonly _handle: number,
200
private readonly _proxy: ExtHostFileSystemShape
201
) {
202
this.capabilities = capabilities;
203
this._registration = fileService.registerProvider(scheme, this);
204
}
205
206
dispose(): void {
207
this._registration.dispose();
208
this._onDidChange.dispose();
209
}
210
211
watch(resource: URI, opts: IWatchOptions) {
212
const session = Math.random();
213
this._proxy.$watch(this._handle, session, resource, opts);
214
return toDisposable(() => {
215
this._proxy.$unwatch(this._handle, session);
216
});
217
}
218
219
$onFileSystemChange(changes: IFileChangeDto[]): void {
220
this._onDidChange.fire(changes.map(RemoteFileSystemProvider._createFileChange));
221
}
222
223
private static _createFileChange(dto: IFileChangeDto): IFileChange {
224
return { resource: URI.revive(dto.resource), type: dto.type };
225
}
226
227
// --- forwarding calls
228
229
async stat(resource: URI): Promise<IStat> {
230
try {
231
return await this._proxy.$stat(this._handle, resource);
232
} catch (err) {
233
throw err;
234
}
235
}
236
237
async readFile(resource: URI): Promise<Uint8Array> {
238
const buffer = await this._proxy.$readFile(this._handle, resource);
239
return buffer.buffer;
240
}
241
242
writeFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void> {
243
return this._proxy.$writeFile(this._handle, resource, VSBuffer.wrap(content), opts);
244
}
245
246
delete(resource: URI, opts: IFileDeleteOptions): Promise<void> {
247
return this._proxy.$delete(this._handle, resource, opts);
248
}
249
250
mkdir(resource: URI): Promise<void> {
251
return this._proxy.$mkdir(this._handle, resource);
252
}
253
254
readdir(resource: URI): Promise<[string, FileType][]> {
255
return this._proxy.$readdir(this._handle, resource);
256
}
257
258
rename(resource: URI, target: URI, opts: IFileOverwriteOptions): Promise<void> {
259
return this._proxy.$rename(this._handle, resource, target, opts);
260
}
261
262
copy(resource: URI, target: URI, opts: IFileOverwriteOptions): Promise<void> {
263
return this._proxy.$copy(this._handle, resource, target, opts);
264
}
265
266
open(resource: URI, opts: IFileOpenOptions): Promise<number> {
267
return this._proxy.$open(this._handle, resource, opts);
268
}
269
270
close(fd: number): Promise<void> {
271
return this._proxy.$close(this._handle, fd);
272
}
273
274
async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
275
const readData = await this._proxy.$read(this._handle, fd, pos, length);
276
data.set(readData.buffer, offset);
277
return readData.byteLength;
278
}
279
280
write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
281
return this._proxy.$write(this._handle, fd, pos, VSBuffer.wrap(data).slice(offset, offset + length));
282
}
283
}
284
285