diff --git a/WheelWizard/Features/AutoUpdating/AutoUpdaterSingletonService.cs b/WheelWizard/Features/AutoUpdating/AutoUpdaterSingletonService.cs index f934e22d..b8d7b0dc 100644 --- a/WheelWizard/Features/AutoUpdating/AutoUpdaterSingletonService.cs +++ b/WheelWizard/Features/AutoUpdating/AutoUpdaterSingletonService.cs @@ -11,7 +11,7 @@ namespace WheelWizard.AutoUpdating; public interface IAutoUpdaterSingletonService { - public Task CheckForUpdatesAsync(); + public Task CheckForUpdatesAsync(bool updateAutomatically = false); } public class AutoUpdaterSingletonService( @@ -22,10 +22,10 @@ IGitHubSingletonService gitHubService { private string CurrentVersion => brandingService.Branding.Version; - public async Task CheckForUpdatesAsync() + public async Task CheckForUpdatesAsync(bool updateAutomatically = false) { // TODO: How to run this in a background thread? - var latestRelease = await GetLatestReleaseAsync(); + var latestRelease = await GetLatestReleaseAsync(showErrors: !updateAutomatically); if (latestRelease?.TagName is null) return; @@ -36,22 +36,25 @@ public async Task CheckForUpdatesAsync() var latestVersion = SemVersion.Parse(latestRelease.TagName.TrimStart('v'), SemVersionStyles.Any); var popupExtraText = t("question.new_version_wh_wz.extra", latestVersion, CurrentVersion)!; - var shouldUpdate = false; - await Dispatcher.UIThread.InvokeAsync(async () => + var shouldUpdate = updateAutomatically; + if (!updateAutomatically) { - shouldUpdate = await new YesNoWindow() - .SetButtonText(t("action.update"), t("action.maybe_later")) - .SetMainText(t("question.new_version_wh_wz.title")) - .SetExtraText(popupExtraText) - .AwaitAnswer(); - }); + await Dispatcher.UIThread.InvokeAsync(async () => + { + shouldUpdate = await new YesNoWindow() + .SetButtonText(t("action.update"), t("action.maybe_later")) + .SetMainText(t("question.new_version_wh_wz.title")) + .SetExtraText(popupExtraText) + .AwaitAnswer(); + }); + } if (!shouldUpdate) return; - var updateResult = await updatePlatform.ExecuteUpdateAsync(asset.BrowserDownloadUrl); + var updateResult = await updatePlatform.ExecuteUpdateAsync(asset.BrowserDownloadUrl, restartApplication: !updateAutomatically); - if (updateResult.IsFailure) + if (updateResult.IsFailure && !updateAutomatically) { await new MessageBoxWindow() .SetMessageType(MessageBoxWindow.MessageType.Warning) @@ -61,23 +64,26 @@ await Dispatcher.UIThread.InvokeAsync(async () => } } - private async Task GetLatestReleaseAsync() + private async Task GetLatestReleaseAsync(bool showErrors) { var releasesResult = await gitHubService.GetReleasesAsync(); if (releasesResult.IsFailure) { - await Dispatcher.UIThread.InvokeAsync(async () => + if (showErrors) { - await new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Error) - .SetTitleText("Failed to check for updates") - .SetInfoText( - "An error occurred while checking for updates. Please try again later. " - + "\nError: " - + releasesResult.Error.Message - ) - .ShowDialog(); - }); + await Dispatcher.UIThread.InvokeAsync(async () => + { + await new MessageBoxWindow() + .SetMessageType(MessageBoxWindow.MessageType.Error) + .SetTitleText("Failed to check for updates") + .SetInfoText( + "An error occurred while checking for updates. Please try again later. " + + "\nError: " + + releasesResult.Error.Message + ) + .ShowDialog(); + }); + } return null; } diff --git a/WheelWizard/Features/AutoUpdating/Platforms/FallbackUpdatePlatform.cs b/WheelWizard/Features/AutoUpdating/Platforms/FallbackUpdatePlatform.cs index 18040c67..803717b1 100644 --- a/WheelWizard/Features/AutoUpdating/Platforms/FallbackUpdatePlatform.cs +++ b/WheelWizard/Features/AutoUpdating/Platforms/FallbackUpdatePlatform.cs @@ -41,5 +41,5 @@ public class FallbackUpdatePlatform(IBrandingSingletonService brandingService) : return null; } - public Task ExecuteUpdateAsync(string downloadUrl) => Task.FromResult(Ok()); + public Task ExecuteUpdateAsync(string downloadUrl, bool restartApplication = true) => Task.FromResult(Ok()); } diff --git a/WheelWizard/Features/AutoUpdating/Platforms/IUpdatePlatform.cs b/WheelWizard/Features/AutoUpdating/Platforms/IUpdatePlatform.cs index ea6a1867..dacb8efc 100644 --- a/WheelWizard/Features/AutoUpdating/Platforms/IUpdatePlatform.cs +++ b/WheelWizard/Features/AutoUpdating/Platforms/IUpdatePlatform.cs @@ -15,5 +15,5 @@ public interface IUpdatePlatform /// /// Executes the update logic for the current platform. /// - Task ExecuteUpdateAsync(string downloadUrl); + Task ExecuteUpdateAsync(string downloadUrl, bool restartApplication = true); } diff --git a/WheelWizard/Features/AutoUpdating/Platforms/LinuxUpdatePlatform.cs b/WheelWizard/Features/AutoUpdating/Platforms/LinuxUpdatePlatform.cs index 0681d092..0db57955 100644 --- a/WheelWizard/Features/AutoUpdating/Platforms/LinuxUpdatePlatform.cs +++ b/WheelWizard/Features/AutoUpdating/Platforms/LinuxUpdatePlatform.cs @@ -23,7 +23,7 @@ public class LinuxUpdatePlatform(IFileSystem fileSystem) : IUpdatePlatform return release.Assets.FirstOrDefault(asset => asset.BrowserDownloadUrl.Contains(identifier, StringComparison.OrdinalIgnoreCase)); } - public async Task ExecuteUpdateAsync(string downloadUrl) + public async Task ExecuteUpdateAsync(string downloadUrl, bool restartApplication = true) { var currentExecutablePath = Environment.ProcessPath; if (currentExecutablePath is null) @@ -55,7 +55,7 @@ public async Task ExecuteUpdateAsync(string downloadUrl) await Task.Delay(201); // Create and run the shell script to perform the update. - var scriptResult = CreateAndRunShellScript(currentExecutablePath, newFilePath); + var scriptResult = CreateAndRunShellScript(currentExecutablePath, newFilePath, restartApplication); if (scriptResult.IsFailure) return scriptResult; @@ -64,7 +64,7 @@ public async Task ExecuteUpdateAsync(string downloadUrl) return Ok(); } - private OperationResult CreateAndRunShellScript(string currentFilePath, string newFilePath) + private OperationResult CreateAndRunShellScript(string currentFilePath, string newFilePath, bool restartApplication) { var currentFolder = fileSystem.Path.GetDirectoryName(currentFilePath); if (currentFolder is null) @@ -74,6 +74,10 @@ private OperationResult CreateAndRunShellScript(string currentFilePath, string n var originalFileName = fileSystem.Path.GetFileName(currentFilePath); var newFileName = fileSystem.Path.GetFileName(newFilePath); + var restartCommand = restartApplication + ? $"nohup {EnvHelper.SingleQuotePath(fileSystem.Path.Combine(currentFolder, originalFileName))} > /dev/null 2>&1 &" + : string.Empty; + var scriptContent = $""" #!/usr/bin/env sh echo 'Starting update process...' @@ -89,7 +93,7 @@ sleep 1 chmod +x {EnvHelper.SingleQuotePath(fileSystem.Path.Combine(currentFolder, originalFileName))} echo 'Starting the updated application...' - nohup {EnvHelper.SingleQuotePath(fileSystem.Path.Combine(currentFolder, originalFileName))} > /dev/null 2>&1 & + {restartCommand} echo 'Cleaning up...' rm -- {EnvHelper.SingleQuotePath(scriptFilePath)} diff --git a/WheelWizard/Features/AutoUpdating/Platforms/WindowsUpdatePlatform.cs b/WheelWizard/Features/AutoUpdating/Platforms/WindowsUpdatePlatform.cs index fe22ec2a..4f016a42 100644 --- a/WheelWizard/Features/AutoUpdating/Platforms/WindowsUpdatePlatform.cs +++ b/WheelWizard/Features/AutoUpdating/Platforms/WindowsUpdatePlatform.cs @@ -15,8 +15,11 @@ public class WindowsUpdatePlatform(IFileSystem fileSystem) : IUpdatePlatform return release.Assets.FirstOrDefault(asset => asset.BrowserDownloadUrl.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)); } - public async Task ExecuteUpdateAsync(string downloadUrl) + public async Task ExecuteUpdateAsync(string downloadUrl, bool restartApplication = true) { + if (!restartApplication) + return await UpdateAsync(downloadUrl, restartApplication: false); + // If running as administrator, update immediately. if (IsAdministrator()) return await UpdateAsync(downloadUrl); @@ -63,7 +66,7 @@ private static bool IsAdministrator() return principal.IsInRole(WindowsBuiltInRole.Administrator); } - private async Task UpdateAsync(string downloadUrl) + private async Task UpdateAsync(string downloadUrl, bool restartApplication = true) { var currentExecutablePath = Environment.ProcessPath; if (currentExecutablePath is null) @@ -95,7 +98,7 @@ private async Task UpdateAsync(string downloadUrl) await Task.Delay(200); // Create and run the PowerShell script to perform the update. - var scriptResult = CreateAndRunPowerShellScript(currentExecutablePath, newFilePath); + var scriptResult = CreateAndRunPowerShellScript(currentExecutablePath, newFilePath, restartApplication); if (scriptResult.IsFailure) return scriptResult; @@ -104,7 +107,7 @@ private async Task UpdateAsync(string downloadUrl) return Ok(); } - private OperationResult CreateAndRunPowerShellScript(string currentFilePath, string newFilePath) + private OperationResult CreateAndRunPowerShellScript(string currentFilePath, string newFilePath, bool restartApplication) { var currentFolder = fileSystem.Path.GetDirectoryName(currentFilePath); if (currentFolder is null) @@ -114,6 +117,10 @@ private OperationResult CreateAndRunPowerShellScript(string currentFilePath, str var originalFileName = fileSystem.Path.GetFileName(currentFilePath); var newFileName = fileSystem.Path.GetFileName(newFilePath); + var restartCommand = restartApplication + ? $"Start-Process -FilePath {EnvHelper.SingleQuotePath(fileSystem.Path.Combine(currentFolder, originalFileName))}" + : string.Empty; + var scriptContent = $$""" Write-Output 'Starting update process...' @@ -161,7 +168,7 @@ exit 1 } Write-Output 'Starting the updated application...' - Start-Process -FilePath {{EnvHelper.SingleQuotePath(fileSystem.Path.Combine(currentFolder, originalFileName))}} + {{restartCommand}} Write-Output 'Cleaning up...' Remove-Item -Path {{EnvHelper.SingleQuotePath(scriptFilePath)}} -Force diff --git a/WheelWizard/Views/App.axaml.cs b/WheelWizard/Views/App.axaml.cs index 399fa5a2..1f61e47a 100644 --- a/WheelWizard/Views/App.axaml.cs +++ b/WheelWizard/Views/App.axaml.cs @@ -4,6 +4,7 @@ using Avalonia.Markup.Xaml; using Microsoft.Extensions.Logging; using WheelWizard.AutoUpdating; +using WheelWizard.CustomDistributions; using WheelWizard.MiiRendering.Services; using WheelWizard.Mods; using WheelWizard.Services; @@ -117,6 +118,9 @@ private static StartupLaunchTarget GetStartupLaunchTarget() return StartupLaunchTarget.None; } + private static bool IsUpdateOnlyRequested() => + Environment.GetCommandLineArgs().Skip(1).Any(argument => argument.Equals("--updateonly", StringComparison.OrdinalIgnoreCase)); + private static async Task OpenGameBananaModWindowAsync() { var protocolArgument = GetLaunchProtocolArgument(); @@ -197,6 +201,12 @@ private async Task InitializeDesktopAsync(IClassicDesktopStyleApplicationLifetim { try { + if (IsUpdateOnlyRequested()) + { + await RunUpdateOnlyAsync(desktop); + return; + } + var resourceInstaller = Services.GetRequiredService(); if (resourceInstaller.GetResolvedResourcePath().IsFailure) { @@ -225,4 +235,20 @@ private async Task InitializeDesktopAsync(IClassicDesktopStyleApplicationLifetim desktop.Shutdown(); } } + + private static async Task RunUpdateOnlyAsync(IClassicDesktopStyleApplicationLifetime desktop) + { + var logger = Services.GetRequiredService>(); + var distributions = Services.GetRequiredService(); + + if (distributions.RetroRewind.GetCurrentVersion() is not null) + { + var updateResult = await Services.GetRequiredService().Update(); + if (updateResult.IsFailure) + logger.LogError(updateResult.Error.Exception, "Failed to update Retro Rewind: {Message}", updateResult.Error.Message); + } + + await Services.GetRequiredService().CheckForUpdatesAsync(updateAutomatically: true); + desktop.Shutdown(); + } }