Merge from vscode 2cd495805cf99b31b6926f08ff4348124b2cf73d

This commit is contained in:
ADS Merger
2020-06-30 04:40:21 +00:00
committed by AzureDataStudio
parent a8a7559229
commit 1388493cc1
602 changed files with 16375 additions and 12940 deletions

View File

@@ -0,0 +1,125 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { KeyValueFileSystemProvider } from 'vs/platform/files/common/keyValueFileSystemProvider';
import * as browser from 'vs/base/browser/browser';
import { IFileSystemProvider } from 'vs/platform/files/common/files';
const INDEXEDDB_VSCODE_DB = 'vscode-web-db';
export const INDEXEDDB_USERDATA_OBJECT_STORE = 'vscode-userdata-store';
export const INDEXEDDB_LOGS_OBJECT_STORE = 'vscode-logs-store';
export class IndexedDB {
private indexedDBPromise: Promise<IDBDatabase | null>;
constructor() {
this.indexedDBPromise = this.openIndexedDB(INDEXEDDB_VSCODE_DB, 2, [INDEXEDDB_USERDATA_OBJECT_STORE, INDEXEDDB_LOGS_OBJECT_STORE]);
}
async createFileSystemProvider(scheme: string, store: string): Promise<IFileSystemProvider | null> {
let fsp: IFileSystemProvider | null = null;
const indexedDB = await this.indexedDBPromise;
if (indexedDB) {
if (indexedDB.objectStoreNames.contains(store)) {
fsp = new IndexedDBFileSystemProvider(scheme, indexedDB, store);
} else {
console.error(`Error while creating indexedDB filesystem provider. Could not find ${store} object store`);
}
}
return fsp;
}
private openIndexedDB(name: string, version: number, stores: string[]): Promise<IDBDatabase | null> {
if (browser.isEdge) {
return Promise.resolve(null);
}
return new Promise((c, e) => {
const request = window.indexedDB.open(name, version);
request.onerror = (err) => e(request.error);
request.onsuccess = () => {
const db = request.result;
for (const store of stores) {
if (!db.objectStoreNames.contains(store)) {
console.error(`Error while creating indexedDB. Could not create ${store} object store`);
c(null);
return;
}
}
c(db);
};
request.onupgradeneeded = () => {
const db = request.result;
for (const store of stores) {
if (!db.objectStoreNames.contains(store)) {
db.createObjectStore(store);
}
}
};
});
}
}
class IndexedDBFileSystemProvider extends KeyValueFileSystemProvider {
constructor(scheme: string, private readonly database: IDBDatabase, private readonly store: string) {
super(scheme);
}
protected async getAllKeys(): Promise<string[]> {
return new Promise(async (c, e) => {
const transaction = this.database.transaction([this.store]);
const objectStore = transaction.objectStore(this.store);
const request = objectStore.getAllKeys();
request.onerror = () => e(request.error);
request.onsuccess = () => c(<string[]>request.result);
});
}
protected hasKey(key: string): Promise<boolean> {
return new Promise<boolean>(async (c, e) => {
const transaction = this.database.transaction([this.store]);
const objectStore = transaction.objectStore(this.store);
const request = objectStore.getKey(key);
request.onerror = () => e(request.error);
request.onsuccess = () => {
c(!!request.result);
};
});
}
protected getValue(key: string): Promise<string> {
return new Promise(async (c, e) => {
const transaction = this.database.transaction([this.store]);
const objectStore = transaction.objectStore(this.store);
const request = objectStore.get(key);
request.onerror = () => e(request.error);
request.onsuccess = () => c(request.result || '');
});
}
protected setValue(key: string, value: string): Promise<void> {
return new Promise(async (c, e) => {
const transaction = this.database.transaction([this.store], 'readwrite');
const objectStore = transaction.objectStore(this.store);
const request = objectStore.put(value, key);
request.onerror = () => e(request.error);
request.onsuccess = () => c();
});
}
protected deleteKey(key: string): Promise<void> {
return new Promise(async (c, e) => {
const transaction = this.database.transaction([this.store], 'readwrite');
const objectStore = transaction.objectStore(this.store);
const request = objectStore.delete(key);
request.onerror = () => e(request.error);
request.onsuccess = () => c();
});
}
}

View File

@@ -287,12 +287,28 @@ export class FileService extends Disposable implements IFileService {
//#region File Reading/Writing
async createFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream = VSBuffer.fromString(''), options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
async canCreateFile(resource: URI, options?: ICreateFileOptions): Promise<Error | true> {
try {
await this.doValidateCreateFile(resource, options);
} catch (error) {
return error;
}
return true;
}
private async doValidateCreateFile(resource: URI, options?: ICreateFileOptions): Promise<void> {
// validate overwrite
if (!options?.overwrite && await this.exists(resource)) {
throw new FileOperationError(localize('fileExists', "Unable to create file '{0}' that already exists when overwrite flag is not set", this.resourceForError(resource)), FileOperationResult.FILE_MODIFIED_SINCE, options);
}
}
async createFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream = VSBuffer.fromString(''), options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
// validate
await this.doValidateCreateFile(resource, options);
// do write into file (this will create it too)
const fileStat = await this.writeFile(resource, bufferOrReadableOrStream);

View File

@@ -140,6 +140,12 @@ export interface IFileService {
*/
canCopy(source: URI, target: URI, overwrite?: boolean): Promise<Error | true>;
/**
* Find out if a file create operation is possible given the arguments. No changes on disk will
* be performed. Returns an Error if the operation cannot be done.
*/
canCreateFile(resource: URI, options?: ICreateFileOptions): Promise<Error | true>;
/**
* Creates a new file with the given path and optional contents. The returned promise
* will have the stat model object as a result.

View File

@@ -0,0 +1,153 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { IFileSystemProviderWithFileReadWriteCapability, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileDeleteOptions, FileWriteOptions, FileChangeType, createFileSystemProviderError, FileSystemProviderErrorCode } from 'vs/platform/files/common/files';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { VSBuffer } from 'vs/base/common/buffer';
import { joinPath, extUri, dirname } from 'vs/base/common/resources';
import { values } from 'vs/base/common/map';
import { localize } from 'vs/nls';
export abstract class KeyValueFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability {
readonly capabilities: FileSystemProviderCapabilities =
FileSystemProviderCapabilities.FileReadWrite
| FileSystemProviderCapabilities.PathCaseSensitive;
readonly onDidChangeCapabilities: Event<void> = Event.None;
private readonly _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());
readonly onDidChangeFile: Event<readonly IFileChange[]> = this._onDidChangeFile.event;
private readonly versions: Map<string, number> = new Map<string, number>();
private readonly dirs: Set<string> = new Set<string>();
constructor(private readonly scheme: string) {
super();
// Add root directory by default
this.dirs.add('/');
}
watch(resource: URI, opts: IWatchOptions): IDisposable {
return Disposable.None;
}
async mkdir(resource: URI): Promise<void> {
try {
const resourceStat = await this.stat(resource);
if (resourceStat.type === FileType.File) {
throw createFileSystemProviderError(localize('fileNotDirectory', "File is not a directory"), FileSystemProviderErrorCode.FileNotADirectory);
}
} catch (error) { /* Ignore */ }
// Make sure parent dir exists
await this.stat(dirname(resource));
this.dirs.add(resource.path);
}
async stat(resource: URI): Promise<IStat> {
try {
const content = await this.readFile(resource);
return {
type: FileType.File,
ctime: 0,
mtime: this.versions.get(resource.toString()) || 0,
size: content.byteLength
};
} catch (e) {
}
const files = await this.readdir(resource);
if (files.length) {
return {
type: FileType.Directory,
ctime: 0,
mtime: 0,
size: 0
};
}
if (this.dirs.has(resource.path)) {
return {
type: FileType.Directory,
ctime: 0,
mtime: 0,
size: 0
};
}
throw createFileSystemProviderError(localize('fileNotExists', "File does not exist"), FileSystemProviderErrorCode.FileNotFound);
}
async readdir(resource: URI): Promise<[string, FileType][]> {
const hasKey = await this.hasKey(resource.path);
if (hasKey) {
throw createFileSystemProviderError(localize('fileNotDirectory', "File is not a directory"), FileSystemProviderErrorCode.FileNotADirectory);
}
const keys = await this.getAllKeys();
const files: Map<string, [string, FileType]> = new Map<string, [string, FileType]>();
for (const key of keys) {
const keyResource = this.toResource(key);
if (extUri.isEqualOrParent(keyResource, resource)) {
const path = extUri.relativePath(resource, keyResource);
if (path) {
const keySegments = path.split('/');
files.set(keySegments[0], [keySegments[0], keySegments.length === 1 ? FileType.File : FileType.Directory]);
}
}
}
return values(files);
}
async readFile(resource: URI): Promise<Uint8Array> {
const hasKey = await this.hasKey(resource.path);
if (!hasKey) {
throw createFileSystemProviderError(localize('fileNotFound', "File not found"), FileSystemProviderErrorCode.FileNotFound);
}
const value = await this.getValue(resource.path);
return VSBuffer.fromString(value).buffer;
}
async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
const hasKey = await this.hasKey(resource.path);
if (!hasKey) {
const files = await this.readdir(resource);
if (files.length) {
throw createFileSystemProviderError(localize('fileIsDirectory', "File is Directory"), FileSystemProviderErrorCode.FileIsADirectory);
}
}
await this.setValue(resource.path, VSBuffer.wrap(content).toString());
this.versions.set(resource.toString(), (this.versions.get(resource.toString()) || 0) + 1);
this._onDidChangeFile.fire([{ resource, type: FileChangeType.UPDATED }]);
}
async delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
const hasKey = await this.hasKey(resource.path);
if (hasKey) {
await this.deleteKey(resource.path);
this.versions.delete(resource.path);
this._onDidChangeFile.fire([{ resource, type: FileChangeType.DELETED }]);
return;
}
if (opts.recursive) {
const files = await this.readdir(resource);
await Promise.all(files.map(([key]) => this.delete(joinPath(resource, key), opts)));
}
}
rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> {
return Promise.reject(new Error('Not Supported'));
}
private toResource(key: string): URI {
return URI.file(key).with({ scheme: this.scheme });
}
protected abstract getAllKeys(): Promise<string[]>;
protected abstract hasKey(key: string): Promise<boolean>;
protected abstract getValue(key: string): Promise<string>;
protected abstract setValue(key: string, value: string): Promise<void>;
protected abstract deleteKey(key: string): Promise<void>;
}

View File

@@ -1611,6 +1611,8 @@ suite.skip('Disk File Service', function () { // {{SQL CARBON EDIT}} Disable occ
const contents = 'Hello World';
const resource = URI.file(join(testDir, 'test.txt'));
assert.equal(await service.canCreateFile(resource), true);
const fileStat = await service.createFile(resource, converter(contents));
assert.equal(fileStat.name, 'test.txt');
assert.equal(existsSync(fileStat.resource.fsPath), true);
@@ -1628,6 +1630,8 @@ suite.skip('Disk File Service', function () { // {{SQL CARBON EDIT}} Disable occ
writeFileSync(resource.fsPath, ''); // create file
assert.ok((await service.canCreateFile(resource)) instanceof Error);
let error;
try {
await service.createFile(resource, VSBuffer.fromString(contents));
@@ -1647,6 +1651,7 @@ suite.skip('Disk File Service', function () { // {{SQL CARBON EDIT}} Disable occ
writeFileSync(resource.fsPath, ''); // create file
assert.equal(await service.canCreateFile(resource, { overwrite: true }), true);
const fileStat = await service.createFile(resource, VSBuffer.fromString(contents), { overwrite: true });
assert.equal(fileStat.name, 'test.txt');
assert.equal(existsSync(fileStat.resource.fsPath), true);