Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/files/common/diskFileSystemProviderClient.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 { VSBuffer } from '../../../base/common/buffer.js';
7
import { CancellationToken } from '../../../base/common/cancellation.js';
8
import { toErrorMessage } from '../../../base/common/errorMessage.js';
9
import { canceled } from '../../../base/common/errors.js';
10
import { Emitter, Event } from '../../../base/common/event.js';
11
import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
12
import { newWriteableStream, ReadableStreamEventPayload, ReadableStreamEvents } from '../../../base/common/stream.js';
13
import { URI } from '../../../base/common/uri.js';
14
import { generateUuid } from '../../../base/common/uuid.js';
15
import { IChannel } from '../../../base/parts/ipc/common/ipc.js';
16
import { createFileSystemProviderError, IFileAtomicReadOptions, IFileDeleteOptions, IFileOpenOptions, IFileOverwriteOptions, IFileReadStreamOptions, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileChange, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileCloneCapability, IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IStat, IWatchOptions, IFileSystemProviderError } from './files.js';
17
import { reviveFileChanges } from './watcher.js';
18
19
export const LOCAL_FILE_SYSTEM_CHANNEL_NAME = 'localFilesystem';
20
21
/**
22
* An implementation of a local disk file system provider
23
* that is backed by a `IChannel` and thus implemented via
24
* IPC on a different process.
25
*/
26
export class DiskFileSystemProviderClient extends Disposable implements
27
IFileSystemProviderWithFileReadWriteCapability,
28
IFileSystemProviderWithOpenReadWriteCloseCapability,
29
IFileSystemProviderWithFileReadStreamCapability,
30
IFileSystemProviderWithFileFolderCopyCapability,
31
IFileSystemProviderWithFileAtomicReadCapability,
32
IFileSystemProviderWithFileCloneCapability {
33
34
constructor(
35
private readonly channel: IChannel,
36
private readonly extraCapabilities: { trash?: boolean; pathCaseSensitive?: boolean }
37
) {
38
super();
39
40
this.registerFileChangeListeners();
41
}
42
43
//#region File Capabilities
44
45
readonly onDidChangeCapabilities: Event<void> = Event.None;
46
47
private _capabilities: FileSystemProviderCapabilities | undefined;
48
get capabilities(): FileSystemProviderCapabilities {
49
if (!this._capabilities) {
50
this._capabilities =
51
FileSystemProviderCapabilities.FileReadWrite |
52
FileSystemProviderCapabilities.FileOpenReadWriteClose |
53
FileSystemProviderCapabilities.FileReadStream |
54
FileSystemProviderCapabilities.FileFolderCopy |
55
FileSystemProviderCapabilities.FileWriteUnlock |
56
FileSystemProviderCapabilities.FileAtomicRead |
57
FileSystemProviderCapabilities.FileAtomicWrite |
58
FileSystemProviderCapabilities.FileAtomicDelete |
59
FileSystemProviderCapabilities.FileClone |
60
FileSystemProviderCapabilities.FileRealpath;
61
62
if (this.extraCapabilities.pathCaseSensitive) {
63
this._capabilities |= FileSystemProviderCapabilities.PathCaseSensitive;
64
}
65
66
if (this.extraCapabilities.trash) {
67
this._capabilities |= FileSystemProviderCapabilities.Trash;
68
}
69
}
70
71
return this._capabilities;
72
}
73
74
//#endregion
75
76
//#region File Metadata Resolving
77
78
stat(resource: URI): Promise<IStat> {
79
return this.channel.call('stat', [resource]);
80
}
81
82
realpath(resource: URI): Promise<string> {
83
return this.channel.call('realpath', [resource]);
84
}
85
86
readdir(resource: URI): Promise<[string, FileType][]> {
87
return this.channel.call('readdir', [resource]);
88
}
89
90
//#endregion
91
92
//#region File Reading/Writing
93
94
async readFile(resource: URI, opts?: IFileAtomicReadOptions): Promise<Uint8Array> {
95
const { buffer } = await this.channel.call('readFile', [resource, opts]) as VSBuffer;
96
97
return buffer;
98
}
99
100
readFileStream(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents<Uint8Array> {
101
const stream = newWriteableStream<Uint8Array>(data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer);
102
const disposables = new DisposableStore();
103
104
// Reading as file stream goes through an event to the remote side
105
disposables.add(this.channel.listen<ReadableStreamEventPayload<VSBuffer>>('readFileStream', [resource, opts])(dataOrErrorOrEnd => {
106
107
// data
108
if (dataOrErrorOrEnd instanceof VSBuffer) {
109
stream.write(dataOrErrorOrEnd.buffer);
110
}
111
112
// end or error
113
else {
114
if (dataOrErrorOrEnd === 'end') {
115
stream.end();
116
} else {
117
let error: Error;
118
119
// Take Error as is if type matches
120
if (dataOrErrorOrEnd instanceof Error) {
121
error = dataOrErrorOrEnd;
122
}
123
124
// Otherwise, try to deserialize into an error.
125
// Since we communicate via IPC, we cannot be sure
126
// that Error objects are properly serialized.
127
else {
128
const errorCandidate = dataOrErrorOrEnd as IFileSystemProviderError;
129
130
error = createFileSystemProviderError(errorCandidate.message ?? toErrorMessage(errorCandidate), errorCandidate.code ?? FileSystemProviderErrorCode.Unknown);
131
}
132
133
stream.error(error);
134
stream.end();
135
}
136
137
// Signal to the remote side that we no longer listen
138
disposables.dispose();
139
}
140
}));
141
142
// Support cancellation
143
disposables.add(token.onCancellationRequested(() => {
144
145
// Ensure to end the stream properly with an error
146
// to indicate the cancellation.
147
stream.error(canceled());
148
stream.end();
149
150
// Ensure to dispose the listener upon cancellation. This will
151
// bubble through the remote side as event and allows to stop
152
// reading the file.
153
disposables.dispose();
154
}));
155
156
return stream;
157
}
158
159
writeFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void> {
160
return this.channel.call('writeFile', [resource, VSBuffer.wrap(content), opts]);
161
}
162
163
open(resource: URI, opts: IFileOpenOptions): Promise<number> {
164
return this.channel.call('open', [resource, opts]);
165
}
166
167
close(fd: number): Promise<void> {
168
return this.channel.call('close', [fd]);
169
}
170
171
async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
172
const [bytes, bytesRead]: [VSBuffer, number] = await this.channel.call('read', [fd, pos, length]);
173
174
// copy back the data that was written into the buffer on the remote
175
// side. we need to do this because buffers are not referenced by
176
// pointer, but only by value and as such cannot be directly written
177
// to from the other process.
178
data.set(bytes.buffer.slice(0, bytesRead), offset);
179
180
return bytesRead;
181
}
182
183
write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
184
return this.channel.call('write', [fd, pos, VSBuffer.wrap(data), offset, length]);
185
}
186
187
//#endregion
188
189
//#region Move/Copy/Delete/Create Folder
190
191
mkdir(resource: URI): Promise<void> {
192
return this.channel.call('mkdir', [resource]);
193
}
194
195
delete(resource: URI, opts: IFileDeleteOptions): Promise<void> {
196
return this.channel.call('delete', [resource, opts]);
197
}
198
199
rename(resource: URI, target: URI, opts: IFileOverwriteOptions): Promise<void> {
200
return this.channel.call('rename', [resource, target, opts]);
201
}
202
203
copy(resource: URI, target: URI, opts: IFileOverwriteOptions): Promise<void> {
204
return this.channel.call('copy', [resource, target, opts]);
205
}
206
207
//#endregion
208
209
//#region Clone File
210
211
cloneFile(resource: URI, target: URI): Promise<void> {
212
return this.channel.call('cloneFile', [resource, target]);
213
}
214
215
//#endregion
216
217
//#region File Watching
218
219
private readonly _onDidChange = this._register(new Emitter<readonly IFileChange[]>());
220
readonly onDidChangeFile = this._onDidChange.event;
221
222
private readonly _onDidWatchError = this._register(new Emitter<string>());
223
readonly onDidWatchError = this._onDidWatchError.event;
224
225
// The contract for file watching via remote is to identify us
226
// via a unique but readonly session ID. Since the remote is
227
// managing potentially many watchers from different clients,
228
// this helps the server to properly partition events to the right
229
// clients.
230
private readonly sessionId = generateUuid();
231
232
private registerFileChangeListeners(): void {
233
234
// The contract for file changes is that there is one listener
235
// for both events and errors from the watcher. So we need to
236
// unwrap the event from the remote and emit through the proper
237
// emitter.
238
this._register(this.channel.listen<IFileChange[] | string>('fileChange', [this.sessionId])(eventsOrError => {
239
if (Array.isArray(eventsOrError)) {
240
const events = eventsOrError;
241
this._onDidChange.fire(reviveFileChanges(events));
242
} else {
243
const error = eventsOrError;
244
this._onDidWatchError.fire(error);
245
}
246
}));
247
}
248
249
watch(resource: URI, opts: IWatchOptions): IDisposable {
250
251
// Generate a request UUID to correlate the watcher
252
// back to us when we ask to dispose the watcher later.
253
const req = generateUuid();
254
255
this.channel.call('watch', [this.sessionId, req, resource, opts]);
256
257
return toDisposable(() => this.channel.call('unwatch', [this.sessionId, req]));
258
}
259
260
//#endregion
261
}
262
263