feat: Backup Paths & Disable Option for Linux

Signed-off-by: Collecting <collecting@noreply.localhost>
This commit is contained in:
Collecting
2025-12-11 07:33:37 +00:00
parent b8fb643417
commit c9cb8a64e7

View File

@@ -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());