diff --git a/package-lock.json b/package-lock.json index 161af9139..8d511c718 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2413,6 +2413,13 @@ "@types/node": "*" } }, + "node_modules/@types/emscripten": { + "version": "1.39.13", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.13.tgz", + "integrity": "sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -11372,6 +11379,7 @@ "wtd-core": "~4.0.1" }, "devDependencies": { + "@types/emscripten": "~1.39.13", "@types/express": "~4.17.21", "@types/ws": "~8.5.12", "langium-cli": "~3.2.0", diff --git a/packages/client/package.json b/packages/client/package.json index 19c9348a7..b2c68f61a 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -31,6 +31,10 @@ "./vscode/services": { "types": "./lib/vscode/index.d.ts", "default": "./lib/vscode/index.js" + }, + "./fs": { + "types": "./lib/fs/index.d.ts", + "default": "./lib/fs/index.js" } }, "typesVersions": { @@ -43,6 +47,9 @@ ], "vscode/services": [ "lib/vscode/index" + ], + "fs": [ + "lib/fs/index" ] } }, diff --git a/packages/client/src/fs/definitions.ts b/packages/client/src/fs/definitions.ts index 08b4183bf..e51942f4c 100644 --- a/packages/client/src/fs/definitions.ts +++ b/packages/client/src/fs/definitions.ts @@ -13,12 +13,12 @@ export type FileReadResultStatus = 'success' | 'denied'; export interface FileReadRequestResult { status: FileReadResultStatus - content: string + content: string | ArrayBuffer } export interface FileUpdate { resourceUri: string - content: string + content: string | ArrayBuffer } export type FileUpdateResultStatus = 'equal' | 'updated' | 'created' | 'denied'; @@ -92,12 +92,6 @@ export interface FileSystemCapabilities { */ listFiles(params: DirectoryListingRequest): Promise - /** - * Set an optional logger - * @param logger the logger implemenation - */ - setLogger?(logger: Logger): void; - } /** @@ -106,18 +100,20 @@ export interface FileSystemCapabilities { export interface FileSystemEndpoint extends FileSystemCapabilities { /** - * Get the type of the client + * Whatever can't be handled in the constructor should be done here */ - getEndpointType(): EndpointType; - - getFileSystem(): FileSystemRealization; + init?(): void; -} + /** + * Set an optional logger + * @param logger the logger implemenation + */ + setLogger?(logger: Logger): void; -/** - * Defines the API for the file system in the local environment - */ -export interface FileSystemRealization extends FileSystemCapabilities { + /** + * Get the type of the client + */ + getEndpointType(): EndpointType; /** * Provide info about the file system diff --git a/packages/client/src/fs/endpoints/emptyEndpoint.ts b/packages/client/src/fs/endpoints/defaultEndpoint.ts similarity index 68% rename from packages/client/src/fs/endpoints/emptyEndpoint.ts rename to packages/client/src/fs/endpoints/defaultEndpoint.ts index f50bb18f3..824424d18 100644 --- a/packages/client/src/fs/endpoints/emptyEndpoint.ts +++ b/packages/client/src/fs/endpoints/defaultEndpoint.ts @@ -4,15 +4,21 @@ * ------------------------------------------------------------------------------------------ */ import { Logger } from 'monaco-languageclient/tools'; -import { DirectoryListingRequest, DirectoryListingRequestResult, EndpointType, FileReadRequest, FileReadRequestResult, FileSystemEndpoint, FileSystemRealization, FileUpdate, FileUpdateResult, StatsRequest, StatsRequestResult } from '../definitions.js'; +import { DirectoryListingRequest, DirectoryListingRequestResult, EndpointType, FileReadRequest, FileReadRequestResult, FileSystemEndpoint, FileUpdate, FileUpdateResult, StatsRequest, StatsRequestResult } from '../definitions.js'; export class EmptyFileSystemEndpoint implements FileSystemEndpoint { - private fileSystem: FileSystemRealization; + private endpointType: EndpointType; private logger?: Logger; - constructor(fileSystem: FileSystemRealization) { - this.fileSystem = fileSystem; + constructor(endpointType: EndpointType) { + this.endpointType = endpointType; + } + + init(): void { } + + getFileSystemInfo(): string { + return 'This file system performs no operations.'; } setLogger(logger: Logger): void { @@ -20,36 +26,35 @@ export class EmptyFileSystemEndpoint implements FileSystemEndpoint { } getEndpointType(): EndpointType { - return EndpointType.EMPTY; - } - - getFileSystem(): FileSystemRealization { - return this.fileSystem; + return this.endpointType; } readFile(params: FileReadRequest): Promise { this.logger?.info(`Reading file: ${params.resourceUri}`); - return this.fileSystem.readFile(params); + return Promise.resolve({ + status: 'denied', + content: '' + }); } writeFile(params: FileUpdate): Promise { this.logger?.info(`Writing file: ${params.resourceUri}`); - return this.fileSystem.writeFile(params); + return Promise.resolve({ status: 'denied' }); } syncFile(params: FileUpdate): Promise { this.logger?.info(`Syncing file: ${params.resourceUri}`); - return this.fileSystem.writeFile(params); + return Promise.resolve({ status: 'denied' }); } getFileStats(params: StatsRequest): Promise { this.logger?.info(`Getting file stats for: "${params.resourceUri}" (${params.type})`); - return this.fileSystem.getFileStats(params); + return Promise.reject('No stats available.'); } listFiles(params: DirectoryListingRequest): Promise { this.logger?.info(`Listing files for directory: "${params.directoryUri}"`); - return this.fileSystem.listFiles(params); + return Promise.reject('No file listing possible.'); } } diff --git a/packages/client/src/fs/index.ts b/packages/client/src/fs/index.ts new file mode 100644 index 000000000..c3b77aca2 --- /dev/null +++ b/packages/client/src/fs/index.ts @@ -0,0 +1,7 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +export * from './definitions.js'; +export * from './endpoints/defaultEndpoint.js'; diff --git a/packages/client/src/fs/realizations/emptyFs.ts b/packages/client/src/fs/realizations/emptyFs.ts deleted file mode 100644 index 479949bff..000000000 --- a/packages/client/src/fs/realizations/emptyFs.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) 2024 TypeFox and others. - * Licensed under the MIT License. See LICENSE in the package root for license information. - * ------------------------------------------------------------------------------------------ */ - -import { DirectoryListingRequest, DirectoryListingRequestResult, FileReadRequest, FileReadRequestResult, FileSystemRealization, FileUpdate, FileUpdateResult, StatsRequest, StatsRequestResult } from '../definitions.js'; - -export class EmptyFileSystem implements FileSystemRealization { - - getFileSystemInfo(): string { - return 'This file system performs no operations.'; - } - - readFile(_params: FileReadRequest): Promise { - return Promise.resolve({ - status: 'denied', - content: '' - }); - } - - writeFile(_params: FileUpdate): Promise { - return Promise.resolve({ status: 'denied' }); - } - - syncFile(_params: FileUpdate): Promise { - return Promise.resolve({ status: 'denied' }); - } - - getFileStats(_params: StatsRequest): Promise { - return Promise.reject('No stats available.'); - } - - listFiles(_params: DirectoryListingRequest): Promise { - return Promise.reject('No file listing possible.'); - } - -} diff --git a/packages/client/test/fs/endpoints/emptyEndpoint.test.ts b/packages/client/test/fs/endpoints/emptyEndpoint.test.ts index aae612ed5..6696de470 100644 --- a/packages/client/test/fs/endpoints/emptyEndpoint.test.ts +++ b/packages/client/test/fs/endpoints/emptyEndpoint.test.ts @@ -4,12 +4,11 @@ * ------------------------------------------------------------------------------------------ */ import { describe, expect, test } from 'vitest'; -import { EmptyFileSystemEndpoint } from '../../../src/fs/endpoints/emptyEndpoint.js'; -import { EmptyFileSystem } from '../../../src/fs/realizations/emptyFs.js'; +import { EmptyFileSystemEndpoint, EndpointType } from 'monaco-languageclient/fs'; describe('EmptyFileSystemEndpoint Tests', () => { - const endpoint = new EmptyFileSystemEndpoint(new EmptyFileSystem()); + const endpoint = new EmptyFileSystemEndpoint(EndpointType.EMPTY); test('readFile', async () => { const result = await endpoint.readFile({ resourceUri: '/tmp/test.js' }); diff --git a/packages/examples/package.json b/packages/examples/package.json index 25b4dd51a..de9ea1f0d 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -97,6 +97,7 @@ "devDependencies": { "@types/express": "~4.17.21", "@types/ws": "~8.5.12", + "@types/emscripten": "~1.39.13", "langium-cli": "~3.2.0", "ts-node": "~10.9.1", "vscode-languageserver-types": "~3.17.5" diff --git a/packages/examples/src/clangd/client/main.ts b/packages/examples/src/clangd/client/main.ts index bd0f46445..2dbcba19f 100644 --- a/packages/examples/src/clangd/client/main.ts +++ b/packages/examples/src/clangd/client/main.ts @@ -11,37 +11,8 @@ import '@codingame/monaco-vscode-cpp-default-extension'; import { createUserConfig } from './config.js'; import helloCppCode from './hello.cpp?raw'; import testerHCode from './tester.h?raw'; -import { ComChannelEndpoint, RawPayload, WorkerMessage, ComRouter } from 'wtd-core'; import { ClangdWorkerHandler } from './workerHandler.js'; - -/** - * Answer the file create request - */ -class FileHandlerMain implements ComRouter { - - private endpointFs?: ComChannelEndpoint; - - setComChannelEndpoint(comChannelEndpoint: ComChannelEndpoint): void { - this.endpointFs = comChannelEndpoint; - } - - async fs_driver_init(message: WorkerMessage) { - await this.endpointFs?.sentAnswer({ - message: WorkerMessage.createFromExisting(message, { - overrideCmd: 'fs_driver_init_confirm', - }), - awaitAnswer: false - }); - - await this.endpointFs?.sentMessage({ - message: WorkerMessage.fromPayload(new RawPayload({ - hello: 'worker', - }), 'fs_follower_init'), - awaitAnswer: true, - expectedAnswer: 'fs_follower_init_confirm' - }); - } -} +import { MainRemoteMessageChannelFs } from './mainRemoteMessageChannelFs.js'; export const runClangdWrapper = () => { const wrapper = new MonacoEditorLanguageClientWrapper(); @@ -50,17 +21,7 @@ export const runClangdWrapper = () => { const channelLs = new MessageChannel(); const channelFs = new MessageChannel(); - const fileHandlerMain = new FileHandlerMain(); - const endpointFs = new ComChannelEndpoint({ - endpointId: 21, - endpointName: 'port_main_fs', - endpointConfig: { - $type: 'DirectImplConfig', - impl: channelFs.port1 - }, - verbose: true - }); - endpointFs.connect(fileHandlerMain); + new MainRemoteMessageChannelFs(channelFs.port1); try { document.querySelector('#button-start')?.addEventListener('click', async () => { diff --git a/packages/examples/src/clangd/client/mainRemoteMessageChannelFs.ts b/packages/examples/src/clangd/client/mainRemoteMessageChannelFs.ts new file mode 100644 index 000000000..de4b97096 --- /dev/null +++ b/packages/examples/src/clangd/client/mainRemoteMessageChannelFs.ts @@ -0,0 +1,69 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { ComChannelEndpoint, RawPayload, WorkerMessage, ComRouter } from 'wtd-core'; + +/** + * Answer the file create request + */ +export class FileHandlerMain implements ComRouter { + + private endpointFs?: ComChannelEndpoint; + + setComChannelEndpoint(comChannelEndpoint: ComChannelEndpoint): void { + this.endpointFs = comChannelEndpoint; + } + + async fs_driver_init(message: WorkerMessage) { + await this.endpointFs?.sentAnswer({ + message: WorkerMessage.createFromExisting(message, { + overrideCmd: 'fs_driver_init_confirm', + }), + awaitAnswer: false + }); + + // send double confirmation + await this.endpointFs?.sentMessage({ + message: WorkerMessage.fromPayload(new RawPayload({ + hello: 'worker', + }), 'fs_follower_init'), + awaitAnswer: true, + expectedAnswer: 'fs_follower_init_confirm' + }); + } + + async syncFile(message: WorkerMessage) { + const rawPayload = message.payloads[0] as RawPayload; + console.log(rawPayload.message.raw.resourceUri); + + await this.endpointFs?.sentAnswer({ + message: WorkerMessage.createFromExisting(message, { + overrideCmd: 'syncFile_confirm', + overridePayloads: new RawPayload({ + status: 'created', + message: `Created: ${rawPayload.message.raw.resourceUri}` + }) + }) + }); + } +} + +export class MainRemoteMessageChannelFs { + + constructor(port: MessagePort) { + + const fileHandlerMain = new FileHandlerMain(); + const endpointFs = new ComChannelEndpoint({ + endpointId: 21, + endpointName: 'port_main_fs', + endpointConfig: { + $type: 'DirectImplConfig', + impl: port + }, + verbose: true + }); + endpointFs.connect(fileHandlerMain); + } +} diff --git a/packages/examples/src/clangd/worker/clangd-server.ts b/packages/examples/src/clangd/worker/clangd-server.ts index 8da42c90c..53a546d24 100644 --- a/packages/examples/src/clangd/worker/clangd-server.ts +++ b/packages/examples/src/clangd/worker/clangd-server.ts @@ -9,30 +9,36 @@ import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageserve import { ComChannelEndpoint, ComRouter, RawPayload, WorkerMessage } from 'wtd-core'; import { COMPILE_ARGS, FILE_PATH, WORKSPACE_PATH } from '../definitions.js'; import { JsonStream } from './json_stream.js'; -import { FileHub } from './filehub.js'; +import { WorkerRemoteMessageChannelFs } from './workerRemoteMessageChannelFs.js'; declare const self: DedicatedWorkerGlobalScope; export class ClangdInteractionWorker implements ComRouter { private endpointWorker?: ComChannelEndpoint; - private fileHub: FileHub = new FileHub(); + // private fileHub: FileHub = new FileHub(); private reader?: BrowserMessageReader; private writer?: BrowserMessageWriter; + private lsMessagePort?: MessagePort; + private fsMessagePort?: MessagePort; + + private emscriptenFS?: typeof FS; + private remoteFs?: WorkerRemoteMessageChannelFs; + setComChannelEndpoint(comChannelEndpoint: ComChannelEndpoint): void { this.endpointWorker = comChannelEndpoint; } async clangd_init(message: WorkerMessage) { const rawPayload = (message.payloads![0] as RawPayload).message.raw; - const lsMessagePort = rawPayload.lsMessagePort as MessagePort; - const fsMessagePort = rawPayload.fsMessagePort as MessagePort; + this.lsMessagePort = rawPayload.lsMessagePort as MessagePort; + this.fsMessagePort = rawPayload.fsMessagePort as MessagePort; - this.reader = new BrowserMessageReader(lsMessagePort); - this.writer = new BrowserMessageWriter(lsMessagePort); + this.reader = new BrowserMessageReader(this.lsMessagePort); + this.writer = new BrowserMessageWriter(this.lsMessagePort); - await this.fileHub.init(fsMessagePort); + // await this.fileHub.init(this.fsMessagePort); this.endpointWorker?.sentAnswer({ message: WorkerMessage.createFromExisting(message, { @@ -49,6 +55,8 @@ export class ClangdInteractionWorker implements ComRouter { overrideCmd: 'clangd_launch_complete' }) }); + + await this.updateFilesystem(); } private async runClangdLanguageServer() { @@ -146,6 +154,8 @@ export class ClangdInteractionWorker implements ComRouter { }); console.log(clangd); + this.emscriptenFS = clangd.FS as typeof FS; + const flags = [ ...COMPILE_ARGS, '--target=wasm32-wasi', @@ -155,18 +165,16 @@ export class ClangdInteractionWorker implements ComRouter { '-isystem/usr/include/wasm32-wasi', ]; - clangd.FS.writeFile(FILE_PATH, ''); - clangd.FS.writeFile( + this.emscriptenFS.writeFile(FILE_PATH, ''); + this.emscriptenFS.writeFile( `${WORKSPACE_PATH}/.clangd`, JSON.stringify({ CompileFlags: { Add: flags } }) ); - clangd.FS.writeFile( - `${WORKSPACE_PATH}/tester.h`, - 'struct Tester {}' - ); - // const test2 = clangd.FS.readFile('/usr/include/wasm32-wasi/stdio.h'); - // console.log(String.fromCharCode.apply(null, test2)); + // emscriptenFS.writeFile( + // `${WORKSPACE_PATH}/tester.h`, + // 'struct Tester {}' + // ); function startServer() { console.log('%c%s', 'font-size: 2em; color: green', 'clangd started'); @@ -188,10 +196,43 @@ export class ClangdInteractionWorker implements ComRouter { // setTimeout(() => { // // test read back - // const test1 = clangd.FS.readFile(`${WORKSPACE_PATH}/tester.h`) as number[]; + // const test1 = emscriptenFS.readFile(`${WORKSPACE_PATH}/tester.h`) as number[]; // console.log(String.fromCharCode.apply(null, test1)); // }, 5000); } + + private async updateFilesystem() { + if (this.fsMessagePort !== undefined && this.emscriptenFS !== undefined) { + this.remoteFs = new WorkerRemoteMessageChannelFs(this.fsMessagePort, this.emscriptenFS); + this.remoteFs.init(); + + const dirUsrIncludeWasm32 = this.emscriptenFS.readdir('/usr/include/wasm32-wasi'); + // const test2 = emscriptenFS.readFile('/usr/include/wasm32-wasi/stdio.h'); + // console.log(String.fromCharCode.apply(null, test2)); + + const allPromises = []; + for (const file of dirUsrIncludeWasm32) { + try { + const filename = `/usr/include/wasm32-wasi/${file}`; + const content = this.emscriptenFS.readFile(filename, { encoding: 'binary' }); + allPromises.push(this.remoteFs.syncFile({ + resourceUri: filename, + content: content + })); + + } catch (e) { + // don't care currently + } + } + const allResults = await Promise.all(allPromises); + allResults.forEach((result) => { + console.log(`syncFile status: ${result.status} message: ${result.message}`); + }); + } else { + return Promise.reject(new Error('No filesystem is available. Aborting...')); + } + + } } new ComChannelEndpoint({ diff --git a/packages/examples/src/clangd/worker/filehub.ts b/packages/examples/src/clangd/worker/filehub.ts deleted file mode 100644 index f0c94d120..000000000 --- a/packages/examples/src/clangd/worker/filehub.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) 2024 TypeFox and others. - * Licensed under the MIT License. See LICENSE in the package root for license information. - * ------------------------------------------------------------------------------------------ */ - -import { ComChannelEndpoint, ComRouter, RawPayload, WorkerMessage } from 'wtd-core'; - -class FileHandler implements ComRouter { - - private portClandFsEndpoint: ComChannelEndpoint; - - setComChannelEndpoint(comChannelEndpoint: ComChannelEndpoint): void { - this.portClandFsEndpoint = comChannelEndpoint; - } - - async fs_follower_init(message: WorkerMessage) { - await this.portClandFsEndpoint.sentAnswer({ - message: WorkerMessage.createFromExisting(message, { - overrideCmd: 'fs_follower_init_confirm' - }) - }); - } -} - -export class FileHub { - - private portClandFsEndpoint?: ComChannelEndpoint; - - async init(port: MessagePort) { - this.portClandFsEndpoint = new ComChannelEndpoint({ - endpointId: 22, - endpointConfig: { - $type: 'DirectImplConfig', - impl: port - }, - verbose: true, - endpointName: 'port_clangd_fs' - }); - this.portClandFsEndpoint.connect(new FileHandler()); - - await this.portClandFsEndpoint.sentMessage({ - message: WorkerMessage.fromPayload(new RawPayload({ - hello: 'main', - }), 'fs_driver_init'), - transferables: [], - awaitAnswer: true, - expectedAnswer: 'fs_driver_init_confirm' - }); - } - -} diff --git a/packages/examples/src/clangd/worker/workerRemoteMessageChannelFs.ts b/packages/examples/src/clangd/worker/workerRemoteMessageChannelFs.ts new file mode 100644 index 000000000..b180732ba --- /dev/null +++ b/packages/examples/src/clangd/worker/workerRemoteMessageChannelFs.ts @@ -0,0 +1,103 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { DirectoryListingRequest, DirectoryListingRequestResult, EndpointType, FileReadRequest, FileReadRequestResult, FileSystemEndpoint, FileUpdate, FileUpdateResult, StatsRequest, StatsRequestResult } from 'monaco-languageclient/fs'; +import { ComChannelEndpoint, ComRouter, RawPayload, WorkerMessage } from 'wtd-core'; + +class FileHandlerWorker implements ComRouter { + + private portClandFsEndpoint: ComChannelEndpoint; + + setComChannelEndpoint(comChannelEndpoint: ComChannelEndpoint): void { + this.portClandFsEndpoint = comChannelEndpoint; + } + + async fs_follower_init(message: WorkerMessage) { + await this.portClandFsEndpoint.sentAnswer({ + message: WorkerMessage.createFromExisting(message, { + overrideCmd: 'fs_follower_init_confirm' + }) + }); + } +} + +export class WorkerRemoteMessageChannelFs implements FileSystemEndpoint { + + private clangdFsEndpoint?: ComChannelEndpoint; + private emscriptenFS: typeof FS; + + constructor(port: MessagePort, emscriptenFS: typeof FS) { + this.emscriptenFS = emscriptenFS; + this.clangdFsEndpoint = new ComChannelEndpoint({ + endpointId: 22, + endpointConfig: { + $type: 'DirectImplConfig', + impl: port + }, + verbose: true, + endpointName: 'port_clangd_fs' + }); + this.clangdFsEndpoint.connect(new FileHandlerWorker()); + } + + getEndpointType(): EndpointType { + return EndpointType.DRIVER; + } + + async init() { + await this.clangdFsEndpoint?.sentMessage({ + message: WorkerMessage.fromPayload(new RawPayload({ + hello: 'main', + }), 'fs_driver_init'), + awaitAnswer: true, + expectedAnswer: 'fs_driver_init_confirm' + }); + } + + getFileSystemInfo(): string { + return 'This file system sends all requests to the remote end of the message channel.'; + } + + readFile(_params: FileReadRequest): Promise { + return Promise.resolve({ + status: 'denied', + content: '' + }); + } + + writeFile(_params: FileUpdate): Promise { + return Promise.resolve({ status: 'denied' }); + } + + async syncFile(params: FileUpdate): Promise { + const content = this.emscriptenFS.readFile(params.resourceUri, { encoding: 'binary' }); + const result = await this.clangdFsEndpoint?.sentMessage({ + message: WorkerMessage.fromPayload(new RawPayload({ + resourceUri: params.resourceUri, + content: content + }), 'syncFile'), + transferables: [content.buffer], + awaitAnswer: true, + expectedAnswer: 'syncFile_confirm' + }); + + const rawPayload = result?.payloads[0] as RawPayload; + // console.log(rawPayload); + + return Promise.resolve({ + status: rawPayload.message.raw?.status, + message: rawPayload.message.raw?.message + }); + } + + getFileStats(_params: StatsRequest): Promise { + return Promise.reject('No stats available.'); + } + + listFiles(_params: DirectoryListingRequest): Promise { + return Promise.reject('No file listing possible.'); + } + +}