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