mirror of
https://github.com/rmcrackan/Libation.git
synced 2025-12-23 22:17:52 -05:00
Refactor DbContext access and disposal
- Remove instance queue. This is a database, after all, and is designed to be accessed and written to concurrently - Reduce the number of calls to DbContexts.Create() - Ensure that no LibationContext remains open across an await boundary. Multithread context access is the most likely culprit for past issues. - Make all Update UserDefinedItem methods asynchronous.
This commit is contained in:
@@ -69,10 +69,10 @@ namespace ApplicationServices
|
||||
return Count;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
public async Task ExecuteAsync()
|
||||
{
|
||||
foreach (var a in actionSets)
|
||||
a.LibraryBooks.UpdateBookStatus(a.newStatus);
|
||||
await a.LibraryBooks.UpdateBookStatusAsync(a.newStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,20 @@ using LibationFileManager;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#nullable enable
|
||||
namespace ApplicationServices
|
||||
{
|
||||
public static class DbContexts
|
||||
{
|
||||
/// <summary>Use for fully functional context, incl. SaveChanges(). For query-only, use the other method</summary>
|
||||
public static LibationContext GetContext()
|
||||
=> InstanceQueue<LibationContext>.WaitToCreateInstance(() =>
|
||||
{
|
||||
var context = !string.IsNullOrEmpty(Configuration.Instance.PostgresqlConnectionString)
|
||||
? LibationContextFactory.CreatePostgres(Configuration.Instance.PostgresqlConnectionString)
|
||||
: LibationContextFactory.CreateSqlite(SqliteStorage.ConnectionString);
|
||||
context.Database.Migrate();
|
||||
return context;
|
||||
});
|
||||
{
|
||||
var context = !string.IsNullOrEmpty(Configuration.Instance.PostgresqlConnectionString)
|
||||
? LibationContextFactory.CreatePostgres(Configuration.Instance.PostgresqlConnectionString)
|
||||
: LibationContextFactory.CreateSqlite(SqliteStorage.ConnectionString);
|
||||
context.Database.Migrate();
|
||||
return context;
|
||||
}
|
||||
|
||||
/// <summary>Use for full library querying. No lazy loading</summary>
|
||||
public static List<LibraryBook> GetLibrary_Flat_NoTracking(bool includeParents = false)
|
||||
@@ -24,5 +24,17 @@ namespace ApplicationServices
|
||||
using var context = GetContext();
|
||||
return context.GetLibrary_Flat_NoTracking(includeParents);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<LibraryBook> GetDeletedLibraryBooks()
|
||||
{
|
||||
using var context = GetContext();
|
||||
return context.GetDeletedLibraryBooks();
|
||||
}
|
||||
|
||||
public static LibraryBook? GetLibraryBook_Flat_NoTracking(string productId, bool caseSensative = true)
|
||||
{
|
||||
using var context = GetContext();
|
||||
return context.GetLibraryBook_Flat_NoTracking(productId, caseSensative);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AudibleApi;
|
||||
using AudibleApi;
|
||||
using AudibleUtilities;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
@@ -11,8 +6,14 @@ using Dinah.Core.Logging;
|
||||
using DtoImporterService;
|
||||
using FileManager;
|
||||
using LibationFileManager;
|
||||
using Microsoft.Extensions.DependencyModel;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static DtoImporterService.PerfLogger;
|
||||
|
||||
#nullable enable
|
||||
@@ -141,9 +142,9 @@ namespace ApplicationServices
|
||||
return default;
|
||||
|
||||
Log.Logger.Information("Begin long-running import");
|
||||
logTime($"pre {nameof(importIntoDbAsync)}");
|
||||
newCount = await importIntoDbAsync(importItems);
|
||||
logTime($"post {nameof(importIntoDbAsync)}");
|
||||
logTime($"pre {nameof(ImportIntoDbAsync)}");
|
||||
newCount = await Task.Run(() => ImportIntoDbAsync(importItems));
|
||||
logTime($"post {nameof(ImportIntoDbAsync)}");
|
||||
Log.Logger.Information($"Import complete. New count {newCount}");
|
||||
|
||||
return (totalCount, newCount);
|
||||
@@ -180,7 +181,8 @@ namespace ApplicationServices
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<int> ImportSingleToDbAsync(AudibleApi.Common.Item item, string accountId, string localeName)
|
||||
public static Task<int> ImportSingleToDbAsync(AudibleApi.Common.Item item, string accountId, string localeName) => Task.Run(() => importSingleToDb(item, accountId, localeName));
|
||||
private static int importSingleToDb(AudibleApi.Common.Item item, string accountId, string localeName)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNull(item, "item");
|
||||
ArgumentValidator.EnsureNotNull(accountId, "accountId");
|
||||
@@ -203,35 +205,23 @@ namespace ApplicationServices
|
||||
return 0;
|
||||
}
|
||||
|
||||
using var context = DbContexts.GetContext();
|
||||
return DoDbSizeChangeOperation(ctx =>
|
||||
{
|
||||
var bookImporter = new BookImporter(ctx);
|
||||
bookImporter.Import(importItems);
|
||||
var book = ctx.LibraryBooks.FirstOrDefault(lb => lb.Book.AudibleProductId == importItem.DtoItem.ProductId);
|
||||
|
||||
var bookImporter = new BookImporter(context);
|
||||
await Task.Run(() => bookImporter.Import(importItems));
|
||||
var book = await Task.Run(() => context.LibraryBooks.FirstOrDefault(lb => lb.Book.AudibleProductId == importItem.DtoItem.ProductId));
|
||||
|
||||
if (book is null)
|
||||
{
|
||||
book = new LibraryBook(bookImporter.Cache[importItem.DtoItem.ProductId], importItem.DtoItem.DateAdded, importItem.AccountId);
|
||||
context.LibraryBooks.Add(book);
|
||||
}
|
||||
else
|
||||
{
|
||||
book.AbsentFromLastScan = false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
int qtyChanged = await Task.Run(() => SaveContext(context));
|
||||
if (qtyChanged > 0)
|
||||
await Task.Run(() => finalizeLibrarySizeChange(context));
|
||||
return qtyChanged;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error(ex, "Error adding single library book to DB. {@DebugInfo}", new { item, accountId, localeName });
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (book is null)
|
||||
{
|
||||
book = new LibraryBook(bookImporter.Cache[importItem.DtoItem.ProductId], importItem.DtoItem.DateAdded, importItem.AccountId);
|
||||
ctx.LibraryBooks.Add(book);
|
||||
}
|
||||
else
|
||||
{
|
||||
book.AbsentFromLastScan = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static LogArchiver? openLogArchive(string? archivePath)
|
||||
{
|
||||
@@ -347,23 +337,21 @@ namespace ApplicationServices
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<int> importIntoDbAsync(List<ImportItem> importItems)
|
||||
private static async Task<int> ImportIntoDbAsync(List<ImportItem> importItems) => await Task.Run(() => importIntoDb(importItems));
|
||||
private static int importIntoDb(List<ImportItem> importItems)
|
||||
{
|
||||
logTime("importIntoDbAsync -- pre db");
|
||||
using var context = DbContexts.GetContext();
|
||||
var libraryBookImporter = new LibraryBookImporter(context);
|
||||
var newCount = await Task.Run(() => libraryBookImporter.Import(importItems));
|
||||
logTime("importIntoDbAsync -- post Import()");
|
||||
int qtyChanges = SaveContext(context);
|
||||
logTime("importIntoDbAsync -- post SaveChanges");
|
||||
logTime("importIntoDbAsync -- pre db");
|
||||
|
||||
// this is any changes at all to the database, not just new books
|
||||
if (qtyChanges > 0)
|
||||
await Task.Run(() => finalizeLibrarySizeChange(context));
|
||||
logTime("importIntoDbAsync -- post finalizeLibrarySizeChange");
|
||||
int newCount = 0;
|
||||
|
||||
return newCount;
|
||||
}
|
||||
DoDbSizeChangeOperation(ctx =>
|
||||
{
|
||||
var libraryBookImporter = new LibraryBookImporter(ctx);
|
||||
newCount = libraryBookImporter.Import(importItems);
|
||||
logTime("importIntoDbAsync -- post Import()");
|
||||
});
|
||||
return newCount;
|
||||
}
|
||||
|
||||
public static int SaveContext(LibationContext context)
|
||||
{
|
||||
@@ -389,57 +377,58 @@ namespace ApplicationServices
|
||||
|
||||
#region remove/restore books
|
||||
public static Task<int> RemoveBooksAsync(this IEnumerable<LibraryBook> idsToRemove) => Task.Run(() => removeBooks(idsToRemove));
|
||||
public static int RemoveBook(this LibraryBook idToRemove) => removeBooks(new[] { idToRemove });
|
||||
private static int removeBooks(IEnumerable<LibraryBook> removeLibraryBooks)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (removeLibraryBooks is null || !removeLibraryBooks.Any())
|
||||
return 0;
|
||||
|
||||
using var context = DbContexts.GetContext();
|
||||
{
|
||||
if (removeLibraryBooks is null || !removeLibraryBooks.Any())
|
||||
return 0;
|
||||
|
||||
return DoDbSizeChangeOperation(ctx =>
|
||||
{
|
||||
// Entry() NoTracking entities before SaveChanges()
|
||||
foreach (var lb in removeLibraryBooks)
|
||||
{
|
||||
{
|
||||
lb.IsDeleted = true;
|
||||
context.Entry(lb).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
||||
ctx.Entry(lb).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var qtyChanges = context.SaveChanges();
|
||||
if (qtyChanges > 0)
|
||||
finalizeLibrarySizeChange(context);
|
||||
|
||||
return qtyChanges;
|
||||
}
|
||||
public static Task<int> RestoreBooksAsync(this IEnumerable<LibraryBook> idsToRemove) => Task.Run(() => restoreBooks(idsToRemove));
|
||||
private static int restoreBooks(this IEnumerable<LibraryBook> libraryBooks)
|
||||
{
|
||||
if (libraryBooks is null || !libraryBooks.Any())
|
||||
return 0;
|
||||
try
|
||||
{
|
||||
return DoDbSizeChangeOperation(ctx =>
|
||||
{
|
||||
// Entry() NoTracking entities before SaveChanges()
|
||||
foreach (var lb in libraryBooks)
|
||||
{
|
||||
lb.IsDeleted = false;
|
||||
ctx.Entry(lb).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error(ex, "Error removing books");
|
||||
Log.Logger.Error(ex, "Error restoring books");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static int RestoreBooks(this IEnumerable<LibraryBook> libraryBooks)
|
||||
{
|
||||
try
|
||||
public static Task<int> PermanentlyDeleteBooksAsync(this IEnumerable<LibraryBook> idsToRemove) => Task.Run(() => permanentlyDeleteBooks(idsToRemove));
|
||||
private static int permanentlyDeleteBooks(this IEnumerable<LibraryBook> libraryBooks)
|
||||
{
|
||||
if (libraryBooks is null || !libraryBooks.Any())
|
||||
return 0;
|
||||
try
|
||||
{
|
||||
if (libraryBooks is null || !libraryBooks.Any())
|
||||
return 0;
|
||||
|
||||
using var context = DbContexts.GetContext();
|
||||
|
||||
// Entry() NoTracking entities before SaveChanges()
|
||||
foreach (var lb in libraryBooks)
|
||||
{
|
||||
lb.IsDeleted = false;
|
||||
context.Entry(lb).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
||||
}
|
||||
|
||||
var qtyChanges = context.SaveChanges();
|
||||
if (qtyChanges > 0)
|
||||
finalizeLibrarySizeChange(context);
|
||||
|
||||
return qtyChanges;
|
||||
return DoDbSizeChangeOperation(ctx =>
|
||||
{
|
||||
ctx.LibraryBooks.RemoveRange(libraryBooks);
|
||||
ctx.Books.RemoveRange(libraryBooks.Select(lb => lb.Book));
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -448,36 +437,40 @@ namespace ApplicationServices
|
||||
}
|
||||
}
|
||||
|
||||
public static int PermanentlyDeleteBooks(this IEnumerable<LibraryBook> libraryBooks)
|
||||
static int DoDbSizeChangeOperation(Action<LibationContext> action)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (libraryBooks is null || !libraryBooks.Any())
|
||||
return 0;
|
||||
try
|
||||
{
|
||||
int qtyChanges;
|
||||
List<LibraryBook>? library;
|
||||
|
||||
using var context = DbContexts.GetContext();
|
||||
using (var context = DbContexts.GetContext())
|
||||
{
|
||||
action?.Invoke(context);
|
||||
|
||||
context.LibraryBooks.RemoveRange(libraryBooks);
|
||||
context.Books.RemoveRange(libraryBooks.Select(lb => lb.Book));
|
||||
qtyChanges = SaveContext(context);
|
||||
logTime("importIntoDbAsync -- post SaveChanges");
|
||||
library = qtyChanges == 0 ? null : context.GetLibrary_Flat_NoTracking(includeParents: true);
|
||||
}
|
||||
|
||||
var qtyChanges = context.SaveChanges();
|
||||
if (qtyChanges > 0)
|
||||
finalizeLibrarySizeChange(context);
|
||||
if (library is not null)
|
||||
finalizeLibrarySizeChange(library);
|
||||
logTime("importIntoDbAsync -- post finalizeLibrarySizeChange");
|
||||
|
||||
return qtyChanges;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error(ex, "Error restoring books");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error(ex, "Error performing DB Size change operation");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// call this whenever books are added or removed from library
|
||||
private static void finalizeLibrarySizeChange(LibationContext context)
|
||||
private static void finalizeLibrarySizeChange(List<LibraryBook> library)
|
||||
{
|
||||
var library = context.GetLibrary_Flat_NoTracking(includeParents: true);
|
||||
LibrarySizeChanged?.Invoke(null, library);
|
||||
}
|
||||
|
||||
@@ -490,21 +483,21 @@ namespace ApplicationServices
|
||||
public static event EventHandler<IEnumerable<LibraryBook>>? BookUserDefinedItemCommitted;
|
||||
|
||||
#region Update book details
|
||||
public static int UpdateUserDefinedItem(
|
||||
public static async Task<int> UpdateUserDefinedItemAsync(
|
||||
this LibraryBook lb,
|
||||
string? tags = null,
|
||||
LiberatedStatus? bookStatus = null,
|
||||
LiberatedStatus? pdfStatus = null,
|
||||
Rating? rating = null)
|
||||
=> new[] { lb }.UpdateUserDefinedItem(tags, bookStatus, pdfStatus, rating);
|
||||
=> await UpdateUserDefinedItemAsync([lb], tags, bookStatus, pdfStatus, rating);
|
||||
|
||||
public static int UpdateUserDefinedItem(
|
||||
public static async Task<int> UpdateUserDefinedItemAsync(
|
||||
this IEnumerable<LibraryBook> lb,
|
||||
string? tags = null,
|
||||
LiberatedStatus? bookStatus = null,
|
||||
LiberatedStatus? pdfStatus = null,
|
||||
Rating? rating = null)
|
||||
=> updateUserDefinedItem(
|
||||
=> await UpdateUserDefinedItemAsync(
|
||||
lb,
|
||||
udi => {
|
||||
// blank tags are expected. null tags are not
|
||||
@@ -521,52 +514,54 @@ namespace ApplicationServices
|
||||
udi.UpdateRating(rating.OverallRating, rating.PerformanceRating, rating.StoryRating);
|
||||
});
|
||||
|
||||
public static int UpdateBookStatus(this LibraryBook lb, LiberatedStatus bookStatus, Version? libationVersion, AudioFormat audioFormat, string audioVersion)
|
||||
=> lb.UpdateUserDefinedItem(udi => { udi.BookStatus = bookStatus; udi.SetLastDownloaded(libationVersion, audioFormat, audioVersion); });
|
||||
public static async Task<int> UpdateBookStatusAsync(this LibraryBook lb, LiberatedStatus bookStatus, Version? libationVersion, AudioFormat audioFormat, string audioVersion)
|
||||
=> await lb.UpdateUserDefinedItemAsync(udi => { udi.BookStatus = bookStatus; udi.SetLastDownloaded(libationVersion, audioFormat, audioVersion); });
|
||||
|
||||
public static int UpdateBookStatus(this LibraryBook libraryBook, LiberatedStatus bookStatus)
|
||||
=> libraryBook.UpdateUserDefinedItem(udi => udi.BookStatus = bookStatus);
|
||||
public static int UpdateBookStatus(this IEnumerable<LibraryBook> libraryBooks, LiberatedStatus bookStatus)
|
||||
=> libraryBooks.UpdateUserDefinedItem(udi => udi.BookStatus = bookStatus);
|
||||
public static async Task<int> UpdateBookStatusAsync(this LibraryBook libraryBook, LiberatedStatus bookStatus)
|
||||
=> await libraryBook.UpdateUserDefinedItemAsync(udi => udi.BookStatus = bookStatus);
|
||||
public static async Task<int> UpdateBookStatusAsync(this IEnumerable<LibraryBook> libraryBooks, LiberatedStatus bookStatus)
|
||||
=> await libraryBooks.UpdateUserDefinedItemAsync(udi => udi.BookStatus = bookStatus);
|
||||
|
||||
public static int UpdatePdfStatus(this LibraryBook libraryBook, LiberatedStatus pdfStatus)
|
||||
=> libraryBook.UpdateUserDefinedItem(udi => udi.SetPdfStatus(pdfStatus));
|
||||
public static int UpdatePdfStatus(this IEnumerable<LibraryBook> libraryBooks, LiberatedStatus pdfStatus)
|
||||
=> libraryBooks.UpdateUserDefinedItem(udi => udi.SetPdfStatus(pdfStatus));
|
||||
public static async Task<int> UpdatePdfStatusAsync(this LibraryBook libraryBook, LiberatedStatus pdfStatus)
|
||||
=> await libraryBook.UpdateUserDefinedItemAsync(udi => udi.SetPdfStatus(pdfStatus));
|
||||
public static async Task<int> UpdatePdfStatusAsync(this IEnumerable<LibraryBook> libraryBooks, LiberatedStatus pdfStatus)
|
||||
=> await libraryBooks.UpdateUserDefinedItemAsync(udi => udi.SetPdfStatus(pdfStatus));
|
||||
|
||||
public static int UpdateTags(this LibraryBook libraryBook, string tags)
|
||||
=> libraryBook.UpdateUserDefinedItem(udi => udi.Tags = tags);
|
||||
public static int UpdateTags(this IEnumerable<LibraryBook> libraryBooks, string tags)
|
||||
=> libraryBooks.UpdateUserDefinedItem(udi => udi.Tags = tags);
|
||||
public static async Task<int> UpdateTagsAsync(this LibraryBook libraryBook, string tags)
|
||||
=> await libraryBook.UpdateUserDefinedItemAsync(udi => udi.Tags = tags);
|
||||
public static async Task<int> UpdateTagsAsync(this IEnumerable<LibraryBook> libraryBooks, string tags)
|
||||
=> await libraryBooks.UpdateUserDefinedItemAsync(udi => udi.Tags = tags);
|
||||
|
||||
public static int UpdateUserDefinedItem(this LibraryBook libraryBook, Action<UserDefinedItem> action)
|
||||
=> libraryBook.updateUserDefinedItem(action);
|
||||
public static int UpdateUserDefinedItem(this IEnumerable<LibraryBook> libraryBooks, Action<UserDefinedItem> action)
|
||||
=> libraryBooks.updateUserDefinedItem(action);
|
||||
public static async Task<int> UpdateUserDefinedItemAsync(this LibraryBook libraryBook, Action<UserDefinedItem> action)
|
||||
=> await UpdateUserDefinedItemAsync([libraryBook], action);
|
||||
|
||||
private static int updateUserDefinedItem(this LibraryBook libraryBook, Action<UserDefinedItem> action) => new[] { libraryBook }.updateUserDefinedItem(action);
|
||||
private static int updateUserDefinedItem(this IEnumerable<LibraryBook> libraryBooks, Action<UserDefinedItem> action)
|
||||
public static Task<int> UpdateUserDefinedItemAsync(this IEnumerable<LibraryBook> libraryBooks, Action<UserDefinedItem> action)
|
||||
=> Task.Run(() => libraryBooks.updateUserDefinedItem(action));
|
||||
|
||||
private static int updateUserDefinedItem(this IEnumerable<LibraryBook> libraryBooks, Action<UserDefinedItem> action)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (libraryBooks is null || !libraryBooks.Any())
|
||||
return 0;
|
||||
|
||||
using var context = DbContexts.GetContext();
|
||||
|
||||
// Entry() instead of Attach() due to possible stack overflow with large tables
|
||||
foreach (var book in libraryBooks)
|
||||
int qtyChanges;
|
||||
using (var context = DbContexts.GetContext())
|
||||
{
|
||||
action?.Invoke(book.Book.UserDefinedItem);
|
||||
// Entry() instead of Attach() due to possible stack overflow with large tables
|
||||
foreach (var book in libraryBooks)
|
||||
{
|
||||
action?.Invoke(book.Book.UserDefinedItem);
|
||||
|
||||
var udiEntity = context.Entry(book.Book.UserDefinedItem);
|
||||
var udiEntity = context.Entry(book.Book.UserDefinedItem);
|
||||
|
||||
udiEntity.State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
||||
if (udiEntity.Reference(udi => udi.Rating).TargetEntry is Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry<Rating> ratingEntry)
|
||||
ratingEntry.State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
||||
}
|
||||
udiEntity.State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
||||
if (udiEntity.Reference(udi => udi.Rating).TargetEntry is Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry<Rating> ratingEntry)
|
||||
ratingEntry.State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
||||
}
|
||||
|
||||
var qtyChanges = context.SaveChanges();
|
||||
qtyChanges = context.SaveChanges();
|
||||
}
|
||||
if (qtyChanges > 0)
|
||||
BookUserDefinedItemCommitted?.Invoke(null, libraryBooks);
|
||||
|
||||
|
||||
@@ -23,9 +23,7 @@ namespace FileLiberator
|
||||
var series = libraryBook.Book.SeriesLink.SingleOrDefault();
|
||||
if (series is not null)
|
||||
{
|
||||
using var context = ApplicationServices.DbContexts.GetContext();
|
||||
var seriesParent = context.GetLibraryBook_Flat_NoTracking(series.Series.AudibleSeriesId);
|
||||
|
||||
LibraryBook seriesParent = ApplicationServices.DbContexts.GetLibraryBook_Flat_NoTracking(series.Series.AudibleSeriesId);
|
||||
if (seriesParent is not null)
|
||||
{
|
||||
return Templates.Folder.GetFilename(seriesParent.ToDto(), AudibleFileStorage.BooksDirectory, "");
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace FileLiberator
|
||||
{
|
||||
if (moveFilesTask.IsCompletedSuccessfully && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
libraryBook.UpdateBookStatus(LiberatedStatus.Liberated, Configuration.LibationVersion, audioFormat, audioVersion);
|
||||
await libraryBook.UpdateBookStatusAsync(LiberatedStatus.Liberated, Configuration.LibationVersion, audioFormat, audioVersion);
|
||||
SetDirectoryTime(libraryBook, finalStorageDir);
|
||||
foreach (var cacheFile in result.CacheFiles.Where(f => File.Exists(f.FilePath)))
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace FileLiberator
|
||||
SetFileTime(libraryBook, actualDownloadedFilePath);
|
||||
SetDirectoryTime(libraryBook, Path.GetDirectoryName(actualDownloadedFilePath));
|
||||
}
|
||||
libraryBook.UpdatePdfStatus(result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated);
|
||||
await libraryBook.UpdatePdfStatusAsync(result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ public class TrashBinViewModel : ViewModelBase, IDisposable
|
||||
public async Task RestoreCheckedAsync()
|
||||
{
|
||||
ControlsEnabled = false;
|
||||
var qtyChanges = await Task.Run(CheckedBooks.RestoreBooks);
|
||||
var qtyChanges = await CheckedBooks.RestoreBooksAsync();
|
||||
if (qtyChanges > 0)
|
||||
Reload();
|
||||
ControlsEnabled = true;
|
||||
@@ -89,7 +89,7 @@ public class TrashBinViewModel : ViewModelBase, IDisposable
|
||||
public async Task PermanentlyDeleteCheckedAsync()
|
||||
{
|
||||
ControlsEnabled = false;
|
||||
var qtyChanges = await Task.Run(CheckedBooks.PermanentlyDeleteBooks);
|
||||
var qtyChanges = await CheckedBooks.PermanentlyDeleteBooksAsync();
|
||||
if (qtyChanges > 0)
|
||||
Reload();
|
||||
ControlsEnabled = true;
|
||||
@@ -97,7 +97,7 @@ public class TrashBinViewModel : ViewModelBase, IDisposable
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
var deletedBooks = DbContexts.GetContext().GetDeletedLibraryBooks();
|
||||
var deletedBooks = DbContexts.GetDeletedLibraryBooks();
|
||||
|
||||
DeletedBooks.Clear();
|
||||
DeletedBooks.AddRange(deletedBooks.Select(lb => new CheckBoxViewModel { Item = lb }));
|
||||
|
||||
@@ -39,19 +39,22 @@ namespace HangoverWinForms
|
||||
deletedCbl.SetItemChecked(i, false);
|
||||
}
|
||||
|
||||
private void saveBtn_Click(object sender, EventArgs e)
|
||||
private async void saveBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
var libraryBooksToRestore = deletedCbl.CheckedItems.Cast<LibraryBook>().ToList();
|
||||
var qtyChanges = libraryBooksToRestore.RestoreBooks();
|
||||
saveBtn.Enabled = false;
|
||||
var qtyChanges = await libraryBooksToRestore.RestoreBooksAsync();
|
||||
if (qtyChanges > 0)
|
||||
reload();
|
||||
}
|
||||
Invoke(reload);
|
||||
Invoke(() => saveBtn.Enabled = true);
|
||||
}
|
||||
|
||||
private void reload()
|
||||
{
|
||||
deletedCbl.Items.Clear();
|
||||
var deletedBooks = DbContexts.GetContext().GetDeletedLibraryBooks();
|
||||
foreach (var lb in deletedBooks)
|
||||
List<LibraryBook> deletedBooks = DbContexts.GetDeletedLibraryBooks();
|
||||
|
||||
foreach (var lb in deletedBooks)
|
||||
deletedCbl.Items.Add(lb);
|
||||
|
||||
setLabel();
|
||||
|
||||
@@ -10,6 +10,7 @@ using LibationFileManager;
|
||||
using ReactiveUI;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace LibationAvalonia.Dialogs
|
||||
@@ -58,10 +59,10 @@ namespace LibationAvalonia.Dialogs
|
||||
LibraryBook = libraryBook;
|
||||
}
|
||||
|
||||
protected override void SaveAndClose()
|
||||
protected override async Task SaveAndCloseAsync()
|
||||
{
|
||||
LibraryBook.UpdateUserDefinedItem(NewTags, bookStatus: BookLiberatedStatus, pdfStatus: PdfLiberatedStatus);
|
||||
base.SaveAndClose();
|
||||
await LibraryBook.UpdateUserDefinedItemAsync(NewTags, bookStatus: BookLiberatedStatus, pdfStatus: PdfLiberatedStatus);
|
||||
await base.SaveAndCloseAsync();
|
||||
}
|
||||
|
||||
public void BookStatus_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
|
||||
@@ -81,17 +81,15 @@ namespace LibationAvalonia.Dialogs
|
||||
return;
|
||||
}
|
||||
|
||||
using var context = DbContexts.GetContext();
|
||||
|
||||
await foreach (var book in AudioFileStorage.FindAudiobooksAsync(selectedFolder, tokenSource.Token))
|
||||
{
|
||||
try
|
||||
{
|
||||
FilePathCache.Insert(book);
|
||||
|
||||
var lb = context.GetLibraryBook_Flat_NoTracking(book.Id);
|
||||
var lb = DbContexts.GetLibraryBook_Flat_NoTracking(book.Id);
|
||||
if (lb is not null && lb.Book?.UserDefinedItem.BookStatus is not LiberatedStatus.Liberated)
|
||||
await Task.Run(() => lb.UpdateBookStatus(LiberatedStatus.Liberated));
|
||||
await lb.UpdateBookStatusAsync(LiberatedStatus.Liberated);
|
||||
|
||||
tokenSource.Token.ThrowIfCancellationRequested();
|
||||
FileFound?.Invoke(this, book);
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace LibationAvalonia.Dialogs
|
||||
public async Task RestoreCheckedAsync()
|
||||
{
|
||||
ControlsEnabled = false;
|
||||
var qtyChanges = await Task.Run(CheckedBooks.RestoreBooks);
|
||||
var qtyChanges = await CheckedBooks.RestoreBooksAsync();
|
||||
if (qtyChanges > 0)
|
||||
Reload();
|
||||
ControlsEnabled = true;
|
||||
@@ -113,7 +113,7 @@ namespace LibationAvalonia.Dialogs
|
||||
public async Task PermanentlyDeleteCheckedAsync()
|
||||
{
|
||||
ControlsEnabled = false;
|
||||
var qtyChanges = await Task.Run(CheckedBooks.PermanentlyDeleteBooks);
|
||||
var qtyChanges = await CheckedBooks.PermanentlyDeleteBooksAsync();
|
||||
if (qtyChanges > 0)
|
||||
Reload();
|
||||
ControlsEnabled = true;
|
||||
@@ -121,8 +121,7 @@ namespace LibationAvalonia.Dialogs
|
||||
|
||||
private void Reload()
|
||||
{
|
||||
using var context = DbContexts.GetContext();
|
||||
var deletedBooks = context.GetDeletedLibraryBooks();
|
||||
var deletedBooks = DbContexts.GetDeletedLibraryBooks();
|
||||
|
||||
DeletedBooks.Clear();
|
||||
DeletedBooks.AddRange(deletedBooks.Select(lb => new CheckBoxViewModel { Item = lb }));
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace LibationAvalonia.ViewModels
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
visibleLibraryBooks.UpdateTags(dialog.NewTags);
|
||||
await visibleLibraryBooks.UpdateTagsAsync(dialog.NewTags);
|
||||
}
|
||||
|
||||
public async Task SetBookDownloadedAsync()
|
||||
@@ -124,7 +124,7 @@ namespace LibationAvalonia.ViewModels
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
visibleLibraryBooks.UpdateBookStatus(dialog.BookLiberatedStatus);
|
||||
await visibleLibraryBooks.UpdateBookStatusAsync(dialog.BookLiberatedStatus);
|
||||
}
|
||||
|
||||
public async Task SetPdfDownloadedAsync()
|
||||
@@ -146,7 +146,7 @@ namespace LibationAvalonia.ViewModels
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
visibleLibraryBooks.UpdatePdfStatus(dialog.BookLiberatedStatus);
|
||||
await visibleLibraryBooks.UpdatePdfStatusAsync(dialog.BookLiberatedStatus);
|
||||
}
|
||||
|
||||
public async Task SetDownloadedAutoAsync()
|
||||
@@ -172,7 +172,7 @@ namespace LibationAvalonia.ViewModels
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
bulkSetStatus.Execute();
|
||||
await bulkSetStatus.ExecuteAsync();
|
||||
}
|
||||
|
||||
public async Task RemoveVisibleAsync()
|
||||
|
||||
@@ -25,8 +25,7 @@ internal class GetLicenseOptions : OptionsBase
|
||||
return;
|
||||
}
|
||||
|
||||
using var dbContext = DbContexts.GetContext();
|
||||
if (dbContext.GetLibraryBook_Flat_NoTracking(Asin) is not LibraryBook libraryBook)
|
||||
if (DbContexts.GetLibraryBook_Flat_NoTracking(Asin) is not LibraryBook libraryBook)
|
||||
{
|
||||
Console.Error.WriteLine($"Book not found with asin={Asin}");
|
||||
return;
|
||||
|
||||
@@ -53,15 +53,10 @@ namespace LibationCli
|
||||
return;
|
||||
}
|
||||
|
||||
LibraryBook libraryBook;
|
||||
using (var dbContext = DbContexts.GetContext())
|
||||
if (DbContexts.GetLibraryBook_Flat_NoTracking(asin) is not LibraryBook libraryBook)
|
||||
{
|
||||
if (dbContext.GetLibraryBook_Flat_NoTracking(asin) is not LibraryBook lb)
|
||||
{
|
||||
Console.Error.WriteLine($"Book not found with asin={asin}");
|
||||
return;
|
||||
}
|
||||
libraryBook = lb;
|
||||
Console.Error.WriteLine($"Book not found with asin={asin}");
|
||||
return;
|
||||
}
|
||||
|
||||
SetDownloadedStatus(libraryBook);
|
||||
|
||||
@@ -53,14 +53,14 @@ namespace LibationCli
|
||||
{
|
||||
var status = SetDownloaded ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated;
|
||||
|
||||
var num = libraryBooks.UpdateBookStatus(status);
|
||||
var num = await libraryBooks.UpdateBookStatusAsync(status);
|
||||
Console.WriteLine($"Set LiberatedStatus to '{status}' on {"book".PluralizeWithCount(num)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var bulkSetStatus = new BulkSetDownloadStatus(libraryBooks, SetDownloaded, SetNotDownloaded);
|
||||
await Task.Run(() => bulkSetStatus.Discover());
|
||||
bulkSetStatus.Execute();
|
||||
await bulkSetStatus.ExecuteAsync();
|
||||
|
||||
foreach (var msg in bulkSetStatus.Messages)
|
||||
Console.WriteLine(msg);
|
||||
|
||||
@@ -77,12 +77,7 @@ namespace LibationCli
|
||||
{
|
||||
foreach (var asin in Asins.Select(a => a.TrimStart('[').TrimEnd(']')))
|
||||
{
|
||||
LibraryBook? lb = null;
|
||||
using (var dbContext = DbContexts.GetContext())
|
||||
{
|
||||
lb = dbContext.GetLibraryBook_Flat_NoTracking(asin, caseSensative: false);
|
||||
}
|
||||
if (lb is not null)
|
||||
if (DbContexts.GetLibraryBook_Flat_NoTracking(asin, caseSensative: false) is LibraryBook lb)
|
||||
{
|
||||
config?.Invoke(lb);
|
||||
await ProcessOneAsync(Processable, lb, true);
|
||||
|
||||
@@ -57,7 +57,7 @@ public class GridContextMenu
|
||||
public void SetDownloaded()
|
||||
{
|
||||
LibraryBookEntries.Select(e => e.LibraryBook)
|
||||
.UpdateUserDefinedItem(udi =>
|
||||
.UpdateUserDefinedItemAsync(udi =>
|
||||
{
|
||||
udi.BookStatus = LiberatedStatus.Liberated;
|
||||
if (udi.Book.HasPdf())
|
||||
@@ -68,7 +68,7 @@ public class GridContextMenu
|
||||
public void SetNotDownloaded()
|
||||
{
|
||||
LibraryBookEntries.Select(e => e.LibraryBook)
|
||||
.UpdateUserDefinedItem(udi =>
|
||||
.UpdateUserDefinedItemAsync(udi =>
|
||||
{
|
||||
udi.BookStatus = LiberatedStatus.NotLiberated;
|
||||
if (udi.Book.HasPdf())
|
||||
@@ -90,8 +90,7 @@ public class GridContextMenu
|
||||
Configuration.Instance.SavePodcastsToParentFolder &&
|
||||
libraryBook.Book.SeriesLink.SingleOrDefault() is SeriesBook series)
|
||||
{
|
||||
using var context = DbContexts.GetContext();
|
||||
var seriesParent = context.GetLibraryBook_Flat_NoTracking(series.Series.AudibleSeriesId);
|
||||
var seriesParent = DbContexts.GetLibraryBook_Flat_NoTracking(series.Series.AudibleSeriesId);
|
||||
folderDto = seriesParent?.ToDto() ?? fileDto;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace LibationUiBase.GridView
|
||||
var api = await LibraryBook.GetApiAsync();
|
||||
|
||||
if (await api.ReviewAsync(Book.AudibleProductId, (int)rating.OverallRating, (int)rating.PerformanceRating, (int)rating.StoryRating))
|
||||
LibraryBook.UpdateUserDefinedItem(Book.UserDefinedItem.Tags, Book.UserDefinedItem.BookStatus, Book.UserDefinedItem.PdfStatus, rating);
|
||||
await LibraryBook.UpdateUserDefinedItemAsync(Book.UserDefinedItem.Tags, Book.UserDefinedItem.BookStatus, Book.UserDefinedItem.PdfStatus, rating);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -364,7 +364,7 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
|
||||
if (dialogResult == SkipResult)
|
||||
{
|
||||
libraryBook.UpdateBookStatus(LiberatedStatus.Error);
|
||||
await libraryBook.UpdateBookStatusAsync(LiberatedStatus.Error);
|
||||
LogInfo($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.TitleWithSubtitle}");
|
||||
}
|
||||
|
||||
|
||||
@@ -277,7 +277,7 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
else if (result == ProcessBookResult.FailedAbort)
|
||||
Queue.ClearQueue();
|
||||
else if (result == ProcessBookResult.FailedSkip)
|
||||
nextBook.LibraryBook.UpdateBookStatus(LiberatedStatus.Error);
|
||||
await nextBook.LibraryBook.UpdateBookStatusAsync(LiberatedStatus.Error);
|
||||
else if (result == ProcessBookResult.LicenseDeniedPossibleOutage && !shownServiceOutageMessage)
|
||||
{
|
||||
await MessageBoxBase.Show($"""
|
||||
|
||||
@@ -70,9 +70,8 @@ namespace LibationUiBase.SeriesView
|
||||
|
||||
if (await api.RemoveItemFromLibraryAsync(Item.ProductId))
|
||||
{
|
||||
using var context = DbContexts.GetContext();
|
||||
var lb = context.GetLibraryBook_Flat_NoTracking(Item.ProductId);
|
||||
int result = await Task.Run((new[] { lb }).PermanentlyDeleteBooks);
|
||||
var lb = DbContexts.GetLibraryBook_Flat_NoTracking(Item.ProductId);
|
||||
int result = await LibraryCommands.PermanentlyDeleteBooksAsync([lb]);
|
||||
InLibrary = result == 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,17 +71,15 @@ namespace LibationWinForms.Dialogs
|
||||
return;
|
||||
}
|
||||
|
||||
using var context = DbContexts.GetContext();
|
||||
|
||||
await foreach (var book in AudioFileStorage.FindAudiobooksAsync(fbd.SelectedPath, tokenSource.Token))
|
||||
{
|
||||
try
|
||||
{
|
||||
FilePathCache.Insert(book);
|
||||
|
||||
var lb = context.GetLibraryBook_Flat_NoTracking(book.Id);
|
||||
var lb = DbContexts.GetLibraryBook_Flat_NoTracking(book.Id);
|
||||
if (lb.Book.UserDefinedItem.BookStatus is not LiberatedStatus.Liberated)
|
||||
await Task.Run(() => lb.UpdateBookStatus(LiberatedStatus.Liberated));
|
||||
await lb.UpdateBookStatusAsync(LiberatedStatus.Liberated);
|
||||
|
||||
tokenSource.Token.ThrowIfCancellationRequested();
|
||||
this.Invoke(FileFound, this, book);
|
||||
|
||||
@@ -23,8 +23,7 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
deletedCheckedTemplate = deletedCheckedLbl.Text;
|
||||
|
||||
using var context = DbContexts.GetContext();
|
||||
var deletedBooks = context.GetDeletedLibraryBooks();
|
||||
var deletedBooks = DbContexts.GetDeletedLibraryBooks();
|
||||
foreach (var lb in deletedBooks)
|
||||
deletedCbl.Items.Add(lb);
|
||||
|
||||
@@ -44,7 +43,7 @@ namespace LibationWinForms.Dialogs
|
||||
var removed = deletedCbl.CheckedItems.Cast<LibraryBook>().ToList();
|
||||
|
||||
removeFromCheckList(removed);
|
||||
await Task.Run(removed.PermanentlyDeleteBooks);
|
||||
await removed.PermanentlyDeleteBooksAsync();
|
||||
|
||||
setControlsEnabled(true);
|
||||
}
|
||||
@@ -56,7 +55,7 @@ namespace LibationWinForms.Dialogs
|
||||
var removed = deletedCbl.CheckedItems.Cast<LibraryBook>().ToList();
|
||||
|
||||
removeFromCheckList(removed);
|
||||
await Task.Run(removed.RestoreBooks);
|
||||
await removed.RestoreBooksAsync();
|
||||
|
||||
setControlsEnabled(true);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace LibationWinForms
|
||||
}
|
||||
}
|
||||
|
||||
private void replaceTagsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
private async void replaceTagsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
var dialog = new TagsBatchDialog();
|
||||
var result = dialog.ShowDialog();
|
||||
@@ -86,10 +86,10 @@ namespace LibationWinForms
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
visibleLibraryBooks.UpdateTags(dialog.NewTags);
|
||||
await visibleLibraryBooks.UpdateTagsAsync(dialog.NewTags);
|
||||
}
|
||||
|
||||
private void setBookDownloadedManualToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
private async void setBookDownloadedManualToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
var dialog = new LiberatedStatusBatchManualDialog();
|
||||
var result = dialog.ShowDialog();
|
||||
@@ -107,10 +107,10 @@ namespace LibationWinForms
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
visibleLibraryBooks.UpdateBookStatus(dialog.BookLiberatedStatus);
|
||||
await visibleLibraryBooks.UpdateBookStatusAsync(dialog.BookLiberatedStatus);
|
||||
}
|
||||
|
||||
private void setPdfDownloadedManualToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
private async void setPdfDownloadedManualToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
var dialog = new LiberatedStatusBatchManualDialog(isPdf: true);
|
||||
var result = dialog.ShowDialog();
|
||||
@@ -128,7 +128,7 @@ namespace LibationWinForms
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
visibleLibraryBooks.UpdatePdfStatus(dialog.BookLiberatedStatus);
|
||||
await visibleLibraryBooks.UpdatePdfStatusAsync(dialog.BookLiberatedStatus);
|
||||
}
|
||||
|
||||
private async void setDownloadedAutoToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
@@ -154,7 +154,7 @@ namespace LibationWinForms
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
bulkSetStatus.Execute();
|
||||
await bulkSetStatus.ExecuteAsync();
|
||||
}
|
||||
|
||||
private async void removeToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace LibationWinForms.GridView
|
||||
gridEntryDataGridView.CellContextMenuStripNeeded += GridEntryDataGridView_CellContextMenuStripNeeded;
|
||||
removeGVColumn.Frozen = false;
|
||||
|
||||
defaultFont = gridEntryDataGridView.DefaultCellStyle.Font;
|
||||
defaultFont = gridEntryDataGridView.DefaultCellStyle.Font ?? gridEntryDataGridView.Font;
|
||||
setGridFontScale(Configuration.Instance.GridFontScaleFactor);
|
||||
setGridScale(Configuration.Instance.GridScaleFactor);
|
||||
Configuration.Instance.PropertyChanged += Configuration_ScaleChanged;
|
||||
|
||||
Reference in New Issue
Block a user