mirror of
https://git.citron-emu.org/citron/emulator
synced 2025-12-19 18:53:32 +00:00
feat: Backup Paths & Disable Option for Linux
Signed-off-by: Collecting <collecting@noreply.localhost>
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "citron/updater/updater_service.h"
|
#include "citron/updater/updater_service.h"
|
||||||
|
#include "citron/uisettings.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/fs/path_util.h"
|
#include "common/fs/path_util.h"
|
||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
@@ -271,8 +272,6 @@ void UpdaterService::OnDownloadFinished() {
|
|||||||
QSettings settings;
|
QSettings settings;
|
||||||
QString channel = settings.value(QStringLiteral("updater/channel"), QStringLiteral("Stable")).toString();
|
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.
|
|
||||||
|
|
||||||
#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();
|
||||||
@@ -323,16 +322,27 @@ void UpdaterService::OnDownloadFinished() {
|
|||||||
|
|
||||||
std::filesystem::path original_appimage_path = appimage_path_env;
|
std::filesystem::path original_appimage_path = appimage_path_env;
|
||||||
std::filesystem::path appimage_dir = original_appimage_path.parent_path();
|
std::filesystem::path appimage_dir = original_appimage_path.parent_path();
|
||||||
std::filesystem::path backup_dir = appimage_dir / "backup";
|
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
|
|
||||||
// 1. Create the backup directory
|
// Check if backups are enabled before doing anything.
|
||||||
|
if (UISettings::values.updater_enable_backups.GetValue()) {
|
||||||
|
const std::string& custom_backup_path = UISettings::values.updater_backup_path.GetValue();
|
||||||
|
std::filesystem::path backup_dir;
|
||||||
|
|
||||||
|
if (!custom_backup_path.empty()) {
|
||||||
|
// User has specified a custom path.
|
||||||
|
backup_dir = custom_backup_path;
|
||||||
|
} else {
|
||||||
|
// Default behavior: create 'backup' folder next to the AppImage.
|
||||||
|
backup_dir = appimage_dir / "backup";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the backup directory
|
||||||
std::filesystem::create_directories(backup_dir, ec);
|
std::filesystem::create_directories(backup_dir, ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
LOG_ERROR(Frontend, "Failed to create backup directory: {}", ec.message());
|
LOG_ERROR(Frontend, "Failed to create backup directory: {}", ec.message());
|
||||||
// Do not stop the update; the backup is a convenience, not critical.
|
|
||||||
} else {
|
} else {
|
||||||
// 2. Create the backup copy of the old AppImage
|
// Create the backup copy of the old AppImage
|
||||||
std::string current_version = GetCurrentVersion();
|
std::string current_version = GetCurrentVersion();
|
||||||
std::string backup_filename = "citron-backup-" + (current_version.empty() ? "unknown" : current_version) + ".AppImage";
|
std::string backup_filename = "citron-backup-" + (current_version.empty() ? "unknown" : current_version) + ".AppImage";
|
||||||
std::filesystem::path backup_filepath = backup_dir / backup_filename;
|
std::filesystem::path backup_filepath = backup_dir / backup_filename;
|
||||||
@@ -343,8 +353,8 @@ void UpdaterService::OnDownloadFinished() {
|
|||||||
LOG_INFO(Frontend, "Created backup of old AppImage at: {}", backup_filepath.string());
|
LOG_INFO(Frontend, "Created backup of old AppImage at: {}", backup_filepath.string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Save the new AppImage to a temporary file
|
|
||||||
std::filesystem::path new_appimage_path = original_appimage_path.string() + ".new";
|
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)) {
|
||||||
@@ -355,17 +365,15 @@ void UpdaterService::OnDownloadFinished() {
|
|||||||
new_file.write(downloaded_data);
|
new_file.write(downloaded_data);
|
||||||
new_file.close();
|
new_file.close();
|
||||||
|
|
||||||
// 4. 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)) {
|
||||||
emit UpdateError(QStringLiteral("Failed to make the new AppImage executable."));
|
emit UpdateError(QStringLiteral("Failed to make the new AppImage executable."));
|
||||||
std::filesystem::remove(new_appimage_path, ec); // Clean up temp file
|
std::filesystem::remove(new_appimage_path, ec);
|
||||||
update_in_progress.store(false);
|
update_in_progress.store(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Atomically replace the old AppImage with the new one
|
|
||||||
std::filesystem::rename(new_appimage_path, original_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());
|
||||||
@@ -374,7 +382,6 @@ void UpdaterService::OnDownloadFinished() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Update or remove the version file as before
|
|
||||||
std::filesystem::path version_file_path = appimage_dir / CITRON_VERSION_FILE;
|
std::filesystem::path version_file_path = appimage_dir / CITRON_VERSION_FILE;
|
||||||
if (channel == QStringLiteral("Stable")) {
|
if (channel == QStringLiteral("Stable")) {
|
||||||
LOG_INFO(Frontend, "Writing stable version marker: {}", current_update_info.version);
|
LOG_INFO(Frontend, "Writing stable version marker: {}", current_update_info.version);
|
||||||
@@ -626,49 +633,49 @@ bool UpdaterService::CreateUpdateHelperScript(const std::filesystem::path& stagi
|
|||||||
std::filesystem::path script_path = staging_path / "apply_update.bat";
|
std::filesystem::path script_path = staging_path / "apply_update.bat";
|
||||||
LOG_INFO(Frontend, "Creating update helper script at: {}", script_path.string());
|
LOG_INFO(Frontend, "Creating update helper script at: {}", script_path.string());
|
||||||
|
|
||||||
// Ensure staging directory exists
|
|
||||||
if (!std::filesystem::exists(staging_path)) {
|
if (!std::filesystem::exists(staging_path)) {
|
||||||
LOG_ERROR(Frontend, "Staging path does not exist: {}", staging_path.string());
|
LOG_ERROR(Frontend, "Staging path does not exist: {}", staging_path.string());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ofstream script(script_path, std::ios::out | std::ios::trunc);
|
std::ofstream script(script_path, std::ios::out | std::ios::trunc);
|
||||||
|
|
||||||
if (!script.is_open()) {
|
if (!script.is_open()) {
|
||||||
LOG_ERROR(Frontend, "Failed to open file for writing: {}", script_path.string());
|
LOG_ERROR(Frontend, "Failed to open file for writing: {}", script_path.string());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert paths to Windows-style paths for the batch script
|
|
||||||
std::string staging_path_str = staging_path.string();
|
std::string staging_path_str = staging_path.string();
|
||||||
std::string app_path_str = app_directory.string();
|
std::string app_path_str = app_directory.string();
|
||||||
std::string exe_path_str = (app_directory / "citron.exe").string();
|
std::string exe_path_str = (app_directory / "citron.exe").string();
|
||||||
|
|
||||||
// Replace forward slashes with backslashes
|
|
||||||
for (auto& ch : staging_path_str) if (ch == '/') ch = '\\';
|
for (auto& ch : staging_path_str) if (ch == '/') ch = '\\';
|
||||||
for (auto& ch : app_path_str) if (ch == '/') ch = '\\';
|
for (auto& ch : app_path_str) if (ch == '/') ch = '\\';
|
||||||
for (auto& ch : exe_path_str) if (ch == '/') ch = '\\';
|
for (auto& ch : exe_path_str) if (ch == '/') ch = '\\';
|
||||||
|
|
||||||
// Write batch script
|
|
||||||
script << "@echo off\n";
|
script << "@echo off\n";
|
||||||
script << "REM Citron Auto-Updater Helper Script\n";
|
script << "REM Citron Auto-Updater Helper Script\n\n";
|
||||||
script << "REM This script applies staged updates after the main application exits\n\n";
|
|
||||||
|
|
||||||
script << "echo Waiting for Citron to close...\n";
|
script << "echo Waiting for Citron to close...\n";
|
||||||
script << "timeout /t 3 /nobreak >nul\n\n";
|
|
||||||
|
|
||||||
script << "echo Applying update...\n";
|
// This loop will continuously check if citron.exe is running.
|
||||||
|
// It will only proceed once the process is no longer found.
|
||||||
|
script << ":wait_loop\n";
|
||||||
|
script << "tasklist /FI \"IMAGENAME eq citron.exe\" | find /I \"citron.exe\" >nul\n";
|
||||||
|
script << "if not errorlevel 1 (\n";
|
||||||
|
script << " timeout /t 1 /nobreak >nul\n";
|
||||||
|
script << " goto wait_loop\n";
|
||||||
|
script << ")\n\n";
|
||||||
|
|
||||||
|
script << "echo Citron has closed. Applying update...\n";
|
||||||
script << "xcopy /E /Y /I \"" << staging_path_str << "\" \"" << app_path_str << "\" >nul 2>&1\n\n";
|
script << "xcopy /E /Y /I \"" << staging_path_str << "\" \"" << app_path_str << "\" >nul 2>&1\n\n";
|
||||||
|
|
||||||
script << "if errorlevel 1 (\n";
|
script << "if errorlevel 1 (\n";
|
||||||
script << " echo Update failed. Please restart Citron manually.\n";
|
script << " echo Update failed. Please restart Citron manually.\n";
|
||||||
script << " timeout /t 5\n";
|
script << " pause\n"; // Pause to let the user see the error
|
||||||
script << " exit /b 1\n";
|
script << " exit /b 1\n";
|
||||||
script << ")\n\n";
|
script << ")\n\n";
|
||||||
|
|
||||||
script << "echo Update applied successfully!\n";
|
script << "echo Update applied successfully!\n";
|
||||||
script << "timeout /t 1 /nobreak >nul\n\n";
|
|
||||||
|
|
||||||
script << "echo Restarting Citron...\n";
|
script << "echo Restarting Citron...\n";
|
||||||
script << "start \"\" \"" << exe_path_str << "\"\n\n";
|
script << "start \"\" \"" << exe_path_str << "\"\n\n";
|
||||||
|
|
||||||
@@ -676,20 +683,12 @@ bool UpdaterService::CreateUpdateHelperScript(const std::filesystem::path& stagi
|
|||||||
script << "rd /s /q \"" << staging_path_str << "\" >nul 2>&1\n\n";
|
script << "rd /s /q \"" << staging_path_str << "\" >nul 2>&1\n\n";
|
||||||
|
|
||||||
script << "REM Delete this script\n";
|
script << "REM Delete this script\n";
|
||||||
script << "del \"%~f0\"\n";
|
script << "(goto) 2>nul & del \"%~f0\"\n";
|
||||||
|
|
||||||
script.flush();
|
script.flush();
|
||||||
script.close();
|
script.close();
|
||||||
|
|
||||||
// Verify the file was created
|
LOG_INFO(Frontend, "Update helper script created successfully: {}", script_path.string());
|
||||||
if (!std::filesystem::exists(script_path)) {
|
|
||||||
LOG_ERROR(Frontend, "Script file was not created despite successful write!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto file_size = std::filesystem::file_size(script_path);
|
|
||||||
LOG_INFO(Frontend, "Update helper script created successfully: {} ({} bytes)",
|
|
||||||
script_path.string(), file_size);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
LOG_ERROR(Frontend, "Exception creating update helper script: {}", e.what());
|
LOG_ERROR(Frontend, "Exception creating update helper script: {}", e.what());
|
||||||
|
|||||||
Reference in New Issue
Block a user