diff --git a/packages/filesystem/src/node/filesystem-backend-module.ts b/packages/filesystem/src/node/filesystem-backend-module.ts index 3fc19513b774a..3b555b6173251 100644 --- a/packages/filesystem/src/node/filesystem-backend-module.ts +++ b/packages/filesystem/src/node/filesystem-backend-module.ts @@ -33,91 +33,20 @@ import { IPCConnectionProvider } from '@theia/core/lib/node'; import { JsonRpcProxyFactory, ConnectionErrorHandler } from '@theia/core'; import { FileSystemWatcherServiceDispatcher } from './filesystem-watcher-dispatcher'; -const SINGLE_THREADED = process.argv.indexOf('--no-cluster') !== -1; -const NSFW_WATCHER_VERBOSE = process.argv.indexOf('--nsfw-watcher-verbose') !== -1; +export const NSFW_SINGLE_THREADED = process.argv.includes('--no-cluster'); +export const NSFW_WATCHER_VERBOSE = process.argv.includes('--nsfw-watcher-verbose'); -export interface FileWatcherSingleProcessOptions { - processType: 'single'; - serverOptions: NsfwFileSystemWatcherServerOptions; -} - -export interface FileWatcherMultiProcessOptions { - processType: 'multi'; +export const NsfwFileSystemWatcherServiceProcessOptions = Symbol('NsfwFileSystemWatcherServiceProcessOptions'); +/** + * Options to control the way the `NsfwFileSystemWatcherService` process is spawned. + */ +export interface NsfwFileSystemWatcherServiceProcessOptions { + /** + * Path to the script that will run the `NsfwFileSystemWatcherService` in a new process. + */ entryPoint: string; } -export const FileWatcherProcessOptions = Symbol('FileWatcherProcessOptions'); -type FileWatcherProcessOptions = FileWatcherSingleProcessOptions | FileWatcherMultiProcessOptions; - -export function bindFileSystemWatcherServer(bind: interfaces.Bind, { singleThreaded }: { singleThreaded: boolean } = { singleThreaded: SINGLE_THREADED }): void { - bind(NsfwOptions).toConstantValue({}); - - bind(FileSystemWatcherServiceDispatcher).toSelf().inSingletonScope(); - - bind(FileSystemWatcherServerClient).toSelf(); - bind(FileSystemWatcherServer).toService(FileSystemWatcherServerClient); - - if (singleThreaded) { - bind(FileWatcherProcessOptions).toDynamicValue(ctx => { - const logger = ctx.container.get(ILogger); - const nsfwOptions = ctx.container.get(NsfwOptions); - return { - processType: 'single', - serverOptions: { - nsfwOptions, - verbose: NSFW_WATCHER_VERBOSE, - info: (message, ...args) => logger.info(message, ...args), - error: (message, ...args) => logger.error(message, ...args) - } - } as FileWatcherSingleProcessOptions; - }).inSingletonScope(); - } else { - bind(FileWatcherProcessOptions).toConstantValue({ - processType: 'multi', - entryPoint: path.resolve(__dirname, 'nsfw-watcher') - }); - } - - bind(FileSystemWatcherService).toDynamicValue(ctx => { - const watcherOptions = ctx.container.get(FileWatcherProcessOptions); - const dispatcher = ctx.container.get(FileSystemWatcherServiceDispatcher); - if (watcherOptions.processType === 'single') { - // Bind and run the watch server in the current process: - const server = new NsfwFileSystemWatcherService(watcherOptions.serverOptions); - server.setClient(dispatcher); - return server; - } else { - // Run the watch server in a child process. - // Bind to a proxy forwarding calls to the child process. - const serverName = 'nsfw-watcher'; - const logger = ctx.container.get(ILogger); - const nsfwOptions = ctx.container.get(NsfwOptions); - const ipcConnectionProvider = ctx.container.get(IPCConnectionProvider); - const proxyFactory = new JsonRpcProxyFactory(); - const serverProxy = proxyFactory.createProxy(); - // We need to call `.setClient` before listening, else the JSON-RPC calls won't go through. - serverProxy.setClient(dispatcher); - const args: string[] = [ - `--nsfwOptions=${JSON.stringify(nsfwOptions)}` - ]; - if (NSFW_WATCHER_VERBOSE) { - args.push('--verbose'); - } - ipcConnectionProvider.listen({ - serverName, - entryPoint: watcherOptions.entryPoint, - errorHandler: new ConnectionErrorHandler({ - serverName, - logger, - }), - env: process.env, - args, - }, connection => proxyFactory.listen(connection)); - return serverProxy; - } - }).inSingletonScope(); -} - export default new ContainerModule(bind => { bind(EncodingService).toSelf().inSingletonScope(); bindFileSystemWatcherServer(bind); @@ -136,3 +65,77 @@ export default new ContainerModule(bind => { bind(NodeFileUploadService).toSelf().inSingletonScope(); bind(MessagingService.Contribution).toService(NodeFileUploadService); }); + +export function bindFileSystemWatcherServer(bind: interfaces.Bind, { singleThreaded = NSFW_SINGLE_THREADED }: { singleThreaded?: boolean } = {}): void { + bind(NsfwOptions).toConstantValue({}); + + bind(FileSystemWatcherServiceDispatcher).toSelf().inSingletonScope(); + + bind(FileSystemWatcherServerClient).toSelf(); + bind(FileSystemWatcherServer).toService(FileSystemWatcherServerClient); + + bind(NsfwFileSystemWatcherServiceProcessOptions).toConstantValue({ + entryPoint: path.resolve(__dirname, 'nsfw-watcher'), + }); + bind(NsfwFileSystemWatcherServerOptions).toDynamicValue(ctx => { + const logger = ctx.container.get(ILogger); + const nsfwOptions = ctx.container.get(NsfwOptions); + return { + nsfwOptions, + verbose: NSFW_WATCHER_VERBOSE, + info: (message, ...args) => logger.info(message, ...args), + error: (message, ...args) => logger.error(message, ...args), + }; + }).inSingletonScope(); + + bind(FileSystemWatcherService).toDynamicValue( + ctx => singleThreaded + ? createNsfwFileSystemWatcherService(ctx) + : spawnNsfwFileSystemWatcherServiceProcess(ctx) + ).inSingletonScope(); +} + +/** + * Run the watch server in the current process. + */ +export function createNsfwFileSystemWatcherService(ctx: interfaces.Context): FileSystemWatcherService { + const options = ctx.container.get(NsfwFileSystemWatcherServerOptions); + const dispatcher = ctx.container.get(FileSystemWatcherServiceDispatcher); + const server = new NsfwFileSystemWatcherService(options); + server.setClient(dispatcher); + return server; +} + +/** + * Run the watch server in a child process. + * Return a proxy forwarding calls to the child process. + */ +export function spawnNsfwFileSystemWatcherServiceProcess(ctx: interfaces.Context): FileSystemWatcherService { + const options = ctx.container.get(NsfwFileSystemWatcherServiceProcessOptions); + const dispatcher = ctx.container.get(FileSystemWatcherServiceDispatcher); + const serverName = 'nsfw-watcher'; + const logger = ctx.container.get(ILogger); + const nsfwOptions = ctx.container.get(NsfwOptions); + const ipcConnectionProvider = ctx.container.get(IPCConnectionProvider); + const proxyFactory = new JsonRpcProxyFactory(); + const serverProxy = proxyFactory.createProxy(); + // We need to call `.setClient` before listening, else the JSON-RPC calls won't go through. + serverProxy.setClient(dispatcher); + const args: string[] = [ + `--nsfwOptions=${JSON.stringify(nsfwOptions)}` + ]; + if (NSFW_WATCHER_VERBOSE) { + args.push('--verbose'); + } + ipcConnectionProvider.listen({ + serverName, + entryPoint: options.entryPoint, + errorHandler: new ConnectionErrorHandler({ + serverName, + logger, + }), + env: process.env, + args, + }, connection => proxyFactory.listen(connection)); + return serverProxy; +} diff --git a/packages/filesystem/src/node/nsfw-watcher/nsfw-filesystem-service.ts b/packages/filesystem/src/node/nsfw-watcher/nsfw-filesystem-service.ts index cbcbe394b6be2..4caf210211811 100644 --- a/packages/filesystem/src/node/nsfw-watcher/nsfw-filesystem-service.ts +++ b/packages/filesystem/src/node/nsfw-watcher/nsfw-filesystem-service.ts @@ -29,6 +29,7 @@ export interface NsfwWatcherOptions { ignored: IMinimatch[] } +export const NsfwFileSystemWatcherServerOptions = Symbol('NsfwFileSystemWatcherServerOptions'); export interface NsfwFileSystemWatcherServerOptions { verbose: boolean; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -434,9 +435,9 @@ export class NsfwFileSystemWatcherService implements FileSystemWatcherService { return watcherId; } - protected createWatcher(clientId: number, fsPath: string, resolvedOptions: WatchOptions): NsfwWatcher { + protected createWatcher(clientId: number, fsPath: string, options: WatchOptions): NsfwWatcher { const watcherOptions: NsfwWatcherOptions = { - ignored: resolvedOptions.ignored + ignored: options.ignored .map(pattern => new Minimatch(pattern, { dot: true })), }; return new NsfwWatcher(clientId, fsPath, watcherOptions, this.options, this.maybeClient);