mirror of
https://git.eden-emu.dev/archive/citron
synced 2026-04-17 02:00:45 -04:00
Merge pull request 'Fix Windows auto updater file locking issue' (#12) from fix/windows-updater-helper into main
Reviewed-on: https://git.citron-emu.org/Citron/Emulator/pulls/12
This commit is contained in:
@@ -6069,8 +6069,22 @@ int main(int argc, char* argv[]) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CITRON_USE_AUTO_UPDATER
|
#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());
|
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::HasStagedUpdate(app_dir)) {
|
||||||
if (Updater::UpdaterService::ApplyStagedUpdate(app_dir)) {
|
if (Updater::UpdaterService::ApplyStagedUpdate(app_dir)) {
|
||||||
// Show a simple message that update was applied
|
// Show a simple message that update was applied
|
||||||
@@ -6079,6 +6093,7 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
OverrideWindowsFont();
|
OverrideWindowsFont();
|
||||||
|
|||||||
@@ -334,6 +334,31 @@ void UpdaterDialog::ShowInstallingState() {
|
|||||||
|
|
||||||
void UpdaterDialog::ShowCompletedState() {
|
void UpdaterDialog::ShowCompletedState() {
|
||||||
current_state = State::Completed;
|
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->titleLabel->setText(QStringLiteral("Update ready!"));
|
||||||
ui->statusLabel->setText(QStringLiteral("The update has been downloaded and prepared "
|
ui->statusLabel->setText(QStringLiteral("The update has been downloaded and prepared "
|
||||||
"successfully. The update will be applied when you "
|
"successfully. The update will be applied when you "
|
||||||
@@ -346,6 +371,7 @@ void UpdaterDialog::ShowCompletedState() {
|
|||||||
ui->progressBar->setValue(100);
|
ui->progressBar->setValue(100);
|
||||||
ui->appImageSelectorLabel->setVisible(false);
|
ui->appImageSelectorLabel->setVisible(false);
|
||||||
ui->appImageSelector->setVisible(false);
|
ui->appImageSelector->setVisible(false);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdaterDialog::ShowErrorState() {
|
void UpdaterDialog::ShowErrorState() {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QSslSocket>
|
#include <QSslSocket>
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
|
#include <QProcess>
|
||||||
|
|
||||||
#ifdef CITRON_ENABLE_LIBARCHIVE
|
#ifdef CITRON_ENABLE_LIBARCHIVE
|
||||||
#include <archive.h>
|
#include <archive.h>
|
||||||
@@ -474,6 +475,13 @@ bool UpdaterService::InstallUpdate(const std::filesystem::path& update_path) {
|
|||||||
manifest << "UPDATE_TIMESTAMP=" << std::time(nullptr) << "\n";
|
manifest << "UPDATE_TIMESTAMP=" << std::time(nullptr) << "\n";
|
||||||
manifest << "APP_DIRECTORY=" << app_directory.string() << "\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.");
|
LOG_INFO(Frontend, "Update staged successfully.");
|
||||||
return true;
|
return true;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
@@ -531,6 +539,95 @@ bool UpdaterService::RestoreBackup() {
|
|||||||
return false;
|
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
|
#endif
|
||||||
|
|
||||||
bool UpdaterService::CleanupFiles() {
|
bool UpdaterService::CleanupFiles() {
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ private:
|
|||||||
bool InstallUpdate(const std::filesystem::path& update_path);
|
bool InstallUpdate(const std::filesystem::path& update_path);
|
||||||
bool CreateBackup();
|
bool CreateBackup();
|
||||||
bool RestoreBackup();
|
bool RestoreBackup();
|
||||||
|
bool CreateUpdateHelperScript(const std::filesystem::path& staging_path);
|
||||||
|
bool LaunchUpdateHelper();
|
||||||
#endif
|
#endif
|
||||||
bool CleanupFiles();
|
bool CleanupFiles();
|
||||||
std::filesystem::path GetTempDirectory() const;
|
std::filesystem::path GetTempDirectory() const;
|
||||||
|
|||||||
Reference in New Issue
Block a user