修正使用绝对路径时的问题

This commit is contained in:
2026-03-31 10:21:24 +08:00
parent 86034c71e0
commit d2dbcd5465
5 changed files with 309 additions and 19 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
build* build*
.qtcreator/

View File

@@ -19,6 +19,9 @@
"ignoreFailures": true "ignoreFailures": true
} }
], ],
"args": [
]
//"visualizerFile": "${workspaceRoot}/.vscode/qt6.natvis", //"visualizerFile": "${workspaceRoot}/.vscode/qt6.natvis",
}, },
"cmake.configureArgs": [ "cmake.configureArgs": [

View File

@@ -27,6 +27,7 @@ configure_file(ToolBox.h.in ToolBox.h)
message(STATUS "Version file config to: ${CMAKE_CURRENT_BINARY_DIR}") message(STATUS "Version file config to: ${CMAKE_CURRENT_BINARY_DIR}")
include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(public)
add_subdirectory(fileUpdater) add_subdirectory(fileUpdater)
if(WIN32) if(WIN32)

View File

@@ -15,6 +15,8 @@
#include <vector> #include <vector>
#include <zoost/zoost.h> #include <zoost/zoost.h>
#include "SimMd5.hpp"
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#endif #endif
@@ -33,27 +35,30 @@ struct FileMapping {
// 操作记录结构 // 操作记录结构
struct OperationRecord { struct OperationRecord {
fs::path source; // 源文件绝对路径 std::string backupRelative; // 相对备份路径
fs::path target; // 目标文件绝对路径 fs::path source; // 文件绝对路径
fs::path backup; // 备份路径 fs::path target; // 目标文件绝对路径
bool exists; // 目标文件是否存在 fs::path backup; // 备份路径
bool isNew; // 是否是新增文件 bool exists; // 目标文件是否存在
bool isNew; // 是否是新增文件
}; };
class AutoUpdateTool class AutoUpdateTool
{ {
private: private:
std::string configFile; std::string configFile;
fs::path workDir; // 工作目录(新文件所在) fs::path workDir; // 工作目录(新文件所在)
fs::path updateDir; // 更新目录(目标目录) fs::path updateDir{"."}; // 更新目录(目标目录)
fs::path backupDir; // 备份目录 fs::path backupDir; // 备份目录
std::string fromType; // 源类型:"m" 或 "n" std::string fromType; // 源类型:"m" 或 "n"
std::string toType; // 目标类型:"m" 或 "n" std::string toType; // 目标类型:"m" 或 "n"
std::vector<FileMapping> mappings; std::vector<FileMapping> mappings;
std::shared_ptr<spdlog::logger> logger; std::shared_ptr<spdlog::logger> logger;
std::string markerDir; // 标记目录名 std::string markerDir; // 标记目录名
std::string logPath_; std::string logPath_;
std::string logName_{"fileUpdater.log"}; std::string logName_{"fileUpdater.log"};
bool verify_{true};
SimpleMD5 md5_;
public: public:
AutoUpdateTool() AutoUpdateTool()
@@ -94,7 +99,7 @@ public:
app.add_option("-w,--work-dir", workDir, "工作目录(新文件所在)")->default_val(fs::current_path()); app.add_option("-w,--work-dir", workDir, "工作目录(新文件所在)")->default_val(fs::current_path());
app.add_option("-u,--update-dir", updateDir, "更新目录(目标目录)")->required(); app.add_option("-u,--update-dir", updateDir, "更新目录(目标目录)");
app.add_option("-b,--backup-dir", backupDir, "备份目录")->required(); app.add_option("-b,--backup-dir", backupDir, "备份目录")->required();
@@ -102,6 +107,8 @@ public:
app.add_option("-t,--to", toType, "目标类型 (m 或 n)")->required()->check(CLI::IsMember({"m", "n"})); app.add_option("-t,--to", toType, "目标类型 (m 或 n)")->required()->check(CLI::IsMember({"m", "n"}));
app.add_flag("-v,--verify", verify_, "是否进行备份校验(默认是)。");
try { try {
app.parse(argc, argv); app.parse(argc, argv);
@@ -223,6 +230,8 @@ public:
for (const auto& mapping : mappings) { for (const auto& mapping : mappings) {
// 确定源路径和目标路径 // 确定源路径和目标路径
std::string fromPath, toPath; std::string fromPath, toPath;
std::string backupRelativePath;
if (fromType == "m") { if (fromType == "m") {
fromPath = mapping.m; fromPath = mapping.m;
} else { } else {
@@ -235,9 +244,20 @@ public:
toPath = mapping.n; toPath = mapping.n;
} }
// 构建绝对路径 fs::path sourceAbs;
fs::path sourceAbs = workDir / fromPath; fs::path targetAbs;
fs::path targetAbs = updateDir / toPath;
if (fs::path(fromPath).is_absolute()) {
sourceAbs = fs::path(fromPath);
} else {
sourceAbs = workDir / fromPath;
backupRelativePath = fromPath;
}
if (fs::path(toPath).is_absolute()) {
targetAbs = fs::path(toPath);
} else {
targetAbs = updateDir / toPath;
}
// 步骤1:检查源路径是否存在 // 步骤1:检查源路径是否存在
if (!checkPath(sourceAbs, mapping.isFile)) { if (!checkPath(sourceAbs, mapping.isFile)) {
@@ -262,6 +282,7 @@ public:
record.target = targetAbs; record.target = targetAbs;
record.exists = fs::exists(targetAbs); record.exists = fs::exists(targetAbs);
record.isNew = !record.exists; record.isNew = !record.exists;
record.backupRelative = backupRelativePath;
if (mapping.isFile) { if (mapping.isFile) {
// 文件处理 // 文件处理
@@ -288,6 +309,7 @@ public:
dirRecord.target = targetAbs / relPath; dirRecord.target = targetAbs / relPath;
dirRecord.exists = fs::exists(dirRecord.target); dirRecord.exists = fs::exists(dirRecord.target);
dirRecord.isNew = !dirRecord.exists; dirRecord.isNew = !dirRecord.exists;
dirRecord.backupRelative = backupRelativePath;
records.push_back(dirRecord); records.push_back(dirRecord);
} }
} }
@@ -327,9 +349,10 @@ public:
// 获取用户输入的标记目录 // 获取用户输入的标记目录
bool getMarkerDirectory() bool getMarkerDirectory()
{ {
std::cout << "\n请输入标记目录名称(留空使用时间戳): "; std::cout << "\n请输入此次备份标记名称(留空使用时间戳): ";
std::getline(boost::nowide::cin, markerDir); std::getline(boost::nowide::cin, markerDir);
boost::trim(markerDir);
if (markerDir.empty()) { if (markerDir.empty()) {
markerDir = getCurrentTimestamp(); markerDir = getCurrentTimestamp();
} }
@@ -350,8 +373,8 @@ public:
{ {
try { try {
// 计算相对更新目录的路径 // 计算相对更新目录的路径
fs::path relPath = fs::relative(record.target, updateDir); // fs::path relPath = fs::relative(record.target, updateDir);
fs::path backupPath = backupDir / markerDir / relPath; fs::path backupPath = backupDir / markerDir / record.backupRelative;
// 创建目标目录 // 创建目标目录
fs::create_directories(backupPath.parent_path()); fs::create_directories(backupPath.parent_path());
@@ -360,8 +383,30 @@ public:
fs::copy(record.target, backupPath, fs::copy_options::overwrite_existing); fs::copy(record.target, backupPath, fs::copy_options::overwrite_existing);
logger->debug("已备份: {} -> {}", record.target.string(), backupPath.string()); logger->debug("已备份: {} -> {}", record.target.string(), backupPath.string());
if (!verify_) {
return true;
}
// 校验MD5
auto hashSrc = md5_.hash_file(record.target.string());
std::string hashBk;
if (fs::is_directory(backupPath)) {
hashBk = md5_.hash_file(fs::path(backupPath).append(fs::path(record.target).filename().string()).string());
} else {
hashBk = md5_.hash_file(backupPath.string());
}
if (hashBk == hashSrc) {
logger->info("==> Hash验证已通过。[{}]", hashSrc);
} else {
logger->error("==> Hash验证未通过。[{}][{}]", hashSrc, hashBk);
return false;
}
return true; return true;
} catch (const fs::filesystem_error& e) { } catch (const fs::filesystem_error& e) {
// logger->error("备份失败: {} - {}", record.target.string(), zoostCommon::ToU8Copy(e.what()));
logger->error("备份失败: {} - {}", record.target.string(), e.what()); logger->error("备份失败: {} - {}", record.target.string(), e.what());
return false; return false;
} }
@@ -403,7 +448,8 @@ public:
if (backupFile(record)) { if (backupFile(record)) {
backupCount++; backupCount++;
} else { } else {
logger->warn("备份失败,但继续执行更新"); logger->warn("备份失败...,请检查,停止更新动作...");
return false;
} }
} }
} }

239
public/SimMd5.hpp Normal file
View File

@@ -0,0 +1,239 @@
#include <iostream>
#include <string>
#include <cstring>
#include <sstream>
#include <iomanip>
#include <vector>
class SimpleMD5 {
private:
// 基础函数定义
#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define I(x, y, z) ((y) ^ ((x) | (~z)))
// 循环左移
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
// 转换函数
#define FF(a, b, c, d, x, s, ac) { \
(a) += F((b), (c), (d)) + (x) + (ac); \
(a) = ROTATE_LEFT((a), (s)); \
(a) += (b); \
}
#define GG(a, b, c, d, x, s, ac) { \
(a) += G((b), (c), (d)) + (x) + (ac); \
(a) = ROTATE_LEFT((a), (s)); \
(a) += (b); \
}
#define HH(a, b, c, d, x, s, ac) { \
(a) += H((b), (c), (d)) + (x) + (ac); \
(a) = ROTATE_LEFT((a), (s)); \
(a) += (b); \
}
#define II(a, b, c, d, x, s, ac) { \
(a) += I((b), (c), (d)) + (x) + (ac); \
(a) = ROTATE_LEFT((a), (s)); \
(a) += (b); \
}
uint32_t state[4]; // 状态变量 (A, B, C, D)
// 填充数据
void pad(std::vector<uint8_t>& data) {
uint64_t original_bit_length = data.size() * 8;
// 添加 1 bit (0x80 字节)
data.push_back(0x80);
// 填充 0 直到长度对 64 取模等于 56
while ((data.size() % 64) != 56) {
data.push_back(0x00);
}
// 添加原始消息长度 (小端序)
for (int i = 0; i < 8; i++) {
data.push_back(static_cast<uint8_t>(original_bit_length >> (i * 8)));
}
}
// 转换 4 字节为 32 位整数 (小端序)
uint32_t to_int32(const uint8_t* bytes) {
return (static_cast<uint32_t>(bytes[0])) |
(static_cast<uint32_t>(bytes[1]) << 8) |
(static_cast<uint32_t>(bytes[2]) << 16) |
(static_cast<uint32_t>(bytes[3]) << 24);
}
// 主转换函数
void transform(const uint8_t block[64]) {
uint32_t a = state[0];
uint32_t b = state[1];
uint32_t c = state[2];
uint32_t d = state[3];
uint32_t x[16];
// 将 64 字节块解码为 16 个 32 位整数
for (int i = 0, j = 0; i < 16; i++, j += 4) {
x[i] = to_int32(&block[j]);
}
/* 第 1 轮 */
FF(a, b, c, d, x[0], 7, 0xd76aa478);
FF(d, a, b, c, x[1], 12, 0xe8c7b756);
FF(c, d, a, b, x[2], 17, 0x242070db);
FF(b, c, d, a, x[3], 22, 0xc1bdceee);
FF(a, b, c, d, x[4], 7, 0xf57c0faf);
FF(d, a, b, c, x[5], 12, 0x4787c62a);
FF(c, d, a, b, x[6], 17, 0xa8304613);
FF(b, c, d, a, x[7], 22, 0xfd469501);
FF(a, b, c, d, x[8], 7, 0x698098d8);
FF(d, a, b, c, x[9], 12, 0x8b44f7af);
FF(c, d, a, b, x[10], 17, 0xffff5bb1);
FF(b, c, d, a, x[11], 22, 0x895cd7be);
FF(a, b, c, d, x[12], 7, 0x6b901122);
FF(d, a, b, c, x[13], 12, 0xfd987193);
FF(c, d, a, b, x[14], 17, 0xa679438e);
FF(b, c, d, a, x[15], 22, 0x49b40821);
/* 第 2 轮 */
GG(a, b, c, d, x[1], 5, 0xf61e2562);
GG(d, a, b, c, x[6], 9, 0xc040b340);
GG(c, d, a, b, x[11], 14, 0x265e5a51);
GG(b, c, d, a, x[0], 20, 0xe9b6c7aa);
GG(a, b, c, d, x[5], 5, 0xd62f105d);
GG(d, a, b, c, x[10], 9, 0x02441453);
GG(c, d, a, b, x[15], 14, 0xd8a1e681);
GG(b, c, d, a, x[4], 20, 0xe7d3fbc8);
GG(a, b, c, d, x[9], 5, 0x21e1cde6);
GG(d, a, b, c, x[14], 9, 0xc33707d6);
GG(c, d, a, b, x[3], 14, 0xf4d50d87);
GG(b, c, d, a, x[8], 20, 0x455a14ed);
GG(a, b, c, d, x[13], 5, 0xa9e3e905);
GG(d, a, b, c, x[2], 9, 0xfcefa3f8);
GG(c, d, a, b, x[7], 14, 0x676f02d9);
GG(b, c, d, a, x[12], 20, 0x8d2a4c8a);
/* 第 3 轮 */
HH(a, b, c, d, x[5], 4, 0xfffa3942);
HH(d, a, b, c, x[8], 11, 0x8771f681);
HH(c, d, a, b, x[11], 16, 0x6d9d6122);
HH(b, c, d, a, x[14], 23, 0xfde5380c);
HH(a, b, c, d, x[1], 4, 0xa4beea44);
HH(d, a, b, c, x[4], 11, 0x4bdecfa9);
HH(c, d, a, b, x[7], 16, 0xf6bb4b60);
HH(b, c, d, a, x[10], 23, 0xbebfbc70);
HH(a, b, c, d, x[13], 4, 0x289b7ec6);
HH(d, a, b, c, x[0], 11, 0xeaa127fa);
HH(c, d, a, b, x[3], 16, 0xd4ef3085);
HH(b, c, d, a, x[6], 23, 0x04881d05);
HH(a, b, c, d, x[9], 4, 0xd9d4d039);
HH(d, a, b, c, x[12], 11, 0xe6db99e5);
HH(c, d, a, b, x[15], 16, 0x1fa27cf8);
HH(b, c, d, a, x[2], 23, 0xc4ac5665);
/* 第 4 轮 */
II(a, b, c, d, x[0], 6, 0xf4292244);
II(d, a, b, c, x[7], 10, 0x432aff97);
II(c, d, a, b, x[14], 15, 0xab9423a7);
II(b, c, d, a, x[5], 21, 0xfc93a039);
II(a, b, c, d, x[12], 6, 0x655b59c3);
II(d, a, b, c, x[3], 10, 0x8f0ccc92);
II(c, d, a, b, x[10], 15, 0xffeff47d);
II(b, c, d, a, x[1], 21, 0x85845dd1);
II(a, b, c, d, x[8], 6, 0x6fa87e4f);
II(d, a, b, c, x[15], 10, 0xfe2ce6e0);
II(c, d, a, b, x[6], 15, 0xa3014314);
II(b, c, d, a, x[13], 21, 0x4e0811a1);
II(a, b, c, d, x[4], 6, 0xf7537e82);
II(d, a, b, c, x[11], 10, 0xbd3af235);
II(c, d, a, b, x[2], 15, 0x2ad7d2bb);
II(b, c, d, a, x[9], 21, 0xeb86d391);
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
}
public:
SimpleMD5() {
reset();
}
// 重置状态
void reset() {
state[0] = 0x67452301;
state[1] = 0xefcdab89;
state[2] = 0x98badcfe;
state[3] = 0x10325476;
}
// 计算字符串的 MD5
std::string hash(const std::string& input) {
return hash(reinterpret_cast<const uint8_t*>(input.c_str()), input.length());
}
// 计算字节数组的 MD5
std::string hash(const uint8_t* data, size_t length) {
reset();
// 将数据复制到 vector 以便填充
std::vector<uint8_t> buffer(data, data + length);
// 填充数据
pad(buffer);
// 处理每个 64 字节块
for (size_t i = 0; i < buffer.size(); i += 64) {
transform(&buffer[i]);
}
// 将结果转换为十六进制字符串
return to_hex_string();
}
// 计算文件的 MD5
std::string hash_file(const std::string& filename) {
reset();
FILE* file = fopen(filename.c_str(), "rb");
if (!file) {
return "";
}
std::vector<uint8_t> buffer;
uint8_t chunk[1024];
size_t bytes_read;
while ((bytes_read = fread(chunk, 1, sizeof(chunk), file)) > 0) {
buffer.insert(buffer.end(), chunk, chunk + bytes_read);
}
fclose(file);
return hash(buffer.data(), buffer.size());
}
private:
// 将结果转换为十六进制字符串
std::string to_hex_string() {
std::stringstream ss;
ss << std::hex << std::setfill('0');
// 以小端序输出每个状态变量
for (int i = 0; i < 4; i++) {
// 将 32 位整数按字节输出
for (int j = 0; j < 4; j++) {
uint8_t byte = (state[i] >> (j * 8)) & 0xFF;
ss << std::setw(2) << static_cast<int>(byte);
}
}
return ss.str();
}
};