UNPKG

@theia/filesystem

Version:
1,039 lines (880 loc) • 36.5 kB
// ***************************************************************************** // Copyright (C) 2020 TypeFox and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // based on https://github.com/microsoft/vscode/blob/04c36be045a94fee58e5f8992d3e3fd980294a84/src/vs/platform/files/common/files.ts import URI from '@theia/core/lib/common/uri'; import { Event } from '@theia/core/lib/common/event'; import { Disposable as IDisposable } from '@theia/core/lib/common/disposable'; import { BinaryBuffer, BinaryBufferReadableStream } from '@theia/core/lib/common/buffer'; import type { TextDocumentContentChangeEvent } from '@theia/core/shared/vscode-languageserver-protocol'; import { ReadableStreamEvents } from '@theia/core/lib/common/stream'; import { CancellationToken } from '@theia/core/lib/common/cancellation'; import { isObject } from '@theia/core/lib/common'; import { MarkdownString } from '@theia/core/lib/common/markdown-rendering'; export const enum FileOperation { CREATE, DELETE, MOVE, COPY } export class FileOperationEvent { constructor(resource: URI, operation: FileOperation.DELETE); constructor(resource: URI, operation: FileOperation.CREATE | FileOperation.MOVE | FileOperation.COPY, target: FileStatWithMetadata); constructor(public readonly resource: URI, public readonly operation: FileOperation, public readonly target?: FileStatWithMetadata) { } isOperation(operation: FileOperation.DELETE): boolean; isOperation(operation: FileOperation.MOVE | FileOperation.COPY | FileOperation.CREATE): this is { readonly target: FileStatWithMetadata }; isOperation(operation: FileOperation): boolean { return this.operation === operation; } } /** * Possible changes that can occur to a file. */ export const enum FileChangeType { UPDATED = 0, ADDED = 1, DELETED = 2 } /** * Identifies a single change in a file. */ export interface FileChange { /** * The type of change that occurred to the file. */ readonly type: FileChangeType; /** * The unified resource identifier of the file that changed. */ readonly resource: URI; } export class FileChangesEvent { constructor(public readonly changes: readonly FileChange[]) { } /** * Returns true if this change event contains the provided file with the given change type (if provided). In case of * type DELETED, this method will also return true if a folder got deleted that is the parent of the * provided file path. */ contains(resource: URI, type?: FileChangeType): boolean { if (!resource) { return false; } const checkForChangeType = typeof type === 'number'; return this.changes.some(change => { if (checkForChangeType && change.type !== type) { return false; } // For deleted also return true when deleted folder is parent of target path if (change.type === FileChangeType.DELETED) { return change.resource.isEqualOrParent(resource); } return change.resource.toString() === resource.toString(); }); } /** * Returns the changes that describe added files. */ getAdded(): FileChange[] { return this.getOfType(FileChangeType.ADDED); } /** * Returns if this event contains added files. */ gotAdded(): boolean { return this.hasType(FileChangeType.ADDED); } /** * Returns the changes that describe deleted files. */ getDeleted(): FileChange[] { return this.getOfType(FileChangeType.DELETED); } /** * Returns if this event contains deleted files. */ gotDeleted(): boolean { return this.hasType(FileChangeType.DELETED); } /** * Returns the changes that describe updated files. */ getUpdated(): FileChange[] { return this.getOfType(FileChangeType.UPDATED); } /** * Returns if this event contains updated files. */ gotUpdated(): boolean { return this.hasType(FileChangeType.UPDATED); } private getOfType(type: FileChangeType): FileChange[] { return this.changes.filter(change => change.type === type); } private hasType(type: FileChangeType): boolean { return this.changes.some(change => change.type === type); } } export interface BaseStat { /** * The unified resource identifier of this file or folder. */ resource: URI; /** * The name which is the last segment * of the {{path}}. */ name: string; /** * The size of the file. * * The value may or may not be resolved as * it is optional. */ size?: number; /** * The last modification date represented as millis from unix epoch. * * The value may or may not be resolved as * it is optional. */ mtime?: number; /** * The creation date represented as millis from unix epoch. * * The value may or may not be resolved as * it is optional. */ ctime?: number; /** * A unique identifier that represents the * current state of the file or directory. * * The value may or may not be resolved as * it is optional. */ etag?: string; } export namespace BaseStat { export function is(arg: unknown): arg is BaseStat { return isObject<BaseStat>(arg) && arg.resource instanceof URI && typeof arg.name === 'string'; } } export interface BaseStatWithMetadata extends BaseStat { mtime: number; ctime: number; etag: string; size: number; } /** * A file resource with meta information. */ export interface FileStat extends BaseStat { /** * The resource is a file. */ isFile: boolean; /** * The resource is a directory. */ isDirectory: boolean; /** * The resource is a symbolic link. */ isSymbolicLink: boolean; /** * The resource is read only. */ isReadonly: boolean; /** * The children of the file stat or undefined if none. */ children?: FileStat[]; } export namespace FileStat { export function is(arg: unknown): arg is FileStat { const fileStat = arg as FileStat; return BaseStat.is(fileStat) && ('isFile' in fileStat && typeof fileStat.isFile === 'boolean') && ('isDirectory' in fileStat && typeof fileStat.isDirectory === 'boolean') && ('isSymbolicLink' in fileStat && typeof fileStat.isSymbolicLink === 'boolean'); } export function asFileType(stat: FileStat): FileType { let res = 0; if (stat.isFile) { res += FileType.File; } else if (stat.isDirectory) { res += FileType.Directory; } if (stat.isSymbolicLink) { res += FileType.SymbolicLink; } return res; } export function toStat(stat: FileStat): Stat | { type: FileType } & Partial<Stat> { return { type: asFileType(stat), ctime: stat.ctime, mtime: stat.mtime, size: stat.size }; } export function fromStat(resource: URI, stat: Stat): FileStatWithMetadata; export function fromStat(resource: URI, stat: { type: FileType } & Partial<Stat>): FileStat; export function fromStat(resource: URI, stat: Stat | { type: FileType } & Partial<Stat>): FileStat { return { resource, name: resource.path.base || resource.path.toString(), isFile: (stat.type & FileType.File) !== 0, isDirectory: (stat.type & FileType.Directory) !== 0, isSymbolicLink: (stat.type & FileType.SymbolicLink) !== 0, isReadonly: !!stat.permissions && (stat.permissions & FilePermission.Readonly) !== 0, mtime: stat.mtime, ctime: stat.ctime, size: stat.size, etag: etag({ mtime: stat.mtime, size: stat.size }) }; } export function dir(resource: string | URI, stat?: Partial<Omit<Stat, 'type'>>): FileStat { return fromStat(resource instanceof URI ? resource : new URI(resource), { type: FileType.Directory, ...stat }); } export function file(resource: string | URI, stat?: Partial<Omit<Stat, 'type'>>): FileStat { return fromStat(resource instanceof URI ? resource : new URI(resource), { type: FileType.File, ...stat }); } } export interface FileStatWithMetadata extends FileStat, BaseStatWithMetadata { mtime: number; ctime: number; etag: string; size: number; children?: FileStatWithMetadata[]; } export interface ResolveFileResult { stat?: FileStat; success: boolean; } export interface ResolveFileResultWithMetadata extends ResolveFileResult { stat?: FileStatWithMetadata; } export interface FileContent extends BaseStatWithMetadata { /** * The content of a file as buffer. */ value: BinaryBuffer; } export interface FileStreamContent extends BaseStatWithMetadata { /** * The content of a file as stream. */ value: BinaryBufferReadableStream; } export interface WriteFileOptions { /** * The last known modification time of the file. This can be used to prevent dirty writes. */ readonly mtime?: number; /** * The etag of the file. This can be used to prevent dirty writes. */ readonly etag?: string; } export interface ReadFileOptions extends FileReadStreamOptions { /** * The optional etag parameter allows to return early from resolving the resource if * the contents on disk match the etag. This prevents accumulated reading of resources * that have been read already with the same etag. * It is the task of the caller to makes sure to handle this error case from the promise. */ readonly etag?: string; } export interface WriteFileOptions { /** * The last known modification time of the file. This can be used to prevent dirty writes. */ readonly mtime?: number; /** * The etag of the file. This can be used to prevent dirty writes. */ readonly etag?: string; } export interface ResolveFileOptions { /** * Automatically continue resolving children of a directory until the provided resources * are found. */ readonly resolveTo?: readonly URI[]; /** * Automatically continue resolving children of a directory if the number of children is 1. */ readonly resolveSingleChildDescendants?: boolean; /** * Will resolve mtime, ctime, size and etag of files if enabled. This can have a negative impact * on performance and thus should only be used when these values are required. */ readonly resolveMetadata?: boolean; } export interface ResolveMetadataFileOptions extends ResolveFileOptions { readonly resolveMetadata: true; } export interface FileOperationOptions { /** * Indicates that a user action triggered the opening, e.g. * via mouse or keyboard use. Default is true. */ fromUserGesture?: boolean } export interface MoveFileOptions extends FileOperationOptions, Partial<FileOverwriteOptions> { } export interface CopyFileOptions extends FileOperationOptions, Partial<FileOverwriteOptions> { } export interface CreateFileOptions extends FileOperationOptions, Partial<FileOverwriteOptions> { } export class FileOperationError extends Error { constructor(message: string, public fileOperationResult: FileOperationResult, public options?: ReadFileOptions & WriteFileOptions & CreateFileOptions) { super(message); Object.setPrototypeOf(this, FileOperationError.prototype); } } export const enum FileOperationResult { FILE_IS_DIRECTORY, FILE_NOT_FOUND, FILE_NOT_MODIFIED_SINCE, FILE_MODIFIED_SINCE, FILE_MOVE_CONFLICT, FILE_READ_ONLY, FILE_PERMISSION_DENIED, FILE_TOO_LARGE, FILE_INVALID_PATH, FILE_EXCEEDS_MEMORY_LIMIT, FILE_NOT_DIRECTORY, FILE_OTHER_ERROR } export interface FileOverwriteOptions { /** * Overwrite the file to create if it already exists on disk. Otherwise * an error will be thrown (FILE_MODIFIED_SINCE). */ overwrite: boolean; } export interface FileReadStreamOptions { /** * Is an integer specifying where to begin reading from in the file. If position is undefined, * data will be read from the current file position. */ readonly position?: number; /** * Is an integer specifying how many bytes to read from the file. By default, all bytes * will be read. */ readonly length?: number; /** * If provided, the size of the file will be checked against the limits. */ limits?: { readonly size?: number; readonly memory?: number; }; } export interface FileUpdateOptions { readEncoding: string; writeEncoding: string; overwriteEncoding: boolean; } export interface FileUpdateResult extends Stat { encoding: string; } export interface FileWriteOptions { overwrite: boolean; create: boolean; } export interface FileOpenOptions { create: boolean; } export interface FileDeleteOptions { recursive: boolean; useTrash: boolean; } export enum FileType { Unknown = 0, File = 1, Directory = 2, SymbolicLink = 64 } export enum FilePermission { /** * File is readonly. */ Readonly = 1 } export interface Stat { type: FileType; /** * The last modification date represented as millis from unix epoch. */ mtime: number; /** * The creation date represented as millis from unix epoch. */ ctime: number; size: number; permissions?: FilePermission; } export interface WatchOptions { recursive: boolean; excludes: string[]; } export const enum FileSystemProviderCapabilities { None = 0, FileReadWrite = 1 << 1, FileOpenReadWriteClose = 1 << 2, FileReadStream = 1 << 4, FileFolderCopy = 1 << 3, PathCaseSensitive = 1 << 10, Readonly = 1 << 11, Trash = 1 << 12, Access = 1 << 24, Update = 1 << 25 } export enum FileSystemProviderErrorCode { FileExists = 'EntryExists', FileNotFound = 'EntryNotFound', FileNotADirectory = 'EntryNotADirectory', FileIsADirectory = 'EntryIsADirectory', FileExceedsMemoryLimit = 'EntryExceedsMemoryLimit', FileTooLarge = 'EntryTooLarge', NoPermissions = 'NoPermissions', Unavailable = 'Unavailable', Unknown = 'Unknown' } export class FileSystemProviderError extends Error { constructor(message: string, public readonly code: FileSystemProviderErrorCode) { super(message); Object.setPrototypeOf(this, FileSystemProviderError.prototype); } } export function createFileSystemProviderError(error: Error | string, code: FileSystemProviderErrorCode): FileSystemProviderError { const providerError = new FileSystemProviderError(error.toString(), code); markAsFileSystemProviderError(providerError, code); return providerError; } export function ensureFileSystemProviderError(error?: Error): Error { if (!error) { return createFileSystemProviderError('Unknown Error', FileSystemProviderErrorCode.Unknown); // https://github.com/Microsoft/vscode/issues/72798 } return error; } export const FileSystemProvider = Symbol('FileSystemProvider'); /** * A {@link FileSystemProvider} provides the capabilities to read, write, discover, and to manage files and folders * of the underlying (potentially virtual) file system. {@link FileSystemProvider}s can be used to serve files from both the * local disk as well as remote locations like ftp-servers, REST-services etc. A {@link FileSystemProvider} is registered for a certain * scheme and can handle all resources whose uri does conform to that scheme. */ export interface FileSystemProvider { /** The {@link FileSystemProviderCapabilities} for this provider. */ readonly capabilities: FileSystemProviderCapabilities; /** * Event that is fired if the capabilities of this provider have changed. */ readonly onDidChangeCapabilities: Event<void>; /** Event that is fired if a (watched) file in the filesystem of this provider has changed. */ readonly onDidChangeFile: Event<readonly FileChange[]>; /** Event that is fired if an error occurred when watching files in the filesystem of this provider. */ readonly onFileWatchError: Event<void>; /** * Watch the given resource and react to changes by firing the {@link FileSystemProvider#onDidChangeFile} event. * @param resource `URI` of the resource to be watched. * @param opts Options to define if the resource should be watched recursively and to * provide a set of resources that should be excluded from watching. * * @returns A `Disposable` that can be invoked to stop watching the resource. */ watch(resource: URI, opts: WatchOptions): IDisposable; /** * Retrieve metadata about a given file. * * @param uri The `URI` of the file to retrieve meta data about. * @returns A promise of the metadata about the resource. */ stat(resource: URI): Promise<Stat>; /** * Create a new directory using the given resource uri. * @param resource The `URI` of the new folder. */ mkdir(resource: URI): Promise<void>; /** * Retrieve the content of a given directory. * @param resource The `URI` of the directory. * * @returns A map containing the {@link FileType} for each child resource, identified by name. */ readdir(resource: URI): Promise<[string, FileType][]>; /** * Delete the given resource. * @param resource The `URI` of the resource to delete. * @param opts Options to define if files should be deleted recursively and if the trash should be used. */ delete(resource: URI, opts: FileDeleteOptions): Promise<void>; /** * Rename a file or folder. * @param from `URI` of the existing file or folder. * @param to `URI` of the target location. * @param opts Options to define if existing files should be overwritten. */ rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void>; /** * Optional function that has to be implemented by {@link FileSystemProviderWithFileFolderCopyCapability}. * See {@link FileSystemProviderWithFileFolderCopyCapability#copy}} for additional documentation. */ copy?(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void>; /** * Optional function that has to be implemented by {@link FileSystemProviderWithFileReadWriteCapability}. * See {@link FileSystemProviderWithFileReadWriteCapability#readFile} for additional documentation. */ readFile?(resource: URI): Promise<Uint8Array>; /** * Optional function that has to be implemented by {@link FileSystemProviderWithFileReadWriteCapability}. * See {@link FileSystemProviderWithFileReadWriteCapability#writeFile} for additional documentation. */ writeFile?(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void>; /** * Optional function that has to be implemented by {@link FileSystemProviderWithFileReadStreamCapability}. * See {@link FileSystemProviderWithFileReadStreamCapability#readFileStream} for additional documentation. */ readFileStream?(resource: URI, opts: FileReadStreamOptions, token: CancellationToken): ReadableStreamEvents<Uint8Array>; /** * Optional function that has to be implemented by {@link FileSystemProviderWithOpenReadWriteCloseCapability}. * See {@link FileSystemProviderWithOpenReadWriteCloseCapability#open} for additional documentation. */ open?(resource: URI, opts: FileOpenOptions): Promise<number>; /** * Optional function that has to be implemented by {@link FileSystemProviderWithOpenReadWriteCloseCapability}. * See {@link FileSystemProviderWithOpenReadWriteCloseCapability#close} for additional documentation. */ close?(fd: number): Promise<void>; /** * Optional function that has to be implemented by {@link FileSystemProviderWithOpenReadWriteCloseCapability}. * See {@link FileSystemProviderWithOpenReadWriteCloseCapability#read} for additional documentation. */ read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number>; /** * Optional function that has to be implemented by {@link FileSystemProviderWithOpenReadWriteCloseCapability}. * See {@link FileSystemProviderWithOpenReadWriteCloseCapability#write} for additional documentation. */ write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number>; /** * Optional function that has to be implemented by {@link FileSystemProviderWithAccessCapability}. * See {@link FileSystemProviderWithAccessCapability#access} for additional documentation. */ access?(resource: URI, mode?: number): Promise<void>; /** * Optional function that has to be implemented by {@link FileSystemProviderWithAccessCapability}. * See {@link FileSystemProviderWithAccessCapability#fsPath} for additional documentation. */ fsPath?(resource: URI): Promise<string>; /** * Optional function that has to be implemented by {@link FileSystemProviderWithUpdateCapability}. * See {@link FileSystemProviderWithUpdateCapability#updateFile} for additional documentation. */ updateFile?(resource: URI, changes: TextDocumentContentChangeEvent[], opts: FileUpdateOptions): Promise<FileUpdateResult>; } /** * Subtype of {@link FileSystemProvider} that ensures that the optional functions needed for providers, that should be * able access files, are implemented. */ export interface FileSystemProviderWithAccessCapability extends FileSystemProvider { /** * Tests a user's permissions for the file or directory specified by URI. * @param resource The `URI` of the file that should be tested. * @param mode An optional integer that specifies the accessibility checks to be performed. * Check `FileAccess.Constants` for possible values of mode. * It is possible to create a mask consisting of the bitwise `OR` of two or more values (e.g. FileAccess.Constants.W_OK | FileAccess.Constants.R_OK). * If `mode` is not defined, `FileAccess.Constants.F_OK` will be used instead. * * @returns A promise that resolves if the user has the required permissions, should be rejected otherwise. */ access(resource: URI, mode?: number): Promise<void>; /** * Returns the path of the given file URI, specific to the backend's operating system. * If the URI is not a file URI, undefined is returned. * * USE WITH CAUTION: You should always prefer URIs to paths if possible, as they are * portable and platform independent. Paths should only be used in cases you directly * interact with the OS, e.g. when running a command on the shell. * * @param resource `URI` of the resource to derive the path from. * * @returns A promise of the corresponding file system path. */ fsPath(resource: URI): Promise<string>; } export function hasAccessCapability(provider: FileSystemProvider): provider is FileSystemProviderWithAccessCapability { return !!(provider.capabilities & FileSystemProviderCapabilities.Access); } /** * Subtype of {@link FileSystemProvider} that ensures that the optional functions needed, for providers that should be * able to update (text) files, are implemented. */ export interface FileSystemProviderWithUpdateCapability extends FileSystemProvider { /** * Update the content of the given (text) file according to the given text document changes. * @param resource `URI` of the resource to update. * @param changes Array of events describing the changes to the file. * @param opts The encoding options. * * @returns A promise of the file metadata that resolves after the update process has completed. */ updateFile(resource: URI, changes: TextDocumentContentChangeEvent[], opts: FileUpdateOptions): Promise<FileUpdateResult>; } export function hasUpdateCapability(provider: FileSystemProvider): provider is FileSystemProviderWithUpdateCapability { return !!(provider.capabilities & FileSystemProviderCapabilities.Update); } export interface ReadOnlyMessageFileSystemProvider { readOnlyMessage: MarkdownString | undefined; readonly onDidChangeReadOnlyMessage: Event<MarkdownString | undefined>; } export namespace ReadOnlyMessageFileSystemProvider { export function is(arg: unknown): arg is ReadOnlyMessageFileSystemProvider { return isObject<ReadOnlyMessageFileSystemProvider>(arg) && 'readOnlyMessage' in arg; } } /** * Subtype of {@link FileSystemProvider} that ensures that the optional functions, needed for providers * that should be able to read & write files, are implemented. */ export interface FileSystemProviderWithFileReadWriteCapability extends FileSystemProvider { /** * Read the contents of the given file as stream. * @param resource The `URI` of the file. * * @return The `ReadableStreamEvents` for the readable stream of the given file. */ readFile(resource: URI): Promise<Uint8Array>; /** * Write data to a file, replacing its entire contents. * @param resource The uri of the file. * @param content The new content of the file. * @param opts Options to define if the file should be created if missing and if an existing file should be overwritten. */ writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void>; } export function hasReadWriteCapability(provider: FileSystemProvider): provider is FileSystemProviderWithFileReadWriteCapability { return !!(provider.capabilities & FileSystemProviderCapabilities.FileReadWrite); } /** * Subtype of {@link FileSystemProvider} that ensures that the optional functions, needed for providers that should be able to copy * file folders, are implemented. */ export interface FileSystemProviderWithFileFolderCopyCapability extends FileSystemProvider { /** * Copy files or folders. * @param from `URI` of the existing file or folder. * @param to `URI` of the destination location. * @param opts Options to define if existing files should be overwritten. */ copy(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void>; } export function hasFileFolderCopyCapability(provider: FileSystemProvider): provider is FileSystemProviderWithFileFolderCopyCapability { return !!(provider.capabilities & FileSystemProviderCapabilities.FileFolderCopy); } /** * Subtype of {@link FileSystemProvider} that ensures that the optional functions, needed for providers that should be able to open,read, write * or close files, are implemented. */ export interface FileSystemProviderWithOpenReadWriteCloseCapability extends FileSystemProvider { /** * Open the give file. * @param resource The `URI` of the file to open. * @param opts Options to define if the file should be created if it does not exist yet. * * @returns A promise of the file descriptor that resolves after the file is open. */ open(resource: URI, opts: FileOpenOptions): Promise<number>; /** * Close the file with the given file descriptor. * @param fd the file descriptor to close. */ close(fd: number): Promise<void>; /** * Read specified content from a given file descriptor into a data buffer. * @param fd The file descriptor referencing the file to read from. * @param pos The offset from the beginning of the file from which data should be read. * @param data The buffer that the data will be written to. * @param offset The offset in the buffer at which to start writing. * @param length The number of bytes to read. * * @returns A promise of the number of bytes read. */ read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number>; /** * Write specified content from the data buffer to the file referenced by the given file descriptor. * @param fd The file descriptor referencing the file to write to. * @param pos The offset from the beginning of the file where this data should be written. * @param offset The part of the buffer to be read from. * @param length The number of bytes to write. * * @returns A promise of the number of bytes written. */ write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number>; } export function hasOpenReadWriteCloseCapability(provider: FileSystemProvider): provider is FileSystemProviderWithOpenReadWriteCloseCapability { return !!(provider.capabilities & FileSystemProviderCapabilities.FileOpenReadWriteClose); } /** * Subtype of {@link FileSystemProvider} that ensures that the optional functions, needed for providers that should be able to read * files as streams, are implemented. */ export interface FileSystemProviderWithFileReadStreamCapability extends FileSystemProvider { /** * Read the contents of the given file as stream. * @param resource The `URI` of the file. * * @return The `ReadableStreamEvents` for the readable stream of the given file. */ readFileStream(resource: URI, opts: FileReadStreamOptions, token: CancellationToken): ReadableStreamEvents<Uint8Array>; } export function hasFileReadStreamCapability(provider: FileSystemProvider): provider is FileSystemProviderWithFileReadStreamCapability { return !!(provider.capabilities & FileSystemProviderCapabilities.FileReadStream); } export function markAsFileSystemProviderError(error: Error, code: FileSystemProviderErrorCode): Error { error.name = code ? `${code} (FileSystemError)` : 'FileSystemError'; return error; } export function toFileSystemProviderErrorCode(error: Error | undefined | null): FileSystemProviderErrorCode { // Guard against abuse if (!error) { return FileSystemProviderErrorCode.Unknown; } // FileSystemProviderError comes with the code if (error instanceof FileSystemProviderError) { return error.code; } // Any other error, check for name match by assuming that the error // went through the markAsFileSystemProviderError() method const match = /^(.+) \(FileSystemError\)$/.exec(error.name); if (!match) { return FileSystemProviderErrorCode.Unknown; } switch (match[1]) { case FileSystemProviderErrorCode.FileExists: return FileSystemProviderErrorCode.FileExists; case FileSystemProviderErrorCode.FileIsADirectory: return FileSystemProviderErrorCode.FileIsADirectory; case FileSystemProviderErrorCode.FileNotADirectory: return FileSystemProviderErrorCode.FileNotADirectory; case FileSystemProviderErrorCode.FileNotFound: return FileSystemProviderErrorCode.FileNotFound; case FileSystemProviderErrorCode.FileExceedsMemoryLimit: return FileSystemProviderErrorCode.FileExceedsMemoryLimit; case FileSystemProviderErrorCode.FileTooLarge: return FileSystemProviderErrorCode.FileTooLarge; case FileSystemProviderErrorCode.NoPermissions: return FileSystemProviderErrorCode.NoPermissions; case FileSystemProviderErrorCode.Unavailable: return FileSystemProviderErrorCode.Unavailable; } return FileSystemProviderErrorCode.Unknown; } export function toFileOperationResult(error: Error): FileOperationResult { // FileSystemProviderError comes with the result already if (error instanceof FileOperationError) { return error.fileOperationResult; } // Otherwise try to find from code switch (toFileSystemProviderErrorCode(error)) { case FileSystemProviderErrorCode.FileNotFound: return FileOperationResult.FILE_NOT_FOUND; case FileSystemProviderErrorCode.FileIsADirectory: return FileOperationResult.FILE_IS_DIRECTORY; case FileSystemProviderErrorCode.FileNotADirectory: return FileOperationResult.FILE_NOT_DIRECTORY; case FileSystemProviderErrorCode.NoPermissions: return FileOperationResult.FILE_PERMISSION_DENIED; case FileSystemProviderErrorCode.FileExists: return FileOperationResult.FILE_MOVE_CONFLICT; case FileSystemProviderErrorCode.FileExceedsMemoryLimit: return FileOperationResult.FILE_EXCEEDS_MEMORY_LIMIT; case FileSystemProviderErrorCode.FileTooLarge: return FileOperationResult.FILE_TOO_LARGE; default: return FileOperationResult.FILE_OTHER_ERROR; } } /** * A hint to disable etag checking for reading/writing. */ export const ETAG_DISABLED = ''; export function etag(stat: { mtime: number, size: number }): string; export function etag(stat: { mtime: number | undefined, size: number | undefined }): string | undefined; export function etag(stat: { mtime: number | undefined, size: number | undefined }): string | undefined { if (typeof stat.size !== 'number' || typeof stat.mtime !== 'number') { return undefined; } return stat.mtime.toString(29) + stat.size.toString(31); } /** * Helper class for formatting and parsing byte sizes. */ export class BinarySize { static readonly KB = 1024; static readonly MB = BinarySize.KB * BinarySize.KB; static readonly GB = BinarySize.MB * BinarySize.KB; static readonly TB = BinarySize.GB * BinarySize.KB; /** * Formats a byte size into a human readable string (e.g., "1.5MB", "2.3GB"). */ static formatSize(size: number): string { if (size < BinarySize.KB) { return size + 'B'; } if (size < BinarySize.MB) { return (size / BinarySize.KB).toFixed(2) + 'KB'; } if (size < BinarySize.GB) { return (size / BinarySize.MB).toFixed(2) + 'MB'; } if (size < BinarySize.TB) { return (size / BinarySize.GB).toFixed(2) + 'GB'; } return (size / BinarySize.TB).toFixed(2) + 'TB'; } /** * Parses a human readable string (e.g., "1.5MB", "2.3GB") and returns the size in bytes */ static parseSize(sizeInput: string | number | undefined): number { if (typeof sizeInput === 'number') { return Math.round(sizeInput); } if (!sizeInput) { return 0; } const trimmed = sizeInput.trim().toUpperCase(); const match = /^(\d+(?:\.\d+)?)([BKMG])?$/.exec(trimmed); // If the format is invalid, return 0 if (!match) { return 0; } const value = parseFloat(match[1]); const unit = match[2]; switch (unit) { case 'K': return Math.round(value * BinarySize.KB); case 'M': return Math.round(value * BinarySize.MB); case 'G': return Math.round(value * BinarySize.GB); case 'T': return Math.round(value * BinarySize.TB); default: return Math.round(value); } } }