refactor: plugin system & config manager
Signed-off-by: MiaoWoo <admin@yumc.pw>
This commit is contained in:
parent
4741e341b7
commit
e9a2aa9745
@ -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) {
|
||||||
|
@ -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()
|
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
@ -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])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -75,5 +75,13 @@ export namespace interfaces {
|
|||||||
* 是否为只读(关闭时将不会自动保存)
|
* 是否为只读(关闭时将不会自动保存)
|
||||||
*/
|
*/
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
|
/**
|
||||||
|
* 配置文件名称
|
||||||
|
*/
|
||||||
|
filename?: string
|
||||||
|
/**
|
||||||
|
* 配置文件全路径
|
||||||
|
*/
|
||||||
|
file?: any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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}...`)
|
||||||
|
@ -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() {
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user