/** * Zmodem IPC Handlers * 处理 rz/sz 文件传输 */ import { ipcMain, IpcMainInvokeEvent, app, dialog } from 'electron' import fs from 'fs' import path from 'path' import type { SSHService } from '../services/ssh-service' import type { ZmodemService } from '../services/zmodem-service' import { createLogger } from '../utils/logger' const logger = createLogger('Zmodem_IPC') // IPC channel names export const ZMODEM_CHANNELS = { SEND: 'zmodem:send', RECEIVE: 'zmodem:receive', CANCEL: 'zmodem:cancel', PROGRESS: 'zmodem:progress', COMPLETE: 'zmodem:complete', } as const export interface ZmodemIPCOptions { sshService: SSHService zmodemService: ZmodemService mainWindowGetter: () => Electron.BrowserWindow | null } export function registerZmodemIPCHandlers(options: ZmodemIPCOptions): void { const { sshService, zmodemService, mainWindowGetter } = options // Zmodem send (rz) - send file to server ipcMain.handle(ZMODEM_CHANNELS.SEND, async (_event: IpcMainInvokeEvent, shellId: string) => { try { const mainWindow = mainWindowGetter() if (!mainWindow) { return { success: false, error: 'Window not available' } } // Open file dialog to select file const result = await dialog.showOpenDialog(mainWindow, { title: '选择要发送的文件', properties: ['openFile'], }) if (result.canceled || !result.filePaths.length) { return { success: false, canceled: true } } const filePath = result.filePaths[0] const fileName = path.basename(filePath) // Read file const fileData = fs.readFileSync(filePath) // Get shell write function const shell = sshService.getShell(shellId) if (!shell) { return { success: false, error: 'Shell not found' } } // Send file via Zmodem protocol const sessionId = zmodemService.sendFile( shellId, fileName, fileData, (data) => { shell.channel.write(data) }, (progress) => { mainWindow.webContents.send(ZMODEM_CHANNELS.PROGRESS, sessionId, progress) }, (success, error) => { mainWindow.webContents.send(ZMODEM_CHANNELS.COMPLETE, sessionId, { success, error }) } ) return { success: true, sessionId, fileName } } catch (error: any) { logger.error('Zmodem send error:', error) return { success: false, error: error.message } } }) // Zmodem receive (sz) - receive file from server ipcMain.handle(ZMODEM_CHANNELS.RECEIVE, async (_event: IpcMainInvokeEvent, shellId: string) => { try { const mainWindow = mainWindowGetter() if (!mainWindow) { return { success: false, error: 'Window not available' } } // Get shell to monitor for incoming data const shell = sshService.getShell(shellId) if (!shell) { return { success: false, error: 'Shell not found' } } // Open save dialog to select destination const result = await dialog.showSaveDialog(mainWindow, { title: '保存文件', defaultPath: app.getPath('downloads'), }) if (result.canceled || !result.filePath) { return { success: false, canceled: true } } // Start Zmodem receive session const sessionId = zmodemService.receiveFile( shellId, result.filePath, (data) => { shell.channel.write(data) }, (progress) => { mainWindow.webContents.send(ZMODEM_CHANNELS.PROGRESS, sessionId, progress) }, (success, error) => { mainWindow.webContents.send(ZMODEM_CHANNELS.COMPLETE, sessionId, { success, error }) } ) return { success: true, sessionId } } catch (error: any) { logger.error('Zmodem receive error:', error) return { success: false, error: error.message } } }) // Zmodem cancel transfer ipcMain.handle(ZMODEM_CHANNELS.CANCEL, async (_event: IpcMainInvokeEvent, sessionId: string) => { try { zmodemService.cancelSession(sessionId) return { success: true } } catch (error: any) { return { success: false, error: error.message } } }) }