import { BotLogger , loggerWrapper , waitUntil , parsePrivateConfigFileName } from '../Utils' ; import { HostingClientBase } from './HostingClientBase' ; import { HostingConfigBase } from './HostingConfigBase' ; import { Application } from 'egg' ; import { join } from 'path' ; import { ComponentService } from './ComponentService' ; import { HostingPlatformComponentInitedEvent , HostingPlatformInitRepoEvent , HostingPlatformRepoRemovedEvent , HostingPlatformTypes , HostingPlatformRepoAddedEvent , HostingPlatformUninstallEvent , HostingPlatformSyncDataEvent , HostingClientSyncDataEvent , HostingClientOnConfigFileChangedEvent , } from './event' ; import watch from 'node-watch' ; import { statSync } from 'fs' ; export abstract class HostingBase < TConfig extends HostingConfigBase , TClient extends HostingClientBase < TConfig , TRawClient > , TRawClient > { protected app : Application ; protected logger : BotLogger ; protected id : number ; protected name : string ; protected hostingType : HostingPlatformTypes ; protected clientMap : Map < string , ( ) => Promise < TClient > > ; protected config : TConfig ; public compService : ComponentService ; constructor ( hostingType , id , config , app ) { this . id = id ; this . config = config ; this . name = config . name ; this . hostingType = hostingType ; this . app = app ; this . logger = loggerWrapper ( app . logger , `[host-${ this . name }]` ) ; this . clientMap = new Map < string , ( ) => Promise < TClient > > ( ) ; this . compService = new ComponentService ( this . name , config . component , app ) ; this . initWebhook ( config ) ; this . onStart ( ) ; } public abstract async getInstalledRepos ( ) ; public abstract async addRepo ( name , payload ) ; protected abstract async initWebhook ( config ) ; public async onStart ( ) { this . app . event . subscribeAll ( HostingPlatformInitRepoEvent , async ( e ) => { if ( e . id === this . id ) this . addRepo ( e . fullName , e . payload ) ; } ) ; this . app . event . subscribeOne ( HostingPlatformSyncDataEvent , async ( e ) => { if ( e . id === this . id ) this . syncData ( ) ; } ) ; this . app . event . subscribeAll ( HostingPlatformComponentInitedEvent , async ( e ) => { if ( e . id === this . id ) this . compService . setComponents ( e . components ) ; } ) ; this . app . event . subscribeOne ( HostingPlatformRepoAddedEvent , async ( e ) => { if ( e . id !== this . id ) return ; await waitUntil ( ( ) => this . clientMap . has ( e . fullName ) , { interval : 500 } ) ; const client = await this . getClient ( e . fullName ) ; if ( client ) { await waitUntil ( ( ) => client . getStarted ( ) , { interval : 500 } ) ; this . app . event . publish ( 'worker' , HostingClientSyncDataEvent , { installationId : this . id , fullName : e . fullName , } ) ; } else { this . logger . error ( `Add client error, client ${ e . fullName } is undefined!` ) ; } } ) ; this . app . event . subscribeAll ( HostingPlatformRepoAddedEvent , async ( e ) => { if ( e . id !== this . id ) return ; await waitUntil ( ( ) => this . compService . getComponentLoaded ( ) , { interval : 500 } ) ; this . addRepo ( e . fullName , e . payload ) ; } ) ; this . app . event . subscribeAll ( HostingPlatformRepoRemovedEvent , async ( e ) => { if ( e . id !== this . id ) return ; const client = await this . getClient ( e . fullName ) ; if ( client ) { await client . onDispose ( ) ; this . clientMap . delete ( e . fullName ) ; } } ) ; this . app . event . subscribeAll ( HostingPlatformUninstallEvent , async ( e ) => { if ( e . id !== this . id || ! e . owner ) return ; this . clientMap . forEach ( async ( _ , fullName ) => { const client = await this . getClient ( fullName ) ; if ( client && client . getOwner ( ) === e . owner ) { await client . onDispose ( ) ; this . clientMap . delete ( fullName ) ; } } ) ; } ) ; if ( this . config . config . private . file ) { this . logger . info ( 'Start to watch file config' ) ; const options = { recursive : true , filter : /\.json$/ } ; watch ( this . config . config . private . file . rootPath , options , async ( event , file ) => { if ( event === 'update' || event === 'remove' ) { const basePathInfo = statSync ( file ) ; if ( ! basePathInfo . isDirectory ( ) ) { const fullName = parsePrivateConfigFileName ( file ) ; this . app . event . publish ( 'worker' , HostingClientOnConfigFileChangedEvent , { installationId : this . id , fullName , option : event , } ) ; } } } ) ; } } private async syncData ( ) { this . logger . info ( 'Start to load components' ) ; const components = await this . compService . loadComponents ( ) ; if ( this . compService . getComponentLoaded ( ) ) { this . app . event . publish ( 'all' , HostingPlatformComponentInitedEvent , { id : this . id , components , } ) ; } else { this . logger . error ( 'Sync hosting base components data error!' ) ; return ; } this . logger . info ( 'Start to load and sync repos' ) ; const repos = await this . getInstalledRepos ( ) ; this . logger . info ( `All installed repos loaded for hosting name=${ this . name }, count=${ repos . length }` ) ; repos . forEach ( async ( repo ) => { const fullName = repo . fullName ; this . app . event . publish ( 'all' , HostingPlatformInitRepoEvent , { id : this . id , fullName , ... repo , } ) ; await waitUntil ( ( ) => this . clientMap . has ( fullName ) , { interval : 500 } ) ; const client = await this . getClient ( fullName ) ; if ( client ) { await waitUntil ( ( ) => client . getStarted ( ) , { interval : 500 } ) ; this . app . event . publish ( 'worker' , HostingClientSyncDataEvent , { installationId : this . id , fullName , } ) ; } else { this . logger . error ( `Add client error, client ${ fullName } is undefined!` ) ; } } ) ; } public async onDispose ( ) { this . clientMap . forEach ( async ( _ , fullName ) => { const client = await this . getClient ( fullName ) ; if ( client ) client . onDispose ( ) ; } ) ; } public async getClient ( name ) { const gen = this . clientMap . get ( name ) ; if ( gen ) { return await gen ( ) ; } return undefined ; } public getName ( ) { return this . name ; } public getConfig ( ) { return this . config ; } protected post ( path , middleware ) { const p = join ( this . id . toString ( ) , path ) ; this . app . installation . post ( p , middleware ) ; return join ( 'installation' , p ) ; } }