feat: split plugin ext stage exec

Signed-off-by: MiaoWoo <admin@yumc.pw>
This commit is contained in:
MiaoWoo 2020-07-06 16:06:47 +08:00
parent 0b0a3bb836
commit 083a01f29f
6 changed files with 167 additions and 148 deletions

View File

@ -152,21 +152,21 @@ export namespace plugin {
} }
export interface PluginMetadata extends BaseMetadata { export interface PluginMetadata extends BaseMetadata {
/** /**
* *
*/ */
name: string name?: string
/** /**
* *
*/ */
prefix?: string prefix?: string
/** /**
* * 1.0.0
*/ */
version: string version?: string
/** /**
* * Unknow
*/ */
author: string | string[] author?: string | string[]
/** /**
* __filename * __filename
*/ */

View File

@ -0,0 +1,36 @@
import { command, plugin, server } from '@ccms/api'
import { provideSingleton, postConstruct, inject } from '@ccms/container'
import { getPluginCommandMetadata, getPluginTabCompleterMetadata } from './utils'
@provideSingleton(PluginCommandManager)
export class PluginCommandManager {
@inject(server.ServerChecker)
private ServerChecker: server.ServerChecker
@inject(command.Command)
private CommandManager: command.Command
constructor() {
process.on('plugin.before.enable', (plugin: plugin.Plugin) => this.registryCommand(plugin))
process.on('plugin.after.disable', (plugin: plugin.Plugin) => this.unregistryCommand(plugin))
}
private registryCommand(pluginInstance: plugin.Plugin) {
let cmds = getPluginCommandMetadata(pluginInstance)
let tabs = getPluginTabCompleterMetadata(pluginInstance)
for (const [_, cmd] of cmds) {
let tab = tabs.get(cmd.name)
if (!this.ServerChecker.check(cmd.servers)) { continue }
this.CommandManager.on(pluginInstance, cmd.name, {
cmd: pluginInstance[cmd.executor].bind(pluginInstance),
tab: tab ? pluginInstance[tab.executor].bind(pluginInstance) : undefined
})
}
}
private unregistryCommand(pluginInstance: plugin.Plugin) {
let cmds = getPluginCommandMetadata(pluginInstance)
cmds.forEach(cmd => {
this.CommandManager.off(pluginInstance, cmd.name)
})
}
}

View File

@ -1,33 +1,75 @@
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import * as fs from '@ccms/common/dist/fs'
import { plugin } from '@ccms/api'
import { getPluginConfigMetadata } from './utils'
export interface PluginConfigLoader { export interface PluginConfigLoader {
load(content: string): any; load(content: string): any
dump(variable: any): string; dump(variable: any): string
} }
export class YamlPluginConfig implements PluginConfigLoader { export class YamlPluginConfig implements PluginConfigLoader {
load(content: string) { load(content: string) {
return yaml.safeLoad(content); return yaml.safeLoad(content)
} }
dump(variable: any): string { dump(variable: any): string {
return yaml.safeDump(variable, { skipInvalid: true }); return yaml.safeDump(variable, { skipInvalid: true })
} }
} }
export class JsonPluginConfig implements PluginConfigLoader { export class JsonPluginConfig implements PluginConfigLoader {
load(content: string) { load(content: string) {
return JSON.parse(content); return JSON.parse(content)
} }
dump(variable: any): string { dump(variable: any): string {
return JSON.stringify(variable); return JSON.stringify(variable)
} }
} }
const configLoaderMap = new Map<string, PluginConfigLoader>(); const configLoaderMap = new Map<string, PluginConfigLoader>()
export function getConfigLoader(format: string) { export function getConfigLoader(format: string) {
if (!configLoaderMap.has(format)) { throw new Error(`Unsupport config format ${format} !`) } if (!configLoaderMap.has(format)) { throw new Error(`Unsupport config format ${format} !`) }
return configLoaderMap.get(format); return configLoaderMap.get(format)
}
function loadConfig(plugin: plugin.Plugin) {
let configs = getPluginConfigMetadata(plugin)
for (let [_, config] of configs) {
try {
let configFile = fs.concat(fs.file(plugin.description.loadMetadata.file).parent, plugin.description.name, config.name + '.' + config.format)
let configFactory = getConfigLoader(config.format)
if (!fs.exists(configFile)) {
base.save(configFile, configFactory.dump(plugin[config.variable]))
console.i18n("ms.plugin.manager.config.save.default", { plugin: plugin.description.name, name: config.name, format: config.format })
} else {
plugin[config.variable] = configFactory.load(base.read(configFile))
plugin[config.variable].save = () => {
let result = configFactory.dump(plugin[config.variable])
base.save(configFile, result)
console.debug(`[${plugin.description.name}] Save Config ${config.variable} to file ${configFile} result ${result}`)
}
console.debug(`[${plugin.description.name}] Load Config ${config.variable} from file ${configFile} result ${JSON.stringify(plugin[config.variable])}`)
}
} catch (error) {
console.i18n("ms.plugin.manager.config.load.error", { plugin: plugin.description.name, name: config.name, format: config.format, error })
console.ex(error)
}
}
}
function saveConfig(plugin: plugin.Plugin) {
let configs = getPluginConfigMetadata(plugin)
for (let [_, config] of configs) {
try {
let configFile = fs.concat(fs.file(plugin.description.loadMetadata.file).parent, plugin.description.name, config.name + '.' + config.format)
let configFactory = getConfigLoader(config.format)
if (!config.readonly) { base.save(configFile, configFactory.dump(plugin[config.variable])) }
} catch (error) {
console.i18n("ms.plugin.manager.config.save.error", { plugin: plugin.description.name, name: config.name, format: config.format, error })
console.ex(error)
}
}
} }
function init() { function init() {
@ -35,6 +77,8 @@ function init() {
let yaml = new YamlPluginConfig() let yaml = new YamlPluginConfig()
configLoaderMap.set("yml", yaml) configLoaderMap.set("yml", yaml)
configLoaderMap.set("yaml", yaml) configLoaderMap.set("yaml", yaml)
process.on('plugin.before.load', loadConfig)
process.on('plugin.after.load', saveConfig)
} }
init() init()

View File

@ -8,10 +8,10 @@ import { getPluginMetadatas, getPluginCommandMetadata, getPluginListenerMetadata
* MiaoScript plugin * MiaoScript plugin
* @param metadata PluginMetadata * @param metadata PluginMetadata
*/ */
export function plugin(metadata: pluginApi.PluginMetadata) { export function plugin(metadata: pluginApi.PluginMetadata | any) {
return function (target: any) { return function (target: any) {
metadata.target = target if (!metadata.source) metadata = { souece: metadata }
metadata.type = "ioc" metadata = { name: target.name, version: '1.0.0', author: 'Unknow', target, type: 'ioc', ...metadata }
decorate(injectable(), target) decorate(injectable(), target)
Reflect.defineMetadata(METADATA_KEY.plugin, metadata, target) Reflect.defineMetadata(METADATA_KEY.plugin, metadata, target)
const previousMetadata: Map<string, pluginApi.PluginMetadata> = getPluginMetadatas() const previousMetadata: Map<string, pluginApi.PluginMetadata> = getPluginMetadatas()

View File

@ -0,0 +1,30 @@
import { event, plugin, server } from '@ccms/api'
import { provideSingleton, postConstruct, inject } from '@ccms/container'
import { getPluginListenerMetadata } from './utils'
@provideSingleton(PluginEventManager)
export class PluginEventManager {
@inject(server.ServerChecker)
private ServerChecker: server.ServerChecker
@inject(event.Event)
private EventManager: event.Event
constructor() {
process.on('plugin.before.enable', (plugin: plugin.Plugin) => this.registryListener(plugin))
process.on('plugin.after.disable', (plugin: plugin.Plugin) => this.unregistryListener(plugin))
}
private registryListener(pluginInstance: plugin.Plugin) {
let events = getPluginListenerMetadata(pluginInstance)
for (const event of events) {
// ignore space listener
if (!this.ServerChecker.check(event.servers)) { continue }
// here must bind this to pluginInstance
this.EventManager.listen(pluginInstance, event.name, pluginInstance[event.executor].bind(pluginInstance))
}
}
private unregistryListener(pluginInstance: plugin.Plugin) {
this.EventManager.disable(pluginInstance)
}
}

View File

@ -1,11 +1,11 @@
import i18n from '@ccms/i18n' import i18n from '@ccms/i18n'
import { plugin, server, command, event } from '@ccms/api' import { plugin, server, event } from '@ccms/api'
import { inject, provideSingleton, Container, ContainerInstance } from '@ccms/container' import { inject, provideSingleton, Container, ContainerInstance } from '@ccms/container'
import * as fs from '@ccms/common/dist/fs'
import './config'
import { interfaces } from './interfaces' import { interfaces } from './interfaces'
import { getConfigLoader } from './config' import { PluginCommandManager } from './command'
import { getPluginCommandMetadata, getPluginListenerMetadata, getPluginTabCompleterMetadata, getPluginConfigMetadata } from './utils' import { PluginEventManager } from './event'
const Thread = Java.type('java.lang.Thread') const Thread = Java.type('java.lang.Thread')
@ -15,15 +15,16 @@ export class PluginManagerImpl implements plugin.PluginManager {
private container: Container private container: Container
@inject(plugin.PluginInstance) @inject(plugin.PluginInstance)
private pluginInstance: any private pluginInstance: any
@inject(plugin.PluginFolder)
private pluginFolder: string
@inject(server.ServerType) @inject(server.ServerType)
private serverType: string private serverType: string
@inject(command.Command)
private CommandManager: command.Command
@inject(event.Event) @inject(event.Event)
private EventManager: event.Event private EventManager: event.Event
@inject(PluginCommandManager)
private commandManager: PluginCommandManager
@inject(PluginEventManager)
private eventManager: PluginEventManager
private initialized: boolean = false private initialized: boolean = false
private sacnnerMap: Map<string, plugin.PluginScanner> private sacnnerMap: Map<string, plugin.PluginScanner>
@ -38,6 +39,10 @@ export class PluginManagerImpl implements plugin.PluginManager {
this.instanceMap = new Map() this.instanceMap = new Map()
this.metadataMap = new Map() this.metadataMap = new Map()
// ignore unused
this.commandManager
this.eventManager
} }
initialize() { initialize() {
@ -90,14 +95,15 @@ export class PluginManagerImpl implements plugin.PluginManager {
console.i18n("ms.plugin.manager.stage", { stage, plugin: plugin.description.name, version: plugin.description.version, author: plugin.description.author }) console.i18n("ms.plugin.manager.stage", { stage, plugin: plugin.description.name, version: plugin.description.version, author: plugin.description.author })
} }
private runPluginStage(plugin: plugin.Plugin, stage: string, ext: Function) { private runPluginStage(plugin: plugin.Plugin, stage: string) {
if (!plugin) { throw new Error(`can't run runPluginStage ${stage} because plugin is ${plugin}`) } if (!plugin) { throw new Error(`can't run runPluginStage ${stage} because plugin is ${plugin}`) }
try { try {
this.logStage(plugin, i18n.translate(`ms.plugin.manager.stage.${stage}`)) this.logStage(plugin, i18n.translate(`ms.plugin.manager.stage.${stage}`))
ext() process.emit(`plugin.before.${stage}`, plugin)
this.runCatch(plugin, stage) this.runCatch(plugin, stage)
this.runCatch(plugin, `${this.serverType}${stage}`) this.runCatch(plugin, `${this.serverType}${stage}`)
plugin.description.loadMetadata.loader[stage](plugin) plugin.description.loadMetadata.loader[stage](plugin)
process.emit(`plugin.after.${stage}`, plugin)
} catch (ex) { } catch (ex) {
console.i18n("ms.plugin.manager.stage.exec.error", { plugin: plugin.description.name, executor: stage, error: ex }) console.i18n("ms.plugin.manager.stage.exec.error", { plugin: plugin.description.name, executor: stage, error: ex })
} }
@ -108,22 +114,7 @@ export class PluginManagerImpl implements plugin.PluginManager {
if (loadMetadata.loaded) { throw new Error(`Plugin ${loadMetadata.name} is already loaded by ${loadMetadata.loader?.type}!`) } if (loadMetadata.loaded) { throw new Error(`Plugin ${loadMetadata.name} is already loaded by ${loadMetadata.loader?.type}!`) }
try { try {
for (const [, loader] of this.loaderMap) { for (const [, loader] of this.loaderMap) {
try { if (this.loaderRequirePlugin(loadMetadata, loader)?.loaded) return loadMetadata.metadata
if (loader.require(loadMetadata).loaded) {
loadMetadata.loader = loader
let metadata = loadMetadata.metadata
this.metadataMap.set(metadata.name, metadata)
metadata.loadMetadata = loadMetadata
return metadata
}
} catch (error) {
if (global.debug) {
console.console(`§6Loader §b${loader.type} §6load §a${loadMetadata.file} §cerror. §4Err: §c${error}`)
console.ex(error)
} else {
console.warn(`Loader ${loader.type} load ${loadMetadata.file} error. Err: ${error}`)
}
}
} }
} catch (error) { } catch (error) {
console.i18n("ms.plugin.manager.initialize.error", { name: loadMetadata.file, ex: error }) console.i18n("ms.plugin.manager.initialize.error", { name: loadMetadata.file, ex: error })
@ -132,6 +123,25 @@ export class PluginManagerImpl implements plugin.PluginManager {
console.console(`§6scanner: §b${loadMetadata.scanner.type} §ccan\'t load §6file §b${loadMetadata.file}. §eskip!`) console.console(`§6scanner: §b${loadMetadata.scanner.type} §ccan\'t load §6file §b${loadMetadata.file}. §eskip!`)
} }
private loaderRequirePlugin(loadMetadata: plugin.PluginLoadMetadata, loader: plugin.PluginLoader) {
try {
if (loader.require(loadMetadata).loaded) {
loadMetadata.loader = loader
let metadata = loadMetadata.metadata
this.metadataMap.set(metadata.name, metadata)
metadata.loadMetadata = loadMetadata
}
return loadMetadata
} catch (error) {
if (global.debug) {
console.console(`§6Loader §b${loader.type} §6load §a${loadMetadata.file} §cerror. §4Err: §c${error}`)
console.ex(error)
} else {
console.warn(`Loader ${loader.type} load ${loadMetadata.file} error. Err: ${error}`)
}
}
}
/** /**
* *
* @param file java.io.File * @param file java.io.File
@ -147,36 +157,21 @@ export class PluginManagerImpl implements plugin.PluginManager {
} }
load(...args: any[]): void { load(...args: any[]): void {
this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => { this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => this.runPluginStage(plugin, 'load'))
this.runPluginStage(plugin, 'load', () => {
this.loadConfig(plugin)
})
})
} }
enable(...args: any[]): void { enable(...args: any[]): void {
this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => { this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => this.runPluginStage(plugin, 'enable'))
this.runPluginStage(plugin, 'enable', () => {
this.registryCommand(plugin)
this.registryListener(plugin)
})
})
} }
disable(...args: any[]): void { disable(...args: any[]): void {
this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => { this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => this.runPluginStage(plugin, 'disable'))
this.runPluginStage(plugin, 'disable', () => {
this.saveConfig(plugin)
this.unregistryCommand(plugin)
this.unregistryListener(plugin)
})
})
} }
reload(...args: any[]): void { reload(...args: any[]): void {
this.checkAndGet(args[0]).forEach((pl: plugin.Plugin) => { this.checkAndGet(args[0]).forEach((pl: plugin.Plugin) => {
this.disable(pl) this.disable(pl)
this.loadFromFile(pl.description.source.toString(), pl.description.loadMetadata.scanner) this.loadFromFile(pl.description.loadMetadata.file, pl.description.loadMetadata.scanner)
}) })
} }
@ -206,19 +201,6 @@ export class PluginManagerImpl implements plugin.PluginManager {
throw new Error(`Plugin ${JSON.stringify(name)} not exist!`) throw new Error(`Plugin ${JSON.stringify(name)} not exist!`)
} }
private allowProcess(servers: string[]) {
// Not set servers -> allow
if (!servers || !servers.length) return true
// include !type -> deny
let denyServers = servers.filter(svr => svr.startsWith("!"))
if (denyServers.length !== 0) {
return !denyServers.includes(`!${this.serverType}`)
} else {
// only include -> allow
return servers.includes(this.serverType)
}
}
private buildPlugins() { private buildPlugins() {
for (const [, metadata] of this.metadataMap) { for (const [, metadata] of this.metadataMap) {
let pluginInstance: plugin.Plugin let pluginInstance: plugin.Plugin
@ -231,77 +213,4 @@ export class PluginManagerImpl implements plugin.PluginManager {
this.instanceMap.set(metadata.name, pluginInstance) this.instanceMap.set(metadata.name, pluginInstance)
} }
} }
private loadConfig(plugin: plugin.Plugin) {
let configs = getPluginConfigMetadata(plugin)
for (let [_, config] of configs) {
try {
let configFile = fs.concat(root, this.pluginFolder, plugin.description.name, config.name + '.' + config.format)
let configFactory = getConfigLoader(config.format)
if (!fs.exists(configFile)) {
base.save(configFile, configFactory.dump(plugin[config.variable]))
console.i18n("ms.plugin.manager.config.save.default", { plugin: plugin.description.name, name: config.name, format: config.format })
} else {
plugin[config.variable] = configFactory.load(base.read(configFile))
plugin[config.variable].save = () => {
let result = configFactory.dump(plugin[config.variable])
base.save(configFile, result)
console.debug(`[${plugin.description.name}] Save Config ${config.variable} to file ${configFile} result ${result}`)
}
console.debug(`[${plugin.description.name}] Load Config ${config.variable} from file ${configFile} result ${JSON.stringify(plugin[config.variable])}`)
}
} catch (error) {
console.i18n("ms.plugin.manager.config.load.error", { plugin: plugin.description.name, name: config.name, format: config.format, error })
console.ex(error)
}
}
}
private saveConfig(plugin: plugin.Plugin) {
let configs = getPluginConfigMetadata(plugin)
for (let [_, config] of configs) {
try {
let configFile = fs.concat(root, this.pluginFolder, plugin.description.name, config.name + '.' + config.format)
let configFactory = getConfigLoader(config.format)
if (!config.readonly) { base.save(configFile, configFactory.dump(plugin[config.variable])) }
} catch (error) {
console.i18n("ms.plugin.manager.config.save.error", { plugin: plugin.description.name, name: config.name, format: config.format, error })
console.ex(error)
}
}
}
private registryCommand(pluginInstance: plugin.Plugin) {
let cmds = getPluginCommandMetadata(pluginInstance)
let tabs = getPluginTabCompleterMetadata(pluginInstance)
for (const [_, cmd] of cmds) {
let tab = tabs.get(cmd.name)
if (!this.allowProcess(cmd.servers)) { continue }
this.CommandManager.on(pluginInstance, cmd.name, {
cmd: pluginInstance[cmd.executor].bind(pluginInstance),
tab: tab ? pluginInstance[tab.executor].bind(pluginInstance) : undefined
})
}
}
private registryListener(pluginInstance: plugin.Plugin) {
let events = getPluginListenerMetadata(pluginInstance)
for (const event of events) {
// ignore space listener
if (!this.allowProcess(event.servers)) { continue }
// here must bind this to pluginInstance
this.EventManager.listen(pluginInstance, event.name, pluginInstance[event.executor].bind(pluginInstance))
}
}
private unregistryCommand(pluginInstance: plugin.Plugin) {
let cmds = getPluginCommandMetadata(pluginInstance)
cmds.forEach(cmd => {
this.CommandManager.off(pluginInstance, cmd.name)
})
}
private unregistryListener(pluginInstance: plugin.Plugin) {
this.EventManager.disable(pluginInstance)
}
} }