// ***************************************************************************** // Copyright (C) 2017 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 // ***************************************************************************** import * as path from 'path'; import { ContainerModule, interfaces } from '@theia/core/shared/inversify'; import { ConnectionHandler, RpcConnectionHandler, ILogger } from '@theia/core/lib/common'; import { FileSystemWatcherServer, FileSystemWatcherService } from '../common/filesystem-watcher-protocol'; import { FileSystemWatcherServerClient } from './filesystem-watcher-client'; import { ParcelFileSystemWatcherService, ParcelFileSystemWatcherServerOptions } from './parcel-watcher/parcel-filesystem-service'; import { NodeFileUploadService } from './upload/node-file-upload-service'; import { ParcelWatcherOptions } from './parcel-watcher/parcel-options'; import { DiskFileSystemProvider } from './disk-file-system-provider'; import { remoteFileSystemPath, RemoteFileSystemServer, RemoteFileSystemClient, FileSystemProviderServer, RemoteFileSystemProxyFactory } from '../common/remote-file-system-provider'; import { FileSystemProvider } from '../common/files'; import { EncodingService } from '@theia/core/lib/common/encoding-service'; import { BackendApplicationContribution, IPCConnectionProvider } from '@theia/core/lib/node'; import { RpcProxyFactory, ConnectionErrorHandler } from '@theia/core'; import { FileSystemWatcherServiceDispatcher } from './filesystem-watcher-dispatcher'; import { bindFileSystemPreferences } from '../common'; export const WATCHER_SINGLE_THREADED = process.argv.includes('--no-cluster'); export const WATCHER_VERBOSE = process.argv.includes('--watcher-verbose'); export const FileSystemWatcherServiceProcessOptions = Symbol('FileSystemWatcherServiceProcessOptions'); /** * Options to control the way the `ParcelFileSystemWatcherService` process is spawned. */ export interface FileSystemWatcherServiceProcessOptions { /** * Path to the script that will run the `ParcelFileSystemWatcherService` in a new process. */ entryPoint: string; } export default new ContainerModule(bind => { bind(EncodingService).toSelf().inSingletonScope(); bindFileSystemWatcherServer(bind); bind(DiskFileSystemProvider).toSelf(); bind(FileSystemProvider).toService(DiskFileSystemProvider); bind(FileSystemProviderServer).toSelf(); bind(RemoteFileSystemServer).toService(FileSystemProviderServer); bind(ConnectionHandler).toDynamicValue(ctx => new RpcConnectionHandler(remoteFileSystemPath, client => { const server = ctx.container.get(RemoteFileSystemServer); server.setClient(client); client.onDidCloseConnection(() => server.dispose()); return server; }, RemoteFileSystemProxyFactory) ).inSingletonScope(); bind(NodeFileUploadService).toSelf().inSingletonScope(); bind(BackendApplicationContribution).toService(NodeFileUploadService); bindFileSystemPreferences(bind); }); export function bindFileSystemWatcherServer(bind: interfaces.Bind): void { bind(ParcelWatcherOptions).toConstantValue({}); bind(FileSystemWatcherServiceDispatcher).toSelf().inSingletonScope(); bind(FileSystemWatcherServerClient).toSelf(); bind(FileSystemWatcherServer).toService(FileSystemWatcherServerClient); bind(FileSystemWatcherServiceProcessOptions).toDynamicValue(ctx => ({ entryPoint: path.join(__dirname, 'parcel-watcher'), })).inSingletonScope(); bind(ParcelFileSystemWatcherServerOptions).toDynamicValue(ctx => { const logger = ctx.container.get(ILogger); const watcherOptions = ctx.container.get(ParcelWatcherOptions); return { parcelOptions: watcherOptions, verbose: WATCHER_VERBOSE, info: (message, ...args) => logger.info(message, ...args), error: (message, ...args) => logger.error(message, ...args), }; }).inSingletonScope(); bind(FileSystemWatcherService).toDynamicValue( ctx => WATCHER_SINGLE_THREADED ? createParcelFileSystemWatcherService(ctx) : spawnParcelFileSystemWatcherServiceProcess(ctx) ).inSingletonScope(); } /** * Run the watch server in the current process. */ export function createParcelFileSystemWatcherService(ctx: interfaces.Context): FileSystemWatcherService { const options = ctx.container.get(ParcelFileSystemWatcherServerOptions); const dispatcher = ctx.container.get(FileSystemWatcherServiceDispatcher); const server = new ParcelFileSystemWatcherService(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 spawnParcelFileSystemWatcherServiceProcess(ctx: interfaces.Context): FileSystemWatcherService { const options = ctx.container.get(FileSystemWatcherServiceProcessOptions); const dispatcher = ctx.container.get(FileSystemWatcherServiceDispatcher); const serverName = 'parcel-watcher'; const logger = ctx.container.get(ILogger); const watcherOptions = ctx.container.get(ParcelWatcherOptions); const ipcConnectionProvider = ctx.container.get(IPCConnectionProvider); const proxyFactory = new RpcProxyFactory(); 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[] = [ `--watchOptions=${JSON.stringify(watcherOptions)}` ]; if (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; }