Serialize getting API instances so that logins don't conflict

This commit is contained in:
Robert McRackan 2020-08-28 10:51:42 -04:00
parent 60f1d8117d
commit a12391f0ab
5 changed files with 21 additions and 62 deletions

View File

@ -41,13 +41,23 @@ namespace ApplicationServices
private static async Task<List<ImportItem>> scanAccountsAsync(ILoginCallback callback, Account[] accounts) private static async Task<List<ImportItem>> scanAccountsAsync(ILoginCallback callback, Account[] accounts)
{ {
var tasks = accounts.Select(account => scanAccountAsync(callback, account)).ToList(); var tasks = new List<Task<List<ImportItem>>>();
foreach (var account in accounts)
{
// get APIs in serial, esp b/c of logins
var api = await AudibleApiActions.GetApiAsync(callback, account);
// add scanAccountAsync as a TASK: do not await
tasks.Add(scanAccountAsync(api, account));
}
// import library in parallel
var arrayOfLists = await Task.WhenAll(tasks); var arrayOfLists = await Task.WhenAll(tasks);
var importItems = arrayOfLists.SelectMany(a => a).ToList(); var importItems = arrayOfLists.SelectMany(a => a).ToList();
return importItems; return importItems;
} }
private static async Task<List<ImportItem>> scanAccountAsync(ILoginCallback callback, Account account) private static async Task<List<ImportItem>> scanAccountAsync(Api api, Account account)
{ {
Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account)); Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
@ -59,7 +69,7 @@ namespace ApplicationServices
LocaleName = localeName, LocaleName = localeName,
}); });
var dtoItems = await AudibleApiActions.GetAllLibraryItemsAsync(account, callback); var dtoItems = await AudibleApiActions.GetLibraryValidatedAsync(api);
return dtoItems.Select(d => new ImportItem { DtoItem = d, AccountId = account.AccountId, LocaleName = localeName }).ToList(); return dtoItems.Select(d => new ImportItem { DtoItem = d, AccountId = account.AccountId, LocaleName = localeName }).ToList();
} }

View File

@ -20,7 +20,7 @@ namespace InternalUtilities
loginCallback); loginCallback);
/// <summary>USE THIS from within Libation. It wraps the call with correct JSONPath</summary> /// <summary>USE THIS from within Libation. It wraps the call with correct JSONPath</summary>
public static Task<Api> GetApiAsync(Account account, ILoginCallback loginCallback = null) public static Task<Api> GetApiAsync(ILoginCallback loginCallback, Account account)
=> EzApiCreator.GetApiAsync( => EzApiCreator.GetApiAsync(
account.Locale, account.Locale,
AudibleApiStorage.AccountsSettingsFile, AudibleApiStorage.AccountsSettingsFile,
@ -32,18 +32,17 @@ namespace InternalUtilities
// 2 retries == 3 total // 2 retries == 3 total
.RetryAsync(2); .RetryAsync(2);
public static Task<List<Item>> GetAllLibraryItemsAsync(Account account, ILoginCallback callback) public static Task<List<Item>> GetLibraryValidatedAsync(Api api)
{ {
// 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 // 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: // 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 }); // 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 // i don't want to incur the cost of making a full dummy call every time because it fails sometimes
return policy.ExecuteAsync(() => getItemsAsync(account, callback)); return policy.ExecuteAsync(() => getItemsAsync(api));
} }
private static async Task<List<Item>> getItemsAsync(Account account, ILoginCallback callback) private static async Task<List<Item>> getItemsAsync(Api api)
{ {
var api = await GetApiAsync(account, callback);
var items = await api.GetAllLibraryItemsAsync(); var items = await api.GetAllLibraryItemsAsync();
// remove episode parents and 'audible plus' check-outs // remove episode parents and 'audible plus' check-outs

View File

@ -1,54 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AudibleApi;
using AudibleApiDTOs;
//
// probably not the best place for this
// but good enough for now
//
namespace InternalUtilities
{
public static class AudibleApiExtensions
{
public static async Task<List<Item>> GetAllLibraryItemsAsync(this Api api)
{
var allItems = new List<Item>();
for (var i = 1; ; i++)
{
var page = await api.GetLibraryAsync(new LibraryOptions
{
NumberOfResultPerPage = 1000,
PageNumber = i,
PurchasedAfter = new DateTime(2000, 1, 1),
ResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS
});
var pageStr = page.ToString();
LibraryDtoV10 libResult;
try
{
// 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;
Serilog.Log.Logger.Information($"Page {i}: {libResult.Items.Length} results");
allItems.AddRange(libResult.Items);
}
return allItems;
}
}
}

View File

@ -17,7 +17,9 @@ namespace LibationWinForms.Dialogs.Login
{ {
Email = this.emailTb.Text; Email = this.emailTb.Text;
Password = this.passwordTb.Text; Password = this.passwordTb.Text;
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
// Close() not needed for AcceptButton
} }
} }
} }

View File

@ -25,7 +25,9 @@ namespace LibationWinForms.Dialogs.Login
private void submitBtn_Click(object sender, EventArgs e) private void submitBtn_Click(object sender, EventArgs e)
{ {
Answer = this.answerTb.Text; Answer = this.answerTb.Text;
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
// Close() not needed for AcceptButton
} }
} }
} }