/** * IPC 通信处理器 * 安全地暴露主进程功能给渲染进程 */ import { ipcMain, app, BrowserWindow, dialog } from 'electron' import { promises as fs } from 'fs' import * as fsSync from 'fs' import { exec } from 'child_process' import util from 'util' import path from 'path' import { getSettings, saveSettings } from './store' import { cacheManager } from './cache-manager' // IPC 通道名称常量 export const IPC_CHANNELS = { // 文件操作 SAVE_FILE: 'file:save', READ_FILE: 'file:read', // 设置操作 GET_SETTINGS: 'settings:get', SAVE_SETTINGS: 'settings:save', // 缓存操作 CACHE_SET_JSON: 'cache:setJSON', CACHE_GET_JSON: 'cache:getJSON', CACHE_SET_BINARY: 'cache:setBinary', CACHE_GET_BINARY: 'cache:getBinary', CACHE_HAS: 'cache:has', CACHE_DELETE: 'cache:delete', CACHE_CLEAR_NAMESPACE: 'cache:clearNamespace', CACHE_CLEAR_ALL: 'cache:clearAll', CACHE_GET_STATS: 'cache:getStats', // 窗口操作 WINDOW_MINIMIZE: 'window:minimize', WINDOW_MAXIMIZE: 'window:maximize', WINDOW_CLOSE: 'window:close', // Git/System 操作 DIALOG_OPEN_DIRECTORY: 'dialog:openDirectory', GIT_CLONE: 'git:clone' } as const /** * 设置所有 IPC 处理器 */ export function setupIpcHandlers() { // ============================================ // 文件操作相关 IPC // ============================================ /** * 保存文件到桌面 * @param content - 要保存的文件内容 * @param filename - 文件名(默认为 demo-note.txt) * @returns 保存结果 */ ipcMain.handle( IPC_CHANNELS.SAVE_FILE, async (_event, content: string, filename: string = 'demo-note.txt') => { try { // 获取桌面路径 const desktopPath = app.getPath('desktop') const filePath = path.join(desktopPath, filename) // 写入文件(使用 UTF-8 编码) await fs.writeFile(filePath, content, 'utf-8') return { success: true, message: `文件已保存到: ${filePath}`, path: filePath } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('保存文件失败:', errorMessage) return { success: false, message: `保存失败: ${errorMessage}`, path: null } } } ) /** * 从桌面读取文件 * @param filename - 文件名(默认为 demo-note.txt) * @returns 文件内容 */ ipcMain.handle( IPC_CHANNELS.READ_FILE, async (_event, filename: string = 'demo-note.txt') => { try { // 获取桌面路径 const desktopPath = app.getPath('desktop') const filePath = path.join(desktopPath, filename) // 检查文件是否存在 try { await fs.access(filePath) } catch { return { success: false, message: '文件不存在,请先保存一个文件', content: null } } // 读取文件内容 const content = await fs.readFile(filePath, 'utf-8') return { success: true, message: '文件读取成功', content } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('读取文件失败:', errorMessage) return { success: false, message: `读取失败: ${errorMessage}`, content: null } } } ) // ============================================ // 用户设置相关 IPC // ============================================ /** * 获取用户设置 * @param key - 可选的设置键名,不传则返回所有设置 * @returns 设置值 */ ipcMain.handle(IPC_CHANNELS.GET_SETTINGS, async (_event, key?: string) => { try { const settings = getSettings(key) return { success: true, data: settings } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('获取设置失败:', errorMessage) return { success: false, data: null, message: errorMessage } } }) /** * 保存用户设置 * @param key - 设置键名 * @param value - 设置值 * @returns 保存结果 */ ipcMain.handle( IPC_CHANNELS.SAVE_SETTINGS, async (_event, key: string, value: unknown) => { try { saveSettings(key, value) return { success: true, message: '设置已保存' } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('保存设置失败:', errorMessage) return { success: false, message: `保存失败: ${errorMessage}` } } } ) // ============================================ // 缓存操作相关 IPC // ============================================ /** * 设置JSON缓存 */ ipcMain.handle( IPC_CHANNELS.CACHE_SET_JSON, async (_event, key: string, data: any, config?: { maxAge?: number; namespace?: string }) => { try { await cacheManager.setJSON(key, data, config) return { success: true, message: '缓存已设置' } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('设置JSON缓存失败:', errorMessage) return { success: false, message: `设置失败: ${errorMessage}` } } } ) /** * 获取JSON缓存 */ ipcMain.handle( IPC_CHANNELS.CACHE_GET_JSON, async (_event, key: string, config?: { maxAge?: number; namespace?: string }) => { try { const data = await cacheManager.getJSON(key, config) return { success: true, data } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('获取JSON缓存失败:', errorMessage) return { success: false, data: null, message: errorMessage } } } ) /** * 设置二进制缓存 */ ipcMain.handle( IPC_CHANNELS.CACHE_SET_BINARY, async (_event, key: string, buffer: Buffer, type: 'binary' | 'image' = 'binary', config?: { maxAge?: number; namespace?: string }) => { try { await cacheManager.setBinary(key, buffer, type, config) return { success: true, message: '二进制缓存已设置' } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('设置二进制缓存失败:', errorMessage) return { success: false, message: `设置失败: ${errorMessage}` } } } ) /** * 获取二进制缓存 */ ipcMain.handle( IPC_CHANNELS.CACHE_GET_BINARY, async (_event, key: string, config?: { maxAge?: number; namespace?: string }) => { try { const buffer = await cacheManager.getBinary(key, config) return { success: true, data: buffer } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('获取二进制缓存失败:', errorMessage) return { success: false, data: null, message: errorMessage } } } ) /** * 检查缓存是否存在 */ ipcMain.handle( IPC_CHANNELS.CACHE_HAS, async (_event, key: string, config?: { namespace?: string }) => { try { const exists = await cacheManager.has(key, config) return { success: true, exists } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('检查缓存失败:', errorMessage) return { success: false, exists: false, message: errorMessage } } } ) /** * 删除单个缓存 */ ipcMain.handle( IPC_CHANNELS.CACHE_DELETE, async (_event, key: string, namespace?: string) => { try { await cacheManager.delete(key, namespace) return { success: true, message: '缓存已删除' } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('删除缓存失败:', errorMessage) return { success: false, message: `删除失败: ${errorMessage}` } } } ) /** * 清空命名空间缓存 */ ipcMain.handle( IPC_CHANNELS.CACHE_CLEAR_NAMESPACE, async (_event, namespace: string) => { try { await cacheManager.clearNamespace(namespace) return { success: true, message: `命名空间 ${namespace} 的缓存已清空` } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('清空命名空间缓存失败:', errorMessage) return { success: false, message: `清空失败: ${errorMessage}` } } } ) /** * 清空所有缓存 */ ipcMain.handle( IPC_CHANNELS.CACHE_CLEAR_ALL, async (_event) => { try { await cacheManager.clearAll() return { success: true, message: '所有缓存已清空' } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('清空所有缓存失败:', errorMessage) return { success: false, message: `清空失败: ${errorMessage}` } } } ) /** * 获取缓存统计信息 */ ipcMain.handle( IPC_CHANNELS.CACHE_GET_STATS, async (_event) => { try { const stats = await cacheManager.getStats() return { success: true, data: stats } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('获取缓存统计信息失败:', errorMessage) return { success: false, data: null, message: errorMessage } } } ) // ============================================ // 窗口操作相关 IPC // ============================================ /** * 最小化窗口 */ ipcMain.handle(IPC_CHANNELS.WINDOW_MINIMIZE, async (event) => { try { const window = BrowserWindow.fromWebContents(event.sender) if (window) { window.minimize() return { success: true } } return { success: false, message: '无法找到窗口' } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('最小化窗口失败:', errorMessage) return { success: false, message: errorMessage } } }) /** * 最大化/还原窗口 */ ipcMain.handle(IPC_CHANNELS.WINDOW_MAXIMIZE, async (event) => { try { const window = BrowserWindow.fromWebContents(event.sender) if (window) { if (window.isMaximized()) { window.unmaximize() } else { window.maximize() } return { success: true, isMaximized: window.isMaximized() } } return { success: false, message: '无法找到窗口' } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('最大化窗口失败:', errorMessage) return { success: false, message: errorMessage } } }) /** * 关闭窗口 */ ipcMain.handle(IPC_CHANNELS.WINDOW_CLOSE, async (event) => { try { const window = BrowserWindow.fromWebContents(event.sender) if (window) { window.close() return { success: true } } return { success: false, message: '无法找到窗口' } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('关闭窗口失败:', errorMessage) return { success: false, message: errorMessage } } }) // ============================================ // Git/System 操作相关 IPC // ============================================ /** * 打开目录选择对话框 */ ipcMain.handle(IPC_CHANNELS.DIALOG_OPEN_DIRECTORY, async (event) => { try { const window = BrowserWindow.fromWebContents(event.sender) if (!window) return { success: false, message: '无法找到窗口' } const result = await dialog.showOpenDialog(window, { properties: ['openDirectory', 'createDirectory'] }) if (result.canceled) { return { success: false, canceled: true } } return { success: true, path: result.filePaths[0], canceled: false } } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('打开目录失败:', errorMessage) return { success: false, message: errorMessage } } }) /** * 执行 Git Clone (使用 spawn 以支持进度流) */ ipcMain.handle(IPC_CHANNELS.GIT_CLONE, async (event, repoUrl: string, targetDir: string) => { return new Promise((resolve) => { const window = BrowserWindow.fromWebContents(event.sender) // 检查目录是否存在 try { if (!fsSync.existsSync(targetDir)) { // 使用同步检查避免 async/await 复杂性 fsSync.mkdirSync(targetDir, { recursive: true }) } else { // 如果目录存在,必须为空 const files = fsSync.readdirSync(targetDir) if (files.length > 0) { return resolve({ success: false, message: `目标文件夹不为空。请选择一个空文件夹。` }) } } } catch (err) { return resolve({ success: false, message: `检查目录失败: ${err}` }) } console.log(`开始 Git Clone (spawn): ${repoUrl} -> ${targetDir}`) // 这里的 spawn 命令不需要 util.promisify const { spawn } = require('child_process') const child = spawn('git', ['clone', '--progress', repoUrl, '.'], { cwd: targetDir }) let stdoutData = '' let stderrData = '' child.stdout.on('data', (data: any) => { const text = data.toString() stdoutData += text // git 的标准输出通常是静默的,除非有错误或特定配置 console.log('Git Stdout:', text) }) child.stderr.on('data', (data: any) => { const text = data.toString() stderrData += text // 发送进度事件到前端 if (window) { window.webContents.send('git:progress', { repoUrl, raw: text }) } }) child.on('close', (code: number) => { console.log(`Git clone process exited with code ${code}`) if (code === 0) { resolve({ success: true, message: '项目克隆成功', stdout: stdoutData }) } else { resolve({ success: false, message: `Git clone 失败 (Code ${code})`, stdout: stderrData // git 错误通常在 stderr }) } }) child.on('error', (err: any) => { console.error('Git spawn error:', err) resolve({ success: false, message: `启动 Git 进程失败: ${err.message}` }) }) }) }) }