mirror of
https://git.citron-emu.org/citron/emulator
synced 2025-12-19 10:43:33 +00:00
fix: AppImage
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
#include <QSslConfiguration>
|
#include <QSslConfiguration>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QSslSocket>
|
#include <QSslSocket>
|
||||||
|
#include <QCryptographicHash>
|
||||||
|
|
||||||
#ifdef CITRON_ENABLE_LIBARCHIVE
|
#ifdef CITRON_ENABLE_LIBARCHIVE
|
||||||
#include <archive.h>
|
#include <archive.h>
|
||||||
@@ -38,17 +39,28 @@
|
|||||||
namespace Updater {
|
namespace Updater {
|
||||||
|
|
||||||
// Helper function to extract a commit hash from a string using std::regex.
|
// 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) {
|
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::regex re("\\b([0-9a-fA-F]{7,40})\\b");
|
||||||
std::smatch match;
|
std::smatch match;
|
||||||
if (std::regex_search(version_string, match, re) && match.size() > 1) {
|
if (std::regex_search(version_string, match, re) && match.size() > 1) {
|
||||||
return match[1].str();
|
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) {
|
UpdaterService::UpdaterService(QObject* parent) : QObject(parent) {
|
||||||
network_manager = std::make_unique<QNetworkAccessManager>(this);
|
network_manager = std::make_unique<QNetworkAccessManager>(this);
|
||||||
InitializeSSL();
|
InitializeSSL();
|
||||||
@@ -117,7 +129,7 @@ void UpdaterService::CheckForUpdates(const std::string& update_url) {
|
|||||||
void UpdaterService::ConfigureSSLForRequest(QNetworkRequest& request) {
|
void UpdaterService::ConfigureSSLForRequest(QNetworkRequest& request) {
|
||||||
if (!QSslSocket::supportsSsl()) return;
|
if (!QSslSocket::supportsSsl()) return;
|
||||||
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
|
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
|
||||||
sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); // Should be VerifyPeer in production
|
sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||||
sslConfig.setProtocol(QSsl::SecureProtocols);
|
sslConfig.setProtocol(QSsl::SecureProtocols);
|
||||||
request.setSslConfiguration(sslConfig);
|
request.setSslConfiguration(sslConfig);
|
||||||
}
|
}
|
||||||
@@ -166,26 +178,14 @@ void UpdaterService::CancelUpdate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string UpdaterService::GetCurrentVersion() const {
|
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;
|
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()) {
|
if (!build_version.empty()) {
|
||||||
std::string hash = ExtractCommitHash(build_version);
|
std::string hash = ExtractCommitHash(build_version);
|
||||||
if (!hash.empty()) {
|
if (!hash.empty()) {
|
||||||
LOG_INFO(Frontend, "Updater Debug: Extracted hash '{}' from SCM_REV.", hash);
|
|
||||||
return 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;
|
std::filesystem::path version_file = app_directory / CITRON_VERSION_FILE;
|
||||||
if (std::filesystem::exists(version_file)) {
|
if (std::filesystem::exists(version_file)) {
|
||||||
std::ifstream file(version_file);
|
std::ifstream file(version_file);
|
||||||
@@ -195,15 +195,13 @@ std::string UpdaterService::GetCurrentVersion() const {
|
|||||||
if (!version_from_file.empty()) {
|
if (!version_from_file.empty()) {
|
||||||
std::string hash = ExtractCommitHash(version_from_file);
|
std::string hash = ExtractCommitHash(version_from_file);
|
||||||
if (!hash.empty()){
|
if (!hash.empty()){
|
||||||
LOG_INFO(Frontend, "Found current version from file: {}", hash);
|
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_WARNING(Frontend, "Could not determine a valid commit hash for the current version.");
|
return "";
|
||||||
return ""; // Return empty if no reliable version is found.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UpdaterService::IsUpdateInProgress() const {
|
bool UpdaterService::IsUpdateInProgress() const {
|
||||||
@@ -223,6 +221,8 @@ void UpdaterService::OnDownloadFinished() {
|
|||||||
|
|
||||||
QByteArray downloaded_data = current_reply->readAll();
|
QByteArray downloaded_data = current_reply->readAll();
|
||||||
|
|
||||||
|
// This logic has been simplified for clarity. The checksum part can be re-added later.
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
QString filename = QStringLiteral("citron_update_%1.zip").arg(QString::fromStdString(current_update_info.version));
|
QString filename = QStringLiteral("citron_update_%1.zip").arg(QString::fromStdString(current_update_info.version));
|
||||||
std::filesystem::path download_path = temp_download_path / filename.toStdString();
|
std::filesystem::path download_path = temp_download_path / filename.toStdString();
|
||||||
@@ -261,10 +261,19 @@ void UpdaterService::OnDownloadFinished() {
|
|||||||
CleanupFiles();
|
CleanupFiles();
|
||||||
});
|
});
|
||||||
#elif defined(__linux__)
|
#elif defined(__linux__)
|
||||||
|
|
||||||
LOG_INFO(Frontend, "AppImage download completed.");
|
LOG_INFO(Frontend, "AppImage download completed.");
|
||||||
QString current_appimage_path_str = QCoreApplication::applicationFilePath();
|
|
||||||
std::filesystem::path current_appimage_path = current_appimage_path_str.toStdString();
|
// Get the path to the original AppImage file from the environment variable.
|
||||||
std::filesystem::path new_appimage_path = current_appimage_path.string() + ".new";
|
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()));
|
QFile new_file(QString::fromStdString(new_appimage_path.string()));
|
||||||
if (!new_file.open(QIODevice::WriteOnly)) {
|
if (!new_file.open(QIODevice::WriteOnly)) {
|
||||||
@@ -275,6 +284,7 @@ void UpdaterService::OnDownloadFinished() {
|
|||||||
new_file.write(downloaded_data);
|
new_file.write(downloaded_data);
|
||||||
new_file.close();
|
new_file.close();
|
||||||
|
|
||||||
|
// Make the new file executable.
|
||||||
if (!new_file.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner |
|
if (!new_file.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner |
|
||||||
QFileDevice::ReadGroup | QFileDevice::ExeGroup |
|
QFileDevice::ReadGroup | QFileDevice::ExeGroup |
|
||||||
QFileDevice::ReadOther | QFileDevice::ExeOther)) {
|
QFileDevice::ReadOther | QFileDevice::ExeOther)) {
|
||||||
@@ -283,8 +293,9 @@ void UpdaterService::OnDownloadFinished() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace the old AppImage with the new one.
|
||||||
std::error_code ec;
|
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) {
|
if (ec) {
|
||||||
LOG_ERROR(Frontend, "Failed to replace old AppImage: {}", ec.message());
|
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. Please close the application and replace it manually."));
|
||||||
@@ -312,7 +323,6 @@ void UpdaterService::OnDownloadError(QNetworkReply::NetworkError) {
|
|||||||
update_in_progress.store(false);
|
update_in_progress.store(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is updated to parse the commit hash from the release name.
|
|
||||||
void UpdaterService::ParseUpdateResponse(const QByteArray& response) {
|
void UpdaterService::ParseUpdateResponse(const QByteArray& response) {
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(response, &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();
|
QString release_name = release_obj.value(QStringLiteral("name")).toString();
|
||||||
|
|
||||||
if (release_name.toLower().contains(platform_identifier)) {
|
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());
|
std::string latest_hash = ExtractCommitHash(release_name.toStdString());
|
||||||
|
|
||||||
if (latest_hash.empty()) {
|
if (latest_hash.empty()) {
|
||||||
continue; // Skip this release if no commit hash is found in its name.
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateInfo update_info;
|
UpdateInfo update_info;
|
||||||
// MODIFIED: The version is now the clean, captured commit hash.
|
|
||||||
update_info.version = latest_hash;
|
update_info.version = latest_hash;
|
||||||
update_info.changelog = release_obj.value(QStringLiteral("body")).toString().toStdString();
|
update_info.changelog = release_obj.value(QStringLiteral("body")).toString().toStdString();
|
||||||
update_info.release_date = release_obj.value(QStringLiteral("published_at")).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();
|
QString asset_name = asset_obj.value(QStringLiteral("name")).toString();
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
if (asset_name.endsWith(QStringLiteral(".zip"))) {
|
if (asset_name.endsWith(QStringLiteral(".zip"))) {
|
||||||
|
#elif defined(__linux__)
|
||||||
|
if (asset_name.endsWith(QStringLiteral(".AppImage"))) {
|
||||||
|
#endif
|
||||||
DownloadOption option;
|
DownloadOption option;
|
||||||
option.name = asset_name.toStdString();
|
option.name = asset_name.toStdString();
|
||||||
option.url = asset_obj.value(QStringLiteral("browser_download_url")).toString().toStdString();
|
option.url = asset_obj.value(QStringLiteral("browser_download_url")).toString().toStdString();
|
||||||
update_info.download_options.push_back(option);
|
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()) {
|
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);
|
update_info.is_newer_version = CompareVersions(GetCurrentVersion(), update_info.version);
|
||||||
current_update_info = update_info;
|
current_update_info = update_info;
|
||||||
emit UpdateCheckCompleted(update_info.is_newer_version, 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."));
|
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 {
|
bool UpdaterService::CompareVersions(const std::string& current, const std::string& latest) const {
|
||||||
if (current.empty()) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (latest.empty()) {
|
if (latest.empty()) {
|
||||||
LOG_WARNING(Frontend, "Latest version from remote is empty, cannot compare.");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MODIFIED: For commit hashes, a simple string inequality check is sufficient and reliable.
|
|
||||||
bool is_newer = (current != latest);
|
bool is_newer = (current != latest);
|
||||||
LOG_INFO(Frontend, "Comparing versions. Current: '{}', Latest: '{}'. Is newer: {}", current, latest, is_newer);
|
|
||||||
return is_newer;
|
return is_newer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user