using ApplicationServices; using DataLayer; using Dinah.Core; using Dinah.Core.Threading; using System; using System.Collections.Generic; using System.ComponentModel; namespace LibationUiBase.GridView { public interface IEntryStatus { static abstract EntryStatus Create(LibraryBook libraryBook); } //This Class holds all book entry status info to help the grid properly render entries. //The reason this info is in here instead of GridEntry is because all of this info is needed //for the "Liberate" column's display and sorting functions. public abstract class EntryStatus : SynchronizeInvoker, IComparable, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public LiberatedStatus? PdfStatus => LibraryCommands.Pdf_Status(Book); public LiberatedStatus BookStatus { get { if (IsSeries) return default; if ((DateTime.Now - lastBookUpdate).TotalSeconds > 2) { //Cache the BookStatus so AudibleFileStorage.AaxcExists isn't //called multiple times per book while sorting the solumn. bookStatus = LibraryCommands.Liberated_Status(Book); lastBookUpdate = DateTime.Now; } return bookStatus; } } public bool Expanded { get => expanded; set { if (value != expanded) { expanded = value; Invalidate(nameof(Expanded), nameof(ButtonImage)); } } } public bool IsSeries { get; } public bool IsEpisode { get; } public bool IsBook => !IsSeries && !IsEpisode; public bool IsUnavailable => !IsSeries & isAbsent & (BookStatus is not LiberatedStatus.Liberated || PdfStatus is not null and not LiberatedStatus.Liberated); public double Opacity => !IsSeries && Book.UserDefinedItem.Tags.ContainsInsensitive("hidden") ? 0.4 : 1; public abstract object BackgroundBrush { get; } public object ButtonImage => GetLiberateIcon(); public string ToolTip => GetTooltip(); private Book Book { get; } private DateTime lastBookUpdate; private LiberatedStatus bookStatus; private bool expanded; private readonly bool isAbsent; private static readonly Dictionary iconCache = new(); protected EntryStatus(LibraryBook libraryBook) { Book = ArgumentValidator.EnsureNotNull(libraryBook, nameof(libraryBook)).Book; isAbsent = libraryBook.AbsentFromLastScan is true; IsEpisode = Book.ContentType is ContentType.Episode; IsSeries = Book.ContentType is ContentType.Parent; } internal protected abstract object LoadImage(byte[] picture); protected abstract object GetResourceImage(string rescName); public void RaisePropertyChanged(PropertyChangedEventArgs args) => this.UIThreadSync(() => PropertyChanged?.Invoke(this, args)); public void RaisePropertyChanged(string propertyName) => RaisePropertyChanged(new PropertyChangedEventArgs(propertyName)); /// Refresh BookStatus (so partial download files are checked again in the filesystem) and raise PropertyChanged for property names. public void Invalidate(params string[] properties) { lastBookUpdate = default; foreach (var property in properties) RaisePropertyChanged(property); } /// Defines the Liberate column's sorting behavior public int CompareTo(object obj) { if (obj is not EntryStatus second) return -1; if (IsSeries && !second.IsSeries) return -1; else if (!IsSeries && second.IsSeries) return 1; else if (IsSeries && second.IsSeries) return 0; else if (IsUnavailable && !second.IsUnavailable) return 1; else if (!IsUnavailable && second.IsUnavailable) return -1; else if (BookStatus == LiberatedStatus.Liberated && second.BookStatus != LiberatedStatus.Liberated) return -1; else if (BookStatus != LiberatedStatus.Liberated && second.BookStatus == LiberatedStatus.Liberated) return 1; else return BookStatus.CompareTo(second.BookStatus); } private object GetLiberateIcon() { if (IsSeries) return Expanded ? GetAndCacheResource("minus") : GetAndCacheResource("plus"); if (BookStatus == LiberatedStatus.Error) return GetAndCacheResource("error"); string image_lib = BookStatus switch { LiberatedStatus.Liberated => "green", LiberatedStatus.PartialDownload => "yellow", LiberatedStatus.NotLiberated => "red", _ => throw new Exception("Unexpected liberation state") }; string image_pdf = PdfStatus switch { LiberatedStatus.Liberated => "_pdf_yes", LiberatedStatus.NotLiberated => "_pdf_no", LiberatedStatus.Error => "_pdf_no", null => "", _ => throw new Exception("Unexpected PDF state") }; return GetAndCacheResource($"liberate_{image_lib}{image_pdf}"); } private string GetTooltip() { if (IsSeries) return Expanded ? "Click to Collpase" : "Click to Expand"; if (IsUnavailable) return "This book cannot be downloaded\nbecause it wasn't found during\nthe most recent library scan"; if (BookStatus == LiberatedStatus.Error) return "Book downloaded ERROR"; string libState = BookStatus switch { LiberatedStatus.Liberated => "Liberated", LiberatedStatus.PartialDownload => "File has been at least\r\npartially downloaded", LiberatedStatus.NotLiberated => "Book NOT downloaded", _ => throw new Exception("Unexpected liberation state") }; string pdfState = PdfStatus switch { LiberatedStatus.Liberated => "\r\nPDF downloaded", LiberatedStatus.NotLiberated => "\r\nPDF NOT downloaded", LiberatedStatus.Error => "\r\nPDF downloaded ERROR", null => "", _ => throw new Exception("Unexpected PDF state") }; var mouseoverText = libState + pdfState; if (BookStatus == LiberatedStatus.NotLiberated || BookStatus == LiberatedStatus.PartialDownload || PdfStatus == LiberatedStatus.NotLiberated) mouseoverText += "\r\nClick to complete"; return mouseoverText; } private object GetAndCacheResource(string rescName) { if (!iconCache.ContainsKey(rescName)) iconCache[rescName] = GetResourceImage(rescName); return iconCache[rescName]; } } }