diff --git a/ApplicationServices/LibraryCommands.cs b/ApplicationServices/LibraryCommands.cs index 724fc937..2c438d2a 100644 --- a/ApplicationServices/LibraryCommands.cs +++ b/ApplicationServices/LibraryCommands.cs @@ -65,20 +65,39 @@ namespace ApplicationServices } } + static System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); + record timeLogEntry(string msg, long totalElapsed, long delta); + static List __log { get; } = new List { new("begin", 0, 0) }; + static void logTime(string s) + { + var totalElapsed = sw.ElapsedMilliseconds; + + var prev = __log.Last().totalElapsed; + var delta = totalElapsed - prev; + + __log.Add(new(s, totalElapsed, delta)); + } + #region FULL LIBRARY scan and import public static async Task<(int totalCount, int newCount)> ImportAccountAsync(Func loginCallbackFactoryFunc, params Account[] accounts) { + sw.Start(); + if (accounts is null || accounts.Length == 0) return (0, 0); try { + logTime($"pre {nameof(scanAccountsAsync)} all"); var importItems = await scanAccountsAsync(loginCallbackFactoryFunc, accounts); + logTime($"post {nameof(scanAccountsAsync)} all"); var totalCount = importItems.Count; Log.Logger.Information($"GetAllLibraryItems: Total count {totalCount}"); + logTime($"pre {nameof(importIntoDbAsync)}"); var newCount = await importIntoDbAsync(importItems); + logTime($"post {nameof(importIntoDbAsync)}"); Log.Logger.Information($"Import: New count {newCount}"); return (totalCount, newCount); @@ -106,6 +125,14 @@ namespace ApplicationServices Log.Logger.Error(ex, "Error importing library"); throw; } + finally + { + sw.Stop(); + var logOutput + = $"{nameof(timeLogEntry.msg)}\t{nameof(timeLogEntry.totalElapsed)}\t{nameof(timeLogEntry.delta)}\r\n" + + __log.Select(t => $"{t.msg}\t{t.totalElapsed}\t{t.delta}").Aggregate((a, b) => $"{a}\r\n{b}"); + var putBreakPointHere = logOutput; + } } private static async Task> scanAccountsAsync(Func loginCallbackFactoryFunc, Account[] accounts) @@ -137,19 +164,28 @@ namespace ApplicationServices Account = account?.MaskedLogEntry ?? "[null]" }); + logTime($"pre scanAccountAsync {account.AccountName}"); + var dtoItems = await AudibleApiActions.GetLibraryValidatedAsync(api, LibraryResponseGroups); + + logTime($"post scanAccountAsync {account.AccountName} qty: {dtoItems.Count}"); + return dtoItems.Select(d => new ImportItem { DtoItem = d, AccountId = account.AccountId, LocaleName = account.Locale?.Name }).ToList(); } private static async Task importIntoDbAsync(List importItems) { + logTime("importIntoDbAsync -- pre db"); using var context = DbContexts.GetContext(); - var libraryImporter = new LibraryImporter(context); + var libraryImporter = new LibraryBookImporter(context); var newCount = await Task.Run(() => libraryImporter.Import(importItems)); + logTime("importIntoDbAsync -- post Import()"); var qtyChanges = context.SaveChanges(); + logTime("importIntoDbAsync -- post SaveChanges"); if (qtyChanges > 0) await Task.Run(() => finalizeLibrarySizeChange()); + logTime("importIntoDbAsync -- post finalizeLibrarySizeChange"); return newCount; } @@ -215,15 +251,14 @@ namespace ApplicationServices } #endregion + // must be here instead of in db layer due to AaxcExists public static LiberatedStatus Liberated_Status(Book book) - => book.Audio_Exists ? LiberatedStatus.Liberated + => book.Audio_Exists ? book.UserDefinedItem.BookStatus : FileManager.AudibleFileStorage.AaxcExists(book.AudibleProductId) ? LiberatedStatus.PartialDownload : LiberatedStatus.NotLiberated; - public static LiberatedStatus? Pdf_Status(Book book) - => !book.HasPdf ? null - : book.PDF_Exists ? LiberatedStatus.Liberated - : LiberatedStatus.NotLiberated; + // exists here for feature predictability. It makes sense for this to be where Liberated_Status is + public static LiberatedStatus? Pdf_Status(Book book) => book.UserDefinedItem.PdfStatus; // below are queries, not commands. maybe I should make a LibraryQueries. except there's already one of those... diff --git a/DataLayer/EfClasses/Book.cs b/DataLayer/EfClasses/Book.cs index 45fee3d2..4334023d 100644 --- a/DataLayer/EfClasses/Book.cs +++ b/DataLayer/EfClasses/Book.cs @@ -57,22 +57,10 @@ namespace DataLayer public UserDefinedItem UserDefinedItem { get; private set; } // UserDefinedItem convenience properties - public bool Audio_Exists - { - get - { - var status = UserDefinedItem?.BookStatus; - return status.HasValue && status.Value != LiberatedStatus.NotLiberated; - } - } - public bool PDF_Exists - { - get - { - var status = UserDefinedItem?.PdfStatus; - return (status.HasValue && status.Value == LiberatedStatus.Liberated); - } - } + /// True if IsLiberated or Error. False if NotLiberated + public bool Audio_Exists => UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated; + /// True if exists and IsLiberated. Else false + public bool PDF_Exists => UserDefinedItem.PdfStatus == LiberatedStatus.Liberated; // is owned, not optional 1:1 /// The product's aggregate community rating diff --git a/DtoImporterService/BookImporter.cs b/DtoImporterService/BookImporter.cs index cb0f7ccb..bb0a874f 100644 --- a/DtoImporterService/BookImporter.cs +++ b/DtoImporterService/BookImporter.cs @@ -39,6 +39,24 @@ namespace DtoImporterService .Except(localProductIds) .ToList(); + #region // explanation of DbContext.Books.GetBooks(b => remainingProductIds.Contains(b.AudibleProductId)).ToList(); + /* + articles suggest loading to Local with + context.Books.Load(); + we want Books and associated fields + context.Books.GetBooks(b => remainingProductIds.Contains(b.AudibleProductId)).ToList(); + this is emulating Load() but with also getting associated fields + + from: Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions + // Summary: + // Enumerates the query. When using Entity Framework, this causes the results of + // the query to be loaded into the associated context. This is equivalent to calling + // ToList and then throwing away the list (without the overhead of actually creating + // the list). + public static void Load([NotNullAttribute] this IQueryable source); + */ + #endregion + // GetBooks() eager loads Series, category, et al if (remainingProductIds.Any()) DbContext.Books.GetBooks(b => remainingProductIds.Contains(b.AudibleProductId)).ToList(); diff --git a/DtoImporterService/LibraryImporter.cs b/DtoImporterService/LibraryBookImporter.cs similarity index 93% rename from DtoImporterService/LibraryImporter.cs rename to DtoImporterService/LibraryBookImporter.cs index 30e586ae..b416db4c 100644 --- a/DtoImporterService/LibraryImporter.cs +++ b/DtoImporterService/LibraryBookImporter.cs @@ -7,9 +7,9 @@ using InternalUtilities; namespace DtoImporterService { - public class LibraryImporter : ItemsImporterBase + public class LibraryBookImporter : ItemsImporterBase { - public LibraryImporter(LibationContext context) : base(context) { } + public LibraryBookImporter(LibationContext context) : base(context) { } public override IEnumerable Validate(IEnumerable importItems) => new LibraryValidator().Validate(importItems.Select(i => i.DtoItem)); diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index 31333725..675b4d8d 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.6.3.1 + 5.6.3.6 diff --git a/LibationWinForms/Form1.cs b/LibationWinForms/Form1.cs index 1430cee3..d1891266 100644 --- a/LibationWinForms/Form1.cs +++ b/LibationWinForms/Form1.cs @@ -138,7 +138,7 @@ namespace LibationWinForms isProcessingGridSelect = prev_isProcessingGridSelect; // UI init complete. now we can apply filter - doFilter(lastGoodFilter); + this.UIThread(() => doFilter(lastGoodFilter)); setBackupCounts(null, null); } diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs index 56409792..2f7a3196 100644 --- a/LibationWinForms/ProductsGrid.cs +++ b/LibationWinForms/ProductsGrid.cs @@ -80,7 +80,10 @@ namespace LibationWinForms { var filePath = FileManager.AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId); if (!Go.To.File(filePath)) - MessageBox.Show($"File not found:\r\n{filePath}"); + { + var suffix = string.IsNullOrWhiteSpace(filePath) ? "" : $":\r\n{filePath}"; + MessageBox.Show($"File not found" + suffix); + } return; }