AssetPro/electron/ipc.ts

570 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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}`
})
})
})
})
}