import * as fse from 'fs-extra' ; import * as path from 'path' ; import { Disposable , env } from 'vscode' ; import { IActionContext } from 'vscode-azureextensionui' ; import { ProjectLanguage } from '../constants' ; import { NotImplementedError } from '../errors' ; import { ext } from '../extensionVariables' ; import { FuncVersion } from '../FuncVersion' ; import { IBindingTemplate } from './IBindingTemplate' ; import { IFunctionTemplate } from './IFunctionTemplate' ; import { ITemplates } from './ITemplates' ; export enum TemplateType { Script = 'Script' , ScriptBundle = 'ScriptBundle' , Dotnet = '.NET' , Java = 'Java' } export abstract class TemplateProviderBase implements Disposable { protected static templateVersionCacheKey : string = 'templateVersion' ; protected static projTemplateKeyCacheKey : string = 'projectTemplateKey' ; public abstract templateType : TemplateType ; public readonly version : FuncVersion ; public readonly language : ProjectLanguage ; public readonly projectPath : string | undefined ; public resourcesLanguage : string | undefined ; /**
     * Indicates a related setting/file changed, so we should refresh the worker runtime key next time we get templates
     * We want to delay reading those files until necessary for performance reasons, hence "may have"
     */ private _projKeyMayHaveChanged : boolean ; /**
     * The project key cached for this session of VS Code, purely meant for performance (since we don't want to read the file system to get detect the proj key every time)
     * NOTE: Not using "cache" in the name because all other "cache" properties/methods are related to the global state cache we use accross sessions
     */ protected _sessionProjKey : string | undefined ; protected abstract backupSubpath : string ; protected _disposables : Disposable [ ] = [ ] ; public constructor ( version , projectPath , language , projectTemplateKey ) { this . version = version ; this . projectPath = projectPath ; this . language = language ; this . _sessionProjKey = projectTemplateKey ; } public dispose ( ) { Disposable . from ( ... this . _disposables ) . dispose ( ) ; } protected async updateCachedValue ( key , value ) { await ext . context . globalState . update ( await this . getCacheKey ( key ) , value ) ; } protected async deleteCachedValue ( key ) { await ext . context . globalState . update ( await this . getCacheKey ( key ) , undefined ) ; } protected async getCachedValue < T > ( key ) { return ext . context . globalState . get < T > ( await this . getCacheKey ( key ) ) ; } public abstract getLatestTemplateVersion ( context ) ; public abstract getLatestTemplates ( context , latestTemplateVersion ) ; public abstract getCachedTemplates ( context ) ; public abstract getBackupTemplates ( context ) ; public abstract cacheTemplates ( context ) ; public abstract clearCachedTemplates ( context ) ; public abstract updateBackupTemplates ( context ) ; /**
     * Unless this is overidden, all templates will be included
     */ public includeTemplate ( _template ) { return true ; } public async getCachedTemplateVersion ( ) { return this . getCachedValue ( TemplateProviderBase . templateVersionCacheKey ) ; } public async cacheTemplateMetadata ( templateVersion ) { await this . updateCachedValue ( TemplateProviderBase . templateVersionCacheKey , templateVersion ) ; await this . updateCachedValue ( TemplateProviderBase . projTemplateKeyCacheKey , this . _sessionProjKey ) ; } public async clearCachedTemplateMetadata ( ) { await this . deleteCachedValue ( TemplateProviderBase . templateVersionCacheKey ) ; await this . deleteCachedValue ( TemplateProviderBase . projTemplateKeyCacheKey ) ; } public async getBackupTemplateVersion ( ) { return ( await fse . readFile ( await this . getBackupVersionPath ( ) ) ) . toString ( ) . trim ( ) ; } public async updateBackupTemplateVersion ( version ) { const filePath = await this . getBackupVersionPath ( ) ; await fse . ensureFile ( filePath ) ; await fse . writeFile ( filePath , version ) ; } protected getBackupPath ( ) { return ext . context . asAbsolutePath ( path . join ( 'resources' , 'backupTemplates' , this . backupSubpath ) ) ; } protected async getCacheKeySuffix ( ) { return '' ; } private async getBackupVersionPath ( ) { return path . join ( this . getBackupPath ( ) , 'version.txt' ) ; } /**
     * Adds version, templateType, and language information to a key to ensure there are no collisions in the cache
     * For backwards compatibility, the original version, templateType, and language will not have this information
     */ private async getCacheKey ( key ) { key = key + await this . getCacheKeySuffix ( ) ; if ( this . version !== FuncVersion . v1 ) { key = `${ key }.${ this . version }` ; } if ( this . templateType !== TemplateType . Script ) { key = `${ key }.${ this . templateType }` ; } if ( env . language && ! /^en(-us)?$/i . test ( env . language ) ) { key = `${ key }.${ env . language }` ; } return key ; } /**
     * Returns true if this template provider has project-specific templates
     */ public supportsProjKey ( ) { return ! ! this . refreshProjKey ; } /**
     * Optional method if the provider has project-specific templates
     */ protected refreshProjKey ? ( context ) ; /**
     * A key used to identify the templates for the current type of project
     */ public async getProjKey ( context ) { if ( ! this . refreshProjKey ) { throw new NotImplementedError ( 'refreshProjKey' , this ) ; } if ( ! this . _sessionProjKey ) { this . _sessionProjKey = await this . refreshProjKey ( context ) ; } return this . _sessionProjKey ; } public projKeyMayHaveChanged ( ) { this . _projKeyMayHaveChanged = true ; } /**
     * Returns true if the key changed
     */ public async updateProjKeyIfChanged ( context , projKey ) { let hasChanged ; if ( ! this . refreshProjKey ) { hasChanged = false ; } else if ( projKey ) { hasChanged = this . _sessionProjKey !== projKey ; this . _sessionProjKey = projKey ; } else if ( this . _projKeyMayHaveChanged ) { const latestProjKey = await this . refreshProjKey ( context ) ; hasChanged = this . _sessionProjKey !== latestProjKey ; this . _sessionProjKey = latestProjKey ; } else { hasChanged = false ; } this . _projKeyMayHaveChanged = false ; return hasChanged ; } public async doesCachedProjKeyMatch ( context ) { if ( this . refreshProjKey ) { const projKey = await this . getProjKey ( context ) ; const cachedProjKey = await this . getCachedValue ( TemplateProviderBase . projTemplateKeyCacheKey ) ; return ! cachedProjKey || projKey === cachedProjKey ; } else { return true ; } } }