mirror of
https://git.citron-emu.org/citron/emulator
synced 2025-12-30 06:54:07 +00:00
Fix Windows auto updater file locking issue
Implement deferred update mechanism using a helper batch script that applies updates after the application exits, avoiding Windows file locking issues. On Windows, the updater now: - Stages update files and creates a helper batch script - Launches the script as a detached process - Exits the application - The script waits for app closure, applies updates, and restarts Citron Linux AppImage updates continue to work as before with the existing method. Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
@@ -6069,8 +6069,22 @@ int main(int argc, char* argv[]) {
|
||||
#endif
|
||||
|
||||
#ifdef CITRON_USE_AUTO_UPDATER
|
||||
// Check for and apply staged updates before starting the main application
|
||||
std::filesystem::path app_dir = std::filesystem::path(QCoreApplication::applicationDirPath().toStdString());
|
||||
|
||||
#ifdef _WIN32
|
||||
// On Windows, updates are applied by the helper script after the app exits.
|
||||
// If we find a staging directory here, it means the helper script failed.
|
||||
// Clean it up to avoid confusion.
|
||||
std::filesystem::path staging_path = app_dir / "update_staging";
|
||||
if (std::filesystem::exists(staging_path)) {
|
||||
try {
|
||||
std::filesystem::remove_all(staging_path);
|
||||
} catch (...) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
#else
|
||||
// On Linux, apply staged updates at startup as before
|
||||
if (Updater::UpdaterService::HasStagedUpdate(app_dir)) {
|
||||
if (Updater::UpdaterService::ApplyStagedUpdate(app_dir)) {
|
||||
// Show a simple message that update was applied
|
||||
@@ -6079,6 +6093,7 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
OverrideWindowsFont();
|
||||
|
||||
@@ -334,6 +334,31 @@ void UpdaterDialog::ShowInstallingState() {
|
||||
|
||||
void UpdaterDialog::ShowCompletedState() {
|
||||
current_state = State::Completed;
|
||||
|
||||
#ifdef _WIN32
|
||||
// On Windows, launch the update helper script and exit immediately
|
||||
ui->titleLabel->setText(QStringLiteral("Update ready!"));
|
||||
ui->statusLabel->setText(QStringLiteral("Citron will now restart to apply the update..."));
|
||||
ui->progressGroup->setVisible(false);
|
||||
ui->downloadButton->setVisible(false);
|
||||
ui->cancelButton->setVisible(false);
|
||||
ui->closeButton->setVisible(false);
|
||||
ui->restartButton->setVisible(false);
|
||||
ui->progressBar->setValue(100);
|
||||
ui->appImageSelectorLabel->setVisible(false);
|
||||
ui->appImageSelector->setVisible(false);
|
||||
|
||||
// Give the user a moment to see the message
|
||||
QTimer::singleShot(1500, this, [this]() {
|
||||
if (updater_service->LaunchUpdateHelper()) {
|
||||
QApplication::quit();
|
||||
} else {
|
||||
ShowErrorState();
|
||||
ui->statusLabel->setText(QStringLiteral("Failed to launch update helper. Please restart Citron manually to apply the update."));
|
||||
}
|
||||
});
|
||||
#else
|
||||
// On Linux, show the restart button as before
|
||||
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 "
|
||||
@@ -346,6 +371,7 @@ void UpdaterDialog::ShowCompletedState() {
|
||||
ui->progressBar->setValue(100);
|
||||
ui->appImageSelectorLabel->setVisible(false);
|
||||
ui->appImageSelector->setVisible(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void UpdaterDialog::ShowErrorState() {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QSslSocket>
|
||||
#include <QCryptographicHash>
|
||||
#include <QProcess>
|
||||
|
||||
#ifdef CITRON_ENABLE_LIBARCHIVE
|
||||
#include <archive.h>
|
||||
@@ -474,6 +475,13 @@ bool UpdaterService::InstallUpdate(const std::filesystem::path& update_path) {
|
||||
manifest << "UPDATE_TIMESTAMP=" << std::time(nullptr) << "\n";
|
||||
manifest << "APP_DIRECTORY=" << app_directory.string() << "\n";
|
||||
}
|
||||
|
||||
// Create the update helper script for deferred update application
|
||||
if (!CreateUpdateHelperScript(staging_path)) {
|
||||
LOG_ERROR(Frontend, "Failed to create update helper script");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO(Frontend, "Update staged successfully.");
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
@@ -531,6 +539,95 @@ bool UpdaterService::RestoreBackup() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool UpdaterService::CreateUpdateHelperScript(const std::filesystem::path& staging_path) {
|
||||
try {
|
||||
std::filesystem::path script_path = staging_path / "apply_update.bat";
|
||||
std::ofstream script(script_path);
|
||||
|
||||
if (!script.is_open()) {
|
||||
LOG_ERROR(Frontend, "Failed to create update helper script");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert paths to Windows-style paths for the batch script
|
||||
std::string staging_path_str = staging_path.string();
|
||||
std::string app_path_str = app_directory.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 : app_path_str) if (ch == '/') ch = '\\';
|
||||
for (auto& ch : exe_path_str) if (ch == '/') ch = '\\';
|
||||
|
||||
// Write batch script
|
||||
script << "@echo off\n";
|
||||
script << "REM Citron Auto-Updater Helper Script\n";
|
||||
script << "REM This script applies staged updates after the main application exits\n\n";
|
||||
|
||||
script << "echo Waiting for Citron to close...\n";
|
||||
script << "timeout /t 3 /nobreak >nul\n\n";
|
||||
|
||||
script << "echo Applying update...\n";
|
||||
script << "xcopy /E /Y /I \"" << staging_path_str << "\" \"" << app_path_str << "\" >nul 2>&1\n\n";
|
||||
|
||||
script << "if errorlevel 1 (\n";
|
||||
script << " echo Update failed. Please restart Citron manually.\n";
|
||||
script << " timeout /t 5\n";
|
||||
script << " exit /b 1\n";
|
||||
script << ")\n\n";
|
||||
|
||||
script << "echo Update applied successfully!\n";
|
||||
script << "timeout /t 1 /nobreak >nul\n\n";
|
||||
|
||||
script << "echo Restarting Citron...\n";
|
||||
script << "start \"\" \"" << exe_path_str << "\"\n\n";
|
||||
|
||||
script << "REM Clean up staging directory\n";
|
||||
script << "rd /s /q \"" << staging_path_str << "\" >nul 2>&1\n\n";
|
||||
|
||||
script << "REM Delete this script\n";
|
||||
script << "del \"%~f0\"\n";
|
||||
|
||||
script.close();
|
||||
|
||||
LOG_INFO(Frontend, "Update helper script created: {}", script_path.string());
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Frontend, "Failed to create update helper script: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool UpdaterService::LaunchUpdateHelper() {
|
||||
try {
|
||||
std::filesystem::path staging_path = app_directory / "update_staging";
|
||||
std::filesystem::path script_path = staging_path / "apply_update.bat";
|
||||
|
||||
if (!std::filesystem::exists(script_path)) {
|
||||
LOG_ERROR(Frontend, "Update helper script not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Launch the batch script as a detached process
|
||||
QString script_path_str = QString::fromStdString(script_path.string());
|
||||
QStringList arguments;
|
||||
|
||||
// Use cmd.exe to run the batch file in a hidden window
|
||||
bool launched = QProcess::startDetached("cmd.exe", QStringList() << "/C" << script_path_str);
|
||||
|
||||
if (launched) {
|
||||
LOG_INFO(Frontend, "Update helper script launched successfully");
|
||||
return true;
|
||||
} else {
|
||||
LOG_ERROR(Frontend, "Failed to launch update helper script");
|
||||
return false;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Frontend, "Failed to launch update helper: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool UpdaterService::CleanupFiles() {
|
||||
|
||||
@@ -79,6 +79,8 @@ private:
|
||||
bool InstallUpdate(const std::filesystem::path& update_path);
|
||||
bool CreateBackup();
|
||||
bool RestoreBackup();
|
||||
bool CreateUpdateHelperScript(const std::filesystem::path& staging_path);
|
||||
bool LaunchUpdateHelper();
|
||||
#endif
|
||||
bool CleanupFiles();
|
||||
std::filesystem::path GetTempDirectory() const;
|
||||
|
||||
Reference in New Issue
Block a user