'use strict' ; import { ExtensionContext , window , ProgressLocation , workspace , ConfigurationTarget } from 'vscode' ; import { instrumentOperationAsVsCodeCommand } from "vscode-extension-telemetry-wrapper" ; import * as path from 'path' ; import * as os from 'os' ; import { utils } from 'pddl-workspace' ; import { ValDownloader as ValDownloaderBase , ValVersion , readValManifest , writeValManifest } from 'ai-planning-val' ; import { PARSER_EXECUTABLE_OR_SERVICE , CONF_PDDL , VALIDATION_PATH , VALUE_SEQ_PATH , VAL_STEP_PATH , VALIDATOR_VERSION } from '../configuration/configuration' ; import { VAL_DOWNLOAD_COMMAND , ValDownloadOptions } from './valCommand' ; import { ensureAbsoluteGlobalStoragePath } from '../utils' ; export class ValDownloader extends ValDownloaderBase { /** Directory where VAL binaries are to be downloaded locally. */ static readonly VAL_DIR = "val" ; /** File that contains version info of last downloaded VAL binaries. */ private valVersionPath : string ; private readonly binaryStorage : string ; constructor ( private context ) { super ( ) ; this . binaryStorage = this . context . globalStoragePath ; this . valVersionPath = path . join ( this . getValPath ( ) , "VAL.version" ) ; } registerCommands ( ) { this . context . subscriptions . push ( instrumentOperationAsVsCodeCommand ( VAL_DOWNLOAD_COMMAND , async ( options ) => { try { const userAgreesToDownload = options ?. bypassConsent ?? await this . promptForConsent ( ) ; if ( ! userAgreesToDownload ) { return ; } await this . downloadConfigureAndCleanUp ( ) ; } catch ( ex ) { const error = ex as Error ; window . showErrorMessage ( error . message ?? "" + error ) ; } } ) ) ; return this ; } protected async downloadDelegate ( url , zipPath , message ) { return await window . withProgress ( { location : ProgressLocation . Window , title : message } , ( ) => { return super . downloadDelegate ( url , zipPath , message ) ; } ) ; } /** Directory where VAL binaries are to be downloaded locally. */ getValPath ( ) { return path . join ( this . binaryStorage , ValDownloader . VAL_DIR ) ; } private asAbsoluteStoragePath ( relativePath ) { return path . join ( this . binaryStorage , relativePath ) ; } private async promptForConsent ( ) { const download = "Download" ; const message = `Please confirm to download [VAL tools](${ ValDownloader . VAL_REPO }) build from ${ ValDownloader . VAL_BINARY_PROJECT }?` ; const answer = await window . showInformationMessage ( message , { modal : true } , download ) ; return answer === download ; } async downloadConfigureAndCleanUp ( ) { const wasValInstalled = await this . isInstalled ( ) ; const previousVersion = wasValInstalled ? await this . readVersion ( ) : undefined ; let newVersion ; try { newVersion = await this . downloadAndConfigure ( ) ; } finally { if ( wasValInstalled && previousVersion && newVersion ) { if ( previousVersion . buildId !== newVersion . buildId ) { console . log ( `The ${ previousVersion . version } and the ${ newVersion . version } differ, cleaning-up the old version.` ) ; const filesAbsPaths = previousVersion . files . map ( f => this . asAbsoluteStoragePath ( path . join ( ValDownloader . VAL_DIR , f ) ) ) ; await ValDownloader . deleteAll ( filesAbsPaths ) ; } } } return newVersion ; } protected getLatestStableValBuildId ( ) { return workspace . getConfiguration ( CONF_PDDL ) . get < number > ( VALIDATOR_VERSION ) ?? - 1 ; } private async downloadAndConfigure ( ) { const buildId = this . getLatestStableValBuildId ( ) ; await utils . afs . mkdirIfDoesNotExist ( this . binaryStorage , 0o755 ) ; const newValVersion = await this . download ( buildId , this . getValPath ( ) ) ; const wasValInstalled = await this . isInstalled ( ) ; const previousVersion = wasValInstalled ? await this . readVersion ( ) : undefined ; await this . writeVersion ( newValVersion ) ; await this . updateConfigurationPaths ( newValVersion , previousVersion ) ; return newValVersion ; } async isInstalled ( ) { return await utils . afs . exists ( this . valVersionPath ) ; } private async readVersion ( ) { return readValManifest ( this . valVersionPath ) ; } private async writeVersion ( valVersion ) { return writeValManifest ( this . valVersionPath , valVersion ) ; } param newValVersion param oldValVersion private async updateConfigurationPaths ( newValVersion , oldValVersion ? ) { const fileToConfig = new Map < string , string > ( ) ; fileToConfig . set ( "Parser" , PARSER_EXECUTABLE_OR_SERVICE ) ; fileToConfig . set ( "Validate" , CONF_PDDL + '.' + VALIDATION_PATH ) ; fileToConfig . set ( "ValueSeq" , CONF_PDDL + '.' + VALUE_SEQ_PATH ) ; fileToConfig . set ( "ValStep" , CONF_PDDL + '.' + VAL_STEP_PATH ) ; for ( const toolName of fileToConfig . keys ( ) ) { const oldToolPath = findValToolPath ( oldValVersion , toolName ) ; const newToolPath = findValToolPath ( newValVersion , toolName ) ; if ( ! newToolPath ) { console . log ( `Tool ${ toolName } not found in the downloaded archive.` ) ; return ; } const oldToolAbsPath = oldToolPath && this . asAbsoluteStoragePath ( path . join ( ValDownloader . VAL_DIR , oldToolPath ) ) ; const newToolAbsPath = this . asAbsoluteStoragePath ( path . join ( ValDownloader . VAL_DIR , newToolPath ) ) ; const configKey = fileToConfig . get ( toolName ) ; if ( configKey && newToolPath ) { await this . updateConfigurationPath ( toolName , configKey , newToolAbsPath , oldToolAbsPath ) ; } } } param toolName param configKey param newToolPath param prevDownloadToolPath private async updateConfigurationPath ( toolName , configKey , newToolPath , prevDownloadToolPath ? ) { const configurationInspect = workspace . getConfiguration ( ) . inspect < string > ( configKey ) ; if ( ! configurationInspect ) { console . log ( "configuration not declared: " + configKey ) ; return ; } const normConfiguredGlobalValue = this . normalizePathIfValid ( ensureAbsoluteGlobalStoragePath ( configurationInspect . globalValue , this . context ) ) ; const normPrevDownloadToolPath = this . normalizePathIfValid ( prevDownloadToolPath ) ; if ( normConfiguredGlobalValue === undefined || normConfiguredGlobalValue === normPrevDownloadToolPath || await this . shouldOverwrite ( toolName , normConfiguredGlobalValue , newToolPath ) ) { return await workspace . getConfiguration ( ) . update ( configKey , newToolPath , ConfigurationTarget . Global ) ; } } protected async shouldOverwrite ( toolName , yourConfiguredPath , newToolPath ) { const overwrite = "Overwrite" ; const message = `VAL tool '${ toolName }' was already configured:\n\nYour configuration: ${ yourConfiguredPath }\n\nJust downloaded: ${ newToolPath }\n\nWish to use the downloaded tool instead?` ; const answer = await window . showInformationMessage ( message , { modal : true } , overwrite , "Keep" ) ; return answer === overwrite ; } private normalizePathIfValid ( pathToNormalize ? ) { if ( ! pathToNormalize ) { return pathToNormalize ; } let normalizedPath = path . normalize ( pathToNormalize ) ; if ( os . platform ( ) === 'darwin' && normalizedPath . includes ( ' ' ) ) { normalizedPath = normalizedPath . replace ( ' ' , '\\ ' ) ; } return normalizedPath ; } async isNewValVersionAvailable ( ) { const isInstalled = await this . isInstalled ( ) ; if ( ! isInstalled ) { return false ; } const latestStableValBuildId = this . getLatestStableValBuildId ( ) ; const installedVersion = await this . readVersion ( ) ; return latestStableValBuildId > installedVersion . buildId ; } } param valVersion param toolName returns function findValToolPath ( valVersion , toolName ) { if ( ! valVersion ) { return undefined ; } const pattern = new RegExp ( "\\b" + toolName + "(?:\\.exe)?$" ) ; return valVersion . files . find ( filePath => pattern . test ( filePath ) ) ; }