gkms-localify-dmm/src/resourceUpdate/resourceUpdate.cpp

229 lines
7.0 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.

#include "stdinclude.hpp"
#include "cpprest/http_client.h"
#include "cpprest/filestream.h"
#include "nlohmann/json.hpp"
#include "GakumasLocalify/Log.h"
#include "gkmsGUI/GUII18n.hpp"
#include <format>
#include "unzip.hpp"
extern std::filesystem::path gakumasLocalPath;
extern std::filesystem::path ProgramConfigJson;
extern bool downloading;
extern float downloadProgress;
extern std::function<void()> g_reload_all_data;
std::string resourceVersionCache = "";
namespace GkmsResourceUpdate {
void saveProgramConfig() {
nlohmann::json config;
config["enableConsole"] = g_enable_console;
config["useRemoteAssets"] = g_useRemoteAssets;
config["transRemoteZipUrl"] = g_remoteResourceUrl;
config["useAPIAssets"] = g_useAPIAssets;
config["useAPIAssetsURL"] = g_useAPIAssetsURL;
std::ofstream out(ProgramConfigJson);
if (!out) {
GakumasLocal::Log::ErrorFmt("SaveProgramConfig error: Cannot open file: %s", ProgramConfigJson.c_str());
return;
}
out << config.dump(4);
GakumasLocal::Log::Info("SaveProgramConfig success");
}
web::http::http_response send_get(std::string url, int timeout) {
web::http::client::http_client_config cfg;
cfg.set_timeout(utility::seconds(30));
web::http::client::http_client client(utility::conversions::to_utf16string(url), cfg);
return client.request(web::http::methods::GET).get();
}
bool DownloadFile(const std::string& url, const std::string& outputPath) {
using namespace utility;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace concurrency::streams;
try {
// 打开输出文件流(同步方式)
auto outTask = fstream::open_ostream(conversions::to_string_t(outputPath));
outTask.wait();
auto fileStream = outTask.get();
// 创建 HTTP 客户端,注意:如果 url 包含完整路径cpprestsdk 会自动解析
http_client client(conversions::to_string_t(url));
downloading = true;
downloadProgress = 0.0f;
// 发起 GET 请求
auto responseTask = client.request(methods::GET);
responseTask.wait();
http_response response = responseTask.get();
if (response.status_code() != status_codes::OK) {
downloading = false;
GakumasLocal::Log::ErrorFmt("DownloadFile error: %d", response.status_code());
return false;
}
// 获取响应头中的文件大小(如果存在)
uint64_t contentLength = 0;
if (response.headers().has(L"Content-Length"))
contentLength = std::stoull(conversions::to_utf8string(response.headers().find(L"Content-Length")->second));
// 读取响应体,逐块写入文件,同时更新进度
auto inStream = response.body();
const size_t bufferSize = 8192;
// std::vector<unsigned char> buffer(bufferSize);
size_t totalDownloaded = 0;
while (true) {
auto readTask = inStream.read(fileStream.streambuf(), bufferSize);
readTask.wait();
size_t bytesRead = readTask.get();
if (bytesRead == 0)
break;
totalDownloaded += bytesRead;
if (contentLength > 0)
downloadProgress = static_cast<float>(totalDownloaded) / static_cast<float>(contentLength);
}
fileStream.close().wait();
downloading = false;
return true;
}
catch (const std::exception& e) {
downloading = false;
GakumasLocal::Log::ErrorFmt("DownloadFile error: %s", e.what());
return false;
}
}
std::string GetCurrentResourceVersion(bool useCache) {
if (useCache) {
if (!resourceVersionCache.empty()) {
return resourceVersionCache;
}
}
auto resourceVersionFile = gakumasLocalPath / "version.txt";
std::ifstream file(resourceVersionFile);
if (!file) {
// GakumasLocal::Log::ErrorFmt("Can't open file: %s", resourceVersionFile.string().c_str());
return "Unknown";
}
std::stringstream buffer;
buffer << file.rdbuf();
std::string content = buffer.str();
// 去除首尾空格和换行符
auto is_not_space = [](unsigned char ch) {
return !std::isspace(ch);
};
// 去除前导空白
content.erase(content.begin(), std::find_if(content.begin(), content.end(), is_not_space));
// 去除尾部空白
content.erase(std::find_if(content.rbegin(), content.rend(), is_not_space).base(), content.end());
resourceVersionCache = content;
return content;
}
bool unzipFileFromURL(std::string downloadUrl, const std::string& unzipPath, const std::string& targetDir = "") {
std::string tempZipFile = (gakumasLocalPath / "temp_download.zip").string();
if (std::filesystem::exists(tempZipFile)) {
std::filesystem::remove(tempZipFile);
}
if (!DownloadFile(downloadUrl, tempZipFile)) {
GakumasLocal::Log::Error("Download zip file failed.");
return false;
}
if (!UnzipFile(tempZipFile, unzipPath, targetDir)) {
GakumasLocal::Log::Error("Unzip file failed.");
return false;
}
return true;
}
void CheckUpdateFromAPI(bool isManual) {
std::thread([isManual]() {
try {
if (!g_useAPIAssets) {
return;
}
GakumasLocal::Log::Info("Checking update from API...");
auto response = send_get(g_useAPIAssetsURL, 30);
if (response.status_code() != 200) {
GakumasLocal::Log::ErrorFmt("Failed to check update from API: %d\n", response.status_code());
return;
}
auto data = nlohmann::json::parse(response.extract_utf8string().get());
std::string remoteVersion = data["tag_name"];
const auto localVersion = GetCurrentResourceVersion(false);
if (localVersion == remoteVersion) {
if (isManual) {
auto check = MessageBoxW(NULL, utility::conversions::to_string_t(GkmsGUII18n::ts("local_file_already_latest")).c_str(),
L"Check Update", MB_OKCANCEL);
if (check != IDOK) {
return;
}
}
else {
return;
}
}
std::string description = data["body"];
auto check = MessageBoxW(NULL, std::format(L"{} -> {}\n\n{}", utility::conversions::to_string_t(localVersion),
utility::conversions::to_string_t(remoteVersion), utility::conversions::to_string_t(description)).c_str(),
L"Resource Update", MB_OKCANCEL);
if (check != IDOK) {
return;
}
if (!data.contains("assets") || !data["assets"].is_array()) {
GakumasLocal::Log::Error("API response doesn't contain assets array.");
return;
}
for (const auto& asset : data["assets"]) {
if (!asset.contains("name") || !asset.contains("browser_download_url"))
continue;
std::string name = asset["name"];
if (name.ends_with(".zip")) {
std::string downloadUrl = asset["browser_download_url"];
if (unzipFileFromURL(downloadUrl, gakumasLocalPath.string())) {
g_reload_all_data();
GakumasLocal::Log::Info("Update completed.");
}
// 仅解压一个文件
return;
}
}
GakumasLocal::Log::Error("No .zip file found.");
}
catch (std::exception& e) {
GakumasLocal::Log::ErrorFmt("Exception occurred in CheckUpdateFromAPI: %s\n", e.what());
}
}).detach();
}
void checkUpdateFromURL(const std::string& downloadUrl) {
std::thread([downloadUrl]() {
if (unzipFileFromURL(downloadUrl, gakumasLocalPath.string(), "local-files")) {
g_reload_all_data();
GakumasLocal::Log::Info("Update completed.");
}
}).detach();
}
}