Files
SimpleRemoter/server/2015Remote/CDlgFileSend.cpp
T
yuanyuanxianganddeepseek-v4-pro da0ec983ba Feature: Show live transfer speed in file-transfer dialog title
Add a smoothed bytes-per-second readout to the CDlgFileSend title,
refreshed about every 200 ms during transfer (e.g. "发送文件 - 5.3M/s").
The base title is captured on the first progress frame and the speed
suffix applied through Unicode APIs (GetWindowTextW / SetWindowTextW),
so the whole title stays correct on non-Chinese systems where the ANSI
message path would misdecode GBK as the system ACP.

Also add the missing "接收文件 (V2)" translation (en_US "Receive File (V2)",
zh_TW "接收檔案 (V2)"), so the V2 receive title no longer falls back to
raw Chinese on non-Chinese systems.

Co-Authored-By: deepseek-v4-pro
2026-10-08 22:15:31 +02:00

301 lines
10 KiB
C++
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.
// CDlgFileSend.cpp: 实现文件
//
#include "stdafx.h"
#include "CDlgFileSend.h"
#include "2015RemoteDlg.h" // For g_2015RemoteDlg->FindHost()
// CDlgFileSend 对话框
IMPLEMENT_DYNAMIC(CDlgFileSend, CDialog)
CDlgFileSend::CDlgFileSend(CMy2015RemoteDlg* pParent, Server* IOCPServer, CONTEXT_OBJECT* ContextObject, BOOL sendFile)
: DialogBase(CDlgFileSend::IDD, pParent, IOCPServer, ContextObject, IDI_File), m_bIsSending(sendFile)
{
m_pParent = (CMy2015RemoteDlg*)pParent;
}
CDlgFileSend::~CDlgFileSend()
{
}
void CDlgFileSend::DoDataExchange(CDataExchange* pDX)
{
__super::DoDataExchange(pDX);
DDX_Control(pDX, IDC_PROGRESS_FILESEND, m_Progress);
}
BEGIN_MESSAGE_MAP(CDlgFileSend, CDialog)
ON_WM_CLOSE()
ON_WM_TIMER()
ON_MESSAGE(WM_UPDATEFILEPROGRESS, &CDlgFileSend::OnUpdateFileProgress)
ON_MESSAGE(WM_FINISHFILESEND, &CDlgFileSend::OnFinishFileSend)
END_MESSAGE_MAP()
// CDlgFileSend 消息处理程序
std::string GetPwdHash();
std::string GetHMAC(int offset);
void RecvData(void* ptr)
{
FileChunkPacket* pkt = (FileChunkPacket*)ptr;
}
void CDlgFileSend::OnReceiveComplete(void)
{
LPBYTE szBuffer = m_ContextObject->InDeCompressedBuffer.GetBuffer();
unsigned len = m_ContextObject->InDeCompressedBuffer.GetBufferLen();
if (len == 0) return;
BYTE cmd = szBuffer[0];
std::string hash = GetPwdHash(), hmac = GetHMAC(100);
// V2 文件完成校验包
if (cmd == COMMAND_FILE_COMPLETE_V2) {
if (len < sizeof(FileCompletePacketV2)) {
Mprintf("[FileSend] FILE_COMPLETE_V2 包太短: %u < %zu\n", len, sizeof(FileCompletePacketV2));
return;
}
FileCompletePacketV2* completePkt = (FileCompletePacketV2*)szBuffer;
// C2C 包:转发到目标客户端
if (completePkt->dstClientID != 0) {
context* dstCtx = m_pParent->FindHost(completePkt->dstClientID);
if (dstCtx) {
dstCtx->Send2Client(szBuffer, len);
Mprintf("[C2C] 转发校验包 -> 客户端 %llu\n", completePkt->dstClientID);
} else {
Mprintf("[C2C] 转发校验包失败: 目标客户端 %llu 不存在\n", completePkt->dstClientID);
}
return;
}
// 目标是服务端,本地校验
bool verifyOk = HandleFileCompleteV2((const char*)szBuffer, len, 0);
Mprintf("[FileSend] 文件校验%s\n", verifyOk ? "通过" : "失败");
return;
}
if (cmd == COMMAND_SEND_FILE_V2) {
// V2 协议
FileChunkPacketV2* chunk = (FileChunkPacketV2*)szBuffer;
// C2C 包:转发到目标客户端,同时更新进度
if (chunk->dstClientID != 0) {
// 如果已标记目标离线,直接忽略后续数据
if (m_bTargetOffline) {
return;
}
// 转发到目标客户端
context* dstCtx = m_pParent->FindHost(chunk->dstClientID);
if (dstCtx) {
dstCtx->Send2Client(szBuffer, len);
double percent = chunk->fileSize ? 100.0 * (chunk->offset + chunk->dataLength) / chunk->fileSize : 100.0;
Mprintf("[C2C] 转发 %d/%d: %.1f%% (%llu/%llu)\n",
1 + chunk->fileIndex, chunk->totalFiles, percent,
chunk->offset + chunk->dataLength, chunk->fileSize);
} else {
Mprintf("[C2C] CDlgFileSend 转发失败: 目标客户端 %llu 不存在,发送取消包\n", chunk->dstClientID);
m_bTargetOffline = TRUE;
// 发送取消包给发送方
FileResumePacketV2 cancelPkt = {};
cancelPkt.cmd = COMMAND_FILE_RESUME;
cancelPkt.transferID = chunk->transferID;
cancelPkt.srcClientID = 0; // 来自服务端
cancelPkt.dstClientID = chunk->srcClientID; // 回复给发送方
cancelPkt.fileIndex = chunk->fileIndex;
cancelPkt.fileSize = chunk->fileSize;
cancelPkt.receivedBytes = chunk->offset;
cancelPkt.flags = FFV2_CANCEL;
cancelPkt.rangeCount = 0;
m_ContextObject->Send2Client((LPBYTE)&cancelPkt, sizeof(cancelPkt));
// 关闭此连接
FinishFileSend(FALSE);
return;
}
BYTE* name = szBuffer + sizeof(FileChunkPacketV2);
UpdateProgress(CString((char*)name, (int)chunk->nameLength), FileProgressInfo(chunk));
return;
}
// 目标是服务端,本地接收
int n = RecvFileChunkV2((char*)szBuffer, len, nullptr, nullptr, hash, hmac, 0);
if (n) {
Mprintf("RecvFileChunkV2 failed: %d\n", n);
}
BYTE* name = szBuffer + sizeof(FileChunkPacketV2);
UpdateProgress(CString((char*)name, (int)chunk->nameLength), FileProgressInfo(chunk));
} else {
// V1 协议
CONNECT_ADDRESS addr = { 0 };
memcpy(addr.pwdHash, hash.c_str(), min(hash.length(), sizeof(addr.pwdHash)));
int n = RecvFileChunk((char*)szBuffer, len, &addr, RecvData, hash, hmac);
if (n) {
Mprintf("RecvFileChunk failed: %d. hash: %s, hmac: %s\n", n, hash.c_str(), hmac.c_str());
}
FileChunkPacket* chunk = (FileChunkPacket*)szBuffer;
BYTE* name = szBuffer + sizeof(FileChunkPacket);
UpdateProgress(CString((char*)name, chunk->nameLength), FileProgressInfo(chunk));
}
}
void CDlgFileSend::UpdateProgress(CString file, const FileProgressInfo& info)
{
if (!GetSafeHwnd()) return;
PostMessageA(WM_UPDATEFILEPROGRESS, (WPARAM)new CString(file), (LPARAM)new FileProgressInfo(info));
}
// 将字节/秒格式化为人类可读的速度串(如 "5.3M/s"、"320K/s"、"800B/s")
static CStringW FormatSpeed(double bps)
{
CStringW s;
if (bps >= 1024.0 * 1024.0)
s.Format(L"%.1fM/s", bps / (1024.0 * 1024.0));
else if (bps >= 1024.0)
s.Format(L"%.0fK/s", bps / 1024.0);
else
s.Format(L"%.0fB/s", bps);
return s;
}
// 累计字节并周期性刷新标题中的速度(如 "发送文件 - 5.3M/s")
void CDlgFileSend::UpdateTitleWithSpeed(uint64_t dataLength)
{
m_ullTotalBytes += dataLength;
ULONGLONG now = GetTickCount64();
// 首帧:用 Unicode API 捕获当前标题作为基础部分(可能已被 V2/C2C 覆盖为 "接收文件 (V2)" 等),
// 并建立采样基准;此时尚无速度可显示。
if (!m_bBaseTitleCaptured) {
WCHAR wBase[128] = { 0 };
::GetWindowTextW(m_hWnd, wBase, 128);
m_strBaseTitle = wBase;
m_bBaseTitleCaptured = true;
m_ullLastTick = now;
m_ullLastBytes = m_ullTotalBytes;
return;
}
ULONGLONG dt = now - m_ullLastTick;
if (dt < 200) return; // 采样间隔未到,仅累积字节
uint64_t delta = m_ullTotalBytes - m_ullLastBytes;
double speed = dt ? (double)delta * 1000.0 / (double)dt : 0.0;
m_dSpeedBps = (m_dSpeedBps == 0.0) ? speed : m_dSpeedBps * 0.6 + speed * 0.4; // 轻度平滑
m_ullLastTick = now;
m_ullLastBytes = m_ullTotalBytes;
// 基础标题(Unicode)+ ASCII 速度后缀,全程 Unicode API,避免非中文系统上 ANSI 消息按系统 ACP 解码乱码
CStringW title = m_strBaseTitle;
title += L" - ";
title += FormatSpeed(m_dSpeedBps);
::SetWindowTextW(m_hWnd, title);
}
void CDlgFileSend::FinishFileSend(BOOL succeed)
{
if (!GetSafeHwnd()) return;
PostMessageA(WM_FINISHFILESEND, NULL, (LPARAM)succeed);
}
LRESULT CDlgFileSend::OnUpdateFileProgress(WPARAM wParam, LPARAM lParam)
{
CString* pFile = (CString*)wParam;
FileProgressInfo* pInfo = (FileProgressInfo*)lParam;
CString status;
double percent = pInfo->fileSize ? (pInfo->offset + pInfo->dataLength) * 100. / pInfo->fileSize : 100.;
m_bIsSending ?
status.FormatL("发送文件(%d/%d): %.2f%%", 1 + pInfo->fileIndex, pInfo->totalFiles, percent):
status.FormatL("接收文件(%d/%d): %.2f%%", 1 + pInfo->fileIndex, pInfo->totalFiles, percent);
SetDlgItemText(IDC_STATIC_CURRENTINDEX, status);
SetDlgItemText(IDC_STATIC_CURRENT_FILE, *pFile);
m_Progress.SetPos((int)percent);
UpdateTitleWithSpeed(pInfo->dataLength);
// 只在第一次显示时置顶,后续更新不抢焦点
if (!IsWindowVisible()) {
ShowWindow(SW_SHOW);
SetWindowPos(&wndTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
// 只有最后一个文件完成时才设置自动关闭定时器
// 避免多文件传输时第一个文件完成后就关闭对话框
bool isLastFile = (pInfo->fileIndex + 1 >= pInfo->totalFiles);
if (percent >= 100. && isLastFile) {
SetTimer(1, 3000, NULL);
} else {
// 传输新文件时取消之前的定时器
KillTimer(1);
}
delete pInfo;
delete pFile;
return 0;
}
LRESULT CDlgFileSend::OnFinishFileSend(WPARAM wParam, LPARAM lParam)
{
BOOL success = (BOOL)lParam;
m_bIsSending ?
SetDlgItemText(IDC_STATIC_CURRENTINDEX, success ? _TR("文件发送完成") : _TR("文件发送失败")):
SetDlgItemText(IDC_STATIC_CURRENTINDEX, success ? _TR("文件接收完成") : _TR("文件接收失败"));
if (success)
m_Progress.SetPos(100);
// 完成后取消置顶,恢复普通窗口
SetWindowPos(&wndNoTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
ShowWindow(SW_SHOW);
SetTimer(1, 3000, NULL); // 3秒后自动关闭
return 0;
}
BOOL CDlgFileSend::OnInitDialog()
{
DialogBase::OnInitDialog();
// 多语言翻译 - Static控件
SetDlgItemText(IDC_STATIC_CURRENTINDEX, _TR("发送文件(999/999):"));
SetIcon(m_hIcon, FALSE);
SetWindowText(m_bIsSending ? _TR("发送文件") : _TR("接收文件"));
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != nullptr) {
pSysMenu->EnableMenuItem(SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
}
return TRUE;
}
void CDlgFileSend::OnClose()
{
// V2传输使用主连接,关闭对话框时不应断开连接
if (!m_bKeepConnection) {
CancelIO();
}
// 等待数据处理完毕
if (IsProcessing()) {
ShowWindow(SW_HIDE);
return;
}
DialogBase::OnClose();
}
void CDlgFileSend::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == 1) {
KillTimer(1);
PostMessageA(WM_CLOSE, 0, 0);
}
}