import { AuthenticatedWebSocket } from '../types'; import { clientStates, sftpService } from '../state'; import WebSocket from 'ws'; export async function handleSftpOperation( ws: AuthenticatedWebSocket, type: string, payload: any, requestId?: string ): Promise { const sessionId = ws.sessionId; const state = sessionId ? clientStates.get(sessionId) : undefined; if (!sessionId || !state) { console.warn(`WebSocket: 收到来自 ${ws.username} 的 SFTP 请求 (${type}),但无活动会话。`); const errPayload: { message: string; requestId?: string } = { message: '无效的会话' }; if (requestId) errPayload.requestId = requestId; if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'sftp_error', payload: errPayload })); return; } if (!requestId) { console.error(`WebSocket: 收到来自 ${ws.username} (会话: ${sessionId}) 的 SFTP 请求 (${type}),但缺少 requestId。`); if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'sftp_error', payload: { message: `SFTP 操作 ${type} 缺少 requestId` } })); return; } try { switch (type) { case 'sftp:readdir': if (payload?.path) sftpService.readdir(sessionId, payload.path, requestId); else throw new Error("Missing 'path' in payload for readdir"); break; case 'sftp:stat': if (payload?.path) sftpService.stat(sessionId, payload.path, requestId); else throw new Error("Missing 'path' in payload for stat"); break; case 'sftp:readfile': if (payload?.path) { const requestedEncoding = payload?.encoding; sftpService.readFile(sessionId, payload.path, requestId, requestedEncoding); } else { throw new Error("Missing 'path' in payload for readfile"); } break; case 'sftp:writefile': const fileContent = payload?.content ?? payload?.data ?? ''; const encoding = payload?.encoding; if (payload?.path) { const dataToSend = (typeof fileContent === 'string') ? fileContent : ''; sftpService.writefile(sessionId, payload.path, dataToSend, requestId, encoding); } else throw new Error("Missing 'path' in payload for writefile"); break; case 'sftp:mkdir': if (payload?.path) sftpService.mkdir(sessionId, payload.path, requestId); else throw new Error("Missing 'path' in payload for mkdir"); break; case 'sftp:rmdir': if (payload?.path) sftpService.rmdir(sessionId, payload.path, requestId); else throw new Error("Missing 'path' in payload for rmdir"); break; case 'sftp:unlink': if (payload?.path) sftpService.unlink(sessionId, payload.path, requestId); else throw new Error("Missing 'path' in payload for unlink"); break; case 'sftp:rename': if (payload?.oldPath && payload?.newPath) sftpService.rename(sessionId, payload.oldPath, payload.newPath, requestId); else throw new Error("Missing 'oldPath' or 'newPath' in payload for rename"); break; case 'sftp:chmod': if (payload?.path && typeof payload?.mode === 'number') sftpService.chmod(sessionId, payload.path, payload.mode, requestId); else throw new Error("Missing 'path' or invalid 'mode' in payload for chmod"); break; case 'sftp:realpath': if (payload?.path) sftpService.realpath(sessionId, payload.path, requestId); else throw new Error("Missing 'path' in payload for realpath"); break; case 'sftp:copy': if (Array.isArray(payload?.sources) && payload?.destination) { sftpService.copy(sessionId, payload.sources, payload.destination, requestId); } else throw new Error("Missing 'sources' (array) or 'destination' in payload for copy"); break; case 'sftp:move': if (Array.isArray(payload?.sources) && payload?.destination) { sftpService.move(sessionId, payload.sources, payload.destination, requestId); } else throw new Error("Missing 'sources' (array) or 'destination' in payload for move"); break; case 'sftp:compress': if (Array.isArray(payload?.sources) && payload?.destination && payload?.format && requestId) { const destinationPath = payload.destination as string; // 从 destinationPath 中提取 targetDirectory 和 destinationArchiveName // pathModule.posix 总是使用 / 作为分隔符 const pathModule = await import('path'); // 动态导入 path 模块 const targetDirectory = pathModule.posix.dirname(destinationPath); const destinationArchiveName = pathModule.posix.basename(destinationPath); const compressPayload = { sources: payload.sources as string[], destinationArchiveName: destinationArchiveName, format: payload.format as 'zip' | 'targz' | 'tarbz2', targetDirectory: targetDirectory, requestId: requestId }; sftpService.compress(sessionId, compressPayload); } else throw new Error("Missing 'sources' (array), 'destination', 'format', or 'requestId' in payload for compress"); break; case 'sftp:decompress': if (payload?.source && requestId) { const decompressPayload = { archivePath: payload.source as string, // destinationDirectory: payload.destination as string, // sftpService.decompress 目前不使用此参数 requestId: requestId }; sftpService.decompress(sessionId, decompressPayload); } else throw new Error("Missing 'source' or 'requestId' in payload for decompress"); break; default: console.warn(`WebSocket: Received unhandled SFTP message type in sftp.handler: ${type}`); if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'sftp_error', payload: { message: `内部未处理的 SFTP 类型: ${type}`, requestId } })); throw new Error(`Unhandled SFTP type: ${type}`); } } catch (sftpCallError: any) { console.error(`WebSocket: Error preparing/calling SFTP service for ${type} (Request ID: ${requestId}):`, sftpCallError); if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'sftp_error', payload: { message: `处理 SFTP 请求 ${type} 时出错: ${sftpCallError.message}`, requestId } })); } } export function handleSftpUploadStart(ws: AuthenticatedWebSocket, payload: any): void { const sessionId = ws.sessionId; const state = sessionId ? clientStates.get(sessionId) : undefined; if (!sessionId || !state) { console.warn(`WebSocket: 收到来自 ${ws.username} 的 SFTP 上传开始请求,但无活动会话。`); if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'sftp:upload:error', payload: { uploadId: payload?.uploadId, message: '无效的会话' } })); return; } if (!payload?.uploadId || !payload?.remotePath || typeof payload?.size !== 'number') { console.error(`WebSocket: 收到来自 ${ws.username} (会话: ${sessionId}) 的 sftp:upload:start 请求,但缺少 uploadId, remotePath 或 size。`); if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'sftp:upload:error', payload: { uploadId: payload?.uploadId, message: '缺少 uploadId, remotePath 或 size' } })); return; } const relativePath = payload?.relativePath; console.log(`WebSocket: SFTP Upload Start - Session: ${sessionId}, UploadID: ${payload.uploadId}, RemotePath: ${payload.remotePath}, Size: ${payload.size}, RelativePath: ${relativePath}`); sftpService.startUpload(sessionId, payload.uploadId, payload.remotePath, payload.size, relativePath); } export async function handleSftpUploadChunk(ws: AuthenticatedWebSocket, payload: any): Promise { const sessionId = ws.sessionId; const state = sessionId ? clientStates.get(sessionId) : undefined; if (!sessionId || !state) return; // Silently ignore if session is gone if (!payload?.uploadId || typeof payload?.chunkIndex !== 'number' || !payload?.data) { console.error(`WebSocket: 收到来自 ${ws.username} (会话: ${sessionId}) 的 sftp:upload:chunk 请求,但缺少 uploadId, chunkIndex 或 data。`); // Optionally send error to client, but be mindful of flooding for many chunks return; } await sftpService.handleUploadChunk(sessionId, payload.uploadId, payload.chunkIndex, payload.data); } export function handleSftpUploadCancel(ws: AuthenticatedWebSocket, payload: any): void { const sessionId = ws.sessionId; const state = sessionId ? clientStates.get(sessionId) : undefined; if (!sessionId || !state) return; // Silently ignore if (!payload?.uploadId) { console.error(`WebSocket: 收到来自 ${ws.username} (会话: ${sessionId}) 的 sftp:upload:cancel 请求,但缺少 uploadId。`); if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'sftp:upload:error', payload: { uploadId: payload?.uploadId, message: '缺少 uploadId' } })); return; } sftpService.cancelUpload(sessionId, payload.uploadId); }