import { ipcMain, IpcMainEvent, BrowserWindow, MessageChannelMain, session } from 'electron'; import SystemManager from './SystemManager'; import ConfigManager from './ConfigManager'; import ConnectionManager from './ConnectionManager'; import FontManager from './FontManager'; import FontObject from './FontObject'; import AppLogger from './AppLogger'; import { Collection } from '../database/entity/Collection.schema'; import { ChannelType } from '../enums/ChannelType'; import { StorageType } from '../enums/StorageType'; export default class MessageHandler { systemManager: SystemManager; configManager: ConfigManager; connectionManager: ConnectionManager; fontManager: FontManager; mainWindow: BrowserWindow; constructor( systemManager: SystemManager, configManager: ConfigManager, connectionManager: ConnectionManager, fontManager: FontManager, mainWindow: BrowserWindow, ) { this.systemManager = systemManager; this.configManager = configManager; this.connectionManager = connectionManager; this.fontManager = fontManager; this.mainWindow = mainWindow; } on(channel: string, done: any) { return ipcMain.on(channel, done); } send(event: IpcMainEvent, channel: string, data: any) { return event.sender.send(channel, data); } handle(channel: string, done: any) { return ipcMain.handle(channel, done); } initialize() { this.initSystemCollection(); this.on(ChannelType.IPC_REQUEST_SYSTEM_BOOT, async (event: IpcMainEvent, _args: any) => { let config = this.configManager.toArray(); let system = this.systemManager.toArray(); const response: StorageType = { ...config, system: system }; this.send(event, ChannelType.IPC_RESPONSE_SYSTEM_BOOT, response); }); this.on(ChannelType.IPC_SYSTEM_REBOOT, async (_event: IpcMainEvent, _args: any) => this.fontManager.reLaunch()); // Config Manager this.handle(ChannelType.IPC_SET_CONFIG, async (_event: IpcMainEvent, args: any) => this.configManager.set(args.key, args.values)); this.handle(ChannelType.IPC_GET_CONFIG, async (_event: IpcMainEvent, args: any) => this.configManager.get(args.key)); this.handle(ChannelType.IPC_ZAP_CONFIG, async (_event: IpcMainEvent) => this.configManager.clear()); // Safe Storage this.handle(ChannelType.IPC_SAFE_STORE, async (_event: IpcMainEvent, args: { key: string; value: string }) => { this.configManager.setSecure(args.key, args.value); }); this.handle(ChannelType.IPC_SAFE_RETRIEVE, async (_event: IpcMainEvent, key: string) => { return this.configManager.getSecure(key); }); // Session: Clear Cache this.handle(ChannelType.IPC_CLEAR_CACHE, async () => { await session.defaultSession.clearCache(); }); // Connection Manager this.handle(ChannelType.IPC_DBCONNECTION_CREATE, async (_event: IpcMainEvent, args: any) => this.configManager.createDbConnection(args), ); this.handle(ChannelType.IPC_DBCONNECTION_ENABLE, (_event: IpcMainEvent, args: any) => this.configManager.enableDbConnection(args)); this.handle(ChannelType.IPC_DBCONNECTION_DELETE, async (_event: IpcMainEvent, name: string) => this.configManager.deleteDbConnection(name), ); this.handle(ChannelType.IPC_DBCONNECTION_TEST, async (_event: IpcMainEvent, args: any) => { const options = this.connectionManager.dataSource.options; if (options.database === args.database) { return this.connectionManager .isInitialized() .then(() => this.sendMessage('success', 'Connection tested successfully')) .catch((err: Error) => this.sendMessage('error', err.message)); } else { return this.connectionManager .createDataSource(args) .then(() => this.sendMessage('success', 'Connection tested successfully')) .catch((err: Error) => this.sendMessage('error', err.message)); } }); this.handle(ChannelType.IPC_DATABASE_DROP, async (_event: IpcMainEvent) => this.connectionManager.getDataSource().dropDatabase()); // Font Manager this.handle(ChannelType.IPC_EXEC_CMD, async (_event: IpcMainEvent, args: any) => this.fontManager.executeCommand(args).catch((err: Error) => this.sendMessage('error', err.message)), ); this.handle(ChannelType.IPC_AUTH_USER, async (_event: IpcMainEvent, args: any) => this.fontManager.systemAuthenticate(args)); this.handle(ChannelType.IPC_SCAN_FILES, async (_event: IpcMainEvent, args: any) => { const { port1, port2 } = new MessageChannelMain(); this.mainWindow.webContents.postMessage(ChannelType.IPC_SCAN_PROGRESS_PORT, null, [port2]); const onProgress = (progress: any) => { try { port1.postMessage(progress); } catch { // Port may be closed if renderer navigated away } }; try { const catalogFiles = await this.fontManager.copyFiles(args.files, args.collectionId); await this.fontManager.scanFiles(catalogFiles, { collection_id: args.collectionId }, onProgress); } finally { port1.close(); } }); this.handle(ChannelType.IPC_SCAN_FOLDERS, async (_event: IpcMainEvent, args: any) => { const { port1, port2 } = new MessageChannelMain(); this.mainWindow.webContents.postMessage(ChannelType.IPC_SCAN_PROGRESS_PORT, null, [port2]); const onProgress = (progress: any) => { try { port1.postMessage(progress); } catch { // Port may be closed if renderer navigated away } }; try { const promises = args.folders.map(async (sourceFolder: string) => { const dest = await this.fontManager.copyFolder(sourceFolder, args.collectionId); await this.fontManager.scanFolder(dest, { collection_id: args.collectionId }, onProgress); }); await Promise.allSettled(promises); } finally { port1.close(); } }); this.handle(ChannelType.IPC_FETCH_NEWS, async (_event: IpcMainEvent, args: any) => this.fontManager.fetchLatestNews(args)); this.handle(ChannelType.IPC_SHOW_MESSAGE_BOX, async (_event: IpcMainEvent, options: any) => this.fontManager.showMessageBox(options)); this.handle(ChannelType.IPC_SHOW_OPEN_DIALOG, async (_event: IpcMainEvent, options: any) => this.fontManager.showOpenDialog(options)); this.handle(ChannelType.IPC_OPEN_PATH, async (_event: IpcMainEvent, path: string) => this.fontManager.openPath(path)); this.handle(ChannelType.IPC_OPEN_FOLDER, async (_event: IpcMainEvent, fullPath: string) => this.fontManager.showItemInFolder(fullPath)); this.handle(ChannelType.IPC_OPEN_EXTERNAL, async (_event: IpcMainEvent, url: string) => this.fontManager.openExternal(url)); this.on(ChannelType.IPC_RELOAD_WINDOW, async (_event: IpcMainEvent) => this.fontManager.reLaunch()); this.on(ChannelType.IPC_EXIT, async (_event: IpcMainEvent) => this.fontManager.exit()); this.on(ChannelType.IPC_QUIT, async (_event: IpcMainEvent) => this.fontManager.quit()); this.on(ChannelType.IPC_BEEP, async (_event: IpcMainEvent) => this.fontManager.beep()); // Collection this.handle(ChannelType.IPC_COLLECTION_FIND, async (_event: IpcMainEvent, args: any) => { return await this.connectionManager.getCollection().find(args); }); this.handle(ChannelType.IPC_COLLECTION_FIND_ONE, async (_event: IpcMainEvent, args: any) => { return await this.connectionManager.getCollectionRepository().findOne(args); }); this.handle(ChannelType.IPC_COLLECTION_FIND_ONE_BY, async (_event: IpcMainEvent, args: any) => { return await this.connectionManager.getCollectionRepository().findOneBy(args); }); this.handle(ChannelType.IPC_COLLECTION_FETCH, async (_event: IpcMainEvent, args: any) => { return await this.fetchCollectionsWithCounts(args); }); this.handle(ChannelType.IPC_COLLECTION_CREATE, async (_event: IpcMainEvent, args: any) => { await this.connectionManager.getCollectionRepository().createCollection(args); return await this.fetchCollectionsWithCounts(args); }); this.handle(ChannelType.IPC_COLLECTION_UPDATE, async (_event: IpcMainEvent, args: any) => { await this.connectionManager.getCollectionRepository().updateCollection(args.collectionId, args.data); return await this.fetchCollectionsWithCounts(args); }); this.handle(ChannelType.IPC_COLLECTION_DELETE, async (_event: IpcMainEvent, args: any) => { await this.connectionManager.getCollectionRepository().deleteCollection(args.collectionId); return await this.fetchCollectionsWithCounts(args); }); this.handle(ChannelType.IPC_COLLECTION_UPDATE_IDS, async (_event: IpcMainEvent, args: any) => { return await this.connectionManager.getCollectionRepository().updateCollectionIds(args.ids, args.data); }); this.handle(ChannelType.IPC_COLLECTION_MOVE, async (_event: IpcMainEvent, args: any) => { await this.connectionManager.getCollectionRepository().moveCollection(args.collectionId, args.newParentId, args.newIndex); return await this.fetchCollectionsWithCounts(args); }); this.handle(ChannelType.IPC_COLLECTION_ENABLE, async (_event: IpcMainEvent, args: any) => { await this.connectionManager.getCollectionRepository().resetEnabled(); await this.connectionManager.getCollectionRepository().updateCollection(args.collectionId, args.data); return await this.fetchCollectionsWithCounts(args); }); this.handle(ChannelType.IPC_COLLECTION_UPDATE_COUNT, async (_event: IpcMainEvent, collectionId: number) => { const { total } = await this.connectionManager.getStoreRepository().fetchCollectionCount(collectionId); await this.connectionManager.getCollectionRepository().updateCollectionCount(collectionId, total); return await this.fetchCollectionsWithCounts({}); }); this.handle(ChannelType.IPC_COLLECTION_UPDATE_COUNTS, async (_event: IpcMainEvent) => { const items = await this.connectionManager.getStoreRepository().fetchCollectionsCount(); await this.connectionManager.getCollectionRepository().updateCollectionCounts(items); return await this.fetchCollectionsWithCounts({}); }); // Smart Collection this.handle(ChannelType.IPC_SMART_COLLECTION_FIND, async (_event: IpcMainEvent) => { return await this.connectionManager.getSmartCollectionRepository().fetchAll(); }); this.handle(ChannelType.IPC_SMART_COLLECTION_CREATE, async (_event: IpcMainEvent, args: any) => { return await this.connectionManager.getSmartCollectionRepository().createSmartCollection(args); }); this.handle(ChannelType.IPC_SMART_COLLECTION_UPDATE, async (_event: IpcMainEvent, args: any) => { return await this.connectionManager.getSmartCollectionRepository().updateSmartCollection(args.id, args.data); }); this.handle(ChannelType.IPC_SMART_COLLECTION_DELETE, async (_event: IpcMainEvent, args: any) => { return await this.connectionManager.getSmartCollectionRepository().deleteSmartCollection(args.id); }); this.handle(ChannelType.IPC_SMART_COLLECTION_EVALUATE, async (_event: IpcMainEvent, args: any) => { const sc = await this.connectionManager.getSmartCollectionRepository().findOneBy({ id: args.id }); if (!sc) return [[], 0]; let rules; try { rules = JSON.parse(sc.rules); } catch { return [[], 0]; } return await this.connectionManager.getStoreRepository().evaluateSmartRules(rules, sc.match_type, { skip: args.skip, take: args.take, order: args.order, }); }); this.handle(ChannelType.IPC_SMART_COLLECTION_PREVIEW, async (_event: IpcMainEvent, args: any) => { let rules; try { rules = typeof args.rules === 'string' ? JSON.parse(args.rules) : args.rules; } catch { return [[], 0]; } return await this.connectionManager.getStoreRepository().evaluateSmartRules(rules, args.match_type ?? 'AND', {}); }); // Store this.handle(ChannelType.IPC_STORE_FIND, async (_event: IpcMainEvent, args: any) => { return await this.connectionManager.getStore().find(args); }); this.handle(ChannelType.IPC_STORE_FIND_ONE, async (_event: IpcMainEvent, args: any) => { return await this.connectionManager.getStoreRepository().findOne(args); }); this.handle(ChannelType.IPC_STORE_FIND_ONE_BY, async (_event: IpcMainEvent, args: any) => { return await this.connectionManager.getStoreRepository().findOneBy(args); }); this.handle(ChannelType.IPC_STORE_FETCH, async (_event: IpcMainEvent, args: any) => { args.ids = []; if (args.collectionId) { const row: Collection = await this.connectionManager.getCollectionRepository().findOneBy({ id: args.collectionId }); if (row) { const children = await this.connectionManager.getCollectionRepository().fetchChildren(row, true, false); if (Array.isArray(children)) { args.ids = Object.keys(children).map((val) => children[val].id); } } } return await this.connectionManager.getStoreRepository().fetch(args); }); this.handle(ChannelType.IPC_STORE_SEARCH, async (_event: IpcMainEvent, args: any) => { return await this.connectionManager.getStoreRepository().search(args); }); this.handle(ChannelType.IPC_STORE_UPDATE, async (_event: IpcMainEvent, args: any) => { await this.connectionManager.getStoreRepository().update(args.id, args.data); return await this.connectionManager.getStoreRepository().findOne({ where: { id: args.id } }); }); this.handle(ChannelType.IPC_STORE_SYNC_SYSTEM, async (_event: IpcMainEvent) => { await this.connectionManager.getStoreRepository().resetSystem(); const row: Collection = await this.connectionManager.getCollectionRepository().findOneBy({ is_system: 1 }); if (row) { const paths = this.systemManager.getPlatformFontPaths(); const promises = []; for (const folder of paths) { promises.push( this.fontManager .scanFolder(folder, { collection_id: row.id, system: 1 }) .catch((err: Error) => AppLogger.getInstance().error(err)), ); } await Promise.allSettled(promises); return await this.connectionManager.getStoreRepository().fetchSystemStats(); } }); this.handle(ChannelType.IPC_STORE_RESET_FAVORITES, async (_event: IpcMainEvent) => { await this.connectionManager.getStoreRepository().resetFavorites(); return await this.connectionManager.getStoreRepository().fetchSystemStats(); }); this.handle(ChannelType.IPC_STORE_SYSTEM_STATS, async (_event: IpcMainEvent) => { return await this.connectionManager.getStoreRepository().fetchSystemStats(); }); this.handle(ChannelType.IPC_FONT_METRICS, async (_event: IpcMainEvent, filePath: string) => { const fontObject = FontObject.fromCache(filePath); if (fontObject.hasError()) { return null; } const font = fontObject.getFont(); return { numGlyphs: font.numGlyphs ?? 0, unitsPerEm: font.unitsPerEm ?? 0, ascent: font.ascent ?? 0, descent: font.descent ?? 0, lineGap: font.lineGap ?? 0, }; }); this.handle(ChannelType.IPC_FONT_GLYPHS, async (_event: IpcMainEvent, filePath: string) => { const fontObject = FontObject.fromCache(filePath); if (fontObject.hasError()) { return []; } const font = fontObject.getFont(); return font.characterSet ? Array.from(font.characterSet).sort((a: number, b: number) => a - b) : []; }); // Logger this.handle(ChannelType.IPC_LOGGER_FIND, async (_event: IpcMainEvent, args: any) => { return await this.connectionManager.getLogger().find(args); }); this.handle(ChannelType.IPC_LOGGER_FIND_ONE, async (_event: IpcMainEvent, args: any) => { return await this.connectionManager.getLoggerRepository().findOne(args); }); this.handle(ChannelType.IPC_LOGGER_FIND_ONE_BY, async (_event: IpcMainEvent, args: any) => { return await this.connectionManager.getLoggerRepository().findOneBy(args); }); this.handle(ChannelType.IPC_LOGGER_CREATE, async (_event: IpcMainEvent, args: any) => { return await this.connectionManager.getLoggerRepository().saveData(args); }); this.handle(ChannelType.IPC_LOGGER_DELETE, async (_event: IpcMainEvent, args: any) => { return await this.connectionManager.getLogger().delete(args.id); }); this.handle(ChannelType.IPC_LOGGER_TRUNCATE, async (_event: IpcMainEvent) => { return await this.connectionManager.getLogger().clear(); }); } // Misc sendMessage(type: string, message: string) { return { type, message }; } async initSystemCollection() { const row = await this.connectionManager.getCollectionRepository().findOneBy({ is_system: 1 }); if (!row) { return await this.connectionManager.getCollectionRepository().createSystemCollection(); } return true; } async fetchCollectionsWithRelations() { return await this.connectionManager .getCollection() .find({ relations: { stores: true, }, }) .catch((err: Error) => AppLogger.getInstance().info(err.message)); } async fetchCollectionsWithCounts(args: any): Promise { return await this.connectionManager.getCollectionRepository().fetchCollectionsWithCounts(args); } }