From 992657b0c5a7baebab006f62d18a8d4f8b957a39 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 10 Apr 2021 19:32:21 +0200 Subject: [PATCH] UI - Improved exception dialog shown at early startup failure Plugins - Provide a better error message on built in plugin installation failure Conditions - Added better null/not null checks for strings --- .../StringNotNullConditionOperator.cs | 13 ++++++ .../Operators/StringNullConditionOperator.cs | 13 ++++++ .../Services/PluginManagementService.cs | 12 ++++- .../Registration/ConditionOperatorService.cs | 2 + src/Artemis.UI/ApplicationStateManager.cs | 46 +++++++++++++++++++ src/Artemis.UI/Bootstrapper.cs | 19 ++++---- 6 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotNullConditionOperator.cs create mode 100644 src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNullConditionOperator.cs diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotNullConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotNullConditionOperator.cs new file mode 100644 index 000000000..3b7c7ac7a --- /dev/null +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNotNullConditionOperator.cs @@ -0,0 +1,13 @@ +namespace Artemis.Core +{ + internal class StringNotNullConditionOperator : ConditionOperator + { + public override string Description => "Is not null"; + public override string Icon => "CheckboxMarkedCircleOutline"; + + public override bool Evaluate(string a) + { + return !string.IsNullOrWhiteSpace(a); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNullConditionOperator.cs b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNullConditionOperator.cs new file mode 100644 index 000000000..84d1f6775 --- /dev/null +++ b/src/Artemis.Core/DefaultTypes/Conditions/Operators/StringNullConditionOperator.cs @@ -0,0 +1,13 @@ +namespace Artemis.Core +{ + internal class StringNullConditionOperator : ConditionOperator + { + public override string Description => "Is null"; + public override string Icon => "Null"; + + public override bool Evaluate(string a) + { + return string.IsNullOrWhiteSpace(a); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index a88c8f788..4f833282d 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -98,11 +98,19 @@ namespace Artemis.Core.Services } else { + PluginInfo pluginInfo; try { // Compare versions, copy if the same when debugging - PluginInfo pluginInfo = CoreJson.DeserializeObject(File.ReadAllText(metadataFile))!; + pluginInfo = CoreJson.DeserializeObject(File.ReadAllText(metadataFile))!; + } + catch (Exception e) + { + throw new ArtemisPluginException($"Failed read plugin metadata needed to install built-in plugin: {e.Message}", e); + } + try + { if (builtInPluginInfo.Version > pluginInfo.Version) { _logger.Debug("Copying updated built-in plugin from {pluginInfo} to {builtInPluginInfo}", pluginInfo, builtInPluginInfo); @@ -111,7 +119,7 @@ namespace Artemis.Core.Services } catch (Exception e) { - throw new ArtemisPluginException("Failed read plugin metadata needed to install built-in plugin", e); + throw new ArtemisPluginException($"Failed to install built-in plugin: {e.Message}", e); } } } diff --git a/src/Artemis.Core/Services/Registration/ConditionOperatorService.cs b/src/Artemis.Core/Services/Registration/ConditionOperatorService.cs index f14a0d0af..ead028ffb 100644 --- a/src/Artemis.Core/Services/Registration/ConditionOperatorService.cs +++ b/src/Artemis.Core/Services/Registration/ConditionOperatorService.cs @@ -61,6 +61,8 @@ namespace Artemis.Core.Services RegisterConditionOperator(Constants.CorePlugin, new StringStartsWithConditionOperator()); RegisterConditionOperator(Constants.CorePlugin, new StringEndsWithConditionOperator()); RegisterConditionOperator(Constants.CorePlugin, new StringMatchesRegexConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new StringNullConditionOperator()); + RegisterConditionOperator(Constants.CorePlugin, new StringNotNullConditionOperator()); // Null checks, at the bottom // TODO: Implement a priority mechanism diff --git a/src/Artemis.UI/ApplicationStateManager.cs b/src/Artemis.UI/ApplicationStateManager.cs index c751abcb6..3802d5494 100644 --- a/src/Artemis.UI/ApplicationStateManager.cs +++ b/src/Artemis.UI/ApplicationStateManager.cs @@ -4,13 +4,16 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; +using System.Reflection; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; using System.Windows; using Artemis.Core; +using Artemis.UI.Screens; using Artemis.UI.Utilities; using Ninject; +using Ookii.Dialogs.Wpf; using Stylet; namespace Artemis.UI @@ -127,5 +130,48 @@ namespace Artemis.UI Execute.OnUIThread(() => Application.Current.Shutdown()); } + + public void DisplayException(Exception e) + { + using TaskDialog dialog = new(); + AssemblyInformationalVersionAttribute versionAttribute = typeof(ApplicationStateManager).Assembly.GetCustomAttribute(); + dialog.WindowTitle = $"Artemis {versionAttribute?.InformationalVersion} build {Constants.BuildInfo.BuildNumberDisplay}"; + dialog.MainInstruction = "Unfortunately Artemis ran into an unhandled exception and cannot continue."; + dialog.Content = e.Message; + dialog.ExpandedInformation = e.StackTrace.Trim(); + + dialog.CollapsedControlText = "Show stack trace"; + dialog.ExpandedControlText = "Hide stack trace"; + + dialog.Footer = "If this keeps happening check out the wiki or hit us up on Discord."; + dialog.FooterIcon = TaskDialogIcon.Error; + dialog.EnableHyperlinks = true; + dialog.HyperlinkClicked += OpenHyperlink; + + TaskDialogButton copyButton = new("Copy stack trace"); + TaskDialogButton closeButton = new("Close") {Default = true}; + dialog.Buttons.Add(copyButton); + dialog.Buttons.Add(closeButton); + dialog.ButtonClicked += (_, args) => + { + if (args.Item == copyButton) + { + Clipboard.SetText(e.ToString()); + args.Cancel = true; + } + }; + + dialog.ShowDialog(Application.Current.MainWindow); + } + + private void OpenHyperlink(object sender, HyperlinkClickedEventArgs e) + { + ProcessStartInfo processInfo = new() + { + FileName = e.Href, + UseShellExecute = true + }; + Process.Start(processInfo); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Bootstrapper.cs b/src/Artemis.UI/Bootstrapper.cs index 11d1f56c9..f1ba4a212 100644 --- a/src/Artemis.UI/Bootstrapper.cs +++ b/src/Artemis.UI/Bootstrapper.cs @@ -48,7 +48,14 @@ namespace Artemis.UI return; } - try { DPIAwareness.Initalize(); } catch (Exception ex) { logger.Error($"Failed to set DPI-Awareness: {ex.Message}"); } + try + { + DPIAwareness.Initalize(); + } + catch (Exception ex) + { + logger.Error($"Failed to set DPI-Awareness: {ex.Message}"); + } IViewManager viewManager = Kernel.Get(); StartupArguments = Args.ToList(); @@ -71,7 +78,7 @@ namespace Artemis.UI Execute.OnUIThreadSync(() => { UIElement view = viewManager.CreateAndBindViewForModelIfNecessary(RootViewModel); - ((TrayViewModel)RootViewModel).SetTaskbarIcon(view); + ((TrayViewModel) RootViewModel).SetTaskbarIcon(view); }); // Initialize the core async so the UI can show the progress @@ -136,15 +143,9 @@ namespace Artemis.UI private void HandleFatalException(Exception e, ILogger logger) { logger.Fatal(e, "Fatal exception during initialization, shutting down."); - - // Can't use a pretty exception dialog here since the UI might not even be visible Execute.OnUIThread(() => { - Kernel.Get().ShowMessageBox(e.Message + "\n\n Please refer the log file for more details.", - "Fatal exception during initialization", - MessageBoxButton.OK, - MessageBoxImage.Error - ); + _applicationStateManager.DisplayException(e); Environment.Exit(1); }); }