From 4c98867f199f4ae9556b7e1b18b28e03e321bdc2 Mon Sep 17 00:00:00 2001 From: muzili <2586850402@qq.com> Date: Thu, 5 Dec 2024 10:36:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=98=A0=E5=B0=84=E7=89=88=E6=9C=AC=E6=A3=80?= =?UTF-8?q?=E6=9F=A5+=E6=96=B0=E7=89=88=E6=9C=AC=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GitHubDesktop2Chinese.cpp | 134 ++++++++++--- include/VersionParse/Version.hpp | 320 +++++++++++++++++++++++++++++++ 2 files changed, 430 insertions(+), 24 deletions(-) create mode 100644 include/VersionParse/Version.hpp diff --git a/GitHubDesktop2Chinese.cpp b/GitHubDesktop2Chinese.cpp index 9f228a3..0a84ae6 100644 --- a/GitHubDesktop2Chinese.cpp +++ b/GitHubDesktop2Chinese.cpp @@ -2,8 +2,8 @@ #define _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING -#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING //消除 converter.to_bytes的警告 -#define _CRT_SECURE_NO_WARNINGS //消除 sprintf的警告 +#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING //消除 converter.to_bytes的警告 +#define _CRT_SECURE_NO_WARNINGS //消除 sprintf的警告 #define PAUSE if(!no_pause) system("pause") @@ -13,19 +13,25 @@ #include -#include "spdlog/spdlog.h" // 日志式输出库 -#include "nlohmann/json.hpp" // JSON读取本地配置库 -#include "WinReg/WinReg.hpp" // 注册表操作库 +#include "spdlog/spdlog.h" // 日志式输出库 +#include "nlohmann/json.hpp" // JSON读取本地配置库 +#include "WinReg/WinReg.hpp" // 注册表操作库 -#include // 参数管理器: https://github.com/CLIUtils/CLI11 +#include // 参数管理器: https://github.com/CLIUtils/CLI11 #include "Utils/utils.hpp" - +#include "VersionParse/Version.hpp" #if _DEBUG #define NO_REPLACE 0 #endif // _DEBUG +#ifndef FILE_VERSION +#define FILE_VERSION "v0.0.0-Dev.0" +#endif // !FILE_VERSION + + +std::Version FileVer{0,0,0}; namespace fs = std::filesystem; @@ -38,8 +44,8 @@ fs::path LocalizationJSON; //fs::path Main_Json_Path; //fs::path Renderer_Json_Path; -bool no_pause; // 程序在结束前是否暂停 -bool only_read_from_remote; // 仅从远程url中读取本地化文件 +bool no_pause; // 程序在结束前是否暂停 +bool only_read_from_remote; // 仅从远程url中读取本地化文件 json localization = R"( { @@ -142,9 +148,16 @@ int main(int argc, char* argv[]) CLI11_PARSE(app, argc, argv); } - + FileVer = std::Version(FILE_VERSION); + // 开发者声明 - spdlog::info("开发者:CNGEGE>2024/04/13"); + spdlog::info("开发者:CNGEGE > 2024/04/13"); + if(FileVer) { + spdlog::info("版本: {}", FileVer.toString(true)); + } + else { + spdlog::warn("程序版本解析失败... at {}", FILE_VERSION); + } if (GetKeyState(VK_SHIFT) & 0x8000 || _debug_goto_devoptions) { // 如果Shift按下, 则进入开发者选项 @@ -153,6 +166,8 @@ int main(int argc, char* argv[]) DeveloperOptions(); } + SetConsoleTitle(FileVer.toString(true).c_str()); + if (LocalizationJSON.empty()) { LocalizationJSON = "localization.json"; } @@ -172,6 +187,40 @@ int main(int argc, char* argv[]) } catch(...) {} } + + // 检查更新 + // https://api.github.com/repos/cngege/GitHubDesktop2Chinese/releases/latest + { + if(FileVer.status != std::Version::Dev) { + spdlog::info("检查更新中.."); + try { + std::string repoinfo; + if(utils::ReadHttpDataString("https://api.github.com", "/repos/cngege/GitHubDesktop2Chinese/releases/latest", repoinfo)) { + auto infojson = json::parse(repoinfo); + auto tag_name = infojson["tag_name"].get(); + std::Version remoteVer(tag_name.c_str()); + if(!remoteVer) { + spdlog::warn("远程仓库中的版本号解析失败, ({})", tag_name); + } + else { + if(FileVer < remoteVer) { + spdlog::info("发现新版本: {}", remoteVer.toString()); + std::string downlink = infojson["assets"][0]["browser_download_url"].get(); + spdlog::info("点击链接下载: {}", downlink); + } + } + PAUSE; + } + else { + spdlog::warn("远程信息读取失败.."); + } + } + catch(...) { + spdlog::warn("检查更新时出现异常"); + } + } + } + // 如果是仅从远程仓库读取汉化文件 if (only_read_from_remote) { spdlog::info("尝试从远程开源项目中获取"); @@ -323,9 +372,38 @@ int main(int argc, char* argv[]) spdlog::info("已新建备份 renderer.js -> renderer.js.bak"); } + // 判断版本 + if(FileVer.status != std::Version::Dev && !localization["minversion"].empty()) { + std::Version JsonVer(localization["minversion"].get().c_str()); + if(!JsonVer) { + spdlog::warn("映射文件中 minversion 解析失败... at {}", localization["minversion"].get().c_str()); + PAUSE; + } + else { + if(FileVer < JsonVer) { + // 不符合要求 + spdlog::warn("文件要求加载器版本至少为: {}, 但加载器版本为: {}", JsonVer.toString(), FileVer.toString()); + spdlog::info("请更新:{}", "https://github.com/cngege/GitHubDesktop2Chinese/releases"); + // 询问是否强制执行 + + if(!no_pause) { + spdlog::info("输入(f)强制执行替换(可能会导致无法打开), 其他退出.."); + std::string input; + std::cin >> input; + if(input != "f" && input != "F") { + return 1; + } + } + } + else { + PAUSE; + } + } + } + else { + PAUSE; + } - PAUSE; - int ret_num = 0; // 处理 main[_dev].json文件 { @@ -365,17 +443,21 @@ int main(int argc, char* argv[]) if(item.value().size() >= 3) { // 对数组第三项进行全局查找 - std::regex pattern3(item.value()[2].get()); - for(std::sregex_iterator it = std::sregex_iterator(main_str.begin(), main_str.end(), pattern3); - it != std::sregex_iterator(); - ++it) { + std::string regex_str = item.value()[2].get(); + std::regex pattern3(regex_str); + std::sregex_iterator it = std::sregex_iterator(main_str.begin(), main_str.end(), pattern3); + if(it != std::sregex_iterator()) { const std::smatch& match = *it; for(size_t i = 1; i < match.size(); i++) { - std::string replace_str = "#\\{" + std::to_string(i)+"\\}"; + std::string replace_str = "#\\{" + std::to_string(i) + "\\}"; std::regex replace_regx(replace_str); item.value()[1] = std::regex_replace(item.value()[1].get(), replace_regx, match[i].str()); } - break; + } + else { + // 如果没有找到,则应该进行提示并跳过此项,以免进行错误的字符插入,造成程序无法打开 + spdlog::warn("[main] 出现一处失效项,此项将跳过: {}", regex_str); + continue; } } @@ -436,17 +518,21 @@ int main(int argc, char* argv[]) if(item.value().size() >= 3) { // 对数组第三项进行全局查找 - std::regex pattern3(item.value()[2].get()); - for(std::sregex_iterator it = std::sregex_iterator(renderer_str.begin(), renderer_str.end(), pattern3); - it != std::sregex_iterator(); - ++it) { + std::string regex_str = item.value()[2].get(); + std::regex pattern3(regex_str); + std::sregex_iterator it = std::sregex_iterator(renderer_str.begin(), renderer_str.end(), pattern3); + if(it != std::sregex_iterator()) { const std::smatch& match = *it; for(size_t i = 1; i < match.size(); i++) { std::string replace_str = "#\\{" + std::to_string(i) + "\\}"; std::regex replace_regx(replace_str); item.value()[1] = std::regex_replace(item.value()[1].get(), replace_regx, match[i].str()); } - break; + } + else { + // 如果没有找到,则应该进行提示并跳过此项,以免进行错误的字符插入,造成程序无法打开 + spdlog::warn("[renderer] 出现一处失效项,此项将跳过: {}", regex_str); + continue; } } diff --git a/include/VersionParse/Version.hpp b/include/VersionParse/Version.hpp new file mode 100644 index 0000000..b8ede6c --- /dev/null +++ b/include/VersionParse/Version.hpp @@ -0,0 +1,320 @@ +#ifndef VERSIONPARSE_VERSION_HPP +#define VERSIONPARSE_VERSION_HPP + + +#pragma once +#include + +namespace std { + +class Version { +public: + enum Status { + Dev, /*开发版*/ + Beta, /*测试版*/ + Release /*正式发布版*/ + }; + int major = 0; + int minor = 0; + int revision = 0; + int betaversion = 0; + Status status = Status::Release; + +private: + enum ParseCheckPosition { + enummajor, /*反序列化标志位 主版本号*/ + enumminor, /*反序列化标志位 次版本号*/ + enumrevision, /*反序列化标志位 修订号*/ + enumbetaversion, /*反序列化标志位 测试版本号*/ + }; + ParseCheckPosition parseCheckPos = ParseCheckPosition::enummajor; + bool vaild = false; +public: + Version(const char* v) { + vaild = Parse(v); + } + + Version(int major, int minor, int revision, Status status = Status::Release, int betaversion = 0) + : major(major), minor(minor), revision(revision), status(status), betaversion(betaversion) + { + vaild = true; + } + +public: + bool operator==(const Version& b) const { + if (major != b.major) return false; + if (minor != b.minor) return false; + if (revision != b.revision) return false; + // 如果双方都为dev 或 beta版 则进行内部版本号的比较 + if (this->status == b.status && this->status != Status::Release) { + if (betaversion != b.betaversion) return false; + } + return true; + } + + bool operator<=(const Version& b) const { + if (major > b.major) return false; + if (minor > b.minor) return false; + if (revision > b.revision) return false; + // 如果双方都为dev 或 beta版 则进行内部版本号的比较 + if (this->status == b.status && this->status != Status::Release) { + if (betaversion > b.betaversion) return false; + } + return true; + } + + bool operator<(const Version& b) const { + return *this <= b && *this != b; + } + + bool operator>=(const Version& b) const { + return b <= *this; + } + + bool operator>(const Version& b) const { + return b < *this; + } + + operator bool() const { + return vaild; + } + +public: + /** + * @brief 转为字符串形式方便阅读 + * @param hasStatus 是否显示版本类型 + * @param hasBetaVer 是否显示测试版本号 + * @return 返回格式为 0.0.0 或 0.0.0 - Beta 或 0.0.0.0 - Beta + */ + std::string toString(bool hasStatus = false, bool hasBetaVer = false) { + std::string ret = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision); + if (hasBetaVer) { + ret += "." + std::to_string(betaversion); + } + if (hasStatus) { + if (status == Status::Dev) ret += " - Dev"; + if (status == Status::Beta) ret += " - Beta"; + if (status == Status::Release) ret += " - Release"; + } + return ret; + } + + /** + * @brief 返回类似 v1.40.100-alpha.456 的版本结构 + * @return + */ + std::string toDetailedString() { + std::string ret = "v" + std::to_string(major) + "." + std::to_string(minor); + if (revision != 0) { + ret += "." + std::to_string(revision); + } + if (status == Status::Dev) ret += "-dev"; + if (status == Status::Beta) ret += "-beta"; + if (status != Status::Release) { + ret += "." + std::to_string(betaversion); + } + return ret; + } + +private: + /** + * @brief 将a~z的字符转为大写,其他字符原样返回 + * @param c 字符 a~z + * @return 返回大写的A~Z + */ + char ToUpper(char c) { + if (c >= 97 && c <= 122) { // a ~ z + return c - 32; + } + return c; + } + + /** + * @brief 将A~Z的字符转为小写,其他字符原样返回 + * @param c 字符 A~Z + * @return 返回小写的a~z + */ + char ToLower(char c) { + if (c >= 65 && c <= 90) { // A ~ Z + return c + 32; + } + return c; + } + + /** + * @brief 检查版本是否是alpha beta + * + * @param sourcevalue + * @param checkvalue 判断字符是否和这个相等 必须小写 这个数不能是空字符串 因为没有意义 + * @return <0: 遇到了 \0, 0: 不是, >0 是 + */ + int CheckIsAlphaOrBeta(const char* sourcevalue, const char* checkvalue) { + int _increase = 1; // 如果返回值大于0 则返回这个数,来告诉调用者应该将原始字节偏移多少才能跳过这个检查值 + for (const char* _v = sourcevalue; *_v != '\0'; _v++) + { + if (ToLower(*_v) != *checkvalue) { + return 0; + } + if (*(checkvalue + 1) == '\0') { + return _increase; + } + if (*(checkvalue + 1) == ' ') { // 空格跳过 + _increase++; + continue; + } + checkvalue++; + _increase++; + } + return -1; + } + + /** + * @brief 反序列化 将字符串反序列化为内部参数 + * @param v + * @return 是否读出了版本号, 至少要读出两位版本号(1.2) + */ + bool Parse(const char* v) { + bool ret = false; + for (;;) { + if (*v == '\0') { + break; + } + // parseCheckPos == ParseCheckPosition::enummajor + if (parseCheckPos == ParseCheckPosition::enummajor) { + if (*v == 'v' || *v == 'V') { + v++; + continue; + } + if (*v == '.') { + parseCheckPos = ParseCheckPosition::enumminor; + v++; + continue; + } + if (isdigit(*v) != 0) { + major = major * 10 + (*v - '0'); + v++; + continue; + } + // 到这里不可能是. \\n 数字 + // 所以只能是非法字符(为了兼容性,将空格放过) + if (*v == ' ') { + v++; + continue; + } + // 非法字符 反序列化失败 + break; + } + // parseCheckPos == ParseCheckPosition::minor + if (parseCheckPos == ParseCheckPosition::enumminor) { + if (*v == '.') { + parseCheckPos = ParseCheckPosition::enumrevision; + v++; + continue; + } + if (isdigit(*v) != 0) { + minor = minor * 10 + (*v - '0'); + v++; + ret = true; + continue; + } + if (*v == '-' || *v == '_') { + v++; + + // 检查是否是 alpha 或者 dev + int ret = CheckIsAlphaOrBeta(v, "alpha"); + if(!ret) ret = CheckIsAlphaOrBeta(v, "dev"); + if (ret < 0) break; + if (ret > 0) { // 是 alpha 内部测试版 -> Dev + status = Status::Dev; + parseCheckPos = ParseCheckPosition::enumbetaversion; + v += ret; + if (*v == '.') { + v++; + } + continue; + } + // 检查是否是 beta + ret = CheckIsAlphaOrBeta(v, "beta"); + if (ret < 0) break; + if (ret > 0) { // 是 beta + status = Status::Beta; + parseCheckPos = ParseCheckPosition::enumbetaversion; + v += ret; + if (*v == '.') { + v++; + } + continue; + } + } + v++; + continue; + } + // parseCheckPos == ParseCheckPosition::enumrevision + if (parseCheckPos == ParseCheckPosition::enumrevision) { + if (*v == '.') { + parseCheckPos = ParseCheckPosition::enumbetaversion; + v++; + continue; + } + if (isdigit(*v) != 0) { + revision = revision * 10 + (*v - '0'); + v++; + continue; + } + if (*v == '-' || *v == '_') { + v++; + + // 检查是否是 alpha 或者 dev + int ret = CheckIsAlphaOrBeta(v, "alpha"); + if (!ret) ret = CheckIsAlphaOrBeta(v, "dev"); + if (ret < 0) break; + if (ret > 0) { // 是 alpha 内部测试版 -> Dev + status = Status::Dev; + parseCheckPos = ParseCheckPosition::enumbetaversion; + v += ret; + if (*v == '.') { + v++; + } + continue; + } + // 检查是否是 beta + ret = CheckIsAlphaOrBeta(v, "beta"); + if (ret < 0) break; + if (ret > 0) { // 是 beta + status = Status::Beta; + parseCheckPos = ParseCheckPosition::enumbetaversion; + v += ret; + if (*v == '.') { + v++; + } + continue; + } + } + v++; + continue; + } + // parseCheckPos == ParseCheckPosition::enumbetaversion + if (parseCheckPos == ParseCheckPosition::enumbetaversion) { + // 这里遇到'.'就结束了, 版本字符串的结构只能支持到 0.0.0.0 + if (*v == '.') { + break; + } + if (isdigit(*v) != 0) { + if (status == Status::Release && ((*v - '0') != 0)) status = Status::Beta; + betaversion = betaversion * 10 + (*v - '0'); + v++; + continue; + } + + v++; + continue; + } + } + return ret; + } +}; + +} + + +#endif // !VERSIONPARSE_VERSION_HPP