Path: blob/main/src/vs/platform/files/common/inMemoryFilesystemProvider.ts
3294 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { VSBuffer } from '../../../base/common/buffer.js';6import { Emitter, Event } from '../../../base/common/event.js';7import { Disposable, IDisposable } from '../../../base/common/lifecycle.js';8import * as resources from '../../../base/common/resources.js';9import { ReadableStreamEvents, newWriteableStream } from '../../../base/common/stream.js';10import { URI } from '../../../base/common/uri.js';11import { FileChangeType, IFileDeleteOptions, IFileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions, createFileSystemProviderError, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileOpenOptions, IFileSystemProviderWithFileAtomicDeleteCapability, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileAtomicWriteCapability, IFileSystemProviderWithFileReadStreamCapability } from './files.js';1213class File implements IStat {1415readonly type: FileType.File;16readonly ctime: number;17mtime: number;18size: number;1920name: string;21data?: Uint8Array;2223constructor(name: string) {24this.type = FileType.File;25this.ctime = Date.now();26this.mtime = Date.now();27this.size = 0;28this.name = name;29}30}3132class Directory implements IStat {3334readonly type: FileType.Directory;35readonly ctime: number;36mtime: number;37size: number;3839name: string;40readonly entries: Map<string, File | Directory>;4142constructor(name: string) {43this.type = FileType.Directory;44this.ctime = Date.now();45this.mtime = Date.now();46this.size = 0;47this.name = name;48this.entries = new Map();49}50}5152type Entry = File | Directory;5354export class InMemoryFileSystemProvider extends Disposable implements55IFileSystemProviderWithFileReadWriteCapability,56IFileSystemProviderWithOpenReadWriteCloseCapability,57IFileSystemProviderWithFileReadStreamCapability,58IFileSystemProviderWithFileAtomicReadCapability,59IFileSystemProviderWithFileAtomicWriteCapability,60IFileSystemProviderWithFileAtomicDeleteCapability {6162private memoryFdCounter = 0;63private readonly fdMemory = new Map<number, Uint8Array>();64private _onDidChangeCapabilities = this._register(new Emitter<void>());65readonly onDidChangeCapabilities = this._onDidChangeCapabilities.event;6667private _capabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive;68get capabilities(): FileSystemProviderCapabilities { return this._capabilities; }6970setReadOnly(readonly: boolean) {71const isReadonly = !!(this._capabilities & FileSystemProviderCapabilities.Readonly);72if (readonly !== isReadonly) {73this._capabilities = readonly ? FileSystemProviderCapabilities.Readonly | FileSystemProviderCapabilities.PathCaseSensitive | FileSystemProviderCapabilities.FileReadWrite74: FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive;75this._onDidChangeCapabilities.fire();76}77}7879root = new Directory('');8081// --- manage file metadata8283async stat(resource: URI): Promise<IStat> {84return this._lookup(resource, false);85}8687async readdir(resource: URI): Promise<[string, FileType][]> {88const entry = this._lookupAsDirectory(resource, false);89const result: [string, FileType][] = [];90entry.entries.forEach((child, name) => result.push([name, child.type]));91return result;92}9394// --- manage file contents9596async readFile(resource: URI): Promise<Uint8Array> {97const data = this._lookupAsFile(resource, false).data;98if (data) {99return data;100}101throw createFileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound);102}103104readFileStream(resource: URI): ReadableStreamEvents<Uint8Array> {105const data = this._lookupAsFile(resource, false).data;106107const stream = newWriteableStream<Uint8Array>(data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer);108stream.end(data);109110return stream;111}112113async writeFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void> {114const basename = resources.basename(resource);115const parent = this._lookupParentDirectory(resource);116let entry = parent.entries.get(basename);117if (entry instanceof Directory) {118throw createFileSystemProviderError('file is directory', FileSystemProviderErrorCode.FileIsADirectory);119}120if (!entry && !opts.create) {121throw createFileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound);122}123if (entry && opts.create && !opts.overwrite) {124throw createFileSystemProviderError('file exists already', FileSystemProviderErrorCode.FileExists);125}126if (!entry) {127entry = new File(basename);128parent.entries.set(basename, entry);129this._fireSoon({ type: FileChangeType.ADDED, resource });130}131entry.mtime = Date.now();132entry.size = content.byteLength;133entry.data = content;134135this._fireSoon({ type: FileChangeType.UPDATED, resource });136}137138// file open/read/write/close139open(resource: URI, opts: IFileOpenOptions): Promise<number> {140const data = this._lookupAsFile(resource, false).data;141if (data) {142const fd = this.memoryFdCounter++;143this.fdMemory.set(fd, data);144return Promise.resolve(fd);145}146throw createFileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound);147}148149close(fd: number): Promise<void> {150this.fdMemory.delete(fd);151return Promise.resolve();152}153154read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {155const memory = this.fdMemory.get(fd);156if (!memory) {157throw createFileSystemProviderError(`No file with that descriptor open`, FileSystemProviderErrorCode.Unavailable);158}159160const toWrite = VSBuffer.wrap(memory).slice(pos, pos + length);161data.set(toWrite.buffer, offset);162return Promise.resolve(toWrite.byteLength);163}164165write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {166const memory = this.fdMemory.get(fd);167if (!memory) {168throw createFileSystemProviderError(`No file with that descriptor open`, FileSystemProviderErrorCode.Unavailable);169}170171const toWrite = VSBuffer.wrap(data).slice(offset, offset + length);172memory.set(toWrite.buffer, pos);173return Promise.resolve(toWrite.byteLength);174}175176// --- manage files/folders177178async rename(from: URI, to: URI, opts: IFileOverwriteOptions): Promise<void> {179if (!opts.overwrite && this._lookup(to, true)) {180throw createFileSystemProviderError('file exists already', FileSystemProviderErrorCode.FileExists);181}182183const entry = this._lookup(from, false);184const oldParent = this._lookupParentDirectory(from);185186const newParent = this._lookupParentDirectory(to);187const newName = resources.basename(to);188189oldParent.entries.delete(entry.name);190entry.name = newName;191newParent.entries.set(newName, entry);192193this._fireSoon(194{ type: FileChangeType.DELETED, resource: from },195{ type: FileChangeType.ADDED, resource: to }196);197}198199async delete(resource: URI, opts: IFileDeleteOptions): Promise<void> {200const dirname = resources.dirname(resource);201const basename = resources.basename(resource);202const parent = this._lookupAsDirectory(dirname, false);203if (parent.entries.has(basename)) {204parent.entries.delete(basename);205parent.mtime = Date.now();206parent.size -= 1;207this._fireSoon({ type: FileChangeType.UPDATED, resource: dirname }, { resource, type: FileChangeType.DELETED });208}209}210211async mkdir(resource: URI): Promise<void> {212if (this._lookup(resource, true)) {213throw createFileSystemProviderError('file exists already', FileSystemProviderErrorCode.FileExists);214}215216const basename = resources.basename(resource);217const dirname = resources.dirname(resource);218const parent = this._lookupAsDirectory(dirname, false);219220const entry = new Directory(basename);221parent.entries.set(entry.name, entry);222parent.mtime = Date.now();223parent.size += 1;224this._fireSoon({ type: FileChangeType.UPDATED, resource: dirname }, { type: FileChangeType.ADDED, resource });225}226227// --- lookup228229private _lookup(uri: URI, silent: false): Entry;230private _lookup(uri: URI, silent: boolean): Entry | undefined;231private _lookup(uri: URI, silent: boolean): Entry | undefined {232const parts = uri.path.split('/');233let entry: Entry = this.root;234for (const part of parts) {235if (!part) {236continue;237}238let child: Entry | undefined;239if (entry instanceof Directory) {240child = entry.entries.get(part);241}242if (!child) {243if (!silent) {244throw createFileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound);245} else {246return undefined;247}248}249entry = child;250}251return entry;252}253254private _lookupAsDirectory(uri: URI, silent: boolean): Directory {255const entry = this._lookup(uri, silent);256if (entry instanceof Directory) {257return entry;258}259throw createFileSystemProviderError('file not a directory', FileSystemProviderErrorCode.FileNotADirectory);260}261262private _lookupAsFile(uri: URI, silent: boolean): File {263const entry = this._lookup(uri, silent);264if (entry instanceof File) {265return entry;266}267throw createFileSystemProviderError('file is a directory', FileSystemProviderErrorCode.FileIsADirectory);268}269270private _lookupParentDirectory(uri: URI): Directory {271const dirname = resources.dirname(uri);272return this._lookupAsDirectory(dirname, false);273}274275// --- manage file events276277private readonly _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());278readonly onDidChangeFile: Event<readonly IFileChange[]> = this._onDidChangeFile.event;279280private _bufferedChanges: IFileChange[] = [];281private _fireSoonHandle?: Timeout;282283watch(resource: URI, opts: IWatchOptions): IDisposable {284// ignore, fires for all changes...285return Disposable.None;286}287288private _fireSoon(...changes: IFileChange[]): void {289this._bufferedChanges.push(...changes);290291if (this._fireSoonHandle) {292clearTimeout(this._fireSoonHandle);293}294295this._fireSoonHandle = setTimeout(() => {296this._onDidChangeFile.fire(this._bufferedChanges);297this._bufferedChanges.length = 0;298}, 5);299}300301override dispose(): void {302super.dispose();303304this.fdMemory.clear();305}306}307308309