using ApplicationServices; using DataLayer; using Dinah.Core.Threading; using System.Collections.Generic; namespace LibationWinForms; public partial class Form1 { private readonly System.ComponentModel.BackgroundWorker updateCountsBw = new(); protected void Configure_BackupCounts() { // init formattable beginBookBackupsToolStripMenuItem.Format(0); beginPdfBackupsToolStripMenuItem.Format(0); LibraryCommands.LibrarySizeChanged += setBackupCounts; LibraryCommands.LibrarySizeChanged += (_, _) => refreshBooksInTrash(); //Pass null to the runner to get the whole library. LibraryCommands.BookUserDefinedItemCommitted += (_, _) => setBackupCounts(null, null); trashBinLbl.Text = ""; trashBinLbl.Visible = false; trashBinLbl.ToolTipText = LibationUiBase.TrashBinUi.StatusToolTip; trashBinLbl.SetLinkLabelColors(); refreshBooksInTrash(); updateCountsBw.DoWork += UpdateCountsBw_DoWork; // Register the error logger first so a failed count is logged exactly once, before the // display handlers below run and (safely) treat the stats as unavailable. updateCountsBw.RunWorkerCompleted += logBackupCountsError; updateCountsBw.RunWorkerCompleted += exportMenuEnable; updateCountsBw.RunWorkerCompleted += updateBottomStats; updateCountsBw.RunWorkerCompleted += update_BeginBookBackups_menuItem; updateCountsBw.RunWorkerCompleted += udpate_BeginPdfOnlyBackups_menuItem; } /// /// Re-read the trash count and show it only when there is something in there. Kept off /// on purpose: that also runs against the visible subset on /// every filter change, where a database round trip would be wasted work. /// private async void refreshBooksInTrash() { int booksInTrash; try { booksInTrash = await System.Threading.Tasks.Task.Run(DbContexts.GetTrashedBookCount); } catch (System.Exception ex) { //A stale count must not take down the window that displays it. Serilog.Log.Logger.Error(ex, "Failed to count books in the trash"); return; } statusStrip1.UIThreadAsync(() => { trashBinLbl.Text = LibationUiBase.TrashBinUi.StatusText(booksInTrash); trashBinLbl.Visible = LibationUiBase.TrashBinUi.ShowStatus(booksInTrash); openTrashBinToolStripMenuItem.Text = LibationUiBase.TrashBinUi.MenuText(booksInTrash); setBooksInTrash(booksInTrash); }); } /// /// Safely extract the completed . Returns null when the /// background count was cancelled or faulted. Reading /// directly rethrows any , which would otherwise /// crash the app; callers must degrade gracefully on null. /// private static LibraryCommands.LibraryStats? getLibraryStats(System.ComponentModel.RunWorkerCompletedEventArgs e) => e.Cancelled || e.Error is not null ? null : e.Result as LibraryCommands.LibraryStats; private static void logBackupCountsError(object? _, System.ComponentModel.RunWorkerCompletedEventArgs e) { if (e.Error is not null) Serilog.Log.Logger.Error(e.Error, "Failed to update backup counts"); } private bool runBackupCountsAgain; private void setBackupCounts(object? _, List? libraryBooks) { runBackupCountsAgain = true; if (!updateCountsBw.IsBusy) updateCountsBw.RunWorkerAsync(libraryBooks); } private void UpdateCountsBw_DoWork(object? sender, System.ComponentModel.DoWorkEventArgs e) { while (runBackupCountsAgain) { runBackupCountsAgain = false; e.Result = LibraryCommands.GetCounts(e.Argument as IEnumerable); } } private void exportMenuEnable(object? _, System.ComponentModel.RunWorkerCompletedEventArgs e) { var libraryStats = getLibraryStats(e); Invoke(() => exportLibraryToolStripMenuItem.Enabled = libraryStats?.HasBookResults is true); } private void updateBottomStats(object? _, System.ComponentModel.RunWorkerCompletedEventArgs e) { var libraryStats = getLibraryStats(e); statusStrip1.UIThreadAsync(() => backupsCountsLbl.Text = libraryStats?.StatusString ?? "ERROR GETTING STATUS"); //Whether the library is empty is only known once these counts have been taken, which is also what //keeps the getting-started panel off screen while a full library is still loading. if (libraryStats is not null) this.UIThreadAsync(() => setLibraryIsEmpty(!libraryStats.HasBookResults)); } // update 'begin book and pdf backups' menu item private void update_BeginBookBackups_menuItem(object? _, System.ComponentModel.RunWorkerCompletedEventArgs e) { var libraryStats = getLibraryStats(e); var menuItemText = libraryStats?.HasPendingBooks is true ? $"{libraryStats.PendingBooks} remaining" : "All books have been liberated"; menuStrip1.UIThreadAsync(() => { beginBookBackupsToolStripMenuItem.Format(menuItemText); beginBookBackupsToolStripMenuItem.Enabled = libraryStats?.HasPendingBooks is true; }); } // update 'begin pdf only backups' menu item private void udpate_BeginPdfOnlyBackups_menuItem(object? _, System.ComponentModel.RunWorkerCompletedEventArgs e) { var libraryStats = getLibraryStats(e); var menuItemText = libraryStats?.pdfsNotDownloaded > 0 ? $"{libraryStats.pdfsNotDownloaded} remaining" : "All PDFs have been downloaded"; menuStrip1.UIThreadAsync(() => { beginPdfBackupsToolStripMenuItem.Format(menuItemText); beginPdfBackupsToolStripMenuItem.Enabled = libraryStats?.pdfsNotDownloaded > 0; }); } }