mirror of
https://git.citron-emu.org/citron/emulator
synced 2025-12-19 02:33:32 +00:00
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:
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6091,25 +6091,17 @@ 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";
|
||||
#ifdef CITRON_USE_AUTO_UPDATER
|
||||
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
|
||||
#else
|
||||
QMessageBox::information(this, tr("Updates"),
|
||||
tr("The automatic updater is not enabled in this build."));
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void GMainWindow::CheckForUpdatesAutomatically() {
|
||||
@@ -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);
|
||||
|
||||
@@ -59,7 +59,7 @@ class QtControllerSelectorDialog;
|
||||
class QtProfileSelectionDialog;
|
||||
class QtSoftwareKeyboardDialog;
|
||||
class QtNXWebEngineView;
|
||||
class UpdaterDialog;
|
||||
namespace Updater { class UpdaterDialog; }
|
||||
|
||||
enum class StartGameType { Normal, Global };
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -48,9 +64,8 @@ UpdaterDialog::UpdaterDialog(QWidget* parent)
|
||||
if (current_state == State::Downloading) {
|
||||
ui->downloadInfoLabel->setText(
|
||||
QStringLiteral("Downloaded: %1 / %2")
|
||||
.arg(FormatBytes(downloaded_bytes))
|
||||
.arg(FormatBytes(total_download_size))
|
||||
);
|
||||
.arg(FormatBytes(downloaded_bytes))
|
||||
.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,20 +107,21 @@ 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) {
|
||||
case Updater::UpdaterService::UpdateResult::Success:
|
||||
ShowCompletedState();
|
||||
break;
|
||||
case Updater::UpdaterService::UpdateResult::Cancelled:
|
||||
close();
|
||||
break;
|
||||
default:
|
||||
ShowErrorState();
|
||||
ui->statusLabel->setText(GetUpdateMessage(result) + QStringLiteral("\n\n") + message);
|
||||
break;
|
||||
case Updater::UpdaterService::UpdateResult::Success:
|
||||
ShowCompletedState();
|
||||
break;
|
||||
case Updater::UpdaterService::UpdateResult::Cancelled:
|
||||
close();
|
||||
break;
|
||||
default:
|
||||
ShowErrorState();
|
||||
ui->statusLabel->setText(GetUpdateMessage(result) + QStringLiteral("\n\n") + message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,8 +132,27 @@ void UpdaterDialog::OnUpdateError(const QString& error_message) {
|
||||
}
|
||||
|
||||
void UpdaterDialog::OnDownloadButtonClicked() {
|
||||
ShowDownloadingState();
|
||||
updater_service->DownloadAndInstallUpdate(current_update_info);
|
||||
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(download_url);
|
||||
} else {
|
||||
OnUpdateError(QStringLiteral("No download URL could be found for the update."));
|
||||
}
|
||||
}
|
||||
|
||||
void UpdaterDialog::OnCancelButtonClicked() {
|
||||
@@ -141,266 +168,212 @@ 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 {
|
||||
switch (result) {
|
||||
case Updater::UpdaterService::UpdateResult::Success:
|
||||
return QStringLiteral("Update completed successfully!");
|
||||
case Updater::UpdaterService::UpdateResult::Failed:
|
||||
return QStringLiteral("Update failed due to an unknown error.");
|
||||
case Updater::UpdaterService::UpdateResult::Cancelled:
|
||||
return QStringLiteral("Update was cancelled.");
|
||||
case Updater::UpdaterService::UpdateResult::NetworkError:
|
||||
return QStringLiteral("Update failed due to a network error.");
|
||||
case Updater::UpdaterService::UpdateResult::ExtractionError:
|
||||
return QStringLiteral("Failed to extract the update archive.");
|
||||
case Updater::UpdaterService::UpdateResult::PermissionError:
|
||||
return QStringLiteral("Update failed due to insufficient permissions.");
|
||||
case Updater::UpdaterService::UpdateResult::InvalidArchive:
|
||||
return QStringLiteral("The downloaded update archive is invalid.");
|
||||
case Updater::UpdaterService::UpdateResult::NoUpdateAvailable:
|
||||
return QStringLiteral("No update is available.");
|
||||
default:
|
||||
return QStringLiteral("Unknown error occurred.");
|
||||
case Updater::UpdaterService::UpdateResult::Success:
|
||||
return QStringLiteral("Update completed successfully!");
|
||||
case Updater::UpdaterService::UpdateResult::Failed:
|
||||
return QStringLiteral("Update failed due to an unknown error.");
|
||||
case Updater::UpdaterService::UpdateResult::Cancelled:
|
||||
return QStringLiteral("Update was cancelled.");
|
||||
case Updater::UpdaterService::UpdateResult::NetworkError:
|
||||
return QStringLiteral("Update failed due to a network error.");
|
||||
case Updater::UpdaterService::UpdateResult::ExtractionError:
|
||||
return QStringLiteral("Failed to extract the update archive.");
|
||||
case Updater::UpdaterService::UpdateResult::PermissionError:
|
||||
return QStringLiteral("Update failed due to insufficient permissions.");
|
||||
case Updater::UpdaterService::UpdateResult::InvalidArchive:
|
||||
return QStringLiteral("The downloaded update archive is invalid.");
|
||||
case Updater::UpdaterService::UpdateResult::NoUpdateAvailable:
|
||||
return QStringLiteral("No update is available.");
|
||||
default:
|
||||
return QStringLiteral("Unknown error occurred.");
|
||||
}
|
||||
}
|
||||
|
||||
#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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -250,4 +266,4 @@ p, li { white-space: pre-wrap; }
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
</ui>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
#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
|
||||
} // namespace Updater
|
||||
|
||||
Reference in New Issue
Block a user