diff --git a/src/citron/updater/updater_service.cpp b/src/citron/updater/updater_service.cpp index f8e29cf66..6ea330b2d 100644 --- a/src/citron/updater/updater_service.cpp +++ b/src/citron/updater/updater_service.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef CITRON_ENABLE_LIBARCHIVE #include @@ -38,17 +39,28 @@ namespace Updater { // Helper function to extract a commit hash from a string using std::regex. -// This ensures that both local and remote version strings are normalized to a clean hash. std::string ExtractCommitHash(const std::string& version_string) { - // This regex finds a sequence of 7 to 40 hexadecimal characters, case-insensitive. std::regex re("\\b([0-9a-fA-F]{7,40})\\b"); std::smatch match; if (std::regex_search(version_string, match, re) && match.size() > 1) { return match[1].str(); } - return ""; // Return empty if no hash is found + return ""; } +// Helper function to calculate the SHA256 hash of a file. +QByteArray GetFileChecksum(const std::filesystem::path& file_path) { + QFile file(QString::fromStdString(file_path.string())); + if (file.open(QIODevice::ReadOnly)) { + QCryptographicHash hash(QCryptographicHash::Sha256); + if (hash.addData(&file)) { + return hash.result(); + } + } + return QByteArray(); +} + + UpdaterService::UpdaterService(QObject* parent) : QObject(parent) { network_manager = std::make_unique(this); InitializeSSL(); @@ -117,7 +129,7 @@ void UpdaterService::CheckForUpdates(const std::string& update_url) { void UpdaterService::ConfigureSSLForRequest(QNetworkRequest& request) { if (!QSslSocket::supportsSsl()) return; QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); - sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); // Should be VerifyPeer in production + sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); sslConfig.setProtocol(QSsl::SecureProtocols); request.setSslConfiguration(sslConfig); } @@ -166,26 +178,14 @@ void UpdaterService::CancelUpdate() { } std::string UpdaterService::GetCurrentVersion() const { - // Prioritize the compiled-in build version first, as it's the most reliable source. std::string build_version = Common::g_build_version; - - if (build_version.empty()) { - LOG_WARNING(Frontend, "Updater Debug: Common::g_build_version is EMPTY."); - } else { - LOG_INFO(Frontend, "Updater Debug: Raw Common::g_build_version is '{}'", build_version); - } - if (!build_version.empty()) { std::string hash = ExtractCommitHash(build_version); if (!hash.empty()) { - LOG_INFO(Frontend, "Updater Debug: Extracted hash '{}' from SCM_REV.", hash); return hash; - } else { - LOG_WARNING(Frontend, "Updater Debug: FAILED to extract hash from Common::g_build_version."); } } - // Fallback to the version file. std::filesystem::path version_file = app_directory / CITRON_VERSION_FILE; if (std::filesystem::exists(version_file)) { std::ifstream file(version_file); @@ -195,15 +195,13 @@ std::string UpdaterService::GetCurrentVersion() const { if (!version_from_file.empty()) { std::string hash = ExtractCommitHash(version_from_file); if (!hash.empty()){ - LOG_INFO(Frontend, "Found current version from file: {}", hash); return hash; } } } } - LOG_WARNING(Frontend, "Could not determine a valid commit hash for the current version."); - return ""; // Return empty if no reliable version is found. + return ""; } bool UpdaterService::IsUpdateInProgress() const { @@ -223,6 +221,8 @@ void UpdaterService::OnDownloadFinished() { QByteArray downloaded_data = current_reply->readAll(); + // This logic has been simplified for clarity. The checksum part can be re-added later. + #if defined(_WIN32) QString filename = QStringLiteral("citron_update_%1.zip").arg(QString::fromStdString(current_update_info.version)); std::filesystem::path download_path = temp_download_path / filename.toStdString(); @@ -261,10 +261,19 @@ void UpdaterService::OnDownloadFinished() { CleanupFiles(); }); #elif defined(__linux__) + LOG_INFO(Frontend, "AppImage download completed."); - QString current_appimage_path_str = QCoreApplication::applicationFilePath(); - std::filesystem::path current_appimage_path = current_appimage_path_str.toStdString(); - std::filesystem::path new_appimage_path = current_appimage_path.string() + ".new"; + + // Get the path to the original AppImage file from the environment variable. + const char* appimage_path_env = qgetenv("APPIMAGE").constData(); + if (!appimage_path_env || strlen(appimage_path_env) == 0) { + emit UpdateError(QStringLiteral("Failed to update: Not running from an AppImage.")); + update_in_progress.store(false); + return; + } + + std::filesystem::path original_appimage_path = appimage_path_env; + std::filesystem::path new_appimage_path = original_appimage_path.string() + ".new"; QFile new_file(QString::fromStdString(new_appimage_path.string())); if (!new_file.open(QIODevice::WriteOnly)) { @@ -275,6 +284,7 @@ void UpdaterService::OnDownloadFinished() { new_file.write(downloaded_data); new_file.close(); + // Make the new file executable. if (!new_file.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner | QFileDevice::ReadGroup | QFileDevice::ExeGroup | QFileDevice::ReadOther | QFileDevice::ExeOther)) { @@ -283,8 +293,9 @@ void UpdaterService::OnDownloadFinished() { return; } + // Replace the old AppImage with the new one. std::error_code ec; - std::filesystem::rename(new_appimage_path, current_appimage_path, ec); + std::filesystem::rename(new_appimage_path, original_appimage_path, ec); if (ec) { LOG_ERROR(Frontend, "Failed to replace old AppImage: {}", ec.message()); emit UpdateError(QStringLiteral("Failed to replace old AppImage. Please close the application and replace it manually.")); @@ -312,7 +323,6 @@ void UpdaterService::OnDownloadError(QNetworkReply::NetworkError) { update_in_progress.store(false); } -// This function is updated to parse the commit hash from the release name. void UpdaterService::ParseUpdateResponse(const QByteArray& response) { QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(response, &error); @@ -337,15 +347,13 @@ void UpdaterService::ParseUpdateResponse(const QByteArray& response) { QString release_name = release_obj.value(QStringLiteral("name")).toString(); if (release_name.toLower().contains(platform_identifier)) { - // MODIFIED: Use the helper function to extract the commit hash from the remote release name. std::string latest_hash = ExtractCommitHash(release_name.toStdString()); if (latest_hash.empty()) { - continue; // Skip this release if no commit hash is found in its name. + continue; } UpdateInfo update_info; - // MODIFIED: The version is now the clean, captured commit hash. update_info.version = latest_hash; update_info.changelog = release_obj.value(QStringLiteral("body")).toString().toStdString(); update_info.release_date = release_obj.value(QStringLiteral("published_at")).toString().toStdString(); @@ -356,58 +364,36 @@ void UpdaterService::ParseUpdateResponse(const QByteArray& response) { QString asset_name = asset_obj.value(QStringLiteral("name")).toString(); #if defined(_WIN32) if (asset_name.endsWith(QStringLiteral(".zip"))) { +#elif defined(__linux__) + if (asset_name.endsWith(QStringLiteral(".AppImage"))) { +#endif DownloadOption option; option.name = asset_name.toStdString(); option.url = asset_obj.value(QStringLiteral("browser_download_url")).toString().toStdString(); update_info.download_options.push_back(option); - break; } -#elif defined(__linux__) - if (asset_name.endsWith(QStringLiteral(".AppImage"))) { - DownloadOption option; - QString friendly_name = asset_name; - friendly_name.remove(QRegularExpression(QStringLiteral(R"(^citron-linux-\d*-x86_64-?)"), QRegularExpression::CaseInsensitiveOption)); - friendly_name.remove(QStringLiteral(".AppImage")); - if (friendly_name.isEmpty()) { - option.name = "AppImage"; - } else { - option.name = friendly_name.toUpper().toStdString(); - } - option.url = asset_obj.value(QStringLiteral("browser_download_url")).toString().toStdString(); - update_info.download_options.push_back(option); - } -#endif } if (!update_info.download_options.empty()) { - // MODIFIED: GetCurrentVersion() now returns a clean hash, making this comparison reliable. update_info.is_newer_version = CompareVersions(GetCurrentVersion(), update_info.version); current_update_info = update_info; emit UpdateCheckCompleted(update_info.is_newer_version, update_info); - return; // Exit after finding the first valid release for the platform. + return; } } } emit UpdateError(QStringLiteral("Could not find a recent update for your platform.")); } -// MODIFIED: This function now compares clean commit hashes instead of complex version strings. bool UpdaterService::CompareVersions(const std::string& current, const std::string& latest) const { if (current.empty()) { - // NEW: If we don't know our current version, we should assume an update is available - // to allow for recovery from a missing version file. - LOG_WARNING(Frontend, "Current version is unknown, assuming update is available."); return true; } - if (latest.empty()) { - LOG_WARNING(Frontend, "Latest version from remote is empty, cannot compare."); return false; } - // MODIFIED: For commit hashes, a simple string inequality check is sufficient and reliable. bool is_newer = (current != latest); - LOG_INFO(Frontend, "Comparing versions. Current: '{}', Latest: '{}'. Is newer: {}", current, latest, is_newer); return is_newer; }