refactor: plugin system & config manager

Signed-off-by: MiaoWoo <admin@yumc.pw>
This commit is contained in:
MiaoWoo 2020-09-24 18:36:10 +08:00
parent 4741e341b7
commit e9a2aa9745
10 changed files with 99 additions and 74 deletions

View File

@ -1,17 +1,17 @@
import { command, plugin, server } from '@ccms/api' import { command, plugin, server } from '@ccms/api'
import { provideSingleton, inject } from '@ccms/container' import { provideSingleton, inject, Autowired } from '@ccms/container'
import { getPluginCommandMetadata, getPluginTabCompleterMetadata } from './utils' import { getPluginCommandMetadata, getPluginTabCompleterMetadata } from './utils'
@provideSingleton(PluginCommandManager) @provideSingleton(PluginCommandManager)
export class PluginCommandManager { export class PluginCommandManager {
@inject(server.ServerChecker) @Autowired()
private ServerChecker: server.ServerChecker
@inject(command.Command)
private CommandManager: command.Command private CommandManager: command.Command
@Autowired()
private ServerChecker: server.ServerChecker
constructor() { constructor() {
process.on('plugin.before.enable', (plugin: plugin.Plugin) => this.registryCommand(plugin)) process.on('plugin.before.enable', this.registryCommand.bind(this))
process.on('plugin.after.disable', (plugin: plugin.Plugin) => this.unregistryCommand(plugin)) process.on('plugin.after.disable', this.unregistryCommand.bind(this))
} }
private registryCommand(pluginInstance: plugin.Plugin) { private registryCommand(pluginInstance: plugin.Plugin) {

View File

@ -1,6 +1,9 @@
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import * as fs from '@ccms/common/dist/fs' import * as fs from '@ccms/common/dist/fs'
import { plugin } from '@ccms/api' import { plugin } from '@ccms/api'
import { provideSingleton } from '@ccms/container'
import { interfaces } from './interfaces'
import { getPluginConfigMetadata } from './utils' import { getPluginConfigMetadata } from './utils'
export interface PluginConfigLoader { export interface PluginConfigLoader {
@ -13,7 +16,7 @@ export class YamlPluginConfig implements PluginConfigLoader {
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, lineWidth: 120 })
} }
} }
@ -22,63 +25,68 @@ export class JsonPluginConfig implements PluginConfigLoader {
return JSON.parse(content) return JSON.parse(content)
} }
dump(variable: any): string { dump(variable: any): string {
return JSON.stringify(variable) return JSON.stringify(variable, undefined, 4)
} }
} }
const configLoaderMap = new Map<string, PluginConfigLoader>() @provideSingleton(PluginConfigManager)
export class PluginConfigManager {
private configLoaderMap = new Map<string, PluginConfigLoader>()
export function getConfigLoader(format: string) { constructor() {
if (!configLoaderMap.has(format)) { throw new Error(`Unsupport config format ${format} !`) } this.configLoaderMap.set("json", new JsonPluginConfig())
return configLoaderMap.get(format) let yaml = new YamlPluginConfig()
} this.configLoaderMap.set("yml", yaml)
this.configLoaderMap.set("yaml", yaml)
process.on('plugin.before.load', this.loadConfig.bind(this))
process.on('plugin.after.disable', this.saveConfig.bind(this))
}
function loadConfig(plugin: plugin.Plugin) { getConfigLoader(format: string) {
if (!this.configLoaderMap.has(format)) { throw new Error(`Unsupport config format ${format} !`) }
return this.configLoaderMap.get(format)
}
loadConfig(plugin: plugin.Plugin) {
let configs = getPluginConfigMetadata(plugin) let configs = getPluginConfigMetadata(plugin)
for (let [_, config] of configs) { for (let [_, config] of configs) {
try { try {
let configFile = fs.concat(fs.file(plugin.description.loadMetadata.file).parent, plugin.description.name, config.name + '.' + config.format) config.file = fs.concat(fs.file(plugin.description.loadMetadata.file).parent, plugin.description.name, config.filename)
let configFactory = getConfigLoader(config.format) let configLoader = this.getConfigLoader(config.format)
if (!fs.exists(configFile)) { if (!fs.exists(config.file)) {
base.save(configFile, configFactory.dump(plugin[config.variable])) base.save(config.file, configLoader.dump(plugin[config.variable]))
console.i18n("ms.plugin.manager.config.save.default", { plugin: plugin.description.name, name: config.name, format: config.format }) console.i18n("ms.plugin.manager.config.save.default", { plugin: plugin.description.name, name: config.name, format: config.format })
} else { } else {
Object.defineProperty(plugin, config.variable, { value: configFactory.load(base.read(configFile)) }) Object.defineProperty(plugin, config.variable, { value: configLoader.load(base.read(config.file)) })
plugin[config.variable].save = () => { plugin[config.variable].save = () => this.saveConfig0(plugin, config)
let result = configFactory.dump(plugin[config.variable]) console.debug(`[${plugin.description.name}] Load Config ${config.variable} from file ${config.file} =>\n${JSON.stringify(plugin[config.variable], undefined, 4)}`)
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) { } catch (error) {
console.i18n("ms.plugin.manager.config.load.error", { plugin: plugin.description.name, name: config.name, format: config.format, error }) console.i18n("ms.plugin.manager.config.load.error", { plugin: plugin.description.name, name: config.name, format: config.format, error })
console.ex(error) console.ex(error)
} }
} }
} }
function saveConfig(plugin: plugin.Plugin) { saveConfig(plugin: plugin.Plugin) {
let configs = getPluginConfigMetadata(plugin) let configs = getPluginConfigMetadata(plugin)
for (let [_, config] of configs) { for (let [_, config] of configs) {
this.saveConfig0(plugin, config)
}
}
private saveConfig0(plugin: plugin.Plugin, metadata: interfaces.ConfigMetadata) {
try { try {
let configFile = fs.concat(fs.file(plugin.description.loadMetadata.file).parent, plugin.description.name, config.name + '.' + config.format) if (metadata.readonly) { console.debug(`[${plugin.description.name}] Skip Save Config ${metadata.variable} Because it's readonly!`) }
let configFactory = getConfigLoader(config.format) metadata.file = fs.concat(fs.file(plugin.description.loadMetadata.file).parent, plugin.description.name, metadata.filename)
if (!config.readonly) { base.save(configFile, configFactory.dump(plugin[config.variable])) } let result = this.getConfigLoader(metadata.format).dump(plugin[metadata.variable])
base.save(metadata.file, result)
console.debug(`[${plugin.description.name}] Save Config ${metadata.variable} to file ${metadata.file} =>\n${result}`)
return true
} catch (error) { } catch (error) {
console.i18n("ms.plugin.manager.config.save.error", { plugin: plugin.description.name, name: config.name, format: config.format, error }) console.i18n("ms.plugin.manager.config.save.error", { plugin: plugin.description.name, name: metadata.name, format: metadata.format, error })
console.ex(error) console.ex(error)
return false
} }
} }
} }
function init() {
configLoaderMap.set("json", new JsonPluginConfig())
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

@ -1,6 +1,6 @@
export const METADATA_KEY = { export const METADATA_KEY = {
plugin: Symbol.for("@ccms/plugin:plugin"), plugin: Symbol.for("@ccms/plugin:plugin"),
souece: Symbol.for("@ccms/plugin:souece"), source: Symbol.for("@ccms/plugin:source"),
cmd: Symbol.for("@ccms/plugin:cmd"), cmd: Symbol.for("@ccms/plugin:cmd"),
tab: Symbol.for("@ccms/plugin:tab"), tab: Symbol.for("@ccms/plugin:tab"),
listener: Symbol.for("@ccms/plugin:listener"), listener: Symbol.for("@ccms/plugin:listener"),
@ -10,4 +10,4 @@ export const METADATA_KEY = {
enable: Symbol.for("@ccms/plugin:stage:enable"), enable: Symbol.for("@ccms/plugin:stage:enable"),
disable: Symbol.for("@ccms/plugin:stage:disable") disable: Symbol.for("@ccms/plugin:stage:disable")
} }
}; }

View File

@ -19,7 +19,7 @@ export function plugin(metadata: pluginApi.PluginMetadata | any) {
Reflect.defineMetadata(METADATA_KEY.plugin, previousMetadata, Reflect) Reflect.defineMetadata(METADATA_KEY.plugin, previousMetadata, Reflect)
const previousSources: Map<string, pluginApi.PluginMetadata> = getPluginSources() const previousSources: Map<string, pluginApi.PluginMetadata> = getPluginSources()
previousSources.set(metadata.source.toString(), metadata) previousSources.set(metadata.source.toString(), metadata)
Reflect.defineMetadata(METADATA_KEY.souece, previousSources, Reflect) Reflect.defineMetadata(METADATA_KEY.source, previousSources, Reflect)
} }
} }
@ -77,9 +77,11 @@ export function config(metadata: interfaces.ConfigMetadata = {}) {
metadata.variable = key metadata.variable = key
metadata.version = metadata.version ?? 1 metadata.version = metadata.version ?? 1
metadata.format = metadata.format ?? 'yml' metadata.format = metadata.format ?? 'yml'
metadata.filename = metadata.filename ?? metadata.name + '.' + metadata.format
let previousMetadata = getPluginConfigMetadata(target) let previousMetadata = getPluginConfigMetadata(target)
previousMetadata.set(metadata.name, metadata) previousMetadata.set(metadata.name, metadata)
Reflect.defineMetadata(METADATA_KEY.config, previousMetadata, target.constructor) Reflect.defineMetadata(METADATA_KEY.config, previousMetadata, target.constructor)
Reflect.defineMetadata(METADATA_KEY.config, metadata, target[key])
} }
} }

View File

@ -1,17 +1,21 @@
import { event, plugin, server } from '@ccms/api' import { event, plugin, server } from '@ccms/api'
import { provideSingleton, postConstruct, inject } from '@ccms/container' import { provideSingleton, Autowired } from '@ccms/container'
import { getPluginListenerMetadata } from './utils' import { getPluginListenerMetadata } from './utils'
@provideSingleton(PluginEventManager) @provideSingleton(PluginEventManager)
export class PluginEventManager { export class PluginEventManager {
@inject(server.ServerChecker) @Autowired()
private ServerChecker: server.ServerChecker
@inject(event.Event)
private EventManager: event.Event private EventManager: event.Event
@Autowired()
private ServerChecker: server.ServerChecker
constructor() { constructor() {
process.on('plugin.before.enable', (plugin: plugin.Plugin) => this.registryListener(plugin)) process.on('plugin.before.enable', this.registryListener.bind(this))
process.on('plugin.after.disable', (plugin: plugin.Plugin) => this.unregistryListener(plugin)) process.on('plugin.after.disable', this.unregistryListener.bind(this))
}
mapEventName() {
return this.EventManager.mapEventName().toFixed(0)
} }
private registryListener(pluginInstance: plugin.Plugin) { private registryListener(pluginInstance: plugin.Plugin) {

View File

@ -75,5 +75,13 @@ export namespace interfaces {
* () * ()
*/ */
readonly?: boolean readonly?: boolean
/**
*
*/
filename?: string
/**
*
*/
file?: any
} }
} }

View File

@ -1,5 +1,5 @@
import { plugin, server } from "@ccms/api" import { plugin, server } from "@ccms/api"
import { inject, ContainerInstance, Container, provideSingletonNamed } from "@ccms/container" import { inject, ContainerInstance, Container, provideSingletonNamed, Autowired } from "@ccms/container"
import { interfaces } from "../interfaces" import { interfaces } from "../interfaces"
import { getPluginStageMetadata, getPluginSources } from "../utils" import { getPluginStageMetadata, getPluginSources } from "../utils"
@ -11,7 +11,7 @@ export class IocLoader implements plugin.PluginLoader {
type: string = LOADER_TYPE_NAME type: string = LOADER_TYPE_NAME
@inject(ContainerInstance) @inject(ContainerInstance)
private container: Container private container: Container
@inject(server.ServerChecker) @Autowired()
private serverChecker: server.ServerChecker private serverChecker: server.ServerChecker
private pluginMetadataMap: Map<string, plugin.PluginMetadata> private pluginMetadataMap: Map<string, plugin.PluginMetadata>
@ -69,7 +69,7 @@ export class IocLoader implements plugin.PluginLoader {
console.i18n('ms.plugin.manager.build.duplicate', { exists: pluginInstance.description.source, source: metadata.source }) console.i18n('ms.plugin.manager.build.duplicate', { exists: pluginInstance.description.source, source: metadata.source })
} }
this.container.rebind(plugin.Plugin).to(metadata.target).inSingletonScope().whenTargetNamed(metadata.name) this.container.rebind(plugin.Plugin).to(metadata.target).inSingletonScope().whenTargetNamed(metadata.name)
} catch{ } catch {
this.container.bind(plugin.Plugin).to(metadata.target).inSingletonScope().whenTargetNamed(metadata.name) this.container.bind(plugin.Plugin).to(metadata.target).inSingletonScope().whenTargetNamed(metadata.name)
} }
} }

View File

@ -1,12 +1,13 @@
import i18n from '@ccms/i18n' import i18n from '@ccms/i18n'
import { plugin, server, event } from '@ccms/api' import { plugin, server, event } from '@ccms/api'
import { inject, provideSingleton, Container, ContainerInstance } from '@ccms/container' import { inject, provideSingleton, Container, ContainerInstance, Autowired } from '@ccms/container'
import './config' import './config'
import { interfaces } from './interfaces' import { interfaces } from './interfaces'
import { PluginTaskManager } from './task' import { PluginTaskManager } from './task'
import { PluginEventManager } from './event' import { PluginEventManager } from './event'
import { PluginCommandManager } from './command' import { PluginCommandManager } from './command'
import { PluginConfigManager } from './config'
const Thread = Java.type('java.lang.Thread') const Thread = Java.type('java.lang.Thread')
@ -18,16 +19,17 @@ export class PluginManagerImpl implements plugin.PluginManager {
private pluginInstance: any private pluginInstance: any
@inject(server.ServerType) @inject(server.ServerType)
private serverType: string private serverType: string
@inject(event.Event)
private EventManager: event.Event @Autowired()
@inject(server.ServerChecker)
private serverChecker: server.ServerChecker private serverChecker: server.ServerChecker
@inject(PluginTaskManager) @Autowired()
private taskManager: PluginTaskManager private taskManager: PluginTaskManager
@inject(PluginEventManager) @Autowired()
private eventManager: PluginEventManager private eventManager: PluginEventManager
@inject(PluginCommandManager) @Autowired()
private configManager: PluginConfigManager
@Autowired()
private commandManager: PluginCommandManager private commandManager: PluginCommandManager
private initialized: boolean = false private initialized: boolean = false
@ -48,6 +50,7 @@ export class PluginManagerImpl implements plugin.PluginManager {
// ignore unused // ignore unused
this.taskManager this.taskManager
this.eventManager this.eventManager
this.configManager
this.commandManager this.commandManager
} }
@ -55,7 +58,7 @@ export class PluginManagerImpl implements plugin.PluginManager {
if (this.pluginInstance === undefined) { throw new Error("Can't found Plugin Instance!") } if (this.pluginInstance === undefined) { throw new Error("Can't found Plugin Instance!") }
if (this.initialized !== true) { if (this.initialized !== true) {
console.i18n('ms.plugin.initialize', { plugin: this.pluginInstance, loader: Thread.currentThread().contextClassLoader }) console.i18n('ms.plugin.initialize', { plugin: this.pluginInstance, loader: Thread.currentThread().contextClassLoader })
console.i18n('ms.plugin.event.map', { count: this.EventManager.mapEventName().toFixed(0), type: this.serverType }) console.i18n('ms.plugin.event.map', { count: this.eventManager.mapEventName(), type: this.serverType })
let pluginScanner = this.container.getAll<plugin.PluginScanner>(plugin.PluginScanner) let pluginScanner = this.container.getAll<plugin.PluginScanner>(plugin.PluginScanner)
pluginScanner.forEach((scanner) => { pluginScanner.forEach((scanner) => {
console.debug(`loading plugin sacnner ${scanner.type}...`) console.debug(`loading plugin sacnner ${scanner.type}...`)

View File

@ -1,9 +1,9 @@
import { plugin, task } from '@ccms/api' import { plugin, task } from '@ccms/api'
import { provideSingleton, inject } from '@ccms/container' import { provideSingleton, Autowired } from '@ccms/container'
@provideSingleton(PluginTaskManager) @provideSingleton(PluginTaskManager)
export class PluginTaskManager { export class PluginTaskManager {
@inject(task.TaskManager) @Autowired()
private taskManager: task.TaskManager private taskManager: task.TaskManager
constructor() { constructor() {

View File

@ -14,7 +14,7 @@ function getPlugin(name: string) {
function getPluginSources() { function getPluginSources() {
let pluginSources: Map<string, plugin.PluginMetadata> = Reflect.getMetadata( let pluginSources: Map<string, plugin.PluginMetadata> = Reflect.getMetadata(
METADATA_KEY.souece, METADATA_KEY.source,
Reflect Reflect
) || pluginSourceCache ) || pluginSourceCache
return pluginSources return pluginSources