diff --git a/AssetEditor.sln b/AssetEditor.sln index b59bb2955..d3895a800 100644 --- a/AssetEditor.sln +++ b/AssetEditor.sln @@ -98,6 +98,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Editors.AnimationMeta", "Ed EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.AnimationMeta", "Editors\AnimationMeta\Test.AnimationMeta\Test.AnimationMeta.csproj", "{E759BE6D-E0A4-46B8-A02A-E8573F579E2F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetEditorUpdater", "AssetEditorUpdater\AssetEditorUpdater.csproj", "{24483A6F-DBD6-4415-B4D8-1A2FB86A2A12}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -228,6 +230,10 @@ Global {E759BE6D-E0A4-46B8-A02A-E8573F579E2F}.Debug|Any CPU.Build.0 = Debug|Any CPU {E759BE6D-E0A4-46B8-A02A-E8573F579E2F}.Release|Any CPU.ActiveCfg = Release|Any CPU {E759BE6D-E0A4-46B8-A02A-E8573F579E2F}.Release|Any CPU.Build.0 = Release|Any CPU + {24483A6F-DBD6-4415-B4D8-1A2FB86A2A12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24483A6F-DBD6-4415-B4D8-1A2FB86A2A12}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24483A6F-DBD6-4415-B4D8-1A2FB86A2A12}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24483A6F-DBD6-4415-B4D8-1A2FB86A2A12}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AssetEditor/App.xaml.cs b/AssetEditor/App.xaml.cs index fb81397e0..1c05f5b9d 100644 --- a/AssetEditor/App.xaml.cs +++ b/AssetEditor/App.xaml.cs @@ -1,15 +1,17 @@ using System; using System.Diagnostics; +using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; using AssetEditor.Services; +using AssetEditor.UiCommands; using AssetEditor.ViewModels; using AssetEditor.Views; -using AssetEditor.Views.Settings; using Microsoft.Extensions.DependencyInjection; using Shared.Core.DependencyInjection; using Shared.Core.DevConfig; using Shared.Core.ErrorHandling; +using Shared.Core.Events; using Shared.Core.PackFiles; using Shared.Core.PackFiles.Utility; using Shared.Core.Services; @@ -29,7 +31,6 @@ protected override void OnStartup(StartupEventArgs e) PackFileLog.IsLoggingEnabled = false; ShutdownMode = ShutdownMode.OnExplicitShutdown; - VersionChecker.CheckVersion(); Current.DispatcherUnhandledException += new DispatcherUnhandledExceptionEventHandler(DispatcherUnhandledExceptionHandler); var forceValidateServiceScopes = Debugger.IsAttached; @@ -38,6 +39,8 @@ protected override void OnStartup(StartupEventArgs e) _ = _serviceProvider.GetRequiredService(); // Force instance of the RecentFilesTracker _ = _serviceProvider.GetRequiredService(); // Force instance of the IScopeRepository + var uiCommandFactory = _serviceProvider.GetRequiredService(); + var settingsService = _serviceProvider.GetRequiredService(); settingsService.AllowSettingsUpdate = true; settingsService.Load(); @@ -48,14 +51,7 @@ protected override void OnStartup(StartupEventArgs e) // Show the settings window if its the first time the tool is ran if (settingsService.CurrentSettings.IsFirstTimeStartingApplication) - { - var settingsWindow = _serviceProvider.GetRequiredService(); - settingsWindow.DataContext = _serviceProvider.GetRequiredService(); - settingsWindow.ShowDialog(); - - settingsService.CurrentSettings.IsFirstTimeStartingApplication = false; - settingsService.Save(); - } + HandleFirstTimeSettings(uiCommandFactory, settingsService); var devConfigManager = _serviceProvider.GetRequiredService(); devConfigManager.Initialize(e); @@ -63,25 +59,38 @@ protected override void OnStartup(StartupEventArgs e) // Load all packfiles if (settingsService.CurrentSettings.LoadCaPacksByDefault) - { - var gamePath = settingsService.GetGamePathForCurrentGame(); - if (gamePath != null) - { - var packfileService = _serviceProvider.GetRequiredService(); - var containerLoader = _serviceProvider.GetRequiredService(); - var loadRes = containerLoader.LoadAllCaFiles(settingsService.CurrentSettings.CurrentGame); - - if (loadRes == null) - MessageBox.Show($"Unable to load all CA packfiles in {gamePath}"); - else - packfileService.AddContainer(loadRes); - } - } + LoadCAPackFiles(settingsService); devConfigManager.CreateTestPackFiles(); devConfigManager.OpenFileOnLoad(); ShowMainWindow(); + + _ = CheckVersion(uiCommandFactory); + } + + private static void HandleFirstTimeSettings(IUiCommandFactory uiCommandFactory, ApplicationSettingsService settingsService) + { + uiCommandFactory.Create().Execute(); + + settingsService.CurrentSettings.IsFirstTimeStartingApplication = false; + settingsService.Save(); + } + + private void LoadCAPackFiles(ApplicationSettingsService settingsService) + { + var gamePath = settingsService.GetGamePathForCurrentGame(); + if (gamePath != null) + { + var packfileService = _serviceProvider.GetRequiredService(); + var containerLoader = _serviceProvider.GetRequiredService(); + var loadRes = containerLoader.LoadAllCaFiles(settingsService.CurrentSettings.CurrentGame); + + if (loadRes == null) + MessageBox.Show($"Unable to load all CA packfiles in {gamePath}"); + else + packfileService.AddContainer(loadRes); + } } void ShowMainWindow() @@ -94,7 +103,7 @@ void ShowMainWindow() mainWindow.Closed += OnMainWindowClosed; mainWindow.Show(); - // Ensure the window doesn't cover up the windows bar. + // Ensure the window doesn't cover up the windows bar mainWindow.MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight; mainWindow.MaxWidth = SystemParameters.MaximizedPrimaryScreenWidth; @@ -109,6 +118,13 @@ private void OnMainWindowClosed(object sender, EventArgs e) Shutdown(); } + private static async Task CheckVersion(IUiCommandFactory uiCommandFactory) + { + var newerReleases = await VersionChecker.GetNewerReleases(); + if (newerReleases != null) + uiCommandFactory.Create().Execute(newerReleases); + } + void DispatcherUnhandledExceptionHandler(object sender, DispatcherUnhandledExceptionEventArgs args) { Logging.Create().Here().Fatal(args.Exception.ToString()); diff --git a/AssetEditor/AssetEditor.csproj b/AssetEditor/AssetEditor.csproj index 551719983..7c9a7c601 100644 --- a/AssetEditor/AssetEditor.csproj +++ b/AssetEditor/AssetEditor.csproj @@ -46,6 +46,7 @@ https://github.com/donkeyProgramming/TheAssetEditor https://github.com/donkeyProgramming/TheAssetEditor AssetEditor + 0.68.0 6.0 @@ -53,6 +54,10 @@ + + + + @@ -77,4 +82,13 @@ False $(NoWarn),1573,1591,1712 + + + + $(MSBuildProjectDirectory)\..\AssetEditorUpdater\AssetEditorUpdater.csproj + $([System.IO.Path]::GetFullPath('$(PublishDir)')) + + + + \ No newline at end of file diff --git a/AssetEditor/DependencyInjectionContainer.cs b/AssetEditor/DependencyInjectionContainer.cs index 21fa86b00..9633f055f 100644 --- a/AssetEditor/DependencyInjectionContainer.cs +++ b/AssetEditor/DependencyInjectionContainer.cs @@ -3,6 +3,7 @@ using AssetEditor.ViewModels; using AssetEditor.Views; using AssetEditor.Views.Settings; +using AssetEditor.Views.Updater; using Microsoft.Extensions.DependencyInjection; using Shared.Core.DependencyInjection; using Shared.Core.DevConfig; @@ -24,6 +25,7 @@ public override void Register(IServiceCollection serviceCollection) serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); + serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); @@ -31,6 +33,8 @@ public override void Register(IServiceCollection serviceCollection) serviceCollection.AddTransient(); serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); diff --git a/AssetEditor/UiCommands/OpenUpdaterWindowCommand.cs b/AssetEditor/UiCommands/OpenUpdaterWindowCommand.cs new file mode 100644 index 000000000..c2047ae4e --- /dev/null +++ b/AssetEditor/UiCommands/OpenUpdaterWindowCommand.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using AssetEditor.ViewModels; +using AssetEditor.Views.Updater; +using Microsoft.Extensions.DependencyInjection; +using Octokit; +using Shared.Core.Events; + +namespace AssetEditor.UiCommands +{ + public class OpenUpdaterWindowCommand(IServiceProvider serviceProvider) : IUiCommand + { + private readonly IServiceProvider _serviceProvider = serviceProvider; + + public void Execute(List newerReleases) + { + var window = _serviceProvider.GetRequiredService(); + var viewModel = _serviceProvider.GetRequiredService(); + viewModel.SetReleaseInfo(newerReleases); + viewModel.SetCloseAction(window.Close); + window.DataContext = viewModel; + window.ShowDialog(); + } + } +} + diff --git a/AssetEditor/ViewModels/MainViewModel.cs b/AssetEditor/ViewModels/MainViewModel.cs index e225e1429..f9ee1739c 100644 --- a/AssetEditor/ViewModels/MainViewModel.cs +++ b/AssetEditor/ViewModels/MainViewModel.cs @@ -55,7 +55,7 @@ public MainViewModel( ToolsFactory = toolFactory; - ApplicationTitle = $"AssetEditor v{VersionChecker.CurrentVersion}"; + ApplicationTitle = $"AssetEditor v{VersionChecker.GetCurrentVersion()}"; CurrentGame = $"Current Game: {GameInformationDatabase.GetGameById(applicationSettingsService.CurrentSettings.CurrentGame).DisplayName}"; } diff --git a/AssetEditor/ViewModels/UpdaterViewModel.cs b/AssetEditor/ViewModels/UpdaterViewModel.cs new file mode 100644 index 000000000..6d5a0aad9 --- /dev/null +++ b/AssetEditor/ViewModels/UpdaterViewModel.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Text; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Octokit; +using Serilog; +using Shared.Core.ErrorHandling; +using Shared.Core.Misc; +using Shared.Core.Services; +using Application = System.Windows.Application; + +namespace AssetEditor.ViewModels +{ + public class ReleaseNoteItem(Release release) + { + public string ReleaseName { get; } = release.Name; + public string PublishedAt { get; } = $"Published {release.PublishedAt.Value:dd MMM yyyy}"; + public string ReleaseNotes { get; } = release.Body; + } + + partial class UpdaterViewModel : ObservableObject + { + private readonly ILogger _logger = Logging.Create(); + private Action _closeAction; + + private const string AssetEditorUpdaterExe = "AssetEditorUpdater.exe"; + + private List _newerReleases = []; + + [ObservableProperty] private ObservableCollection _releaseNotesItems = []; + + [ObservableProperty] private string _latestVersionInfo; + + public void SetReleaseInfo(List newerReleases) + { + _newerReleases = newerReleases; + + var currentVersion = VersionChecker.GetCurrentVersion(); + var latestRelease = _newerReleases[0]; + var latestVersion = VersionChecker.ParseReleaseVersion(latestRelease.TagName); + + var stringBuilder = new StringBuilder(); + stringBuilder.AppendLine($"A new version of AssetEditor is available! The AssetEditor donkeys have been busy..."); + stringBuilder.AppendLine(); + stringBuilder.AppendLine($"Your current version is {currentVersion}. The latest verison is {latestVersion}."); + stringBuilder.AppendLine(); + stringBuilder.AppendLine("Update to get the changes detailed in the release notes below."); + LatestVersionInfo = stringBuilder.ToString(); + + ReleaseNotesItems.Clear(); + foreach (var release in _newerReleases) + ReleaseNotesItems.Add(new ReleaseNoteItem(release)); + } + + [RelayCommand] public void Update() + { + if (Debugger.IsAttached) + return; + + var currentVersion = VersionChecker.GetCurrentVersion(); + var latestRelease = _newerReleases[0]; + var latestVersion = VersionChecker.ParseReleaseVersion(latestRelease.TagName); + _logger.Information($"Updating AssetEditor from version {currentVersion} to version {latestVersion}"); + + DeleteUpdateDirectory(); + + LaunchUpdater(); + + Application.Current.Shutdown(); + } + + public static void DeleteUpdateDirectory() + { + var updateDirectory = DirectoryHelper.UpdateDirectory; + if (Directory.Exists(updateDirectory)) + Directory.Delete(updateDirectory, true); + } + + public static void LaunchUpdater() + { + var currentDirectory = AppContext.BaseDirectory; + var updaterPath = Path.Combine(currentDirectory, AssetEditorUpdaterExe); + + var processStartInfo = new ProcessStartInfo + { + FileName = updaterPath, + WorkingDirectory = currentDirectory, + UseShellExecute = true, + }; + + if (UpdaterRequiresAdministratorPrivileges()) + processStartInfo.Verb = "runas"; + + Process.Start(processStartInfo); + } + + public static bool UpdaterRequiresAdministratorPrivileges() + { + try + { + var currentDirectory = AppContext.BaseDirectory; + var testFilePath = Path.Combine(currentDirectory, $"updater_admin_privileges_test{Guid.NewGuid():N}.tmp"); + + using (File.Create(testFilePath, 1, FileOptions.DeleteOnClose)) + { + } + + return false; + } + catch (UnauthorizedAccessException) + { + return true; + } + } + + [RelayCommand] public void CloseWindowAction() => _closeAction?.Invoke(); + + + public void SetCloseAction(Action closeAction) => _closeAction = closeAction; + } +} diff --git a/AssetEditor/Views/Updater/UpdaterWindow.xaml b/AssetEditor/Views/Updater/UpdaterWindow.xaml new file mode 100644 index 000000000..e1781fb56 --- /dev/null +++ b/AssetEditor/Views/Updater/UpdaterWindow.xaml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +