import fs from 'fs/promises'; import path from 'path'; import Yaml from 'yaml'; import chokidar from 'chokidar'; import { debounce } from 'throttle-debounce'; import EventEmitter from 'events'; import App from './App'; import { BaseProvider } from './base/provider/BaseProvider'; import { ChannelConfig } from './Config'; export class ChannelManager extends EventEmitter { private app: App; private channelPath: string; private loadChannelCallback: (file: string) => any; private removeChannelCallback: (file: string) => any; private setLoading?: debounce<Function>; private watcher!: chokidar.FSWatcher; public channels: { [key: string]: BaseProvider }; public channelName: { [key: string]: string }; constructor(app: App, channelPath: string) { super(); this.app = app; this.channelPath = channelPath; this.channels = {}; this.channelName = {}; this.loadChannelCallback = this.loadChannel.bind(this); this.removeChannelCallback = this.removeChannel.bind(this); } /** * 加载所有Channel */ async initialize() { this.watcher = chokidar.watch(this.channelPath, { ignored: '*.bak', ignorePermissionErrors: true, persistent: true }); this.watcher.on('add', this.loadChannelCallback); this.watcher.on('change', this.loadChannelCallback); this.watcher.on('unlink', this.removeChannelCallback); } /** * 获取Channel ID * @param {string} file - channel config file * @returns */ getChannelId(file: string): string { let channelPath = path.relative(this.channelPath, file).replace(/\\/g, "/").replace(/\..*?$/, ""); return channelPath; } /** * 获取Channel的全名 * @param {string} channelId - channel ID */ getChannelFullName(channelId: string): string { // 从最顶层开始查找 let pathList = channelId.split("/"); let nameList: string[] = []; for (let i = 0; i < pathList.length; i++) { let currentPath = pathList.slice(0, i + 1).join("/"); let findedName = this.channelName[currentPath]; if (findedName) { nameList.push(findedName); } else { nameList.push(pathList[i]); } } return nameList.join("/"); } /** * 获取Provider名 * @param config * @returns */ getProviderName(config: ChannelConfig): string { return config.provider; } /** * 读取或更新Channel配置文件 * @param {string} file */ async loadChannel(file: string) { try { let content = await fs.readFile(file, { encoding: 'utf-8' }); let config = Yaml.parse(content); let channelId = this.getChannelId(file); if (path.basename(channelId) === "_group") { // 只是group标记 channelId = path.dirname(channelId); this.channelName[channelId] = config?.name; } else { if (BaseProvider.checkConfig(config)) { // console.log(`正在加载Channel: ${channelId}`); // 处理channel let providerName = this.getProviderName(config); let isReload = false; if (channelId in this.channels) { // 重载配置 isReload = true; await this.channels[channelId].destory(); } // 读取配置 let channel = this.app.createChannel(providerName, channelId, config); if (channel) { await channel.initialize(); // 更新列表 this.channels[channelId] = channel; this.channelName[channelId] = config?.name; if (isReload) { this.emit('reload', channelId); console.log(`已重载Channel: ${channelId}`); } else { this.emit('add', channelId); console.log(`已加载Channel: ${channelId}`); } } } else { console.error(`配置文件: ${file} 格式错误`); } } return true; } catch (err) { console.error(err); return false; } } /** * 移除Channel * @param {string} file */ async removeChannel(file: string) { let channelId = this.getChannelId(file); if (path.basename(channelId) === "_group") { // 仅删除组名 channelId = path.basename(channelId); delete this.channelName[channelId]; } else { let channel = this.channels[channelId]; if (channel) { await channel.destory(); delete this.channels[channelId]; delete this.channelName[channelId]; this.emit('remove', channelId); console.log("已移除Channel: ", this.getChannelFullName(channelId)); } } } onLoad() { } }