Merge branch 'rmcrackan:master' into master
This commit is contained in:
commit
926f8a957e
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0-windows</TargetFramework>
|
<TargetFramework>net6.0-windows</TargetFramework>
|
||||||
<Version>7.3.0.1</Version>
|
<Version>7.4.0.1</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -33,18 +33,20 @@ namespace ApplicationServices
|
|||||||
|
|
||||||
//These are the minimum response groups required for the
|
//These are the minimum response groups required for the
|
||||||
//library scanner to pass all validation and filtering.
|
//library scanner to pass all validation and filtering.
|
||||||
var libraryResponseGroups =
|
var libraryOptions = new LibraryOptions
|
||||||
LibraryOptions.ResponseGroupOptions.ProductAttrs |
|
{
|
||||||
LibraryOptions.ResponseGroupOptions.ProductDesc |
|
ResponseGroups
|
||||||
LibraryOptions.ResponseGroupOptions.Relationships;
|
= LibraryOptions.ResponseGroupOptions.ProductAttrs
|
||||||
|
| LibraryOptions.ResponseGroupOptions.ProductDesc
|
||||||
if (accounts is null || accounts.Length == 0)
|
| LibraryOptions.ResponseGroupOptions.Relationships
|
||||||
|
};
|
||||||
|
if (accounts is null || accounts.Length == 0)
|
||||||
return new List<LibraryBook>();
|
return new List<LibraryBook>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
logTime($"pre {nameof(scanAccountsAsync)} all");
|
logTime($"pre {nameof(scanAccountsAsync)} all");
|
||||||
var libraryItems = await scanAccountsAsync(apiExtendedfunc, accounts, libraryResponseGroups);
|
var libraryItems = await scanAccountsAsync(apiExtendedfunc, accounts, libraryOptions);
|
||||||
logTime($"post {nameof(scanAccountsAsync)} all");
|
logTime($"post {nameof(scanAccountsAsync)} all");
|
||||||
|
|
||||||
var totalCount = libraryItems.Count;
|
var totalCount = libraryItems.Count;
|
||||||
@ -102,7 +104,12 @@ namespace ApplicationServices
|
|||||||
}
|
}
|
||||||
|
|
||||||
logTime($"pre {nameof(scanAccountsAsync)} all");
|
logTime($"pre {nameof(scanAccountsAsync)} all");
|
||||||
var importItems = await scanAccountsAsync(apiExtendedfunc, accounts, LibraryOptions.ResponseGroupOptions.ALL_OPTIONS);
|
var libraryOptions = new LibraryOptions
|
||||||
|
{
|
||||||
|
ResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS,
|
||||||
|
ImageSizes = LibraryOptions.ImageSizeOptions._500 | LibraryOptions.ImageSizeOptions._1215
|
||||||
|
};
|
||||||
|
var importItems = await scanAccountsAsync(apiExtendedfunc, accounts, libraryOptions);
|
||||||
logTime($"post {nameof(scanAccountsAsync)} all");
|
logTime($"post {nameof(scanAccountsAsync)} all");
|
||||||
|
|
||||||
var totalCount = importItems.Count;
|
var totalCount = importItems.Count;
|
||||||
@ -150,7 +157,7 @@ namespace ApplicationServices
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<List<ImportItem>> scanAccountsAsync(Func<Account, Task<ApiExtended>> apiExtendedfunc, Account[] accounts, LibraryOptions.ResponseGroupOptions libraryResponseGroups)
|
private static async Task<List<ImportItem>> scanAccountsAsync(Func<Account, Task<ApiExtended>> apiExtendedfunc, Account[] accounts, LibraryOptions libraryOptions)
|
||||||
{
|
{
|
||||||
var tasks = new List<Task<List<ImportItem>>>();
|
var tasks = new List<Task<List<ImportItem>>>();
|
||||||
foreach (var account in accounts)
|
foreach (var account in accounts)
|
||||||
@ -159,7 +166,7 @@ namespace ApplicationServices
|
|||||||
var apiExtended = await apiExtendedfunc(account);
|
var apiExtended = await apiExtendedfunc(account);
|
||||||
|
|
||||||
// add scanAccountAsync as a TASK: do not await
|
// add scanAccountAsync as a TASK: do not await
|
||||||
tasks.Add(scanAccountAsync(apiExtended, account, libraryResponseGroups));
|
tasks.Add(scanAccountAsync(apiExtended, account, libraryOptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
// import library in parallel
|
// import library in parallel
|
||||||
@ -168,7 +175,7 @@ namespace ApplicationServices
|
|||||||
return importItems;
|
return importItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<List<ImportItem>> scanAccountAsync(ApiExtended apiExtended, Account account, LibraryOptions.ResponseGroupOptions libraryResponseGroups)
|
private static async Task<List<ImportItem>> scanAccountAsync(ApiExtended apiExtended, Account account, LibraryOptions libraryOptions)
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNull(account, nameof(account));
|
ArgumentValidator.EnsureNotNull(account, nameof(account));
|
||||||
|
|
||||||
@ -179,7 +186,7 @@ namespace ApplicationServices
|
|||||||
|
|
||||||
logTime($"pre scanAccountAsync {account.AccountName}");
|
logTime($"pre scanAccountAsync {account.AccountName}");
|
||||||
|
|
||||||
var dtoItems = await apiExtended.GetLibraryValidatedAsync(libraryResponseGroups, Configuration.Instance.ImportEpisodes);
|
var dtoItems = await apiExtended.GetLibraryValidatedAsync(libraryOptions, Configuration.Instance.ImportEpisodes);
|
||||||
|
|
||||||
logTime($"post scanAccountAsync {account.AccountName} qty: {dtoItems.Count}");
|
logTime($"post scanAccountAsync {account.AccountName} qty: {dtoItems.Count}");
|
||||||
|
|
||||||
@ -259,30 +266,47 @@ namespace ApplicationServices
|
|||||||
/// Occurs when <see cref="UserDefinedItem.Tags"/>, <see cref="UserDefinedItem.BookStatus"/>, or <see cref="UserDefinedItem.PdfStatus"/>
|
/// Occurs when <see cref="UserDefinedItem.Tags"/>, <see cref="UserDefinedItem.BookStatus"/>, or <see cref="UserDefinedItem.PdfStatus"/>
|
||||||
/// changed values are successfully persisted.
|
/// changed values are successfully persisted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static event EventHandler<string> BookUserDefinedItemCommitted;
|
public static event EventHandler BookUserDefinedItemCommitted;
|
||||||
|
|
||||||
#region Update book details
|
#region Update book details
|
||||||
public static int UpdateUserDefinedItem(Book book)
|
public static int UpdateUserDefinedItem(params Book[] books) => UpdateUserDefinedItem(books.ToList());
|
||||||
|
public static int UpdateUserDefinedItem(IEnumerable<Book> books)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (books is null || !books.Any())
|
||||||
|
return 0;
|
||||||
|
|
||||||
using var context = DbContexts.GetContext();
|
using var context = DbContexts.GetContext();
|
||||||
|
|
||||||
// Attach() NoTracking entities before SaveChanges()
|
// Attach() NoTracking entities before SaveChanges()
|
||||||
context.Attach(book.UserDefinedItem).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
foreach (var book in books)
|
||||||
|
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;
|
||||||
|
|
||||||
|
// semi-arbitrary. At some point it's more worth it to do a full re-index than to do one offs.
|
||||||
|
// I did not benchmark before choosing the number here
|
||||||
|
if (qtyChanges > 15)
|
||||||
|
SearchEngineCommands.FullReIndex();
|
||||||
|
else
|
||||||
{
|
{
|
||||||
SearchEngineCommands.UpdateLiberatedStatus(book);
|
foreach (var book in books)
|
||||||
SearchEngineCommands.UpdateBookTags(book);
|
{
|
||||||
BookUserDefinedItemCommitted?.Invoke(null, book.AudibleProductId);
|
SearchEngineCommands.UpdateLiberatedStatus(book);
|
||||||
|
SearchEngineCommands.UpdateBookTags(book);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BookUserDefinedItemCommitted?.Invoke(null, null);
|
||||||
|
|
||||||
return qtyChanges;
|
return qtyChanges;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Logger.Error(ex, $"Error updating {nameof(book.UserDefinedItem)}");
|
Log.Logger.Error(ex, $"Error updating {nameof(Book.UserDefinedItem)}");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -106,16 +106,16 @@ namespace AudibleUtilities
|
|||||||
// 2 retries == 3 total
|
// 2 retries == 3 total
|
||||||
.RetryAsync(2);
|
.RetryAsync(2);
|
||||||
|
|
||||||
public Task<List<Item>> GetLibraryValidatedAsync(LibraryOptions.ResponseGroupOptions responseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS, bool importEpisodes = true)
|
public Task<List<Item>> GetLibraryValidatedAsync(LibraryOptions libraryOptions, bool importEpisodes = true)
|
||||||
{
|
{
|
||||||
// bug on audible's side. the 1st time after a long absence, a query to get library will return without titles or authors. a subsequent identical query will be successful. this is true whether or not tokens are refreshed
|
// bug on audible's side. the 1st time after a long absence, a query to get library will return without titles or authors. a subsequent identical query will be successful. this is true whether or not tokens are refreshed
|
||||||
// worse, this 1st dummy call doesn't seem to help:
|
// worse, this 1st dummy call doesn't seem to help:
|
||||||
// var page = await api.GetLibraryAsync(new AudibleApi.LibraryOptions { NumberOfResultPerPage = 1, PageNumber = 1, PurchasedAfter = DateTime.Now.AddYears(-20), ResponseGroups = AudibleApi.LibraryOptions.ResponseGroupOptions.ALL_OPTIONS });
|
// var page = await api.GetLibraryAsync(new AudibleApi.LibraryOptions { NumberOfResultPerPage = 1, PageNumber = 1, PurchasedAfter = DateTime.Now.AddYears(-20), ResponseGroups = AudibleApi.LibraryOptions.ResponseGroupOptions.ALL_OPTIONS });
|
||||||
// i don't want to incur the cost of making a full dummy call every time because it fails sometimes
|
// i don't want to incur the cost of making a full dummy call every time because it fails sometimes
|
||||||
return policy.ExecuteAsync(() => getItemsAsync(responseGroups, importEpisodes));
|
return policy.ExecuteAsync(() => getItemsAsync(libraryOptions, importEpisodes));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<Item>> getItemsAsync(LibraryOptions.ResponseGroupOptions responseGroups, bool importEpisodes)
|
private async Task<List<Item>> getItemsAsync(LibraryOptions libraryOptions, bool importEpisodes)
|
||||||
{
|
{
|
||||||
var items = new List<Item>();
|
var items = new List<Item>();
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@ -131,7 +131,7 @@ namespace AudibleUtilities
|
|||||||
Serilog.Log.Logger.Debug("Begin initial library scan");
|
Serilog.Log.Logger.Debug("Begin initial library scan");
|
||||||
|
|
||||||
if (!items.Any())
|
if (!items.Any())
|
||||||
items = await Api.GetAllLibraryItemsAsync(responseGroups);
|
items = await Api.GetAllLibraryItemsAsync(libraryOptions);
|
||||||
|
|
||||||
Serilog.Log.Logger.Debug("Initial library scan complete. Begin episode scan");
|
Serilog.Log.Logger.Debug("Initial library scan complete. Begin episode scan");
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AudibleApi" Version="2.8.0.1" />
|
<PackageReference Include="AudibleApi" Version="2.8.1.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -12,13 +12,13 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Dinah.EntityFrameworkCore" Version="4.0.0.3" />
|
<PackageReference Include="Dinah.EntityFrameworkCore" Version="4.0.1.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.4">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.4" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.5" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.4">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.5">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@ -141,9 +141,11 @@ namespace DataLayer
|
|||||||
get => _bookStatus;
|
get => _bookStatus;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (_bookStatus != value)
|
// PartialDownload is a live/ephemeral status, not a persistent one. Do not store
|
||||||
{
|
var displayStatus = value == LiberatedStatus.PartialDownload ? LiberatedStatus.NotLiberated : value;
|
||||||
_bookStatus = value;
|
if (_bookStatus != displayStatus)
|
||||||
|
{
|
||||||
|
_bookStatus = displayStatus;
|
||||||
OnItemChanged(nameof(BookStatus));
|
OnItemChanged(nameof(BookStatus));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -79,6 +79,7 @@ namespace FileLiberator
|
|||||||
DownloadCoverArt(libraryBook);
|
DownloadCoverArt(libraryBook);
|
||||||
|
|
||||||
libraryBook.Book.UserDefinedItem.BookStatus = LiberatedStatus.Liberated;
|
libraryBook.Book.UserDefinedItem.BookStatus = LiberatedStatus.Liberated;
|
||||||
|
ApplicationServices.LibraryCommands.UpdateUserDefinedItem(libraryBook.Book);
|
||||||
|
|
||||||
return new StatusHandler();
|
return new StatusHandler();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ namespace FileLiberator
|
|||||||
var result = verifyDownload(actualDownloadedFilePath);
|
var result = verifyDownload(actualDownloadedFilePath);
|
||||||
|
|
||||||
libraryBook.Book.UserDefinedItem.PdfStatus = result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated;
|
libraryBook.Book.UserDefinedItem.PdfStatus = result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated;
|
||||||
|
ApplicationServices.LibraryCommands.UpdateUserDefinedItem(libraryBook.Book);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<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="..\AudibleUtilities\AudibleUtilities.csproj" />
|
<ProjectReference Include="..\AudibleUtilities\AudibleUtilities.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
@ -61,7 +63,7 @@ namespace LibationWinForms.BookLiberation
|
|||||||
await new BackupSingle(logMe, backupBook, libraryBook).RunBackupAsync();
|
await new BackupSingle(logMe, backupBook, libraryBook).RunBackupAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task BackupAllBooksAsync()
|
public static async Task BackupAllBooksAsync(List<LibraryBook> libraryBooks = null)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Information("Begin " + nameof(BackupAllBooksAsync));
|
Serilog.Log.Logger.Information("Begin " + nameof(BackupAllBooksAsync));
|
||||||
|
|
||||||
@ -69,7 +71,7 @@ namespace LibationWinForms.BookLiberation
|
|||||||
var logMe = LogMe.RegisterForm(automatedBackupsForm);
|
var logMe = LogMe.RegisterForm(automatedBackupsForm);
|
||||||
var backupBook = CreateBackupBook(logMe);
|
var backupBook = CreateBackupBook(logMe);
|
||||||
|
|
||||||
await new BackupLoop(logMe, backupBook, automatedBackupsForm).RunBackupAsync();
|
await new BackupLoop(logMe, backupBook, automatedBackupsForm, libraryBooks).RunBackupAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task ConvertAllBooksAsync()
|
public static async Task ConvertAllBooksAsync()
|
||||||
@ -255,6 +257,8 @@ $@" Title: {libraryBook.Book.Title}
|
|||||||
if (dialogResult == SkipResult)
|
if (dialogResult == SkipResult)
|
||||||
{
|
{
|
||||||
libraryBook.Book.UserDefinedItem.BookStatus = LiberatedStatus.Error;
|
libraryBook.Book.UserDefinedItem.BookStatus = LiberatedStatus.Error;
|
||||||
|
ApplicationServices.LibraryCommands.UpdateUserDefinedItem(libraryBook.Book);
|
||||||
|
|
||||||
LogMe.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}");
|
LogMe.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,13 +311,16 @@ An error occurred while trying to process this book.
|
|||||||
protected override MessageBoxDefaultButton SkipDialogDefaultButton => MessageBoxDefaultButton.Button1;
|
protected override MessageBoxDefaultButton SkipDialogDefaultButton => MessageBoxDefaultButton.Button1;
|
||||||
protected override DialogResult SkipResult => DialogResult.Ignore;
|
protected override DialogResult SkipResult => DialogResult.Ignore;
|
||||||
|
|
||||||
public BackupLoop(LogMe logMe, Processable processable, AutomatedBackupsForm automatedBackupsForm)
|
private List<LibraryBook> libraryBooks { get; }
|
||||||
: base(logMe, processable, automatedBackupsForm) { }
|
|
||||||
|
public BackupLoop(LogMe logMe, Processable processable, AutomatedBackupsForm automatedBackupsForm, List<LibraryBook> libraryBooks = null)
|
||||||
|
: base(logMe, processable, automatedBackupsForm)
|
||||||
|
=> this.libraryBooks = libraryBooks ?? ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking();
|
||||||
|
|
||||||
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(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking()))
|
foreach (var libraryBook in Processable.GetValidLibraryBooks(libraryBooks))
|
||||||
{
|
{
|
||||||
var keepGoing = await ProcessOneAsync(libraryBook, validate: false);
|
var keepGoing = await ProcessOneAsync(libraryBook, validate: false);
|
||||||
if (!keepGoing)
|
if (!keepGoing)
|
||||||
|
|||||||
@ -58,11 +58,7 @@ namespace LibationWinForms
|
|||||||
public string Category { get; private set; }
|
public string Category { get; private set; }
|
||||||
public string Misc { get; private set; }
|
public string Misc { get; private set; }
|
||||||
public string Description { get; private set; }
|
public string Description { get; private set; }
|
||||||
public string DisplayTags
|
public string DisplayTags => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
|
||||||
{
|
|
||||||
get => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
|
|
||||||
set => Book.UserDefinedItem.Tags = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// these 2 values being in 1 field is the trick behind getting the liberated+pdf 'stoplight' icon to draw. See: LiberateDataGridViewImageButtonCell.Paint
|
// these 2 values being in 1 field is the trick behind getting the liberated+pdf 'stoplight' icon to draw. See: LiberateDataGridViewImageButtonCell.Paint
|
||||||
public (LiberatedStatus BookStatus, LiberatedStatus? PdfStatus) Liberate
|
public (LiberatedStatus BookStatus, LiberatedStatus? PdfStatus) Liberate
|
||||||
@ -77,18 +73,10 @@ namespace LibationWinForms
|
|||||||
}
|
}
|
||||||
return (_bookStatus, _pdfStatus);
|
return (_bookStatus, _pdfStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_bookStatus = value.BookStatus;
|
|
||||||
_pdfStatus = value.PdfStatus;
|
|
||||||
LibraryBook.Book.UserDefinedItem.BookStatus = value.BookStatus;
|
|
||||||
LibraryBook.Book.UserDefinedItem.PdfStatus = value.PdfStatus;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public event EventHandler<string> LibraryBookUpdated;
|
public event EventHandler LibraryBookUpdated;
|
||||||
public event EventHandler Committed;
|
public event EventHandler Committed;
|
||||||
|
|
||||||
// alias
|
// alias
|
||||||
@ -98,18 +86,18 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
public async Task DownloadBook()
|
public async Task DownloadBook()
|
||||||
{
|
{
|
||||||
if (!DownloadInProgress)
|
if (DownloadInProgress)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
DownloadInProgress = true;
|
||||||
{
|
await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(LibraryBook);
|
||||||
DownloadInProgress = true;
|
UpdateLiberatedStatus();
|
||||||
await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(LibraryBook);
|
}
|
||||||
UpdateLiberatedStatus();
|
finally
|
||||||
}
|
{
|
||||||
finally
|
DownloadInProgress = false;
|
||||||
{
|
|
||||||
DownloadInProgress = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +144,7 @@ namespace LibationWinForms
|
|||||||
UserDefinedItem.ItemChanged += UserDefinedItem_ItemChanged;
|
UserDefinedItem.ItemChanged += UserDefinedItem_ItemChanged;
|
||||||
|
|
||||||
// this will never have a value when triggered by ctor b/c nothing can subscribe to the event until after ctor is complete
|
// this will never have a value when triggered by ctor b/c nothing can subscribe to the event until after ctor is complete
|
||||||
LibraryBookUpdated?.Invoke(this, AudibleProductId);
|
LibraryBookUpdated?.Invoke(this, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PictureStorage_PictureCached(object sender, PictureCachedEventArgs e)
|
private void PictureStorage_PictureCached(object sender, PictureCachedEventArgs e)
|
||||||
@ -196,38 +184,28 @@ namespace LibationWinForms
|
|||||||
NotifyPropertyChanged(nameof(Liberate));
|
NotifyPropertyChanged(nameof(Liberate));
|
||||||
break;
|
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()
|
/// <summary>Save edits to the database</summary>
|
||||||
|
public void Commit(string newTags, LiberatedStatus bookStatus, LiberatedStatus? pdfStatus)
|
||||||
{
|
{
|
||||||
// We don't want LiberatedStatus.PartialDownload to be a persistent status.
|
// validate
|
||||||
// If display/icon status is PartialDownload then save NotLiberated to db then restore PartialDownload for display
|
if (DisplayTags.EqualsInsensitive(newTags) &&
|
||||||
var displayStatus = Book.UserDefinedItem.BookStatus;
|
Liberate.BookStatus == bookStatus &&
|
||||||
var saveStatus = displayStatus == LiberatedStatus.PartialDownload ? LiberatedStatus.NotLiberated : displayStatus;
|
Liberate.PdfStatus == pdfStatus)
|
||||||
Book.UserDefinedItem.BookStatus = saveStatus;
|
return;
|
||||||
|
|
||||||
|
// update cache
|
||||||
|
_bookStatus = bookStatus;
|
||||||
|
_pdfStatus = pdfStatus;
|
||||||
|
|
||||||
|
// set + save
|
||||||
|
Book.UserDefinedItem.Tags = newTags;
|
||||||
|
Book.UserDefinedItem.BookStatus = bookStatus;
|
||||||
|
Book.UserDefinedItem.PdfStatus = pdfStatus;
|
||||||
LibraryCommands.UpdateUserDefinedItem(Book);
|
LibraryCommands.UpdateUserDefinedItem(Book);
|
||||||
|
|
||||||
Book.UserDefinedItem.BookStatus = displayStatus;
|
// notify
|
||||||
|
|
||||||
Committed?.Invoke(this, null);
|
Committed?.Invoke(this, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -146,15 +146,8 @@ namespace LibationWinForms
|
|||||||
private static void Details_Click(GridEntry liveGridEntry)
|
private static void Details_Click(GridEntry liveGridEntry)
|
||||||
{
|
{
|
||||||
var bookDetailsForm = new BookDetailsDialog(liveGridEntry.LibraryBook);
|
var bookDetailsForm = new BookDetailsDialog(liveGridEntry.LibraryBook);
|
||||||
if (bookDetailsForm.ShowDialog() != DialogResult.OK)
|
if (bookDetailsForm.ShowDialog() == DialogResult.OK)
|
||||||
return;
|
liveGridEntry.Commit(bookDetailsForm.NewTags, bookDetailsForm.BookLiberatedStatus, bookDetailsForm.PdfLiberatedStatus);
|
||||||
|
|
||||||
liveGridEntry.BeginEdit();
|
|
||||||
|
|
||||||
liveGridEntry.DisplayTags = bookDetailsForm.NewTags;
|
|
||||||
liveGridEntry.Liberate = (bookDetailsForm.BookLiberatedStatus, bookDetailsForm.PdfLiberatedStatus);
|
|
||||||
|
|
||||||
liveGridEntry.EndEdit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -235,7 +228,7 @@ namespace LibationWinForms
|
|||||||
{
|
{
|
||||||
var entry = new GridEntry(libraryBook);
|
var entry = new GridEntry(libraryBook);
|
||||||
entry.Committed += Filter;
|
entry.Committed += Filter;
|
||||||
entry.LibraryBookUpdated += (sender, productId) => _dataGridView.InvalidateRow(_dataGridView.GetRowIdOfBoundItem((GridEntry)sender));
|
entry.LibraryBookUpdated += (sender, _) => _dataGridView.InvalidateRow(_dataGridView.GetRowIdOfBoundItem((GridEntry)sender));
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,14 +261,19 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
// Causes repainting of the DataGridView
|
// Causes repainting of the DataGridView
|
||||||
bindingContext.ResumeBinding();
|
bindingContext.ResumeBinding();
|
||||||
VisibleCountChanged?.Invoke(this, _dataGridView.AsEnumerable().Count(r => r.Visible));
|
VisibleCountChanged?.Invoke(this, GetVisible().Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region DataGridView Macro
|
internal IEnumerable<DataLayer.LibraryBook> GetVisible()
|
||||||
|
=> _dataGridView
|
||||||
|
.AsEnumerable()
|
||||||
|
.Where(row => row.Visible)
|
||||||
|
.Select(row => ((GridEntry)row.DataBoundItem).LibraryBook)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem<GridEntry>(rowIndex);
|
private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem<GridEntry>(rowIndex);
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Column Customizations
|
#region Column Customizations
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user