Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/files/common/inMemoryFilesystemProvider.ts
3294 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 { Emitter, Event } from '../../../base/common/event.js';
8
import { Disposable, IDisposable } from '../../../base/common/lifecycle.js';
9
import * as resources from '../../../base/common/resources.js';
10
import { ReadableStreamEvents, newWriteableStream } from '../../../base/common/stream.js';
11
import { URI } from '../../../base/common/uri.js';
12
import { FileChangeType, IFileDeleteOptions, IFileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions, createFileSystemProviderError, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileOpenOptions, IFileSystemProviderWithFileAtomicDeleteCapability, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileAtomicWriteCapability, IFileSystemProviderWithFileReadStreamCapability } from './files.js';
13
14
class File implements IStat {
15
16
readonly type: FileType.File;
17
readonly ctime: number;
18
mtime: number;
19
size: number;
20
21
name: string;
22
data?: Uint8Array;
23
24
constructor(name: string) {
25
this.type = FileType.File;
26
this.ctime = Date.now();
27
this.mtime = Date.now();
28
this.size = 0;
29
this.name = name;
30
}
31
}
32
33
class Directory implements IStat {
34
35
readonly type: FileType.Directory;
36
readonly ctime: number;
37
mtime: number;
38
size: number;
39
40
name: string;
41
readonly entries: Map<string, File | Directory>;
42
43
constructor(name: string) {
44
this.type = FileType.Directory;
45
this.ctime = Date.now();
46
this.mtime = Date.now();
47
this.size = 0;
48
this.name = name;
49
this.entries = new Map();
50
}
51
}
52
53
type Entry = File | Directory;
54
55
export class InMemoryFileSystemProvider extends Disposable implements
56
IFileSystemProviderWithFileReadWriteCapability,
57
IFileSystemProviderWithOpenReadWriteCloseCapability,
58
IFileSystemProviderWithFileReadStreamCapability,
59
IFileSystemProviderWithFileAtomicReadCapability,
60
IFileSystemProviderWithFileAtomicWriteCapability,
61
IFileSystemProviderWithFileAtomicDeleteCapability {
62
63
private memoryFdCounter = 0;
64
private readonly fdMemory = new Map<number, Uint8Array>();
65
private _onDidChangeCapabilities = this._register(new Emitter<void>());
66
readonly onDidChangeCapabilities = this._onDidChangeCapabilities.event;
67
68
private _capabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive;
69
get capabilities(): FileSystemProviderCapabilities { return this._capabilities; }
70
71
setReadOnly(readonly: boolean) {
72
const isReadonly = !!(this._capabilities & FileSystemProviderCapabilities.Readonly);
73
if (readonly !== isReadonly) {
74
this._capabilities = readonly ? FileSystemProviderCapabilities.Readonly | FileSystemProviderCapabilities.PathCaseSensitive | FileSystemProviderCapabilities.FileReadWrite
75
: FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive;
76
this._onDidChangeCapabilities.fire();
77
}
78
}
79
80
root = new Directory('');
81
82
// --- manage file metadata
83
84
async stat(resource: URI): Promise<IStat> {
85
return this._lookup(resource, false);
86
}
87
88
async readdir(resource: URI): Promise<[string, FileType][]> {
89
const entry = this._lookupAsDirectory(resource, false);
90
const result: [string, FileType][] = [];
91
entry.entries.forEach((child, name) => result.push([name, child.type]));
92
return result;
93
}
94
95
// --- manage file contents
96
97
async readFile(resource: URI): Promise<Uint8Array> {
98
const data = this._lookupAsFile(resource, false).data;
99
if (data) {
100
return data;
101
}
102
throw createFileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound);
103
}
104
105
readFileStream(resource: URI): ReadableStreamEvents<Uint8Array> {
106
const data = this._lookupAsFile(resource, false).data;
107
108
const stream = newWriteableStream<Uint8Array>(data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer);
109
stream.end(data);
110
111
return stream;
112
}
113
114
async writeFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void> {
115
const basename = resources.basename(resource);
116
const parent = this._lookupParentDirectory(resource);
117
let entry = parent.entries.get(basename);
118
if (entry instanceof Directory) {
119
throw createFileSystemProviderError('file is directory', FileSystemProviderErrorCode.FileIsADirectory);
120
}
121
if (!entry && !opts.create) {
122
throw createFileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound);
123
}
124
if (entry && opts.create && !opts.overwrite) {
125
throw createFileSystemProviderError('file exists already', FileSystemProviderErrorCode.FileExists);
126
}
127
if (!entry) {
128
entry = new File(basename);
129
parent.entries.set(basename, entry);
130
this._fireSoon({ type: FileChangeType.ADDED, resource });
131
}
132
entry.mtime = Date.now();
133
entry.size = content.byteLength;
134
entry.data = content;
135
136
this._fireSoon({ type: FileChangeType.UPDATED, resource });
137
}
138
139
// file open/read/write/close
140
open(resource: URI, opts: IFileOpenOptions): Promise<number> {
141
const data = this._lookupAsFile(resource, false).data;
142
if (data) {
143
const fd = this.memoryFdCounter++;
144
this.fdMemory.set(fd, data);
145
return Promise.resolve(fd);
146
}
147
throw createFileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound);
148
}
149
150
close(fd: number): Promise<void> {
151
this.fdMemory.delete(fd);
152
return Promise.resolve();
153
}
154
155
read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
156
const memory = this.fdMemory.get(fd);
157
if (!memory) {
158
throw createFileSystemProviderError(`No file with that descriptor open`, FileSystemProviderErrorCode.Unavailable);
159
}
160
161
const toWrite = VSBuffer.wrap(memory).slice(pos, pos + length);
162
data.set(toWrite.buffer, offset);
163
return Promise.resolve(toWrite.byteLength);
164
}
165
166
write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
167
const memory = this.fdMemory.get(fd);
168
if (!memory) {
169
throw createFileSystemProviderError(`No file with that descriptor open`, FileSystemProviderErrorCode.Unavailable);
170
}
171
172
const toWrite = VSBuffer.wrap(data).slice(offset, offset + length);
173
memory.set(toWrite.buffer, pos);
174
return Promise.resolve(toWrite.byteLength);
175
}
176
177
// --- manage files/folders
178
179
async rename(from: URI, to: URI, opts: IFileOverwriteOptions): Promise<void> {
180
if (!opts.overwrite && this._lookup(to, true)) {
181
throw createFileSystemProviderError('file exists already', FileSystemProviderErrorCode.FileExists);
182
}
183
184
const entry = this._lookup(from, false);
185
const oldParent = this._lookupParentDirectory(from);
186
187
const newParent = this._lookupParentDirectory(to);
188
const newName = resources.basename(to);
189
190
oldParent.entries.delete(entry.name);
191
entry.name = newName;
192
newParent.entries.set(newName, entry);
193
194
this._fireSoon(
195
{ type: FileChangeType.DELETED, resource: from },
196
{ type: FileChangeType.ADDED, resource: to }
197
);
198
}
199
200
async delete(resource: URI, opts: IFileDeleteOptions): Promise<void> {
201
const dirname = resources.dirname(resource);
202
const basename = resources.basename(resource);
203
const parent = this._lookupAsDirectory(dirname, false);
204
if (parent.entries.has(basename)) {
205
parent.entries.delete(basename);
206
parent.mtime = Date.now();
207
parent.size -= 1;
208
this._fireSoon({ type: FileChangeType.UPDATED, resource: dirname }, { resource, type: FileChangeType.DELETED });
209
}
210
}
211
212
async mkdir(resource: URI): Promise<void> {
213
if (this._lookup(resource, true)) {
214
throw createFileSystemProviderError('file exists already', FileSystemProviderErrorCode.FileExists);
215
}
216
217
const basename = resources.basename(resource);
218
const dirname = resources.dirname(resource);
219
const parent = this._lookupAsDirectory(dirname, false);
220
221
const entry = new Directory(basename);
222
parent.entries.set(entry.name, entry);
223
parent.mtime = Date.now();
224
parent.size += 1;
225
this._fireSoon({ type: FileChangeType.UPDATED, resource: dirname }, { type: FileChangeType.ADDED, resource });
226
}
227
228
// --- lookup
229
230
private _lookup(uri: URI, silent: false): Entry;
231
private _lookup(uri: URI, silent: boolean): Entry | undefined;
232
private _lookup(uri: URI, silent: boolean): Entry | undefined {
233
const parts = uri.path.split('/');
234
let entry: Entry = this.root;
235
for (const part of parts) {
236
if (!part) {
237
continue;
238
}
239
let child: Entry | undefined;
240
if (entry instanceof Directory) {
241
child = entry.entries.get(part);
242
}
243
if (!child) {
244
if (!silent) {
245
throw createFileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound);
246
} else {
247
return undefined;
248
}
249
}
250
entry = child;
251
}
252
return entry;
253
}
254
255
private _lookupAsDirectory(uri: URI, silent: boolean): Directory {
256
const entry = this._lookup(uri, silent);
257
if (entry instanceof Directory) {
258
return entry;
259
}
260
throw createFileSystemProviderError('file not a directory', FileSystemProviderErrorCode.FileNotADirectory);
261
}
262
263
private _lookupAsFile(uri: URI, silent: boolean): File {
264
const entry = this._lookup(uri, silent);
265
if (entry instanceof File) {
266
return entry;
267
}
268
throw createFileSystemProviderError('file is a directory', FileSystemProviderErrorCode.FileIsADirectory);
269
}
270
271
private _lookupParentDirectory(uri: URI): Directory {
272
const dirname = resources.dirname(uri);
273
return this._lookupAsDirectory(dirname, false);
274
}
275
276
// --- manage file events
277
278
private readonly _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());
279
readonly onDidChangeFile: Event<readonly IFileChange[]> = this._onDidChangeFile.event;
280
281
private _bufferedChanges: IFileChange[] = [];
282
private _fireSoonHandle?: Timeout;
283
284
watch(resource: URI, opts: IWatchOptions): IDisposable {
285
// ignore, fires for all changes...
286
return Disposable.None;
287
}
288
289
private _fireSoon(...changes: IFileChange[]): void {
290
this._bufferedChanges.push(...changes);
291
292
if (this._fireSoonHandle) {
293
clearTimeout(this._fireSoonHandle);
294
}
295
296
this._fireSoonHandle = setTimeout(() => {
297
this._onDidChangeFile.fire(this._bufferedChanges);
298
this._bufferedChanges.length = 0;
299
}, 5);
300
}
301
302
override dispose(): void {
303
super.dispose();
304
305
this.fdMemory.clear();
306
}
307
}
308
309