Files
SimpleRemoter/client/FileManager.cpp
T
yuanyuanxianganddeepseek-v4-pro 76decec0fe Feature: Add file-transfer speed dialog with multi-language support
Add a novice-friendly "File Transfer Speed" dialog under the Advanced menu
that controls file-transfer speed in both directions without touching
SimplePlugins. Speed is passed through InitFileUpload's existing
chunkSizeKb/sendDurationMs arguments, so the precompiled library stays
untouched. Seven presets run from ~0.3 MB/s up to a new 10 MB/s tier plus an
unlimited tier; the chosen value is stored in [transfer] ChunkKb/DurationMs.

Upload reads THIS_CFG at the master's InitFileUpload (2015RemoteDlg.cpp:2225)
and takes effect on restart. Download is pushed to the client over a new
CMD_FILE_SPEED (252) command, sent from OnOnlineFileManager and
OnOnlineDesktopManager before each session opens, and read by all four client
InitFileUpload call sites via GetTransferSpeed so the process-global refcount
cannot latch a stale speed. All Chinese strings go through LangManager, with
en_US/zh_TW/de_DE entries added for the tier names, format strings and hint
text.

Co-Authored-By: deepseek-v4-pro
2026-10-02 19:57:57 +02:00

1196 lines
38 KiB
C++

// FileManager.cpp: implementation of the CFileManager class.
//
//////////////////////////////////////////////////////////////////////
#include "FileManager.h"
#include <shellapi.h>
#include <thread>
#include "ZstdArchive.h"
#include "file_upload.h"
#include "IOCPClient.h"
#include "KernelManager.h"
#if ENABLE_FILE_MNG
typedef struct {
DWORD dwSizeHigh;
DWORD dwSizeLow;
} FILESIZE;
struct SearchParam {
CFileManager *pThis;
char szPath[MAX_PATH];
char szName[MAX_PATH];
};
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CFileManager::CFileManager(CClientSocket *pClient, int h, void* user):CManager(pClient)
{
m_nTransferMode = TRANSFER_MODE_NORMAL;
m_bSearching = false;
m_hSearchThread = NULL;
// 初始化V2文件传输模块
CKernelManager* main = (CKernelManager*)pClient->GetMain();
m_Signature = main ? main->m_LoginSignature : pClient->m_LoginSignature;
if (!m_Signature.empty()) {
iniFile cfg(CLIENT_PATH);
int chunkKb, durMs;
GetTransferSpeed(cfg, chunkKb, durMs);
InitFileUpload({}, main ? main->m_LoginMsg : pClient->m_LoginMsg,
main ? main->m_LoginSignature : pClient->m_LoginSignature, chunkKb, durMs, Logf);
}
// 发送驱动器列表, 开始进行文件管理,建立新线程
SendDriveList();
}
CFileManager::~CFileManager()
{
m_bSearching = false;
if (m_hSearchThread) {
WaitForSingleObject(m_hSearchThread, 5000);
SAFE_CLOSE_HANDLE(m_hSearchThread);
}
m_UploadList.clear();
if (!m_Signature.empty())
UninitFileUpload();
}
// 展开环境变量 (如 %USERPROFILE%, %DESKTOP% 等)
static std::string ExpandPath(const char* path)
{
char szExpanded[MAX_PATH];
DWORD len = ExpandEnvironmentStringsA(path, szExpanded, MAX_PATH);
if (len > 0 && len < MAX_PATH) {
return szExpanded;
}
return path; // 展开失败则返回原路径
}
std::string GetExtractDir(const std::string& archivePath)
{
if (archivePath.size() >= 5) {
std::string ext = archivePath.substr(archivePath.size() - 5);
for (char& c : ext) c = tolower(c);
if (ext == ".zsta") {
return archivePath.substr(0, archivePath.size() - 5);
}
}
return archivePath + "_extract";
}
std::string GetDirectory(const std::string& filePath)
{
size_t pos = filePath.find_last_of("/\\");
if (pos != std::string::npos) {
return filePath.substr(0, pos);
}
return ".";
}
VOID CFileManager::OnReceive(PBYTE lpBuffer, ULONG nSize)
{
switch (lpBuffer[0]) {
case CMD_COMPRESS_FILES: {
std::vector<std::string> paths = ParseMultiStringPath((char*)lpBuffer + 1, nSize - 1);
// 展开所有路径中的环境变量
for (size_t i = 0; i < paths.size(); i++) {
paths[i] = ExpandPath(paths[i].c_str());
}
zsta::Error err = zsta::CZstdArchive::Compress(std::vector<std::string>(paths.begin() + 1, paths.end()), paths.at(0));
if (err != zsta::Error::Success) {
Mprintf("压缩失败: %s\n", zsta::CZstdArchive::GetErrorString(err));
} else {
std::string dir = GetDirectory(paths.at(0));
SendFilesList((char*)dir.c_str());
}
break;
}
case CMD_UNCOMPRESS_FILES: {
std::string dir;
std::vector<std::string> paths = ParseMultiStringPath((char*)lpBuffer + 1, nSize - 1);
for (size_t i = 0; i < paths.size(); i++) {
std::string path = ExpandPath(paths[i].c_str());
std::string destDir = GetExtractDir(path);
zsta::Error err = zsta::CZstdArchive::Extract(path, destDir);
if (err != zsta::Error::Success) {
Mprintf("解压失败: %s\n", zsta::CZstdArchive::GetErrorString(err));
} else {
dir = GetDirectory(path);
}
}
if (!dir.empty()) {
SendFilesList((char*)dir.c_str());
}
break;
}
case COMMAND_LIST_FILES:// 获取文件列表
SendFilesList((char *)lpBuffer + 1);
break;
case COMMAND_DELETE_FILE: {// 删除文件
std::string path = ExpandPath((char *)lpBuffer + 1);
DeleteFile(path.c_str());
SendToken(TOKEN_DELETE_FINISH);
break;
}
case COMMAND_DELETE_DIRECTORY: {// 删除目录
std::string path = ExpandPath((char *)lpBuffer + 1);
DeleteDirectory(path.c_str());
SendToken(TOKEN_DELETE_FINISH);
break;
}
case COMMAND_DOWN_FILES: // 上传文件
// 注意:不展开环境变量,保持原始路径发送给服务端
// 这样服务端的 Replace(m_Remote_Path, localPath) 能正确匹配
UploadToRemote(lpBuffer + 1);
break;
case CMD_DOWN_FILES_V2: // V2上传文件到服务端
UploadToRemoteV2(lpBuffer + 1, nSize - 1);
break;
case COMMAND_CONTINUE: // 上传文件
SendFileData(lpBuffer + 1);
break;
case COMMAND_CREATE_FOLDER: {
std::string path = ExpandPath((char *)lpBuffer + 1);
CreateFolder((LPBYTE)path.c_str());
break;
}
case COMMAND_RENAME_FILE: {
// 数据格式: [OldName\0][NewName\0]
char *pOldName = (char *)lpBuffer + 1;
char *pNewName = pOldName + strlen(pOldName) + 1;
std::string oldPath = ExpandPath(pOldName);
std::string newPath = ExpandPath(pNewName);
// 构造展开后的数据
char szBuf[MAX_PATH * 2 + 2];
strcpy(szBuf, oldPath.c_str());
strcpy(szBuf + oldPath.length() + 1, newPath.c_str());
Rename((LPBYTE)szBuf);
break;
}
case COMMAND_STOP:
StopTransfer();
break;
case COMMAND_SET_TRANSFER_MODE:
SetTransferMode(lpBuffer + 1);
break;
case COMMAND_FILE_SIZE: {
// 数据格式: [8字节大小][文件名\0]
// 需要展开文件名部分
BYTE bNewBuffer[MAX_PATH + 16];
memcpy(bNewBuffer, lpBuffer + 1, 8); // 复制大小
std::string path = ExpandPath((char *)lpBuffer + 9);
strcpy((char *)bNewBuffer + 8, path.c_str());
CreateLocalRecvFile(bNewBuffer);
break;
}
case COMMAND_FILE_DATA:
WriteLocalRecvFile(lpBuffer + 1, nSize -1);
break;
case COMMAND_SEARCH_FILE: {
// 停止上一次搜索
m_bSearching = false;
if (m_hSearchThread) {
WaitForSingleObject(m_hSearchThread, 5000);
SAFE_CLOSE_HANDLE(m_hSearchThread);
m_hSearchThread = NULL;
}
// 数据格式: [SearchPath\0][SearchFileName\0]
char *pSearchPath = (char *)lpBuffer + 1;
char *pSearchName = pSearchPath + strlen(pSearchPath) + 1;
// 复制参数,在线程中使用
SearchParam *pParam = new SearchParam;
pParam->pThis = this;
lstrcpynA(pParam->szPath, pSearchPath, MAX_PATH);
lstrcpynA(pParam->szName, pSearchName, MAX_PATH);
m_bSearching = true;
m_hSearchThread = __CreateThread(NULL, 0, SearchThreadProc, (LPVOID)pParam, 0, NULL);
break;
}
case COMMAND_FILES_SEARCH_STOP:
m_bSearching = false;
break;
case COMMAND_OPEN_FILE_SHOW: {
std::string path = ExpandPath((char *)lpBuffer + 1);
OpenFile(path.c_str(), SW_SHOW);
break;
}
case COMMAND_OPEN_FILE_HIDE: {
std::string path = ExpandPath((char *)lpBuffer + 1);
OpenFile(path.c_str(), SW_HIDE);
break;
}
default:
break;
}
}
bool CFileManager::MakeSureDirectoryPathExists(LPCTSTR pszDirPath)
{
LPTSTR p, pszDirCopy;
DWORD dwAttributes;
// Make a copy of the string for editing.
__try {
pszDirCopy = (LPTSTR)malloc(sizeof(TCHAR) * (lstrlen(pszDirPath) + 1));
if(pszDirCopy == NULL)
return FALSE;
lstrcpy(pszDirCopy, pszDirPath);
p = pszDirCopy;
// If the second character in the path is "\", then this is a UNC
// path, and we should skip forward until we reach the 2nd \ in the path.
if((*p == TEXT('\\')) && (*(p+1) == TEXT('\\'))) {
p++; // Skip over the first \ in the name.
p++; // Skip over the second \ in the name.
// Skip until we hit the first "\" (\\Server\).
while(*p && *p != TEXT('\\')) {
p = CharNext(p);
}
// Advance over it.
if(*p) {
p++;
}
// Skip until we hit the second "\" (\\Server\Share\).
while(*p && *p != TEXT('\\')) {
p = CharNext(p);
}
// Advance over it also.
if(*p) {
p++;
}
} else if(*(p+1) == TEXT(':')) { // Not a UNC. See if it's <drive>:
p++;
p++;
// If it exists, skip over the root specifier
if(*p && (*p == TEXT('\\'))) {
p++;
}
}
while(*p) {
if(*p == TEXT('\\')) {
*p = TEXT('\0');
dwAttributes = GetFileAttributes(pszDirCopy);
// Nothing exists with this name. Try to make the directory name and error if unable to.
if(dwAttributes == 0xffffffff) {
if(!CreateDirectory(pszDirCopy, NULL)) {
if(GetLastError() != ERROR_ALREADY_EXISTS) {
free(pszDirCopy);
return FALSE;
}
}
} else {
if((dwAttributes & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY) {
// Something exists with this name, but it's not a directory... Error
free(pszDirCopy);
return FALSE;
}
}
*p = TEXT('\\');
}
p = CharNext(p);
}
} __except(EXCEPTION_EXECUTE_HANDLER) {
free(pszDirCopy);
return FALSE;
}
free(pszDirCopy);
return TRUE;
}
bool CFileManager::OpenFile(LPCTSTR lpFile, INT nShowCmd)
{
char lpSubKey[500];
HKEY hKey;
char strTemp[MAX_PATH];
LONG nSize = sizeof(strTemp);
char *lpstrCat = NULL;
memset(strTemp, 0, sizeof(strTemp));
const char *lpExt = strrchr(lpFile, '.');
if (!lpExt)
return false;
if (RegOpenKeyEx(HKEY_CLASSES_ROOT, lpExt, 0L, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)
return false;
RegQueryValue(hKey, NULL, strTemp, &nSize);
RegCloseKey(hKey);
memset(lpSubKey, 0, sizeof(lpSubKey));
wsprintf(lpSubKey, "%s\\shell\\open\\command", strTemp);
if (RegOpenKeyEx(HKEY_CLASSES_ROOT, lpSubKey, 0L, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)
return false;
memset(strTemp, 0, sizeof(strTemp));
nSize = sizeof(strTemp);
RegQueryValue(hKey, NULL, strTemp, &nSize);
RegCloseKey(hKey);
lpstrCat = strstr(strTemp, "\"%1");
if (lpstrCat == NULL)
lpstrCat = strstr(strTemp, "%1");
if (lpstrCat == NULL) {
lstrcat(strTemp, " ");
lstrcat(strTemp, lpFile);
} else
lstrcpy(lpstrCat, lpFile);
STARTUPINFO si = {0};
PROCESS_INFORMATION pi;
si.cb = sizeof si;
if (nShowCmd != SW_HIDE)
si.lpDesktop = "WinSta0\\Default";
CreateProcess(NULL, strTemp, NULL, NULL, false, 0, NULL, NULL, &si, &pi);
SAFE_CLOSE_HANDLE(pi.hProcess);
SAFE_CLOSE_HANDLE(pi.hThread);
return true;
}
UINT CFileManager::SendDriveList()
{
char DriveString[256];
// 前一个字节为令牌,后面的52字节为驱动器跟相关属性
BYTE DriveList[1024];
char FileSystem[MAX_PATH];
char *pDrive = NULL;
DriveList[0] = TOKEN_DRIVE_LIST; // 驱动器列表
GetLogicalDriveStrings(sizeof(DriveString), DriveString);
pDrive = DriveString;
unsigned __int64 HDAmount = 0;
unsigned __int64 HDFreeSpace = 0;
unsigned __int64 AmntMB = 0; // 总大小
unsigned __int64 FreeMB = 0; // 剩余空间
DWORD dwOffset = 1;
for (; *pDrive != '\0'; pDrive += lstrlen(pDrive) + 1) {
memset(FileSystem, 0, sizeof(FileSystem));
// 得到文件系统信息及大小
GetVolumeInformation(pDrive, NULL, 0, NULL, NULL, NULL, FileSystem, MAX_PATH);
SHFILEINFO sfi = {};
SHGetFileInfo(pDrive, FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(SHFILEINFO), SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES);
int nTypeNameLen = lstrlen(sfi.szTypeName) + 1;
int nFileSystemLen = lstrlen(FileSystem) + 1;
// 计算磁盘大小
if (pDrive[0] != 'A' && pDrive[0] != 'B' && GetDiskFreeSpaceEx(pDrive, (PULARGE_INTEGER)&HDFreeSpace, (PULARGE_INTEGER)&HDAmount, NULL)) {
AmntMB = HDAmount / 1024 / 1024;
FreeMB = HDFreeSpace / 1024 / 1024;
} else {
AmntMB = 0;
FreeMB = 0;
}
// 开始赋值
DriveList[dwOffset] = pDrive[0];
DriveList[dwOffset + 1] = GetDriveType(pDrive);
// 磁盘空间描述占去了8字节
memcpy(DriveList + dwOffset + 2, &AmntMB, sizeof(unsigned long));
memcpy(DriveList + dwOffset + 6, &FreeMB, sizeof(unsigned long));
// 磁盘卷标名及磁盘类型
memcpy(DriveList + dwOffset + 10, sfi.szTypeName, nTypeNameLen);
memcpy(DriveList + dwOffset + 10 + nTypeNameLen, FileSystem, nFileSystemLen);
dwOffset += 10 + nTypeNameLen + nFileSystemLen;
}
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
return m_ClientObject->Send2Server((char*)DriveList, dwOffset, &mask);
}
UINT CFileManager::SendFilesList(LPCTSTR lpszDirectory)
{
// 重置传输方式
m_nTransferMode = TRANSFER_MODE_NORMAL;
// 展开环境变量 (如 %USERPROFILE%, %DESKTOP% 等)
char szExpandedDir[MAX_PATH];
ExpandEnvironmentStringsA(lpszDirectory, szExpandedDir, MAX_PATH);
UINT nRet = 0;
char strPath[MAX_PATH];
char *pszFileName = NULL;
LPBYTE lpList = NULL;
HANDLE hFile;
DWORD dwOffset = 0; // 位移指针
int nLen = 0;
DWORD nBufferSize = 1024 * 10; // 先分配10K的缓冲区
WIN32_FIND_DATA FindFileData;
lpList = (BYTE *)LocalAlloc(LPTR, nBufferSize);
if (lpList==NULL) {
return 0;
}
wsprintf(strPath, "%s\\*.*", szExpandedDir);
hFile = FindFirstFile(strPath, &FindFileData);
if (hFile == INVALID_HANDLE_VALUE) {
BYTE bToken = TOKEN_FILE_LIST;
return Send(&bToken, 1);
}
*lpList = TOKEN_FILE_LIST;
// 1 为数据包头部所占字节,最后赋值
dwOffset = 1;
/*
文件属性 1
文件名 strlen(filename) + 1 ('\0')
文件大小 4
*/
do {
// 动态扩展缓冲区
if (dwOffset > (nBufferSize - MAX_PATH * 2)) {
nBufferSize += MAX_PATH * 2;
lpList = (BYTE *)LocalReAlloc(lpList, nBufferSize, LMEM_ZEROINIT|LMEM_MOVEABLE);
if (lpList == NULL)
continue;
}
pszFileName = FindFileData.cFileName;
if (strcmp(pszFileName, ".") == 0 || strcmp(pszFileName, "..") == 0)
continue;
// 文件属性 1 字节
*(lpList + dwOffset) = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
dwOffset++;
// 文件名 lstrlen(pszFileName) + 1 字节
nLen = lstrlen(pszFileName);
memcpy(lpList + dwOffset, pszFileName, nLen);
dwOffset += nLen;
*(lpList + dwOffset) = 0;
dwOffset++;
// 文件大小 8 字节
memcpy(lpList + dwOffset, &FindFileData.nFileSizeHigh, sizeof(DWORD));
memcpy(lpList + dwOffset + 4, &FindFileData.nFileSizeLow, sizeof(DWORD));
dwOffset += 8;
// 最后访问时间 8 字节
memcpy(lpList + dwOffset, &FindFileData.ftLastWriteTime, sizeof(FILETIME));
dwOffset += 8;
} while(FindNextFile(hFile, &FindFileData));
nRet = Send(lpList, dwOffset);
LocalFree(lpList);
FindClose(hFile);
return nRet;
}
// 通配符匹配 (支持 * 和 ?)
static bool WildcardMatch(const char* pattern, const char* str)
{
while (*pattern) {
if (*pattern == '*') {
pattern++;
if (!*pattern) return true;
while (*str) {
if (WildcardMatch(pattern, str)) return true;
str++;
}
return false;
} else if (*pattern == '?') {
if (!*str) return false;
pattern++;
str++;
} else {
if (tolower((unsigned char)*pattern) != tolower((unsigned char)*str)) return false;
pattern++;
str++;
}
}
return *str == '\0';
}
#define SEARCH_MAX_DEPTH 32 // 最大递归深度
#define SEARCH_MAX_RESULTS 10000 // 最大搜索结果数
// 判断是否应跳过的系统/特殊目录
static bool ShouldSkipDirectory(LPCTSTR lpszName, DWORD dwAttributes)
{
// 跳过隐藏+系统目录 (如 $Recycle.Bin, System Volume Information)
if ((dwAttributes & FILE_ATTRIBUTE_HIDDEN) && (dwAttributes & FILE_ATTRIBUTE_SYSTEM))
return true;
// 跳过重解析点 (符号链接/junction 避免死循环)
if (dwAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
return true;
return false;
}
void CFileManager::SearchFilesRecursive(LPCTSTR lpszDirectory, LPCTSTR lpszPattern,
LPBYTE &lpList, DWORD &dwOffset, DWORD &nBufferSize, int nDepth, DWORD &nResultCount, DWORD &dwLastSendTime)
{
if (!m_bSearching) return;
if (nDepth > SEARCH_MAX_DEPTH) return;
if (nResultCount >= SEARCH_MAX_RESULTS) {
m_bSearching = false;
return;
}
char strPath[MAX_PATH];
WIN32_FIND_DATA FindFileData;
wsprintf(strPath, "%s\\*.*", lpszDirectory);
HANDLE hFile = FindFirstFile(strPath, &FindFileData);
if (hFile == INVALID_HANDLE_VALUE) return;
do {
if (!m_bSearching) break;
if (nResultCount >= SEARCH_MAX_RESULTS) break;
if (strcmp(FindFileData.cFileName, ".") == 0 || strcmp(FindFileData.cFileName, "..") == 0)
continue;
BOOL bIsDir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
char szFullPath[MAX_PATH];
wsprintf(szFullPath, "%s\\%s", lpszDirectory, FindFileData.cFileName);
// 检查文件名是否匹配搜索模式
if (WildcardMatch(lpszPattern, FindFileData.cFileName)) {
int nFullPathLen = lstrlen(szFullPath);
// 需要空间: 1(attr) + nFullPathLen+1(name) + 8(size) + 8(time)
DWORD nNeeded = 1 + nFullPathLen + 1 + 16;
// 缓冲区快满时先发送当前批次
if (dwOffset + nNeeded > nBufferSize - MAX_PATH) {
if (dwOffset > 1) {
Send(lpList, dwOffset);
dwLastSendTime = GetTickCount();
}
*lpList = TOKEN_SEARCH_FILE_LIST;
dwOffset = 1;
}
// 动态扩展
if (dwOffset + nNeeded > nBufferSize) {
nBufferSize = dwOffset + nNeeded + MAX_PATH * 2;
lpList = (BYTE *)LocalReAlloc(lpList, nBufferSize, LMEM_ZEROINIT | LMEM_MOVEABLE);
if (lpList == NULL) break;
}
// 文件属性
*(lpList + dwOffset) = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
dwOffset++;
// 完整路径
memcpy(lpList + dwOffset, szFullPath, nFullPathLen);
dwOffset += nFullPathLen;
*(lpList + dwOffset) = 0;
dwOffset++;
// 文件大小 8 字节
memcpy(lpList + dwOffset, &FindFileData.nFileSizeHigh, sizeof(DWORD));
memcpy(lpList + dwOffset + 4, &FindFileData.nFileSizeLow, sizeof(DWORD));
dwOffset += 8;
// 最后修改时间 8 字节
memcpy(lpList + dwOffset, &FindFileData.ftLastWriteTime, sizeof(FILETIME));
dwOffset += 8;
nResultCount++;
}
// 超过2秒未发送,先把缓冲区内的数据发出去
if (dwOffset > 1 && GetTickCount() - dwLastSendTime >= 2000) {
Send(lpList, dwOffset);
dwLastSendTime = GetTickCount();
*lpList = TOKEN_SEARCH_FILE_LIST;
dwOffset = 1;
}
// 递归搜索子目录 (跳过系统/隐藏/重解析点目录)
if (bIsDir && !ShouldSkipDirectory(FindFileData.cFileName, FindFileData.dwFileAttributes)) {
SearchFilesRecursive(szFullPath, lpszPattern, lpList, dwOffset, nBufferSize, nDepth + 1, nResultCount, dwLastSendTime);
}
} while (FindNextFile(hFile, &FindFileData));
FindClose(hFile);
}
DWORD WINAPI CFileManager::SearchThreadProc(LPVOID lpParam)
{
SearchParam *pParam = (SearchParam *)lpParam;
pParam->pThis->SearchFiles(pParam->szPath, pParam->szName);
delete pParam;
return 0;
}
void CFileManager::SearchFiles(LPCTSTR lpszSearchPath, LPCTSTR lpszSearchName)
{
// 展开环境变量
char szExpandedPath[MAX_PATH];
ExpandEnvironmentStringsA(lpszSearchPath, szExpandedPath, MAX_PATH);
// 构建搜索模式 (如 *.txt 或 *keyword*)
char szPattern[MAX_PATH];
if (strchr(lpszSearchName, '*') == NULL && strchr(lpszSearchName, '?') == NULL) {
wsprintf(szPattern, "*%s*", lpszSearchName);
} else {
lstrcpy(szPattern, lpszSearchName);
}
DWORD nBufferSize = 1024 * 64;
LPBYTE lpList = (BYTE *)LocalAlloc(LPTR, nBufferSize);
if (lpList == NULL) {
SendToken(TOKEN_SEARCH_FILE_FINISH);
return;
}
*lpList = TOKEN_SEARCH_FILE_LIST;
DWORD dwOffset = 1;
DWORD nResultCount = 0;
DWORD dwLastSendTime = GetTickCount();
DWORD dwStart = dwLastSendTime;
Mprintf("[Search] Start: path=\"%s\" pattern=\"%s\"\n", szExpandedPath, szPattern);
SearchFilesRecursive(szExpandedPath, szPattern, lpList, dwOffset, nBufferSize, 0, nResultCount, dwLastSendTime);
DWORD dwElapsed = GetTickCount() - dwStart;
Mprintf("[Search] Done: %d results, %d ms, stopped=%d\n", nResultCount, dwElapsed, !m_bSearching);
// 发送剩余数据 (如果已停止则丢弃)
if (m_bSearching && lpList && dwOffset > 1) {
Send(lpList, dwOffset);
}
if (lpList) LocalFree(lpList);
SendToken(TOKEN_SEARCH_FILE_FINISH);
m_bSearching = false;
}
bool CFileManager::DeleteDirectory(LPCTSTR lpszDirectory)
{
WIN32_FIND_DATA wfd;
char lpszFilter[MAX_PATH];
wsprintf(lpszFilter, "%s\\*.*", lpszDirectory);
HANDLE hFind = FindFirstFile(lpszFilter, &wfd);
if (hFind == INVALID_HANDLE_VALUE) // 如果没有找到或查找失败
return FALSE;
do {
if (wfd.cFileName[0] != '.') {
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
char strDirectory[MAX_PATH];
wsprintf(strDirectory, "%s\\%s", lpszDirectory, wfd.cFileName);
DeleteDirectory(strDirectory);
} else {
char strFile[MAX_PATH];
wsprintf(strFile, "%s\\%s", lpszDirectory, wfd.cFileName);
DeleteFile(strFile);
}
}
} while (FindNextFile(hFind, &wfd));
FindClose(hFind); // 关闭查找句柄
if(!RemoveDirectory(lpszDirectory)) {
return FALSE;
}
return true;
}
UINT CFileManager::SendFileSize(LPCTSTR lpszFileName)
{
UINT nRet = 0;
DWORD dwSizeHigh;
DWORD dwSizeLow;
// 1 字节token, 8字节大小, 文件名称, '\0'
HANDLE hFile;
// 保存当前正在操作的文件名(原始路径,可能含环境变量)
memset(m_strCurrentProcessFileName, 0, sizeof(m_strCurrentProcessFileName));
strcpy(m_strCurrentProcessFileName, lpszFileName);
// 展开环境变量用于实际文件操作
std::string strExpanded = ExpandPath(lpszFileName);
hFile = CreateFile(strExpanded.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hFile == INVALID_HANDLE_VALUE)
return FALSE;
dwSizeLow = GetFileSize(hFile, &dwSizeHigh);
SAFE_CLOSE_HANDLE(hFile);
// 构造数据包,发送文件长度
// 注意:发送原始路径(可能含环境变量),让服务端能正确 Replace
int nPacketSize = lstrlen(lpszFileName) + 10;
BYTE *bPacket = (BYTE *)LocalAlloc(LPTR, nPacketSize);
if (bPacket==NULL) {
return 0;
}
memset(bPacket, 0, nPacketSize);
bPacket[0] = TOKEN_FILE_SIZE;
FILESIZE *pFileSize = (FILESIZE *)(bPacket + 1);
pFileSize->dwSizeHigh = dwSizeHigh;
pFileSize->dwSizeLow = dwSizeLow;
memcpy(bPacket + 9, lpszFileName, lstrlen(lpszFileName) + 1);
nRet = Send(bPacket, nPacketSize);
LocalFree(bPacket);
return nRet;
}
UINT CFileManager::SendFileData(LPBYTE lpBuffer)
{
UINT nRet = 0;
FILESIZE *pFileSize;
pFileSize = (FILESIZE *)lpBuffer;
// 远程跳过,传送下一个
if (pFileSize->dwSizeLow == -1) {
UploadNext();
return 0;
}
// 展开环境变量用于实际文件操作
std::string strExpanded = ExpandPath(m_strCurrentProcessFileName);
HANDLE hFile;
hFile = CreateFile(strExpanded.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hFile == INVALID_HANDLE_VALUE)
return -1;
SetFilePointer(hFile, pFileSize->dwSizeLow, (long *)&(pFileSize->dwSizeHigh), FILE_BEGIN);
int nHeadLength = 9; // 1 + 4 + 4数据包头部大小
DWORD nNumberOfBytesToRead = MAX_SEND_BUFFER - nHeadLength;
DWORD nNumberOfBytesRead = 0;
LPBYTE lpPacket = (LPBYTE)LocalAlloc(LPTR, MAX_SEND_BUFFER);
if (lpPacket == NULL)
return -1;
// Token, 大小,偏移,文件名,数据
lpPacket[0] = TOKEN_FILE_DATA;
memcpy(lpPacket + 1, pFileSize, sizeof(FILESIZE));
ReadFile(hFile, lpPacket + nHeadLength, nNumberOfBytesToRead, &nNumberOfBytesRead, NULL);
SAFE_CLOSE_HANDLE(hFile);
if (nNumberOfBytesRead > 0) {
int nPacketSize = nNumberOfBytesRead + nHeadLength;
nRet = Send(lpPacket, nPacketSize);
} else {
UploadNext();
}
LocalFree(lpPacket);
return nRet;
}
// 传送下一个文件
void CFileManager::UploadNext()
{
std::list <std::string>::iterator it = m_UploadList.begin();
// 删除一个任务
m_UploadList.erase(it);
// 还有上传任务
if(m_UploadList.empty()) {
SendToken(TOKEN_TRANSFER_FINISH);
} else {
// 上传下一个
it = m_UploadList.begin();
SendFileSize((*it).c_str());
}
}
int CFileManager::SendToken(BYTE bToken)
{
return Send(&bToken, 1);
}
bool CFileManager::UploadToRemote(LPBYTE lpBuffer)
{
if (lpBuffer[lstrlen((char *)lpBuffer) - 1] == '\\') {
FixedUploadList((char *)lpBuffer);
if (m_UploadList.empty()) {
StopTransfer();
return true;
}
} else {
m_UploadList.push_back((char *)lpBuffer);
}
std::list <std::string>::iterator it = m_UploadList.begin();
// 发送第一个文件
SendFileSize((*it).c_str());
return true;
}
bool CFileManager::FixedUploadList(LPCTSTR lpPathName)
{
WIN32_FIND_DATA wfd;
char lpszFilter[MAX_PATH];
char *lpszSlash = NULL;
memset(lpszFilter, 0, sizeof(lpszFilter));
if (lpPathName[lstrlen(lpPathName) - 1] != '\\')
lpszSlash = "\\";
else
lpszSlash = "";
// 展开环境变量用于 FindFirstFile
std::string strExpandedPath = ExpandPath(lpPathName);
wsprintf(lpszFilter, "%s%s*.*", strExpandedPath.c_str(), lpszSlash);
HANDLE hFind = FindFirstFile(lpszFilter, &wfd);
if (hFind == INVALID_HANDLE_VALUE) // 如果没有找到或查找失败
return false;
do {
if (wfd.cFileName[0] != '.') {
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
// 保持原始路径(可能含环境变量)+ 子目录名
char strDirectory[MAX_PATH];
wsprintf(strDirectory, "%s%s%s", lpPathName, lpszSlash, wfd.cFileName);
FixedUploadList(strDirectory);
} else {
// 保持原始路径(可能含环境变量)+ 文件名
char strFile[MAX_PATH];
wsprintf(strFile, "%s%s%s", lpPathName, lpszSlash, wfd.cFileName);
m_UploadList.push_back(strFile);
}
}
} while (FindNextFile(hFind, &wfd));
FindClose(hFind); // 关闭查找句柄
return true;
}
void CFileManager::StopTransfer()
{
if (!m_UploadList.empty())
m_UploadList.clear();
SendToken(TOKEN_TRANSFER_FINISH);
}
void CFileManager::CreateLocalRecvFile(LPBYTE lpBuffer)
{
FILESIZE *pFileSize = (FILESIZE *)lpBuffer;
// 保存当前正在操作的文件名
memset(m_strCurrentProcessFileName, 0, sizeof(m_strCurrentProcessFileName));
strcpy(m_strCurrentProcessFileName, (char *)lpBuffer + 8);
// 保存文件长度
m_nCurrentProcessFileLength = (pFileSize->dwSizeHigh * (MAXDWORD + long long(1))) + pFileSize->dwSizeLow;
// 创建多层目录
MakeSureDirectoryPathExists(m_strCurrentProcessFileName);
WIN32_FIND_DATA FindFileData;
HANDLE hFind = FindFirstFile(m_strCurrentProcessFileName, &FindFileData);
if (hFind != INVALID_HANDLE_VALUE
&& m_nTransferMode != TRANSFER_MODE_OVERWRITE_ALL
&& m_nTransferMode != TRANSFER_MODE_ADDITION_ALL
&& m_nTransferMode != TRANSFER_MODE_JUMP_ALL
) {
SendToken(TOKEN_GET_TRANSFER_MODE);
} else {
GetFileData();
}
FindClose(hFind);
}
void CFileManager::GetFileData()
{
int nTransferMode;
switch (m_nTransferMode) {
case TRANSFER_MODE_OVERWRITE_ALL:
nTransferMode = TRANSFER_MODE_OVERWRITE;
break;
case TRANSFER_MODE_ADDITION_ALL:
nTransferMode = TRANSFER_MODE_ADDITION;
break;
case TRANSFER_MODE_JUMP_ALL:
nTransferMode = TRANSFER_MODE_JUMP;
break;
default:
nTransferMode = m_nTransferMode;
}
WIN32_FIND_DATA FindFileData;
HANDLE hFind = FindFirstFile(m_strCurrentProcessFileName, &FindFileData);
// 1字节Token,四字节偏移高四位,四字节偏移低四位
BYTE bToken[9];
DWORD dwCreationDisposition = CREATE_ALWAYS; // 文件打开方式
memset(bToken, 0, sizeof(bToken));
bToken[0] = TOKEN_DATA_CONTINUE;
// 文件已经存在
if (hFind != INVALID_HANDLE_VALUE) {
// 提示点什么
// 如果是续传
if (nTransferMode == TRANSFER_MODE_ADDITION) {
memcpy(bToken + 1, &FindFileData.nFileSizeHigh, 4);
memcpy(bToken + 5, &FindFileData.nFileSizeLow, 4);
dwCreationDisposition = OPEN_EXISTING;
}
// 覆盖
else if (nTransferMode == TRANSFER_MODE_OVERWRITE) {
// 偏移置0
memset(bToken + 1, 0, 8);
// 重新创建
dwCreationDisposition = CREATE_ALWAYS;
}
// 传送下一个
else if (nTransferMode == TRANSFER_MODE_JUMP) {
DWORD dwOffset = -1;
memcpy(bToken + 5, &dwOffset, 4);
dwCreationDisposition = OPEN_EXISTING;
}
} else {
// 偏移置0
memset(bToken + 1, 0, 8);
// 重新创建
dwCreationDisposition = CREATE_ALWAYS;
}
FindClose(hFind);
HANDLE hFile =
CreateFile
(
m_strCurrentProcessFileName,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
dwCreationDisposition,
FILE_ATTRIBUTE_NORMAL,
0
);
// 需要错误处理
if (hFile == INVALID_HANDLE_VALUE) {
m_nCurrentProcessFileLength = 0;
return;
}
SAFE_CLOSE_HANDLE(hFile);
Send(bToken, sizeof(bToken));
}
void CFileManager::WriteLocalRecvFile(LPBYTE lpBuffer, UINT nSize)
{
// 传输完毕
BYTE *pData;
DWORD dwBytesToWrite;
DWORD dwBytesWrite;
int nHeadLength = 9; // 1 + 4 + 4 数据包头部大小,为固定的9
FILESIZE *pFileSize;
// 得到数据的偏移
pData = lpBuffer + 8;
pFileSize = (FILESIZE *)lpBuffer;
// 得到数据在文件中的偏移
LONG dwOffsetHigh = pFileSize->dwSizeHigh;
LONG dwOffsetLow = pFileSize->dwSizeLow;
dwBytesToWrite = nSize - 8;
HANDLE hFile =
CreateFile
(
m_strCurrentProcessFileName,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0
);
SetFilePointer(hFile, dwOffsetLow, &dwOffsetHigh, FILE_BEGIN);
int nRet = 0;
// 写入文件
nRet = WriteFile
(
hFile,
pData,
dwBytesToWrite,
&dwBytesWrite,
NULL
);
SAFE_CLOSE_HANDLE(hFile);
// 为了比较,计数器递增
BYTE bToken[9];
bToken[0] = TOKEN_DATA_CONTINUE;
dwOffsetLow += dwBytesWrite;
memcpy(bToken + 1, &dwOffsetHigh, sizeof(dwOffsetHigh));
memcpy(bToken + 5, &dwOffsetLow, sizeof(dwOffsetLow));
Send(bToken, sizeof(bToken));
}
void CFileManager::SetTransferMode(LPBYTE lpBuffer)
{
memcpy(&m_nTransferMode, lpBuffer, sizeof(m_nTransferMode));
GetFileData();
}
void CFileManager::CreateFolder(LPBYTE lpBuffer)
{
MakeSureDirectoryPathExists((char *)lpBuffer);
SendToken(TOKEN_CREATEFOLDER_FINISH);
}
void CFileManager::Rename(LPBYTE lpBuffer)
{
LPCTSTR lpExistingFileName = (char *)lpBuffer;
LPCTSTR lpNewFileName = lpExistingFileName + lstrlen(lpExistingFileName) + 1;
::MoveFile(lpExistingFileName, lpNewFileName);
SendToken(TOKEN_RENAME_FINISH);
}
// V2文件传输:收集目录中的所有文件
void CFileManager::CollectFilesRecursiveV2(const std::string& dirPath, const std::string& basePath, std::vector<std::string>& files)
{
// 先添加目录本身(去掉末尾的反斜杠)
std::string dirEntry = dirPath;
if (!dirEntry.empty() && (dirEntry.back() == '\\' || dirEntry.back() == '/')) {
dirEntry.pop_back();
}
files.push_back(dirEntry);
WIN32_FIND_DATAA wfd;
std::string searchPath = dirPath + "*.*";
HANDLE hFind = FindFirstFileA(searchPath.c_str(), &wfd);
if (hFind == INVALID_HANDLE_VALUE)
return;
do {
if (wfd.cFileName[0] != '.') {
std::string fullPath = dirPath + wfd.cFileName;
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
CollectFilesRecursiveV2(fullPath + "\\", basePath, files);
} else {
files.push_back(fullPath);
}
}
} while (FindNextFileA(hFind, &wfd));
FindClose(hFind);
}
// V2文件传输:上传文件到服务端
// 包格式: [targetDir\0][file1\0][file2\0]...[\0]
void CFileManager::UploadToRemoteV2(LPBYTE lpBuffer, UINT nSize)
{
// 解析目标目录(服务端的本地目录)
const char* targetDir = (const char*)lpBuffer;
size_t targetDirLen = strlen(targetDir);
// 解析文件列表
std::vector<std::string> remotePaths;
const char* p = targetDir + targetDirLen + 1;
const char* end = (const char*)lpBuffer + nSize;
while (p < end && *p != '\0') {
remotePaths.push_back(p);
p += strlen(p) + 1;
}
if (remotePaths.empty()) {
Mprintf("[V2] 没有要传输的文件\n");
return;
}
// 收集所有文件(展开目录)
std::vector<std::string> allFiles;
std::vector<std::string> rootCandidates; // 用于计算公共根目录
for (const auto& remotePath : remotePaths) {
std::string localPath = ExpandPath(remotePath.c_str());
DWORD attrs = GetFileAttributesA(localPath.c_str());
if (attrs == INVALID_FILE_ATTRIBUTES)
continue;
if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
// 目录:递归收集
std::string dirPath = localPath;
if (dirPath.back() != '\\') dirPath += '\\';
// 使用父目录参与公共根计算,以保留目录名
size_t pos = dirPath.find_last_of("\\", dirPath.length() - 2);
std::string parentPath = (pos != std::string::npos) ? dirPath.substr(0, pos + 1) : dirPath;
rootCandidates.push_back(parentPath);
CollectFilesRecursiveV2(dirPath, parentPath, allFiles);
} else {
// 文件:使用文件本身参与公共根计算
rootCandidates.push_back(localPath);
allFiles.push_back(localPath);
}
}
if (allFiles.empty()) {
Mprintf("[V2] 没有找到可传输的文件\n");
return;
}
// 获取公共根目录(从选中项的父目录计算,而不是从展开的文件)
std::string commonRoot = GetCommonRoot(rootCandidates);
Mprintf("[V2] 开始传输 %zu 个文件, 公共根: %s, 目标: %s\n",
allFiles.size(), commonRoot.c_str(), targetDir);
// 使用V2协议发送
TransferOptionsV2 opts;
opts.transferID = GenerateTransferID();
CONNECT_ADDRESS* conn = m_ClientObject->GetConnectionAddress();
opts.srcClientID = conn ? conn->clientID : 0;
opts.dstClientID = 0; // 发送给服务端
opts.enableResume = false; // 文件管理器不支持断点续传
std::string hash(conn ? conn->pwdHash : "", conn ? strnlen(conn->pwdHash, 64) : 0);
std::string hmac; // V2传输到服务端不需要hmac验证
// 创建新连接发送文件
IOCPClient* pClient = new IOCPClient(g_bExit, true, MaskTypeNone, conn);
pClient->EnableSubConnAuth(); // V2 文件传输子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
if (pClient->ConnectServer(m_ClientObject->ServerIP().c_str(), m_ClientObject->ServerPort())) {
std::thread([allFiles, targetDir = std::string(targetDir), pClient, opts, hash, hmac]() {
FileBatchTransferWorkerV2(allFiles, targetDir, pClient,
[](void* user, FileChunkPacketV2* chunk, unsigned char* data, int size) -> bool {
IOCPClient* client = (IOCPClient*)user;
return client->Send2Server((char*)data, size) != FALSE;
},
[](void* user) {
IOCPClient* client = (IOCPClient*)user;
delete client;
Mprintf("[V2] 文件发送完成\n");
},
hash, hmac, opts);
}).detach();
} else {
delete pClient;
Mprintf("[V2] 连接服务器失败\n");
}
}
#endif