Add Avalonia setup

This commit is contained in:
Michael Bucari-Tovo 2022-07-24 13:04:19 -06:00
parent ffd947eb2e
commit fe6cfc899b
16 changed files with 355 additions and 200 deletions

View File

@ -6,6 +6,11 @@ using LibationFileManager;
using LibationAvalonia.Views;
using System;
using Avalonia.Platform;
using LibationAvalonia.Dialogs;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.IO;
using ApplicationServices;
namespace LibationAvalonia
{
@ -31,6 +36,9 @@ namespace LibationAvalonia
AssetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>();
}
public static Task<List<DataLayer.LibraryBook>> LibraryTask;
public static bool SetupRequired;
public override void OnFrameworkInitializationCompleted()
{
LoadStyles();
@ -41,16 +49,140 @@ namespace LibationAvalonia
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var mainWindow = new MainWindow();
desktop.MainWindow = mainWindow;
mainWindow.RestoreSizeAndLocation(Configuration.Instance);
mainWindow.OnLoad();
if (SetupRequired)
{
var config = Configuration.Instance;
var defaultLibationFilesDir = Configuration.UserProfile;
// check for existing settings in default location
var defaultSettingsFile = Path.Combine(defaultLibationFilesDir, "Settings.json");
if (Configuration.SettingsFileIsValid(defaultSettingsFile))
config.SetLibationFiles(defaultLibationFilesDir);
if (config.LibationSettingsAreValid)
return;
var setupDialog = new SetupDialog { Config = config };
setupDialog.Closing += Setup_Closing;
desktop.MainWindow = setupDialog;
}
else
ShowMainWindow(desktop);
}
base.OnFrameworkInitializationCompleted();
}
private void LoadStyles()
private void Setup_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
var setupDialog = sender as SetupDialog;
var desktop = ApplicationLifetime as IClassicDesktopStyleApplicationLifetime;
try
{
// all returns should be preceded by either:
// - if config.LibationSettingsAreValid
// - error message, Exit()
if ((!setupDialog.IsNewUser
&& !setupDialog.IsReturningUser) ||
!RunInstall(setupDialog))
{
CancelInstallation();
return;
}
// most migrations go in here
AppScaffolding.LibationScaffolding.RunPostConfigMigrations(setupDialog.Config);
MessageBox.VerboseLoggingWarning_ShowIfTrue();
#if !DEBUG
checkForUpdate();
#endif
// logging is init'd here
AppScaffolding.LibationScaffolding.RunPostMigrationScaffolding(setupDialog.Config);
}
catch (Exception ex)
{
var title = "Fatal error, pre-logging";
var body = "An unrecoverable error occurred. Since this error happened before logging could be initialized, this error can not be written to the log file.";
try
{
MessageBox.ShowAdminAlert(null, body, title, ex);
}
catch
{
MessageBox.Show($"{body}\r\n\r\n{ex.Message}\r\n\r\n{ex.StackTrace}", title, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return;
}
LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists();
ShowMainWindow(desktop);
}
private static bool RunInstall(SetupDialog setupDialog)
{
var config = setupDialog.Config;
if (setupDialog.IsNewUser)
{
config.SetLibationFiles(Configuration.UserProfile);
}
else if (setupDialog.IsReturningUser)
{
var libationFilesDialog = new LibationFilesDialog();
if (libationFilesDialog.ShowDialogSynchronously<DialogResult>(setupDialog) != DialogResult.OK)
return false;
config.SetLibationFiles(libationFilesDialog.SelectedDirectory);
if (config.LibationSettingsAreValid)
return true;
// path did not result in valid settings
var continueResult = MessageBox.Show(
$"No valid settings were found at this location.\r\nWould you like to create a new install settings in this folder?\r\n\r\n{libationFilesDialog.SelectedDirectory}",
"New install?",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question);
if (continueResult != DialogResult.Yes)
return false;
}
// INIT DEFAULT SETTINGS
// if 'new user' was clicked, or if 'returning user' chose new install: show basic settings dialog
config.Books ??= Path.Combine(Configuration.UserProfile, "Books");
return new SettingsDialog().ShowDialogSynchronously<DialogResult>(setupDialog) == DialogResult.OK
&& config.LibationSettingsAreValid;
}
static void CancelInstallation()
{
MessageBox.Show("Initial set up cancelled.", "Cancelled", MessageBoxButtons.OK, MessageBoxIcon.Warning);
Environment.Exit(0);
}
private static void ShowMainWindow(IClassicDesktopStyleApplicationLifetime desktop)
{
var mainWindow = new MainWindow();
desktop.MainWindow = mainWindow;
mainWindow.RestoreSizeAndLocation(Configuration.Instance);
mainWindow.OnLoad();
mainWindow.OnLibraryLoaded(LibraryTask.GetAwaiter().GetResult());
mainWindow.Show();
}
private static void LoadStyles()
{
ProcessQueueBookFailedBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookFailedBrush");
ProcessQueueBookCompletedBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookCompletedBrush");

View File

@ -0,0 +1,16 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x2="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:LibationAvalonia"
x2:Class="LibationAvalonia.AppBasic">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme Mode="Light"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
<StyleInclude Source="/Assets/LibationStyles.xaml"/>
</Application.Styles>
</Application>

View File

@ -0,0 +1,28 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Controls;
using LibationAvalonia.Dialogs;
namespace LibationAvalonia
{
public class AppBasic : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public Window MainWindow { get; set; }
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new SetupDialog();
}
base.OnFrameworkInitializationCompleted();
}
}
}

View File

@ -8,6 +8,7 @@ namespace LibationAvalonia.Dialogs
{
public abstract class DialogWindow : Window
{
public bool SaveAndRestorePosition { get; set; } = true;
public Control ControlToFocusOnShow { get; set; }
public DialogWindow()
{
@ -21,16 +22,22 @@ namespace LibationAvalonia.Dialogs
this.AttachDevTools();
#endif
}
public DialogWindow(bool saveAndRestorePosition) : this()
{
SaveAndRestorePosition = saveAndRestorePosition;
}
private void DialogWindow_Initialized(object sender, EventArgs e)
{
this.WindowStartupLocation = WindowStartupLocation.CenterOwner;
this.RestoreSizeAndLocation(Configuration.Instance);
if (SaveAndRestorePosition)
this.RestoreSizeAndLocation(Configuration.Instance);
}
private void DialogWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
this.SaveSizeAndLocation(Configuration.Instance);
if (SaveAndRestorePosition)
this.SaveSizeAndLocation(Configuration.Instance);
}
private void DialogWindow_Opened(object sender, EventArgs e)

View File

@ -17,6 +17,7 @@
Grid.Row="0"
Margin="5"
Directory="{Binding Directory, Mode=TwoWay}"
SubDirectory=""
KnownDirectories="{Binding KnownDirectories}" />
<Button
@ -24,6 +25,7 @@
HorizontalAlignment="Right"
Margin="5"
Padding="30,3,30,3"
Content="Save" />
Content="Save"
Click="SaveButton_Click" />
</Grid>
</Window>

View File

@ -8,7 +8,7 @@ using System.Threading.Tasks;
namespace LibationAvalonia.Dialogs
{
public partial class LibationFilesDialog : DialogWindow
public partial class LibationFilesDialog : Window
{
private class DirSelectOptions
{
@ -19,27 +19,32 @@ namespace LibationAvalonia.Dialogs
Configuration.KnownDirectories.MyDocs
};
public string Directory { get; set; } = Configuration.Instance.LibationFiles;
public string Directory { get; set; } = Configuration.GetKnownDirectoryPath(Configuration.KnownDirectories.UserProfile);
}
private DirSelectOptions dirSelectOptions;
public string SelectedDirectory => dirSelectOptions.Directory;
public LibationFilesDialog()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
DataContext = dirSelectOptions = new();
}
protected override async Task SaveAndCloseAsync()
public void SaveButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var libationDir = dirSelectOptions.Directory;
if (!System.IO.Directory.Exists(libationDir))
{
MessageBox.Show("Not saving change to Libation Files location. This folder does not exist:\r\n" + libationDir, "Folder does not exist", MessageBoxButtons.OK, MessageBoxIcon.Error);
MessageBox.Show("Not saving change to Libation Files location. This folder does not exist:\r\n" + libationDir, "Folder does not exist", MessageBoxButtons.OK, MessageBoxIcon.Error, saveAndRestorePosition: false);
return;
}
await base.SaveAndCloseAsync();
Close(DialogResult.OK);
}
private void InitializeComponent()

View File

@ -12,6 +12,10 @@ namespace LibationAvalonia.Dialogs
{
InitializeComponent();
}
public MessageBoxWindow(bool saveAndRestorePosition):base(saveAndRestorePosition)
{
InitializeComponent();
}
private void InitializeComponent()
{

View File

@ -0,0 +1,50 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="330"
MinWidth="500" MinHeight="330"
MaxWidth="500" MaxHeight="330"
x:Class="LibationAvalonia.Dialogs.SetupDialog"
WindowStartupLocation="CenterScreen"
Icon="/Assets/libation.ico"
Title="Welcome to Libation">
<Grid Margin="10" RowDefinitions="*,Auto,Auto">
<TextBlock Grid.Row="0" Text="This appears to be your first time using Libation or a previous setup was incomplete.
&#xa;
&#xa;Please fill in a few settings. You can also change these settings later.
&#xa;
&#xa;After you make your selections, get started by importing your library.
&#xa;Go to Import > Scan Library
&#xa;
&#xa;Download your entire library from the &quot;Liberate&quot; tab or
&#xa;liberate your books one at a time by clicking the stoplight." />
<Button
Grid.Row="1"
Margin="0,10,0,10"
Padding="0,10,0,10"
HorizontalAlignment="Stretch"
Click="NewUser_Click">
<TextBlock
TextAlignment="Center"
Text="NEW USER&#xa;&#xa;Choose Settings"/>
</Button>
<Button
Grid.Row="2"
Padding="0,10,0,10"
HorizontalAlignment="Stretch"
Click="ReturningUser_Click">
<TextBlock
TextAlignment="Center"
Text="RETURNING USER&#xa;&#xa;I have previously installed Libation. This is an upgrade or re-install."/>
</Button>
</Grid>
</Window>

View File

@ -0,0 +1,39 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using LibationFileManager;
namespace LibationAvalonia.Dialogs
{
public partial class SetupDialog : Window
{
public bool IsNewUser { get;private set; }
public bool IsReturningUser { get;private set; }
public Configuration Config { get; init; }
public SetupDialog()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
public void NewUser_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
IsNewUser = true;
Close(DialogResult.OK);
}
public void ReturningUser_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
IsReturningUser = true;
Close(DialogResult.OK);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -12,7 +12,7 @@ namespace LibationAvalonia
static readonly WindowIcon WindowIcon;
static FormSaveExtension()
{
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && desktop.MainWindow is not null)
WindowIcon = desktop.MainWindow.Icon;
else
WindowIcon = null;

View File

@ -80,6 +80,9 @@
</ItemGroup>
<ItemGroup>
<Compile Update="AppBasic.axaml.cs">
<DependentUpon>AppBasic.axaml</DependentUpon>
</Compile>
<Compile Update="Views\ProcessBookControl.axaml.cs">
<DependentUpon>ProcessBookControl.axaml</DependentUpon>
</Compile>

View File

@ -62,8 +62,8 @@ namespace LibationAvalonia
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
=> ShowCoreAsync(null, text, caption, buttons, icon, defaultButton);
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
=> ShowCoreAsync(null, text, caption, buttons, icon, MessageBoxDefaultButton.Button1);
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, bool saveAndRestorePosition = true)
=> ShowCoreAsync(null, text, caption, buttons, icon, MessageBoxDefaultButton.Button1, saveAndRestorePosition);
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons)
=> ShowCoreAsync(null, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
public static DialogResult Show(string text, string caption)
@ -148,9 +148,9 @@ Libation.
}
private static DialogResult ShowCoreAsync(Window owner, string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
private static DialogResult ShowCoreAsync(Window owner, string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, bool saveAndRestorePosition = true)
{
var dialog = new MessageBoxWindow();
var dialog = new MessageBoxWindow(saveAndRestorePosition);
dialog.HideMinMaxBtns();

View File

@ -18,24 +18,33 @@ namespace LibationAvalonia
static async Task Main()
{
//***********************************************//
// //
// do not use Configuration before this line //
// //
//***********************************************//
// Migrations which must occur before configuration is loaded for the first time. Usually ones which alter the Configuration
var config = AppScaffolding.LibationScaffolding.RunPreConfigMigrations();
App.SetupRequired = !config.LibationSettingsAreValid;
//Start as much work in parallel as possible.
var runDbMigrationsTask = Task.Run(() => RunDbMigrations());
var classicLifetimeTask = Task.Run(() => new ClassicDesktopStyleApplicationLifetime());
var appBuilderTask = Task.Run(BuildAvaloniaApp);
if (!await runDbMigrationsTask)
return;
var dbLibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
if (!App.SetupRequired)
{
if (!RunDbMigrations(config))
return;
App.LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
}
(await appBuilderTask).SetupWithLifetime(await classicLifetimeTask);
var form1 = (Views.MainWindow)classicLifetimeTask.Result.MainWindow;
form1.OnLibraryLoaded(await dbLibraryTask);
var assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
classicLifetimeTask.Result.Start(null);
}
@ -44,20 +53,15 @@ namespace LibationAvalonia
.UsePlatformDetect()
.LogToTrace()
.UseReactiveUI();
public static AppBuilder BuildAvaloniaAppBasic()
=> AppBuilder.Configure<AppBasic>()
.UsePlatformDetect()
.LogToTrace();
private static bool RunDbMigrations()
public static bool RunDbMigrations(Configuration config)
{
try
{
//***********************************************//
// //
// do not use Configuration before this line //
// //
//***********************************************//
// Migrations which must occur before configuration is loaded for the first time. Usually ones which alter the Configuration
var config = AppScaffolding.LibationScaffolding.RunPreConfigMigrations();
AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists();
// most migrations go in here
AppScaffolding.LibationScaffolding.RunPostConfigMigrations(config);
AppScaffolding.LibationScaffolding.RunPostMigrationScaffolding(config);

View File

@ -6,7 +6,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>..\bin\Release\publish\linux-x64\</PublishDir>
<PublishDir>..\bin\publish\linux-x64\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net6.0</TargetFramework>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>

View File

@ -345,7 +345,7 @@ namespace LibationFileManager
#endregion
#region known directories
public static string AppDir_Relative => $@".\{LIBATION_FILES_KEY}";
public static string AppDir_Relative => $@".{Path.PathSeparator}{LIBATION_FILES_KEY}";
public static string AppDir_Absolute => Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Exe.FileLocationOnDisk), LIBATION_FILES_KEY));
public static string MyDocs => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Libation"));
public static string WinTemp => Path.GetFullPath(Path.Combine(Path.GetTempPath(), "Libation"));
@ -522,8 +522,6 @@ namespace LibationFileManager
public void SetLibationFiles(string directory)
{
libationFilesPathCache = null;
// ensure exists
if (!File.Exists(APPSETTINGS_JSON))
{
@ -532,6 +530,8 @@ namespace LibationFileManager
System.Threading.Thread.Sleep(100);
}
libationFilesPathCache = null;
var startingContents = File.ReadAllText(APPSETTINGS_JSON);
var jObj = JObject.Parse(startingContents);

View File

@ -1,14 +1,12 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using ApplicationServices;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.ReactiveUI;
using Dinah.Core;
using LibationFileManager;
using LibationWinForms.Dialogs;
using Serilog;
namespace LibationWinForms
{
@ -20,98 +18,6 @@ namespace LibationWinForms
[STAThread]
static void Main()
{
var sw = System.Diagnostics.Stopwatch.StartNew();
var config = LoadLibationConfig();
if (config is null) return;
var bmp = System.Drawing.SystemIcons.Error.ToBitmap();
/*
Results below compare startup times when parallelizing startup tasks vs when
running everything sequentially, from the entry point until after the call to
OnLoadedLibrary() returns. Tests were run on a ReadyToRun enabled release build.
The first run is substantially slower than all subsequent runs for both serial
and parallel. This is most likely due to file system caching speeding up
subsequent runs, and it's significant because in the wild, most runs are "cold"
and will not benefit from caching.
All times are in milliseconds.
Run Parallel Serial
1 2837 5835
2 1566 2774
3 1562 2316
4 1642 2388
5 1596 2391
6 1591 2358
7 1492 2363
8 1542 2335
9 1600 2418
10 1564 2359
11 1567 2379
Min 1492 2316
Q1 1562 2358
Med 1567 2379
Q3 1600 2418
Max 2837 5835
*/
void Form1_Load(object sender, EventArgs e)
{
sw.Stop();
//MessageBox.Show(sw.ElapsedMilliseconds.ToString());
}
//For debug purposes, always run AvaloniaUI.
if (config.GetNonString<bool>("BetaOptIn"))
{
//Start as much work in parallel as possible.
var runDbMigrationsTask = Task.Run(() => RunDbMigrations(config));
var classicLifetimeTask = Task.Run(() => new ClassicDesktopStyleApplicationLifetime());
var appBuilderTask = Task.Run(BuildAvaloniaApp);
if (!runDbMigrationsTask.GetAwaiter().GetResult())
return;
var runOtherMigrationsTask = Task.Run(() => RunOtherMigrations(config));
var dbLibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
appBuilderTask.GetAwaiter().GetResult().SetupWithLifetime(classicLifetimeTask.GetAwaiter().GetResult());
if (!runOtherMigrationsTask.GetAwaiter().GetResult())
return;
var form1 = (AvaloniaUI.Views.MainWindow)classicLifetimeTask.Result.MainWindow;
form1.Opened += Form1_Load;
form1.OnLibraryLoaded(dbLibraryTask.GetAwaiter().GetResult());
classicLifetimeTask.Result.Start(null);
}
else
{
if (!RunDbMigrations(config) || !RunOtherMigrations(config))
return;
var form1 = new Form1();
form1.Shown += Form1_Load;
System.Windows.Forms.Application.Run(form1);
}
}
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<AvaloniaUI.App>()
.UsePlatformDetect()
.LogToTrace()
.UseReactiveUI();
private static Configuration LoadLibationConfig()
{
try
{
@ -120,7 +26,7 @@ namespace LibationWinForms
//AllocConsole();
// run as early as possible. see notes in postLoggingGlobalExceptionHandling
System.Windows.Forms.Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
ApplicationConfiguration.Initialize();
@ -131,40 +37,13 @@ namespace LibationWinForms
//***********************************************//
// Migrations which must occur before configuration is loaded for the first time. Usually ones which alter the Configuration
var config = AppScaffolding.LibationScaffolding.RunPreConfigMigrations();
AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists();
return config;
}
catch (Exception ex)
{
DisplayStartupErrorMessage(ex);
return null;
}
}
private static bool RunDbMigrations(Configuration config)
{
try
{
// do this as soon as possible (post-config)
RunInstaller(config);
// most migrations go in here
AppScaffolding.LibationScaffolding.RunPostConfigMigrations(config);
return true;
}
catch (Exception ex)
{
DisplayStartupErrorMessage(ex);
return false;
}
}
private static bool RunOtherMigrations(Configuration config)
{
try
{
// migrations which require Forms or are long-running
RunWindowsOnlyMigrations(config);
@ -175,36 +54,26 @@ namespace LibationWinForms
#endif
// logging is init'd here
AppScaffolding.LibationScaffolding.RunPostMigrationScaffolding(config);
// global exception handling (ShowAdminAlert) attempts to use logging. only call it after logging has been init'd
#if WINDOWS7_0_OR_GREATER
postLoggingGlobalExceptionHandling();
#endif
return true;
}
catch (Exception ex)
{
DisplayStartupErrorMessage(ex);
return false;
var title = "Fatal error, pre-logging";
var body = "An unrecoverable error occurred. Since this error happened before logging could be initialized, this error can not be written to the log file.";
try
{
MessageBoxLib.ShowAdminAlert(null, body, title, ex);
}
catch
{
MessageBox.Show($"{body}\r\n\r\n{ex.Message}\r\n\r\n{ex.StackTrace}", title, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return;
}
}
// global exception handling (ShowAdminAlert) attempts to use logging. only call it after logging has been init'd
postLoggingGlobalExceptionHandling();
private static void DisplayStartupErrorMessage(Exception ex)
{
var title = "Fatal error, pre-logging";
var body = "An unrecoverable error occurred. Since this error happened before logging could be initialized, this error can not be written to the log file.";
#if WINDOWS7_0_OR_GREATER
try
{
MessageBoxLib.ShowAdminAlert(null, body, title, ex);
}
catch
{
MessageBox.Show($"{body}\r\n\r\n{ex.Message}\r\n\r\n{ex.StackTrace}", title, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
#endif
Application.Run(new Form1());
}
private static void RunInstaller(Configuration config)
@ -229,7 +98,7 @@ namespace LibationWinForms
static void CancelInstallation()
{
MessageBox.Show("Initial set up cancelled.", "Cancelled", MessageBoxButtons.OK, MessageBoxIcon.Warning);
System.Windows.Forms.Application.Exit();
Application.Exit();
Environment.Exit(0);
}
@ -301,23 +170,19 @@ namespace LibationWinForms
try
{
upgradeProperties = AppScaffolding.LibationScaffolding.GetLatestRelease();
upgradeProperties = AppScaffolding.LibationScaffolding.GetLatestRelease();
if (upgradeProperties is null)
return;
}
catch (Exception ex)
{
#if WINDOWS7_0_OR_GREATER
MessageBoxLib.ShowAdminAlert(null, "Error checking for update", "Error checking for update", ex);
#endif
return;
}
if (upgradeProperties.ZipUrl is null)
{
#if WINDOWS7_0_OR_GREATER
MessageBox.Show(upgradeProperties.HtmlUrl, "New version available");
#endif
return;
}
@ -330,7 +195,7 @@ namespace LibationWinForms
AppDomain.CurrentDomain.UnhandledException += (_, e) => MessageBoxLib.ShowAdminAlert(null, "Libation has crashed due to an unhandled error.", "Application crash!", (Exception)e.ExceptionObject);
// these 2 lines makes it graceful. sync (eg in main form's ctor) and thread exceptions will still crash us, but event (sync, void async, Task async) will not
System.Windows.Forms.Application.ThreadException += (_, e) => MessageBoxLib.ShowAdminAlert(null, "Libation has encountered an unexpected error.", "Unexpected error", e.Exception);
Application.ThreadException += (_, e) => MessageBoxLib.ShowAdminAlert(null, "Libation has encountered an unexpected error.", "Unexpected error", e.Exception);
// move to beginning of execution. crashes app if this is called post-RunInstaller: System.InvalidOperationException: 'Thread exception mode cannot be changed once any Controls are created on the thread.'
//// I never found a case where including made a difference. I think this enum is default and including it will override app user config file
//Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);