diff --git a/src/citron/main.cpp b/src/citron/main.cpp index df241b5f1..1d6cd4d60 100644 --- a/src/citron/main.cpp +++ b/src/citron/main.cpp @@ -6187,12 +6187,10 @@ int main(int argc, char* argv[]) { void GMainWindow::OnCheckForUpdates() { #ifdef CITRON_USE_AUTO_UPDATER - std::string update_url = "https://api.github.com/repos/Zephyron-Dev/Citron-CI/releases"; - auto* updater_dialog = new Updater::UpdaterDialog(this); updater_dialog->setAttribute(Qt::WA_DeleteOnClose); updater_dialog->show(); - updater_dialog->CheckForUpdates(update_url); + updater_dialog->CheckForUpdates(); #else QMessageBox::information(this, tr("Updates"), tr("The automatic updater is not enabled in this build.")); @@ -6208,8 +6206,6 @@ void GMainWindow::CheckForUpdatesAutomatically() { LOG_INFO(Frontend, "Checking for updates automatically..."); - std::string update_url = "https://api.github.com/repos/Zephyron-Dev/Citron-CI/releases"; - auto* updater_service = new Updater::UpdaterService(this); connect(updater_service, &Updater::UpdaterService::UpdateCheckCompleted, this, @@ -6249,7 +6245,7 @@ void GMainWindow::CheckForUpdatesAutomatically() { updater_service->deleteLater(); }); - updater_service->CheckForUpdates(update_url); + updater_service->CheckForUpdates(); #endif } diff --git a/src/citron/updater/updater_dialog.cpp b/src/citron/updater/updater_dialog.cpp index 3f3421c3f..49ad20c25 100644 --- a/src/citron/updater/updater_dialog.cpp +++ b/src/citron/updater/updater_dialog.cpp @@ -93,9 +93,9 @@ UpdaterDialog::UpdaterDialog(QWidget* parent) UpdaterDialog::~UpdaterDialog() = default; -void UpdaterDialog::CheckForUpdates(const std::string& update_url) { +void UpdaterDialog::CheckForUpdates() { ShowCheckingState(); - updater_service->CheckForUpdates(update_url); + updater_service->CheckForUpdates(); } void UpdaterDialog::OnUpdateCheckCompleted(bool has_update, const Updater::UpdateInfo& update_info) { diff --git a/src/citron/updater/updater_dialog.h b/src/citron/updater/updater_dialog.h index 8a44fffec..acc83b815 100644 --- a/src/citron/updater/updater_dialog.h +++ b/src/citron/updater/updater_dialog.h @@ -27,7 +27,7 @@ namespace Updater { explicit UpdaterDialog(QWidget* parent = nullptr); ~UpdaterDialog() override; - void CheckForUpdates(const std::string& update_url); + void CheckForUpdates(); private slots: void OnUpdateCheckCompleted(bool has_update, const Updater::UpdateInfo& update_info); diff --git a/src/citron/updater/updater_service.cpp b/src/citron/updater/updater_service.cpp index 1e17f0a3d..592322466 100644 --- a/src/citron/updater/updater_service.cpp +++ b/src/citron/updater/updater_service.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #ifdef CITRON_ENABLE_LIBARCHIVE #include @@ -39,7 +40,9 @@ namespace Updater { -// Helper function to extract a commit hash from a string using std::regex. +const std::string STABLE_UPDATE_URL = "https://git.citron-emu.org/api/v1/repos/Citron/Emulator/releases"; +const std::string NIGHTLY_UPDATE_URL = "https://api.github.com/repos/Zephyron-Dev/Citron-CI/releases"; + std::string ExtractCommitHash(const std::string& version_string) { std::regex re("\\b([0-9a-fA-F]{7,40})\\b"); std::smatch match; @@ -116,15 +119,15 @@ void UpdaterService::InitializeSSL() { LOG_INFO(Frontend, "SSL initialized successfully"); } -void UpdaterService::CheckForUpdates(const std::string& update_url) { +void UpdaterService::CheckForUpdates() { if (update_in_progress.load()) { emit UpdateError(QStringLiteral("Update operation already in progress")); return; } - if (update_url.empty()) { - emit UpdateError(QStringLiteral("Update URL not configured")); - return; - } + QSettings settings; + QString channel = settings.value(QStringLiteral("updater/channel"), QStringLiteral("Stable")).toString(); + std::string update_url = (channel == QStringLiteral("Nightly")) ? NIGHTLY_UPDATE_URL : STABLE_UPDATE_URL; + LOG_INFO(Frontend, "Selected update channel: {}", channel.toStdString()); LOG_INFO(Frontend, "Checking for updates from: {}", update_url); QUrl url{QString::fromStdString(update_url)}; QNetworkRequest request{url}; @@ -132,10 +135,10 @@ void UpdaterService::CheckForUpdates(const std::string& update_url) { request.setRawHeader("Accept", QByteArrayLiteral("application/json")); request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); current_reply = network_manager->get(request); - connect(current_reply, &QNetworkReply::finished, this, [this]() { + connect(current_reply, &QNetworkReply::finished, this, [this, channel]() { if (!current_reply) return; if (current_reply->error() == QNetworkReply::NoError) { - ParseUpdateResponse(current_reply->readAll()); + ParseUpdateResponse(current_reply->readAll(), channel); } else { emit UpdateError(QStringLiteral("Update check failed: %1").arg(current_reply->errorString())); } @@ -196,29 +199,56 @@ void UpdaterService::CancelUpdate() { } std::string UpdaterService::GetCurrentVersion() const { - std::string build_version = Common::g_build_version; - if (!build_version.empty()) { - std::string hash = ExtractCommitHash(build_version); - if (!hash.empty()) { - return hash; + QSettings settings; + QString channel = settings.value(QStringLiteral("updater/channel"), QStringLiteral("Stable")).toString(); + + // If the user's setting is Nightly, we must ignore version.txt and only use the commit hash. + if (channel == QStringLiteral("Nightly")) { + std::string build_version = Common::g_build_version; + if (!build_version.empty()) { + std::string hash = ExtractCommitHash(build_version); + if (!hash.empty()) { + return hash; + } } + return ""; // Fallback if no hash is found } - std::filesystem::path version_file = app_directory / CITRON_VERSION_FILE; + // Otherwise (channel is Stable), we prioritize version.txt. + std::filesystem::path search_path; +#ifdef __linux__ + const char* appimage_path_env = qgetenv("APPIMAGE").constData(); + if (appimage_path_env && strlen(appimage_path_env) > 0) { + search_path = std::filesystem::path(appimage_path_env).parent_path(); + } else { + search_path = app_directory; + } +#else + search_path = app_directory; +#endif + + std::filesystem::path version_file = search_path / CITRON_VERSION_FILE; if (std::filesystem::exists(version_file)) { std::ifstream file(version_file); if (file.is_open()) { std::string version_from_file; std::getline(file, version_from_file); if (!version_from_file.empty()) { - std::string hash = ExtractCommitHash(version_from_file); - if (!hash.empty()){ - return hash; - } + return version_from_file; } } } + // Fallback for Stable channel: If version.txt is missing, use the commit hash. + // This allows a nightly build to correctly check for a stable update. + std::string build_version = Common::g_build_version; + if (!build_version.empty()) { + std::string hash = ExtractCommitHash(build_version); + if (!hash.empty()) { + return hash; + } + } + return ""; } @@ -227,17 +257,19 @@ bool UpdaterService::IsUpdateInProgress() const { } void UpdaterService::OnDownloadFinished() { - if (cancel_requested.load()) { + if (cancel_requested.load() || !current_reply) { update_in_progress.store(false); return; } - if (!current_reply || current_reply->error() != QNetworkReply::NoError) { - if(current_reply) emit UpdateError(QStringLiteral("Download failed: %1").arg(current_reply->errorString())); + if (current_reply->error() != QNetworkReply::NoError) { + emit UpdateError(QStringLiteral("Download failed: %1").arg(current_reply->errorString())); update_in_progress.store(false); return; } QByteArray downloaded_data = current_reply->readAll(); + QSettings settings; + QString channel = settings.value(QStringLiteral("updater/channel"), QStringLiteral("Stable")).toString(); // This logic has been simplified for clarity. The checksum part can be re-added later. @@ -311,16 +343,29 @@ void UpdaterService::OnDownloadFinished() { return; } - // Replace the old AppImage with the new one. std::error_code 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.")); + emit UpdateError(QStringLiteral("Failed to replace old AppImage.")); update_in_progress.store(false); return; } + std::filesystem::path version_file_path = original_appimage_path.parent_path() / CITRON_VERSION_FILE; + if (channel == QStringLiteral("Stable")) { + LOG_INFO(Frontend, "Writing stable version marker: {}", current_update_info.version); + std::ofstream version_file(version_file_path); + if (version_file.is_open()) { + version_file << current_update_info.version; + } + } else { + LOG_INFO(Frontend, "Nightly update, removing stable version marker if it exists."); + if (std::filesystem::exists(version_file_path)) { + std::filesystem::remove(version_file_path); + } + } + LOG_INFO(Frontend, "AppImage updated successfully."); emit UpdateCompleted(UpdateResult::Success, QStringLiteral("Update successful. Please restart the application.")); update_in_progress.store(false); @@ -329,8 +374,8 @@ void UpdaterService::OnDownloadFinished() { void UpdaterService::OnDownloadProgress(qint64 bytes_received, qint64 bytes_total) { if (bytes_total > 0) { - int percentage = static_cast((bytes_received * 100) / bytes_total); - emit UpdateDownloadProgress(percentage, bytes_received, bytes_total); + emit UpdateDownloadProgress(static_cast((bytes_received * 100) / bytes_total), + bytes_received, bytes_total); } } @@ -341,63 +386,51 @@ void UpdaterService::OnDownloadError(QNetworkReply::NetworkError) { update_in_progress.store(false); } -void UpdaterService::ParseUpdateResponse(const QByteArray& response) { +void UpdaterService::ParseUpdateResponse(const QByteArray& response, const QString& channel) { QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(response, &error); - if (error.error != QJsonParseError::NoError) { - emit UpdateError(QStringLiteral("Failed to parse JSON: %1").arg(error.errorString())); + if (error.error != QJsonParseError::NoError || !doc.isArray()) { + emit UpdateError(QStringLiteral("Failed to parse update response.")); return; } - if (!doc.isArray()) { - emit UpdateError(QStringLiteral("JSON response is not an array.")); - return; - } - - QString platform_identifier; -#if defined(_WIN32) - platform_identifier = QStringLiteral("windows"); -#elif defined(__linux__) - platform_identifier = QStringLiteral("linux"); -#endif for (const QJsonValue& release_value : doc.array()) { QJsonObject release_obj = release_value.toObject(); - QString release_name = release_obj.value(QStringLiteral("name")).toString(); + std::string latest_version; + if (channel == QStringLiteral("Stable")) { + latest_version = release_obj.value(QStringLiteral("tag_name")).toString().toStdString(); + } else { + latest_version = ExtractCommitHash(release_obj.value(QStringLiteral("name")).toString().toStdString()); + } - if (release_name.toLower().contains(platform_identifier)) { - std::string latest_hash = ExtractCommitHash(release_name.toStdString()); + if (latest_version.empty()) continue; - if (latest_hash.empty()) { - continue; - } + UpdateInfo update_info; + update_info.version = latest_version; + update_info.changelog = release_obj.value(QStringLiteral("body")).toString().toStdString(); + update_info.release_date = release_obj.value(QStringLiteral("published_at")).toString().toStdString(); - UpdateInfo update_info; - 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(); - - QJsonArray assets = release_obj.value(QStringLiteral("assets")).toArray(); - for (const QJsonValue& asset_value : assets) { - QJsonObject asset_obj = asset_value.toObject(); - 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"))) { + QJsonArray assets = release_obj.value(QStringLiteral("assets")).toArray(); + for (const QJsonValue& asset_value : assets) { + QJsonObject asset_obj = asset_value.toObject(); + QString asset_name = asset_obj.value(QStringLiteral("name")).toString(); +#if defined(__linux__) + if (asset_name.endsWith(QStringLiteral(".AppImage"))) { +#else + if (asset_name.endsWith(QStringLiteral(".zip"))) { #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); - } + 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); } + } - if (!update_info.download_options.empty()) { - 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; - } + if (!update_info.download_options.empty()) { + 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; } } emit UpdateError(QStringLiteral("Could not find a recent update for your platform.")); diff --git a/src/citron/updater/updater_service.h b/src/citron/updater/updater_service.h index c8bccf3fa..bfa18aa90 100644 --- a/src/citron/updater/updater_service.h +++ b/src/citron/updater/updater_service.h @@ -44,7 +44,7 @@ public: explicit UpdaterService(QObject* parent = nullptr); ~UpdaterService() override; - void CheckForUpdates(const std::string& update_url); + void CheckForUpdates(); void DownloadAndInstallUpdate(const std::string& download_url); void CancelUpdate(); std::string GetCurrentVersion() const; @@ -72,7 +72,7 @@ private slots: private: void InitializeSSL(); void ConfigureSSLForRequest(QNetworkRequest& request); - void ParseUpdateResponse(const QByteArray& response); + void ParseUpdateResponse(const QByteArray& response, const QString& channel); bool CompareVersions(const std::string& current, const std::string& latest) const; #ifdef _WIN32