Fix: V2 file transfer broken via FileManager dialog (both directions)

This commit is contained in:
yuanyuanxiang
2026-05-10 13:50:04 +02:00
parent d46176f4ef
commit 153cbddcf6
2 changed files with 126 additions and 8 deletions

View File

@@ -8,8 +8,13 @@
#include "InputDlg.h"
#include "ZstdArchive.h"
#include "2015RemoteDlg.h"
#include "CDlgFileSend.h"
#include <Shlobj.h>
// 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<vector,uint64> 当 wParam 载体UI 端 delete
auto* payload = new std::pair<std::vector<BYTE>, uint64_t>(
std::vector<BYTE>(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<std::vector<BYTE>, 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<std::vector<BYTE>, 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() + FindHostByIPNAT/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);
}
}
}

View File

@@ -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<uint64_t, std::pair<HWND, class CDlgFileSend*>> 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();