Merge branch 'fix/autoupdater' into 'main'

feat(updater): Overhaul to use commit hashes and improve UI

See merge request citron/emulator!117
This commit is contained in:
Zephyron
2025-10-25 14:45:50 +10:00
10 changed files with 470 additions and 814 deletions

View File

@@ -95,7 +95,7 @@ set(DEFAULT_ENABLE_OPENSSL ON)
if (ANDROID OR WIN32 OR APPLE)
set(DEFAULT_ENABLE_OPENSSL OFF)
endif()
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ON)
if (ANDROID AND CITRON_DOWNLOAD_ANDROID_VVL)
set(vvl_version "vulkan-sdk-1.4.328.1")

View File

@@ -53,4 +53,11 @@ if (BUILD_REPOSITORY)
endif()
endif()
# Add a fallback for local builds
# If BUILD_VERSION is still "0", it means the CI-specific logic was not executed.
# In that case, we should use the git description which contains the commit hash.
if (BUILD_VERSION STREQUAL "0")
set(BUILD_VERSION ${GIT_DESC})
endif()
configure_file(scm_rev.cpp.in scm_rev.cpp @ONLY)

View File

@@ -265,6 +265,8 @@ if (CITRON_USE_AUTO_UPDATER)
$<$<BOOL:${WIN32}>:_WIN32>
CITRON_USE_AUTO_UPDATER
)
target_link_libraries(citron PRIVATE Qt6::Network)
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
@@ -429,7 +431,7 @@ else()
target_link_libraries(citron PRIVATE common core input_common frontend_common network video_core)
endif()
target_link_libraries(citron PRIVATE Boost::headers glad Qt6::Widgets)
target_link_libraries(citron PRIVATE Boost::headers glad Qt6::Widgets Qt6::Network)
target_link_libraries(citron PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
# Add libarchive for updater functionality

View File

@@ -6092,20 +6092,12 @@ int main(int argc, char* argv[]) {
void GMainWindow::OnCheckForUpdates() {
#ifdef CITRON_USE_AUTO_UPDATER
#ifdef _WIN32
// Use HTTP URL to bypass SSL issues (will be redirected to HTTPS but handled by updater)
// TODO: Fix SSL libraries and revert to https://releases.citron-emu.org/api/check
std::string update_url = "http://releases.citron-emu.org/api/check";
std::string update_url = "https://api.github.com/repos/Zephyron-Dev/Citron-CI/releases";
// Create and show the updater dialog
auto* updater_dialog = new UpdaterDialog(this);
auto* updater_dialog = new Updater::UpdaterDialog(this);
updater_dialog->setAttribute(Qt::WA_DeleteOnClose);
updater_dialog->show();
updater_dialog->CheckForUpdates(update_url);
#else
QMessageBox::information(this, tr("Updates"),
tr("The update dialog is only available on Windows in this build."));
#endif
#else
QMessageBox::information(this, tr("Updates"),
tr("The automatic updater is not enabled in this build."));
@@ -6122,7 +6114,7 @@ void GMainWindow::CheckForUpdatesAutomatically() {
LOG_INFO(Frontend, "Checking for updates automatically...");
// Use HTTP URL to bypass SSL issues
std::string update_url = "http://releases.citron-emu.org/api/check";
std::string update_url = "https://api.github.com/repos/Zephyron-Dev/Citron-CI/releases";
// Create updater service for silent background check
auto* updater_service = new Updater::UpdaterService(this);

View File

@@ -59,7 +59,7 @@ class QtControllerSelectorDialog;
class QtProfileSelectionDialog;
class QtSoftwareKeyboardDialog;
class QtNXWebEngineView;
class UpdaterDialog;
namespace Updater { class UpdaterDialog; }
enum class StartGameType { Normal, Global };

View File

@@ -2,17 +2,33 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "citron/updater/updater_dialog.h"
#ifdef _WIN32
#include "ui_updater_dialog.h"
#include <QApplication>
#include <QMessageBox>
#include <QCloseEvent>
#include <QDateTime>
#include <QDesktopServices>
#include <QUrl>
#include <QTimer>
#include <QMessageBox>
#include <QProcess>
#include <QTimer>
#include <QUrl>
namespace Updater {
// Helper function to format the date and time
QString FormatDateTimeString(const std::string& iso_string) {
if (iso_string.empty() || iso_string == "Unknown") {
return QStringLiteral("Unknown");
}
// Parse the ISO 8601 date string provided by the GitHub API.
QDateTime date_time = QDateTime::fromString(QString::fromStdString(iso_string), Qt::ISODate);
if (!date_time.isValid()) {
return QString::fromStdString(iso_string); // Fallback to original if parsing fails.
}
// Convert from UTC (which the 'Z' indicates) to the user's local time
// and format it in a friendly, readable way.
return date_time.toLocalTime().toString(QStringLiteral("MMMM d, yyyy 'at' hh:mm AP"));
}
UpdaterDialog::UpdaterDialog(QWidget* parent)
: QDialog(parent), ui(std::make_unique<Ui::UpdaterDialog>()),
@@ -23,16 +39,16 @@ UpdaterDialog::UpdaterDialog(QWidget* parent)
ui->setupUi(this);
// Set up connections
connect(updater_service.get(), &Updater::UpdaterService::UpdateCheckCompleted,
this, &UpdaterDialog::OnUpdateCheckCompleted);
connect(updater_service.get(), &Updater::UpdaterService::UpdateDownloadProgress,
this, &UpdaterDialog::OnUpdateDownloadProgress);
connect(updater_service.get(), &Updater::UpdaterService::UpdateInstallProgress,
this, &UpdaterDialog::OnUpdateInstallProgress);
connect(updater_service.get(), &Updater::UpdaterService::UpdateCompleted,
this, &UpdaterDialog::OnUpdateCompleted);
connect(updater_service.get(), &Updater::UpdaterService::UpdateError,
this, &UpdaterDialog::OnUpdateError);
connect(updater_service.get(), &Updater::UpdaterService::UpdateCheckCompleted, this,
&UpdaterDialog::OnUpdateCheckCompleted);
connect(updater_service.get(), &Updater::UpdaterService::UpdateDownloadProgress, this,
&UpdaterDialog::OnUpdateDownloadProgress);
connect(updater_service.get(), &Updater::UpdaterService::UpdateInstallProgress, this,
&UpdaterDialog::OnUpdateInstallProgress);
connect(updater_service.get(), &Updater::UpdaterService::UpdateCompleted, this,
&UpdaterDialog::OnUpdateCompleted);
connect(updater_service.get(), &Updater::UpdaterService::UpdateError, this,
&UpdaterDialog::OnUpdateError);
// Set up UI connections
connect(ui->downloadButton, &QPushButton::clicked, this, &UpdaterDialog::OnDownloadButtonClicked);
@@ -49,8 +65,7 @@ UpdaterDialog::UpdaterDialog(QWidget* parent)
ui->downloadInfoLabel->setText(
QStringLiteral("Downloaded: %1 / %2")
.arg(FormatBytes(downloaded_bytes))
.arg(FormatBytes(total_download_size))
);
.arg(FormatBytes(total_download_size)));
}
});
}
@@ -62,25 +77,17 @@ void UpdaterDialog::CheckForUpdates(const std::string& update_url) {
updater_service->CheckForUpdates(update_url);
}
void UpdaterDialog::ShowUpdateAvailable(const Updater::UpdateInfo& update_info) {
current_update_info = update_info;
ShowUpdateAvailableState();
}
void UpdaterDialog::ShowUpdateChecking() {
ShowCheckingState();
}
void UpdaterDialog::OnUpdateCheckCompleted(bool has_update, const Updater::UpdateInfo& update_info) {
if (has_update) {
current_update_info = update_info;
ShowUpdateAvailableState();
} else {
ShowNoUpdateState();
ShowNoUpdateState(update_info);
}
}
void UpdaterDialog::OnUpdateDownloadProgress(int percentage, qint64 bytes_received, qint64 bytes_total) {
void UpdaterDialog::OnUpdateDownloadProgress(int percentage, qint64 bytes_received,
qint64 bytes_total) {
downloaded_bytes = bytes_received;
total_download_size = bytes_total;
@@ -100,7 +107,8 @@ void UpdaterDialog::OnUpdateInstallProgress(int percentage, const QString& curre
ui->downloadInfoLabel->setText(current_file);
}
void UpdaterDialog::OnUpdateCompleted(Updater::UpdaterService::UpdateResult result, const QString& message) {
void UpdaterDialog::OnUpdateCompleted(Updater::UpdaterService::UpdateResult result,
const QString& message) {
progress_timer->stop();
switch (result) {
@@ -124,8 +132,27 @@ void UpdaterDialog::OnUpdateError(const QString& error_message) {
}
void UpdaterDialog::OnDownloadButtonClicked() {
std::string download_url;
#ifdef __linux__
if (ui->appImageSelector->isVisible() && !current_update_info.download_options.empty()) {
int current_index = ui->appImageSelector->currentIndex();
if (current_index >= 0 && static_cast<size_t>(current_index) < current_update_info.download_options.size()) {
download_url = current_update_info.download_options[current_index].url;
}
}
#endif
if (download_url.empty() && !current_update_info.download_options.empty()) {
download_url = current_update_info.download_options[0].url;
}
if (!download_url.empty()) {
ShowDownloadingState();
updater_service->DownloadAndInstallUpdate(current_update_info);
updater_service->DownloadAndInstallUpdate(download_url);
} else {
OnUpdateError(QStringLiteral("No download URL could be found for the update."));
}
}
void UpdaterDialog::OnCancelButtonClicked() {
@@ -141,192 +168,187 @@ void UpdaterDialog::OnCloseButtonClicked() {
}
void UpdaterDialog::OnRestartButtonClicked() {
// Ask for confirmation
int ret = QMessageBox::question(this, QStringLiteral("Restart Citron"),
QStringLiteral("Are you sure you want to restart Citron now?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes);
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (ret == QMessageBox::Yes) {
// Get the current executable path
QString program = QApplication::applicationFilePath();
QStringList arguments = QApplication::arguments();
arguments.removeFirst(); // Remove the program name
arguments.removeFirst();
// Start the new instance
QProcess::startDetached(program, arguments);
// Close the current instance
QApplication::quit();
}
}
void UpdaterDialog::SetupUI() {
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setFixedSize(size());
// Set current version
// MODIFIED: Use setMinimumSize to allow the window to be resized larger.
setMinimumSize(size());
ui->currentVersionValue->setText(QString::fromStdString(updater_service->GetCurrentVersion()));
ui->appImageSelectorLabel->setVisible(false);
ui->appImageSelector->setVisible(false);
ShowCheckingState();
}
void UpdaterDialog::ShowCheckingState() {
current_state = State::Checking;
ui->titleLabel->setText(QStringLiteral("Checking for updates..."));
ui->statusLabel->setText(QStringLiteral("Please wait while we check for available updates..."));
ui->updateInfoGroup->setVisible(false);
ui->changelogGroup->setVisible(false);
ui->progressGroup->setVisible(false);
ui->downloadButton->setVisible(false);
ui->cancelButton->setVisible(true);
ui->closeButton->setVisible(false);
ui->restartButton->setVisible(false);
ui->cancelButton->setText(QStringLiteral("Cancel"));
ui->appImageSelectorLabel->setVisible(false);
ui->appImageSelector->setVisible(false);
}
void UpdaterDialog::ShowNoUpdateState() {
void UpdaterDialog::ShowNoUpdateState(const Updater::UpdateInfo& update_info) {
current_state = State::NoUpdate;
ui->titleLabel->setText(QStringLiteral("No updates available"));
ui->statusLabel->setText(QStringLiteral("You are running the latest version of Citron."));
ui->updateInfoGroup->setVisible(true);
ui->latestVersionValue->setText(QString::fromStdString(update_info.version));
// MODIFIED: Use the new helper function to format the release date.
ui->releaseDateValue->setText(FormatDateTimeString(update_info.release_date));
ui->changelogGroup->setVisible(false);
ui->progressGroup->setVisible(false);
ui->downloadButton->setVisible(false);
ui->cancelButton->setVisible(false);
ui->closeButton->setVisible(true);
ui->restartButton->setVisible(false);
ui->appImageSelectorLabel->setVisible(false);
ui->appImageSelector->setVisible(false);
}
void UpdaterDialog::ShowUpdateAvailableState() {
current_state = State::UpdateAvailable;
ui->titleLabel->setText(QStringLiteral("Update available"));
ui->statusLabel->setText(QStringLiteral("A new version of Citron is available for download."));
// Fill in update information
ui->latestVersionValue->setText(QString::fromStdString(current_update_info.version));
ui->releaseDateValue->setText(QString::fromStdString(current_update_info.release_date));
// Show changelog if available
// MODIFIED: Use the new helper function to format the release date.
ui->releaseDateValue->setText(FormatDateTimeString(current_update_info.release_date));
if (!current_update_info.changelog.empty()) {
ui->changelogText->setPlainText(QString::fromStdString(current_update_info.changelog));
// MODIFIED: Use setMarkdown to render the changelog with formatting and links.
ui->changelogText->setMarkdown(QString::fromStdString(current_update_info.changelog));
ui->changelogGroup->setVisible(true);
} else {
ui->changelogGroup->setVisible(false);
}
#ifdef __linux__
if (current_update_info.download_options.size() > 1) {
ui->appImageSelector->clear();
for (const auto& option : current_update_info.download_options) {
ui->appImageSelector->addItem(QString::fromStdString(option.name));
}
ui->appImageSelectorLabel->setVisible(true);
ui->appImageSelector->setVisible(true);
} else {
ui->appImageSelectorLabel->setVisible(false);
ui->appImageSelector->setVisible(false);
}
#else
ui->appImageSelectorLabel->setVisible(false);
ui->appImageSelector->setVisible(false);
#endif
ui->updateInfoGroup->setVisible(true);
ui->progressGroup->setVisible(false);
ui->downloadButton->setVisible(true);
ui->cancelButton->setVisible(true);
ui->closeButton->setVisible(false);
ui->restartButton->setVisible(false);
ui->cancelButton->setText(QStringLiteral("Later"));
}
void UpdaterDialog::ShowDownloadingState() {
current_state = State::Downloading;
ui->titleLabel->setText(QStringLiteral("Downloading update..."));
ui->statusLabel->setText(QStringLiteral("Please wait while the update is being downloaded and installed."));
ui->statusLabel->setText(
QStringLiteral("Please wait while the update is being downloaded and installed."));
ui->updateInfoGroup->setVisible(false);
ui->changelogGroup->setVisible(false);
ui->progressGroup->setVisible(true);
ui->progressLabel->setText(QStringLiteral("Preparing download..."));
ui->progressBar->setValue(0);
ui->downloadInfoLabel->setText(QStringLiteral(""));
ui->downloadButton->setVisible(false);
ui->cancelButton->setVisible(true);
ui->closeButton->setVisible(false);
ui->restartButton->setVisible(false);
ui->cancelButton->setText(QStringLiteral("Cancel"));
ui->appImageSelectorLabel->setVisible(false);
ui->appImageSelector->setVisible(false);
progress_timer->start();
}
void UpdaterDialog::ShowInstallingState() {
current_state = State::Installing;
ui->titleLabel->setText(QStringLiteral("Installing update..."));
ui->statusLabel->setText(QStringLiteral("Please wait while the update is being installed. Do not close the application."));
ui->statusLabel->setText(QStringLiteral(
"Please wait while the update is being installed. Do not close the application."));
ui->progressLabel->setText(QStringLiteral("Installing..."));
ui->downloadInfoLabel->setText(QStringLiteral(""));
ui->cancelButton->setVisible(false);
ui->appImageSelectorLabel->setVisible(false);
ui->appImageSelector->setVisible(false);
}
void UpdaterDialog::ShowCompletedState() {
current_state = State::Completed;
ui->titleLabel->setText(QStringLiteral("Update ready!"));
ui->statusLabel->setText(QStringLiteral("The update has been downloaded and prepared successfully. The update will be applied when you restart Citron."));
ui->statusLabel->setText(QStringLiteral("The update has been downloaded and prepared "
"successfully. The update will be applied when you "
"restart Citron."));
ui->progressGroup->setVisible(false);
ui->downloadButton->setVisible(false);
ui->cancelButton->setVisible(false);
ui->closeButton->setVisible(true);
ui->restartButton->setVisible(true);
ui->progressBar->setValue(100);
ui->appImageSelectorLabel->setVisible(false);
ui->appImageSelector->setVisible(false);
}
void UpdaterDialog::ShowErrorState() {
current_state = State::Error;
ui->titleLabel->setText(QStringLiteral("Update failed"));
// statusLabel text is set by the caller
ui->updateInfoGroup->setVisible(false);
ui->changelogGroup->setVisible(false);
ui->progressGroup->setVisible(false);
ui->downloadButton->setVisible(false);
ui->cancelButton->setVisible(false);
ui->closeButton->setVisible(true);
ui->restartButton->setVisible(false);
}
void UpdaterDialog::UpdateDownloadProgress(int percentage, qint64 bytes_received, qint64 bytes_total) {
downloaded_bytes = bytes_received;
total_download_size = bytes_total;
ui->progressBar->setValue(percentage);
ui->progressLabel->setText(QStringLiteral("Downloading update... %1%").arg(percentage));
}
void UpdaterDialog::UpdateInstallProgress(int percentage, const QString& current_file) {
ui->progressBar->setValue(percentage);
ui->progressLabel->setText(QStringLiteral("Installing update... %1%").arg(percentage));
ui->downloadInfoLabel->setText(current_file);
ui->appImageSelectorLabel->setVisible(false);
ui->appImageSelector->setVisible(false);
}
QString UpdaterDialog::FormatBytes(qint64 bytes) const {
const QStringList units = {QStringLiteral("B"), QStringLiteral("KB"), QStringLiteral("MB"), QStringLiteral("GB")};
const QStringList units = {QStringLiteral("B"), QStringLiteral("KB"), QStringLiteral("MB"),
QStringLiteral("GB")};
double size = bytes;
int unit = 0;
while (size >= 1024.0 && unit < units.size() - 1) {
size /= 1024.0;
unit++;
}
return QStringLiteral("%1 %2").arg(QString::number(size, 'f', unit == 0 ? 0 : 1)).arg(units[unit]);
return QStringLiteral("%1 %2")
.arg(QString::number(size, 'f', unit == 0 ? 0 : 1))
.arg(units[unit]);
}
QString UpdaterDialog::GetUpdateMessage(Updater::UpdaterService::UpdateResult result) const {
@@ -352,55 +374,6 @@ QString UpdaterDialog::GetUpdateMessage(Updater::UpdaterService::UpdateResult re
}
}
#include "updater_dialog.moc"
#else // _WIN32
// Forward declarations for non-Windows platforms
namespace Updater {
struct UpdateInfo {};
class UpdaterService {
public:
enum class UpdateResult { Success };
};
}
// Stub implementations for non-Windows platforms
UpdaterDialog::UpdaterDialog(QWidget* parent) : QDialog(parent) {}
UpdaterDialog::~UpdaterDialog() = default;
void UpdaterDialog::CheckForUpdates(const std::string&) {}
void UpdaterDialog::ShowUpdateAvailable(const Updater::UpdateInfo&) {}
void UpdaterDialog::ShowUpdateChecking() {}
void UpdaterDialog::OnUpdateCheckCompleted(bool, const Updater::UpdateInfo&) {}
void UpdaterDialog::OnUpdateDownloadProgress(int, qint64, qint64) {}
void UpdaterDialog::OnUpdateInstallProgress(int, const QString&) {}
#ifdef _WIN32
void UpdaterDialog::OnUpdateCompleted(Updater::UpdaterService::UpdateResult, const QString&) {}
#else
void UpdaterDialog::OnUpdateCompleted(int, const QString&) {}
#endif
void UpdaterDialog::OnUpdateError(const QString&) {}
void UpdaterDialog::OnDownloadButtonClicked() {}
void UpdaterDialog::OnCancelButtonClicked() {}
void UpdaterDialog::OnCloseButtonClicked() {}
void UpdaterDialog::OnRestartButtonClicked() {}
void UpdaterDialog::SetupUI() {}
void UpdaterDialog::ShowCheckingState() {}
void UpdaterDialog::ShowNoUpdateState() {}
void UpdaterDialog::ShowUpdateAvailableState() {}
void UpdaterDialog::ShowDownloadingState() {}
void UpdaterDialog::ShowInstallingState() {}
void UpdaterDialog::ShowCompletedState() {}
void UpdaterDialog::ShowErrorState() {}
void UpdaterDialog::UpdateDownloadProgress(int, qint64, qint64) {}
void UpdaterDialog::UpdateInstallProgress(int, const QString&) {}
QString UpdaterDialog::FormatBytes(qint64) const { return QString(); }
#ifdef _WIN32
QString UpdaterDialog::GetUpdateMessage(Updater::UpdaterService::UpdateResult) const { return QString(); }
#else
QString UpdaterDialog::GetUpdateMessage(int) const { return QString(); }
#endif
} // namespace Updater
#include "updater_dialog.moc"
#endif // _WIN32

View File

@@ -3,29 +3,22 @@
#pragma once
#include <memory>
#include <QDialog>
#include <QProgressBar>
#include <QLabel>
#include <QPushButton>
#include <QTextBrowser>
#include <QTimer>
#ifdef _WIN32
#include <memory>
#include "citron/updater/updater_service.h"
#else
// Forward declarations for non-Windows platforms
namespace Updater {
struct UpdateInfo;
class UpdaterService;
}
#endif
// Forward declare QString for the helper function.
class QString;
namespace Ui {
class UpdaterDialog;
}
namespace Updater {
// Add the declaration for the date formatting helper function.
QString FormatDateTimeString(const std::string& iso_string);
class UpdaterDialog : public QDialog {
Q_OBJECT
@@ -33,73 +26,40 @@ public:
explicit UpdaterDialog(QWidget* parent = nullptr);
~UpdaterDialog() override;
// Check for updates using the given URL
void CheckForUpdates(const std::string& update_url);
// Show update available dialog
void ShowUpdateAvailable(const Updater::UpdateInfo& update_info);
// Show update checking dialog
void ShowUpdateChecking();
private slots:
void OnUpdateCheckCompleted(bool has_update, const Updater::UpdateInfo& update_info);
void OnUpdateDownloadProgress(int percentage, qint64 bytes_received, qint64 bytes_total);
void OnUpdateInstallProgress(int percentage, const QString& current_file);
#ifdef _WIN32
void OnUpdateCompleted(Updater::UpdaterService::UpdateResult result, const QString& message);
#else
void OnUpdateCompleted(int result, const QString& message);
#endif
void OnUpdateError(const QString& error_message);
void OnDownloadButtonClicked();
void OnCancelButtonClicked();
void OnCloseButtonClicked();
void OnRestartButtonClicked();
private:
enum class State { Checking, NoUpdate, UpdateAvailable, Downloading, Installing, Completed, Error };
void SetupUI();
void ShowCheckingState();
void ShowNoUpdateState();
void ShowNoUpdateState(const Updater::UpdateInfo& update_info);
void ShowUpdateAvailableState();
void ShowDownloadingState();
void ShowInstallingState();
void ShowCompletedState();
void ShowErrorState();
void UpdateDownloadProgress(int percentage, qint64 bytes_received, qint64 bytes_total);
void UpdateInstallProgress(int percentage, const QString& current_file);
QString FormatBytes(qint64 bytes) const;
#ifdef _WIN32
QString GetUpdateMessage(Updater::UpdaterService::UpdateResult result) const;
#else
QString GetUpdateMessage(int result) const;
#endif
private:
#ifdef _WIN32
std::unique_ptr<Ui::UpdaterDialog> ui;
std::unique_ptr<Updater::UpdaterService> updater_service;
Updater::UpdateInfo current_update_info;
// UI state
enum class State {
Checking,
NoUpdate,
UpdateAvailable,
Downloading,
Installing,
Completed,
Error
};
State current_state = State::Checking;
// Progress tracking
qint64 total_download_size = 0;
qint64 downloaded_bytes = 0;
UpdateInfo current_update_info;
State current_state;
qint64 total_download_size;
qint64 downloaded_bytes;
QTimer* progress_timer;
#endif // _WIN32
};
} // namespace Updater

View File

@@ -192,6 +192,22 @@ p, li { white-space: pre-wrap; }
</property>
</spacer>
</item>
<!-- Start of added widgets -->
<item>
<layout class="QHBoxLayout" name="appImageSelectorLayout">
<item>
<widget class="QLabel" name="appImageSelectorLabel">
<property name="text">
<string>Select AppImage:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="appImageSelector"/>
</item>
</layout>
</item>
<!-- End of added widgets -->
<item>
<layout class="QHBoxLayout" name="buttonLayout">
<item>

File diff suppressed because it is too large Load Diff

View File

@@ -3,24 +3,31 @@
#pragma once
#include <memory>
#include <functional>
#include <QObject>
#include <string>
#include <filesystem>
#include <thread>
#include <atomic>
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QProgressDialog>
#include <QMessageBox>
#include <memory>
#include <vector>
#include <QString>
#include <QNetworkAccessManager>
namespace Updater {
// Declaration for the helper function.
QString FormatDateTimeString(const std::string& iso_string);
std::string ExtractCommitHash(const std::string& version_string);
struct DownloadOption {
std::string name;
std::string url;
};
struct UpdateInfo {
std::string version;
std::string download_url;
std::vector<DownloadOption> download_options;
std::string changelog;
std::string release_date;
bool is_newer_version = false;
@@ -30,36 +37,17 @@ class UpdaterService : public QObject {
Q_OBJECT
public:
enum class UpdateResult {
Success,
Failed,
Cancelled,
NetworkError,
ExtractionError,
PermissionError,
InvalidArchive,
NoUpdateAvailable
};
enum class UpdateResult { Success, Failed, Cancelled, NetworkError, ExtractionError, PermissionError, InvalidArchive, NoUpdateAvailable };
explicit UpdaterService(QObject* parent = nullptr);
~UpdaterService() override;
// Check for updates from the configured URL
void CheckForUpdates(const std::string& update_url);
// Download and install update
void DownloadAndInstallUpdate(const UpdateInfo& update_info);
// Cancel current operation
void DownloadAndInstallUpdate(const std::string& download_url);
void CancelUpdate();
// Get current application version
std::string GetCurrentVersion() const;
// Check if update is in progress
bool IsUpdateInProgress() const;
// Static methods for startup update handling
static bool HasStagedUpdate(const std::filesystem::path& app_directory);
static bool ApplyStagedUpdate(const std::filesystem::path& app_directory);
@@ -76,50 +64,37 @@ private slots:
void OnDownloadError(QNetworkReply::NetworkError error);
private:
// Network operations
void InitializeSSL();
void ConfigureSSLForRequest(QNetworkRequest& request);
void ParseUpdateResponse(const QByteArray& response);
bool CompareVersions(const std::string& current, const std::string& latest) const;
// SSL configuration
void InitializeSSL();
void ConfigureSSLForRequest(QNetworkRequest& request);
// File operations
#ifdef _WIN32
bool ExtractArchive(const std::filesystem::path& archive_path, const std::filesystem::path& extract_path);
#if defined(_WIN32) && !defined(CITRON_ENABLE_LIBARCHIVE)
#ifndef CITRON_ENABLE_LIBARCHIVE
bool ExtractArchiveWindows(const std::filesystem::path& archive_path, const std::filesystem::path& extract_path);
#endif
bool InstallUpdate(const std::filesystem::path& update_path);
bool CreateBackup();
bool RestoreBackup();
#endif
bool CleanupFiles();
// Helper functions
std::filesystem::path GetTempDirectory() const;
std::filesystem::path GetApplicationDirectory() const;
std::filesystem::path GetBackupDirectory() const;
bool EnsureDirectoryExists(const std::filesystem::path& path) const;
// Network components
std::unique_ptr<QNetworkAccessManager> network_manager;
QNetworkReply* current_reply = nullptr;
// Update state
std::atomic<bool> update_in_progress{false};
std::atomic<bool> cancel_requested{false};
UpdateInfo current_update_info;
// File paths
std::filesystem::path app_directory;
std::filesystem::path temp_download_path;
std::filesystem::path backup_path;
std::filesystem::path app_directory;
// Constants
static constexpr const char* CITRON_VERSION_FILE = "version.txt";
static constexpr const char* UPDATE_MANIFEST_FILE = "update_manifest.json";
static constexpr const char* BACKUP_DIRECTORY = "backup";
static constexpr const char* TEMP_DIRECTORY = "temp";
static constexpr size_t MAX_DOWNLOAD_SIZE = 500 * 1024 * 1024; // 500MB limit
};
} // namespace Updater