From 0e32bb6b619df2b61d5575ac18cb50c88e1fcefb Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 9 Mar 2023 21:35:12 +0100 Subject: [PATCH] Output install log to file --- .../Repositories/ReleaseRepository.cs | 7 +- .../ApplicationStateManager.cs | 24 ++++--- .../WindowsUpdateNotificationProvider.cs | 68 ++++++++++++------- src/Artemis.UI.Windows/Scripts/update.ps1 | 26 ++++--- src/Artemis.UI/DryIoc/ContainerExtensions.cs | 2 +- .../Settings/Tabs/ReleasesTabViewModel.cs | 4 +- ....cs => BasicUpdateNotificationProvider.cs} | 64 +++++++++++------ .../Updating/IUpdateNotificationProvider.cs | 2 +- .../Services/Updating/UpdateService.cs | 15 +++- 9 files changed, 139 insertions(+), 73 deletions(-) rename src/Artemis.UI/Services/Updating/{InAppUpdateNotificationProvider.cs => BasicUpdateNotificationProvider.cs} (56%) diff --git a/src/Artemis.Storage/Repositories/ReleaseRepository.cs b/src/Artemis.Storage/Repositories/ReleaseRepository.cs index 3516c6c80..c5c18ae27 100644 --- a/src/Artemis.Storage/Repositories/ReleaseRepository.cs +++ b/src/Artemis.Storage/Repositories/ReleaseRepository.cs @@ -15,13 +15,14 @@ public class ReleaseRepository : IReleaseRepository _repository.Database.GetCollection().EnsureIndex(s => s.Version, true); } - public void SaveVersionInstallDate(string version) + public bool SaveVersionInstallDate(string version) { ReleaseEntity release = _repository.Query().Where(r => r.Version == version).FirstOrDefault(); if (release != null) - return; + return false; _repository.Insert(new ReleaseEntity {Version = version, InstalledAt = DateTimeOffset.UtcNow}); + return true; } public ReleaseEntity GetPreviousInstalledVersion() @@ -32,6 +33,6 @@ public class ReleaseRepository : IReleaseRepository public interface IReleaseRepository : IRepository { - void SaveVersionInstallDate(string version); + bool SaveVersionInstallDate(string version); ReleaseEntity GetPreviousInstalledVersion(); } \ No newline at end of file diff --git a/src/Artemis.UI.Windows/ApplicationStateManager.cs b/src/Artemis.UI.Windows/ApplicationStateManager.cs index 05435e5ac..966324b7a 100644 --- a/src/Artemis.UI.Windows/ApplicationStateManager.cs +++ b/src/Artemis.UI.Windows/ApplicationStateManager.cs @@ -99,18 +99,12 @@ public class ApplicationStateManager argsList.Add("--autorun"); // Retain startup arguments after update by providing them to the script - string script = $"\"{Path.Combine(Constants.UpdatingFolder, "installing", "scripts", "update.ps1")}\""; + string script = Path.Combine(Constants.UpdatingFolder, "installing", "scripts", "update.ps1"); string source = $"-sourceDirectory \"{Path.Combine(Constants.UpdatingFolder, "installing")}\""; string destination = $"-destinationDirectory \"{Constants.ApplicationFolder}\""; string args = argsList.Any() ? $"-artemisArgs \"{string.Join(',', argsList)}\"" : ""; - // Run the PowerShell script included in the new version, that way any changes made to the script are used - ProcessStartInfo info = new() - { - Arguments = $"-File {script} {source} {destination} {args}", - FileName = "PowerShell.exe" - }; - Process.Start(info); + RunScriptWithOutputFile(script, $"{source} {destination} {args}", Path.Combine(Constants.DataFolder, "update-log.txt")); // Lets try a graceful shutdown, PowerShell will kill if needed if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime) @@ -142,6 +136,20 @@ public class ApplicationStateManager Process.Start(info); } + private void RunScriptWithOutputFile(string script, string arguments, string outputFile) + { + // Use > for files that are bigger than 200kb to start fresh, otherwise use >> to append + string redirectSymbol = File.Exists(outputFile) && new FileInfo(outputFile).Length > 200000 ? ">" : ">>"; + ProcessStartInfo info = new() + { + Arguments = $"PowerShell -File \"{script}\" {arguments} {redirectSymbol} \"{outputFile}\"", + FileName = "PowerShell.exe", + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = true, + }; + Process.Start(info); + } + [System.Runtime.InteropServices.DllImport("user32.dll")] private static extern int GetSystemMetrics(int nIndex); } \ No newline at end of file diff --git a/src/Artemis.UI.Windows/Providers/WindowsUpdateNotificationProvider.cs b/src/Artemis.UI.Windows/Providers/WindowsUpdateNotificationProvider.cs index 3b434360d..7af9d6927 100644 --- a/src/Artemis.UI.Windows/Providers/WindowsUpdateNotificationProvider.cs +++ b/src/Artemis.UI.Windows/Providers/WindowsUpdateNotificationProvider.cs @@ -35,30 +35,12 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider ToastNotificationManagerCompat.OnActivated += ToastNotificationManagerCompatOnOnActivated; } - private async void ToastNotificationManagerCompatOnOnActivated(ToastNotificationActivatedEventArgsCompat e) - { - ToastArguments args = ToastArguments.Parse(e.Argument); - Guid releaseId = Guid.Parse(args.Get("releaseId")); - string releaseVersion = args.Get("releaseVersion"); - string action = "view-changes"; - if (args.Contains("action")) - action = args.Get("action"); - - if (action == "install") - await InstallRelease(releaseId, releaseVersion); - else if (action == "view-changes") - ViewRelease(releaseId); - else if (action == "cancel") - _cancellationTokenSource?.Cancel(); - else if (action == "restart-for-update") - _updateService.RestartForUpdate(false); - } - + /// public void ShowNotification(Guid releaseId, string releaseVersion) { GetBuilderForRelease(releaseId, releaseVersion) .AddText("Update available") - .AddText($"Artemis version {releaseVersion} has been released") + .AddText($"Artemis {releaseVersion} has been released") .AddButton(new ToastButton() .SetContent("Install") .AddArgument("action", "install").SetAfterActivationBehavior(ToastAfterActivationBehavior.PendingUpdate)) @@ -66,14 +48,24 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider .Show(t => t.Tag = releaseId.ToString()); } - private void ViewRelease(Guid releaseId) + /// + public void ShowInstalledNotification(string installedVersion) + { + new ToastContentBuilder().AddArgument("releaseVersion", installedVersion) + .AddText("Update installed") + .AddText($"Artemis {installedVersion} has been installed") + .AddButton(new ToastButton().SetContent("View changes").AddArgument("action", "view-changes")) + .Show(); + } + + private void ViewRelease(string releaseVersion) { Dispatcher.UIThread.Post(() => { _mainWindowService.OpenMainWindow(); if (_mainWindowService.HostScreen == null) return; - + // TODO: When proper routing has been implemented, use that here // Create a settings VM to navigate to SettingsViewModel settingsViewModel = _getSettingsViewModel(_mainWindowService.HostScreen); @@ -83,7 +75,7 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider // Navigate to the settings VM _mainWindowService.HostScreen.Router.Navigate.Execute(settingsViewModel); // Navigate to the release tab - releaseTabViewModel.PreselectId = releaseId; + releaseTabViewModel.PreselectVersion = releaseVersion; settingsViewModel.SelectedTab = releaseTabViewModel; }); } @@ -128,10 +120,18 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider installer.PropertyChanged -= InstallerOnPropertyChanged; } + // If the main window is not open the user isn't busy, restart straight away + if (!_mainWindowService.IsMainWindowOpen) + { + _updateService.RestartForUpdate(true); + return; + } + + // Ask for a restart because the user is actively using Artemis GetBuilderForRelease(releaseId, releaseVersion) .AddAudio(new ToastAudio {Silent = true}) .AddText("Update ready") - .AddText($"Artemis version {releaseVersion} is ready to be applied") + .AddText("Artemis must restart to finish the update") .AddButton(new ToastButton().SetContent("Restart Artemis").AddArgument("action", "restart-for-update")) .AddButton(new ToastButton().SetContent("Later").AddArgument("action", "postpone-update")) .Show(t => t.Tag = releaseId.ToString()); @@ -160,4 +160,24 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider return data; } + + private async void ToastNotificationManagerCompatOnOnActivated(ToastNotificationActivatedEventArgsCompat e) + { + ToastArguments args = ToastArguments.Parse(e.Argument); + + Guid releaseId = args.Contains("releaseId") ? Guid.Parse(args.Get("releaseId")) : Guid.Empty; + string releaseVersion = args.Get("releaseVersion"); + string action = "view-changes"; + if (args.Contains("action")) + action = args.Get("action"); + + if (action == "install") + await InstallRelease(releaseId, releaseVersion); + else if (action == "view-changes") + ViewRelease(releaseVersion); + else if (action == "cancel") + _cancellationTokenSource?.Cancel(); + else if (action == "restart-for-update") + _updateService.RestartForUpdate(false); + } } \ No newline at end of file diff --git a/src/Artemis.UI.Windows/Scripts/update.ps1 b/src/Artemis.UI.Windows/Scripts/update.ps1 index 26fbd1165..d78fa8358 100644 --- a/src/Artemis.UI.Windows/Scripts/update.ps1 +++ b/src/Artemis.UI.Windows/Scripts/update.ps1 @@ -1,17 +1,16 @@ param ( - [Parameter(Mandatory=$true)][string]$sourceDirectory, - [Parameter(Mandatory=$true)][string]$destinationDirectory, - [Parameter(Mandatory=$false)][string]$artemisArgs + [Parameter(Mandatory = $true)][string]$sourceDirectory, + [Parameter(Mandatory = $true)][string]$destinationDirectory, + [Parameter(Mandatory = $false)][string]$artemisArgs ) Write-Host "Artemis update script v1" -Write-Host "Please do not close this window, this should not take long" -Write-Host "" # Wait up to 10 seconds for the process to shut down -for ($i=1; $i -le 10; $i++) { +for ($i = 1; $i -le 10; $i++) { $process = Get-Process -Name Artemis.UI.Windows -ErrorAction SilentlyContinue - if (!$process) { + if (!$process) + { break } Write-Host "Waiting for Artemis to shut down ($i / 10)" @@ -20,13 +19,15 @@ for ($i=1; $i -le 10; $i++) { # If the process is still running, kill it $process = Get-Process -Name Artemis.UI.Windows -ErrorAction SilentlyContinue -if ($process) { +if ($process) +{ Stop-Process -Id $process.Id -Force Start-Sleep -Seconds 1 } # Check if the destination directory exists -if (!(Test-Path $destinationDirectory)) { +if (!(Test-Path $destinationDirectory)) +{ Write-Error "The destination directory does not exist" } @@ -44,8 +45,11 @@ Write-Host "Finished! Restarting Artemis" Start-Sleep -Seconds 1 # When finished, run the updated version -if ($artemisArgs) { +if ($artemisArgs) +{ Start-Process -FilePath "$destinationDirectory\Artemis.UI.Windows.exe" -WorkingDirectory $destinationDirectory -ArgumentList $artemisArgs -} else { +} +else +{ Start-Process -FilePath "$destinationDirectory\Artemis.UI.Windows.exe" -WorkingDirectory $destinationDirectory } \ No newline at end of file diff --git a/src/Artemis.UI/DryIoc/ContainerExtensions.cs b/src/Artemis.UI/DryIoc/ContainerExtensions.cs index cd7b167a5..9a4095b20 100644 --- a/src/Artemis.UI/DryIoc/ContainerExtensions.cs +++ b/src/Artemis.UI/DryIoc/ContainerExtensions.cs @@ -36,7 +36,7 @@ public static class ContainerExtensions container.Register(Reuse.Singleton); container.Register(Reuse.Singleton); - container.Register(); + container.Register(); container.RegisterMany(thisAssembly, type => type.IsAssignableTo(), Reuse.Singleton); } diff --git a/src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabViewModel.cs index 8d4c54f1c..de7f7cdd5 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabViewModel.cs @@ -54,14 +54,14 @@ public class ReleasesTabViewModel : ActivatableViewModelBase { await _updateService.CacheLatestRelease(); await GetMoreReleases(d.AsCancellationToken()); - SelectedReleaseViewModel = ReleaseViewModels.FirstOrDefault(r => r.ReleaseId == PreselectId) ?? ReleaseViewModels.FirstOrDefault(); + SelectedReleaseViewModel = ReleaseViewModels.FirstOrDefault(r => r.Version == PreselectVersion) ?? ReleaseViewModels.FirstOrDefault(); }); } public ReadOnlyObservableCollection ReleaseViewModels { get; } public string Channel { get; } - public Guid? PreselectId { get; set; } + public string? PreselectVersion { get; set; } public ReleaseViewModel? SelectedReleaseViewModel { diff --git a/src/Artemis.UI/Services/Updating/InAppUpdateNotificationProvider.cs b/src/Artemis.UI/Services/Updating/BasicUpdateNotificationProvider.cs similarity index 56% rename from src/Artemis.UI/Services/Updating/InAppUpdateNotificationProvider.cs rename to src/Artemis.UI/Services/Updating/BasicUpdateNotificationProvider.cs index 46a06a2ea..6d0335ad7 100644 --- a/src/Artemis.UI/Services/Updating/InAppUpdateNotificationProvider.cs +++ b/src/Artemis.UI/Services/Updating/BasicUpdateNotificationProvider.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using System.Threading.Tasks; using Artemis.UI.Screens.Settings; using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services.Builders; @@ -9,35 +8,67 @@ using ReactiveUI; namespace Artemis.UI.Services.Updating; -public class InAppUpdateNotificationProvider : IUpdateNotificationProvider +public class BasicUpdateNotificationProvider : IUpdateNotificationProvider { private readonly Func _getSettingsViewModel; private readonly IMainWindowService _mainWindowService; private readonly INotificationService _notificationService; - private Action? _notification; + private Action? _available; + private Action? _installed; - public InAppUpdateNotificationProvider(INotificationService notificationService, IMainWindowService mainWindowService, Func getSettingsViewModel) + public BasicUpdateNotificationProvider(INotificationService notificationService, IMainWindowService mainWindowService, Func getSettingsViewModel) { _notificationService = notificationService; _mainWindowService = mainWindowService; _getSettingsViewModel = getSettingsViewModel; } - private void ShowInAppNotification(Guid releaseId, string releaseVersion) + /// + public void ShowNotification(Guid releaseId, string releaseVersion) { - _notification?.Invoke(); - _notification = _notificationService.CreateNotification() + if (_mainWindowService.IsMainWindowOpen) + ShowAvailable(releaseVersion); + else + _mainWindowService.MainWindowOpened += (_, _) => ShowAvailable(releaseVersion); + } + + /// + public void ShowInstalledNotification(string installedVersion) + { + if (_mainWindowService.IsMainWindowOpen) + ShowInstalled(installedVersion); + else + _mainWindowService.MainWindowOpened += (_, _) => ShowInstalled(installedVersion); + } + + private void ShowAvailable(string releaseVersion) + { + _available?.Invoke(); + _available = _notificationService.CreateNotification() .WithTitle("Update available") - .WithMessage($"Artemis version {releaseVersion} has been released") + .WithMessage($"Artemis {releaseVersion} has been released") .WithSeverity(NotificationSeverity.Success) .WithTimeout(TimeSpan.FromSeconds(15)) - .HavingButton(b => b.WithText("View release").WithAction(() => ViewRelease(releaseId))) + .HavingButton(b => b.WithText("View release").WithAction(() => ViewRelease(releaseVersion))) .Show(); } - private void ViewRelease(Guid releaseId) + private void ShowInstalled(string installedVersion) { - _notification?.Invoke(); + _installed?.Invoke(); + _installed = _notificationService.CreateNotification() + .WithTitle("Update installed") + .WithMessage($"Artemis {installedVersion} has been installed.") + .WithSeverity(NotificationSeverity.Success) + .WithTimeout(TimeSpan.FromSeconds(15)) + .HavingButton(b => b.WithText("View release").WithAction(() => ViewRelease(installedVersion))) + .Show(); + } + + private void ViewRelease(string version) + { + _installed?.Invoke(); + _available?.Invoke(); if (_mainWindowService.HostScreen == null) return; @@ -51,16 +82,7 @@ public class InAppUpdateNotificationProvider : IUpdateNotificationProvider // Navigate to the settings VM _mainWindowService.HostScreen.Router.Navigate.Execute(settingsViewModel); // Navigate to the release tab - releaseTabViewModel.PreselectId = releaseId; + releaseTabViewModel.PreselectVersion = version; settingsViewModel.SelectedTab = releaseTabViewModel; } - - /// - public void ShowNotification(Guid releaseId, string releaseVersion) - { - if (_mainWindowService.IsMainWindowOpen) - ShowInAppNotification(releaseId, releaseVersion); - else - _mainWindowService.MainWindowOpened += (_, _) => ShowInAppNotification(releaseId, releaseVersion); - } } \ No newline at end of file diff --git a/src/Artemis.UI/Services/Updating/IUpdateNotificationProvider.cs b/src/Artemis.UI/Services/Updating/IUpdateNotificationProvider.cs index 5f77faf60..0dc3bf356 100644 --- a/src/Artemis.UI/Services/Updating/IUpdateNotificationProvider.cs +++ b/src/Artemis.UI/Services/Updating/IUpdateNotificationProvider.cs @@ -1,9 +1,9 @@ using System; -using System.Threading.Tasks; namespace Artemis.UI.Services.Updating; public interface IUpdateNotificationProvider { void ShowNotification(Guid releaseId, string releaseVersion); + void ShowInstalledNotification(string installedVersion); } \ No newline at end of file diff --git a/src/Artemis.UI/Services/Updating/UpdateService.cs b/src/Artemis.UI/Services/Updating/UpdateService.cs index 246277d94..e264d9431 100644 --- a/src/Artemis.UI/Services/Updating/UpdateService.cs +++ b/src/Artemis.UI/Services/Updating/UpdateService.cs @@ -66,7 +66,7 @@ public class UpdateService : IUpdateService private void ProcessReleaseStatus() { string currentVersion = Constants.CurrentVersion; - _releaseRepository.SaveVersionInstallDate(currentVersion); + bool updated = _releaseRepository.SaveVersionInstallDate(currentVersion); PreviousVersion = _releaseRepository.GetPreviousInstalledVersion()?.Version; if (!Directory.Exists(Constants.UpdatingFolder)) @@ -88,6 +88,9 @@ public class UpdateService : IUpdateService _logger.Warning(e, "Failed to clean up old update file at {FilePath}", file); } } + + // if (updated) + _updateNotificationProvider.Value.ShowInstalledNotification(currentVersion); } private void ShowUpdateNotification(IGetNextRelease_NextPublishedRelease release) @@ -194,7 +197,15 @@ public class UpdateService : IUpdateService if (Directory.Exists(Path.Combine(Constants.UpdatingFolder, "installing"))) { _logger.Warning("Cleaning up leftover installing folder, did an update go wrong?"); - Directory.Delete(Path.Combine(Constants.UpdatingFolder, "installing")); + try + { + Directory.Delete(Path.Combine(Constants.UpdatingFolder, "installing"), true); + } + catch (Exception e) + { + _logger.Error(e, "Failed to delete leftover installing folder"); + } + } // If an update is pending, don't bother with anything else