Path: blob/main/src/vs/workbench/api/browser/mainThreadFileSystem.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 { Emitter, Event } from '../../../base/common/event.js';6import { IDisposable, toDisposable, DisposableStore, DisposableMap } from '../../../base/common/lifecycle.js';7import { URI, UriComponents } from '../../../base/common/uri.js';8import { IFileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IStat, IWatchOptions, FileType, IFileOverwriteOptions, IFileDeleteOptions, IFileOpenOptions, FileOperationError, FileOperationResult, FileSystemProviderErrorCode, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithFileFolderCopyCapability, FilePermission, toFileSystemProviderErrorCode, IFileStatWithPartialMetadata, IFileStat } from '../../../platform/files/common/files.js';9import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js';10import { ExtHostContext, ExtHostFileSystemShape, IFileChangeDto, MainContext, MainThreadFileSystemShape } from '../common/extHost.protocol.js';11import { VSBuffer } from '../../../base/common/buffer.js';12import { IMarkdownString } from '../../../base/common/htmlContent.js';1314@extHostNamedCustomer(MainContext.MainThreadFileSystem)15export class MainThreadFileSystem implements MainThreadFileSystemShape {1617private readonly _proxy: ExtHostFileSystemShape;18private readonly _fileProvider = new DisposableMap<number, RemoteFileSystemProvider>();19private readonly _disposables = new DisposableStore();2021constructor(22extHostContext: IExtHostContext,23@IFileService private readonly _fileService: IFileService24) {25this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystem);2627const infoProxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemInfo);2829for (const entry of _fileService.listCapabilities()) {30infoProxy.$acceptProviderInfos(URI.from({ scheme: entry.scheme, path: '/dummy' }), entry.capabilities);31}32this._disposables.add(_fileService.onDidChangeFileSystemProviderRegistrations(e => infoProxy.$acceptProviderInfos(URI.from({ scheme: e.scheme, path: '/dummy' }), e.provider?.capabilities ?? null)));33this._disposables.add(_fileService.onDidChangeFileSystemProviderCapabilities(e => infoProxy.$acceptProviderInfos(URI.from({ scheme: e.scheme, path: '/dummy' }), e.provider.capabilities)));34}3536dispose(): void {37this._disposables.dispose();38this._fileProvider.dispose();39}4041async $registerFileSystemProvider(handle: number, scheme: string, capabilities: FileSystemProviderCapabilities, readonlyMessage?: IMarkdownString): Promise<void> {42this._fileProvider.set(handle, new RemoteFileSystemProvider(this._fileService, scheme, capabilities, readonlyMessage, handle, this._proxy));43}4445$unregisterProvider(handle: number): void {46this._fileProvider.deleteAndDispose(handle);47}4849$onFileSystemChange(handle: number, changes: IFileChangeDto[]): void {50const fileProvider = this._fileProvider.get(handle);51if (!fileProvider) {52throw new Error('Unknown file provider');53}54fileProvider.$onFileSystemChange(changes);55}565758// --- consumer fs, vscode.workspace.fs5960async $stat(uri: UriComponents): Promise<IStat> {61try {62const stat = await this._fileService.stat(URI.revive(uri));63return {64ctime: stat.ctime,65mtime: stat.mtime,66size: stat.size,67permissions: stat.readonly ? FilePermission.Readonly : undefined,68type: MainThreadFileSystem._asFileType(stat)69};70} catch (err) {71return MainThreadFileSystem._handleError(err);72}73}7475async $readdir(uri: UriComponents): Promise<[string, FileType][]> {76try {77const stat = await this._fileService.resolve(URI.revive(uri), { resolveMetadata: false });78if (!stat.isDirectory) {79const err = new Error(stat.name);80err.name = FileSystemProviderErrorCode.FileNotADirectory;81throw err;82}83return !stat.children ? [] : stat.children.map(child => [child.name, MainThreadFileSystem._asFileType(child)] as [string, FileType]);84} catch (err) {85return MainThreadFileSystem._handleError(err);86}87}8889private static _asFileType(stat: IFileStat | IFileStatWithPartialMetadata): FileType {90let res = 0;91if (stat.isFile) {92res += FileType.File;9394} else if (stat.isDirectory) {95res += FileType.Directory;96}97if (stat.isSymbolicLink) {98res += FileType.SymbolicLink;99}100return res;101}102103async $readFile(uri: UriComponents): Promise<VSBuffer> {104try {105const file = await this._fileService.readFile(URI.revive(uri));106return file.value;107} catch (err) {108return MainThreadFileSystem._handleError(err);109}110}111112async $writeFile(uri: UriComponents, content: VSBuffer): Promise<void> {113try {114await this._fileService.writeFile(URI.revive(uri), content);115} catch (err) {116return MainThreadFileSystem._handleError(err);117}118}119120async $rename(source: UriComponents, target: UriComponents, opts: IFileOverwriteOptions): Promise<void> {121try {122await this._fileService.move(URI.revive(source), URI.revive(target), opts.overwrite);123} catch (err) {124return MainThreadFileSystem._handleError(err);125}126}127128async $copy(source: UriComponents, target: UriComponents, opts: IFileOverwriteOptions): Promise<void> {129try {130await this._fileService.copy(URI.revive(source), URI.revive(target), opts.overwrite);131} catch (err) {132return MainThreadFileSystem._handleError(err);133}134}135136async $mkdir(uri: UriComponents): Promise<void> {137try {138await this._fileService.createFolder(URI.revive(uri));139} catch (err) {140return MainThreadFileSystem._handleError(err);141}142}143144async $delete(uri: UriComponents, opts: IFileDeleteOptions): Promise<void> {145try {146return await this._fileService.del(URI.revive(uri), opts);147} catch (err) {148return MainThreadFileSystem._handleError(err);149}150}151152private static _handleError(err: any): never {153if (err instanceof FileOperationError) {154switch (err.fileOperationResult) {155case FileOperationResult.FILE_NOT_FOUND:156err.name = FileSystemProviderErrorCode.FileNotFound;157break;158case FileOperationResult.FILE_IS_DIRECTORY:159err.name = FileSystemProviderErrorCode.FileIsADirectory;160break;161case FileOperationResult.FILE_PERMISSION_DENIED:162err.name = FileSystemProviderErrorCode.NoPermissions;163break;164case FileOperationResult.FILE_MOVE_CONFLICT:165err.name = FileSystemProviderErrorCode.FileExists;166break;167}168} else if (err instanceof Error) {169const code = toFileSystemProviderErrorCode(err);170if (code !== FileSystemProviderErrorCode.Unknown) {171err.name = code;172}173}174175throw err;176}177178$ensureActivation(scheme: string): Promise<void> {179return this._fileService.activateProvider(scheme);180}181}182183class RemoteFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileFolderCopyCapability {184185private readonly _onDidChange = new Emitter<readonly IFileChange[]>();186private readonly _registration: IDisposable;187188readonly onDidChangeFile: Event<readonly IFileChange[]> = this._onDidChange.event;189190readonly capabilities: FileSystemProviderCapabilities;191readonly onDidChangeCapabilities: Event<void> = Event.None;192193constructor(194fileService: IFileService,195scheme: string,196capabilities: FileSystemProviderCapabilities,197public readonly readOnlyMessage: IMarkdownString | undefined,198private readonly _handle: number,199private readonly _proxy: ExtHostFileSystemShape200) {201this.capabilities = capabilities;202this._registration = fileService.registerProvider(scheme, this);203}204205dispose(): void {206this._registration.dispose();207this._onDidChange.dispose();208}209210watch(resource: URI, opts: IWatchOptions) {211const session = Math.random();212this._proxy.$watch(this._handle, session, resource, opts);213return toDisposable(() => {214this._proxy.$unwatch(this._handle, session);215});216}217218$onFileSystemChange(changes: IFileChangeDto[]): void {219this._onDidChange.fire(changes.map(RemoteFileSystemProvider._createFileChange));220}221222private static _createFileChange(dto: IFileChangeDto): IFileChange {223return { resource: URI.revive(dto.resource), type: dto.type };224}225226// --- forwarding calls227228async stat(resource: URI): Promise<IStat> {229try {230return await this._proxy.$stat(this._handle, resource);231} catch (err) {232throw err;233}234}235236async readFile(resource: URI): Promise<Uint8Array> {237const buffer = await this._proxy.$readFile(this._handle, resource);238return buffer.buffer;239}240241writeFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void> {242return this._proxy.$writeFile(this._handle, resource, VSBuffer.wrap(content), opts);243}244245delete(resource: URI, opts: IFileDeleteOptions): Promise<void> {246return this._proxy.$delete(this._handle, resource, opts);247}248249mkdir(resource: URI): Promise<void> {250return this._proxy.$mkdir(this._handle, resource);251}252253readdir(resource: URI): Promise<[string, FileType][]> {254return this._proxy.$readdir(this._handle, resource);255}256257rename(resource: URI, target: URI, opts: IFileOverwriteOptions): Promise<void> {258return this._proxy.$rename(this._handle, resource, target, opts);259}260261copy(resource: URI, target: URI, opts: IFileOverwriteOptions): Promise<void> {262return this._proxy.$copy(this._handle, resource, target, opts);263}264265open(resource: URI, opts: IFileOpenOptions): Promise<number> {266return this._proxy.$open(this._handle, resource, opts);267}268269close(fd: number): Promise<void> {270return this._proxy.$close(this._handle, fd);271}272273async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {274const readData = await this._proxy.$read(this._handle, fd, pos, length);275data.set(readData.buffer, offset);276return readData.byteLength;277}278279write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {280return this._proxy.$write(this._handle, fd, pos, VSBuffer.wrap(data).slice(offset, offset + length));281}282}283284285