diff --git a/.gitignore b/.gitignore index 820bf85c1e..88cffef366 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules yarn-error.log .DS_Store +.idea diff --git a/browser-app/package.json b/browser-app/package.json new file mode 100644 index 0000000000..7e946fa14a --- /dev/null +++ b/browser-app/package.json @@ -0,0 +1,7 @@ +{ + "name": "$name$", + "version": "$version$", + "dependencies": { + $END$ + } +} diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-api-provider.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-api-provider.ts index 1e50554079..6d8aa82317 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-api-provider.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-api-provider.ts @@ -13,6 +13,7 @@ import { injectable, interfaces } from 'inversify'; import { PLUGIN_RPC_CONTEXT } from '../common/che-protocol'; import { CheApiMainImpl } from './che-api-main-impl'; import { CheVariablesMainImpl } from './che-variables-main'; +import { CheTaskMainImpl } from './che-task-main'; @injectable() export class CheApiProvider implements MainPluginApiProvider { @@ -20,6 +21,7 @@ export class CheApiProvider implements MainPluginApiProvider { initialize(rpc: RPCProtocol, container: interfaces.Container): void { rpc.set(PLUGIN_RPC_CONTEXT.CHE_API_MAIN, new CheApiMainImpl(container)); rpc.set(PLUGIN_RPC_CONTEXT.CHE_VARIABLES_MAIN, new CheVariablesMainImpl(container, rpc)); + rpc.set(PLUGIN_RPC_CONTEXT.CHE_TASK_MAIN, new CheTaskMainImpl(container, rpc)); } } diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-frontend-module.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-frontend-module.ts index 4f89493d9f..4d16b05776 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-frontend-module.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-frontend-module.ts @@ -11,8 +11,15 @@ import { ContainerModule } from 'inversify'; import { MainPluginApiProvider } from '@theia/plugin-ext/lib/common/plugin-ext-api-contribution'; import { CheApiProvider } from './che-api-provider'; -import { CheApiService, CHE_API_SERVICE_PATH } from '../common/che-protocol'; +import { + CHE_API_SERVICE_PATH, + CHE_TASK_SERVICE_PATH, + CheApiService, + CheTaskClient, + CheTaskService +} from '../common/che-protocol'; import { WebSocketConnectionProvider } from '@theia/core/lib/browser'; +import { CheTaskClientImpl } from './che-task-client'; export default new ContainerModule(bind => { bind(CheApiProvider).toSelf().inSingletonScope(); @@ -22,4 +29,11 @@ export default new ContainerModule(bind => { const provider = ctx.container.get(WebSocketConnectionProvider); return provider.createProxy(CHE_API_SERVICE_PATH); }).inSingletonScope(); + + bind(CheTaskClient).to(CheTaskClientImpl).inSingletonScope(); + bind(CheTaskService).toDynamicValue(ctx => { + const provider = ctx.container.get(WebSocketConnectionProvider); + const client: CheTaskClient = ctx.container.get(CheTaskClient); + return provider.createProxy(CHE_TASK_SERVICE_PATH, client); + }).inSingletonScope(); }); diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-client.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-client.ts new file mode 100644 index 0000000000..43380e50a8 --- /dev/null +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-client.ts @@ -0,0 +1,51 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +import { CheTaskClient } from '../common/che-protocol'; +import { Emitter, Event } from '@theia/core'; +import { injectable } from 'inversify'; +import { TaskConfiguration, TaskInfo } from '@eclipse-che/plugin'; + +@injectable() +export class CheTaskClientImpl implements CheTaskClient { + private readonly onKillEventEmitter: Emitter; + private taskInfoHandler: ((id: number) => Promise) | undefined; + private runTaskHandler: ((id: number, config: TaskConfiguration, ctx?: string) => Promise) | undefined; + constructor() { + this.onKillEventEmitter = new Emitter(); + } + + async runTask(id: number, taskConfig: TaskConfiguration, ctx?: string): Promise { + if (this.runTaskHandler) { + return await this.runTaskHandler(id, taskConfig, ctx); + } + } + + async getTaskInfo(id: number): Promise { + if (this.taskInfoHandler) { + return await this.taskInfoHandler(id); + } + } + + get onKillEvent(): Event { + return this.onKillEventEmitter.event; + } + + async killTask(id: number): Promise { + this.onKillEventEmitter.fire(id); + } + + setTaskInfoHandler(handler: (id: number) => Promise) { + this.taskInfoHandler = handler; + } + + setRunTaskHandler(handler: (id: number, config: TaskConfiguration, ctx?: string) => Promise) { + this.runTaskHandler = handler; + } +} diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-main.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-main.ts new file mode 100644 index 0000000000..a1105278ae --- /dev/null +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-main.ts @@ -0,0 +1,33 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +import { CheTask, CheTaskMain, CheTaskService, CheTaskClient, PLUGIN_RPC_CONTEXT } from '../common/che-protocol'; +import { RPCProtocol } from '@theia/plugin-ext/lib/api/rpc-protocol'; +import { interfaces, injectable } from 'inversify'; + +@injectable() +export class CheTaskMainImpl implements CheTaskMain{ + private readonly delegate: CheTaskService; + private readonly cheTaskClient: CheTaskClient; + constructor(container: interfaces.Container, rpc: RPCProtocol) { + const proxy: CheTask = rpc.getProxy(PLUGIN_RPC_CONTEXT.CHE_TASK); + this.delegate = container.get(CheTaskService); + this.cheTaskClient = container.get(CheTaskClient); + this.cheTaskClient.onKillEvent(id => proxy.$killTask(id)); + this.cheTaskClient.setTaskInfoHandler(id => proxy.$getTaskInfo(id)); + this.cheTaskClient.setRunTaskHandler((id, config, ctx) => proxy.$runTask(id, config, ctx)); + } + $registerTaskRunner(type: string): Promise { + return this.delegate.registerTaskRunner(type); + } + + $disposeTaskRunner(type: string): Promise { + return this.delegate.disposeTaskRunner(type); + } +} diff --git a/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts b/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts index e78b6b70d5..277078856f 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts @@ -10,6 +10,7 @@ import { ProxyIdentifier, createProxyIdentifier } from '@theia/plugin-ext/lib/api/rpc-protocol'; import * as che from '@eclipse-che/plugin'; +import { Event, JsonRpcServer } from '@theia/core'; export interface CheApiPlugin { } @@ -31,6 +32,21 @@ export interface CheVariablesMain { $resolve(value: string): Promise; } +export interface CheTask { + registerTaskRunner(type: string, runner: che.TaskRunner): Promise; + fireTaskExited(id: number): Promise; + $runTask(id: number, config: che.TaskConfiguration, ctx?: string): Promise; + $killTask(id: number): Promise; + $killTask(id: number): Promise; + $getTaskInfo(id: number): Promise; +} + +export const CheTaskMain = Symbol('CheTaskMain'); +export interface CheTaskMain { + $registerTaskRunner(type: string): Promise; + $disposeTaskRunner(type: string): Promise; +} + export interface Variable { name: string, description: string, @@ -285,6 +301,8 @@ export const PLUGIN_RPC_CONTEXT = { CHE_API_MAIN: >createProxyIdentifier('CheApiMain'), CHE_VARIABLES: >createProxyIdentifier('CheVariables'), CHE_VARIABLES_MAIN: >createProxyIdentifier('CheVariablesMain'), + CHE_TASK: >createProxyIdentifier('CheTask'), + CHE_TASK_MAIN: >createProxyIdentifier('CheTaskMain'), }; // Theia RPC protocol @@ -300,3 +318,22 @@ export interface CheApiService { getFactoryById(factoryId: string): Promise; } + +export const CHE_TASK_SERVICE_PATH = '/che-task-service'; + +export const CheTaskService = Symbol('CheTaskService'); +export interface CheTaskService extends JsonRpcServer{ + registerTaskRunner(type: string): Promise; + disposeTaskRunner(type: string): Promise; + disconnectClient(client: CheTaskClient): void; +} + +export const CheTaskClient = Symbol('CheTaskClient'); +export interface CheTaskClient { + runTask(id: number, taskConfig: che.TaskConfiguration, ctx?: string): Promise; + killTask(id: number): Promise; + getTaskInfo(id: number): Promise; + setTaskInfoHandler(func: (id: number) => Promise): void; + setRunTaskHandler(func: (id: number, config: che.TaskConfiguration, ctx?: string) => Promise): void; + onKillEvent: Event +} diff --git a/extensions/eclipse-che-theia-plugin-ext/src/node/che-backend-module.ts b/extensions/eclipse-che-theia-plugin-ext/src/node/che-backend-module.ts index 12fe3ad32f..e25948388a 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/node/che-backend-module.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/node/che-backend-module.ts @@ -15,7 +15,14 @@ import { ChePluginApiContribution } from './che-plugin-script-service'; import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application'; import { ConnectionHandler, JsonRpcConnectionHandler } from '@theia/core'; import { CheApiServiceImpl } from './che-api-service'; -import { CheApiService, CHE_API_SERVICE_PATH } from '../common/che-protocol'; +import { + CHE_API_SERVICE_PATH, + CHE_TASK_SERVICE_PATH, + CheApiService, + CheTaskClient, + CheTaskService +} from '../common/che-protocol'; +import {CheTaskServiceImpl} from "./che-task-service"; export default new ContainerModule(bind => { bind(ChePluginApiProvider).toSelf().inSingletonScope(); @@ -30,4 +37,15 @@ export default new ContainerModule(bind => { ctx.container.get(CheApiService) ) ).inSingletonScope(); + + bind(CheTaskService).toDynamicValue(ctx => new CheTaskServiceImpl(ctx.container)).inSingletonScope(); + bind(ConnectionHandler).toDynamicValue(ctx => + new JsonRpcConnectionHandler(CHE_TASK_SERVICE_PATH, client => { + const server: CheTaskService = ctx.container.get(CheTaskService); + server.setClient(client); + client.onDidCloseConnection(() => server.disconnectClient(client)); + return server; + } + ) + ).inSingletonScope(); }); diff --git a/extensions/eclipse-che-theia-plugin-ext/src/node/che-task-service.ts b/extensions/eclipse-che-theia-plugin-ext/src/node/che-task-service.ts new file mode 100644 index 0000000000..c38ee1e62c --- /dev/null +++ b/extensions/eclipse-che-theia-plugin-ext/src/node/che-task-service.ts @@ -0,0 +1,102 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +import { CheTaskClient, CheTaskService } from '../common/che-protocol'; +import { injectable, interfaces } from 'inversify'; +import { Task, TaskManager, TaskOptions, TaskRunnerRegistry } from '@theia/task/lib/node' +import { Disposable, ILogger } from '@theia/core'; +import { TaskConfiguration, TaskInfo } from '@theia/task/lib/common/task-protocol'; + +@injectable() +export class CheTaskServiceImpl implements CheTaskService { + private readonly runnerRegistry: TaskRunnerRegistry; + private readonly taskManager: TaskManager; + private readonly logger: ILogger; + private readonly disposableMap: Map; + private readonly clients: CheTaskClient[]; + private taskId: number; + constructor(container: interfaces.Container) { + this.runnerRegistry = container.get(TaskRunnerRegistry); + this.taskManager = container.get(TaskManager); + this.logger = container.get(ILogger); + this.disposableMap = new Map(); + this.clients = []; + this.taskId = 0; + } + + async registerTaskRunner(type: string): Promise { + const runner = { + async run(taskConfig: TaskConfiguration, ctx?: string): Promise { + return runTask(taskConfig, ctx); + } + }; + this.disposableMap.set(type, this.runnerRegistry.registerRunner(type, runner)); + const runTask = async (config: TaskConfiguration, ctx?: string): Promise => { + const id = this.taskId ++; + for (const client of this.clients) { + await client.runTask(id, config, ctx); + } + return new CheTask(id, this.taskManager, this.logger, { label: config.label, config }, this.clients); + }; + } + + dispose() { + // do nothing + } + + setClient(client: CheTaskClient ){ + this.clients.push(client); + } + + async disposeTaskRunner(type: string): Promise { + const disposable = this.disposableMap.get(type); + if (disposable) { + disposable.dispose(); + } + } + + async disconnectClient(client: CheTaskClient) { + const idx = this.clients.indexOf(client); + if (idx > -1) { + this.clients.splice(idx, 1); + } + } +} + +class CheTask extends Task { + private readonly clients: CheTaskClient[]; + constructor(id: number, + taskManager: TaskManager, + logger: ILogger, + options: TaskOptions, + clients: CheTaskClient[]) { + super(taskManager, logger, options); + this.clients = clients; + this.taskId = id; + } + + async getRuntimeInfo(): Promise { + for (const client of this.clients) { + const taskInfo = await client.getTaskInfo(this.taskId); + if (taskInfo) { + return { + taskId: this.taskId, + terminalId: taskInfo.terminalId, + ctx: taskInfo.ctx, + config: taskInfo.config + }; + } + } + throw new Error('Information not found'); + } + + async kill(): Promise { + this.clients.forEach(client => client.killTask(this.taskId)); + } +} diff --git a/extensions/eclipse-che-theia-plugin-ext/src/plugin/api-factory.ts b/extensions/eclipse-che-theia-plugin-ext/src/plugin/api-factory.ts index 12b2501ea7..fc6ca1e7d6 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/plugin/api-factory.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/plugin/api-factory.ts @@ -21,6 +21,7 @@ import { import { CheApiPluginImpl } from './che-workspace'; import { CheVariablesImpl } from './che-variables-impl'; import { PLUGIN_RPC_CONTEXT } from '../common/che-protocol'; +import { CheTaskImpl } from './che-task-impl'; export interface ApiFactory { (plugin: Plugin): typeof che; } @@ -28,6 +29,7 @@ export interface ApiFactory { export function createAPIFactory(rpc: RPCProtocol): ApiFactory { const chePluginImpl = new CheApiPluginImpl(rpc); const cheVariablesImpl = rpc.set(PLUGIN_RPC_CONTEXT.CHE_VARIABLES, new CheVariablesImpl(rpc)); + const cheTaskImpl = rpc.set(PLUGIN_RPC_CONTEXT.CHE_TASK, new CheTaskImpl(rpc)); return function (plugin: Plugin): typeof che { const ws: typeof che.workspace = { @@ -82,10 +84,20 @@ export function createAPIFactory(rpc: RPCProtocol): ApiFactory { } }; + const task: typeof che.task = { + registerTaskRunner(type: string, runner: che.TaskRunner): Promise { + return cheTaskImpl.registerTaskRunner(type, runner); + }, + fireTaskExited(id: number): Promise { + return cheTaskImpl.fireTaskExited(id); + } + }; + return { workspace: ws, factory, - variables: variable + variables: variable, + task }; }; } diff --git a/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-task-impl.ts b/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-task-impl.ts new file mode 100644 index 0000000000..35348ba98b --- /dev/null +++ b/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-task-impl.ts @@ -0,0 +1,61 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +import { CheTask, CheTaskMain, PLUGIN_RPC_CONTEXT } from '../common/che-protocol'; +import { TaskRunner, Disposable, Task, TaskInfo } from '@eclipse-che/plugin'; +import { RPCProtocol } from '@theia/plugin-ext/lib/api/rpc-protocol'; +import { TaskConfiguration } from '@theia/task/lib/common'; + +export class CheTaskImpl implements CheTask { + private readonly cheTaskMain: CheTaskMain; + private readonly runnerMap: Map; + private readonly taskMap: Map; + constructor(rpc: RPCProtocol) { + this.cheTaskMain = rpc.getProxy(PLUGIN_RPC_CONTEXT.CHE_TASK_MAIN); + this.runnerMap = new Map(); + this.taskMap = new Map(); + } + async registerTaskRunner(type: string, runner: TaskRunner): Promise { + this.runnerMap.set(type, runner); + await this.cheTaskMain.$registerTaskRunner(type); + return { + dispose: async () => { + await this.cheTaskMain.$disposeTaskRunner(type); + } + } + } + + async $runTask(id: number, config: TaskConfiguration, ctx?: string): Promise { + const runner = this.runnerMap.get(config.type); + if (runner) { + const task = await runner.run(config, ctx); + this.taskMap.set(id, task); + } + } + + async $killTask(id: number): Promise { + const task = this.taskMap.get(id); + if (task) { + await task.kill(); + this.taskMap.delete(id); + } + } + + async $getTaskInfo(id: number): Promise { + const task = this.taskMap.get(id); + console.log(id, this.taskMap); + if (task) { + return task.getRuntimeInfo(); + } + } + + async fireTaskExited(id: number): Promise { + this.taskMap.delete(id); + } +} diff --git a/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts b/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts index 1167d1198a..191060bdea 100644 --- a/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts +++ b/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts @@ -335,4 +335,42 @@ declare module '@eclipse-che/plugin' { dispose(): PromiseLike; } + export namespace task { + export function registerTaskRunner(type: string, runner: TaskRunner): Promise; + export function fireTaskExited(id: number): Promise; + } + + /** A Task Runner knows how to run a Task of a particular type. */ + export interface TaskRunner { + /** Runs a task based on the given task configuration. */ + run(taskConfig: TaskConfiguration, ctx?: string): Promise; + } + + export interface Task { + /** Terminates the task. */ + kill(): Promise; + /** Returns runtime information about task. */ + getRuntimeInfo(): TaskInfo; + } + + /** Runtime information about Task. */ + export interface TaskInfo { + /** internal unique task id */ + readonly taskId: number, + /** terminal id. Defined if task is run as a terminal process */ + readonly terminalId?: number, + /** context that was passed as part of task creation, if any */ + readonly ctx?: string, + /** task config used for launching a task */ + readonly config: TaskConfiguration + } + + export interface TaskConfiguration { + readonly type: string; + /** A label that uniquely identifies a task configuration */ + readonly label: string; + + /** Additional task type specific properties. */ + readonly [key: string]: string; + } }