using ApplicationServices;
using Avalonia.Threading;
using DataLayer;
using LibationFileManager;
using LibationUiBase;
using ReactiveUI;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace LibationAvalonia.ViewModels;
partial class MainVM
{
private readonly System.ComponentModel.BackgroundWorker updateCountsBw = new();
/// The "Begin Book and PDF Backup" menu item header text
public string BookBackupsToolStripText { get; private set; } = "Begin Book and PDF Backups: 0";
/// The "Begin PDF Only Backup" menu item header text
public string PdfBackupsToolStripText { get; private set; } = "Begin PDF Only Backups: 0";
/// How many books are in the trash, where nothing else on screen would reveal them
public int BooksInTrash
{
get => field;
private set
{
this.RaiseAndSetIfChanged(ref field, value);
this.RaisePropertyChanged(nameof(TrashBinMenuText));
this.RaisePropertyChanged(nameof(TrashBinStatusText));
this.RaisePropertyChanged(nameof(TrashBinStatusVisible));
RaiseGettingStartedChanged();
}
}
public string TrashBinMenuText => TrashBinUi.MenuText(BooksInTrash);
public string TrashBinStatusText => TrashBinUi.StatusText(BooksInTrash);
public bool TrashBinStatusVisible => TrashBinUi.ShowStatus(BooksInTrash);
public string TrashBinStatusToolTip => TrashBinUi.StatusToolTip;
///
/// Re-read the trash count. Kept off on purpose: that also runs
/// against the visible subset on every filter change, where a database round trip would be wasted work.
///
public async Task RefreshBooksInTrashAsync()
{
try
{
var count = await Task.Run(DbContexts.GetTrashedBookCount);
await Dispatcher.UIThread.InvokeAsync(() => BooksInTrash = count);
}
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");
}
}
/// The user's library statistics
public LibraryCommands.LibraryStats? LibraryStats
{
get => field;
set
{
this.RaiseAndSetIfChanged(ref field, value);
BookBackupsToolStripText
= LibraryStats?.HasPendingBooks ?? false
? "Begin " + menufyText($"Book and PDF Backups: {LibraryStats.PendingBooks} remaining")
: "All books have been liberated";
PdfBackupsToolStripText
= LibraryStats?.pdfsNotDownloaded > 0
? "Begin " + menufyText($"PDF Only Backups: {LibraryStats.pdfsNotDownloaded} remaining")
: "All PDFs have been downloaded";
this.RaisePropertyChanged(nameof(BookBackupsToolStripText));
this.RaisePropertyChanged(nameof(PdfBackupsToolStripText));
//Whether the library is empty is only known once these counts have been taken.
RaiseGettingStartedChanged();
}
}
private void Configure_BackupCounts()
{
//Pass null to the setup count to get the whole library.
LibraryCommands.BookUserDefinedItemCommitted += async (_, _)
=> await SetBackupCountsAsync(null);
updateCountsBw.DoWork += UpdateCountsBw_DoWork;
updateCountsBw.RunWorkerCompleted += UpdateCountsBw_CompletedAsync;
}
private bool runBackupCountsAgain;
public async Task SetBackupCountsAsync(IEnumerable? 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 async void UpdateCountsBw_CompletedAsync(object? sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
// Reading e.Result rethrows any exception from UpdateCountsBw_DoWork. Because this is an
// async void handler with no SynchronizationContext, that would become an unhandled
// exception and crash the app. Check e.Error/e.Cancelled first and degrade gracefully.
if (e.Cancelled)
return;
if (e.Error is not null)
{
Serilog.Log.Logger.Error(e.Error, "Failed to update backup counts");
return;
}
if (e.Result is not LibraryCommands.LibraryStats stats)
return;
LibraryStats = stats;
if (Configuration.Instance.AutoDownloadEpisodes
&& stats.PendingBooks + stats.pdfsNotDownloaded > 0)
{
// RunWorkerCompleted has no SynchronizationContext; queue items require the UI thread.
await Dispatcher.UIThread.InvokeAsync(async () => await BackupAllBooksAsync(stats.LibraryBooks, notifyIfNothingQueued: false));
}
}
}