Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove abstract key-val fsp and make indexedDB fsp operate on Uint8Arrs #107590

Merged
merged 2 commits into from Sep 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
155 changes: 145 additions & 10 deletions src/vs/platform/files/browser/indexedDBFileSystemProvider.ts
Expand Up @@ -3,9 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { KeyValueFileSystemProvider } from 'vs/platform/files/common/keyValueFileSystemProvider';
import { URI } from 'vs/base/common/uri';
import { IFileSystemProvider, 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 { localize } from 'vs/nls';
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';
Expand Down Expand Up @@ -63,13 +68,143 @@ export class IndexedDB {

}

class IndexedDBFileSystemProvider extends KeyValueFileSystemProvider {
class IndexedDBFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability {

constructor(scheme: string, private readonly database: IDBDatabase, private readonly store: string) {
super(scheme);
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, private readonly database: IDBDatabase, private readonly store: string) {
super();
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 [...files.values()];
}

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);
if (typeof value === 'string') {
return VSBuffer.fromString(value).buffer;
} else {
return value;
}
}

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, content);
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 async getAllKeys(): Promise<string[]> {
async getAllKeys(): Promise<string[]> {
return new Promise(async (c, e) => {
const transaction = this.database.transaction([this.store]);
const objectStore = transaction.objectStore(this.store);
Expand All @@ -79,7 +214,7 @@ class IndexedDBFileSystemProvider extends KeyValueFileSystemProvider {
});
}

protected hasKey(key: string): Promise<boolean> {
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);
Expand All @@ -91,7 +226,7 @@ class IndexedDBFileSystemProvider extends KeyValueFileSystemProvider {
});
}

protected getValue(key: string): Promise<string> {
getValue(key: string): Promise<Uint8Array | string> {
return new Promise(async (c, e) => {
const transaction = this.database.transaction([this.store]);
const objectStore = transaction.objectStore(this.store);
Expand All @@ -101,7 +236,7 @@ class IndexedDBFileSystemProvider extends KeyValueFileSystemProvider {
});
}

protected setValue(key: string, value: string): Promise<void> {
setValue(key: string, value: Uint8Array): Promise<void> {
return new Promise(async (c, e) => {
const transaction = this.database.transaction([this.store], 'readwrite');
const objectStore = transaction.objectStore(this.store);
Expand All @@ -111,7 +246,7 @@ class IndexedDBFileSystemProvider extends KeyValueFileSystemProvider {
});
}

protected deleteKey(key: string): Promise<void> {
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);
Expand Down
152 changes: 0 additions & 152 deletions src/vs/platform/files/common/keyValueFileSystemProvider.ts

This file was deleted.