From 664fcc50e2f1b34d63d2a773314fd370c5829473 Mon Sep 17 00:00:00 2001 From: Robert McRackan Date: Mon, 4 Nov 2019 23:07:40 -0500 Subject: [PATCH] Finalize move from library scraping to api --- ApplicationService/ApplicationService.csproj | 15 +++ ApplicationService/LibraryIndexer.cs | 25 ++++ ApplicationService/SearchEngineActions.cs | 26 ++++ DtoImporterService/BookImporter.cs | 15 +-- DtoImporterService/CategoryImporter.cs | 17 +-- DtoImporterService/ContributorImporter.cs | 13 +- DtoImporterService/DtoImporterService.csproj | 1 + DtoImporterService/LibraryImporter.cs | 13 +- DtoImporterService/SeriesImporter.cs | 14 +- FileManager/UNTESTED/AudibleApiStorage.cs | 9 ++ InternalUtilities/InternalUtilities.csproj | 1 - .../UNTESTED/AudibleApiActions.cs | 76 +++++++++++ .../UNTESTED/AudibleApiValidators.cs | 89 +++++++++++++ .../UNTESTED/SearchEngineActions.cs | 26 ---- Libation.sln | 9 +- LibationWinForm/LibationWinForm.csproj | 8 ++ .../Dialogs/IndexDialogs/ScanLibraryDialog.cs | 2 +- .../Dialogs/IndexLibraryDialog.Designer.cs | 66 ++++++++++ .../UNTESTED/Dialogs/IndexLibraryDialog.cs | 43 +++++++ LibationWinForm/UNTESTED/Form1.cs | 61 +-------- .../ScrapingDomainServices.csproj | 2 + .../UNTESTED/DownloadBook.cs | 3 +- .../UNTESTED/IProcessableExt.cs | 8 +- ScrapingDomainServices/UNTESTED/Indexer.cs | 2 +- .../Dialogs/IndexLibraryDialog.Designer.cs | 66 ++++++++++ .../Dialogs/IndexLibraryDialog.cs | 20 +++ .../Dialogs/IndexLibraryDialog.resx | 120 ++++++++++++++++++ WinFormsDesigner/WinFormsDesigner.csproj | 9 ++ __TODO.txt | 25 +--- 29 files changed, 604 insertions(+), 180 deletions(-) create mode 100644 ApplicationService/ApplicationService.csproj create mode 100644 ApplicationService/LibraryIndexer.cs create mode 100644 ApplicationService/SearchEngineActions.cs create mode 100644 FileManager/UNTESTED/AudibleApiStorage.cs create mode 100644 InternalUtilities/UNTESTED/AudibleApiActions.cs create mode 100644 InternalUtilities/UNTESTED/AudibleApiValidators.cs delete mode 100644 InternalUtilities/UNTESTED/SearchEngineActions.cs create mode 100644 LibationWinForm/UNTESTED/Dialogs/IndexLibraryDialog.Designer.cs create mode 100644 LibationWinForm/UNTESTED/Dialogs/IndexLibraryDialog.cs create mode 100644 WinFormsDesigner/Dialogs/IndexLibraryDialog.Designer.cs create mode 100644 WinFormsDesigner/Dialogs/IndexLibraryDialog.cs create mode 100644 WinFormsDesigner/Dialogs/IndexLibraryDialog.resx diff --git a/ApplicationService/ApplicationService.csproj b/ApplicationService/ApplicationService.csproj new file mode 100644 index 00000000..516c02c4 --- /dev/null +++ b/ApplicationService/ApplicationService.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.1 + + + + + + + + + + + diff --git a/ApplicationService/LibraryIndexer.cs b/ApplicationService/LibraryIndexer.cs new file mode 100644 index 00000000..c111e5a2 --- /dev/null +++ b/ApplicationService/LibraryIndexer.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading.Tasks; +using AudibleApi; +using DtoImporterService; +using InternalUtilities; + +namespace ApplicationService +{ + public class LibraryIndexer + { + public async Task<(int totalCount, int newCount)> IndexAsync(ILoginCallback callback) + { + var audibleApiActions = new AudibleApiActions(); + var items = await audibleApiActions.GetAllLibraryItemsAsync(callback); + var totalCount = items.Count; + + var libImporter = new LibraryImporter(); + var newCount = await Task.Run(() => libImporter.Import(items)); + + await SearchEngineActions.FullReIndexAsync(); + + return (totalCount, newCount); + } + } +} diff --git a/ApplicationService/SearchEngineActions.cs b/ApplicationService/SearchEngineActions.cs new file mode 100644 index 00000000..e6508abb --- /dev/null +++ b/ApplicationService/SearchEngineActions.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using DataLayer; + +namespace ApplicationService +{ + public static class SearchEngineActions + { + public static async Task FullReIndexAsync() + { + var engine = new LibationSearchEngine.SearchEngine(); + await engine.CreateNewIndexAsync().ConfigureAwait(false); + } + + public static void UpdateBookTags(Book book) + { + var engine = new LibationSearchEngine.SearchEngine(); + engine.UpdateTags(book.AudibleProductId, book.UserDefinedItem.Tags); + } + + public static async Task ProductReIndexAsync(string productId) + { + var engine = new LibationSearchEngine.SearchEngine(); + await engine.UpdateBookAsync(productId).ConfigureAwait(false); + } + } +} diff --git a/DtoImporterService/BookImporter.cs b/DtoImporterService/BookImporter.cs index a0b67140..bf24b409 100644 --- a/DtoImporterService/BookImporter.cs +++ b/DtoImporterService/BookImporter.cs @@ -3,24 +3,13 @@ using System.Collections.Generic; using System.Linq; using AudibleApiDTOs; using DataLayer; +using InternalUtilities; namespace DtoImporterService { public class BookImporter : ItemsImporterBase { - public override IEnumerable Validate(IEnumerable items) - { - var exceptions = new List(); - - if (items.Any(i => string.IsNullOrWhiteSpace(i.ProductId))) - exceptions.Add(new ArgumentException($"Collection contains item(s) with blank {nameof(Item.ProductId)}", nameof(items))); - if (items.Any(i => string.IsNullOrWhiteSpace(i.Title))) - exceptions.Add(new ArgumentException($"Collection contains item(s) with blank {nameof(Item.Title)}", nameof(items))); - if (items.Any(i => i.Authors is null)) - exceptions.Add(new ArgumentException($"Collection contains item(s) with null {nameof(Item.Authors)}", nameof(items))); - - return exceptions; - } + public override IEnumerable Validate(IEnumerable items) => new BookValidator().Validate(items); protected override int DoImport(IEnumerable items, LibationContext context) { diff --git a/DtoImporterService/CategoryImporter.cs b/DtoImporterService/CategoryImporter.cs index 707f8c96..4936ed02 100644 --- a/DtoImporterService/CategoryImporter.cs +++ b/DtoImporterService/CategoryImporter.cs @@ -3,26 +3,13 @@ using System.Collections.Generic; using System.Linq; using AudibleApiDTOs; using DataLayer; +using InternalUtilities; namespace DtoImporterService { public class CategoryImporter : ItemsImporterBase { - public override IEnumerable Validate(IEnumerable items) - { - var exceptions = new List(); - - var distinct = items.GetCategoriesDistinct(); - if (distinct.Any(s => s.CategoryId is null)) - exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Categories)} with null {nameof(Ladder.CategoryId)}", nameof(items))); - if (distinct.Any(s => s.CategoryName is null)) - exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Categories)} with null {nameof(Ladder.CategoryName)}", nameof(items))); - - if (items.GetCategoryPairsDistinct().Any(p => p.Length > 2)) - exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Categories)} with wrong number of categories. Expecting 0, 1, or 2 categories per title", nameof(items))); - - return exceptions; - } + public override IEnumerable Validate(IEnumerable items) => new CategoryValidator().Validate(items); protected override int DoImport(IEnumerable items, LibationContext context) { diff --git a/DtoImporterService/ContributorImporter.cs b/DtoImporterService/ContributorImporter.cs index f76830b4..b0338422 100644 --- a/DtoImporterService/ContributorImporter.cs +++ b/DtoImporterService/ContributorImporter.cs @@ -3,22 +3,13 @@ using System.Collections.Generic; using System.Linq; using AudibleApiDTOs; using DataLayer; +using InternalUtilities; namespace DtoImporterService { public class ContributorImporter : ItemsImporterBase { - public override IEnumerable Validate(IEnumerable items) - { - var exceptions = new List(); - - if (items.GetAuthorsDistinct().Any(a => string.IsNullOrWhiteSpace(a.Name))) - exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Authors)} with null {nameof(Person.Name)}", nameof(items))); - if (items.GetNarratorsDistinct().Any(a => string.IsNullOrWhiteSpace(a.Name))) - exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Narrators)} with null {nameof(Person.Name)}", nameof(items))); - - return exceptions; - } + public override IEnumerable Validate(IEnumerable items) => new ContributorValidator().Validate(items); protected override int DoImport(IEnumerable items, LibationContext context) { diff --git a/DtoImporterService/DtoImporterService.csproj b/DtoImporterService/DtoImporterService.csproj index 87abb7e9..8b8454dd 100644 --- a/DtoImporterService/DtoImporterService.csproj +++ b/DtoImporterService/DtoImporterService.csproj @@ -7,6 +7,7 @@ + diff --git a/DtoImporterService/LibraryImporter.cs b/DtoImporterService/LibraryImporter.cs index 0de82371..120cba7c 100644 --- a/DtoImporterService/LibraryImporter.cs +++ b/DtoImporterService/LibraryImporter.cs @@ -3,22 +3,13 @@ using System.Collections.Generic; using System.Linq; using AudibleApiDTOs; using DataLayer; +using InternalUtilities; namespace DtoImporterService { public class LibraryImporter : ItemsImporterBase { - public override IEnumerable Validate(IEnumerable items) - { - var exceptions = new List(); - - if (items.Any(i => string.IsNullOrWhiteSpace(i.ProductId))) - exceptions.Add(new ArgumentException($"Collection contains item(s) with null or blank {nameof(Item.ProductId)}", nameof(items))); - if (items.Any(i => i.DateAdded < new DateTime(1980, 1, 1))) - exceptions.Add(new ArgumentException($"Collection contains item(s) with invalid {nameof(Item.DateAdded)}", nameof(items))); - - return exceptions; - } + public override IEnumerable Validate(IEnumerable items) => new LibraryValidator().Validate(items); protected override int DoImport(IEnumerable items, LibationContext context) { diff --git a/DtoImporterService/SeriesImporter.cs b/DtoImporterService/SeriesImporter.cs index 8e74aff4..e1736a87 100644 --- a/DtoImporterService/SeriesImporter.cs +++ b/DtoImporterService/SeriesImporter.cs @@ -3,23 +3,13 @@ using System.Collections.Generic; using System.Linq; using AudibleApiDTOs; using DataLayer; +using InternalUtilities; namespace DtoImporterService { public class SeriesImporter : ItemsImporterBase { - public override IEnumerable Validate(IEnumerable items) - { - var exceptions = new List(); - - var distinct = items .GetSeriesDistinct(); - if (distinct.Any(s => s.SeriesId is null)) - exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(AudibleApiDTOs.Series.SeriesId)}", nameof(items))); - if (distinct.Any(s => s.SeriesName is null)) - exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(AudibleApiDTOs.Series.SeriesName)}", nameof(items))); - - return exceptions; - } + public override IEnumerable Validate(IEnumerable items) => new SeriesValidator().Validate(items); protected override int DoImport(IEnumerable items, LibationContext context) { diff --git a/FileManager/UNTESTED/AudibleApiStorage.cs b/FileManager/UNTESTED/AudibleApiStorage.cs new file mode 100644 index 00000000..f0941310 --- /dev/null +++ b/FileManager/UNTESTED/AudibleApiStorage.cs @@ -0,0 +1,9 @@ +using System.IO; + +namespace FileManager +{ + public static class AudibleApiStorage + { + public static string IdentityTokensFile => Path.Combine(Configuration.Instance.LibationFiles, "IdentityTokens.json"); + } +} diff --git a/InternalUtilities/InternalUtilities.csproj b/InternalUtilities/InternalUtilities.csproj index 367bd677..312da9e6 100644 --- a/InternalUtilities/InternalUtilities.csproj +++ b/InternalUtilities/InternalUtilities.csproj @@ -6,7 +6,6 @@ - diff --git a/InternalUtilities/UNTESTED/AudibleApiActions.cs b/InternalUtilities/UNTESTED/AudibleApiActions.cs new file mode 100644 index 00000000..2c256262 --- /dev/null +++ b/InternalUtilities/UNTESTED/AudibleApiActions.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AudibleApi; +using AudibleApiDTOs; +using FileManager; + +namespace InternalUtilities +{ + public class AudibleApiActions + { + public async Task> GetAllLibraryItemsAsync(ILoginCallback callback) + { + // 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 tokens are refreshed + // 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 }); + // i don't want to incur the cost of making a full dummy call every time because it fails sometimes + + try + { + return await getItemsAsync(callback); + } + catch + { + return await getItemsAsync(callback); + } + } + + private async Task> getItemsAsync(ILoginCallback callback) + { + var api = await EzApiCreator.GetApiAsync(AudibleApiStorage.IdentityTokensFile, callback, Configuration.Instance.LocaleCountryCode); + var items = await AudibleApiExtensions.GetAllLibraryItemsAsync(api); + + // remove episode parents + items.RemoveAll(i => i.Episodes); + + #region // episode handling. doesn't quite work + // // add individual/children episodes + // var childIds = items + // .Where(i => i.Episodes) + // .SelectMany(ep => ep.Relationships) + // .Where(r => r.RelationshipToProduct == AudibleApiDTOs.RelationshipToProduct.Child && r.RelationshipType == AudibleApiDTOs.RelationshipType.Episode) + // .Select(c => c.Asin) + // .ToList(); + // foreach (var childId in childIds) + // { + // var bookResult = await api.GetLibraryBookAsync(childId, AudibleApi.LibraryOptions.ResponseGroupOptions.ALL_OPTIONS); + // var bookItem = AudibleApiDTOs.LibraryApiV10.FromJson(bookResult.ToString()).Item; + // items.Add(bookItem); + // } + #endregion + + var validators = new List(); + validators.AddRange(getValidators()); + foreach (var v in validators) + { + var exceptions = v.Validate(items); + if (exceptions != null && exceptions.Any()) + throw new AggregateException(exceptions); + } + + return items; + } + + private static List getValidators() + { + var type = typeof(IValidator); + var types = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(s => s.GetTypes()) + .Where(p => type.IsAssignableFrom(p) && !p.IsInterface); + + return types.Select(t => Activator.CreateInstance(t) as IValidator).ToList(); + } + } +} diff --git a/InternalUtilities/UNTESTED/AudibleApiValidators.cs b/InternalUtilities/UNTESTED/AudibleApiValidators.cs new file mode 100644 index 00000000..0769a62e --- /dev/null +++ b/InternalUtilities/UNTESTED/AudibleApiValidators.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AudibleApiDTOs; + +namespace InternalUtilities +{ + public interface IValidator + { + IEnumerable Validate(IEnumerable items); + } + public class LibraryValidator : IValidator + { + public IEnumerable Validate(IEnumerable items) + { + var exceptions = new List(); + + if (items.Any(i => string.IsNullOrWhiteSpace(i.ProductId))) + exceptions.Add(new ArgumentException($"Collection contains item(s) with null or blank {nameof(Item.ProductId)}", nameof(items))); + if (items.Any(i => i.DateAdded < new DateTime(1980, 1, 1))) + exceptions.Add(new ArgumentException($"Collection contains item(s) with invalid {nameof(Item.DateAdded)}", nameof(items))); + + return exceptions; + } + } + public class BookValidator : IValidator + { + public IEnumerable Validate(IEnumerable items) + { + var exceptions = new List(); + + if (items.Any(i => string.IsNullOrWhiteSpace(i.ProductId))) + exceptions.Add(new ArgumentException($"Collection contains item(s) with blank {nameof(Item.ProductId)}", nameof(items))); + if (items.Any(i => string.IsNullOrWhiteSpace(i.Title))) + exceptions.Add(new ArgumentException($"Collection contains item(s) with blank {nameof(Item.Title)}", nameof(items))); + if (items.Any(i => i.Authors is null)) + exceptions.Add(new ArgumentException($"Collection contains item(s) with null {nameof(Item.Authors)}", nameof(items))); + + return exceptions; + } + } + public class CategoryValidator : IValidator + { + public IEnumerable Validate(IEnumerable items) + { + var exceptions = new List(); + + var distinct = items.GetCategoriesDistinct(); + if (distinct.Any(s => s.CategoryId is null)) + exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Categories)} with null {nameof(Ladder.CategoryId)}", nameof(items))); + if (distinct.Any(s => s.CategoryName is null)) + exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Categories)} with null {nameof(Ladder.CategoryName)}", nameof(items))); + + if (items.GetCategoryPairsDistinct().Any(p => p.Length > 2)) + exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Categories)} with wrong number of categories. Expecting 0, 1, or 2 categories per title", nameof(items))); + + return exceptions; + } + } + public class ContributorValidator : IValidator + { + public IEnumerable Validate(IEnumerable items) + { + var exceptions = new List(); + + if (items.GetAuthorsDistinct().Any(a => string.IsNullOrWhiteSpace(a.Name))) + exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Authors)} with null {nameof(Person.Name)}", nameof(items))); + if (items.GetNarratorsDistinct().Any(a => string.IsNullOrWhiteSpace(a.Name))) + exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Narrators)} with null {nameof(Person.Name)}", nameof(items))); + + return exceptions; + } + } + public class SeriesValidator : IValidator + { + public IEnumerable Validate(IEnumerable items) + { + var exceptions = new List(); + + var distinct = items.GetSeriesDistinct(); + if (distinct.Any(s => s.SeriesId is null)) + exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(AudibleApiDTOs.Series.SeriesId)}", nameof(items))); + if (distinct.Any(s => s.SeriesName is null)) + exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(AudibleApiDTOs.Series.SeriesName)}", nameof(items))); + + return exceptions; + } + } +} diff --git a/InternalUtilities/UNTESTED/SearchEngineActions.cs b/InternalUtilities/UNTESTED/SearchEngineActions.cs deleted file mode 100644 index 97197c53..00000000 --- a/InternalUtilities/UNTESTED/SearchEngineActions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Threading.Tasks; -using DataLayer; - -namespace InternalUtilities -{ - public static class SearchEngineActions - { - public static async Task FullReIndexAsync() - { - var engine = new LibationSearchEngine.SearchEngine(); - await engine.CreateNewIndexAsync().ConfigureAwait(false); - } - - public static void UpdateBookTags(Book book) - { - var engine = new LibationSearchEngine.SearchEngine(); - engine.UpdateTags(book.AudibleProductId, book.UserDefinedItem.Tags); - } - - public static async Task ProductReIndexAsync(string productId) - { - var engine = new LibationSearchEngine.SearchEngine(); - await engine.UpdateBookAsync(productId).ConfigureAwait(false); - } - } -} diff --git a/Libation.sln b/Libation.sln index 3f86fcf8..6476d440 100644 --- a/Libation.sln +++ b/Libation.sln @@ -87,6 +87,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudibleApiDTOs.Tests", "..\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudibleApiClientExample", "..\audible api\AudibleApi\_Demos\AudibleApiClientExample\AudibleApiClientExample.csproj", "{282EEE16-F569-47E1-992F-C6DB8AEC7AA6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApplicationService", "ApplicationService\ApplicationService.csproj", "{B95650EA-25F0-449E-BA5D-99126BC5D730}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -213,6 +215,10 @@ Global {282EEE16-F569-47E1-992F-C6DB8AEC7AA6}.Debug|Any CPU.Build.0 = Debug|Any CPU {282EEE16-F569-47E1-992F-C6DB8AEC7AA6}.Release|Any CPU.ActiveCfg = Release|Any CPU {282EEE16-F569-47E1-992F-C6DB8AEC7AA6}.Release|Any CPU.Build.0 = Release|Any CPU + {B95650EA-25F0-449E-BA5D-99126BC5D730}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B95650EA-25F0-449E-BA5D-99126BC5D730}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B95650EA-25F0-449E-BA5D-99126BC5D730}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B95650EA-25F0-449E-BA5D-99126BC5D730}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -227,7 +233,7 @@ Global {7BD02E29-3430-4D06-88D2-5CECEE9ABD01} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05} {393B5B27-D15C-4F77-9457-FA14BA8F3C73} = {41CDCC73-9B81-49DD-9570-C54406E852AF} {06882742-27A6-4347-97D9-56162CEC9C11} = {F0CBB7A7-D3FB-41FF-8F47-CF3F6A592249} - {2E1F5DB4-40CC-4804-A893-5DCE0193E598} = {F0CBB7A7-D3FB-41FF-8F47-CF3F6A592249} + {2E1F5DB4-40CC-4804-A893-5DCE0193E598} = {41CDCC73-9B81-49DD-9570-C54406E852AF} {9F1AA3DE-962F-469B-82B2-46F93491389B} = {F61184E7-2426-4A13-ACEF-5689928E2CE2} {E874D000-AD3A-4629-AC65-7219C2C7C1F0} = {38E6C6D9-963A-4C5B-89F4-F2F14885ADFD} {FF12ADA0-8975-4E67-B6EA-4AC82E0C8994} = {38E6C6D9-963A-4C5B-89F4-F2F14885ADFD} @@ -248,6 +254,7 @@ Global {C03C5D65-3B7F-453B-972F-23950B7E0604} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05} {6069D7F6-BEA0-4917-AFD4-4EB680CB0EDD} = {38E6C6D9-963A-4C5B-89F4-F2F14885ADFD} {282EEE16-F569-47E1-992F-C6DB8AEC7AA6} = {F61184E7-2426-4A13-ACEF-5689928E2CE2} + {B95650EA-25F0-449E-BA5D-99126BC5D730} = {41CDCC73-9B81-49DD-9570-C54406E852AF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {615E00ED-BAEF-4E8E-A92A-9B82D87942A9} diff --git a/LibationWinForm/LibationWinForm.csproj b/LibationWinForm/LibationWinForm.csproj index abb394e5..d48e56bf 100644 --- a/LibationWinForm/LibationWinForm.csproj +++ b/LibationWinForm/LibationWinForm.csproj @@ -11,7 +11,9 @@ + + @@ -21,6 +23,12 @@ True Resources.resx + + Form + + + IndexLibraryDialog.cs + diff --git a/LibationWinForm/UNTESTED/Dialogs/IndexDialogs/ScanLibraryDialog.cs b/LibationWinForm/UNTESTED/Dialogs/IndexDialogs/ScanLibraryDialog.cs index 8053772d..10350e13 100644 --- a/LibationWinForm/UNTESTED/Dialogs/IndexDialogs/ScanLibraryDialog.cs +++ b/LibationWinForm/UNTESTED/Dialogs/IndexDialogs/ScanLibraryDialog.cs @@ -17,7 +17,7 @@ namespace LibationWinForm public string StringBasedValidate() => null; - List successMessages = new List(); + List successMessages { get; } = new List(); public string SuccessMessage => string.Join("\r\n", successMessages); public int NewBooksAdded { get; private set; } diff --git a/LibationWinForm/UNTESTED/Dialogs/IndexLibraryDialog.Designer.cs b/LibationWinForm/UNTESTED/Dialogs/IndexLibraryDialog.Designer.cs new file mode 100644 index 00000000..6d11b629 --- /dev/null +++ b/LibationWinForm/UNTESTED/Dialogs/IndexLibraryDialog.Designer.cs @@ -0,0 +1,66 @@ +namespace LibationWinForm +{ + partial class IndexLibraryDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(28, 24); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(260, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Scanning Audible library. This may take a few minutes"; + // + // IndexLibraryDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(319, 63); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "IndexLibraryDialog"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Scan Library"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + } +} \ No newline at end of file diff --git a/LibationWinForm/UNTESTED/Dialogs/IndexLibraryDialog.cs b/LibationWinForm/UNTESTED/Dialogs/IndexLibraryDialog.cs new file mode 100644 index 00000000..6eb19d7b --- /dev/null +++ b/LibationWinForm/UNTESTED/Dialogs/IndexLibraryDialog.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Windows.Forms; +using ApplicationService; + +namespace LibationWinForm +{ + public partial class IndexLibraryDialog : Form, IIndexLibraryDialog + { + public IndexLibraryDialog() + { + InitializeComponent(); + + var btn = new Button(); + AcceptButton = btn; + + btn.Location = new System.Drawing.Point(this.Size.Width + 10, 0); + // required for FindForm() to work + this.Controls.Add(btn); + + this.Shown += (_, __) => AcceptButton.PerformClick(); + } + + public string StringBasedValidate() => null; + + List successMessages { get; } = new List(); + public string SuccessMessage => string.Join("\r\n", successMessages); + + public int NewBooksAdded { get; private set; } + public int TotalBooksProcessed { get; private set; } + + public async Task DoMainWorkAsync() + { + var callback = new Login.WinformResponder(); + var refresher = new LibraryIndexer(); + (TotalBooksProcessed, NewBooksAdded) = await refresher.IndexAsync(callback); + + successMessages.Add($"Total processed: {TotalBooksProcessed}"); + successMessages.Add($"New: {NewBooksAdded}"); + } + } +} diff --git a/LibationWinForm/UNTESTED/Form1.cs b/LibationWinForm/UNTESTED/Form1.cs index 630c8107..cf4f1c53 100644 --- a/LibationWinForm/UNTESTED/Form1.cs +++ b/LibationWinForm/UNTESTED/Form1.cs @@ -360,66 +360,7 @@ namespace LibationWinForm // legacy/scraping method //await indexDialog(new ScanLibraryDialog()); // new/api method -await audibleApi(); - } - - private async Task audibleApi() - { - var identityFilePath = System.IO.Path.Combine(config.LibationFiles, "IdentityTokens.json"); - var callback = new Login.WinformResponder(); - var api = await AudibleApi.EzApiCreator.GetApiAsync(identityFilePath, callback, config.LocaleCountryCode); - - int totalCount; - int newCount; - - // 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 tokens are refreshed - // 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 }); - // i don't want to incur the cost of making a full dummy call every time because it fails sometimes - try - { - var items = await InternalUtilities.AudibleApiExtensions.GetAllLibraryItemsAsync(api); - - // remove episode parents - items.RemoveAll(i => i.Episodes); -// // add individual/children episodes -// var childIds = items -// .Where(i => i.Episodes) -// .SelectMany(ep => ep.Relationships) -// .Where(r => r.RelationshipToProduct == AudibleApiDTOs.RelationshipToProduct.Child && r.RelationshipType == AudibleApiDTOs.RelationshipType.Episode) -// .Select(c => c.Asin) -// .ToList(); -// foreach (var childId in childIds) -// { -//// clean this up -// var bookResult = await api.GetLibraryBookAsync(childId, AudibleApi.LibraryOptions.ResponseGroupOptions.ALL_OPTIONS); -// var bookResultString = bookResult.ToString(); -// var bookResultJson = AudibleApiDTOs.LibraryApiV10.FromJson(bookResultString); -// var bookItem = bookResultJson.Item; -// items.Add(bookItem); -// } -// extract code in 'try' so retry in 'catch' isn't duplicate code - totalCount = items.Count; - newCount = await Task.Run(() => new DtoImporterService.LibraryImporter().Import(items)); - } - catch (Exception ex1) - { - try - { - var items = await InternalUtilities.AudibleApiExtensions.GetAllLibraryItemsAsync(api); - items.RemoveAll(i => i.Episodes); - totalCount = items.Count; - newCount = await Task.Run(() => new DtoImporterService.LibraryImporter().Import(items)); - } - catch (Exception ex2) - { - MessageBox.Show("Error importing library.\r\n" + ex2.Message, "Error importing library", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - } - - await InternalUtilities.SearchEngineActions.FullReIndexAsync(); - await indexComplete(totalCount, newCount); +await indexDialog(new IndexLibraryDialog()); } private async void reimportMostRecentLibraryScanToolStripMenuItem_Click(object sender, EventArgs e) diff --git a/ScrapingDomainServices/ScrapingDomainServices.csproj b/ScrapingDomainServices/ScrapingDomainServices.csproj index 66a40729..d6878cbf 100644 --- a/ScrapingDomainServices/ScrapingDomainServices.csproj +++ b/ScrapingDomainServices/ScrapingDomainServices.csproj @@ -6,7 +6,9 @@ + + diff --git a/ScrapingDomainServices/UNTESTED/DownloadBook.cs b/ScrapingDomainServices/UNTESTED/DownloadBook.cs index a12e5137..e9f9e183 100644 --- a/ScrapingDomainServices/UNTESTED/DownloadBook.cs +++ b/ScrapingDomainServices/UNTESTED/DownloadBook.cs @@ -83,8 +83,7 @@ tempAaxFilename = await performApiDownloadAsync(libraryBook, tempAaxFilename); private async Task performApiDownloadAsync(LibraryBook libraryBook, string tempAaxFilename) { - var identityFilePath = Path.Combine(Configuration.Instance.LibationFiles, "IdentityTokens.json"); - var api = await AudibleApi.EzApiCreator.GetApiAsync(identityFilePath); + var api = await AudibleApi.EzApiCreator.GetApiAsync(AudibleApiStorage.IdentityTokensFile); var progress = new Progress(); progress.ProgressChanged += (_, e) => Invoke_DownloadProgressChanged(this, e); diff --git a/ScrapingDomainServices/UNTESTED/IProcessableExt.cs b/ScrapingDomainServices/UNTESTED/IProcessableExt.cs index 0346b14b..ce6519af 100644 --- a/ScrapingDomainServices/UNTESTED/IProcessableExt.cs +++ b/ScrapingDomainServices/UNTESTED/IProcessableExt.cs @@ -38,12 +38,8 @@ namespace ScrapingDomainServices var libraryBooks = LibraryQueries.GetLibrary_Flat_NoTracking(); foreach (var libraryBook in libraryBooks) - if ( -// hardcoded blacklist -//episodes -!libraryBook.Book.AudibleProductId.In("B079ZTTL4J", "B0779LK1TX", "B0779H7B38", "B0779M3KGC", "B076PQ6G9Z", "B07D4M18YC") && - await processable.ValidateAsync(libraryBook)) - return libraryBook; + if (await processable.ValidateAsync(libraryBook)) + return libraryBook; return null; } diff --git a/ScrapingDomainServices/UNTESTED/Indexer.cs b/ScrapingDomainServices/UNTESTED/Indexer.cs index d63d433b..c220532a 100644 --- a/ScrapingDomainServices/UNTESTED/Indexer.cs +++ b/ScrapingDomainServices/UNTESTED/Indexer.cs @@ -4,12 +4,12 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; +using ApplicationService; using DataLayer; using Dinah.Core; using Dinah.Core.Collections.Generic; using DTOs; using FileManager; -using InternalUtilities; using Newtonsoft.Json; namespace ScrapingDomainServices diff --git a/WinFormsDesigner/Dialogs/IndexLibraryDialog.Designer.cs b/WinFormsDesigner/Dialogs/IndexLibraryDialog.Designer.cs new file mode 100644 index 00000000..c7087173 --- /dev/null +++ b/WinFormsDesigner/Dialogs/IndexLibraryDialog.Designer.cs @@ -0,0 +1,66 @@ +namespace LibationWinForm_Framework.Dialogs +{ + partial class IndexLibraryDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(28, 24); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(260, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Scanning Audible library. This may take a few minutes"; + // + // IndexLibraryDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(319, 63); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "IndexLibraryDialog"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Scan Library"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + } +} \ No newline at end of file diff --git a/WinFormsDesigner/Dialogs/IndexLibraryDialog.cs b/WinFormsDesigner/Dialogs/IndexLibraryDialog.cs new file mode 100644 index 00000000..68f74e04 --- /dev/null +++ b/WinFormsDesigner/Dialogs/IndexLibraryDialog.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace LibationWinForm_Framework.Dialogs +{ + public partial class IndexLibraryDialog : Form + { + public IndexLibraryDialog() + { + InitializeComponent(); + } + } +} diff --git a/WinFormsDesigner/Dialogs/IndexLibraryDialog.resx b/WinFormsDesigner/Dialogs/IndexLibraryDialog.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/WinFormsDesigner/Dialogs/IndexLibraryDialog.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/WinFormsDesigner/WinFormsDesigner.csproj b/WinFormsDesigner/WinFormsDesigner.csproj index f1a1660c..3d632224 100644 --- a/WinFormsDesigner/WinFormsDesigner.csproj +++ b/WinFormsDesigner/WinFormsDesigner.csproj @@ -77,6 +77,12 @@ EditQuickFilters.cs + + Form + + + IndexLibraryDialog.cs + Form @@ -157,6 +163,9 @@ EditTagsDialog.cs + + IndexLibraryDialog.cs + AudibleLoginDialog.cs diff --git a/__TODO.txt b/__TODO.txt index d8d36ecc..c63bc22d 100644 --- a/__TODO.txt +++ b/__TODO.txt @@ -1,25 +1,11 @@ -- begin REPLACE SCRAPING WITH API --------------------------------------------------------------------------------------------------------------------- -integrate API into libation. replace all authentication, audible communication - -IN-PROGRESS ------------ library import UI -- disable main ui -- updates on which stage and how long it's expected to take -- error handling -- dialog/pop up when done. show how many new and total move biz logic out of UI (Form1.scanLibraryToolStripMenuItem_Click) - non-db dependent: InternalUtilities. see: SearchEngineActions InternalUtilities.AudibleApiExtensions may not belong here. not sure - db dependent: eg DtoImporterService -extract IdentityTokens.json into FileManager -replace all hardcoded occurances -FIX -// hardcoded blacklist - -datalayer stuff (eg: Book) need better ToString MOVE TO LEGACY -------------- @@ -81,15 +67,14 @@ need a way to liberate ad hoc books and pdf.s use pdf icon with and without and X over it to indicate status -- end ENHANCEMENT, PERFORMANCE: GRID --------------------------------------------------------------------------------------------------------------------- --- begin ENHANCEMENTS, GET LIBRARY --------------------------------------------------------------------------------------------------------------------- +-- begin ENHANCEMENT, GET LIBRARY --------------------------------------------------------------------------------------------------------------------- Audible API. GET /1.0/library , GET /1.0/library/{asin} TONS of expensive conversion: GetLibraryAsync > string > JObject > string > LibraryApiV10 --- end ENHANCEMENTS, GET LIBRARY --------------------------------------------------------------------------------------------------------------------- +-- end ENHANCEMENT, GET LIBRARY --------------------------------------------------------------------------------------------------------------------- --- begin ENHANCEMENTS, EPISODES --------------------------------------------------------------------------------------------------------------------- -grid: episodes need a better Download_Status and Length/LengthInMinutes -download: need a way to liberate episodes. show in grid 'x/y downloaded/liberated' etc --- end ENHANCEMENTS, EPISODES --------------------------------------------------------------------------------------------------------------------- +-- begin ENHANCEMENT, DEBUGGING --------------------------------------------------------------------------------------------------------------------- +datalayer stuff (eg: Book) need better ToString +-- end ENHANCEMENT, DEBUGGING --------------------------------------------------------------------------------------------------------------------- -- begin BUG, MOVING FILES --------------------------------------------------------------------------------------------------------------------- with libation closed, move files