Revert "Revert "use dom api to resolve file schema on web""

This reverts commit 1292b973d0.
This commit is contained in:
João Moreno
2021-04-20 07:18:57 +02:00
parent 1292b973d0
commit eaabe28485
4 changed files with 252 additions and 8 deletions
@@ -0,0 +1,192 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. 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 } from 'vs/platform/files/common/files';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { extUri } from 'vs/base/common/resources';
function split(path: string): [string, string] | undefined {
const match = /^(.*)\/([^/]+)$/.exec(path);
if (!match) {
return undefined;
}
const [, parentPath, name] = match;
return [parentPath, name];
}
function isRoot(uri: URI): boolean {
return /^(\/[^/]+)\/?$/.test(uri.path);
}
export class HTMLFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability {
private readonly files = new Map<string, FileSystemFileHandle>();
private readonly directories = new Map<string, FileSystemDirectoryHandle>();
readonly capabilities: FileSystemProviderCapabilities =
FileSystemProviderCapabilities.FileReadWrite
| FileSystemProviderCapabilities.PathCaseSensitive;
readonly onDidChangeCapabilities = Event.None;
private readonly _onDidChangeFile = new Emitter<readonly IFileChange[]>();
readonly onDidChangeFile = this._onDidChangeFile.event;
private readonly _onDidErrorOccur = new Emitter<string>();
readonly onDidErrorOccur = this._onDidErrorOccur.event;
async readFile(resource: URI): Promise<Uint8Array> {
const handle = await this.getFileHandle(resource);
if (!handle) {
throw new Error('File not found.');
}
const file = await handle.getFile();
return new Uint8Array(await file.arrayBuffer());
}
async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
const handle = await this.getFileHandle(resource);
if (!handle) {
throw new Error('File not found.');
}
const writable = await handle.createWritable();
await writable.write(content);
await writable.close();
}
watch(resource: URI, opts: IWatchOptions): IDisposable {
return Disposable.None;
}
async stat(resource: URI): Promise<IStat> {
const handler = this.files.get(resource.authority);
if (handler) {
const file = await handler.getFile();
return {
type: FileType.File,
mtime: file.lastModified,
ctime: 0,
size: file.size
};
}
if (isRoot(resource)) {
return {
type: FileType.Directory,
mtime: 0,
ctime: 0,
size: 0
};
}
const parent = await this.getParentDirectoryHandle(resource);
if (!parent) {
throw new Error('Stat error: no parent found');
}
const name = extUri.basename(resource);
for await (const [childName, child] of parent) {
if (childName === name) {
if (child.kind === 'file') {
const file = await child.getFile();
return {
type: FileType.File,
mtime: file.lastModified,
ctime: 0,
size: file.size
};
} else {
return {
type: FileType.Directory,
mtime: 0,
ctime: 0,
size: 0
};
}
}
}
throw new Error('Stat error: entry not found');
}
mkdir(resource: URI): Promise<void> {
throw new Error('Method not implemented.');
}
async readdir(resource: URI): Promise<[string, FileType][]> {
const parent = await this.getDirectoryHandle(resource);
if (!parent) {
throw new Error('Stat error: no parent found');
}
const result: [string, FileType][] = [];
for await (const [name, child] of parent) {
result.push([name, child.kind === 'file' ? FileType.File : FileType.Directory]);
}
return result;
}
delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
throw new Error('Method not implemented: delete');
}
rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> {
throw new Error('Method not implemented: rename');
}
private async getDirectoryHandle(uri: URI): Promise<FileSystemDirectoryHandle | undefined> {
if (isRoot(uri)) {
return this.directories.get(uri.authority);
}
const splitResult = split(uri.path);
if (!splitResult) {
return undefined;
}
const parent = await this.getDirectoryHandle(URI.from({ ...uri, path: splitResult[0] }));
return await parent?.getDirectoryHandle(extUri.basename(uri));
}
private async getParentDirectoryHandle(uri: URI): Promise<FileSystemDirectoryHandle | undefined> {
return this.getDirectoryHandle(URI.from({ ...uri, path: extUri.dirname(uri).path }));
}
private async getFileHandle(uri: URI): Promise<FileSystemFileHandle | undefined> {
const result = this.files.get(uri.authority);
if (result) {
return result;
}
const parent = await this.getParentDirectoryHandle(uri);
const name = extUri.basename(uri);
return await parent?.getFileHandle(name);
}
registerFileHandle(uuid: string, handle: FileSystemFileHandle): void {
this.files.set(uuid, handle);
}
dispose(): void {
this._onDidChangeFile.dispose();
}
}
+3
View File
@@ -62,6 +62,7 @@ import { BrowserWindow } from 'vs/workbench/browser/window';
import { ITimerService } from 'vs/workbench/services/timer/browser/timerService';
import { WorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/common/workspaceTrust';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { HTMLFileSystemProvider } from 'vs/platform/files/browser/htmlFileSystemProvider';
class BrowserMain extends Disposable {
@@ -307,6 +308,8 @@ class BrowserMain extends Disposable {
}
});
}
fileService.registerProvider(Schemas.file, new HTMLFileSystemProvider());
}
private async createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: IFileService, logService: ILogService): Promise<BrowserStorageService> {
@@ -8,9 +8,17 @@ import { URI } from 'vs/base/common/uri';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { AbstractFileDialogService } from 'vs/workbench/services/dialogs/browser/abstractFileDialogService';
import { Schemas } from 'vs/base/common/network';
import { memoize } from 'vs/base/common/decorators';
import { HTMLFileSystemProvider } from 'vs/platform/files/browser/htmlFileSystemProvider';
import { generateUuid } from 'vs/base/common/uuid';
export class FileDialogService extends AbstractFileDialogService implements IFileDialogService {
@memoize
private get fileSystemProvider(): HTMLFileSystemProvider {
return this.fileService.getProvider(Schemas.file) as HTMLFileSystemProvider;
}
async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
const schema = this.getFileSystemSchema(options);
@@ -18,7 +26,11 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
options.defaultUri = await this.defaultFilePath(schema);
}
return this.pickFileFolderAndOpenSimplified(schema, options, false);
if (this.shouldUseSimplified(schema)) {
return this.pickFileFolderAndOpenSimplified(schema, options, false);
}
throw new Error('Method not implemented.');
}
async pickFileAndOpen(options: IPickAndOpenOptions): Promise<any> {
@@ -28,7 +40,17 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
options.defaultUri = await this.defaultFilePath(schema);
}
return this.pickFileAndOpenSimplified(schema, options, false);
if (this.shouldUseSimplified(schema)) {
return this.pickFileAndOpenSimplified(schema, options, false);
}
const [handle] = await window.showOpenFilePicker({ multiple: false });
const uuid = generateUuid();
const uri = URI.from({ scheme: Schemas.file, authority: uuid, path: `/${handle.name}` });
this.fileSystemProvider.registerFileHandle(uuid, handle);
await this.openerService.open(uri, { fromUserGesture: true, editorOptions: { pinned: true } });
}
async pickFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
@@ -38,7 +60,11 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
options.defaultUri = await this.defaultFolderPath(schema);
}
return this.pickFolderAndOpenSimplified(schema, options);
if (this.shouldUseSimplified(schema)) {
return this.pickFolderAndOpenSimplified(schema, options);
}
throw new Error('Method not implemented.');
}
async pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise<void> {
@@ -48,27 +74,50 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
options.defaultUri = await this.defaultWorkspacePath(schema);
}
return this.pickWorkspaceAndOpenSimplified(schema, options);
if (this.shouldUseSimplified(schema)) {
return this.pickWorkspaceAndOpenSimplified(schema, options);
}
throw new Error('Method not implemented.');
}
async pickFileToSave(defaultUri: URI, availableFileSystems?: string[]): Promise<URI | undefined> {
const schema = this.getFileSystemSchema({ defaultUri, availableFileSystems });
return this.pickFileToSaveSimplified(schema, this.getPickFileToSaveDialogOptions(defaultUri, availableFileSystems));
if (this.shouldUseSimplified(schema)) {
return this.pickFileToSaveSimplified(schema, this.getPickFileToSaveDialogOptions(defaultUri, availableFileSystems));
}
throw new Error('Method not implemented.');
}
async showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
const schema = this.getFileSystemSchema(options);
return this.showSaveDialogSimplified(schema, options);
if (this.shouldUseSimplified(schema)) {
return this.showSaveDialogSimplified(schema, options);
}
throw new Error('Method not implemented.');
}
async showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined> {
const schema = this.getFileSystemSchema(options);
return this.showOpenDialogSimplified(schema, options);
if (this.shouldUseSimplified(schema)) {
return this.showOpenDialogSimplified(schema, options);
}
throw new Error('Method not implemented.');
}
protected addFileSchemaIfNeeded(schema: string): string[] {
return schema === Schemas.untitled ? [Schemas.file] : [schema];
}
private shouldUseSimplified(schema: string): boolean {
return schema !== Schemas.file;
}
}
registerSingleton(IFileDialogService, FileDialogService, true);
@@ -39,7 +39,7 @@ function defaultUriScheme(environmentService: IWorkbenchEnvironmentService, cont
return configuration.scheme;
}
throw new Error('Empty workspace is not supported in browser when there is no remote connection.');
return Schemas.file;
}
registerSingleton(IPathService, BrowserPathService, true);