cross thread issue. add temp time logging in ImportAccountAsync

This commit is contained in:
Robert McRackan 2021-09-03 11:36:55 -04:00
parent dccb2d73d6
commit 57fa1bd763
7 changed files with 71 additions and 27 deletions

View File

@ -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<timeLogEntry> __log { get; } = new List<timeLogEntry> { 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 #region FULL LIBRARY scan and import
public static async Task<(int totalCount, int newCount)> ImportAccountAsync(Func<Account, ILoginCallback> loginCallbackFactoryFunc, params Account[] accounts) public static async Task<(int totalCount, int newCount)> ImportAccountAsync(Func<Account, ILoginCallback> loginCallbackFactoryFunc, params Account[] accounts)
{ {
sw.Start();
if (accounts is null || accounts.Length == 0) if (accounts is null || accounts.Length == 0)
return (0, 0); return (0, 0);
try try
{ {
logTime($"pre {nameof(scanAccountsAsync)} all");
var importItems = await scanAccountsAsync(loginCallbackFactoryFunc, accounts); var importItems = await scanAccountsAsync(loginCallbackFactoryFunc, accounts);
logTime($"post {nameof(scanAccountsAsync)} all");
var totalCount = importItems.Count; var totalCount = importItems.Count;
Log.Logger.Information($"GetAllLibraryItems: Total count {totalCount}"); Log.Logger.Information($"GetAllLibraryItems: Total count {totalCount}");
logTime($"pre {nameof(importIntoDbAsync)}");
var newCount = await importIntoDbAsync(importItems); var newCount = await importIntoDbAsync(importItems);
logTime($"post {nameof(importIntoDbAsync)}");
Log.Logger.Information($"Import: New count {newCount}"); Log.Logger.Information($"Import: New count {newCount}");
return (totalCount, newCount); return (totalCount, newCount);
@ -106,6 +125,14 @@ namespace ApplicationServices
Log.Logger.Error(ex, "Error importing library"); Log.Logger.Error(ex, "Error importing library");
throw; 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<List<ImportItem>> scanAccountsAsync(Func<Account, ILoginCallback> loginCallbackFactoryFunc, Account[] accounts) private static async Task<List<ImportItem>> scanAccountsAsync(Func<Account, ILoginCallback> loginCallbackFactoryFunc, Account[] accounts)
@ -137,19 +164,28 @@ namespace ApplicationServices
Account = account?.MaskedLogEntry ?? "[null]" Account = account?.MaskedLogEntry ?? "[null]"
}); });
logTime($"pre scanAccountAsync {account.AccountName}");
var dtoItems = await AudibleApiActions.GetLibraryValidatedAsync(api, LibraryResponseGroups); 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(); return dtoItems.Select(d => new ImportItem { DtoItem = d, AccountId = account.AccountId, LocaleName = account.Locale?.Name }).ToList();
} }
private static async Task<int> importIntoDbAsync(List<ImportItem> importItems) private static async Task<int> importIntoDbAsync(List<ImportItem> importItems)
{ {
logTime("importIntoDbAsync -- pre db");
using var context = DbContexts.GetContext(); using var context = DbContexts.GetContext();
var libraryImporter = new LibraryImporter(context); var libraryImporter = new LibraryBookImporter(context);
var newCount = await Task.Run(() => libraryImporter.Import(importItems)); var newCount = await Task.Run(() => libraryImporter.Import(importItems));
logTime("importIntoDbAsync -- post Import()");
var qtyChanges = context.SaveChanges(); var qtyChanges = context.SaveChanges();
logTime("importIntoDbAsync -- post SaveChanges");
if (qtyChanges > 0) if (qtyChanges > 0)
await Task.Run(() => finalizeLibrarySizeChange()); await Task.Run(() => finalizeLibrarySizeChange());
logTime("importIntoDbAsync -- post finalizeLibrarySizeChange");
return newCount; return newCount;
} }
@ -215,15 +251,14 @@ namespace ApplicationServices
} }
#endregion #endregion
// must be here instead of in db layer due to AaxcExists
public static LiberatedStatus Liberated_Status(Book book) 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 : FileManager.AudibleFileStorage.AaxcExists(book.AudibleProductId) ? LiberatedStatus.PartialDownload
: LiberatedStatus.NotLiberated; : LiberatedStatus.NotLiberated;
public static LiberatedStatus? Pdf_Status(Book book) // exists here for feature predictability. It makes sense for this to be where Liberated_Status is
=> !book.HasPdf ? null public static LiberatedStatus? Pdf_Status(Book book) => book.UserDefinedItem.PdfStatus;
: book.PDF_Exists ? LiberatedStatus.Liberated
: LiberatedStatus.NotLiberated;
// 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...

View File

@ -57,22 +57,10 @@ namespace DataLayer
public UserDefinedItem UserDefinedItem { get; private set; } public UserDefinedItem UserDefinedItem { get; private set; }
// UserDefinedItem convenience properties // UserDefinedItem convenience properties
public bool Audio_Exists /// <summary>True if IsLiberated or Error. False if NotLiberated</summary>
{ public bool Audio_Exists => UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated;
get /// <summary>True if exists and IsLiberated. Else false</summary>
{ public bool PDF_Exists => UserDefinedItem.PdfStatus == LiberatedStatus.Liberated;
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);
}
}
// is owned, not optional 1:1 // is owned, not optional 1:1
/// <summary>The product's aggregate community rating</summary> /// <summary>The product's aggregate community rating</summary>

View File

@ -39,6 +39,24 @@ namespace DtoImporterService
.Except(localProductIds) .Except(localProductIds)
.ToList(); .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<TSource>([NotNullAttribute] this IQueryable<TSource> source);
*/
#endregion
// GetBooks() eager loads Series, category, et al // GetBooks() eager loads Series, category, et al
if (remainingProductIds.Any()) if (remainingProductIds.Any())
DbContext.Books.GetBooks(b => remainingProductIds.Contains(b.AudibleProductId)).ToList(); DbContext.Books.GetBooks(b => remainingProductIds.Contains(b.AudibleProductId)).ToList();

View File

@ -7,9 +7,9 @@ using InternalUtilities;
namespace DtoImporterService 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<Exception> Validate(IEnumerable<ImportItem> importItems) => new LibraryValidator().Validate(importItems.Select(i => i.DtoItem)); public override IEnumerable<Exception> Validate(IEnumerable<ImportItem> importItems) => new LibraryValidator().Validate(importItems.Select(i => i.DtoItem));

View File

@ -13,7 +13,7 @@
<!-- <PublishSingleFile>true</PublishSingleFile> --> <!-- <PublishSingleFile>true</PublishSingleFile> -->
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<Version>5.6.3.1</Version> <Version>5.6.3.6</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

View File

@ -138,7 +138,7 @@ namespace LibationWinForms
isProcessingGridSelect = prev_isProcessingGridSelect; isProcessingGridSelect = prev_isProcessingGridSelect;
// UI init complete. now we can apply filter // UI init complete. now we can apply filter
doFilter(lastGoodFilter); this.UIThread(() => doFilter(lastGoodFilter));
setBackupCounts(null, null); setBackupCounts(null, null);
} }

View File

@ -80,7 +80,10 @@ namespace LibationWinForms
{ {
var filePath = FileManager.AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId); var filePath = FileManager.AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId);
if (!Go.To.File(filePath)) 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; return;
} }