diff --git a/server/2015Remote/FileManagerDlg.cpp b/server/2015Remote/FileManagerDlg.cpp index e7e14bc..811dea0 100644 --- a/server/2015Remote/FileManagerDlg.cpp +++ b/server/2015Remote/FileManagerDlg.cpp @@ -8,8 +8,13 @@ #include "InputDlg.h" #include "ZstdArchive.h" #include "2015RemoteDlg.h" +#include "CDlgFileSend.h" #include +// V2 接收用:定义在 CPasswordDlg.cpp,按本仓约定就地前置声明 +std::string GetPwdHash(); +std::string GetHMAC(int offset); + #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE @@ -176,6 +181,8 @@ BEGIN_MESSAGE_MAP(CFileManagerDlg, CDialog) ON_MESSAGE(WM_MY_MESSAGE, OnMyMessage) ON_MESSAGE(WM_LOCAL_SEARCH_DONE, OnLocalSearchDone) ON_MESSAGE(WM_LOCAL_SEARCH_PROGRESS, OnLocalSearchProgress) + ON_MESSAGE(WM_RECVFILEV2_CHUNK, &CFileManagerDlg::OnRecvFileV2Chunk) + ON_MESSAGE(WM_RECVFILEV2_COMPLETE, &CFileManagerDlg::OnRecvFileV2Complete) //}}AFX_MSG_MAP ON_COMMAND(ID_FILEMANGER_COMPRESS, &CFileManagerDlg::OnFilemangerCompress) ON_COMMAND(ID_FILEMANGER_UNCOMPRESS, &CFileManagerDlg::OnFilemangerUncompress) @@ -994,6 +1001,28 @@ void CFileManagerDlg::OnReceiveComplete() break; case TOKEN_CLIENTID: break; + case COMMAND_SEND_FILE_V2: + case COMMAND_FILE_COMPLETE_V2: { + // V2 下载(远程→本地):客户端把 chunk 通过 FileManager 子连接推回服务端。 + // 此函数在 NotifyProc -> worker 线程上调用。窗口创建/UI 操作必须回 UI 线程, + // 否则消息泵不通,进度框不会显示(落盘可在任意线程,影响仅限 UI)。 + // 拷贝数据后 PostMessage 回自己;UI 线程的 OnRecvFileV2Chunk/Complete 处理。 + LPBYTE buf = m_ContextObject->m_DeCompressionBuffer.GetBuffer(0); + unsigned len = m_ContextObject->m_DeCompressionBuffer.GetBufferLen(); + size_t minSize = (buf[0] == COMMAND_FILE_COMPLETE_V2) + ? sizeof(FileCompletePacketV2) : sizeof(FileChunkPacketV2); + if (len >= minSize) { + // 两种结构 cmd/transferID 偏移一致,可共用 FileChunkPacketV2 取 transferID + uint64_t transferID = ((FileChunkPacketV2*)buf)->transferID; + UINT msg = (buf[0] == COMMAND_FILE_COMPLETE_V2) + ? WM_RECVFILEV2_COMPLETE : WM_RECVFILEV2_CHUNK; + // 用 std::pair 当 wParam 载体,UI 端 delete + auto* payload = new std::pair, uint64_t>( + std::vector(buf, buf + len), transferID); + PostMessage(msg, (WPARAM)payload, 0); + } + break; + } default: SendException(); break; @@ -2682,10 +2711,87 @@ void CFileManagerDlg::OnLocalStop() void CFileManagerDlg::PostNcDestroy() { - // TODO: Add your specialized code here and/or call the base class + // 清理 V2 接收进度框:注意 CDialogBase::PostNcDestroy 写死 `delete this`, + // 对话框关窗时会自删——这里**不能** delete,否则双重释放。 + // 用 HWND 而非 ptr 判活,避免野指针;SendMessage(WM_CLOSE) 让它走自己关闭路径。 + for (auto& entry : m_FileRecvDlgs) { + HWND hWnd = entry.second.first; + if (hWnd && ::IsWindow(hWnd)) { + ::SendMessage(hWnd, WM_CLOSE, 0, 0); + } + } + m_FileRecvDlgs.clear(); __super::PostNcDestroy(); } +// V2 下载(远程→本地)chunk 处理:UI 线程上执行,安全创建/操作进度框 +LRESULT CFileManagerDlg::OnRecvFileV2Chunk(WPARAM wParam, LPARAM /*lParam*/) +{ + auto* payload = (std::pair, uint64_t>*)wParam; + if (!payload) return 0; + + BYTE* szBuffer = payload->first.data(); + size_t len = payload->first.size(); + uint64_t transferID = payload->second; + FileChunkPacketV2* pkt = (FileChunkPacketV2*)szBuffer; + + // 按 transferID 懒加载/复用进度框 + // 注意:CDlgFileSend 的 PostNcDestroy 自删(CDialogBase 默认行为), + // 窗口被自动关闭后 dlg 是野指针,HWND 失效是唯一可信号——重建即可, + // 旧 ptr 不再访问、不能 delete。 + auto& entry = m_FileRecvDlgs[transferID]; + CDlgFileSend* dlg = entry.second; + if (dlg == nullptr || !::IsWindow(entry.first)) { + dlg = new CDlgFileSend(g_2015RemoteDlg, m_ContextObject->GetServer(), + m_ContextObject, FALSE); + dlg->Create(IDD_DIALOG_FILESEND, GetDesktopWindow()); + dlg->SetWindowTextA(_TR("接收文件 (V2)")); + dlg->ShowWindow(SW_SHOW); + dlg->m_bKeepConnection = TRUE; // FileManager 子连接复用,对话框关闭时不断开 + entry = { dlg->GetSafeHwnd(), dlg }; + } + + // 落盘 + std::string hash = GetPwdHash(), hmac = GetHMAC(100); + int n = RecvFileChunkV2((char*)szBuffer, len, nullptr, nullptr, hash, hmac, 0); + if (n) { + Mprintf("[FileManager] RecvFileChunkV2 failed: %d\n", n); + } + + // 进度 + BYTE* name = szBuffer + sizeof(FileChunkPacketV2); + dlg->UpdateProgress(CString((char*)name, (int)pkt->nameLength), FileProgressInfo(pkt)); + + delete payload; + return 0; +} + +// V2 文件完成校验:UI 线程 +LRESULT CFileManagerDlg::OnRecvFileV2Complete(WPARAM wParam, LPARAM /*lParam*/) +{ + auto* payload = (std::pair, uint64_t>*)wParam; + if (!payload) return 0; + + BYTE* szBuffer = payload->first.data(); + size_t len = payload->first.size(); + uint64_t transferID = payload->second; + + bool verifyOk = HandleFileCompleteV2((const char*)szBuffer, len, 0); + Mprintf("[FileManager] V2 文件校验%s\n", verifyOk ? "通过" : "失败"); + + // 关闭对应进度框 + auto it = m_FileRecvDlgs.find(transferID); + if (it != m_FileRecvDlgs.end()) { + if (::IsWindow(it->second.first)) { + it->second.second->FinishFileSend(verifyOk); + } + m_FileRecvDlgs.erase(it); + } + + delete payload; + return 0; +} + void CFileManagerDlg::SendTransferMode() { CFileTransferModeDlg dlg(this); @@ -3211,18 +3317,19 @@ void CFileManagerDlg::OnTransferV2ToRemote() // 通知客户端目标目录(使用远程当前目录) // 由 SendFilesToClientV2 内部的 COMMAND_C2C_PREPARE 处理 - // 调用V2传输 - 需要通过IP找到主连接(m_ContextObject是子连接) - if (g_2015RemoteDlg && m_ContextObject) { - // 通过子连接的IP地址找到主连接 - std::string peerIP = m_ContextObject->GetPeerName(); - context* mainCtx = g_2015RemoteDlg->FindHostByIP(peerIP); + // 调用V2传输 - 通过 clientID 找主连接(m_ContextObject 是子连接)。 + // 不能用 GetPeerName() + FindHostByIP:NAT/frpc/反代场景下子连接的 socket peer + // 常是 127.0.0.1 或内网地址,跟主连接登录时存的 RES_CLIENT_PUBIP 对不上, + // 会找到错误的 ctx 或返回 NULL(剪贴板 V2 走 FindHost(clientID) 没此问题)。 + if (g_2015RemoteDlg) { + uint64_t clientID = GetClientID(); + context* mainCtx = clientID ? g_2015RemoteDlg->FindHost(clientID) : nullptr; if (mainCtx) { - // 使用远程当前目录作为目标目录 std::string remoteDir = m_Remote_Path.GetString(); g_2015RemoteDlg->SendFilesToClientV2(mainCtx, files, remoteDir); ShowMessage(_TRF("V2传输已启动,共 %d 个文件 -> %s"), (int)files.size(), remoteDir.c_str()); } else { - ShowMessage(_TRF("找不到主连接: %s"), peerIP.c_str()); + ShowMessage(_TRF("找不到主连接: clientID=%llu"), clientID); } } } diff --git a/server/2015Remote/FileManagerDlg.h b/server/2015Remote/FileManagerDlg.h index e5381c1..a345e4e 100644 --- a/server/2015Remote/FileManagerDlg.h +++ b/server/2015Remote/FileManagerDlg.h @@ -36,6 +36,8 @@ #define WM_MY_MESSAGE (WM_USER+300) #define WM_LOCAL_SEARCH_DONE (WM_USER+302) #define WM_LOCAL_SEARCH_PROGRESS (WM_USER+303) +#define WM_RECVFILEV2_CHUNK (WM_USER+304) +#define WM_RECVFILEV2_COMPLETE (WM_USER+305) // FileManagerDlg.h : header file // @@ -269,6 +271,15 @@ protected: void DropItemOnList(CListCtrl* pDragList, CListCtrl* pDropList); private: bool m_bIsUpload; // 是否是把本地主机传到远程上,标志方向位 + + // V2 下载(远程→本地):FileManager 子连接的 NotifyProc 在 worker 线程上 + // 直接调 OnReceiveComplete,不能在那里 new 进度框(窗口创建在 worker 线程 + // 没有消息泵,PostMessage 投不出去)。把 chunk 数据拷贝出来 PostMessage 回 + // 自己(UI 线程)处理,参考 ScreenSpyDlg 同样的模式。按 transferID 维护进度框。 + std::map> m_FileRecvDlgs; + afx_msg LRESULT OnRecvFileV2Chunk(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnRecvFileV2Complete(WPARAM wParam, LPARAM lParam); + bool MakeSureDirectoryPathExists(LPCTSTR pszDirPath); void SendTransferMode(); void SendFileData();