From 2f065fdd629787c0b59afd033db95763526c50dc Mon Sep 17 00:00:00 2001 From: "@Noxis" Date: Tue, 29 Sep 2020 12:51:11 +0300 Subject: [PATCH 01/28] Test startup args parsing --- WowUp.WPF/App.xaml.cs | 67 ++++++++++++++++++++++-- WowUp.WPF/Properties/launchSettings.json | 8 +++ WowUp.WPF/Utilities/StartupOptions.cs | 22 ++++++++ WowUp.WPF/WowUp.WPF.csproj | 1 + 4 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 WowUp.WPF/Properties/launchSettings.json create mode 100644 WowUp.WPF/Utilities/StartupOptions.cs diff --git a/WowUp.WPF/App.xaml.cs b/WowUp.WPF/App.xaml.cs index 88e013fd..673539b6 100644 --- a/WowUp.WPF/App.xaml.cs +++ b/WowUp.WPF/App.xaml.cs @@ -1,7 +1,11 @@ -using Microsoft.Extensions.DependencyInjection; +using CommandLine; +using Microsoft.Extensions.DependencyInjection; using Serilog; using System; +using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -10,6 +14,7 @@ using WowUp.Common.Services.Contracts; using WowUp.WPF.AddonProviders; using WowUp.WPF.AddonProviders.Contracts; using WowUp.WPF.Enums; +using WowUp.WPF.Extensions; using WowUp.WPF.Repositories; using WowUp.WPF.Repositories.Contracts; using WowUp.WPF.Services; @@ -32,6 +37,9 @@ namespace WowUp.WPF private readonly ServiceProvider _serviceProvider; private readonly IAnalyticsService _analyticsService; + private readonly ISessionService _sessionService; + private readonly IAddonService _addonService; + private bool _startSilent; [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] @@ -61,14 +69,67 @@ namespace WowUp.WPF _serviceProvider = serviceCollection.BuildServiceProvider(); _analyticsService = _serviceProvider.GetRequiredService(); + _sessionService = _serviceProvider.GetRequiredService(); + _addonService = _serviceProvider.GetRequiredService(); } + async void TestParsed(StartupOptions options) + { + if (options.ClientType != Common.Enums.WowClientType.None) + _sessionService.SelectedClientType = options.ClientType; + if (options.Update) + { + var addons = await _addonService.GetAddons(_sessionService.SelectedClientType); + var addonsToUpdate = addons.Where(x => x.CanUpdate() || x.CanInstall() && !x.IsIgnored || options.Force); + await addonsToUpdate.ForEachAsync(2, async x => + await _addonService.InstallAddon(x.Id + //Without these lines addons are updated as expected but the view stays unrefreshed + //If I uncomment them, installation is not working + + //,(s,e) => { + // if (s == Common.Enums.AddonInstallState.Complete) + // _addonService.UpdateAddon(x); + //} + ) + ); + } + if (options.Silent) + { + _startSilent = true; + } + if (options.InputURLs.Any()) + { + await options.InputURLs.ForEachAsync(2, async x => + { + var potentialAddon = await _addonService.GetAddonByUri(new Uri(x), _sessionService.SelectedClientType); + if (potentialAddon != null) + await _addonService.InstallAddon(potentialAddon, _sessionService.SelectedClientType + //,(s,e) => { + // if (s == Common.Enums.AddonInstallState.Complete) + // _addonService.UpdateAddon(); + //} + ); + }); + } + + } + void TestNotParsed(IEnumerable errors) + { + Debug.WriteLine(string.Join("\r\n", errors.Select(x => x.ToString()).ToArray())); + Current.Shutdown(); + } protected override void OnStartup(StartupEventArgs e) { + var args = Environment.GetCommandLineArgs().Skip(1); + Parser.Default.ParseArguments(args).WithParsed(TestParsed).WithNotParsed(TestNotParsed); + HandleSingleInstance(); - var mainWindow = _serviceProvider.GetRequiredService(); - mainWindow.Show(); + if(!_startSilent) + { + var mainWindow = _serviceProvider.GetRequiredService(); + mainWindow.Show(); + } } protected override void OnExit(ExitEventArgs e) diff --git a/WowUp.WPF/Properties/launchSettings.json b/WowUp.WPF/Properties/launchSettings.json new file mode 100644 index 00000000..df584f42 --- /dev/null +++ b/WowUp.WPF/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "WowUp.WPF": { + "commandName": "Project", + "commandLineArgs": "-u" + } + } +} \ No newline at end of file diff --git a/WowUp.WPF/Utilities/StartupOptions.cs b/WowUp.WPF/Utilities/StartupOptions.cs new file mode 100644 index 00000000..11a2b995 --- /dev/null +++ b/WowUp.WPF/Utilities/StartupOptions.cs @@ -0,0 +1,22 @@ +using CommandLine; +using System; +using System.Collections.Generic; +using System.Text; +using WowUp.Common.Enums; + +namespace WowUp.WPF.Utilities +{ + public class StartupOptions + { + [Option(shortName: 'i', longName: "install", HelpText = "Specify addon urls to install them")] + public IEnumerable InputURLs { get; set; } + [Option(shortName: 's', longName: "silent", HelpText = "Start application minimized")] + public bool Silent { get; set; } + [Option(shortName: 'u', longName: "update", HelpText = "Update non-ignored addons on startup")] + public bool Update { get; set; } + [Option(shortName: 'f', longName: "force", HelpText = "Update all addons on startup")] + public bool Force { get; set; } + [Option(shortName: 'c', longName: "client", HelpText = "Specify client version to use", Default = WowClientType.None)] + public WowClientType ClientType { get; set; } + } +} diff --git a/WowUp.WPF/WowUp.WPF.csproj b/WowUp.WPF/WowUp.WPF.csproj index 375bdce6..d72f582b 100644 --- a/WowUp.WPF/WowUp.WPF.csproj +++ b/WowUp.WPF/WowUp.WPF.csproj @@ -54,6 +54,7 @@ + From 99c503b78cbbd4471fefa508ba4224c44f182862 Mon Sep 17 00:00:00 2001 From: jliddev Date: Wed, 30 Sep 2020 20:19:15 -0500 Subject: [PATCH 02/28] start multiselect --- WowUp.WPF/Views/AddonsView.xaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/WowUp.WPF/Views/AddonsView.xaml b/WowUp.WPF/Views/AddonsView.xaml index 7c61efe1..58d29c7a 100644 --- a/WowUp.WPF/Views/AddonsView.xaml +++ b/WowUp.WPF/Views/AddonsView.xaml @@ -249,6 +249,7 @@ BorderThickness="0" VirtualizingPanel.ScrollUnit="Pixel" BorderBrush="Transparent" + SelectionMode="Extended" Sorting="AddonGrid_Sorting"> + + + + From 9af531b44ff1223c5b2296f490dad61ea68fdc3d Mon Sep 17 00:00:00 2001 From: "@Noxis" Date: Thu, 1 Oct 2020 15:13:30 +0300 Subject: [PATCH 03/28] Startup args test 2 --- WowUp.WPF/App.xaml.cs | 62 ++------------------- WowUp.WPF/Models/WowUp/StartupOptions.cs | 19 +++++++ WowUp.WPF/Properties/launchSettings.json | 2 +- WowUp.WPF/Services/SessionService.cs | 29 +++++++++- WowUp.WPF/Utilities/StartupHelper.cs | 24 ++++++++ WowUp.WPF/Utilities/StartupOptions.cs | 22 -------- WowUp.WPF/ViewModels/MainWindowViewModel.cs | 19 ++++++- 7 files changed, 94 insertions(+), 83 deletions(-) create mode 100644 WowUp.WPF/Models/WowUp/StartupOptions.cs create mode 100644 WowUp.WPF/Utilities/StartupHelper.cs delete mode 100644 WowUp.WPF/Utilities/StartupOptions.cs diff --git a/WowUp.WPF/App.xaml.cs b/WowUp.WPF/App.xaml.cs index 673539b6..a1c58b2d 100644 --- a/WowUp.WPF/App.xaml.cs +++ b/WowUp.WPF/App.xaml.cs @@ -15,6 +15,7 @@ using WowUp.WPF.AddonProviders; using WowUp.WPF.AddonProviders.Contracts; using WowUp.WPF.Enums; using WowUp.WPF.Extensions; +using WowUp.WPF.Models.WowUp; using WowUp.WPF.Repositories; using WowUp.WPF.Repositories.Contracts; using WowUp.WPF.Services; @@ -37,9 +38,6 @@ namespace WowUp.WPF private readonly ServiceProvider _serviceProvider; private readonly IAnalyticsService _analyticsService; - private readonly ISessionService _sessionService; - private readonly IAddonService _addonService; - private bool _startSilent; [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] @@ -69,67 +67,15 @@ namespace WowUp.WPF _serviceProvider = serviceCollection.BuildServiceProvider(); _analyticsService = _serviceProvider.GetRequiredService(); - _sessionService = _serviceProvider.GetRequiredService(); - _addonService = _serviceProvider.GetRequiredService(); - } - - async void TestParsed(StartupOptions options) - { - if (options.ClientType != Common.Enums.WowClientType.None) - _sessionService.SelectedClientType = options.ClientType; - if (options.Update) - { - var addons = await _addonService.GetAddons(_sessionService.SelectedClientType); - var addonsToUpdate = addons.Where(x => x.CanUpdate() || x.CanInstall() && !x.IsIgnored || options.Force); - await addonsToUpdate.ForEachAsync(2, async x => - await _addonService.InstallAddon(x.Id - //Without these lines addons are updated as expected but the view stays unrefreshed - //If I uncomment them, installation is not working - - //,(s,e) => { - // if (s == Common.Enums.AddonInstallState.Complete) - // _addonService.UpdateAddon(x); - //} - ) - ); - } - if (options.Silent) - { - _startSilent = true; - } - if (options.InputURLs.Any()) - { - await options.InputURLs.ForEachAsync(2, async x => - { - var potentialAddon = await _addonService.GetAddonByUri(new Uri(x), _sessionService.SelectedClientType); - if (potentialAddon != null) - await _addonService.InstallAddon(potentialAddon, _sessionService.SelectedClientType - //,(s,e) => { - // if (s == Common.Enums.AddonInstallState.Complete) - // _addonService.UpdateAddon(); - //} - ); - }); - } - - } - void TestNotParsed(IEnumerable errors) - { - Debug.WriteLine(string.Join("\r\n", errors.Select(x => x.ToString()).ToArray())); - Current.Shutdown(); } protected override void OnStartup(StartupEventArgs e) { - var args = Environment.GetCommandLineArgs().Skip(1); - Parser.Default.ParseArguments(args).WithParsed(TestParsed).WithNotParsed(TestNotParsed); + StartupHelper.SetOptions(); HandleSingleInstance(); - if(!_startSilent) - { - var mainWindow = _serviceProvider.GetRequiredService(); - mainWindow.Show(); - } + var mainWindow = _serviceProvider.GetRequiredService(); + mainWindow.Show(); } protected override void OnExit(ExitEventArgs e) diff --git a/WowUp.WPF/Models/WowUp/StartupOptions.cs b/WowUp.WPF/Models/WowUp/StartupOptions.cs new file mode 100644 index 00000000..ed91187d --- /dev/null +++ b/WowUp.WPF/Models/WowUp/StartupOptions.cs @@ -0,0 +1,19 @@ +using CommandLine; +using System; +using System.Collections.Generic; +using WowUp.Common.Enums; + +namespace WowUp.WPF.Models.WowUp +{ + public class StartupOptions + { + [Option(shortName: 'i', longName: "install", HelpText = "Specify addon URLs to install them")] + public IEnumerable InputURLs { get; set; } + [Option(shortName: 'm', longName: "minimized", HelpText = "Start the application minimized")] + public bool Minimized { get; set; } + [Option(shortName: 'q', longName: "quit", HelpText = "Exit the application after auto-updates")] + public bool Quit { get; set; } + [Option(shortName: 'c', longName: "client", HelpText = "Specify client version to use", Default = WowClientType.None)] + public WowClientType ClientType { get; set; } + } +} diff --git a/WowUp.WPF/Properties/launchSettings.json b/WowUp.WPF/Properties/launchSettings.json index df584f42..8ebf388f 100644 --- a/WowUp.WPF/Properties/launchSettings.json +++ b/WowUp.WPF/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "WowUp.WPF": { "commandName": "Project", - "commandLineArgs": "-u" + "commandLineArgs": "-q" } } } \ No newline at end of file diff --git a/WowUp.WPF/Services/SessionService.cs b/WowUp.WPF/Services/SessionService.cs index 07085e25..a38a9acb 100644 --- a/WowUp.WPF/Services/SessionService.cs +++ b/WowUp.WPF/Services/SessionService.cs @@ -4,10 +4,14 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Windows; +using System.Windows.Threading; using WowUp.Common.Enums; using WowUp.Common.Models; using WowUp.Common.Models.Events; +using WowUp.WPF.Extensions; using WowUp.WPF.Services.Contracts; +using WowUp.WPF.Utilities; namespace WowUp.WPF.Services { @@ -114,7 +118,10 @@ namespace WowUp.WPF.Services public void AppLoaded() { - if(_updateCheckTimer == null) + if (StartupHelper.StartupOptions.ClientType != WowClientType.None) + SelectedClientType = StartupHelper.StartupOptions.ClientType; + + if (_updateCheckTimer == null) { _updateCheckTimer = new Timer(_ => UpdateCheckTimerElapsed(), null, TimeSpan.FromSeconds(0), TimeSpan.FromMinutes(60)); } @@ -123,6 +130,21 @@ namespace WowUp.WPF.Services { _autoUpdateCheckTimer = new Timer(_ => ProcessAutoUpdates(), null, TimeSpan.FromSeconds(0), TimeSpan.FromMinutes(60)); } + + //if (StartupHelper.StartupOptions.InputURLs.Any()) + //{ + // await StartupHelper.StartupOptions.InputURLs.ForEachAsync(2, async x => + // { + // var potentialAddon = await _addonService.GetAddonByUri(new Uri(x), SelectedClientType); + // if (potentialAddon != null) + // await _addonService.InstallAddon(potentialAddon, SelectedClientType + // //,(s,e) => { + // // if (s == Common.Enums.AddonInstallState.Complete) + // // _addonService.UpdateAddon(); + // //} + // ); + // }); + //} } public void SetContextText(object requestor, string text) @@ -144,6 +166,11 @@ namespace WowUp.WPF.Services { TaskbarIcon.ShowBalloonTip("WowUp", $"Automatically updated {updateCount} addons.", TaskbarIcon.Icon, true); } + + if (StartupHelper.StartupOptions.Quit) + { + await Application.Current.Dispatcher.BeginInvoke(() => { Application.Current.Shutdown(); }, DispatcherPriority.ApplicationIdle); + } } private void SetContextText(string text) diff --git a/WowUp.WPF/Utilities/StartupHelper.cs b/WowUp.WPF/Utilities/StartupHelper.cs new file mode 100644 index 00000000..f4dc88ee --- /dev/null +++ b/WowUp.WPF/Utilities/StartupHelper.cs @@ -0,0 +1,24 @@ +using CommandLine; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using WowUp.WPF.Models.WowUp; + +namespace WowUp.WPF.Utilities +{ + public static class StartupHelper + { + public static void SetOptions() + { + var args = Environment.GetCommandLineArgs().Skip(1); + Parser.Default.ParseArguments(args) + .WithParsed( + options => StartupOptions = options) + .WithNotParsed( + errors => Debug.WriteLine(string.Join("\r\n", errors.Select(x => x.ToString()).ToArray()))); + } + public static StartupOptions StartupOptions { get; private set; } + } +} diff --git a/WowUp.WPF/Utilities/StartupOptions.cs b/WowUp.WPF/Utilities/StartupOptions.cs deleted file mode 100644 index 11a2b995..00000000 --- a/WowUp.WPF/Utilities/StartupOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CommandLine; -using System; -using System.Collections.Generic; -using System.Text; -using WowUp.Common.Enums; - -namespace WowUp.WPF.Utilities -{ - public class StartupOptions - { - [Option(shortName: 'i', longName: "install", HelpText = "Specify addon urls to install them")] - public IEnumerable InputURLs { get; set; } - [Option(shortName: 's', longName: "silent", HelpText = "Start application minimized")] - public bool Silent { get; set; } - [Option(shortName: 'u', longName: "update", HelpText = "Update non-ignored addons on startup")] - public bool Update { get; set; } - [Option(shortName: 'f', longName: "force", HelpText = "Update all addons on startup")] - public bool Force { get; set; } - [Option(shortName: 'c', longName: "client", HelpText = "Specify client version to use", Default = WowClientType.None)] - public WowClientType ClientType { get; set; } - } -} diff --git a/WowUp.WPF/ViewModels/MainWindowViewModel.cs b/WowUp.WPF/ViewModels/MainWindowViewModel.cs index 8f728bc2..be85086a 100644 --- a/WowUp.WPF/ViewModels/MainWindowViewModel.cs +++ b/WowUp.WPF/ViewModels/MainWindowViewModel.cs @@ -1,13 +1,16 @@ -using Hardcodet.Wpf.TaskbarNotification; +using CommandLine; +using Hardcodet.Wpf.TaskbarNotification; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.ObjectModel; using System.Linq; using System.Windows; using System.Windows.Controls; +using System.Windows.Threading; using WowUp.Common.Services.Contracts; using WowUp.WPF.Entities; using WowUp.WPF.Extensions; +using WowUp.WPF.Models.WowUp; using WowUp.WPF.Repositories.Contracts; using WowUp.WPF.Services.Contracts; using WowUp.WPF.Utilities; @@ -196,6 +199,13 @@ namespace WowUp.WPF.ViewModels public void OnSourceInitialized(Window window) { + if (StartupHelper.StartupOptions.Minimized) + { + window.Hide(); + window.ShowInTaskbar = false; + window.IsVisibleChanged += Window_IsVisibleChanged; + } + var windowPref = _preferenceRepository.FindByKey(WindowPlacementKey); var windowStatePref = _preferenceRepository.FindByKey(WindowStateKey); if (windowPref == null) @@ -223,6 +233,13 @@ namespace WowUp.WPF.ViewModels } } + private void Window_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) + { + var window = (Window)sender; + window.IsVisibleChanged -= Window_IsVisibleChanged; + window.ShowInTaskbar = true; + } + public void OnClosing(Window window) { var placement = window.GetPlacement(); From d51627463c85df50e858e23b017d28172718f80b Mon Sep 17 00:00:00 2001 From: "@Noxis" Date: Thu, 1 Oct 2020 16:45:39 +0300 Subject: [PATCH 04/28] Startup args parsing pull candidate --- WowUp.WPF/Properties/launchSettings.json | 3 +- WowUp.WPF/Services/SessionService.cs | 51 +++++++++++++-------- WowUp.WPF/ViewModels/MainWindowViewModel.cs | 2 +- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/WowUp.WPF/Properties/launchSettings.json b/WowUp.WPF/Properties/launchSettings.json index 8ebf388f..35e73a83 100644 --- a/WowUp.WPF/Properties/launchSettings.json +++ b/WowUp.WPF/Properties/launchSettings.json @@ -1,8 +1,7 @@ { "profiles": { "WowUp.WPF": { - "commandName": "Project", - "commandLineArgs": "-q" + "commandName": "Project" } } } \ No newline at end of file diff --git a/WowUp.WPF/Services/SessionService.cs b/WowUp.WPF/Services/SessionService.cs index a38a9acb..f51a421d 100644 --- a/WowUp.WPF/Services/SessionService.cs +++ b/WowUp.WPF/Services/SessionService.cs @@ -116,9 +116,37 @@ namespace WowUp.WPF.Services SessionChanged?.Invoke(this, new SessionEventArgs(_sessionState)); } - public void AppLoaded() + public async void AppLoaded() { - if (StartupHelper.StartupOptions.ClientType != WowClientType.None) + if (StartupHelper.StartupOptions?.InputURLs.Any() == true) + { + await StartupHelper.StartupOptions.InputURLs.ForEachAsync(2, async x => + { + PotentialAddon potentialAddon = null; + try + { + potentialAddon = await _addonService.GetAddonByUri(new Uri(x), SelectedClientType); + } + catch + { + MessageBox.Show($"Failed to import addon by URI: {x}"); + } + if (potentialAddon != null) + { + try + { + await _addonService.InstallAddon(potentialAddon, SelectedClientType); + } + catch + { + MessageBox.Show($"Failed to install addon {potentialAddon.Name}"); + } + } + + }); + } + + if (StartupHelper.StartupOptions != null && StartupHelper.StartupOptions.ClientType != WowClientType.None) SelectedClientType = StartupHelper.StartupOptions.ClientType; if (_updateCheckTimer == null) @@ -131,20 +159,7 @@ namespace WowUp.WPF.Services _autoUpdateCheckTimer = new Timer(_ => ProcessAutoUpdates(), null, TimeSpan.FromSeconds(0), TimeSpan.FromMinutes(60)); } - //if (StartupHelper.StartupOptions.InputURLs.Any()) - //{ - // await StartupHelper.StartupOptions.InputURLs.ForEachAsync(2, async x => - // { - // var potentialAddon = await _addonService.GetAddonByUri(new Uri(x), SelectedClientType); - // if (potentialAddon != null) - // await _addonService.InstallAddon(potentialAddon, SelectedClientType - // //,(s,e) => { - // // if (s == Common.Enums.AddonInstallState.Complete) - // // _addonService.UpdateAddon(); - // //} - // ); - // }); - //} + } public void SetContextText(object requestor, string text) @@ -167,9 +182,9 @@ namespace WowUp.WPF.Services TaskbarIcon.ShowBalloonTip("WowUp", $"Automatically updated {updateCount} addons.", TaskbarIcon.Icon, true); } - if (StartupHelper.StartupOptions.Quit) + if (StartupHelper.StartupOptions?.Quit == true) { - await Application.Current.Dispatcher.BeginInvoke(() => { Application.Current.Shutdown(); }, DispatcherPriority.ApplicationIdle); + await Application.Current.Dispatcher.BeginInvoke(() => { Application.Current.Shutdown(); }, DispatcherPriority.SystemIdle); } } diff --git a/WowUp.WPF/ViewModels/MainWindowViewModel.cs b/WowUp.WPF/ViewModels/MainWindowViewModel.cs index be85086a..f5530437 100644 --- a/WowUp.WPF/ViewModels/MainWindowViewModel.cs +++ b/WowUp.WPF/ViewModels/MainWindowViewModel.cs @@ -199,7 +199,7 @@ namespace WowUp.WPF.ViewModels public void OnSourceInitialized(Window window) { - if (StartupHelper.StartupOptions.Minimized) + if (StartupHelper.StartupOptions?.Minimized == true) { window.Hide(); window.ShowInTaskbar = false; From aa0fc2010e559ed9df3b966bff2e021473847277 Mon Sep 17 00:00:00 2001 From: jliddev Date: Thu, 1 Oct 2020 10:24:04 -0500 Subject: [PATCH 05/28] Add a selector for when auto scanning the wow db does not work. --- WowUp.WPF/MainWindow.xaml | 28 +++++++++ .../Services/Contracts/IWarcraftService.cs | 2 + WowUp.WPF/Services/WarcraftService.cs | 26 ++++---- WowUp.WPF/ViewModels/MainWindowViewModel.cs | 62 ++++++++++++++++++- WowUp.WPF/ViewModels/OptionsViewModel.cs | 12 ++-- 5 files changed, 110 insertions(+), 20 deletions(-) diff --git a/WowUp.WPF/MainWindow.xaml b/WowUp.WPF/MainWindow.xaml index 753e617c..3ba54250 100644 --- a/WowUp.WPF/MainWindow.xaml +++ b/WowUp.WPF/MainWindow.xaml @@ -2,6 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WowUp.WPF" xmlns:vw = "clr-namespace:WowUp.WPF.Views" @@ -123,6 +124,33 @@ Foreground="{StaticResource White2Brush}" FontSize="12" HorizontalAlignment="Center"/> + + + + + + + + + + + + + + > ListAddons(WowClientType clientType); + + string GetClientFolderName(WowClientType clientType); } } diff --git a/WowUp.WPF/Services/WarcraftService.cs b/WowUp.WPF/Services/WarcraftService.cs index 9b8fd2c8..9e9b3d23 100644 --- a/WowUp.WPF/Services/WarcraftService.cs +++ b/WowUp.WPF/Services/WarcraftService.cs @@ -253,6 +253,19 @@ namespace WowUp.WPF.Services return addons; } + public string GetClientFolderName(WowClientType clientType) + { + return clientType switch + { + WowClientType.Retail => RetailFolderName, + WowClientType.Classic => ClassicFolderName, + WowClientType.RetailPtr => RetailPtrFolderName, + WowClientType.ClassicPtr => ClassicPtrFolderName, + WowClientType.Beta => BetaFolderName, + _ => string.Empty, + }; + } + private async Task GetAddonFolder(DirectoryInfo directory) { var toc = await ParseToc(directory); @@ -295,19 +308,6 @@ namespace WowUp.WPF.Services return new TocParser(fileText).GetMetaData(); } - private string GetClientFolderName(WowClientType clientType) - { - return clientType switch - { - WowClientType.Retail => RetailFolderName, - WowClientType.Classic => ClassicFolderName, - WowClientType.RetailPtr => RetailPtrFolderName, - WowClientType.ClassicPtr => ClassicPtrFolderName, - WowClientType.Beta => BetaFolderName, - _ => string.Empty, - }; - } - private string GetClientLocationPreferenceKey(WowClientType clientType) { return clientType switch diff --git a/WowUp.WPF/ViewModels/MainWindowViewModel.cs b/WowUp.WPF/ViewModels/MainWindowViewModel.cs index 8f728bc2..3e8771dc 100644 --- a/WowUp.WPF/ViewModels/MainWindowViewModel.cs +++ b/WowUp.WPF/ViewModels/MainWindowViewModel.cs @@ -5,6 +5,7 @@ using System.Collections.ObjectModel; using System.Linq; using System.Windows; using System.Windows.Controls; +using WowUp.Common.Enums; using WowUp.Common.Services.Contracts; using WowUp.WPF.Entities; using WowUp.WPF.Extensions; @@ -27,10 +28,15 @@ namespace WowUp.WPF.ViewModels private readonly IAnalyticsService _analyticsService; private readonly ISessionService _sessionService; + public ObservableCollection WowClientTypes { get; set; } + public ObservableCollection TabItems { get; set; } + public Command SelectWowCommand { get; set; } public Command CloseWindowCommand { get; set; } public Command TaskbarIconCloseCommand { get; set; } public Command TaskbarIconClickCommand { get; set; } + public Command SetWowLocationCommand { get; set; } + public Command SelectedWowClientChangedCommand { get; set; } private TaskbarIcon _taskbarIcon; public TaskbarIcon TaskbarIcon @@ -116,7 +122,19 @@ namespace WowUp.WPF.ViewModels set { SetProperty(ref _contextText, value); } } - public ObservableCollection TabItems { get; set; } + private WowClientType _selectedClientType; + public WowClientType SelectedClientType + { + get => _selectedClientType; + set { SetProperty(ref _selectedClientType, value); } + } + + private string _wowClientHint; + public string WowClientHint + { + get => _wowClientHint; + set { SetProperty(ref _wowClientHint, value); } + } public ApplicationUpdateControlViewModel ApplicationUpdateControlViewModel { get; set; } @@ -140,17 +158,59 @@ namespace WowUp.WPF.ViewModels CloseWindowCommand = new Command(() => OnCloseWindow()); TaskbarIconCloseCommand = new Command(() => OnTaskbarIconClose()); TaskbarIconClickCommand = new Command(() => OnTaskbarIconClick()); + SetWowLocationCommand = new Command(() => OnSetWowLocation()); + SelectedWowClientChangedCommand = new Command(() => OnSelectedWowClientChanged()); ApplicationUpdateControlViewModel = serviceProvider.GetService(); TabItems = new ObservableCollection(); + WowClientTypes = new ObservableCollection( + Enum.GetValues(typeof(WowClientType)) + .Cast() + .Where(type => type != WowClientType.None)); + migrationService.MigrateDatabase(); InitializeView(); _sessionService.SessionChanged += SessionService_SessionChanged; _sessionService.ContextTextChanged += SessionService_ContextTextChanged; + + SetClientHint(); + } + + /// + /// Handle when the user wants to change the install folder for a particular client + /// + /// + private void OnSetWowLocation() + { + var selectedPath = DialogUtilities.SelectFolder(); + if (string.IsNullOrEmpty(selectedPath)) + { + return; + } + + if (!_warcraftService.SetWowFolderPath(SelectedClientType, selectedPath)) + { + MessageBox.Show($"Unable to set \"{selectedPath}\" as your {SelectedClientType} folder"); + return; + } + + _sessionService.SelectedClientType = SelectedClientType; + InitializeView(); + } + + private void OnSelectedWowClientChanged() + { + SetClientHint(); + } + + private void SetClientHint() + { + var clientFolderName = _warcraftService.GetClientFolderName(SelectedClientType); + WowClientHint = $"Select the folder that contains the {SelectedClientType} client folder _{clientFolderName}"; } private void OnTaskbarIconClick() diff --git a/WowUp.WPF/ViewModels/OptionsViewModel.cs b/WowUp.WPF/ViewModels/OptionsViewModel.cs index 93ed9ec2..d4eb2fad 100644 --- a/WowUp.WPF/ViewModels/OptionsViewModel.cs +++ b/WowUp.WPF/ViewModels/OptionsViewModel.cs @@ -192,17 +192,17 @@ namespace WowUp.WPF.ViewModels WowUpReleaseChannelChangedCommand = new Command(() => OnWowUpReleaseChannelChange(SelectedWowUpReleaseChannelType)); DumpDebugDataCommand = new Command(() => DumpDebugData()); - RetailAddonChannelChangeCommand = new Command(() => OnAddonChannelChange(WowClientType.Retail, SelectedRetailAddonChannelType));; + RetailAddonChannelChangeCommand = new Command(() => OnAddonChannelChange(WowClientType.Retail, SelectedRetailAddonChannelType)); RetailPtrAddonChannelChangeCommand = new Command(() => OnAddonChannelChange(WowClientType.RetailPtr, SelectedRetailPtrAddonChannelType)); ClassicAddonChannelChangeCommand = new Command(() => OnAddonChannelChange(WowClientType.Classic, SelectedClassicAddonChannelType)); ClassicPtrAddonChannelChangeCommand = new Command(() => OnAddonChannelChange(WowClientType.ClassicPtr, SelectedClassicPtrAddonChannelType)); BetaAddonChannelChangeCommand = new Command(() => OnAddonChannelChange(WowClientType.Beta, SelectedBetaAddonChannelType)); - RetailAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.Retail, RetailAutoUpdateAddons)); ; - RetailPtrAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.RetailPtr, RetailPtrAutoUpdateAddons)); ; - ClassicAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.Classic, ClassicAutoUpdateAddons)); ; - ClassicPtrAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.ClassicPtr, ClassicPtrAutoUpdateAddons)); ; - BetaAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.Beta, BetaAutoUpdateAddons)); ; + RetailAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.Retail, RetailAutoUpdateAddons)); + RetailPtrAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.RetailPtr, RetailPtrAutoUpdateAddons)); + ClassicAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.Classic, ClassicAutoUpdateAddons)); + ClassicPtrAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.ClassicPtr, ClassicPtrAutoUpdateAddons)); + BetaAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.Beta, BetaAutoUpdateAddons)); AddonChannelNames = new ObservableCollection { From 865d2e1bf45f8b9b34efe4b397911f35fb6b5b35 Mon Sep 17 00:00:00 2001 From: jliddev Date: Thu, 1 Oct 2020 10:25:43 -0500 Subject: [PATCH 06/28] Update WowUp.WPF.csproj --- WowUp.WPF/WowUp.WPF.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WowUp.WPF/WowUp.WPF.csproj b/WowUp.WPF/WowUp.WPF.csproj index a2efea08..71815a9b 100644 --- a/WowUp.WPF/WowUp.WPF.csproj +++ b/WowUp.WPF/WowUp.WPF.csproj @@ -10,7 +10,7 @@ WowUp Jliddev WowUp - 1.18.0-beta.3 + 1.18.0-beta.4 wowup_logo_512np_RRT_icon.ico jliddev https://wowup.io From adafdf128e71430dbba345e28480cad54c0dab2f Mon Sep 17 00:00:00 2001 From: jliddev Date: Thu, 1 Oct 2020 10:27:14 -0500 Subject: [PATCH 07/28] Update AddonsView.xaml --- WowUp.WPF/Views/AddonsView.xaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/WowUp.WPF/Views/AddonsView.xaml b/WowUp.WPF/Views/AddonsView.xaml index 58d29c7a..7c61efe1 100644 --- a/WowUp.WPF/Views/AddonsView.xaml +++ b/WowUp.WPF/Views/AddonsView.xaml @@ -249,7 +249,6 @@ BorderThickness="0" VirtualizingPanel.ScrollUnit="Pixel" BorderBrush="Transparent" - SelectionMode="Extended" Sorting="AddonGrid_Sorting"> - - - - From 887f35d065b9517dd67bfd6220163e49b34cacec Mon Sep 17 00:00:00 2001 From: jliddev Date: Thu, 1 Oct 2020 15:41:25 -0500 Subject: [PATCH 08/28] Bulk operations --- WowUp.WPF/Assets/changelog.json | 2 +- WowUp.WPF/ViewModels/AddonsViewViewModel.cs | 58 ++++++++++++++++++++- WowUp.WPF/Views/AddonsView.xaml | 20 +++++-- WowUp.WPF/Views/AddonsView.xaml.cs | 13 ++++- WowUp.WPF/Views/Styles.xaml | 2 + WowUp.WPF/WowUp.WPF.csproj | 2 +- 6 files changed, 90 insertions(+), 7 deletions(-) diff --git a/WowUp.WPF/Assets/changelog.json b/WowUp.WPF/Assets/changelog.json index 863f3f1c..4428b933 100644 --- a/WowUp.WPF/Assets/changelog.json +++ b/WowUp.WPF/Assets/changelog.json @@ -2,7 +2,7 @@ "ChangeLogs": [ { "Version": "1.18.0", - "Description": "Add an 'Open Folder' option to the addon context menu (By Xathz).\nFeatured addons are now sorted by download count." + "Description": "Add an 'Open Folder' option to the addon context menu (By Xathz).\nAdd new ability to manually select your initial WoW client folder if the intial auto detection fails.\nNew ability to perform bulk operations on 'My Addons' page.\nFeatured addons are now sorted by download count." }, { "Version": "1.17.1", diff --git a/WowUp.WPF/ViewModels/AddonsViewViewModel.cs b/WowUp.WPF/ViewModels/AddonsViewViewModel.cs index 59a8a469..eea913c6 100644 --- a/WowUp.WPF/ViewModels/AddonsViewViewModel.cs +++ b/WowUp.WPF/ViewModels/AddonsViewViewModel.cs @@ -29,6 +29,8 @@ namespace WowUp.WPF.ViewModels private readonly ISessionService _sessionService; private List _addons; + private bool _disableUpdateLoad; + private IEnumerable _selectedAddons; private string _busyText; public string BusyText @@ -170,6 +172,27 @@ namespace WowUp.WPF.ViewModels set { SetProperty(ref _selectedClientType, value); } } + private ContextMenu _activeContextMenu; + public ContextMenu ActiveContextMenu + { + get => _activeContextMenu; + set { SetProperty(ref _activeContextMenu, value); } + } + + private string _multiRowMenuTitle; + public string MultiRowMenuTitle + { + get => _multiRowMenuTitle; + set { SetProperty(ref _multiRowMenuTitle, value); } + } + + private bool _multiRowMenuAutoUpdateCheck; + public bool MultiRowMenuAutoUpdateCheck + { + get => _multiRowMenuAutoUpdateCheck; + set { SetProperty(ref _multiRowMenuAutoUpdateCheck, value); } + } + public SearchInputViewModel SearchInputViewModel { get; set; } public Command LoadItemsCommand { get; set; } @@ -181,6 +204,10 @@ namespace WowUp.WPF.ViewModels public Command SelectedWowClientCommand { get; set; } public Command GridSortingCommand { get; set; } public Command ViewInitializedCommand { get; set; } + public Command AutoUpdateCheckedCommand { get; set; } + + public ContextMenu MultiRowMenu { get; set; } + public ContextMenu RowMenu { get; set; } public ObservableCollection DisplayAddons { get; set; } public ObservableCollection ClientTypeNames { get; set; } @@ -244,6 +271,7 @@ namespace WowUp.WPF.ViewModels SelectedWowClientCommand = new Command(async () => await OnSelectedWowClientChanged(SelectedClientType)); GridSortingCommand = new Command((args) => OnGridSorting(args as DataGridSortingEventArgs)); ViewInitializedCommand = new Command(() => OnViewInitialized()); + AutoUpdateCheckedCommand = new Command(() => OnAutoUpdateCheckedCommand()); SearchInputViewModel = serviceProvider.GetService(); SearchInputViewModel.TextChanged += SearchInputViewModel_TextChanged; @@ -261,6 +289,34 @@ namespace WowUp.WPF.ViewModels Initialize(); } + private async void OnAutoUpdateCheckedCommand() + { + _disableUpdateLoad = true; + foreach (var addon in _selectedAddons) + { + addon.AutoUpdateEnabled = MultiRowMenuAutoUpdateCheck; + _addonService.UpdateAddon(addon); + + var listItem = DisplayAddons.FirstOrDefault(item => item.Addon.Id == addon.Id); + listItem.IsAutoUpdated = addon.AutoUpdateEnabled; + } + _disableUpdateLoad = false; + } + + public void OnDataGridSelectionChange(IEnumerable selectedItems) + { + _selectedAddons = selectedItems.Select(item => item.Addon); + MultiRowMenuTitle = selectedItems.Count() > 1 + ? $"{selectedItems.Count()} addons selected" + : string.Empty; + + ActiveContextMenu = selectedItems.Count() > 1 + ? MultiRowMenu + : RowMenu; + + MultiRowMenuAutoUpdateCheck = selectedItems.All(item => item.IsAutoUpdated); + } + private void SessionService_TabChanged(object sender, Type tabType) { SetAddonCountContextText(DisplayAddons.Count); @@ -579,7 +635,7 @@ namespace WowUp.WPF.ViewModels private void AddonUpdated(Addon addon) { - if (IsBusy) + if (IsBusy || _disableUpdateLoad) { return; } diff --git a/WowUp.WPF/Views/AddonsView.xaml b/WowUp.WPF/Views/AddonsView.xaml index 7c61efe1..1b96825b 100644 --- a/WowUp.WPF/Views/AddonsView.xaml +++ b/WowUp.WPF/Views/AddonsView.xaml @@ -9,6 +9,7 @@ xmlns:vw = "clr-namespace:WowUp.WPF.Views" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" + x:Name="AddonsViewControl" Initialized="UserControl_Initialized"> @@ -247,10 +248,21 @@ HorizontalGridLinesBrush="{StaticResource Dark2}" RowHeaderWidth="0" BorderThickness="0" + SelectionMode="Extended" VirtualizingPanel.ScrollUnit="Pixel" + SelectionChanged="AddonGrid_SelectionChanged" BorderBrush="Transparent" Sorting="AddonGrid_Sorting"> + + + + @@ -326,12 +338,15 @@ @@ -388,8 +403,7 @@ - (); + _viewModel.OnDataGridSelectionChange(selectedItems); + } } } diff --git a/WowUp.WPF/Views/Styles.xaml b/WowUp.WPF/Views/Styles.xaml index 817205de..571b5e5f 100644 --- a/WowUp.WPF/Views/Styles.xaml +++ b/WowUp.WPF/Views/Styles.xaml @@ -9,6 +9,7 @@ #6B69D6 #504FA1 #383773 + #282836 #FFFFFF #DDDDDD #CCCCCC @@ -45,6 +46,7 @@ #6B69D6 #504FA1 #383773 + #282836 #315891 #24416c #01BAEF diff --git a/WowUp.WPF/WowUp.WPF.csproj b/WowUp.WPF/WowUp.WPF.csproj index 71815a9b..75301237 100644 --- a/WowUp.WPF/WowUp.WPF.csproj +++ b/WowUp.WPF/WowUp.WPF.csproj @@ -10,7 +10,7 @@ WowUp Jliddev WowUp - 1.18.0-beta.4 + 1.18.0-beta.5 wowup_logo_512np_RRT_icon.ico jliddev https://wowup.io From 7b52ebe51dd58b406ce7fd44d6432fb848c00ab3 Mon Sep 17 00:00:00 2001 From: jliddev Date: Thu, 1 Oct 2020 23:43:43 -0500 Subject: [PATCH 09/28] Bugfix nullable date issue with CF response. --- WowUp.Common/Models/Curse/CurseFile.cs | 2 +- WowUp.WPF/Assets/changelog.json | 4 ++++ WowUp.WPF/WowUp.WPF.csproj | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/WowUp.Common/Models/Curse/CurseFile.cs b/WowUp.Common/Models/Curse/CurseFile.cs index 0b3d1c49..bf52f0d5 100644 --- a/WowUp.Common/Models/Curse/CurseFile.cs +++ b/WowUp.Common/Models/Curse/CurseFile.cs @@ -36,7 +36,7 @@ namespace WowUp.Common.Models.Curse public int? FileTypeId { get; set; } public object ExposeAsAlternative { get; set; } public long PackageFingerprintId { get; set; } - public DateTime GameVersionDateReleased { get; set; } + public DateTime? GameVersionDateReleased { get; set; } public long GameVersionMappingId { get; set; } public int GameVersionId { get; set; } public int GameId { get; set; } diff --git a/WowUp.WPF/Assets/changelog.json b/WowUp.WPF/Assets/changelog.json index 7e514000..1c4a1b65 100644 --- a/WowUp.WPF/Assets/changelog.json +++ b/WowUp.WPF/Assets/changelog.json @@ -1,5 +1,9 @@ { "ChangeLogs": [ + { + "Version": "1.17.2", + "Description": "Fix an issue with handling CurseForge API responses." + }, { "Version": "1.17.1", "Description": "Fix a visual bug where auto updated addons were not shown up to date on the My Addons page." diff --git a/WowUp.WPF/WowUp.WPF.csproj b/WowUp.WPF/WowUp.WPF.csproj index 984780ff..7c5ea8f3 100644 --- a/WowUp.WPF/WowUp.WPF.csproj +++ b/WowUp.WPF/WowUp.WPF.csproj @@ -10,7 +10,7 @@ WowUp Jliddev WowUp - 1.17.1 + 1.17.2 wowup_logo_512np_RRT_icon.ico jliddev https://wowup.io From dda8ff84cdccd31255134e5a4f83835c0f60abe9 Mon Sep 17 00:00:00 2001 From: jliddev Date: Thu, 1 Oct 2020 23:47:13 -0500 Subject: [PATCH 10/28] version bump --- WowUp.WPF/WowUp.WPF.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WowUp.WPF/WowUp.WPF.csproj b/WowUp.WPF/WowUp.WPF.csproj index 75301237..dc73bdd4 100644 --- a/WowUp.WPF/WowUp.WPF.csproj +++ b/WowUp.WPF/WowUp.WPF.csproj @@ -10,7 +10,7 @@ WowUp Jliddev WowUp - 1.18.0-beta.5 + 1.18.0-beta.6 wowup_logo_512np_RRT_icon.ico jliddev https://wowup.io From dc0467d31baeb02ded37114677d21723fbf08ed6 Mon Sep 17 00:00:00 2001 From: "@Noxis" Date: Fri, 2 Oct 2020 08:10:07 +0300 Subject: [PATCH 11/28] Some polishes --- WowUp.WPF/App.xaml.cs | 12 +++--------- WowUp.WPF/Models/WowUp/StartupOptions.cs | 1 - WowUp.WPF/Utilities/StartupHelper.cs | 6 ++---- WowUp.WPF/ViewModels/MainWindowViewModel.cs | 3 --- 4 files changed, 5 insertions(+), 17 deletions(-) diff --git a/WowUp.WPF/App.xaml.cs b/WowUp.WPF/App.xaml.cs index a1c58b2d..ae4a2d75 100644 --- a/WowUp.WPF/App.xaml.cs +++ b/WowUp.WPF/App.xaml.cs @@ -1,11 +1,7 @@ -using CommandLine; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Serilog; using System; -using System.Collections.Generic; -using System.Diagnostics; using System.IO; -using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -14,8 +10,6 @@ using WowUp.Common.Services.Contracts; using WowUp.WPF.AddonProviders; using WowUp.WPF.AddonProviders.Contracts; using WowUp.WPF.Enums; -using WowUp.WPF.Extensions; -using WowUp.WPF.Models.WowUp; using WowUp.WPF.Repositories; using WowUp.WPF.Repositories.Contracts; using WowUp.WPF.Services; @@ -70,10 +64,10 @@ namespace WowUp.WPF } protected override void OnStartup(StartupEventArgs e) { - StartupHelper.SetOptions(); - HandleSingleInstance(); + StartupHelper.SetOptions(); + var mainWindow = _serviceProvider.GetRequiredService(); mainWindow.Show(); } diff --git a/WowUp.WPF/Models/WowUp/StartupOptions.cs b/WowUp.WPF/Models/WowUp/StartupOptions.cs index ed91187d..b9853dfd 100644 --- a/WowUp.WPF/Models/WowUp/StartupOptions.cs +++ b/WowUp.WPF/Models/WowUp/StartupOptions.cs @@ -1,5 +1,4 @@ using CommandLine; -using System; using System.Collections.Generic; using WowUp.Common.Enums; diff --git a/WowUp.WPF/Utilities/StartupHelper.cs b/WowUp.WPF/Utilities/StartupHelper.cs index f4dc88ee..2bd64422 100644 --- a/WowUp.WPF/Utilities/StartupHelper.cs +++ b/WowUp.WPF/Utilities/StartupHelper.cs @@ -1,9 +1,7 @@ using CommandLine; +using Serilog; using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using System.Text; using WowUp.WPF.Models.WowUp; namespace WowUp.WPF.Utilities @@ -17,7 +15,7 @@ namespace WowUp.WPF.Utilities .WithParsed( options => StartupOptions = options) .WithNotParsed( - errors => Debug.WriteLine(string.Join("\r\n", errors.Select(x => x.ToString()).ToArray()))); + errors => Log.Error(string.Join("\r\n", errors.Select(x => x.ToString()).ToArray()))); } public static StartupOptions StartupOptions { get; private set; } } diff --git a/WowUp.WPF/ViewModels/MainWindowViewModel.cs b/WowUp.WPF/ViewModels/MainWindowViewModel.cs index cf163a2b..a391a2f4 100644 --- a/WowUp.WPF/ViewModels/MainWindowViewModel.cs +++ b/WowUp.WPF/ViewModels/MainWindowViewModel.cs @@ -1,4 +1,3 @@ -using CommandLine; using Hardcodet.Wpf.TaskbarNotification; using Microsoft.Extensions.DependencyInjection; using System; @@ -7,10 +6,8 @@ using System.Linq; using System.Windows; using System.Windows.Controls; using WowUp.Common.Enums; -using WowUp.Common.Services.Contracts; using WowUp.WPF.Entities; using WowUp.WPF.Extensions; -using WowUp.WPF.Models.WowUp; using WowUp.WPF.Repositories.Contracts; using WowUp.WPF.Services.Contracts; using WowUp.WPF.Utilities; From 93ec7c1ddf1e083908a150ea56b8a63a81241770 Mon Sep 17 00:00:00 2001 From: "@Noxis" Date: Fri, 2 Oct 2020 08:14:12 +0300 Subject: [PATCH 12/28] Delete launchSettings.json --- WowUp.WPF/Properties/launchSettings.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 WowUp.WPF/Properties/launchSettings.json diff --git a/WowUp.WPF/Properties/launchSettings.json b/WowUp.WPF/Properties/launchSettings.json deleted file mode 100644 index 35e73a83..00000000 --- a/WowUp.WPF/Properties/launchSettings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "profiles": { - "WowUp.WPF": { - "commandName": "Project" - } - } -} \ No newline at end of file From 66f84902132cba8170cf242220073af7f5b3615f Mon Sep 17 00:00:00 2001 From: "@Noxis" Date: Fri, 2 Oct 2020 08:16:23 +0300 Subject: [PATCH 13/28] Some more polishes --- WowUp.WPF/Services/SessionService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WowUp.WPF/Services/SessionService.cs b/WowUp.WPF/Services/SessionService.cs index f51a421d..8ff64dbe 100644 --- a/WowUp.WPF/Services/SessionService.cs +++ b/WowUp.WPF/Services/SessionService.cs @@ -118,6 +118,9 @@ namespace WowUp.WPF.Services public async void AppLoaded() { + if (StartupHelper.StartupOptions != null && StartupHelper.StartupOptions.ClientType != WowClientType.None) + SelectedClientType = StartupHelper.StartupOptions.ClientType; + if (StartupHelper.StartupOptions?.InputURLs.Any() == true) { await StartupHelper.StartupOptions.InputURLs.ForEachAsync(2, async x => @@ -146,9 +149,6 @@ namespace WowUp.WPF.Services }); } - if (StartupHelper.StartupOptions != null && StartupHelper.StartupOptions.ClientType != WowClientType.None) - SelectedClientType = StartupHelper.StartupOptions.ClientType; - if (_updateCheckTimer == null) { _updateCheckTimer = new Timer(_ => UpdateCheckTimerElapsed(), null, TimeSpan.FromSeconds(0), TimeSpan.FromMinutes(60)); From 32924e9b4fc68a187a6b5f304dee444d6e076b28 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 2 Oct 2020 10:24:46 -0500 Subject: [PATCH 14/28] Try to fix old images getting stuck in the cache. --- WowUp.WPF/Converters/UriToThumbnailConverter.cs | 13 ++----------- WowUp.WPF/ViewModels/AddonsViewViewModel.cs | 4 +++- WowUp.WPF/WowUp.WPF.csproj | 2 +- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/WowUp.WPF/Converters/UriToThumbnailConverter.cs b/WowUp.WPF/Converters/UriToThumbnailConverter.cs index 4f297982..8311d1f9 100644 --- a/WowUp.WPF/Converters/UriToThumbnailConverter.cs +++ b/WowUp.WPF/Converters/UriToThumbnailConverter.cs @@ -31,18 +31,9 @@ namespace WowUp.WPF.Converters BitmapImage thumbnail = new BitmapImage(); thumbnail.BeginInit(); thumbnail.DecodePixelWidth = 80; - - if (uri.IsFile) - { - imageStream = FileUtilities.GetMemoryStreamFromFile(uri.LocalPath); - thumbnail.StreamSource = imageStream; - } - else - { - thumbnail.UriSource = uri; - } - + thumbnail.CreateOptions = BitmapCreateOptions.IgnoreImageCache; thumbnail.CacheOption = BitmapCacheOption.OnLoad; + thumbnail.UriSource = uri; thumbnail.EndInit(); return thumbnail; } diff --git a/WowUp.WPF/ViewModels/AddonsViewViewModel.cs b/WowUp.WPF/ViewModels/AddonsViewViewModel.cs index eea913c6..80bdb568 100644 --- a/WowUp.WPF/ViewModels/AddonsViewViewModel.cs +++ b/WowUp.WPF/ViewModels/AddonsViewViewModel.cs @@ -614,8 +614,10 @@ namespace WowUp.WPF.ViewModels } // If this addon is already in the list, ignore it - if (DisplayAddons.Any(da => da.Addon.Id == addon.Id)) + var displayItem = DisplayAddons.FirstOrDefault(listItem => listItem.Addon.Id == addon.Id); + if (displayItem != null) { + displayItem.ThumbnailUrl = addon.ThumbnailUrl; return; } diff --git a/WowUp.WPF/WowUp.WPF.csproj b/WowUp.WPF/WowUp.WPF.csproj index dc73bdd4..af3a7baf 100644 --- a/WowUp.WPF/WowUp.WPF.csproj +++ b/WowUp.WPF/WowUp.WPF.csproj @@ -10,7 +10,7 @@ WowUp Jliddev WowUp - 1.18.0-beta.6 + 1.18.0-beta.7 wowup_logo_512np_RRT_icon.ico jliddev https://wowup.io From e308ba38511e6f4b4ae08fe4e510b03ba8b555bc Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 2 Oct 2020 11:47:49 -0500 Subject: [PATCH 15/28] Update addon thumbnail any time a change is detected, not just install --- WowUp.WPF/Services/AddonService.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/WowUp.WPF/Services/AddonService.cs b/WowUp.WPF/Services/AddonService.cs index 455bfba6..41cedc4d 100644 --- a/WowUp.WPF/Services/AddonService.cs +++ b/WowUp.WPF/Services/AddonService.cs @@ -269,7 +269,16 @@ namespace WowUp.WPF.Services if (result == null || latestFile == null || latestFile.Version == addon.LatestVersion) { - await SyncThumbnail(addon); + if(addon.ThumbnailUrl != result.ThumbnailUrl) + { + addon.ThumbnailUrl = result.ThumbnailUrl; + _addonRepository.UpdateItem(addon); + await SyncThumbnail(addon, true); + } + else + { + await SyncThumbnail(addon); + } continue; } @@ -293,9 +302,9 @@ namespace WowUp.WPF.Services } } - private async Task SyncThumbnail(Addon addon) + private async Task SyncThumbnail(Addon addon, bool force = false) { - if (!File.Exists(addon.GetThumbnailPath())) + if (force || !File.Exists(addon.GetThumbnailPath())) { await CacheThumbnail(addon); } @@ -480,6 +489,7 @@ namespace WowUp.WPF.Services try { + Log.Information($"Caching thumbnail {addon.Name}: {addon.ThumbnailUrl}"); using var imageStream = await addon.ThumbnailUrl.GetStreamAsync(); using Image image = Image.Load(imageStream); @@ -496,7 +506,7 @@ namespace WowUp.WPF.Services } catch(Exception ex) { - _analyticsService.Track(ex, "Failed to download thumbnail"); + _analyticsService.Track(ex, $"Failed to download thumbnail {addon.Name}"); } } From 0f2de47c97d24eedf04a09a3f6d27cf91d38ad86 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 2 Oct 2020 11:50:07 -0500 Subject: [PATCH 16/28] version bump --- WowUp.WPF/WowUp.WPF.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WowUp.WPF/WowUp.WPF.csproj b/WowUp.WPF/WowUp.WPF.csproj index af3a7baf..44194921 100644 --- a/WowUp.WPF/WowUp.WPF.csproj +++ b/WowUp.WPF/WowUp.WPF.csproj @@ -10,7 +10,7 @@ WowUp Jliddev WowUp - 1.18.0-beta.7 + 1.18.0-beta.8 wowup_logo_512np_RRT_icon.ico jliddev https://wowup.io From 58cc93d801958f07a0ba8f51464a7207fa5d7016 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 2 Oct 2020 13:55:12 -0500 Subject: [PATCH 17/28] Move parsing to App. Minor style tweaks. --- WowUp.WPF/App.xaml.cs | 21 +++++- WowUp.WPF/Services/SessionService.cs | 75 ++++++++++++--------- WowUp.WPF/Utilities/StartupHelper.cs | 22 ------ WowUp.WPF/ViewModels/MainWindowViewModel.cs | 12 +--- 4 files changed, 63 insertions(+), 67 deletions(-) delete mode 100644 WowUp.WPF/Utilities/StartupHelper.cs diff --git a/WowUp.WPF/App.xaml.cs b/WowUp.WPF/App.xaml.cs index ae4a2d75..c56146f3 100644 --- a/WowUp.WPF/App.xaml.cs +++ b/WowUp.WPF/App.xaml.cs @@ -1,7 +1,9 @@ -using Microsoft.Extensions.DependencyInjection; +using CommandLine; +using Microsoft.Extensions.DependencyInjection; using Serilog; using System; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -10,6 +12,7 @@ using WowUp.Common.Services.Contracts; using WowUp.WPF.AddonProviders; using WowUp.WPF.AddonProviders.Contracts; using WowUp.WPF.Enums; +using WowUp.WPF.Models.WowUp; using WowUp.WPF.Repositories; using WowUp.WPF.Repositories.Contracts; using WowUp.WPF.Services; @@ -40,6 +43,8 @@ namespace WowUp.WPF [DllImport("user32.dll")] public static extern int SetForegroundWindow(IntPtr hwnd); + public static StartupOptions StartupOptions { get; private set; } + public App() { AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(ExceptionHandler); @@ -56,6 +61,8 @@ namespace WowUp.WPF Log.Information($"Starting {AppUtilities.CurrentVersion}"); + ParseCommandLineArgs(); + var serviceCollection = new ServiceCollection(); ConfigureServices(serviceCollection); @@ -66,8 +73,6 @@ namespace WowUp.WPF { HandleSingleInstance(); - StartupHelper.SetOptions(); - var mainWindow = _serviceProvider.GetRequiredService(); mainWindow.Show(); } @@ -160,5 +165,15 @@ namespace WowUp.WPF //there is already another instance running! Current.Shutdown(); } + + private void ParseCommandLineArgs() + { + var args = Environment.GetCommandLineArgs().Skip(1); + Parser.Default.ParseArguments(args) + .WithParsed( + options => StartupOptions = options) + .WithNotParsed( + errors => Log.Error(string.Join("\r\n", errors.Select(x => x.ToString()).ToArray()))); + } } } diff --git a/WowUp.WPF/Services/SessionService.cs b/WowUp.WPF/Services/SessionService.cs index 8ff64dbe..e607475a 100644 --- a/WowUp.WPF/Services/SessionService.cs +++ b/WowUp.WPF/Services/SessionService.cs @@ -11,7 +11,6 @@ using WowUp.Common.Models; using WowUp.Common.Models.Events; using WowUp.WPF.Extensions; using WowUp.WPF.Services.Contracts; -using WowUp.WPF.Utilities; namespace WowUp.WPF.Services { @@ -58,7 +57,6 @@ namespace WowUp.WPF.Services StatusText = string.Empty, UpdaterReady = false }; - } private async void UpdateCheckTimerElapsed() @@ -118,37 +116,13 @@ namespace WowUp.WPF.Services public async void AppLoaded() { - if (StartupHelper.StartupOptions != null && StartupHelper.StartupOptions.ClientType != WowClientType.None) - SelectedClientType = StartupHelper.StartupOptions.ClientType; - - if (StartupHelper.StartupOptions?.InputURLs.Any() == true) + if (App.StartupOptions != null && App.StartupOptions.ClientType != WowClientType.None) { - await StartupHelper.StartupOptions.InputURLs.ForEachAsync(2, async x => - { - PotentialAddon potentialAddon = null; - try - { - potentialAddon = await _addonService.GetAddonByUri(new Uri(x), SelectedClientType); - } - catch - { - MessageBox.Show($"Failed to import addon by URI: {x}"); - } - if (potentialAddon != null) - { - try - { - await _addonService.InstallAddon(potentialAddon, SelectedClientType); - } - catch - { - MessageBox.Show($"Failed to install addon {potentialAddon.Name}"); - } - } - - }); + SelectedClientType = App.StartupOptions.ClientType; } + await ProcessInputUrls(); + if (_updateCheckTimer == null) { _updateCheckTimer = new Timer(_ => UpdateCheckTimerElapsed(), null, TimeSpan.FromSeconds(0), TimeSpan.FromMinutes(60)); @@ -158,8 +132,6 @@ namespace WowUp.WPF.Services { _autoUpdateCheckTimer = new Timer(_ => ProcessAutoUpdates(), null, TimeSpan.FromSeconds(0), TimeSpan.FromMinutes(60)); } - - } public void SetContextText(object requestor, string text) @@ -173,6 +145,41 @@ namespace WowUp.WPF.Services SetContextText(text); } + private async Task ProcessInputUrls() + { + if (!App.StartupOptions?.InputURLs.Any() ?? false) + { + return; + } + + await App.StartupOptions.InputURLs.ForEachAsync(2, async x => + { + PotentialAddon potentialAddon = null; + try + { + potentialAddon = await _addonService.GetAddonByUri(new Uri(x), SelectedClientType); + } + catch + { + MessageBox.Show($"Failed to import addon by URI: {x}"); + return; + } + + if (potentialAddon != null) + { + try + { + await _addonService.InstallAddon(potentialAddon, SelectedClientType); + } + catch + { + MessageBox.Show($"Failed to install addon {potentialAddon.Name}"); + } + } + + }); + } + private async void ProcessAutoUpdates() { var updateCount = await _addonService.ProcessAutoUpdates(); @@ -182,8 +189,10 @@ namespace WowUp.WPF.Services TaskbarIcon.ShowBalloonTip("WowUp", $"Automatically updated {updateCount} addons.", TaskbarIcon.Icon, true); } - if (StartupHelper.StartupOptions?.Quit == true) + if (App.StartupOptions?.Quit == true) { + // Artificial delay to allow notification to fire. + await Task.Delay(3000); await Application.Current.Dispatcher.BeginInvoke(() => { Application.Current.Shutdown(); }, DispatcherPriority.SystemIdle); } } diff --git a/WowUp.WPF/Utilities/StartupHelper.cs b/WowUp.WPF/Utilities/StartupHelper.cs deleted file mode 100644 index 2bd64422..00000000 --- a/WowUp.WPF/Utilities/StartupHelper.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CommandLine; -using Serilog; -using System; -using System.Linq; -using WowUp.WPF.Models.WowUp; - -namespace WowUp.WPF.Utilities -{ - public static class StartupHelper - { - public static void SetOptions() - { - var args = Environment.GetCommandLineArgs().Skip(1); - Parser.Default.ParseArguments(args) - .WithParsed( - options => StartupOptions = options) - .WithNotParsed( - errors => Log.Error(string.Join("\r\n", errors.Select(x => x.ToString()).ToArray()))); - } - public static StartupOptions StartupOptions { get; private set; } - } -} diff --git a/WowUp.WPF/ViewModels/MainWindowViewModel.cs b/WowUp.WPF/ViewModels/MainWindowViewModel.cs index a391a2f4..a0c4228e 100644 --- a/WowUp.WPF/ViewModels/MainWindowViewModel.cs +++ b/WowUp.WPF/ViewModels/MainWindowViewModel.cs @@ -219,6 +219,7 @@ namespace WowUp.WPF.ViewModels return; } + Application.Current.MainWindow.ShowInTaskbar = true; Application.Current.MainWindow.Show(); Application.Current.MainWindow.WindowState = WindowState.Normal; Application.Current.MainWindow.Activate(); @@ -255,11 +256,11 @@ namespace WowUp.WPF.ViewModels public void OnSourceInitialized(Window window) { - if (StartupHelper.StartupOptions?.Minimized == true) + if (App.StartupOptions?.Minimized == true) { window.Hide(); window.ShowInTaskbar = false; - window.IsVisibleChanged += Window_IsVisibleChanged; + window.WindowState = WindowState.Minimized; } var windowPref = _preferenceRepository.FindByKey(WindowPlacementKey); @@ -289,13 +290,6 @@ namespace WowUp.WPF.ViewModels } } - private void Window_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) - { - var window = (Window)sender; - window.IsVisibleChanged -= Window_IsVisibleChanged; - window.ShowInTaskbar = true; - } - public void OnClosing(Window window) { var placement = window.GetPlacement(); From cffb838cf92d85c90cfec3c9d2e34dc36d710e7d Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 2 Oct 2020 14:19:24 -0500 Subject: [PATCH 18/28] Fix another CurseForge API error. Add some better error logging around CurseForge scanning. --- WowUp.Common/Models/Curse/CurseFile.cs | 2 +- .../AddonProviders/CurseAddonProvider.cs | 35 ++++++++++++------- WowUp.WPF/Assets/changelog.json | 4 +++ WowUp.WPF/WowUp.WPF.csproj | 2 +- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/WowUp.Common/Models/Curse/CurseFile.cs b/WowUp.Common/Models/Curse/CurseFile.cs index bf52f0d5..d6f985f3 100644 --- a/WowUp.Common/Models/Curse/CurseFile.cs +++ b/WowUp.Common/Models/Curse/CurseFile.cs @@ -37,7 +37,7 @@ namespace WowUp.Common.Models.Curse public object ExposeAsAlternative { get; set; } public long PackageFingerprintId { get; set; } public DateTime? GameVersionDateReleased { get; set; } - public long GameVersionMappingId { get; set; } + public long? GameVersionMappingId { get; set; } public int GameVersionId { get; set; } public int GameId { get; set; } public bool IsServerPack { get; set; } diff --git a/WowUp.WPF/AddonProviders/CurseAddonProvider.cs b/WowUp.WPF/AddonProviders/CurseAddonProvider.cs index 0350c7c9..485627a9 100644 --- a/WowUp.WPF/AddonProviders/CurseAddonProvider.cs +++ b/WowUp.WPF/AddonProviders/CurseAddonProvider.cs @@ -1,5 +1,6 @@ using Flurl; using Flurl.Http; +using Serilog; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -332,24 +333,34 @@ namespace WowUp.WPF.AddonProviders private async Task MapAddonFolders(List scanResults, WowClientType clientType) { - //var fingerprintStr = string.Join(",", scanResults.Select(sf => sf.FolderScanner.Fingerprint)); - var fingerprintResponse = await GetAddonsByFingerprints(scanResults.Select(sf => sf.FolderScanner.Fingerprint)); + var fingerprints = scanResults.Select(sf => sf.FolderScanner.Fingerprint); - foreach (var scanResult in scanResults) + try { - // Curse can deliver the wrong result sometimes, ensure the result matches the client type - scanResult.ExactMatch = fingerprintResponse.ExactMatches - .FirstOrDefault(exactMatch => - IsClientType(exactMatch.File.GameVersionFlavor, clientType) && - HasMatchingFingerprint(scanResult, exactMatch)); + var fingerprintResponse = await GetAddonsByFingerprints(fingerprints); - // If the addon does not have an exact match, check the partial matches. - if (scanResult.ExactMatch == null) + foreach (var scanResult in scanResults) { - scanResult.ExactMatch = fingerprintResponse.PartialMatches - .FirstOrDefault(partialMatch => partialMatch.File.Modules.Any(module => module.Fingerprint == scanResult.FolderScanner.Fingerprint)); + // Curse can deliver the wrong result sometimes, ensure the result matches the client type + scanResult.ExactMatch = fingerprintResponse.ExactMatches + .FirstOrDefault(exactMatch => + IsClientType(exactMatch.File.GameVersionFlavor, clientType) && + HasMatchingFingerprint(scanResult, exactMatch)); + + // If the addon does not have an exact match, check the partial matches. + if (scanResult.ExactMatch == null) + { + scanResult.ExactMatch = fingerprintResponse.PartialMatches + .FirstOrDefault(partialMatch => partialMatch.File.Modules.Any(module => module.Fingerprint == scanResult.FolderScanner.Fingerprint)); + } } } + catch(Exception ex) + { + Log.Error(ex, "Failed to map addon folders"); + Log.Error($"Fingerprints\n{string.Join(",", fingerprints)}"); + throw; + } } private bool HasMatchingFingerprint(CurseScanResult scanResult, CurseMatch exactMatch) diff --git a/WowUp.WPF/Assets/changelog.json b/WowUp.WPF/Assets/changelog.json index 1c4a1b65..e3c19a23 100644 --- a/WowUp.WPF/Assets/changelog.json +++ b/WowUp.WPF/Assets/changelog.json @@ -1,5 +1,9 @@ { "ChangeLogs": [ + { + "Version": "1.17.3", + "Description": "Fix another CurseForge API error.\nAdd some better error logging around CurseForge scanning." + }, { "Version": "1.17.2", "Description": "Fix an issue with handling CurseForge API responses." diff --git a/WowUp.WPF/WowUp.WPF.csproj b/WowUp.WPF/WowUp.WPF.csproj index 7c5ea8f3..4646af5d 100644 --- a/WowUp.WPF/WowUp.WPF.csproj +++ b/WowUp.WPF/WowUp.WPF.csproj @@ -10,7 +10,7 @@ WowUp Jliddev WowUp - 1.17.2 + 1.17.3 wowup_logo_512np_RRT_icon.ico jliddev https://wowup.io From dffefab111523374aa8f44416679f82ac09e5277 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 2 Oct 2020 14:42:06 -0500 Subject: [PATCH 19/28] Update WowUp.WPF.csproj --- WowUp.WPF/WowUp.WPF.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WowUp.WPF/WowUp.WPF.csproj b/WowUp.WPF/WowUp.WPF.csproj index 73a2fba3..9655c098 100644 --- a/WowUp.WPF/WowUp.WPF.csproj +++ b/WowUp.WPF/WowUp.WPF.csproj @@ -10,7 +10,7 @@ WowUp Jliddev WowUp - 1.18.0-beta.8 + 1.18.0-beta.9 wowup_logo_512np_RRT_icon.ico jliddev https://wowup.io From 5ba46a2ddfa6e04e28069d3b891b11d1bccc3bfe Mon Sep 17 00:00:00 2001 From: jliddev Date: Sat, 3 Oct 2020 22:29:32 -0500 Subject: [PATCH 20/28] Bulk change channel operations --- .../ViewModels/AddonListItemViewModel.cs | 2 +- WowUp.WPF/ViewModels/AddonsViewViewModel.cs | 72 +++++++++++++++++-- WowUp.WPF/Views/AddonsView.xaml | 14 ++++ WowUp.WPF/Views/AddonsView.xaml.cs | 1 + 4 files changed, 84 insertions(+), 5 deletions(-) diff --git a/WowUp.WPF/ViewModels/AddonListItemViewModel.cs b/WowUp.WPF/ViewModels/AddonListItemViewModel.cs index c17548d5..ace706c8 100644 --- a/WowUp.WPF/ViewModels/AddonListItemViewModel.cs +++ b/WowUp.WPF/ViewModels/AddonListItemViewModel.cs @@ -281,7 +281,7 @@ namespace WowUp.WPF.ViewModels _epicBrush = Application.Current.Resources["EpicBrush"] as SolidColorBrush; } - private void SetupDisplayState() + public void SetupDisplayState() { Name = _addon.Name; CurrentVersion = string.IsNullOrEmpty(_addon.InstalledVersion) diff --git a/WowUp.WPF/ViewModels/AddonsViewViewModel.cs b/WowUp.WPF/ViewModels/AddonsViewViewModel.cs index 80bdb568..3ac07f81 100644 --- a/WowUp.WPF/ViewModels/AddonsViewViewModel.cs +++ b/WowUp.WPF/ViewModels/AddonsViewViewModel.cs @@ -10,7 +10,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Data; using WowUp.Common.Enums; -using WowUp.Common.Services.Contracts; +using WowUp.Common.Extensions; using WowUp.WPF.Entities; using WowUp.WPF.Extensions; using WowUp.WPF.Services.Contracts; @@ -193,6 +193,27 @@ namespace WowUp.WPF.ViewModels set { SetProperty(ref _multiRowMenuAutoUpdateCheck, value); } } + private bool _multiRowMenuStableChannelCheck; + public bool MultiRowMenuStableChannelCheck + { + get => _multiRowMenuStableChannelCheck; + set { SetProperty(ref _multiRowMenuStableChannelCheck, value); } + } + + private bool _multiRowMenuBetaChannelCheck; + public bool MultiRowMenuBetaChannelCheck + { + get => _multiRowMenuBetaChannelCheck; + set { SetProperty(ref _multiRowMenuBetaChannelCheck, value); } + } + + private bool _multiRowMenuAlphaChannelCheck; + public bool MultiRowMenuAlphaChannelCheck + { + get => _multiRowMenuAlphaChannelCheck; + set { SetProperty(ref _multiRowMenuAlphaChannelCheck, value); } + } + public SearchInputViewModel SearchInputViewModel { get; set; } public Command LoadItemsCommand { get; set; } @@ -205,9 +226,13 @@ namespace WowUp.WPF.ViewModels public Command GridSortingCommand { get; set; } public Command ViewInitializedCommand { get; set; } public Command AutoUpdateCheckedCommand { get; set; } - + public Command StableChannelCheckedCommand { get; set; } + public Command BetaChannelCheckedCommand { get; set; } + public Command AlphaChannelCheckedCommand { get; set; } + public ContextMenu MultiRowMenu { get; set; } public ContextMenu RowMenu { get; set; } + public DataGrid DataGrid { get; set; } public ObservableCollection DisplayAddons { get; set; } public ObservableCollection ClientTypeNames { get; set; } @@ -272,6 +297,9 @@ namespace WowUp.WPF.ViewModels GridSortingCommand = new Command((args) => OnGridSorting(args as DataGridSortingEventArgs)); ViewInitializedCommand = new Command(() => OnViewInitialized()); AutoUpdateCheckedCommand = new Command(() => OnAutoUpdateCheckedCommand()); + StableChannelCheckedCommand = new Command(() => OnChangeAllChannelCommand(AddonChannelType.Stable)); + BetaChannelCheckedCommand = new Command(() => OnChangeAllChannelCommand(AddonChannelType.Beta)); + AlphaChannelCheckedCommand = new Command(() => OnChangeAllChannelCommand(AddonChannelType.Alpha)); SearchInputViewModel = serviceProvider.GetService(); SearchInputViewModel.TextChanged += SearchInputViewModel_TextChanged; @@ -289,7 +317,7 @@ namespace WowUp.WPF.ViewModels Initialize(); } - private async void OnAutoUpdateCheckedCommand() + private void OnAutoUpdateCheckedCommand() { _disableUpdateLoad = true; foreach (var addon in _selectedAddons) @@ -303,7 +331,26 @@ namespace WowUp.WPF.ViewModels _disableUpdateLoad = false; } - public void OnDataGridSelectionChange(IEnumerable selectedItems) + private void OnChangeAllChannelCommand(AddonChannelType addonChannel) + { + _disableUpdateLoad = true; + foreach (var addon in _selectedAddons) + { + addon.ChannelType = addonChannel; + _addonService.UpdateAddon(addon); + + var listItem = DisplayAddons.FirstOrDefault(item => item.Addon.Id == addon.Id); + listItem.Addon.ChannelType = addonChannel; + listItem.SetupDisplayState(); + } + + SetSelectionChannelState(); + + _disableUpdateLoad = false; + } + + public void OnDataGridSelectionChange( + IEnumerable selectedItems) { _selectedAddons = selectedItems.Select(item => item.Addon); MultiRowMenuTitle = selectedItems.Count() > 1 @@ -315,6 +362,23 @@ namespace WowUp.WPF.ViewModels : RowMenu; MultiRowMenuAutoUpdateCheck = selectedItems.All(item => item.IsAutoUpdated); + + SetSelectionChannelState(); + } + + private void SetSelectionChannelState() + { + MultiRowMenuStableChannelCheck = DataGrid.SelectedItems + .Cast() + .All(item => item.Addon.ChannelType == AddonChannelType.Stable); + + MultiRowMenuBetaChannelCheck = DataGrid.SelectedItems + .Cast() + .All(item => item.Addon.ChannelType == AddonChannelType.Beta); + + MultiRowMenuAlphaChannelCheck = DataGrid.SelectedItems + .Cast() + .All(item => item.Addon.ChannelType == AddonChannelType.Alpha); } private void SessionService_TabChanged(object sender, Type tabType) diff --git a/WowUp.WPF/Views/AddonsView.xaml b/WowUp.WPF/Views/AddonsView.xaml index 1b96825b..39409aff 100644 --- a/WowUp.WPF/Views/AddonsView.xaml +++ b/WowUp.WPF/Views/AddonsView.xaml @@ -262,6 +262,20 @@ IsCheckable="True" IsChecked="{Binding MultiRowMenuAutoUpdateCheck}" Command="{Binding AutoUpdateCheckedCommand}" /> + + + + + Date: Sat, 3 Oct 2020 23:04:47 -0500 Subject: [PATCH 21/28] Bulk addon uninstall --- WowUp.WPF/ViewModels/AddonsViewViewModel.cs | 94 +++++++++++++++++++-- WowUp.WPF/Views/AddonsView.xaml | 5 ++ WowUp.WPF/Views/AddonsView.xaml.cs | 1 - 3 files changed, 90 insertions(+), 10 deletions(-) diff --git a/WowUp.WPF/ViewModels/AddonsViewViewModel.cs b/WowUp.WPF/ViewModels/AddonsViewViewModel.cs index 3ac07f81..15cd9107 100644 --- a/WowUp.WPF/ViewModels/AddonsViewViewModel.cs +++ b/WowUp.WPF/ViewModels/AddonsViewViewModel.cs @@ -10,7 +10,6 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Data; using WowUp.Common.Enums; -using WowUp.Common.Extensions; using WowUp.WPF.Entities; using WowUp.WPF.Extensions; using WowUp.WPF.Services.Contracts; @@ -23,6 +22,7 @@ namespace WowUp.WPF.ViewModels private static readonly object ClientNamesLock = new object(); private static readonly object DisplayAddonsLock = new object(); + private readonly IAnalyticsService _analyticsService; private readonly IServiceProvider _serviceProvider; private readonly IWarcraftService _warcraftService; private readonly IAddonService _addonService; @@ -30,6 +30,7 @@ namespace WowUp.WPF.ViewModels private List _addons; private bool _disableUpdateLoad; + private IEnumerable _selectedRows; private IEnumerable _selectedAddons; private string _busyText; @@ -229,21 +230,24 @@ namespace WowUp.WPF.ViewModels public Command StableChannelCheckedCommand { get; set; } public Command BetaChannelCheckedCommand { get; set; } public Command AlphaChannelCheckedCommand { get; set; } + public Command ReInstallAllCommand { get; set; } + public Command UninstallAllCommand { get; set; } public ContextMenu MultiRowMenu { get; set; } public ContextMenu RowMenu { get; set; } - public DataGrid DataGrid { get; set; } public ObservableCollection DisplayAddons { get; set; } public ObservableCollection ClientTypeNames { get; set; } public AddonsViewViewModel( + IAnalyticsService analyticsService, IServiceProvider serviceProvider, IAddonService addonService, IWarcraftService warcraftService, ISessionService sessionService) { _addonService = addonService; + _analyticsService = analyticsService; _warcraftService = warcraftService; _serviceProvider = serviceProvider; _sessionService = sessionService; @@ -300,6 +304,8 @@ namespace WowUp.WPF.ViewModels StableChannelCheckedCommand = new Command(() => OnChangeAllChannelCommand(AddonChannelType.Stable)); BetaChannelCheckedCommand = new Command(() => OnChangeAllChannelCommand(AddonChannelType.Beta)); AlphaChannelCheckedCommand = new Command(() => OnChangeAllChannelCommand(AddonChannelType.Alpha)); + ReInstallAllCommand = new Command(async () => await ReInstallAll()); + UninstallAllCommand = new Command(async () => await UninstallAll()); SearchInputViewModel = serviceProvider.GetService(); SearchInputViewModel.TextChanged += SearchInputViewModel_TextChanged; @@ -349,9 +355,80 @@ namespace WowUp.WPF.ViewModels _disableUpdateLoad = false; } + private async Task ReInstallAll() + { + IsBusy = true; + EnableUpdateAll = false; + EnableRefresh = false; + EnableRescan = false; + + await _selectedAddons.ForEachAsync(2, async (addon) => + { + try + { + await _addonService.InstallAddon(addon.Id); + } + catch (Exception ex) + { + _analyticsService.Track(ex, "Failed during bulk install"); + } + }); + + IsBusy = false; + EnableUpdateAll = CanUpdateAll; + EnableRefresh = true; + EnableRescan = true; + + await _analyticsService.TrackUserAction( + "Addons", + "ReInstallBulk", + _selectedAddons.Count().ToString()); + } + + public async Task UninstallAll() + { + var messageBoxResult = MessageBox.Show( + $"Are you sure you want to remove {_selectedAddons.Count()} addons? This will remove all related folders from your World of Warcraft folder.", + "Uninstall Addon?", + MessageBoxButton.YesNo); + + if (messageBoxResult != MessageBoxResult.Yes) + { + return; + } + + IsBusy = true; + EnableUpdateAll = false; + EnableRefresh = false; + EnableRescan = false; + + foreach (var addon in _selectedAddons.ToList()) + { + try + { + await _addonService.UninstallAddon(addon); + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to uninstall addon {addon.Name}"); + } + } + + IsBusy = false; + EnableUpdateAll = CanUpdateAll; + EnableRefresh = true; + EnableRescan = true; + + await _analyticsService.TrackUserAction( + "Addons", + "UninstallBulk", + _selectedAddons.Count().ToString()); + } + public void OnDataGridSelectionChange( IEnumerable selectedItems) { + _selectedRows = selectedItems; _selectedAddons = selectedItems.Select(item => item.Addon); MultiRowMenuTitle = selectedItems.Count() > 1 ? $"{selectedItems.Count()} addons selected" @@ -368,16 +445,13 @@ namespace WowUp.WPF.ViewModels private void SetSelectionChannelState() { - MultiRowMenuStableChannelCheck = DataGrid.SelectedItems - .Cast() + MultiRowMenuStableChannelCheck = _selectedRows .All(item => item.Addon.ChannelType == AddonChannelType.Stable); - MultiRowMenuBetaChannelCheck = DataGrid.SelectedItems - .Cast() + MultiRowMenuBetaChannelCheck = _selectedRows .All(item => item.Addon.ChannelType == AddonChannelType.Beta); - MultiRowMenuAlphaChannelCheck = DataGrid.SelectedItems - .Cast() + MultiRowMenuAlphaChannelCheck = _selectedRows .All(item => item.Addon.ChannelType == AddonChannelType.Alpha); } @@ -497,13 +571,15 @@ namespace WowUp.WPF.ViewModels } finally { - EnableUpdateAll = DisplayAddons.Any(addon => addon.CanUpdate || addon.CanInstall); + EnableUpdateAll = CanUpdateAll; EnableRefresh = true; EnableRescan = true; IsBusy = false; } } + public bool CanUpdateAll => DisplayAddons.Any(addon => addon.CanUpdate || addon.CanInstall); + public async Task UpdateAllRetailClassic() { await UpdateAllWithSpinner(WowClientType.Retail, WowClientType.Classic); diff --git a/WowUp.WPF/Views/AddonsView.xaml b/WowUp.WPF/Views/AddonsView.xaml index 39409aff..91b90c94 100644 --- a/WowUp.WPF/Views/AddonsView.xaml +++ b/WowUp.WPF/Views/AddonsView.xaml @@ -276,6 +276,11 @@ IsChecked="{Binding MultiRowMenuAlphaChannelCheck}" Command="{Binding AlphaChannelCheckedCommand}"> + + + Date: Sat, 3 Oct 2020 23:07:37 -0500 Subject: [PATCH 22/28] Tray icon handle double clicks too --- WowUp.WPF/MainWindow.xaml | 3 ++- WowUp.WPF/WowUp.WPF.csproj | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/WowUp.WPF/MainWindow.xaml b/WowUp.WPF/MainWindow.xaml index 3ba54250..3b51e1e0 100644 --- a/WowUp.WPF/MainWindow.xaml +++ b/WowUp.WPF/MainWindow.xaml @@ -189,7 +189,8 @@ x:Name="TrayIcon" IconSource="/Assets/wowup_logo_512np_RRT_icon.ico" ToolTipText="WowUp" - LeftClickCommand="{Binding TaskbarIconClickCommand}"> + LeftClickCommand="{Binding TaskbarIconClickCommand}" + DoubleClickCommand="{Binding TaskbarIconClickCommand}"> diff --git a/WowUp.WPF/WowUp.WPF.csproj b/WowUp.WPF/WowUp.WPF.csproj index 9655c098..05281c1f 100644 --- a/WowUp.WPF/WowUp.WPF.csproj +++ b/WowUp.WPF/WowUp.WPF.csproj @@ -10,7 +10,7 @@ WowUp Jliddev WowUp - 1.18.0-beta.9 + 1.18.0-beta.10 wowup_logo_512np_RRT_icon.ico jliddev https://wowup.io From 807d9716592a6db6bd2273121cbe31e9b40c7899 Mon Sep 17 00:00:00 2001 From: jliddev Date: Sat, 3 Oct 2020 23:53:32 -0500 Subject: [PATCH 23/28] Prep for release --- WowUp.WPF/Assets/changelog.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WowUp.WPF/Assets/changelog.json b/WowUp.WPF/Assets/changelog.json index 397c0566..751f24be 100644 --- a/WowUp.WPF/Assets/changelog.json +++ b/WowUp.WPF/Assets/changelog.json @@ -2,7 +2,7 @@ "ChangeLogs": [ { "Version": "1.18.0", - "Description": "Add an 'Open Folder' option to the addon context menu (By Xathz).\nAdd new ability to manually select your initial WoW client folder if the intial auto detection fails.\nNew ability to perform bulk operations on 'My Addons' page.\nFeatured addons are now sorted by download count." + "Description": "Add an 'Open Folder' option to the addon context menu (By Xathz).\nAdd command line paramater to start the app minimized '-m' (By Noxis).\nAdd command line paramater to start the app and quit after auto updates are complete '-q' (By Noxis).\nAdd selector for when the app fails to automatically find WoW install folders.\nAdd the ability to select multiple addons and update their AutoUpdate status.\nAdd the ability to select multiple addons and update their Channel.\nAdd the ability to select multiple addons and re-install them.\nAdd the ability to select multiple addons and un-install them.\nFeatured addons are now sorted by download count.\nAddon icons should now be updated when a change is detected.\nThe system tray icon will now show the window when double clicked." }, { "Version": "1.17.3", From 93efb9ffc397e259a967fb10fc54a68f999c53aa Mon Sep 17 00:00:00 2001 From: jliddev Date: Sun, 4 Oct 2020 00:04:39 -0500 Subject: [PATCH 24/28] Remove left click tray icon delay --- WowUp.WPF/MainWindow.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WowUp.WPF/MainWindow.xaml b/WowUp.WPF/MainWindow.xaml index 3b51e1e0..dfee29c7 100644 --- a/WowUp.WPF/MainWindow.xaml +++ b/WowUp.WPF/MainWindow.xaml @@ -189,8 +189,8 @@ x:Name="TrayIcon" IconSource="/Assets/wowup_logo_512np_RRT_icon.ico" ToolTipText="WowUp" - LeftClickCommand="{Binding TaskbarIconClickCommand}" - DoubleClickCommand="{Binding TaskbarIconClickCommand}"> + NoLeftClickDelay="True" + LeftClickCommand="{Binding TaskbarIconClickCommand}"> From 8d64f86742a932de702772e298d235e0297b9326 Mon Sep 17 00:00:00 2001 From: jliddev Date: Sun, 4 Oct 2020 00:05:43 -0500 Subject: [PATCH 25/28] Version bump --- WowUp.WPF/WowUp.WPF.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WowUp.WPF/WowUp.WPF.csproj b/WowUp.WPF/WowUp.WPF.csproj index 05281c1f..d5f5ad33 100644 --- a/WowUp.WPF/WowUp.WPF.csproj +++ b/WowUp.WPF/WowUp.WPF.csproj @@ -10,7 +10,7 @@ WowUp Jliddev WowUp - 1.18.0-beta.10 + 1.18.0-beta.11 wowup_logo_512np_RRT_icon.ico jliddev https://wowup.io From ead35e05170325b47eb7698d2a23f50929caaa44 Mon Sep 17 00:00:00 2001 From: john liddell Date: Sun, 4 Oct 2020 13:44:54 -0500 Subject: [PATCH 26/28] Add some scanning logging --- WowUp.WPF/AddonProviders/CurseAddonProvider.cs | 1 + WowUp.WPF/Services/AddonService.cs | 2 ++ WowUp.WPF/Services/WarcraftService.cs | 1 + 3 files changed, 4 insertions(+) diff --git a/WowUp.WPF/AddonProviders/CurseAddonProvider.cs b/WowUp.WPF/AddonProviders/CurseAddonProvider.cs index 485627a9..2626a54c 100644 --- a/WowUp.WPF/AddonProviders/CurseAddonProvider.cs +++ b/WowUp.WPF/AddonProviders/CurseAddonProvider.cs @@ -46,6 +46,7 @@ namespace WowUp.WPF.AddonProviders AddonChannelType addonChannelType, IEnumerable addonFolders) { + Log.Debug($"{Name} Scanning {addonFolders.Count()} addons"); var addonDirectory = addonFolders.FirstOrDefault()?.Directory.Parent.FullName; var scanResults = await GetScanResults(addonFolders); diff --git a/WowUp.WPF/Services/AddonService.cs b/WowUp.WPF/Services/AddonService.cs index 41cedc4d..97d0e43e 100644 --- a/WowUp.WPF/Services/AddonService.cs +++ b/WowUp.WPF/Services/AddonService.cs @@ -630,6 +630,8 @@ namespace WowUp.WPF.Services } } + Log.Debug($"Scanned {addonFolders.Count} folders"); + var matchedAddonFolders = addonFolders.Where(af => af.MatchingAddon != null); var matchedGroups = matchedAddonFolders.GroupBy(af => $"{af.MatchingAddon.ProviderName}{af.MatchingAddon.ExternalId}"); diff --git a/WowUp.WPF/Services/WarcraftService.cs b/WowUp.WPF/Services/WarcraftService.cs index 9e9b3d23..a45ae5e1 100644 --- a/WowUp.WPF/Services/WarcraftService.cs +++ b/WowUp.WPF/Services/WarcraftService.cs @@ -246,6 +246,7 @@ namespace WowUp.WPF.Services foreach (var directory in addonDirectories) { + Log.Debug($"Getting addon folder {directory.Name}"); var addonFolder = await GetAddonFolder(directory); addons.Add(addonFolder); } From ed42ad7b078093750315680f7893fc9d18981f7e Mon Sep 17 00:00:00 2001 From: jliddev Date: Sun, 4 Oct 2020 13:48:59 -0500 Subject: [PATCH 27/28] Add some extra logging during scan --- WowUp.WPF/AddonProviders/GitHubAddonProvider.cs | 4 +++- WowUp.WPF/AddonProviders/TukUiAddonProvider.cs | 1 + WowUp.WPF/AddonProviders/WowInterfaceAddonProvider.cs | 2 ++ WowUp.WPF/Services/AddonService.cs | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/WowUp.WPF/AddonProviders/GitHubAddonProvider.cs b/WowUp.WPF/AddonProviders/GitHubAddonProvider.cs index e112aa72..c286c297 100644 --- a/WowUp.WPF/AddonProviders/GitHubAddonProvider.cs +++ b/WowUp.WPF/AddonProviders/GitHubAddonProvider.cs @@ -1,4 +1,5 @@ using Flurl.Http; +using Serilog; using System; using System.Collections.Generic; using System.IO; @@ -29,8 +30,9 @@ namespace WowUp.WPF.AddonProviders public Task Scan( WowClientType clientType, AddonChannelType addonChannelType, - IEnumerable addonFolder) + IEnumerable addonFolders) { + Log.Debug($"{Name} Scanning {addonFolders.Count()} addons"); return Task.CompletedTask; } diff --git a/WowUp.WPF/AddonProviders/TukUiAddonProvider.cs b/WowUp.WPF/AddonProviders/TukUiAddonProvider.cs index d64e2a99..fc5b7e2d 100644 --- a/WowUp.WPF/AddonProviders/TukUiAddonProvider.cs +++ b/WowUp.WPF/AddonProviders/TukUiAddonProvider.cs @@ -42,6 +42,7 @@ namespace WowUp.WPF.AddonProviders AddonChannelType addonChannelType, IEnumerable addonFolders) { + Log.Debug($"{Name} Scanning {addonFolders.Count()} addons"); var addons = await GetAllAddons(clientType); foreach (var addonFolder in addonFolders) diff --git a/WowUp.WPF/AddonProviders/WowInterfaceAddonProvider.cs b/WowUp.WPF/AddonProviders/WowInterfaceAddonProvider.cs index 34eeeeb4..8e54ef9b 100644 --- a/WowUp.WPF/AddonProviders/WowInterfaceAddonProvider.cs +++ b/WowUp.WPF/AddonProviders/WowInterfaceAddonProvider.cs @@ -1,4 +1,5 @@ using Flurl.Http; +using Serilog; using System; using System.Collections.Generic; using System.Linq; @@ -40,6 +41,7 @@ namespace WowUp.WPF.AddonProviders AddonChannelType addonChannelType, IEnumerable addonFolders) { + Log.Debug($"{Name} Scanning {addonFolders.Count()} addons"); foreach (var addonFolder in addonFolders) { if (string.IsNullOrEmpty(addonFolder.Toc.WowInterfaceId)) diff --git a/WowUp.WPF/Services/AddonService.cs b/WowUp.WPF/Services/AddonService.cs index 97d0e43e..c78797cb 100644 --- a/WowUp.WPF/Services/AddonService.cs +++ b/WowUp.WPF/Services/AddonService.cs @@ -630,7 +630,7 @@ namespace WowUp.WPF.Services } } - Log.Debug($"Scanned {addonFolders.Count} folders"); + Log.Debug($"Scanned {addonFolders.Count()} folders"); var matchedAddonFolders = addonFolders.Where(af => af.MatchingAddon != null); var matchedGroups = matchedAddonFolders.GroupBy(af => $"{af.MatchingAddon.ProviderName}{af.MatchingAddon.ExternalId}"); From e440ef70379a66ea3f4d992fcfc9ca202abf272b Mon Sep 17 00:00:00 2001 From: jliddev Date: Sun, 4 Oct 2020 13:49:16 -0500 Subject: [PATCH 28/28] version bump --- WowUp.WPF/WowUp.WPF.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WowUp.WPF/WowUp.WPF.csproj b/WowUp.WPF/WowUp.WPF.csproj index d5f5ad33..e11ef6c5 100644 --- a/WowUp.WPF/WowUp.WPF.csproj +++ b/WowUp.WPF/WowUp.WPF.csproj @@ -10,7 +10,7 @@ WowUp Jliddev WowUp - 1.18.0-beta.11 + 1.18.0-beta.12 wowup_logo_512np_RRT_icon.ico jliddev https://wowup.io