'use strict' ; import * as path from 'path' ; import { Disposable , ShellExecution , Task , TaskDefinition , TaskEndEvent , TaskExecution , TaskRevealKind , tasks , TaskScope , Uri , window } from 'vscode' ; import { instanceOfHttpConnectionRefusedError } from 'pddl-planning-service-client' ; import { planner , utils } from 'pddl-workspace' ; import { fileOrFolderExists } from '../utils' ; /** Tracks a long-running planning service execution. */ export class PlanningServiceExecution { constructor ( public readonly servicePath , public readonly wasAlreadyRunning , public readonly execution ? ) { } static alreadyRunning ( servicePath ) { return new PlanningServiceExecution ( servicePath , true ) ; } static justStarted ( servicePath , execution ) { return new PlanningServiceExecution ( servicePath , false , execution ) ; } } export abstract class LongRunningPlannerProvider implements planner . PlannerProvider { abstract kind : planner . PlannerKind ; abstract getNewPlannerLabel ( ) ; abstract configurePlanner ( previousConfiguration ? ) ; private planningServiceExecutions = new Map < string , PlanningServiceExecution > ( ) ; constructor ( disposables ) { tasks . onDidEndTask ( this . handleTaskEnded , this , disposables ) ; } private handleTaskEnded ( event ) { const taskExecution = [ ... this . planningServiceExecutions . values ( ) ] . find ( e => e . execution === event . execution ) ; if ( taskExecution ) { this . planningServiceExecutions . delete ( taskExecution . servicePath ) ; } } param configuration returns public isServiceWasStarted ( configuration ) { return configuration . path !== undefined && this . planningServiceExecutions . has ( configuration . path ) ; } abstract isServiceAccessible ( configuration ) ; param hostname returns static isLocal ( hostname ) { return [ 'localhost' , '127.0.0.1' ] . includes ( hostname ) ; } param failedPlanner param reason returns async troubleshoot ( failedPlanner , reason ) { let troubleShootingInfo = '' ; const troubleShootings = new Map < string , ( p : planner . Planner ) => Promise < void > > ( ) ; if ( instanceOfHttpConnectionRefusedError ( reason ) ) { const configuration = failedPlanner . providerConfiguration . configuration ; troubleShootingInfo += `Service ${ configuration . url } cannot be reached.\n` ; const isLocal = LongRunningPlannerProvider . isLocal ( reason . address ) ; const serviceExePath = configuration . path ; if ( isLocal && serviceExePath !== undefined ) { const fileName = path . basename ( serviceExePath ) ; if ( await fileOrFolderExists ( Uri . file ( serviceExePath ) ) ) { if ( this . isServiceWasStarted ( configuration ) ) { troubleShootingInfo += `Service appears to be running, but not responding. Click 'Re-start the service'.\n` ; troubleShootings . set ( `Re-start the service` , async ( ) => this . startService ( configuration ) ) ; } else { troubleShootingInfo += `Service does not appear to be running. Click 'Start the service' to execute '${ fileName }'.\n` ; troubleShootings . set ( `Start the service` , async ( ) => this . startService ( configuration ) ) ; } } else { troubleShootingInfo += `Service does not appear to be running. The configured server '${ fileName }' is not a valid file.\n` ; } } } return { info : troubleShootingInfo , options : troubleShootings } ; } param configuration async suggestStartService ( configuration ) { const serviceExePath = configuration . path ; if ( ! serviceExePath ) { throw new Error ( `Expected planner configuration with a 'path' specified` ) ; } const fileName = path . basename ( serviceExePath ) ; const startService = 'Start the service' ; const answer = await window . showWarningMessage ( `Service at '${ configuration . url }' does not appear to be running. Click '${ startService }' to execute '${ fileName }'.\n` , startService ) ; if ( answer === startService ) { this . startService ( configuration ) ; } } param configuration public startService ( configuration ) { const executablePath = configuration . path ; if ( ! executablePath ) { throw new Error ( `Expected configuration with attribute: 'path'` ) ; } const taskExecution = new ShellExecution ( utils . Util . q ( executablePath ) , { cwd : configuration . cwd ?? path . isAbsolute ( executablePath ) ? path . dirname ( executablePath ) : undefined } ) ; const task = new Task ( this . taskDefinition , TaskScope . Global , configuration . title , this . taskSource , taskExecution ) ; task . presentationOptions = { reveal : TaskRevealKind . Always , echo : true } ; task . isBackground = true ; tasks . executeTask ( task ) . then ( taskExecution => { this . planningServiceExecutions . set ( executablePath , PlanningServiceExecution . justStarted ( executablePath , taskExecution ) ) ; } ) ; } get taskDefinition ( ) : TaskDefinition { return { type : 'PlanningService:' + this . kind . kind } ; } get taskSource ( ) : string { return 'PDDL' ; } }