diff --git a/DtoImporterService/BookImporter.cs b/DtoImporterService/BookImporter.cs index 8f59c837..5874e213 100644 --- a/DtoImporterService/BookImporter.cs +++ b/DtoImporterService/BookImporter.cs @@ -51,77 +51,81 @@ namespace DtoImporterService var book = context.Books.Local.SingleOrDefault(p => p.AudibleProductId == item.ProductId); if (book is null) { - // nested logic is required so order of names is retained. else, contributors may appear in the order they were inserted into the db - var authors = item - .Authors - .Select(a => context.Contributors.Local.Single(c => a.Name == c.Name)) - .ToList(); - - // if no narrators listed, author is the narrator - if (item.Narrators is null || !item.Narrators.Any()) - item.Narrators = item.Authors; - // nested logic is required so order of names is retained. else, contributors may appear in the order they were inserted into the db - var narrators = item - .Narrators - .Select(n => context.Contributors.Local.Single(c => n.Name == c.Name)) - .ToList(); - - book = context.Books.Add(new Book( - new AudibleProductId(item.ProductId), - item.Title, - item.Description, - item.LengthInMinutes, - authors, - narrators) - ).Entity; - - var publisherName = item.Publisher; - if (!string.IsNullOrWhiteSpace(publisherName)) - { - var publisher = context.Contributors.Local.Single(c => publisherName == c.Name); - book.ReplacePublisher(publisher); - } - + book = createNewBook(item, context); qtyNew++; } - // set/update book-specific info which may have changed - book.PictureId = item.PictureId; - book.UpdateProductRating(item.Product_OverallStars, item.Product_PerformanceStars, item.Product_StoryStars); - if (!string.IsNullOrWhiteSpace(item.SupplementUrl)) - book.AddSupplementDownloadUrl(item.SupplementUrl); - - // important to update user-specific info. this will have changed if user has rated/reviewed the book since last library import - book.UserDefinedItem.UpdateRating(item.MyUserRating_Overall, item.MyUserRating_Performance, item.MyUserRating_Story); - - // - // this was round 1 when it was a 2 step process - // - //// update series even for existing books. these are occasionally updated - //var seriesIds = item.Series.Select(kvp => kvp.SeriesId).ToList(); - //var allSeries = context.Series.Local.Where(c => seriesIds.Contains(c.AudibleSeriesId)).ToList(); - //foreach (var series in allSeries) - // book.UpsertSeries(series); - - // these will upsert over library-scraped series, but will not leave orphans - if (item.Series != null) - { - foreach (var seriesEntry in item.Series) - { - var series = context.Series.Local.Single(s => seriesEntry.SeriesId == s.AudibleSeriesId); - book.UpsertSeries(series, seriesEntry.Index); - } - } - - // categories are laid out for a breadcrumb. category is 1st, subcategory is 2nd - var category = context.Categories.Local.SingleOrDefault(c => c.AudibleCategoryId == item.Categories.LastOrDefault().CategoryId); - if (category != null) - book.UpdateCategory(category, context); - - book.UpdateBookDetails(item.IsAbridged, item.DatePublished); + updateBook(item, book, context); } return qtyNew; } + + private static Book createNewBook(Item item, LibationContext context) + { + // nested logic is required so order of names is retained. else, contributors may appear in the order they were inserted into the db + var authors = item + .Authors + .Select(a => context.Contributors.Local.Single(c => a.Name == c.Name)) + .ToList(); + + // if no narrators listed, author is the narrator + if (item.Narrators is null || !item.Narrators.Any()) + item.Narrators = item.Authors; + // nested logic is required so order of names is retained. else, contributors may appear in the order they were inserted into the db + var narrators = item + .Narrators + .Select(n => context.Contributors.Local.Single(c => n.Name == c.Name)) + .ToList(); + + var book = context.Books.Add(new Book( + new AudibleProductId(item.ProductId), + item.Title, + item.Description, + item.LengthInMinutes, + authors, + narrators) + ).Entity; + + var publisherName = item.Publisher; + if (!string.IsNullOrWhiteSpace(publisherName)) + { + var publisher = context.Contributors.Local.Single(c => publisherName == c.Name); + book.ReplacePublisher(publisher); + } + + // categories are laid out for a breadcrumb. category is 1st, subcategory is 2nd + var category = context.Categories.Local.SingleOrDefault(c => c.AudibleCategoryId == item.Categories.LastOrDefault().CategoryId); + if (category != null) + book.UpdateCategory(category, context); + + book.UpdateBookDetails(item.IsAbridged, item.DatePublished); + + if (!string.IsNullOrWhiteSpace(item.SupplementUrl)) + book.AddSupplementDownloadUrl(item.SupplementUrl); + + return book; + } + + private static void updateBook(Item item, Book book, LibationContext context) + { + // set/update book-specific info which may have changed + book.PictureId = item.PictureId; + book.UpdateProductRating(item.Product_OverallStars, item.Product_PerformanceStars, item.Product_StoryStars); + + // important to update user-specific info. this will have changed if user has rated/reviewed the book since last library import + book.UserDefinedItem.UpdateRating(item.MyUserRating_Overall, item.MyUserRating_Performance, item.MyUserRating_Story); + + // update series even for existing books. these are occasionally updated + // these will upsert over library-scraped series, but will not leave orphans + if (item.Series != null) + { + foreach (var seriesEntry in item.Series) + { + var series = context.Series.Local.Single(s => seriesEntry.SeriesId == s.AudibleSeriesId); + book.UpsertSeries(series, seriesEntry.Index); + } + } + } } } diff --git a/DtoImporterService/ContributorImporter.cs b/DtoImporterService/ContributorImporter.cs index 16da65f8..77126008 100644 --- a/DtoImporterService/ContributorImporter.cs +++ b/DtoImporterService/ContributorImporter.cs @@ -19,10 +19,10 @@ namespace DtoImporterService var publishers = items.GetPublishersDistinct().ToList(); // load db existing => .Local - var allNames = authors - .Select(a => a.Name) + var allNames = publishers + .Union(authors.Select(n => n.Name)) .Union(narrators.Select(n => n.Name)) - .Union(publishers) + .Where(name => !string.IsNullOrWhiteSpace(name)) .ToList(); loadLocal_contributors(allNames, context); @@ -36,9 +36,6 @@ namespace DtoImporterService private void loadLocal_contributors(List contributorNames, LibationContext context) { - contributorNames.Remove(null); - contributorNames.Remove(""); - //// BAD: very inefficient // var x = context.Contributors.Local.Where(c => !contribNames.Contains(c.Name)); diff --git a/DtoImporterService/ImporterBase.cs b/DtoImporterService/ImporterBase.cs index a7dfbb5d..6678f23b 100644 --- a/DtoImporterService/ImporterBase.cs +++ b/DtoImporterService/ImporterBase.cs @@ -20,12 +20,28 @@ namespace DtoImporterService } } - var exceptions = Validate(param); - if (exceptions != null && exceptions.Any()) - throw new AggregateException($"Device Jobs Service configuration validation failed", exceptions); + try + { + var exceptions = Validate(param); + if (exceptions != null && exceptions.Any()) + throw new AggregateException($"Importer validation failed", exceptions); + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, "Import error: validation"); + throw; + } - var result = func(param, context); - return result; + try + { + var result = func(param, context); + return result; + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, "Import error: post-validation importing"); + throw; + } } IEnumerable Validate(T param); } diff --git a/DtoImporterService/SeriesImporter.cs b/DtoImporterService/SeriesImporter.cs index e1736a87..218255c4 100644 --- a/DtoImporterService/SeriesImporter.cs +++ b/DtoImporterService/SeriesImporter.cs @@ -17,17 +17,17 @@ namespace DtoImporterService var series = items.GetSeriesDistinct().ToList(); // load db existing => .Local - var seriesIds = series.Select(s => s.SeriesId).ToList(); - loadLocal_series(seriesIds, context); + loadLocal_series(series, context); // upsert var qtyNew = upsertSeries(series, context); return qtyNew; } - private void loadLocal_series(List seriesIds, LibationContext context) + private void loadLocal_series(List series, LibationContext context) { - var localIds = context.Series.Local.Select(s => s.AudibleSeriesId); + var seriesIds = series.Select(s => s.SeriesId).ToList(); + var localIds = context.Series.Local.Select(s => s.AudibleSeriesId).ToList(); var remainingSeriesIds = seriesIds .Distinct() .Except(localIds) diff --git a/InternalUtilities/UNTESTED/AudibleApiActions.cs b/InternalUtilities/UNTESTED/AudibleApiActions.cs index 8601734d..35ec6833 100644 --- a/InternalUtilities/UNTESTED/AudibleApiActions.cs +++ b/InternalUtilities/UNTESTED/AudibleApiActions.cs @@ -28,7 +28,7 @@ namespace InternalUtilities private async Task> getItemsAsync(ILoginCallback callback) { - var api = await EzApiCreator.GetApiAsync(AudibleApiStorage.IdentityTokensFile, callback, Configuration.Instance.LocaleCountryCode); + var api = await getApiAsync(callback); var items = await AudibleApiExtensions.GetAllLibraryItemsAsync(api); // remove episode parents @@ -62,6 +62,20 @@ namespace InternalUtilities return items; } + private async Task getApiAsync(ILoginCallback callback) + { + try + { + return await EzApiCreator.GetApiAsync(AudibleApiStorage.IdentityTokensFile, callback, Configuration.Instance.LocaleCountryCode); + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, "Error getting Audible API"); + throw; + } + + } + private static List getValidators() { var type = typeof(IValidator); diff --git a/InternalUtilities/UNTESTED/AudibleApiExtensions.cs b/InternalUtilities/UNTESTED/AudibleApiExtensions.cs index 029116d6..b2556a14 100644 --- a/InternalUtilities/UNTESTED/AudibleApiExtensions.cs +++ b/InternalUtilities/UNTESTED/AudibleApiExtensions.cs @@ -27,8 +27,19 @@ namespace InternalUtilities ResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS }); - // important! use this convert method - var libResult = LibraryDtoV10.FromJson(page.ToString()); + string pageStr = null; + LibraryDtoV10 libResult; + try + { + pageStr = page.ToString(); + // important! use this convert method + libResult = LibraryDtoV10.FromJson(pageStr); + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, "Error converting library for importing use. Full library:\r\n" + pageStr); + throw; + } if (!libResult.Items.Any()) break; diff --git a/LibationWinForm/UNTESTED/Dialogs/IndexLibraryDialog.cs b/LibationWinForm/UNTESTED/Dialogs/IndexLibraryDialog.cs index 6d7eaa2b..3773636b 100644 --- a/LibationWinForm/UNTESTED/Dialogs/IndexLibraryDialog.cs +++ b/LibationWinForm/UNTESTED/Dialogs/IndexLibraryDialog.cs @@ -1,4 +1,5 @@ -using System.Windows.Forms; +using System; +using System.Windows.Forms; using ApplicationServices; namespace LibationWinForm @@ -20,9 +21,11 @@ namespace LibationWinForm { (TotalBooksProcessed, NewBooksAdded) = await LibraryCommands.IndexLibraryAsync(new Login.WinformResponder()); } - catch + catch (Exception ex) { - MessageBox.Show("Error importing library. Please try again. If this happens after 2 or 3 tries, contact administrator", "Error importing library", MessageBoxButtons.OK, MessageBoxIcon.Error); + var msg = "Error importing library. Please try again. If this happens after 2 or 3 tries, contact administrator"; + Serilog.Log.Logger.Error(ex, msg); + MessageBox.Show(msg, "Error importing library", MessageBoxButtons.OK, MessageBoxIcon.Error); } this.Close(); diff --git a/LibationWinForm/UNTESTED/Program.cs b/LibationWinForm/UNTESTED/Program.cs index c685e83c..9c001836 100644 --- a/LibationWinForm/UNTESTED/Program.cs +++ b/LibationWinForm/UNTESTED/Program.cs @@ -1,5 +1,6 @@ using System; using System.Windows.Forms; +using Serilog; namespace LibationWinForm { @@ -14,6 +15,8 @@ namespace LibationWinForm if (!createSettings()) return; + init(); + Application.Run(new Form1()); } @@ -29,7 +32,7 @@ Please fill in a few settings on the following page. You can also change these s After you make your selections, get started by importing your library. Go to Import > Scan Library ".Trim(); - MessageBox.Show(welcomeText, "Welcom to Libation", MessageBoxButtons.OK); + MessageBox.Show(welcomeText, "Welcome to Libation", MessageBoxButtons.OK); var dialogResult = new SettingsDialog().ShowDialog(); if (dialogResult != DialogResult.OK) { @@ -39,5 +42,14 @@ Go to Import > Scan Library return true; } + + private static void init() + { + var logPath = System.IO.Path.Combine(FileManager.Configuration.Instance.LibationFiles, "Log.log"); + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.File(logPath, rollingInterval: RollingInterval.Month) + .CreateLogger(); + } } } diff --git a/REFERENCE.txt b/REFERENCE.txt index 69de6dbf..db1a8615 100644 --- a/REFERENCE.txt +++ b/REFERENCE.txt @@ -1,6 +1,7 @@ -- begin VERSIONING --------------------------------------------------------------------------------------------------------------------- https://github.com/rmcrackan/Libation/releases +v3.1-beta.3 : fixed known performance issue: Full-screen grid is slow to respond loading when books aren't liberated v3.1-beta.2 : fixed known performance issue: Tag add/edit v3.1-beta.1 : RELEASE TO BETA v3.0.3 : Switch to SQLite. No longer relies on LocalDB, which must be installed separately