Merge pull request #90 from Mbucari/master
Fully implemented the MVVM pattern
This commit is contained in:
commit
343c3b62d6
@ -11,13 +11,6 @@ using Serilog;
|
|||||||
|
|
||||||
namespace ApplicationServices
|
namespace ApplicationServices
|
||||||
{
|
{
|
||||||
// subtly different from DataLayer.LiberatedStatus
|
|
||||||
// - DataLayer.LiberatedStatus: has no concept of partially downloaded
|
|
||||||
// - ApplicationServices.LiberatedState: has no concept of Error/skipped
|
|
||||||
public enum LiberatedState { NotDownloaded, PartialDownload, Liberated }
|
|
||||||
|
|
||||||
public enum PdfState { NoPdf, Downloaded, NotDownloaded }
|
|
||||||
|
|
||||||
public static class LibraryCommands
|
public static class LibraryCommands
|
||||||
{
|
{
|
||||||
private static LibraryOptions.ResponseGroupOptions LibraryResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS;
|
private static LibraryOptions.ResponseGroupOptions LibraryResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS;
|
||||||
@ -162,94 +155,24 @@ namespace ApplicationServices
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Update book details
|
#region Update book details
|
||||||
public static int UpdateUserDefinedItem(Book book, string newTags, LiberatedStatus bookStatus, LiberatedStatus? pdfStatus)
|
|
||||||
|
public static int UpdateUserDefinedItem(Book book)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var context = DbContexts.GetContext();
|
using var context = DbContexts.GetContext();
|
||||||
|
|
||||||
var udi = book.UserDefinedItem;
|
|
||||||
|
|
||||||
var tagsChanged = udi.Tags != newTags;
|
|
||||||
var bookStatusChanged = udi.BookStatus != bookStatus;
|
|
||||||
var pdfStatusChanged = udi.PdfStatus != pdfStatus;
|
|
||||||
|
|
||||||
if (!tagsChanged && !bookStatusChanged && !pdfStatusChanged)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
udi.Tags = newTags;
|
|
||||||
udi.BookStatus = bookStatus;
|
|
||||||
udi.PdfStatus = pdfStatus;
|
|
||||||
|
|
||||||
// Attach() NoTracking entities before SaveChanges()
|
// Attach() NoTracking entities before SaveChanges()
|
||||||
context.Attach(udi).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
context.Attach(book.UserDefinedItem).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
||||||
var qtyChanges = context.SaveChanges();
|
var qtyChanges = context.SaveChanges();
|
||||||
|
if (qtyChanges > 0)
|
||||||
if (qtyChanges == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (tagsChanged)
|
|
||||||
SearchEngineCommands.UpdateBookTags(book);
|
|
||||||
if (bookStatusChanged || pdfStatusChanged)
|
|
||||||
SearchEngineCommands.UpdateLiberatedStatus(book);
|
SearchEngineCommands.UpdateLiberatedStatus(book);
|
||||||
|
|
||||||
return qtyChanges;
|
return qtyChanges;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Logger.Error(ex, "Error updating tags");
|
Log.Logger.Error(ex, $"Error updating {nameof(book.UserDefinedItem)}");
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int UpdateBook(LibraryBook libraryBook, LiberatedStatus liberatedStatus)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var context = DbContexts.GetContext();
|
|
||||||
|
|
||||||
var udi = libraryBook.Book.UserDefinedItem;
|
|
||||||
|
|
||||||
if (udi.BookStatus == liberatedStatus)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Attach() NoTracking entities before SaveChanges()
|
|
||||||
udi.BookStatus = liberatedStatus;
|
|
||||||
context.Attach(udi).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
|
||||||
var qtyChanges = context.SaveChanges();
|
|
||||||
if (qtyChanges > 0)
|
|
||||||
SearchEngineCommands.UpdateLiberatedStatus(libraryBook.Book);
|
|
||||||
|
|
||||||
return qtyChanges;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Logger.Error(ex, "Error updating tags");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int UpdatePdf(LibraryBook libraryBook, LiberatedStatus liberatedStatus)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var context = DbContexts.GetContext();
|
|
||||||
|
|
||||||
var udi = libraryBook.Book.UserDefinedItem;
|
|
||||||
|
|
||||||
if (udi.PdfStatus == liberatedStatus)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Attach() NoTracking entities before SaveChanges()
|
|
||||||
udi.PdfStatus = liberatedStatus;
|
|
||||||
context.Attach(udi).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
|
||||||
var qtyChanges = context.SaveChanges();
|
|
||||||
|
|
||||||
return qtyChanges;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Logger.Error(ex, "Error updating tags");
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -257,15 +180,15 @@ namespace ApplicationServices
|
|||||||
|
|
||||||
// below are queries, not commands. maybe I should make a LibraryQueries. except there's already one of those...
|
// below are queries, not commands. maybe I should make a LibraryQueries. except there's already one of those...
|
||||||
|
|
||||||
public static LiberatedState Liberated_Status(Book book)
|
public static LiberatedStatus Liberated_Status(Book book)
|
||||||
=> book.Audio_Exists ? LiberatedState.Liberated
|
=> book.Audio_Exists ? LiberatedStatus.Liberated
|
||||||
: FileManager.AudibleFileStorage.AaxcExists(book.AudibleProductId) ? LiberatedState.PartialDownload
|
: FileManager.AudibleFileStorage.AaxcExists(book.AudibleProductId) ? LiberatedStatus.PartialDownload
|
||||||
: LiberatedState.NotDownloaded;
|
: LiberatedStatus.NotLiberated;
|
||||||
|
|
||||||
public static PdfState Pdf_Status(Book book)
|
public static LiberatedStatus? Pdf_Status(Book book)
|
||||||
=> !book.Supplements.Any() ? PdfState.NoPdf
|
=> !book.Supplements.Any() ? null
|
||||||
: book.PDF_Exists ? PdfState.Downloaded
|
: book.PDF_Exists ? LiberatedStatus.Liberated
|
||||||
: PdfState.NotDownloaded;
|
: LiberatedStatus.NotLiberated;
|
||||||
|
|
||||||
public record LibraryStats(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress, int pdfsDownloaded, int pdfsNotDownloaded) { }
|
public record LibraryStats(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress, int pdfsDownloaded, int pdfsNotDownloaded) { }
|
||||||
public static LibraryStats GetCounts()
|
public static LibraryStats GetCounts()
|
||||||
@ -276,9 +199,9 @@ namespace ApplicationServices
|
|||||||
.AsParallel()
|
.AsParallel()
|
||||||
.Select(lb => Liberated_Status(lb.Book))
|
.Select(lb => Liberated_Status(lb.Book))
|
||||||
.ToList();
|
.ToList();
|
||||||
var booksFullyBackedUp = results.Count(r => r == LiberatedState.Liberated);
|
var booksFullyBackedUp = results.Count(r => r == LiberatedStatus.Liberated);
|
||||||
var booksDownloadedOnly = results.Count(r => r == LiberatedState.PartialDownload);
|
var booksDownloadedOnly = results.Count(r => r == LiberatedStatus.PartialDownload);
|
||||||
var booksNoProgress = results.Count(r => r == LiberatedState.NotDownloaded);
|
var booksNoProgress = results.Count(r => r == LiberatedStatus.NotLiberated);
|
||||||
|
|
||||||
Log.Logger.Information("Book counts. {@DebugInfo}", new { total = results.Count, booksFullyBackedUp, booksDownloadedOnly, booksNoProgress });
|
Log.Logger.Information("Book counts. {@DebugInfo}", new { total = results.Count, booksFullyBackedUp, booksDownloadedOnly, booksNoProgress });
|
||||||
|
|
||||||
@ -287,8 +210,8 @@ namespace ApplicationServices
|
|||||||
.Where(lb => lb.Book.Supplements.Any())
|
.Where(lb => lb.Book.Supplements.Any())
|
||||||
.Select(lb => Pdf_Status(lb.Book))
|
.Select(lb => Pdf_Status(lb.Book))
|
||||||
.ToList();
|
.ToList();
|
||||||
var pdfsDownloaded = boolResults.Count(r => r == PdfState.Downloaded);
|
var pdfsDownloaded = boolResults.Count(r => r == LiberatedStatus.Liberated);
|
||||||
var pdfsNotDownloaded = boolResults.Count(r => r == PdfState.NotDownloaded);
|
var pdfsNotDownloaded = boolResults.Count(r => r == LiberatedStatus.NotLiberated);
|
||||||
|
|
||||||
Log.Logger.Information("PDF counts. {@DebugInfo}", new { total = boolResults.Count, pdfsDownloaded, pdfsNotDownloaded });
|
Log.Logger.Information("PDF counts. {@DebugInfo}", new { total = boolResults.Count, pdfsDownloaded, pdfsNotDownloaded });
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,11 @@ namespace DataLayer
|
|||||||
NotLiberated = 0,
|
NotLiberated = 0,
|
||||||
Liberated = 1,
|
Liberated = 1,
|
||||||
/// <summary>Error occurred during liberation. Don't retry</summary>
|
/// <summary>Error occurred during liberation. Don't retry</summary>
|
||||||
Error = 2
|
Error = 2,
|
||||||
|
|
||||||
|
/// <summary>Application-state only. Not a valid persistence state.</summary>
|
||||||
|
PartialDownload = 0x1000
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UserDefinedItem
|
public class UserDefinedItem
|
||||||
@ -38,7 +42,15 @@ namespace DataLayer
|
|||||||
public string Tags
|
public string Tags
|
||||||
{
|
{
|
||||||
get => _tags;
|
get => _tags;
|
||||||
set => _tags = sanitize(value);
|
set
|
||||||
|
{
|
||||||
|
var newTags = sanitize(value);
|
||||||
|
if (_tags != newTags)
|
||||||
|
{
|
||||||
|
_tags = newTags;
|
||||||
|
ItemChanged?.Invoke(this, nameof(Tags));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> TagsEnumerated => Tags == "" ? new string[0] : Tags.Split(null as char[], StringSplitOptions.RemoveEmptyEntries);
|
public IEnumerable<string> TagsEnumerated => Tags == "" ? new string[0] : Tags.Split(null as char[], StringSplitOptions.RemoveEmptyEntries);
|
||||||
@ -95,10 +107,38 @@ namespace DataLayer
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region LiberatedStatuses
|
#region LiberatedStatuses
|
||||||
public LiberatedStatus BookStatus { get; set; }
|
|
||||||
public LiberatedStatus? PdfStatus { get; set; }
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
private LiberatedStatus _bookStatus;
|
||||||
|
private LiberatedStatus? _pdfStatus;
|
||||||
|
public LiberatedStatus BookStatus
|
||||||
|
{
|
||||||
|
get => _bookStatus;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_bookStatus != value)
|
||||||
|
{
|
||||||
|
_bookStatus = value;
|
||||||
|
ItemChanged?.Invoke(this, nameof(BookStatus));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public LiberatedStatus? PdfStatus
|
||||||
|
{
|
||||||
|
get => _pdfStatus;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_pdfStatus != value)
|
||||||
|
{
|
||||||
|
_pdfStatus = value;
|
||||||
|
ItemChanged?.Invoke(this, nameof(PdfStatus));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when <see cref="Tags"/>, <see cref="BookStatus"/>, or <see cref="PdfStatus"/> values change.
|
||||||
|
/// </summary>
|
||||||
|
public static event EventHandler<string> ItemChanged;
|
||||||
public override string ToString() => $"{Book} {Rating} {Tags}";
|
public override string ToString() => $"{Book} {Rating} {Tags}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,22 +16,22 @@ namespace FileLiberator
|
|||||||
public class DownloadDecryptBook : IAudioDecodable
|
public class DownloadDecryptBook : IAudioDecodable
|
||||||
{
|
{
|
||||||
|
|
||||||
private AaxcDownloadConverter aaxcDownloader;
|
private AaxcDownloadConverter aaxcDownloader;
|
||||||
|
|
||||||
public event EventHandler<TimeSpan> StreamingTimeRemaining;
|
public event EventHandler<TimeSpan> StreamingTimeRemaining;
|
||||||
public event EventHandler<Action<byte[]>> RequestCoverArt;
|
public event EventHandler<Action<byte[]>> RequestCoverArt;
|
||||||
public event EventHandler<string> TitleDiscovered;
|
public event EventHandler<string> TitleDiscovered;
|
||||||
public event EventHandler<string> AuthorsDiscovered;
|
public event EventHandler<string> AuthorsDiscovered;
|
||||||
public event EventHandler<string> NarratorsDiscovered;
|
public event EventHandler<string> NarratorsDiscovered;
|
||||||
public event EventHandler<byte[]> CoverImageDiscovered;
|
public event EventHandler<byte[]> CoverImageDiscovered;
|
||||||
public event EventHandler<string> StreamingBegin;
|
public event EventHandler<string> StreamingBegin;
|
||||||
public event EventHandler<DownloadProgress> StreamingProgressChanged;
|
public event EventHandler<DownloadProgress> StreamingProgressChanged;
|
||||||
public event EventHandler<string> StreamingCompleted;
|
public event EventHandler<string> StreamingCompleted;
|
||||||
public event EventHandler<LibraryBook> Begin;
|
public event EventHandler<LibraryBook> Begin;
|
||||||
public event EventHandler<string> StatusUpdate;
|
public event EventHandler<string> StatusUpdate;
|
||||||
public event EventHandler<LibraryBook> Completed;
|
public event EventHandler<LibraryBook> Completed;
|
||||||
|
|
||||||
public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
|
public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
|
||||||
{
|
{
|
||||||
Begin?.Invoke(this, libraryBook);
|
Begin?.Invoke(this, libraryBook);
|
||||||
|
|
||||||
@ -47,10 +47,12 @@ namespace FileLiberator
|
|||||||
return new StatusHandler { "Decrypt failed" };
|
return new StatusHandler { "Decrypt failed" };
|
||||||
|
|
||||||
// moves files and returns dest dir
|
// moves files and returns dest dir
|
||||||
_ = moveFilesToBooksDir(libraryBook.Book, outputAudioFilename);
|
var moveResults = MoveFilesToBooksDir(libraryBook.Book, outputAudioFilename);
|
||||||
|
|
||||||
// only need to update if success. if failure, it will remain at 0 == NotLiberated
|
if (!moveResults.movedAudioFile)
|
||||||
ApplicationServices.LibraryCommands.UpdateBook(libraryBook, LiberatedStatus.Liberated);
|
return new StatusHandler { "Cannot find final audio file after decryption" };
|
||||||
|
|
||||||
|
libraryBook.Book.UserDefinedItem.BookStatus = LiberatedStatus.Liberated;
|
||||||
|
|
||||||
return new StatusHandler();
|
return new StatusHandler();
|
||||||
}
|
}
|
||||||
@ -123,7 +125,7 @@ namespace FileLiberator
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void AaxcDownloader_RetrievedCoverArt(object sender, byte[] e)
|
private void AaxcDownloader_RetrievedCoverArt(object sender, byte[] e)
|
||||||
{
|
{
|
||||||
if (e is null && Configuration.Instance.AllowLibationFixup)
|
if (e is null && Configuration.Instance.AllowLibationFixup)
|
||||||
{
|
{
|
||||||
@ -143,7 +145,7 @@ namespace FileLiberator
|
|||||||
NarratorsDiscovered?.Invoke(this, e.Narrator ?? "[unknown]");
|
NarratorsDiscovered?.Invoke(this, e.Narrator ?? "[unknown]");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string moveFilesToBooksDir(Book product, string outputAudioFilename)
|
private static (string destinationDir, bool movedAudioFile) MoveFilesToBooksDir(Book product, string outputAudioFilename)
|
||||||
{
|
{
|
||||||
// create final directory. move each file into it. MOVE AUDIO FILE LAST
|
// create final directory. move each file into it. MOVE AUDIO FILE LAST
|
||||||
// new dir: safetitle_limit50char + " [" + productId + "]"
|
// new dir: safetitle_limit50char + " [" + productId + "]"
|
||||||
@ -158,6 +160,7 @@ namespace FileLiberator
|
|||||||
// audio filename: safetitle_limit50char + " [" + productId + "]." + audio_ext
|
// audio filename: safetitle_limit50char + " [" + productId + "]." + audio_ext
|
||||||
var audioFileName = FileUtility.GetValidFilename(destinationDir, product.Title, musicFileExt, product.AudibleProductId);
|
var audioFileName = FileUtility.GetValidFilename(destinationDir, product.Title, musicFileExt, product.AudibleProductId);
|
||||||
|
|
||||||
|
bool movedAudioFile = false;
|
||||||
foreach (var f in sortedFiles)
|
foreach (var f in sortedFiles)
|
||||||
{
|
{
|
||||||
var dest
|
var dest
|
||||||
@ -170,11 +173,14 @@ namespace FileLiberator
|
|||||||
Cue.UpdateFileName(f, audioFileName);
|
Cue.UpdateFileName(f, audioFileName);
|
||||||
|
|
||||||
File.Move(f.FullName, dest);
|
File.Move(f.FullName, dest);
|
||||||
|
|
||||||
|
movedAudioFile |= AudibleFileStorage.Audio.IsFileTypeMatch(f);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudibleFileStorage.Audio.Refresh();
|
AudibleFileStorage.Audio.Refresh();
|
||||||
|
|
||||||
return destinationDir;
|
return (destinationDir, movedAudioFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<FileInfo> getProductFilesSorted(Book product, string outputAudioFilename)
|
private static List<FileInfo> getProductFilesSorted(Book product, string outputAudioFilename)
|
||||||
@ -197,26 +203,26 @@ namespace FileLiberator
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void validate(LibraryBook libraryBook)
|
private static void validate(LibraryBook libraryBook)
|
||||||
{
|
{
|
||||||
string errorString(string field)
|
string errorString(string field)
|
||||||
=> $"{errorTitle()}\r\nCannot download book. {field} is not known. Try re-importing the account which owns this book.";
|
=> $"{errorTitle()}\r\nCannot download book. {field} is not known. Try re-importing the account which owns this book.";
|
||||||
|
|
||||||
string errorTitle()
|
string errorTitle()
|
||||||
{
|
{
|
||||||
var title
|
var title
|
||||||
= (libraryBook.Book.Title.Length > 53)
|
= (libraryBook.Book.Title.Length > 53)
|
||||||
? $"{libraryBook.Book.Title.Truncate(50)}..."
|
? $"{libraryBook.Book.Title.Truncate(50)}..."
|
||||||
: libraryBook.Book.Title;
|
: libraryBook.Book.Title;
|
||||||
var errorBookTitle = $"{title} [{libraryBook.Book.AudibleProductId}]";
|
var errorBookTitle = $"{title} [{libraryBook.Book.AudibleProductId}]";
|
||||||
return errorBookTitle;
|
return errorBookTitle;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(libraryBook.Account))
|
if (string.IsNullOrWhiteSpace(libraryBook.Account))
|
||||||
throw new Exception(errorString("Account"));
|
throw new Exception(errorString("Account"));
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(libraryBook.Book.Locale))
|
if (string.IsNullOrWhiteSpace(libraryBook.Book.Locale))
|
||||||
throw new Exception(errorString("Locale"));
|
throw new Exception(errorString("Locale"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Validate(LibraryBook libraryBook) => !libraryBook.Book.Audio_Exists;
|
public bool Validate(LibraryBook libraryBook) => !libraryBook.Book.Audio_Exists;
|
||||||
|
|
||||||
|
|||||||
@ -23,8 +23,7 @@ namespace FileLiberator
|
|||||||
await downloadPdfAsync(libraryBook, proposedDownloadFilePath);
|
await downloadPdfAsync(libraryBook, proposedDownloadFilePath);
|
||||||
var result = verifyDownload(libraryBook);
|
var result = verifyDownload(libraryBook);
|
||||||
|
|
||||||
var liberatedStatus = result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated;
|
libraryBook.Book.UserDefinedItem.PdfStatus = result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated;
|
||||||
ApplicationServices.LibraryCommands.UpdatePdf(libraryBook, liberatedStatus);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Dinah.Core\Dinah.Core\Dinah.Core.csproj" />
|
<ProjectReference Include="..\..\Dinah.Core\Dinah.Core\Dinah.Core.csproj" />
|
||||||
<ProjectReference Include="..\AaxDecrypter\AaxDecrypter.csproj" />
|
<ProjectReference Include="..\AaxDecrypter\AaxDecrypter.csproj" />
|
||||||
<ProjectReference Include="..\ApplicationServices\ApplicationServices.csproj" />
|
|
||||||
<ProjectReference Include="..\DataLayer\DataLayer.csproj" />
|
<ProjectReference Include="..\DataLayer\DataLayer.csproj" />
|
||||||
<ProjectReference Include="..\FileManager\FileManager.csproj" />
|
<ProjectReference Include="..\FileManager\FileManager.csproj" />
|
||||||
<ProjectReference Include="..\InternalUtilities\InternalUtilities.csproj" />
|
<ProjectReference Include="..\InternalUtilities\InternalUtilities.csproj" />
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ApplicationServices;
|
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using Dinah.Core.ErrorHandling;
|
using Dinah.Core.ErrorHandling;
|
||||||
@ -18,10 +17,8 @@ namespace FileLiberator
|
|||||||
|
|
||||||
|
|
||||||
// when used in foreach: stateful. deferred execution
|
// when used in foreach: stateful. deferred execution
|
||||||
public static IEnumerable<LibraryBook> GetValidLibraryBooks(this IProcessable processable)
|
public static IEnumerable<LibraryBook> GetValidLibraryBooks(this IProcessable processable, IEnumerable<LibraryBook> library)
|
||||||
=> DbContexts.GetContext()
|
=> library.Where(libraryBook => processable.Validate(libraryBook));
|
||||||
.GetLibrary_Flat_NoTracking()
|
|
||||||
.Where(libraryBook => processable.Validate(libraryBook));
|
|
||||||
|
|
||||||
public static async Task<StatusHandler> ProcessSingleAsync(this IProcessable processable, LibraryBook libraryBook, bool validate)
|
public static async Task<StatusHandler> ProcessSingleAsync(this IProcessable processable, LibraryBook libraryBook, bool validate)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -86,7 +86,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dinah.EntityFrameworkCore.T
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAXClean", "..\AAXClean\AAXClean.csproj", "{94BEB7CC-511D-45AB-9F09-09BE858EE486}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAXClean", "..\AAXClean\AAXClean.csproj", "{94BEB7CC-511D-45AB-9F09-09BE858EE486}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hoopla", "Hoopla\Hoopla.csproj", "{D8F56E5A-3E65-41A6-B7E7-C4515A264B1F}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hoopla", "Hoopla\Hoopla.csproj", "{D8F56E5A-3E65-41A6-B7E7-C4515A264B1F}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@ -222,7 +222,7 @@ Global
|
|||||||
{8BD8E012-F44F-4EE2-A234-D66C14D5FE4B} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
|
{8BD8E012-F44F-4EE2-A234-D66C14D5FE4B} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
|
||||||
{1AE65B61-9C05-4C80-ABFF-48F16E22FDF1} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
|
{1AE65B61-9C05-4C80-ABFF-48F16E22FDF1} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
|
||||||
{59A10DF3-63EC-43F1-A3BF-4000CFA118D2} = {751093DD-5DBA-463E-ADBE-E05FAFB6983E}
|
{59A10DF3-63EC-43F1-A3BF-4000CFA118D2} = {751093DD-5DBA-463E-ADBE-E05FAFB6983E}
|
||||||
{393B5B27-D15C-4F77-9457-FA14BA8F3C73} = {41CDCC73-9B81-49DD-9570-C54406E852AF}
|
{393B5B27-D15C-4F77-9457-FA14BA8F3C73} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
|
||||||
{06882742-27A6-4347-97D9-56162CEC9C11} = {F0CBB7A7-D3FB-41FF-8F47-CF3F6A592249}
|
{06882742-27A6-4347-97D9-56162CEC9C11} = {F0CBB7A7-D3FB-41FF-8F47-CF3F6A592249}
|
||||||
{2E1F5DB4-40CC-4804-A893-5DCE0193E598} = {41CDCC73-9B81-49DD-9570-C54406E852AF}
|
{2E1F5DB4-40CC-4804-A893-5DCE0193E598} = {41CDCC73-9B81-49DD-9570-C54406E852AF}
|
||||||
{9F1AA3DE-962F-469B-82B2-46F93491389B} = {F61184E7-2426-4A13-ACEF-5689928E2CE2}
|
{9F1AA3DE-962F-469B-82B2-46F93491389B} = {F61184E7-2426-4A13-ACEF-5689928E2CE2}
|
||||||
|
|||||||
@ -50,24 +50,24 @@ namespace LibationWinForms.BookLiberation
|
|||||||
|
|
||||||
public static class ProcessorAutomationController
|
public static class ProcessorAutomationController
|
||||||
{
|
{
|
||||||
public static async Task BackupSingleBookAsync(LibraryBook libraryBook, EventHandler<LibraryBook> completedAction = null)
|
public static async Task BackupSingleBookAsync(LibraryBook libraryBook)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Information($"Begin {nameof(BackupSingleBookAsync)} {{@DebugInfo}}", new { libraryBook?.Book?.AudibleProductId });
|
Serilog.Log.Logger.Information($"Begin {nameof(BackupSingleBookAsync)} {{@DebugInfo}}", new { libraryBook?.Book?.AudibleProductId });
|
||||||
|
|
||||||
var logMe = LogMe.RegisterForm();
|
var logMe = LogMe.RegisterForm();
|
||||||
var backupBook = CreateBackupBook(completedAction, logMe);
|
var backupBook = CreateBackupBook(logMe);
|
||||||
|
|
||||||
// continue even if libraryBook is null. we'll display even that in the processing box
|
// continue even if libraryBook is null. we'll display even that in the processing box
|
||||||
await new BackupSingle(logMe, backupBook, libraryBook).RunBackupAsync();
|
await new BackupSingle(logMe, backupBook, libraryBook).RunBackupAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task BackupAllBooksAsync(EventHandler<LibraryBook> completedAction = null)
|
public static async Task BackupAllBooksAsync()
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Information("Begin " + nameof(BackupAllBooksAsync));
|
Serilog.Log.Logger.Information("Begin " + nameof(BackupAllBooksAsync));
|
||||||
|
|
||||||
var automatedBackupsForm = new AutomatedBackupsForm();
|
var automatedBackupsForm = new AutomatedBackupsForm();
|
||||||
var logMe = LogMe.RegisterForm(automatedBackupsForm);
|
var logMe = LogMe.RegisterForm(automatedBackupsForm);
|
||||||
var backupBook = CreateBackupBook(completedAction, logMe);
|
var backupBook = CreateBackupBook(logMe);
|
||||||
|
|
||||||
await new BackupLoop(logMe, backupBook, automatedBackupsForm).RunBackupAsync();
|
await new BackupLoop(logMe, backupBook, automatedBackupsForm).RunBackupAsync();
|
||||||
}
|
}
|
||||||
@ -84,19 +84,19 @@ namespace LibationWinForms.BookLiberation
|
|||||||
await new BackupLoop(logMe, convertBook, automatedBackupsForm).RunBackupAsync();
|
await new BackupLoop(logMe, convertBook, automatedBackupsForm).RunBackupAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task BackupAllPdfsAsync(EventHandler<LibraryBook> completedAction = null)
|
public static async Task BackupAllPdfsAsync()
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Information("Begin " + nameof(BackupAllPdfsAsync));
|
Serilog.Log.Logger.Information("Begin " + nameof(BackupAllPdfsAsync));
|
||||||
|
|
||||||
var automatedBackupsForm = new AutomatedBackupsForm();
|
var automatedBackupsForm = new AutomatedBackupsForm();
|
||||||
var logMe = LogMe.RegisterForm(automatedBackupsForm);
|
var logMe = LogMe.RegisterForm(automatedBackupsForm);
|
||||||
|
|
||||||
var downloadPdf = CreateProcessable<DownloadPdf, PdfDownloadForm>(logMe, completedAction);
|
var downloadPdf = CreateProcessable<DownloadPdf, PdfDownloadForm>(logMe);
|
||||||
|
|
||||||
await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync();
|
await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IProcessable CreateBackupBook(EventHandler<LibraryBook> completedAction, LogMe logMe)
|
private static IProcessable CreateBackupBook(LogMe logMe)
|
||||||
{
|
{
|
||||||
var downloadPdf = CreateProcessable<DownloadPdf, PdfDownloadForm>(logMe);
|
var downloadPdf = CreateProcessable<DownloadPdf, PdfDownloadForm>(logMe);
|
||||||
|
|
||||||
@ -104,7 +104,6 @@ namespace LibationWinForms.BookLiberation
|
|||||||
async void onDownloadDecryptBookCompleted(object sender, LibraryBook e)
|
async void onDownloadDecryptBookCompleted(object sender, LibraryBook e)
|
||||||
{
|
{
|
||||||
await downloadPdf.TryProcessAsync(e);
|
await downloadPdf.TryProcessAsync(e);
|
||||||
completedAction(sender, e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var downloadDecryptBook = CreateProcessable<DownloadDecryptBook, AudioDecryptForm>(logMe, onDownloadDecryptBookCompleted);
|
var downloadDecryptBook = CreateProcessable<DownloadDecryptBook, AudioDecryptForm>(logMe, onDownloadDecryptBookCompleted);
|
||||||
@ -246,7 +245,7 @@ $@" Title: {libraryBook.Book.Title}
|
|||||||
|
|
||||||
if (dialogResult == SkipResult)
|
if (dialogResult == SkipResult)
|
||||||
{
|
{
|
||||||
ApplicationServices.LibraryCommands.UpdateBook(libraryBook, LiberatedStatus.Error);
|
libraryBook.Book.UserDefinedItem.BookStatus = LiberatedStatus.Error;
|
||||||
LogMe.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}");
|
LogMe.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,7 +304,7 @@ An error occurred while trying to process this book.
|
|||||||
protected override async Task RunAsync()
|
protected override async Task RunAsync()
|
||||||
{
|
{
|
||||||
// support for 'skip this time only' requires state. iterators provide this state for free. therefore: use foreach/iterator here
|
// support for 'skip this time only' requires state. iterators provide this state for free. therefore: use foreach/iterator here
|
||||||
foreach (var libraryBook in Processable.GetValidLibraryBooks())
|
foreach (var libraryBook in Processable.GetValidLibraryBooks(ApplicationServices.DbContexts.GetContext().GetLibrary_Flat_NoTracking()))
|
||||||
{
|
{
|
||||||
var keepGoing = await ProcessOneAsync(libraryBook, validate: false);
|
var keepGoing = await ProcessOneAsync(libraryBook, validate: false);
|
||||||
if (!keepGoing)
|
if (!keepGoing)
|
||||||
|
|||||||
@ -384,15 +384,14 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
#region liberate menu
|
#region liberate menu
|
||||||
private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e)
|
private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
=> await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync(updateGridRow);
|
=> await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync();
|
||||||
|
|
||||||
private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e)
|
private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
=> await BookLiberation.ProcessorAutomationController.BackupAllPdfsAsync(updateGridRow);
|
=> await BookLiberation.ProcessorAutomationController.BackupAllPdfsAsync();
|
||||||
|
|
||||||
private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e)
|
private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
=> await BookLiberation.ProcessorAutomationController.ConvertAllBooksAsync();
|
=> await BookLiberation.ProcessorAutomationController.ConvertAllBooksAsync();
|
||||||
|
|
||||||
private void updateGridRow(object _, LibraryBook libraryBook) => currProductsGrid.RefreshRow(libraryBook.Book.AudibleProductId);
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Export menu
|
#region Export menu
|
||||||
|
|||||||
@ -12,6 +12,9 @@ using Dinah.Core.Drawing;
|
|||||||
|
|
||||||
namespace LibationWinForms
|
namespace LibationWinForms
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The View Model for a LibraryBook
|
||||||
|
/// </summary>
|
||||||
internal class GridEntry : AsyncNotifyPropertyChanged, IMemberComparable
|
internal class GridEntry : AsyncNotifyPropertyChanged, IMemberComparable
|
||||||
{
|
{
|
||||||
#region implementation properties
|
#region implementation properties
|
||||||
@ -26,12 +29,15 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
private Book Book => LibraryBook.Book;
|
private Book Book => LibraryBook.Book;
|
||||||
private Image _cover;
|
private Image _cover;
|
||||||
|
private Action Refilter { get; }
|
||||||
|
|
||||||
public GridEntry(LibraryBook libraryBook)
|
public GridEntry(LibraryBook libraryBook, Action refilterOnChanged = null)
|
||||||
{
|
{
|
||||||
LibraryBook = libraryBook;
|
LibraryBook = libraryBook;
|
||||||
|
Refilter = refilterOnChanged;
|
||||||
_memberValues = CreateMemberValueDictionary();
|
_memberValues = CreateMemberValueDictionary();
|
||||||
|
|
||||||
|
|
||||||
//Get cover art. If it's default, subscribe to PictureCached
|
//Get cover art. If it's default, subscribe to PictureCached
|
||||||
{
|
{
|
||||||
(bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(new FileManager.PictureDefinition(Book.PictureId, FileManager.PictureSize._80x80));
|
(bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(new FileManager.PictureDefinition(Book.PictureId, FileManager.PictureSize._80x80));
|
||||||
@ -58,7 +64,7 @@ namespace LibationWinForms
|
|||||||
Description = GetDescriptionDisplay(Book);
|
Description = GetDescriptionDisplay(Book);
|
||||||
}
|
}
|
||||||
|
|
||||||
//DisplayTags and Liberate properties are live.
|
UserDefinedItem.ItemChanged += UserDefinedItem_ItemChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PictureStorage_PictureCached(object sender, FileManager.PictureCachedEventArgs e)
|
private void PictureStorage_PictureCached(object sender, FileManager.PictureCachedEventArgs e)
|
||||||
@ -70,8 +76,77 @@ namespace LibationWinForms
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Data Source properties
|
#region detect changes to the model, update the view, and save to database.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This event handler receives notifications from the model that it has changed.
|
||||||
|
/// Save to the database and notify the view that it's changed.
|
||||||
|
/// </summary>
|
||||||
|
private void UserDefinedItem_ItemChanged(object sender, string itemName)
|
||||||
|
{
|
||||||
|
var udi = sender as UserDefinedItem;
|
||||||
|
|
||||||
|
if (udi.Book.AudibleProductId != Book.AudibleProductId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (itemName)
|
||||||
|
{
|
||||||
|
case nameof(udi.Tags):
|
||||||
|
{
|
||||||
|
Book.UserDefinedItem.Tags = udi.Tags;
|
||||||
|
NotifyPropertyChanged(nameof(DisplayTags));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case nameof(udi.BookStatus):
|
||||||
|
{
|
||||||
|
Book.UserDefinedItem.BookStatus = udi.BookStatus;
|
||||||
|
NotifyPropertyChanged(nameof(Liberate));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case nameof(udi.PdfStatus):
|
||||||
|
{
|
||||||
|
Book.UserDefinedItem.PdfStatus = udi.PdfStatus;
|
||||||
|
NotifyPropertyChanged(nameof(Liberate));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!suspendCommit)
|
||||||
|
Commit();
|
||||||
|
}
|
||||||
|
private bool suspendCommit = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begin editing the model, suspending commits until <see cref="EndEdit"/> is called.
|
||||||
|
/// </summary>
|
||||||
|
public void BeginEdit() => suspendCommit = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save all edits to the database.
|
||||||
|
/// </summary>
|
||||||
|
public void EndEdit()
|
||||||
|
{
|
||||||
|
Commit();
|
||||||
|
suspendCommit = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Commit()
|
||||||
|
{
|
||||||
|
//We don't want LiberatedStatus.PartialDownload to be a persistent status.
|
||||||
|
var bookStatus = Book.UserDefinedItem.BookStatus;
|
||||||
|
var saveStatus = bookStatus == LiberatedStatus.PartialDownload ? LiberatedStatus.NotLiberated : bookStatus;
|
||||||
|
Book.UserDefinedItem.BookStatus = saveStatus;
|
||||||
|
|
||||||
|
LibraryCommands.UpdateUserDefinedItem(Book);
|
||||||
|
|
||||||
|
Book.UserDefinedItem.BookStatus = bookStatus;
|
||||||
|
|
||||||
|
Refilter?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Model properties exposed to the view
|
||||||
public Image Cover
|
public Image Cover
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -96,8 +171,22 @@ namespace LibationWinForms
|
|||||||
public string Category { get; }
|
public string Category { get; }
|
||||||
public string Misc { get; }
|
public string Misc { get; }
|
||||||
public string Description { get; }
|
public string Description { get; }
|
||||||
public string DisplayTags => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
|
public string DisplayTags
|
||||||
public (LiberatedState, PdfState) Liberate => (LibraryCommands.Liberated_Status(Book), LibraryCommands.Pdf_Status(Book));
|
{
|
||||||
|
get => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
|
||||||
|
set => Book.UserDefinedItem.Tags = value;
|
||||||
|
}
|
||||||
|
public (LiberatedStatus BookStatus, LiberatedStatus? PdfStatus) Liberate
|
||||||
|
{
|
||||||
|
get => (LibraryCommands.Liberated_Status(LibraryBook.Book), LibraryCommands.Pdf_Status(LibraryBook.Book));
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
LibraryBook.Book.UserDefinedItem.BookStatus = value.BookStatus;
|
||||||
|
LibraryBook.Book.UserDefinedItem.PdfStatus = value.PdfStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Data Sorting
|
#region Data Sorting
|
||||||
@ -121,7 +210,7 @@ namespace LibationWinForms
|
|||||||
{ nameof(Category), () => Category },
|
{ nameof(Category), () => Category },
|
||||||
{ nameof(Misc), () => Misc },
|
{ nameof(Misc), () => Misc },
|
||||||
{ nameof(DisplayTags), () => DisplayTags },
|
{ nameof(DisplayTags), () => DisplayTags },
|
||||||
{ nameof(Liberate), () => Liberate.Item1 }
|
{ nameof(Liberate), () => Liberate.BookStatus }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Instantiate comparers for every exposed member object type.
|
// Instantiate comparers for every exposed member object type.
|
||||||
@ -131,7 +220,7 @@ namespace LibationWinForms
|
|||||||
{ typeof(int), new ObjectComparer<int>() },
|
{ typeof(int), new ObjectComparer<int>() },
|
||||||
{ typeof(float), new ObjectComparer<float>() },
|
{ typeof(float), new ObjectComparer<float>() },
|
||||||
{ typeof(DateTime), new ObjectComparer<DateTime>() },
|
{ typeof(DateTime), new ObjectComparer<DateTime>() },
|
||||||
{ typeof(LiberatedState), new ObjectComparer<LiberatedState>() },
|
{ typeof(LiberatedStatus), new ObjectComparer<LiberatedStatus>() },
|
||||||
};
|
};
|
||||||
|
|
||||||
public virtual object GetMemberValue(string memberName) => _memberValues[memberName]();
|
public virtual object GetMemberValue(string memberName) => _memberValues[memberName]();
|
||||||
@ -200,5 +289,11 @@ namespace LibationWinForms
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
~GridEntry()
|
||||||
|
{
|
||||||
|
UserDefinedItem.ItemChanged -= UserDefinedItem_ItemChanged;
|
||||||
|
FileManager.PictureStorage.PictureCached -= PictureStorage_PictureCached;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Dinah.Core\Dinah.Core.WindowsDesktop\Dinah.Core.WindowsDesktop.csproj" />
|
<ProjectReference Include="..\..\Dinah.Core\Dinah.Core.WindowsDesktop\Dinah.Core.WindowsDesktop.csproj" />
|
||||||
|
<ProjectReference Include="..\ApplicationServices\ApplicationServices.csproj" />
|
||||||
<ProjectReference Include="..\FileLiberator\FileLiberator.csproj" />
|
<ProjectReference Include="..\FileLiberator\FileLiberator.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using System;
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using DataLayer;
|
||||||
|
|
||||||
namespace LibationWinForms
|
namespace LibationWinForms
|
||||||
{
|
{
|
||||||
@ -20,9 +21,11 @@ namespace LibationWinForms
|
|||||||
{
|
{
|
||||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts);
|
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts);
|
||||||
|
|
||||||
if (value is (LiberatedState liberatedState, PdfState pdfState))
|
if (value is (LiberatedStatus, LiberatedStatus) or (LiberatedStatus, null))
|
||||||
{
|
{
|
||||||
(string mouseoverText, Bitmap buttonImage) = GetLiberateDisplay(liberatedState, pdfState);
|
var (bookState, pdfState) = ((LiberatedStatus bookState, LiberatedStatus? pdfState))value;
|
||||||
|
|
||||||
|
(string mouseoverText, Bitmap buttonImage) = GetLiberateDisplay(bookState, pdfState);
|
||||||
|
|
||||||
DrawButtonImage(graphics, buttonImage, cellBounds);
|
DrawButtonImage(graphics, buttonImage, cellBounds);
|
||||||
|
|
||||||
@ -30,29 +33,31 @@ namespace LibationWinForms
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (string mouseoverText, Bitmap buttonImage) GetLiberateDisplay(LiberatedState liberatedStatus, PdfState pdfStatus)
|
private static (string mouseoverText, Bitmap buttonImage) GetLiberateDisplay(LiberatedStatus liberatedStatus, LiberatedStatus? pdfStatus)
|
||||||
{
|
{
|
||||||
(string libState, string image_lib) = liberatedStatus switch
|
(string libState, string image_lib) = liberatedStatus switch
|
||||||
{
|
{
|
||||||
LiberatedState.Liberated => ("Liberated", "green"),
|
LiberatedStatus.Liberated => ("Liberated", "green"),
|
||||||
LiberatedState.PartialDownload => ("File has been at least\r\npartially downloaded", "yellow"),
|
LiberatedStatus.PartialDownload => ("File has been at least\r\npartially downloaded", "yellow"),
|
||||||
LiberatedState.NotDownloaded => ("Book NOT downloaded", "red"),
|
LiberatedStatus.NotLiberated => ("Book NOT downloaded", "red"),
|
||||||
|
LiberatedStatus.Error => ("Book downloaded ERROR", "red"),
|
||||||
_ => throw new Exception("Unexpected liberation state")
|
_ => throw new Exception("Unexpected liberation state")
|
||||||
};
|
};
|
||||||
|
|
||||||
(string pdfState, string image_pdf) = pdfStatus switch
|
(string pdfState, string image_pdf) = pdfStatus switch
|
||||||
{
|
{
|
||||||
PdfState.Downloaded => ("\r\nPDF downloaded", "_pdf_yes"),
|
LiberatedStatus.Liberated => ("\r\nPDF downloaded", "_pdf_yes"),
|
||||||
PdfState.NotDownloaded => ("\r\nPDF NOT downloaded", "_pdf_no"),
|
LiberatedStatus.NotLiberated => ("\r\nPDF NOT downloaded", "_pdf_no"),
|
||||||
PdfState.NoPdf => ("", ""),
|
LiberatedStatus.Error => ("\r\nPDF downloaded ERROR", "_pdf_no"),
|
||||||
|
null => ("", ""),
|
||||||
_ => throw new Exception("Unexpected PDF state")
|
_ => throw new Exception("Unexpected PDF state")
|
||||||
};
|
};
|
||||||
|
|
||||||
var mouseoverText = libState + pdfState;
|
var mouseoverText = libState + pdfState;
|
||||||
|
|
||||||
if (liberatedStatus == LiberatedState.NotDownloaded ||
|
if (liberatedStatus == LiberatedStatus.NotLiberated ||
|
||||||
liberatedStatus == LiberatedState.PartialDownload ||
|
liberatedStatus == LiberatedStatus.PartialDownload ||
|
||||||
pdfStatus == PdfState.NotDownloaded)
|
pdfStatus == LiberatedStatus.NotLiberated)
|
||||||
mouseoverText += "\r\nClick to complete";
|
mouseoverText += "\r\nClick to complete";
|
||||||
|
|
||||||
var buttonImage = (Bitmap)Properties.Resources.ResourceManager.GetObject($"liberate_{image_lib}{image_pdf}");
|
var buttonImage = (Bitmap)Properties.Resources.ResourceManager.GetObject($"liberate_{image_lib}{image_pdf}");
|
||||||
|
|||||||
@ -86,7 +86,7 @@ namespace LibationWinForms
|
|||||||
}
|
}
|
||||||
|
|
||||||
// else: liberate
|
// else: liberate
|
||||||
await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(libraryBook, (_, __) => RefreshRow(libraryBook.Book.AudibleProductId));
|
await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(libraryBook);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Details_Click(GridEntry liveGridEntry)
|
private void Details_Click(GridEntry liveGridEntry)
|
||||||
@ -95,15 +95,14 @@ namespace LibationWinForms
|
|||||||
if (bookDetailsForm.ShowDialog() != DialogResult.OK)
|
if (bookDetailsForm.ShowDialog() != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var qtyChanges = LibraryCommands.UpdateUserDefinedItem(liveGridEntry.LibraryBook.Book, bookDetailsForm.NewTags, bookDetailsForm.BookLiberatedStatus, bookDetailsForm.PdfLiberatedStatus);
|
liveGridEntry.BeginEdit();
|
||||||
if (qtyChanges == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
//Re-apply filters
|
liveGridEntry.DisplayTags = bookDetailsForm.NewTags;
|
||||||
Filter();
|
liveGridEntry.Liberate = (bookDetailsForm.BookLiberatedStatus, bookDetailsForm.PdfLiberatedStatus);
|
||||||
|
|
||||||
//Update whole GridEntry row
|
liveGridEntry.EndEdit();
|
||||||
liveGridEntry.NotifyPropertyChanged();
|
|
||||||
|
BackupCountsChanged?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -132,7 +131,7 @@ namespace LibationWinForms
|
|||||||
}
|
}
|
||||||
|
|
||||||
var orderedGridEntries = lib
|
var orderedGridEntries = lib
|
||||||
.Select(lb => new GridEntry(lb)).ToList()
|
.Select(lb => new GridEntry(lb, Filter)).ToList()
|
||||||
// default load order
|
// default load order
|
||||||
.OrderByDescending(ge => (DateTime)ge.GetMemberValue(nameof(ge.PurchaseDate)))
|
.OrderByDescending(ge => (DateTime)ge.GetMemberValue(nameof(ge.PurchaseDate)))
|
||||||
//// more advanced example: sort by author, then series, then title
|
//// more advanced example: sort by author, then series, then title
|
||||||
@ -150,19 +149,6 @@ namespace LibationWinForms
|
|||||||
BackupCountsChanged?.Invoke(this, EventArgs.Empty);
|
BackupCountsChanged?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshRow(string productId)
|
|
||||||
{
|
|
||||||
var liveGridEntry = getGridEntry((ge) => ge.AudibleProductId == productId);
|
|
||||||
|
|
||||||
// update GridEntry Liberate cell
|
|
||||||
liveGridEntry?.NotifyPropertyChanged(nameof(liveGridEntry.Liberate));
|
|
||||||
|
|
||||||
// needed in case filtering by -IsLiberated and it gets changed to Liberated. want to immediately show the change
|
|
||||||
Filter();
|
|
||||||
|
|
||||||
BackupCountsChanged?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Filter
|
#region Filter
|
||||||
@ -195,12 +181,7 @@ namespace LibationWinForms
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region DataGridView Macro
|
#region DataGridView Macro
|
||||||
|
|
||||||
private GridEntry getGridEntry(Func<GridEntry, bool> predicate)
|
|
||||||
=> ((SortableBindingList<GridEntry>)gridEntryBindingSource.DataSource).FirstOrDefault(predicate);
|
|
||||||
|
|
||||||
private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem<GridEntry>(rowIndex);
|
private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem<GridEntry>(rowIndex);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user