scraping => api transition almost complete

This commit is contained in:
Robert McRackan 2019-11-04 14:16:57 -05:00
parent df889a60a4
commit 591d84e719
41 changed files with 995 additions and 2494 deletions

View file

@ -11,7 +11,7 @@
<ProjectReference Include="..\..\audible api\AudibleApi\AudibleApi\AudibleApi.csproj" />
<ProjectReference Include="..\..\Dinah.Core\Dinah.Core.Drawing\Dinah.Core.Drawing.csproj" />
<ProjectReference Include="..\..\Dinah.Core\Dinah.Core.Windows.Forms\Dinah.Core.Windows.Forms.csproj" />
<ProjectReference Include="..\AudibleApiDomainService\AudibleApiDomainService.csproj" />
<ProjectReference Include="..\DtoImporterService\DtoImporterService.csproj" />
<ProjectReference Include="..\ScrapingDomainServices\ScrapingDomainServices.csproj" />
</ItemGroup>

View file

@ -83,8 +83,8 @@ namespace LibationWinForm.BookLiberation
// close form on DOWNLOAD completed, not final Completed. Else for BackupBook this form won't close until DECRYPT is also complete
void fileDownloadCompleted(object _, string __) => downloadDialog.Close();
void downloadProgressChanged(object _, System.Net.DownloadProgressChangedEventArgs arg)
=> downloadDialog.DownloadProgressChanged(arg.BytesReceived, arg.TotalBytesToReceive);
void downloadProgressChanged(object _, Dinah.Core.Net.Http.DownloadProgress progress)
=> downloadDialog.DownloadProgressChanged(progress.BytesReceived, progress.TotalBytesToReceive.Value);
void unsubscribe(object _ = null, EventArgs __ = null)
{

View file

@ -3,7 +3,7 @@ using LibationWinForm.Dialogs.Login;
namespace LibationWinForm.Login
{
public class WinformResponder : AudibleApiDomainService.IAudibleApiResponder
public class WinformResponder : AudibleApi.ILoginCallback
{
public string Get2faCode()
{

View file

@ -357,18 +357,48 @@ namespace LibationWinForm
private async void scanLibraryToolStripMenuItem_Click(object sender, EventArgs e)
{
// audible api
var settings = new AudibleApiDomainService.Settings(config);
var responder = new Login.WinformResponder();
var client = await AudibleApiDomainService.AudibleApiLibationClient.CreateClientAsync(settings, responder);
await client.ImportLibraryAsync();
// scrape
await indexDialog(new ScanLibraryDialog());
// legacy/scraping method
//await indexDialog(new ScanLibraryDialog());
// new/api method
await audibleApi();
}
private async void reimportMostRecentLibraryScanToolStripMenuItem_Click(object sender, EventArgs e)
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;
// seems to be very common the 1st time after long absence. either figure out why, or run 2x before declaring error
try
{
var items = await InternalUtilities.AudibleApiExtensions.GetAllLibraryItemsAsync(api);
totalCount = items.Count;
newCount = await Task.Run(() => new DtoImporterService.LibraryImporter().Import(items));
}
catch
{
try
{
var items = await InternalUtilities.AudibleApiExtensions.GetAllLibraryItemsAsync(api);
totalCount = items.Count;
newCount = await Task.Run(() => new DtoImporterService.LibraryImporter().Import(items));
}
catch (Exception ex)
{
MessageBox.Show("Error importing library.\r\n" + ex.Message, "Error importing library", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
await InternalUtilities.SearchEngineActions.FullReIndexAsync();
await indexComplete(totalCount, newCount);
}
private async void reimportMostRecentLibraryScanToolStripMenuItem_Click(object sender, EventArgs e)
{
// DO NOT ConfigureAwait(false)
// this would result in index() => reloadGrid() => setGrid() => "gridPanel.Controls.Remove(currProductsGrid);"
@ -377,15 +407,15 @@ await client.ImportLibraryAsync();
MessageBox.Show($"Total processed: {TotalBooksProcessed}\r\nNew: {NewBooksAdded}");
await index(NewBooksAdded, TotalBooksProcessed);
await indexComplete(TotalBooksProcessed, NewBooksAdded);
}
private async Task indexDialog(IIndexLibraryDialog dialog)
{
if (!dialog.RunDialog().In(DialogResult.Abort, DialogResult.Cancel, DialogResult.None))
await index(dialog.NewBooksAdded, dialog.TotalBooksProcessed);
await indexComplete(dialog.TotalBooksProcessed, dialog.NewBooksAdded);
}
private async Task index(int newBooksAdded, int totalBooksProcessed)
private async Task indexComplete(int totalBooksProcessed, int newBooksAdded)
{
// update backup counts if we have new library items
if (newBooksAdded > 0)

View file

@ -7,103 +7,132 @@ using DataLayer;
namespace LibationWinForm
{
internal class GridEntry
{
private LibraryBook libraryBook;
private Book book => libraryBook.Book;
internal class GridEntry
{
private LibraryBook libraryBook;
private Book book => libraryBook.Book;
public Book GetBook() => book;
public Book GetBook() => book;
// this special case is obvious and ugly
public void REPLACE_Library_Book(LibraryBook libraryBook) => this.libraryBook = libraryBook;
// this special case is obvious and ugly
public void REPLACE_Library_Book(LibraryBook libraryBook) => this.libraryBook = libraryBook;
public GridEntry(LibraryBook libraryBook) => this.libraryBook = libraryBook;
public GridEntry(LibraryBook libraryBook) => this.libraryBook = libraryBook;
// hide from public fields from Data Source GUI with [Browsable(false)]
// hide from public fields from Data Source GUI with [Browsable(false)]
[Browsable(false)]
public string Tags => book.UserDefinedItem.Tags;
[Browsable(false)]
public IEnumerable<string> TagsEnumerated => book.UserDefinedItem.TagsEnumerated;
[Browsable(false)]
public string Tags => book.UserDefinedItem.Tags;
[Browsable(false)]
public IEnumerable<string> TagsEnumerated => book.UserDefinedItem.TagsEnumerated;
private Dictionary<string, string> formatReplacements { get; } = new Dictionary<string, string>();
public bool TryGetFormatted(string key, out string value) => formatReplacements.TryGetValue(key, out value);
// formatReplacements is what gets displayed
// the value that gets returned from the property is the cell's value
// this allows for the value to be sorted one way and displayed another
// eg:
// orig title: The Computer
// formatReplacement: The Computer
// value for sorting: Computer
private Dictionary<string, string> formatReplacements { get; } = new Dictionary<string, string>();
public bool TryGetFormatted(string key, out string value) => formatReplacements.TryGetValue(key, out value);
public Image Cover =>
Dinah.Core.Drawing.ImageConverter.GetPictureFromBytes(
FileManager.PictureStorage.GetImage(book.PictureId, FileManager.PictureStorage.PictureSize._80x80)
);
public Image Cover =>
Dinah.Core.Drawing.ImageConverter.GetPictureFromBytes(
FileManager.PictureStorage.GetImage(book.PictureId, FileManager.PictureStorage.PictureSize._80x80)
);
public string Title
{
get
{
formatReplacements[nameof(Title)] = book.Title;
public string Title
{
get
{
formatReplacements[nameof(Title)] = book.Title;
var sortName = book.Title
.Replace("|", "")
.Replace(":", "")
.ToLowerInvariant();
if (sortName.StartsWith("the ") || sortName.StartsWith("a ") || sortName.StartsWith("an "))
sortName = sortName.Substring(sortName.IndexOf(" ") + 1);
var sortName = book.Title
.Replace("|", "")
.Replace(":", "")
.ToLowerInvariant();
if (sortName.StartsWith("the ") || sortName.StartsWith("a ") || sortName.StartsWith("an "))
sortName = sortName.Substring(sortName.IndexOf(" ") + 1);
return sortName;
}
}
return sortName;
}
}
public string Authors => book.AuthorNames;
public string Narrators => book.NarratorNames;
public string Authors => book.AuthorNames;
public string Narrators => book.NarratorNames;
public int Length
{
get
{
formatReplacements[nameof(Length)]
= book.LengthInMinutes == 0
? "[pre-release]"
: $"{book.LengthInMinutes / 60} hr {book.LengthInMinutes % 60} min";
public int Length
{
get
{
formatReplacements[nameof(Length)]
= book.LengthInMinutes == 0
? ""
: $"{book.LengthInMinutes / 60} hr {book.LengthInMinutes % 60} min";
return book.LengthInMinutes;
}
}
return book.LengthInMinutes;
}
}
public string Series => book.SeriesNames;
public string Series => book.SeriesNames;
public string Description
=> book.Description == null ? ""
: book.Description.Length < 63 ? book.Description
: book.Description.Substring(0, 60) + "...";
private string descriptionCache = null;
public string Description
{
get
{
// HtmlAgilityPack is expensive. cache results
if (descriptionCache is null)
{
if (book.Description is null)
descriptionCache = "";
else
{
var doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(book.Description);
var noHtml = doc.DocumentNode.InnerText;
descriptionCache
= noHtml.Length < 63
? noHtml
: noHtml.Substring(0, 60) + "...";
}
}
public string Category => string.Join(" > ", book.CategoriesNames);
return descriptionCache;
}
}
// star ratings retain numeric value but display star text. this is needed because just using star text doesn't sort correctly:
// - star
// - star star
// - star 1/2
public string Category => string.Join(" > ", book.CategoriesNames);
public string Product_Rating
{
get
{
Rating rating = book.Rating;
// star ratings retain numeric value but display star text. this is needed because just using star text doesn't sort correctly:
// - star
// - star star
// - star 1/2
formatReplacements[nameof(Product_Rating)] = starString(rating);
public string Product_Rating
{
get
{
formatReplacements[nameof(Product_Rating)] = starString(book.Rating);
return firstScore(book.Rating);
}
}
return firstScore(rating);
}
}
public DateTime? Purchase_Date => libraryBook.DateAdded;
public string Purchase_Date
{
get
{
formatReplacements[nameof(Purchase_Date)] = libraryBook.DateAdded.ToString("d");
return libraryBook.DateAdded.ToString("yyyy-MM-dd HH:mm:ss");
}
}
public string My_Rating
{
get
{
Rating rating = book.UserDefinedItem.Rating;
formatReplacements[nameof(My_Rating)] = starString(rating);
return firstScore(rating);
formatReplacements[nameof(My_Rating)] = starString(book.UserDefinedItem.Rating);
return firstScore(book.UserDefinedItem.Rating);
}
}

View file

@ -4,6 +4,7 @@ using System.Linq;
using System.Windows.Forms;
using Dinah.Core.DataBinding;
using DataLayer;
using Dinah.Core.Collections.Generic;
namespace LibationWinForm
{
@ -76,11 +77,14 @@ namespace LibationWinForm
col.HeaderText = col.HeaderText.Replace("_", " ");
if (col.Name == nameof(GridEntry.Title))
col.Width *= 2;
if (col.Name == nameof(GridEntry.Misc))
col.Width = (int)(col.Width * 1.35);
col.Width = col.Name switch
{
nameof(GridEntry.Cover) => 80,
nameof(GridEntry.Title) => col.Width * 2,
nameof(GridEntry.Misc) => (int)(col.Width * 1.35),
var n when n.In(nameof(GridEntry.My_Rating), nameof(GridEntry.Product_Rating)) => col.Width + 8,
_ => col.Width
};
}
@ -88,12 +92,14 @@ namespace LibationWinForm
// transform into sorted GridEntry.s BEFORE binding
//
var lib = LibraryQueries.GetLibrary_Flat_NoTracking();
var orderedGridEntries = lib
var orderedGridEntries = lib
.Select(lb => new GridEntry(lb)).ToList()
// default load order: sort by author, then series, then title
.OrderBy(ge => ge.Authors)
.ThenBy(ge => ge.Series)
.ThenBy(ge => ge.Title)
// default load order
.OrderByDescending(ge => ge.Purchase_Date)
//// more advanced example: sort by author, then series, then title
//.OrderBy(ge => ge.Authors)
// .ThenBy(ge => ge.Series)
// .ThenBy(ge => ge.Title)
.ToList();
//