Path: blob/main/src/vs/workbench/api/common/extHostFileSystemConsumer.ts
3296 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 { MainContext, MainThreadFileSystemShape } from './extHost.protocol.js';6import type * as vscode from 'vscode';7import * as files from '../../../platform/files/common/files.js';8import { FileSystemError } from './extHostTypes.js';9import { VSBuffer } from '../../../base/common/buffer.js';10import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';11import { IExtHostRpcService } from './extHostRpcService.js';12import { IExtHostFileSystemInfo } from './extHostFileSystemInfo.js';13import { IDisposable, toDisposable } from '../../../base/common/lifecycle.js';14import { ResourceQueue } from '../../../base/common/async.js';15import { IExtUri, extUri, extUriIgnorePathCase } from '../../../base/common/resources.js';16import { Schemas } from '../../../base/common/network.js';17import { IMarkdownString } from '../../../base/common/htmlContent.js';1819export class ExtHostConsumerFileSystem {2021readonly _serviceBrand: undefined;2223readonly value: vscode.FileSystem;2425private readonly _proxy: MainThreadFileSystemShape;26private readonly _fileSystemProvider = new Map<string, { impl: vscode.FileSystemProvider; extUri: IExtUri; isReadonly: boolean }>();2728private readonly _writeQueue = new ResourceQueue();2930constructor(31@IExtHostRpcService extHostRpc: IExtHostRpcService,32@IExtHostFileSystemInfo fileSystemInfo: IExtHostFileSystemInfo,33) {34this._proxy = extHostRpc.getProxy(MainContext.MainThreadFileSystem);35const that = this;3637this.value = Object.freeze({38async stat(uri: vscode.Uri): Promise<vscode.FileStat> {39try {40let stat;4142const provider = that._fileSystemProvider.get(uri.scheme);43if (provider) {44// use shortcut45await that._proxy.$ensureActivation(uri.scheme);46stat = await provider.impl.stat(uri);47} else {48stat = await that._proxy.$stat(uri);49}5051return {52type: stat.type,53ctime: stat.ctime,54mtime: stat.mtime,55size: stat.size,56permissions: stat.permissions === files.FilePermission.Readonly ? 1 : undefined57};58} catch (err) {59ExtHostConsumerFileSystem._handleError(err);60}61},62async readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> {63try {64const provider = that._fileSystemProvider.get(uri.scheme);65if (provider) {66// use shortcut67await that._proxy.$ensureActivation(uri.scheme);68return (await provider.impl.readDirectory(uri)).slice(); // safe-copy69} else {70return await that._proxy.$readdir(uri);71}72} catch (err) {73return ExtHostConsumerFileSystem._handleError(err);74}75},76async createDirectory(uri: vscode.Uri): Promise<void> {77try {78const provider = that._fileSystemProvider.get(uri.scheme);79if (provider && !provider.isReadonly) {80// use shortcut81await that._proxy.$ensureActivation(uri.scheme);82return await that.mkdirp(provider.impl, provider.extUri, uri);83} else {84return await that._proxy.$mkdir(uri);85}86} catch (err) {87return ExtHostConsumerFileSystem._handleError(err);88}89},90async readFile(uri: vscode.Uri): Promise<Uint8Array> {91try {92const provider = that._fileSystemProvider.get(uri.scheme);93if (provider) {94// use shortcut95await that._proxy.$ensureActivation(uri.scheme);96return (await provider.impl.readFile(uri)).slice(); // safe-copy97} else {98const buff = await that._proxy.$readFile(uri);99return buff.buffer;100}101} catch (err) {102return ExtHostConsumerFileSystem._handleError(err);103}104},105async writeFile(uri: vscode.Uri, content: Uint8Array): Promise<void> {106try {107const provider = that._fileSystemProvider.get(uri.scheme);108if (provider && !provider.isReadonly) {109// use shortcut110await that._proxy.$ensureActivation(uri.scheme);111await that.mkdirp(provider.impl, provider.extUri, provider.extUri.dirname(uri));112return await that._writeQueue.queueFor(uri, () => Promise.resolve(provider.impl.writeFile(uri, content, { create: true, overwrite: true })));113} else {114return await that._proxy.$writeFile(uri, VSBuffer.wrap(content));115}116} catch (err) {117return ExtHostConsumerFileSystem._handleError(err);118}119},120async delete(uri: vscode.Uri, options?: { recursive?: boolean; useTrash?: boolean }): Promise<void> {121try {122const provider = that._fileSystemProvider.get(uri.scheme);123if (provider && !provider.isReadonly && !options?.useTrash /* no shortcut: use trash */) {124// use shortcut125await that._proxy.$ensureActivation(uri.scheme);126return await provider.impl.delete(uri, { recursive: false, ...options });127} else {128return await that._proxy.$delete(uri, { recursive: false, useTrash: false, atomic: false, ...options });129}130} catch (err) {131return ExtHostConsumerFileSystem._handleError(err);132}133},134async rename(oldUri: vscode.Uri, newUri: vscode.Uri, options?: { overwrite?: boolean }): Promise<void> {135try {136// no shortcut: potentially involves different schemes, does mkdirp137return await that._proxy.$rename(oldUri, newUri, { ...{ overwrite: false }, ...options });138} catch (err) {139return ExtHostConsumerFileSystem._handleError(err);140}141},142async copy(source: vscode.Uri, destination: vscode.Uri, options?: { overwrite?: boolean }): Promise<void> {143try {144// no shortcut: potentially involves different schemes, does mkdirp145return await that._proxy.$copy(source, destination, { ...{ overwrite: false }, ...options });146} catch (err) {147return ExtHostConsumerFileSystem._handleError(err);148}149},150isWritableFileSystem(scheme: string): boolean | undefined {151const capabilities = fileSystemInfo.getCapabilities(scheme);152if (typeof capabilities === 'number') {153return !(capabilities & files.FileSystemProviderCapabilities.Readonly);154}155return undefined;156}157});158}159160private async mkdirp(provider: vscode.FileSystemProvider, providerExtUri: IExtUri, directory: vscode.Uri): Promise<void> {161const directoriesToCreate: string[] = [];162163while (!providerExtUri.isEqual(directory, providerExtUri.dirname(directory))) {164try {165const stat = await provider.stat(directory);166if ((stat.type & files.FileType.Directory) === 0) {167throw FileSystemError.FileExists(`Unable to create folder '${directory.scheme === Schemas.file ? directory.fsPath : directory.toString(true)}' that already exists but is not a directory`);168}169170break; // we have hit a directory that exists -> good171} catch (error) {172if (files.toFileSystemProviderErrorCode(error) !== files.FileSystemProviderErrorCode.FileNotFound) {173throw error;174}175176// further go up and remember to create this directory177directoriesToCreate.push(providerExtUri.basename(directory));178directory = providerExtUri.dirname(directory);179}180}181182for (let i = directoriesToCreate.length - 1; i >= 0; i--) {183directory = providerExtUri.joinPath(directory, directoriesToCreate[i]);184185try {186await provider.createDirectory(directory);187} catch (error) {188if (files.toFileSystemProviderErrorCode(error) !== files.FileSystemProviderErrorCode.FileExists) {189// For mkdirp() we tolerate that the mkdir() call fails190// in case the folder already exists. This follows node.js191// own implementation of fs.mkdir({ recursive: true }) and192// reduces the chances of race conditions leading to errors193// if multiple calls try to create the same folders194// As such, we only throw an error here if it is other than195// the fact that the file already exists.196// (see also https://github.com/microsoft/vscode/issues/89834)197throw error;198}199}200}201}202203private static _handleError(err: any): never {204// desired error type205if (err instanceof FileSystemError) {206throw err;207}208209// file system provider error210if (err instanceof files.FileSystemProviderError) {211switch (err.code) {212case files.FileSystemProviderErrorCode.FileExists: throw FileSystemError.FileExists(err.message);213case files.FileSystemProviderErrorCode.FileNotFound: throw FileSystemError.FileNotFound(err.message);214case files.FileSystemProviderErrorCode.FileNotADirectory: throw FileSystemError.FileNotADirectory(err.message);215case files.FileSystemProviderErrorCode.FileIsADirectory: throw FileSystemError.FileIsADirectory(err.message);216case files.FileSystemProviderErrorCode.NoPermissions: throw FileSystemError.NoPermissions(err.message);217case files.FileSystemProviderErrorCode.Unavailable: throw FileSystemError.Unavailable(err.message);218219default: throw new FileSystemError(err.message, err.name as files.FileSystemProviderErrorCode);220}221}222223// generic error224if (!(err instanceof Error)) {225throw new FileSystemError(String(err));226}227228// no provider (unknown scheme) error229if (err.name === 'ENOPRO' || err.message.includes('ENOPRO')) {230throw FileSystemError.Unavailable(err.message);231}232233// file system error234switch (err.name) {235case files.FileSystemProviderErrorCode.FileExists: throw FileSystemError.FileExists(err.message);236case files.FileSystemProviderErrorCode.FileNotFound: throw FileSystemError.FileNotFound(err.message);237case files.FileSystemProviderErrorCode.FileNotADirectory: throw FileSystemError.FileNotADirectory(err.message);238case files.FileSystemProviderErrorCode.FileIsADirectory: throw FileSystemError.FileIsADirectory(err.message);239case files.FileSystemProviderErrorCode.NoPermissions: throw FileSystemError.NoPermissions(err.message);240case files.FileSystemProviderErrorCode.Unavailable: throw FileSystemError.Unavailable(err.message);241242default: throw new FileSystemError(err.message, err.name as files.FileSystemProviderErrorCode);243}244}245246// ---247248addFileSystemProvider(scheme: string, provider: vscode.FileSystemProvider, options?: { isCaseSensitive?: boolean; isReadonly?: boolean | IMarkdownString }): IDisposable {249this._fileSystemProvider.set(scheme, { impl: provider, extUri: options?.isCaseSensitive ? extUri : extUriIgnorePathCase, isReadonly: !!options?.isReadonly });250return toDisposable(() => this._fileSystemProvider.delete(scheme));251}252253getFileSystemProviderExtUri(scheme: string) {254return this._fileSystemProvider.get(scheme)?.extUri ?? extUri;255}256}257258export interface IExtHostConsumerFileSystem extends ExtHostConsumerFileSystem { }259export const IExtHostConsumerFileSystem = createDecorator<IExtHostConsumerFileSystem>('IExtHostConsumerFileSystem');260261262