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 {
/**
*
*
*/
name: string
name?: string
/**
*
*/
prefix?: string
/**
*
* 1.0.0
*/
version: string
version?: string
/**
*
* Unknow
*/
author: string | string[]
author?: string | string[]
/**
* __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 fs from '@ccms/common/dist/fs'
import { plugin } from '@ccms/api'
import { getPluginConfigMetadata } from './utils'
export interface PluginConfigLoader {
load(content: string): any;
dump(variable: any): string;
load(content: string): any
dump(variable: any): string
}
export class YamlPluginConfig implements PluginConfigLoader {
load(content: string) {
return yaml.safeLoad(content);
return yaml.safeLoad(content)
}
dump(variable: any): string {
return yaml.safeDump(variable, { skipInvalid: true });
return yaml.safeDump(variable, { skipInvalid: true })
}
}
export class JsonPluginConfig implements PluginConfigLoader {
load(content: string) {
return JSON.parse(content);
return JSON.parse(content)
}
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) {
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() {
@ -35,6 +77,8 @@ function init() {
let yaml = new YamlPluginConfig()
configLoaderMap.set("yml", yaml)
configLoaderMap.set("yaml", yaml)
process.on('plugin.before.load', loadConfig)
process.on('plugin.after.load', saveConfig)
}
init()

View File

@ -8,10 +8,10 @@ import { getPluginMetadatas, getPluginCommandMetadata, getPluginListenerMetadata
* MiaoScript plugin
* @param metadata PluginMetadata
*/
export function plugin(metadata: pluginApi.PluginMetadata) {
export function plugin(metadata: pluginApi.PluginMetadata | any) {
return function (target: any) {
metadata.target = target
metadata.type = "ioc"
if (!metadata.source) metadata = { souece: metadata }
metadata = { name: target.name, version: '1.0.0', author: 'Unknow', target, type: 'ioc', ...metadata }
decorate(injectable(), target)
Reflect.defineMetadata(METADATA_KEY.plugin, metadata, target)
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 { plugin, server, command, event } from '@ccms/api'
import { plugin, server, event } from '@ccms/api'
import { inject, provideSingleton, Container, ContainerInstance } from '@ccms/container'
import * as fs from '@ccms/common/dist/fs'
import './config'
import { interfaces } from './interfaces'
import { getConfigLoader } from './config'
import { getPluginCommandMetadata, getPluginListenerMetadata, getPluginTabCompleterMetadata, getPluginConfigMetadata } from './utils'
import { PluginCommandManager } from './command'
import { PluginEventManager } from './event'
const Thread = Java.type('java.lang.Thread')
@ -15,15 +15,16 @@ export class PluginManagerImpl implements plugin.PluginManager {
private container: Container
@inject(plugin.PluginInstance)
private pluginInstance: any
@inject(plugin.PluginFolder)
private pluginFolder: string
@inject(server.ServerType)
private serverType: string
@inject(command.Command)
private CommandManager: command.Command
@inject(event.Event)
private EventManager: event.Event
@inject(PluginCommandManager)
private commandManager: PluginCommandManager
@inject(PluginEventManager)
private eventManager: PluginEventManager
private initialized: boolean = false
private sacnnerMap: Map<string, plugin.PluginScanner>
@ -38,6 +39,10 @@ export class PluginManagerImpl implements plugin.PluginManager {
this.instanceMap = new Map()
this.metadataMap = new Map()
// ignore unused
this.commandManager
this.eventManager
}
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 })
}
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}`) }
try {
this.logStage(plugin, i18n.translate(`ms.plugin.manager.stage.${stage}`))
ext()
process.emit(`plugin.before.${stage}`, plugin)
this.runCatch(plugin, stage)
this.runCatch(plugin, `${this.serverType}${stage}`)
plugin.description.loadMetadata.loader[stage](plugin)
process.emit(`plugin.after.${stage}`, plugin)
} catch (ex) {
console.i18n("ms.plugin.manager.stage.exec.error", { plugin: plugin.description.name, executor: stage, error: ex })
}
@ -108,14 +114,24 @@ export class PluginManagerImpl implements plugin.PluginManager {
if (loadMetadata.loaded) { throw new Error(`Plugin ${loadMetadata.name} is already loaded by ${loadMetadata.loader?.type}!`) }
try {
for (const [, loader] of this.loaderMap) {
if (this.loaderRequirePlugin(loadMetadata, loader)?.loaded) return loadMetadata.metadata
}
} catch (error) {
console.i18n("ms.plugin.manager.initialize.error", { name: loadMetadata.file, ex: error })
console.ex(error)
}
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 metadata
}
return loadMetadata
} catch (error) {
if (global.debug) {
console.console(`§6Loader §b${loader.type} §6load §a${loadMetadata.file} §cerror. §4Err: §c${error}`)
@ -125,12 +141,6 @@ export class PluginManagerImpl implements plugin.PluginManager {
}
}
}
} catch (error) {
console.i18n("ms.plugin.manager.initialize.error", { name: loadMetadata.file, ex: error })
console.ex(error)
}
console.console(`§6scanner: §b${loadMetadata.scanner.type} §ccan\'t load §6file §b${loadMetadata.file}. §eskip!`)
}
/**
*
@ -147,36 +157,21 @@ export class PluginManagerImpl implements plugin.PluginManager {
}
load(...args: any[]): void {
this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => {
this.runPluginStage(plugin, 'load', () => {
this.loadConfig(plugin)
})
})
this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => this.runPluginStage(plugin, 'load'))
}
enable(...args: any[]): void {
this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => {
this.runPluginStage(plugin, 'enable', () => {
this.registryCommand(plugin)
this.registryListener(plugin)
})
})
this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => this.runPluginStage(plugin, 'enable'))
}
disable(...args: any[]): void {
this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => {
this.runPluginStage(plugin, 'disable', () => {
this.saveConfig(plugin)
this.unregistryCommand(plugin)
this.unregistryListener(plugin)
})
})
this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => this.runPluginStage(plugin, 'disable'))
}
reload(...args: any[]): void {
this.checkAndGet(args[0]).forEach((pl: plugin.Plugin) => {
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!`)
}
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() {
for (const [, metadata] of this.metadataMap) {
let pluginInstance: plugin.Plugin
@ -231,77 +213,4 @@ export class PluginManagerImpl implements plugin.PluginManager {
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)
}
}