Form thread safety

This commit is contained in:
Michael Bucari-Tovo 2025-07-21 22:52:17 -06:00
parent 7848366818
commit 1fdcea929f
6 changed files with 87 additions and 69 deletions

View File

@ -1,5 +1,6 @@
using AudibleApi;
using AudibleUtilities;
using Avalonia.Threading;
using LibationUiBase.Forms;
using System.Threading.Tasks;
@ -17,42 +18,46 @@ namespace LibationAvalonia.Dialogs.Login
}
public async Task<string> Get2faCodeAsync(string prompt)
{
var dialog = new _2faCodeDialog(prompt);
if (await dialog.ShowDialogAsync() is DialogResult.OK)
return dialog.Code;
return null;
}
=> await Dispatcher.UIThread.InvokeAsync(async () =>
{
var dialog = new _2faCodeDialog(prompt);
if (await dialog.ShowDialogAsync() is DialogResult.OK)
return dialog.Code;
return null;
});
public async Task<(string password, string guess)> GetCaptchaAnswerAsync(string password, byte[] captchaImage)
{
var dialog = new CaptchaDialog(password, captchaImage);
if (await dialog.ShowDialogAsync() is DialogResult.OK)
return (dialog.Password, dialog.Answer);
return (null, null);
}
=> await Dispatcher.UIThread.InvokeAsync(async () =>
{
var dialog = new CaptchaDialog(password, captchaImage);
if (await dialog.ShowDialogAsync() is DialogResult.OK)
return (dialog.Password, dialog.Answer);
return (null, null);
});
public async Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)
{
var dialog = new MfaDialog(mfaConfig);
if (await dialog.ShowDialogAsync() is DialogResult.OK)
return (dialog.SelectedName, dialog.SelectedValue);
return (null, null);
}
=> await Dispatcher.UIThread.InvokeAsync(async () =>
{
var dialog = new MfaDialog(mfaConfig);
if (await dialog.ShowDialogAsync() is DialogResult.OK)
return (dialog.SelectedName, dialog.SelectedValue);
return (null, null);
});
public async Task<(string email, string password)> GetLoginAsync()
{
var dialog = new LoginCallbackDialog(_account);
if (await dialog.ShowDialogAsync() is DialogResult.OK)
return (_account.AccountId, dialog.Password);
return (null, null);
}
=> await Dispatcher.UIThread.InvokeAsync(async () =>
{
var dialog = new LoginCallbackDialog(_account);
if (await dialog.ShowDialogAsync() is DialogResult.OK)
return (_account.AccountId, dialog.Password);
return (null, null);
});
public async Task ShowApprovalNeededAsync()
{
var dialog = new ApprovalNeededDialog();
await dialog.ShowDialogAsync();
}
=> await Dispatcher.UIThread.InvokeAsync(async () =>
{
var dialog = new ApprovalNeededDialog();
await dialog.ShowDialogAsync();
});
}
}

View File

@ -1,5 +1,6 @@
using AudibleApi;
using AudibleUtilities;
using Avalonia.Threading;
using LibationFileManager;
using LibationUiBase.Forms;
using System;
@ -21,6 +22,9 @@ namespace LibationAvalonia.Dialogs.Login
}
public async Task<ChoiceOut?> StartAsync(ChoiceIn choiceIn)
=> await Dispatcher.UIThread.InvokeAsync(() => StartAsyncInternal(choiceIn));
private async Task<ChoiceOut?> StartAsyncInternal(ChoiceIn choiceIn)
{
if (Configuration.IsWindows && Environment.OSVersion.Version.Major >= 10)
{

View File

@ -22,7 +22,7 @@ namespace LibationAvalonia.Views
public MainWindow()
{
DataContext = new MainVM(this);
ApiExtended.LoginChoiceFactory = account => new Dialogs.Login.AvaloniaLoginChoiceEager(account);
ApiExtended.LoginChoiceFactory = account => Dispatcher.UIThread.Invoke(() => new Dialogs.Login.AvaloniaLoginChoiceEager(account));
AudibleApiStorage.LoadError += AudibleApiStorage_LoadError;
InitializeComponent();

View File

@ -5,18 +5,19 @@ namespace LibationWinForms.Dialogs.Login
{
public abstract class WinformLoginBase
{
private readonly IWin32Window _owner;
protected WinformLoginBase(IWin32Window owner)
protected Control Owner { get; }
protected WinformLoginBase(Control owner)
{
_owner = owner;
Owner = owner;
}
/// <returns>True if ShowDialog's DialogResult == OK</returns>
protected bool ShowDialog(Form dialog)
{
var result = dialog.ShowDialog(_owner);
Serilog.Log.Logger.Debug("{@DebugInfo}", new { DialogResult = result });
return result == DialogResult.OK;
}
=> Owner.Invoke(() =>
{
var result = dialog.ShowDialog(Owner);
Serilog.Log.Logger.Debug("{@DebugInfo}", new { DialogResult = result });
return result == DialogResult.OK;
});
}
}

View File

@ -13,48 +13,53 @@ namespace LibationWinForms.Login
public string DeviceName { get; } = "Libation";
public WinformLoginCallback(Account account, IWin32Window owner) : base(owner)
public WinformLoginCallback(Account account, Control owner) : base(owner)
{
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
}
public Task<string> Get2faCodeAsync(string prompt)
{
using var dialog = new _2faCodeDialog(prompt);
if (ShowDialog(dialog))
return Task.FromResult(dialog.Code);
return Task.FromResult<string>(null);
}
=> Owner.Invoke(() =>
{
using var dialog = new _2faCodeDialog(prompt);
if (ShowDialog(dialog))
return Task.FromResult(dialog.Code);
return Task.FromResult<string>(null);
});
public Task<(string password, string guess)> GetCaptchaAnswerAsync(string password, byte[] captchaImage)
{
using var dialog = new CaptchaDialog(password, captchaImage);
if (ShowDialog(dialog))
return Task.FromResult((dialog.Password, dialog.Answer));
return Task.FromResult<(string, string)>((null,null));
}
=> Owner.Invoke(() =>
{
using var dialog = new CaptchaDialog(password, captchaImage);
if (ShowDialog(dialog))
return Task.FromResult((dialog.Password, dialog.Answer));
return Task.FromResult<(string, string)>((null, null));
});
public Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)
{
using var dialog = new MfaDialog(mfaConfig);
if (ShowDialog(dialog))
return Task.FromResult((dialog.SelectedName, dialog.SelectedValue));
return Task.FromResult<(string, string)>((null, null));
}
=> Owner.Invoke(() =>
{
using var dialog = new MfaDialog(mfaConfig);
if (ShowDialog(dialog))
return Task.FromResult((dialog.SelectedName, dialog.SelectedValue));
return Task.FromResult<(string, string)>((null, null));
});
public Task<(string email, string password)> GetLoginAsync()
{
using var dialog = new LoginCallbackDialog(_account);
if (ShowDialog(dialog))
return Task.FromResult((dialog.Email, dialog.Password));
return Task.FromResult<(string, string)>((null, null));
}
=> Owner.Invoke(() =>
{
using var dialog = new LoginCallbackDialog(_account);
if (ShowDialog(dialog))
return Task.FromResult((dialog.Email, dialog.Password));
return Task.FromResult<(string, string)>((null, null));
});
public Task ShowApprovalNeededAsync()
{
using var dialog = new ApprovalNeededDialog();
ShowDialog(dialog);
return Task.CompletedTask;
}
=> Owner.Invoke(() =>
{
using var dialog = new ApprovalNeededDialog();
ShowDialog(dialog);
return Task.CompletedTask;
});
}
}

View File

@ -13,13 +13,16 @@ namespace LibationWinForms.Login
private Account _account { get; }
public WinformLoginChoiceEager(Account account, IWin32Window owner) : base(owner)
public WinformLoginChoiceEager(Account account, Control owner) : base(owner)
{
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
LoginCallback = new WinformLoginCallback(_account, owner);
}
public Task<ChoiceOut> StartAsync(ChoiceIn choiceIn)
=> Owner.Invoke(() => StartAsyncInternal(choiceIn));
private Task<ChoiceOut> StartAsyncInternal(ChoiceIn choiceIn)
{
if (Environment.OSVersion.Version.Major >= 10)
{