Files
azuredatastudio/src/vs/platform/files/common/inMemoryFilesystemProvider.ts
Charles Gagnon 2bc6a0cd01 VS Code merge to df8fe74bd55313de0dd2303bc47a4aab0ca56b0e (#17979)
* Merge from vscode 504f934659740e9d41501cad9f162b54d7745ad9

* delete unused folders

* distro

* Bump build node version

* update chokidar

* FIx hygiene errors

* distro

* Fix extension lint issues

* Remove strict-vscode

* Add copyright header exemptions

* Bump vscode-extension-telemetry to fix webpacking issue with zone.js

* distro

* Fix failing tests (revert marked.js back to current one until we decide to update)

* Skip searchmodel test

* Fix mac build

* temp debug script loading

* Try disabling coverage

* log error too

* Revert "log error too"

This reverts commit af0183e5d4ab458fdf44b88fbfab9908d090526f.

* Revert "temp debug script loading"

This reverts commit 3d687d541c76db2c5b55626c78ae448d3c25089c.

* Add comments explaining coverage disabling

* Fix ansi_up loading issue

* Merge latest from ads

* Use newer option

* Fix compile

* add debug logging warn

* Always log stack

* log more

* undo debug

* Update to use correct base path (+cleanup)

* distro

* fix compile errors

* Remove strict-vscode

* Fix sql editors not showing

* Show db dropdown input & fix styling

* Fix more info in gallery

* Fix gallery asset requests

* Delete unused workflow

* Fix tapable resolutions for smoke test compile error

* Fix smoke compile

* Disable crash reporting

* Disable interactive

Co-authored-by: ADS Merger <karlb@microsoft.com>
2022-01-06 09:06:56 -08:00

231 lines
7.1 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import * as resources from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, FileWriteOptions, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions } from 'vs/platform/files/common/files';
class File implements IStat {
type: FileType.File;
ctime: number;
mtime: number;
size: number;
name: string;
data?: Uint8Array;
constructor(name: string) {
this.type = FileType.File;
this.ctime = Date.now();
this.mtime = Date.now();
this.size = 0;
this.name = name;
}
}
class Directory implements IStat {
type: FileType.Directory;
ctime: number;
mtime: number;
size: number;
name: string;
entries: Map<string, File | Directory>;
constructor(name: string) {
this.type = FileType.Directory;
this.ctime = Date.now();
this.mtime = Date.now();
this.size = 0;
this.name = name;
this.entries = new Map();
}
}
export type Entry = File | Directory;
export class InMemoryFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability {
readonly capabilities: FileSystemProviderCapabilities =
FileSystemProviderCapabilities.FileReadWrite
| FileSystemProviderCapabilities.PathCaseSensitive;
readonly onDidChangeCapabilities: Event<void> = Event.None;
root = new Directory('');
// --- manage file metadata
async stat(resource: URI): Promise<IStat> {
return this._lookup(resource, false);
}
async readdir(resource: URI): Promise<[string, FileType][]> {
const entry = this._lookupAsDirectory(resource, false);
let result: [string, FileType][] = [];
entry.entries.forEach((child, name) => result.push([name, child.type]));
return result;
}
// --- manage file contents
async readFile(resource: URI): Promise<Uint8Array> {
const data = this._lookupAsFile(resource, false).data;
if (data) {
return data;
}
throw new FileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound);
}
async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
let basename = resources.basename(resource);
let parent = this._lookupParentDirectory(resource);
let entry = parent.entries.get(basename);
if (entry instanceof Directory) {
throw new FileSystemProviderError('file is directory', FileSystemProviderErrorCode.FileIsADirectory);
}
if (!entry && !opts.create) {
throw new FileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound);
}
if (entry && opts.create && !opts.overwrite) {
throw new FileSystemProviderError('file exists already', FileSystemProviderErrorCode.FileExists);
}
if (!entry) {
entry = new File(basename);
parent.entries.set(basename, entry);
this._fireSoon({ type: FileChangeType.ADDED, resource });
}
entry.mtime = Date.now();
entry.size = content.byteLength;
entry.data = content;
this._fireSoon({ type: FileChangeType.UPDATED, resource });
}
// --- manage files/folders
async rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> {
if (!opts.overwrite && this._lookup(to, true)) {
throw new FileSystemProviderError('file exists already', FileSystemProviderErrorCode.FileExists);
}
let entry = this._lookup(from, false);
let oldParent = this._lookupParentDirectory(from);
let newParent = this._lookupParentDirectory(to);
let newName = resources.basename(to);
oldParent.entries.delete(entry.name);
entry.name = newName;
newParent.entries.set(newName, entry);
this._fireSoon(
{ type: FileChangeType.DELETED, resource: from },
{ type: FileChangeType.ADDED, resource: to }
);
}
async delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
let dirname = resources.dirname(resource);
let basename = resources.basename(resource);
let parent = this._lookupAsDirectory(dirname, false);
if (parent.entries.has(basename)) {
parent.entries.delete(basename);
parent.mtime = Date.now();
parent.size -= 1;
this._fireSoon({ type: FileChangeType.UPDATED, resource: dirname }, { resource, type: FileChangeType.DELETED });
}
}
async mkdir(resource: URI): Promise<void> {
let basename = resources.basename(resource);
let dirname = resources.dirname(resource);
let parent = this._lookupAsDirectory(dirname, false);
let entry = new Directory(basename);
parent.entries.set(entry.name, entry);
parent.mtime = Date.now();
parent.size += 1;
this._fireSoon({ type: FileChangeType.UPDATED, resource: dirname }, { type: FileChangeType.ADDED, resource });
}
// --- lookup
private _lookup(uri: URI, silent: false): Entry;
private _lookup(uri: URI, silent: boolean): Entry | undefined;
private _lookup(uri: URI, silent: boolean): Entry | undefined {
let parts = uri.path.split('/');
let entry: Entry = this.root;
for (const part of parts) {
if (!part) {
continue;
}
let child: Entry | undefined;
if (entry instanceof Directory) {
child = entry.entries.get(part);
}
if (!child) {
if (!silent) {
throw new FileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound);
} else {
return undefined;
}
}
entry = child;
}
return entry;
}
private _lookupAsDirectory(uri: URI, silent: boolean): Directory {
let entry = this._lookup(uri, silent);
if (entry instanceof Directory) {
return entry;
}
throw new FileSystemProviderError('file not a directory', FileSystemProviderErrorCode.FileNotADirectory);
}
private _lookupAsFile(uri: URI, silent: boolean): File {
let entry = this._lookup(uri, silent);
if (entry instanceof File) {
return entry;
}
throw new FileSystemProviderError('file is a directory', FileSystemProviderErrorCode.FileIsADirectory);
}
private _lookupParentDirectory(uri: URI): Directory {
const dirname = resources.dirname(uri);
return this._lookupAsDirectory(dirname, false);
}
// --- manage file events
private readonly _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());
readonly onDidChangeFile: Event<readonly IFileChange[]> = this._onDidChangeFile.event;
private _bufferedChanges: IFileChange[] = [];
private _fireSoonHandle?: any;
watch(resource: URI, opts: IWatchOptions): IDisposable {
// ignore, fires for all changes...
return Disposable.None;
}
private _fireSoon(...changes: IFileChange[]): void {
this._bufferedChanges.push(...changes);
if (this._fireSoonHandle) {
clearTimeout(this._fireSoonHandle);
}
this._fireSoonHandle = setTimeout(() => {
this._onDidChangeFile.fire(this._bufferedChanges);
this._bufferedChanges.length = 0;
}, 5);
}
}