diff --git a/Source/LibationAvalonia/ViewModels/MainVM.BackupCounts.cs b/Source/LibationAvalonia/ViewModels/MainVM.BackupCounts.cs
index ea34788b..c2a76350 100644
--- a/Source/LibationAvalonia/ViewModels/MainVM.BackupCounts.cs
+++ b/Source/LibationAvalonia/ViewModels/MainVM.BackupCounts.cs
@@ -1,4 +1,5 @@
using ApplicationServices;
+using Avalonia.Threading;
using DataLayer;
using LibationFileManager;
using ReactiveUI;
@@ -77,6 +78,9 @@ partial class MainVM
if (Configuration.Instance.AutoDownloadEpisodes
&& stats.PendingBooks + stats.pdfsNotDownloaded > 0)
- await BackupAllBooksAsync(stats.LibraryBooks);
+ {
+ // RunWorkerCompleted has no SynchronizationContext; queue items require the UI thread.
+ await Dispatcher.UIThread.InvokeAsync(async () => await BackupAllBooksAsync(stats.LibraryBooks));
+ }
}
}
diff --git a/Source/LibationUiBase/ProcessQueue/ProcessQueueViewModel.cs b/Source/LibationUiBase/ProcessQueue/ProcessQueueViewModel.cs
index b727bae8..9fc2ac83 100644
--- a/Source/LibationUiBase/ProcessQueue/ProcessQueueViewModel.cs
+++ b/Source/LibationUiBase/ProcessQueue/ProcessQueueViewModel.cs
@@ -255,7 +255,16 @@ public class ProcessQueueViewModel : ReactiveObject
&& entry.Status is ProcessBookStatus.Completed
&& Queue.RemoveCompleted(entry);
+ ///
+ /// ProcessBookViewModel requires a captured UI SynchronizationContext. Callers may resume on a
+ /// thread-pool thread after await (e.g. auto-download after BackgroundWorker).
+ ///
+ private void RunOnQueueUiThread(Action action) => Invoke(action);
+
private void AddDownloadPdf(IList entries, Configuration config)
+ => RunOnQueueUiThread(() => addDownloadPdfCore(entries, config));
+
+ private void addDownloadPdfCore(IList entries, Configuration config)
{
var procs = entries.Where(e => !IsBookInQueue(e)).Select(Create).ToArray();
Serilog.Log.Logger.Information("Queueing {count} books for PDF-only download", procs.Length);
@@ -266,6 +275,9 @@ public class ProcessQueueViewModel : ReactiveObject
}
private void AddDownloadDecrypt(IList entries, Configuration config)
+ => RunOnQueueUiThread(() => addDownloadDecryptCore(entries, config));
+
+ private void addDownloadDecryptCore(IList entries, Configuration config)
{
var procs = entries.Where(e => !IsBookInQueue(e)).Select(Create).ToArray();
Serilog.Log.Logger.Information("Queueing {count} books ofr download/decrypt", procs.Length);
@@ -276,6 +288,9 @@ public class ProcessQueueViewModel : ReactiveObject
}
private void AddConvertMp3(IList entries, Configuration config)
+ => RunOnQueueUiThread(() => addConvertMp3Core(entries, config));
+
+ private void addConvertMp3Core(IList entries, Configuration config)
{
var procs = entries.Where(e => !IsBookInQueue(e)).Select(Create).ToArray();
Serilog.Log.Logger.Information("Queueing {count} books for mp3 conversion", procs.Length);
diff --git a/Source/LibationWinForms/Form1._NonUI.cs b/Source/LibationWinForms/Form1._NonUI.cs
index e8e623c5..a5708735 100644
--- a/Source/LibationWinForms/Form1._NonUI.cs
+++ b/Source/LibationWinForms/Form1._NonUI.cs
@@ -1,5 +1,6 @@
using ApplicationServices;
using AudibleUtilities;
+using Dinah.Core.Threading;
using Dinah.Core.WindowsDesktop.Drawing;
using FileManager;
using LibationFileManager;
@@ -26,14 +27,19 @@ public partial class Form1
// wire-up event to automatically download after scan.
// winforms only. this should NOT be allowed in cli
- updateCountsBw.RunWorkerCompleted += async (object? sender, System.ComponentModel.RunWorkerCompletedEventArgs e) =>
- {
- if (!Configuration.Instance.AutoDownloadEpisodes || e.Result is not LibraryCommands.LibraryStats libraryStats)
- return;
+ updateCountsBw.RunWorkerCompleted += (_, e) => tryAutoDownloadAfterCounts(e);
+ }
- if ((libraryStats.PendingBooks + libraryStats.pdfsNotDownloaded) > 0)
- await BackupAllBooksAsync(libraryStats.LibraryBooks);
- };
+ private void tryAutoDownloadAfterCounts(System.ComponentModel.RunWorkerCompletedEventArgs e)
+ {
+ if (!Configuration.Instance.AutoDownloadEpisodes || e.Result is not LibraryCommands.LibraryStats libraryStats)
+ return;
+
+ if (libraryStats.PendingBooks + libraryStats.pdfsNotDownloaded <= 0)
+ return;
+
+ // RunWorkerCompleted has no SynchronizationContext; queue items require the UI thread.
+ this.UIThreadAsync(() => _ = BackupAllBooksAsync(libraryStats.LibraryBooks));
}
private static object? LoadResourceImage(string resourceName)