Merge pull request #271 from Mbucari/master
Move Remove Books function into main grid, added more db migrations and fixups for episodes
@ -221,9 +221,10 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
int bytesRead;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ);
|
bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ);
|
||||||
_writeFile.Write(buff, 0, bytesRead);
|
_writeFile.Write(buff, 0, bytesRead);
|
||||||
|
|
||||||
downloadPosition += bytesRead;
|
downloadPosition += bytesRead;
|
||||||
@ -237,7 +238,7 @@ namespace AaxDecrypter
|
|||||||
downloadedPiece.Set();
|
downloadedPiece.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
} while (downloadPosition < ContentLength && !IsCancelled);
|
} while (downloadPosition < ContentLength && !IsCancelled && bytesRead > 0);
|
||||||
|
|
||||||
_writeFile.Close();
|
_writeFile.Close();
|
||||||
_networkStream.Close();
|
_networkStream.Close();
|
||||||
|
|||||||
@ -5,10 +5,10 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using ApplicationServices;
|
using ApplicationServices;
|
||||||
using AudibleUtilities;
|
using AudibleUtilities;
|
||||||
using Dinah.Core;
|
|
||||||
using Dinah.Core.IO;
|
using Dinah.Core.IO;
|
||||||
using Dinah.Core.Logging;
|
using Dinah.Core.Logging;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
@ -405,25 +405,71 @@ namespace AppScaffolding
|
|||||||
|
|
||||||
public static void migrate_from_7_10_1(Configuration config)
|
public static void migrate_from_7_10_1(Configuration config)
|
||||||
{
|
{
|
||||||
//This migration removes books and series with SERIES_ prefix that were created
|
var lastNigrationThres = config.GetNonString<bool>($"{nameof(migrate_from_7_10_1)}_ThrewError");
|
||||||
//as a hack workaround in 7.10.1. Said workaround was removed in 7.10.2
|
|
||||||
|
|
||||||
var migrated = config.GetNonString<bool>(nameof(migrate_from_7_10_1));
|
if (lastNigrationThres) return;
|
||||||
|
|
||||||
if (migrated) return;
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
//https://github.com/rmcrackan/Libation/issues/270#issuecomment-1152863629
|
||||||
|
//This migration helps fix databases contaminated with the 7.10.1 hack workaround
|
||||||
|
//and those with improperly identified or missing series. This does not solve cases
|
||||||
|
//where individual episodes are in the db with a valid series link, but said series'
|
||||||
|
//parents have not been imported into the database. For those cases, Libation will
|
||||||
|
//attempt fixup by retrieving parents from the catalog endpoint
|
||||||
|
|
||||||
using var context = DbContexts.GetContext();
|
using var context = DbContexts.GetContext();
|
||||||
|
|
||||||
var booksToRemove = context.Books.Where(b => b.AudibleProductId.StartsWith("SERIES_")).ToArray();
|
//This migration removes books and series with SERIES_ prefix that were created
|
||||||
var seriesToRemove = context.Series.Where(s => s.AudibleSeriesId.StartsWith("SERIES_")).ToArray();
|
//as a hack workaround in 7.10.1. Said workaround was removed in 7.10.2
|
||||||
var lbToRemove = context.LibraryBooks.Where(lb => booksToRemove.Any(b => b == lb.Book)).ToArray();
|
string removeHackSeries = "delete " +
|
||||||
|
"from series " +
|
||||||
|
"where AudibleSeriesId like 'SERIES%'";
|
||||||
|
|
||||||
context.LibraryBooks.RemoveRange(lbToRemove);
|
string removeHackBooks = "delete " +
|
||||||
context.Books.RemoveRange(booksToRemove);
|
"from books " +
|
||||||
context.Series.RemoveRange(seriesToRemove);
|
"where AudibleProductId like 'SERIES%'";
|
||||||
|
|
||||||
|
//Detect series parents that were added to the database as books with ContentType.Episode,
|
||||||
|
//and change them to ContentType.Parent
|
||||||
|
string updateContentType =
|
||||||
|
"UPDATE books " +
|
||||||
|
"SET contenttype = 4 " +
|
||||||
|
"WHERE audibleproductid IN (SELECT books.audibleproductid " +
|
||||||
|
"FROM books " +
|
||||||
|
"INNER JOIN series " +
|
||||||
|
"ON ( books.audibleproductid = " +
|
||||||
|
"series.audibleseriesid) " +
|
||||||
|
"WHERE books.contenttype = 2)";
|
||||||
|
|
||||||
|
//Then detect series parents that were added to the database as books with ContentType.Parent
|
||||||
|
//but are missing a series link, and add the link (don't know how this happened)
|
||||||
|
string addMissingSeriesLink =
|
||||||
|
"INSERT INTO seriesbook " +
|
||||||
|
"SELECT series.seriesid, " +
|
||||||
|
"books.bookid, " +
|
||||||
|
"'- 1' " +
|
||||||
|
"FROM books " +
|
||||||
|
"LEFT OUTER JOIN seriesbook " +
|
||||||
|
"ON books.bookid = seriesbook.bookid " +
|
||||||
|
"INNER JOIN series " +
|
||||||
|
"ON books.audibleproductid = series.audibleseriesid " +
|
||||||
|
"WHERE books.contenttype = 4 " +
|
||||||
|
"AND seriesbook.seriesid IS NULL";
|
||||||
|
|
||||||
|
context.Database.ExecuteSqlRaw(removeHackSeries);
|
||||||
|
context.Database.ExecuteSqlRaw(removeHackBooks);
|
||||||
|
context.Database.ExecuteSqlRaw(updateContentType);
|
||||||
|
context.Database.ExecuteSqlRaw(addMissingSeriesLink);
|
||||||
|
|
||||||
LibraryCommands.SaveContext(context);
|
LibraryCommands.SaveContext(context);
|
||||||
config.SetObject(nameof(migrate_from_7_10_1), true);
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Serilog.Log.Logger.Error(ex, "An error occured while running database migrations in {0}", nameof(migrate_from_7_10_1));
|
||||||
|
config.SetObject($"{nameof(migrate_from_7_10_1)}_ThrewError", true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,10 +27,17 @@ namespace ApplicationServices
|
|||||||
ScanEnd += (_, __) => Scanning = false;
|
ScanEnd += (_, __) => Scanning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<List<LibraryBook>> FindInactiveBooks(Func<Account, Task<ApiExtended>> apiExtendedfunc, List<LibraryBook> existingLibrary, params Account[] accounts)
|
public static async Task<List<LibraryBook>> FindInactiveBooks(Func<Account, Task<ApiExtended>> apiExtendedfunc, IEnumerable<LibraryBook> existingLibrary, params Account[] accounts)
|
||||||
{
|
{
|
||||||
logRestart();
|
logRestart();
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (Scanning)
|
||||||
|
return new();
|
||||||
|
}
|
||||||
|
ScanBegin?.Invoke(null, accounts.Length);
|
||||||
|
|
||||||
//These are the minimum response groups required for the
|
//These are the minimum response groups required for the
|
||||||
//library scanner to pass all validation and filtering.
|
//library scanner to pass all validation and filtering.
|
||||||
var libraryOptions = new LibraryOptions
|
var libraryOptions = new LibraryOptions
|
||||||
@ -83,6 +90,7 @@ namespace ApplicationServices
|
|||||||
{
|
{
|
||||||
stop();
|
stop();
|
||||||
var putBreakPointHere = logOutput;
|
var putBreakPointHere = logOutput;
|
||||||
|
ScanEnd?.Invoke(null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,8 +108,8 @@ namespace ApplicationServices
|
|||||||
{
|
{
|
||||||
if (Scanning)
|
if (Scanning)
|
||||||
return (0, 0);
|
return (0, 0);
|
||||||
ScanBegin?.Invoke(null, accounts.Length);
|
|
||||||
}
|
}
|
||||||
|
ScanBegin?.Invoke(null, accounts.Length);
|
||||||
|
|
||||||
logTime($"pre {nameof(scanAccountsAsync)} all");
|
logTime($"pre {nameof(scanAccountsAsync)} all");
|
||||||
var libraryOptions = new LibraryOptions
|
var libraryOptions = new LibraryOptions
|
||||||
@ -118,6 +126,22 @@ namespace ApplicationServices
|
|||||||
if (totalCount == 0)
|
if (totalCount == 0)
|
||||||
return default;
|
return default;
|
||||||
|
|
||||||
|
|
||||||
|
Log.Logger.Information("Begin scan for orphaned episode parents");
|
||||||
|
var newParents = await findAndAddMissingParents(apiExtendedfunc, accounts);
|
||||||
|
Log.Logger.Information($"Orphan episode scan complete. New parents count {newParents}");
|
||||||
|
|
||||||
|
if (newParents >= 0)
|
||||||
|
{
|
||||||
|
//If any episodes are still orphaned, their series have been
|
||||||
|
//removed from the catalog and wel'll never be able to find them.
|
||||||
|
|
||||||
|
//only do this if findAndAddMissingParents returned >= 0. If it
|
||||||
|
//returned < 0, an error happened and there's still a chance that
|
||||||
|
//a future successful run will find missing parents.
|
||||||
|
removedOrphanedEpisodes();
|
||||||
|
}
|
||||||
|
|
||||||
Log.Logger.Information("Begin long-running import");
|
Log.Logger.Information("Begin long-running import");
|
||||||
logTime($"pre {nameof(importIntoDbAsync)}");
|
logTime($"pre {nameof(importIntoDbAsync)}");
|
||||||
var newCount = await importIntoDbAsync(importItems);
|
var newCount = await importIntoDbAsync(importItems);
|
||||||
@ -211,6 +235,84 @@ namespace ApplicationServices
|
|||||||
return newCount;
|
return newCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void removedOrphanedEpisodes()
|
||||||
|
{
|
||||||
|
using var context = DbContexts.GetContext();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var orphanedEpisodes =
|
||||||
|
context
|
||||||
|
.GetLibrary_Flat_NoTracking(includeParents: true)
|
||||||
|
.FindOrphanedEpisodes();
|
||||||
|
|
||||||
|
context.LibraryBooks.RemoveRange(orphanedEpisodes);
|
||||||
|
context.Books.RemoveRange(orphanedEpisodes.Select(lb => lb.Book));
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Serilog.Log.Logger.Error(ex, "An error occured while trying to remove orphaned episodes from the database");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async Task<int> findAndAddMissingParents(Func<Account, Task<ApiExtended>> apiExtendedfunc, Account[] accounts)
|
||||||
|
{
|
||||||
|
using var context = DbContexts.GetContext();
|
||||||
|
|
||||||
|
var library = context.GetLibrary_Flat_NoTracking(includeParents: true);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var orphanedEpisodes = library.FindOrphanedEpisodes().ToList();
|
||||||
|
|
||||||
|
if (!orphanedEpisodes.Any())
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
var orphanedSeries =
|
||||||
|
orphanedEpisodes
|
||||||
|
.SelectMany(lb => lb.Book.SeriesLink)
|
||||||
|
.DistinctBy(s => s.Series.AudibleSeriesId)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// We're only calling the Catalog endpoint, so it doesn't matter which account we use.
|
||||||
|
var apiExtended = await apiExtendedfunc(accounts[0]);
|
||||||
|
|
||||||
|
var seriesParents = orphanedSeries.Select(o => o.Series.AudibleSeriesId).ToList();
|
||||||
|
var items = await apiExtended.Api.GetCatalogProductsAsync(seriesParents, CatalogOptions.ResponseGroupOptions.ALL_OPTIONS);
|
||||||
|
|
||||||
|
List<ImportItem> newParentsImportItems = new();
|
||||||
|
foreach (var sp in orphanedSeries)
|
||||||
|
{
|
||||||
|
var seriesItem = items.First(i => i.Asin == sp.Series.AudibleSeriesId);
|
||||||
|
|
||||||
|
if (seriesItem.Relationships is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var episode = orphanedEpisodes.First(l => l.Book.AudibleProductId == sp.Book.AudibleProductId);
|
||||||
|
|
||||||
|
seriesItem.PurchaseDate = new DateTimeOffset(episode.DateAdded);
|
||||||
|
seriesItem.Series = new AudibleApi.Common.Series[]
|
||||||
|
{
|
||||||
|
new AudibleApi.Common.Series{ Asin = seriesItem.Asin, Title = seriesItem.TitleWithSubtitle, Sequence = "-1"}
|
||||||
|
};
|
||||||
|
|
||||||
|
newParentsImportItems.Add(new ImportItem { DtoItem = seriesItem, AccountId = episode.Account, LocaleName = episode.Book.Locale });
|
||||||
|
}
|
||||||
|
|
||||||
|
var newCoutn = new LibraryBookImporter(context)
|
||||||
|
.Import(newParentsImportItems);
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return newCoutn;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Serilog.Log.Logger.Error(ex, "An error occured while trying to scan for orphaned episode parents.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static int SaveContext(LibationContext context)
|
public static int SaveContext(LibationContext context)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
199
Source/AudibleUtilities/Mkb79Auth.cs
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using AudibleApi;
|
||||||
|
using AudibleApi.Authorization;
|
||||||
|
using Dinah.Core;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace AudibleUtilities
|
||||||
|
{
|
||||||
|
public partial class Mkb79Auth : IIdentityMaintainer
|
||||||
|
{
|
||||||
|
[JsonProperty("website_cookies")]
|
||||||
|
private JObject _websiteCookies { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("adp_token")]
|
||||||
|
public string AdpToken { get; private set; }
|
||||||
|
|
||||||
|
[JsonProperty("access_token")]
|
||||||
|
public string AccessToken { get; private set; }
|
||||||
|
|
||||||
|
[JsonProperty("refresh_token")]
|
||||||
|
public string RefreshToken { get; private set; }
|
||||||
|
|
||||||
|
[JsonProperty("device_private_key")]
|
||||||
|
public string DevicePrivateKey { get; private set; }
|
||||||
|
|
||||||
|
[JsonProperty("store_authentication_cookie")]
|
||||||
|
private JObject _storeAuthenticationCookie { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("device_info")]
|
||||||
|
public DeviceInfo DeviceInfo { get; private set; }
|
||||||
|
|
||||||
|
[JsonProperty("customer_info")]
|
||||||
|
public CustomerInfo CustomerInfo { get; private set; }
|
||||||
|
|
||||||
|
[JsonProperty("expires")]
|
||||||
|
private double _expires { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("locale_code")]
|
||||||
|
public string LocaleCode { get; private set; }
|
||||||
|
|
||||||
|
[JsonProperty("activation_bytes")]
|
||||||
|
public string ActivationBytes { get; private set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public Dictionary<string, string> WebsiteCookies
|
||||||
|
{
|
||||||
|
get => _websiteCookies.ToObject<Dictionary<string, string>>();
|
||||||
|
private set => _websiteCookies = JObject.Parse(JsonConvert.SerializeObject(value, Converter.Settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string StoreAuthenticationCookie
|
||||||
|
{
|
||||||
|
get => _storeAuthenticationCookie.ToObject<Dictionary<string, string>>()["cookie"];
|
||||||
|
private set => _storeAuthenticationCookie = JObject.Parse(JsonConvert.SerializeObject(new Dictionary<string, string>() { { "cookie", value } }, Converter.Settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public DateTime AccessTokenExpires
|
||||||
|
{
|
||||||
|
get => DateTimeOffset.FromUnixTimeMilliseconds((long)(_expires * 1000)).DateTime;
|
||||||
|
private set => _expires = new DateTimeOffset(value).ToUnixTimeMilliseconds() / 1000d;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore] public ISystemDateTime SystemDateTime { get; } = new SystemDateTime();
|
||||||
|
[JsonIgnore] public Locale Locale => Localization.Get(LocaleCode);
|
||||||
|
[JsonIgnore] public string DeviceSerialNumber => DeviceInfo.DeviceSerialNumber;
|
||||||
|
[JsonIgnore] public string DeviceType => DeviceInfo.DeviceType;
|
||||||
|
[JsonIgnore] public string AmazonAccountId => CustomerInfo.UserId;
|
||||||
|
|
||||||
|
public Task<AccessToken> GetAccessTokenAsync()
|
||||||
|
=> Task.FromResult(new AccessToken(AccessToken, AccessTokenExpires));
|
||||||
|
|
||||||
|
public Task<AdpToken> GetAdpTokenAsync()
|
||||||
|
=> Task.FromResult(new AdpToken(AdpToken));
|
||||||
|
|
||||||
|
public Task<PrivateKey> GetPrivateKeyAsync()
|
||||||
|
=> Task.FromResult(new PrivateKey(DevicePrivateKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class CustomerInfo
|
||||||
|
{
|
||||||
|
[JsonProperty("account_pool")]
|
||||||
|
public string AccountPool { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("user_id")]
|
||||||
|
public string UserId { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("home_region")]
|
||||||
|
public string HomeRegion { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("given_name")]
|
||||||
|
public string GivenName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class DeviceInfo
|
||||||
|
{
|
||||||
|
[JsonProperty("device_name")]
|
||||||
|
public string DeviceName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("device_serial_number")]
|
||||||
|
public string DeviceSerialNumber { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("device_type")]
|
||||||
|
public string DeviceType { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class Mkb79Auth
|
||||||
|
{
|
||||||
|
public static Mkb79Auth FromJson(string json)
|
||||||
|
=> JsonConvert.DeserializeObject<Mkb79Auth>(json, Converter.Settings);
|
||||||
|
|
||||||
|
public string ToJson()
|
||||||
|
=> JObject.Parse(JsonConvert.SerializeObject(this, Converter.Settings)).ToString(Formatting.Indented);
|
||||||
|
|
||||||
|
public async Task<Account> ToAccountAsync()
|
||||||
|
{
|
||||||
|
var refreshToken = new RefreshToken(RefreshToken);
|
||||||
|
|
||||||
|
var authorize = new Authorize(Locale);
|
||||||
|
var newToken = await authorize.RefreshAccessTokenAsync(refreshToken);
|
||||||
|
AccessToken = newToken.TokenValue;
|
||||||
|
AccessTokenExpires = newToken.Expires;
|
||||||
|
|
||||||
|
var api = new Api(this);
|
||||||
|
var email = await api.GetEmailAsync();
|
||||||
|
var account = new Account(email)
|
||||||
|
{
|
||||||
|
DecryptKey = ActivationBytes,
|
||||||
|
AccountName = $"{email} - {Locale.Name}",
|
||||||
|
IdentityTokens = new Identity(Locale)
|
||||||
|
};
|
||||||
|
|
||||||
|
account.IdentityTokens.Update(
|
||||||
|
await GetPrivateKeyAsync(),
|
||||||
|
await GetAdpTokenAsync(),
|
||||||
|
await GetAccessTokenAsync(),
|
||||||
|
refreshToken,
|
||||||
|
WebsiteCookies.Select(c => new KeyValuePair<string, string>(c.Key, c.Value)),
|
||||||
|
DeviceSerialNumber,
|
||||||
|
DeviceType,
|
||||||
|
AmazonAccountId,
|
||||||
|
DeviceInfo.DeviceName,
|
||||||
|
StoreAuthenticationCookie);
|
||||||
|
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Mkb79Auth FromAccount(Account account)
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
AccessToken = account.IdentityTokens.ExistingAccessToken.TokenValue,
|
||||||
|
ActivationBytes = string.IsNullOrEmpty(account.DecryptKey) ? null : account.DecryptKey,
|
||||||
|
AdpToken = account.IdentityTokens.AdpToken.Value,
|
||||||
|
CustomerInfo = new CustomerInfo
|
||||||
|
{
|
||||||
|
AccountPool = "Amazon",
|
||||||
|
GivenName = string.Empty,
|
||||||
|
HomeRegion = "NA",
|
||||||
|
Name = string.Empty,
|
||||||
|
UserId = account.IdentityTokens.AmazonAccountId
|
||||||
|
},
|
||||||
|
DeviceInfo = new DeviceInfo
|
||||||
|
{
|
||||||
|
DeviceName = account.IdentityTokens.DeviceName,
|
||||||
|
DeviceSerialNumber = account.IdentityTokens.DeviceSerialNumber,
|
||||||
|
DeviceType = account.IdentityTokens.DeviceType,
|
||||||
|
},
|
||||||
|
DevicePrivateKey = account.IdentityTokens.PrivateKey,
|
||||||
|
AccessTokenExpires = account.IdentityTokens.ExistingAccessToken.Expires,
|
||||||
|
LocaleCode = account.Locale.CountryCode,
|
||||||
|
RefreshToken = account.IdentityTokens.RefreshToken.Value,
|
||||||
|
StoreAuthenticationCookie = account.IdentityTokens.StoreAuthenticationCookie,
|
||||||
|
WebsiteCookies = new(account.IdentityTokens.Cookies.ToKeyValuePair()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Serialize
|
||||||
|
{
|
||||||
|
public static string ToJson(this Mkb79Auth self)
|
||||||
|
=> JObject.Parse(JsonConvert.SerializeObject(self, Converter.Settings)).ToString(Formatting.Indented);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class Converter
|
||||||
|
{
|
||||||
|
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
|
||||||
|
DateParseHandling = DateParseHandling.None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -44,6 +44,8 @@ namespace DataLayer
|
|||||||
|
|
||||||
public static bool IsEpisodeParent(this Book book)
|
public static bool IsEpisodeParent(this Book book)
|
||||||
=> book.ContentType is ContentType.Parent;
|
=> book.ContentType is ContentType.Parent;
|
||||||
|
public static bool HasLiberated(this Book book)
|
||||||
|
=> book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated ||
|
||||||
|
book.UserDefinedItem.PdfStatus is not null and LiberatedStatus.Liberated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,11 +43,24 @@ namespace DataLayer
|
|||||||
.Include(le => le.Book).ThenInclude(b => b.ContributorsLink).ThenInclude(c => c.Contributor)
|
.Include(le => le.Book).ThenInclude(b => b.ContributorsLink).ThenInclude(c => c.Contributor)
|
||||||
.Include(le => le.Book).ThenInclude(b => b.Category).ThenInclude(c => c.ParentCategory);
|
.Include(le => le.Book).ThenInclude(b => b.Category).ThenInclude(c => c.ParentCategory);
|
||||||
|
|
||||||
|
public static IEnumerable<LibraryBook> ParentedEpisodes(this IEnumerable<LibraryBook> libraryBooks)
|
||||||
|
=> libraryBooks.Where(lb => lb.Book.IsEpisodeParent()).SelectMany(s => libraryBooks.FindChildren(s));
|
||||||
|
|
||||||
|
public static IEnumerable<LibraryBook> FindOrphanedEpisodes(this IEnumerable<LibraryBook> libraryBooks)
|
||||||
|
=> libraryBooks
|
||||||
|
.Where(lb => lb.Book.IsEpisodeChild())
|
||||||
|
.ExceptBy(
|
||||||
|
libraryBooks
|
||||||
|
.ParentedEpisodes()
|
||||||
|
.Select(ge => ge.Book.AudibleProductId), ge => ge.Book.AudibleProductId);
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
public static LibraryBook? FindSeriesParent(this IEnumerable<LibraryBook> libraryBooks, LibraryBook seriesEpisode)
|
public static LibraryBook? FindSeriesParent(this IEnumerable<LibraryBook> libraryBooks, LibraryBook seriesEpisode)
|
||||||
{
|
{
|
||||||
if (seriesEpisode.Book.SeriesLink is null) return null;
|
if (seriesEpisode.Book.SeriesLink is null) return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
//Parent books will always have exactly 1 SeriesBook due to how
|
//Parent books will always have exactly 1 SeriesBook due to how
|
||||||
//they are imported in ApiExtended.getChildEpisodesAsync()
|
//they are imported in ApiExtended.getChildEpisodesAsync()
|
||||||
return libraryBooks.FirstOrDefault(
|
return libraryBooks.FirstOrDefault(
|
||||||
@ -56,6 +69,12 @@ namespace DataLayer
|
|||||||
seriesEpisode.Book.SeriesLink.Any(
|
seriesEpisode.Book.SeriesLink.Any(
|
||||||
s => s.Series.AudibleSeriesId == lb.Book.SeriesLink.Single().Series.AudibleSeriesId));
|
s => s.Series.AudibleSeriesId == lb.Book.SeriesLink.Single().Series.AudibleSeriesId));
|
||||||
}
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Serilog.Log.Error(ex, "Query error in {0}", nameof(FindSeriesParent));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
public static IEnumerable<LibraryBook> FindChildren(this IEnumerable<LibraryBook> bookList, LibraryBook parent)
|
public static IEnumerable<LibraryBook> FindChildren(this IEnumerable<LibraryBook> bookList, LibraryBook parent)
|
||||||
|
|||||||
@ -31,7 +31,9 @@
|
|||||||
this.cancelBtn = new System.Windows.Forms.Button();
|
this.cancelBtn = new System.Windows.Forms.Button();
|
||||||
this.saveBtn = new System.Windows.Forms.Button();
|
this.saveBtn = new System.Windows.Forms.Button();
|
||||||
this.dataGridView1 = new System.Windows.Forms.DataGridView();
|
this.dataGridView1 = new System.Windows.Forms.DataGridView();
|
||||||
|
this.importBtn = new System.Windows.Forms.Button();
|
||||||
this.DeleteAccount = new System.Windows.Forms.DataGridViewButtonColumn();
|
this.DeleteAccount = new System.Windows.Forms.DataGridViewButtonColumn();
|
||||||
|
this.ExportAccount = new System.Windows.Forms.DataGridViewButtonColumn();
|
||||||
this.LibraryScan = new System.Windows.Forms.DataGridViewCheckBoxColumn();
|
this.LibraryScan = new System.Windows.Forms.DataGridViewCheckBoxColumn();
|
||||||
this.AccountId = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
this.AccountId = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||||
this.Locale = new System.Windows.Forms.DataGridViewComboBoxColumn();
|
this.Locale = new System.Windows.Forms.DataGridViewComboBoxColumn();
|
||||||
@ -43,9 +45,10 @@
|
|||||||
//
|
//
|
||||||
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||||
this.cancelBtn.Location = new System.Drawing.Point(713, 415);
|
this.cancelBtn.Location = new System.Drawing.Point(832, 479);
|
||||||
|
this.cancelBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.cancelBtn.Name = "cancelBtn";
|
this.cancelBtn.Name = "cancelBtn";
|
||||||
this.cancelBtn.Size = new System.Drawing.Size(75, 23);
|
this.cancelBtn.Size = new System.Drawing.Size(88, 27);
|
||||||
this.cancelBtn.TabIndex = 2;
|
this.cancelBtn.TabIndex = 2;
|
||||||
this.cancelBtn.Text = "Cancel";
|
this.cancelBtn.Text = "Cancel";
|
||||||
this.cancelBtn.UseVisualStyleBackColor = true;
|
this.cancelBtn.UseVisualStyleBackColor = true;
|
||||||
@ -54,9 +57,10 @@
|
|||||||
// saveBtn
|
// saveBtn
|
||||||
//
|
//
|
||||||
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.saveBtn.Location = new System.Drawing.Point(612, 415);
|
this.saveBtn.Location = new System.Drawing.Point(714, 479);
|
||||||
|
this.saveBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.saveBtn.Name = "saveBtn";
|
this.saveBtn.Name = "saveBtn";
|
||||||
this.saveBtn.Size = new System.Drawing.Size(75, 23);
|
this.saveBtn.Size = new System.Drawing.Size(88, 27);
|
||||||
this.saveBtn.TabIndex = 1;
|
this.saveBtn.TabIndex = 1;
|
||||||
this.saveBtn.Text = "Save";
|
this.saveBtn.Text = "Save";
|
||||||
this.saveBtn.UseVisualStyleBackColor = true;
|
this.saveBtn.UseVisualStyleBackColor = true;
|
||||||
@ -71,60 +75,83 @@
|
|||||||
this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||||
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||||
this.DeleteAccount,
|
this.DeleteAccount,
|
||||||
|
this.ExportAccount,
|
||||||
this.LibraryScan,
|
this.LibraryScan,
|
||||||
this.AccountId,
|
this.AccountId,
|
||||||
this.Locale,
|
this.Locale,
|
||||||
this.AccountName});
|
this.AccountName});
|
||||||
this.dataGridView1.Location = new System.Drawing.Point(12, 12);
|
this.dataGridView1.Location = new System.Drawing.Point(14, 14);
|
||||||
|
this.dataGridView1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.dataGridView1.MultiSelect = false;
|
this.dataGridView1.MultiSelect = false;
|
||||||
this.dataGridView1.Name = "dataGridView1";
|
this.dataGridView1.Name = "dataGridView1";
|
||||||
this.dataGridView1.Size = new System.Drawing.Size(776, 397);
|
this.dataGridView1.Size = new System.Drawing.Size(905, 458);
|
||||||
this.dataGridView1.TabIndex = 0;
|
this.dataGridView1.TabIndex = 0;
|
||||||
this.dataGridView1.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.DataGridView1_CellContentClick);
|
this.dataGridView1.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.DataGridView1_CellContentClick);
|
||||||
this.dataGridView1.DefaultValuesNeeded += new System.Windows.Forms.DataGridViewRowEventHandler(this.dataGridView1_DefaultValuesNeeded);
|
this.dataGridView1.DefaultValuesNeeded += new System.Windows.Forms.DataGridViewRowEventHandler(this.dataGridView1_DefaultValuesNeeded);
|
||||||
//
|
//
|
||||||
|
// importBtn
|
||||||
|
//
|
||||||
|
this.importBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||||
|
this.importBtn.Location = new System.Drawing.Point(14, 480);
|
||||||
|
this.importBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
|
this.importBtn.Name = "importBtn";
|
||||||
|
this.importBtn.Size = new System.Drawing.Size(156, 27);
|
||||||
|
this.importBtn.TabIndex = 1;
|
||||||
|
this.importBtn.Text = "Import from audible-cli";
|
||||||
|
this.importBtn.UseVisualStyleBackColor = true;
|
||||||
|
this.importBtn.Click += new System.EventHandler(this.importBtn_Click);
|
||||||
|
//
|
||||||
// DeleteAccount
|
// DeleteAccount
|
||||||
//
|
//
|
||||||
this.DeleteAccount.HeaderText = "Delete";
|
this.DeleteAccount.HeaderText = "Delete";
|
||||||
this.DeleteAccount.Name = "DeleteAccount";
|
this.DeleteAccount.Name = "DeleteAccount";
|
||||||
this.DeleteAccount.ReadOnly = true;
|
this.DeleteAccount.ReadOnly = true;
|
||||||
this.DeleteAccount.Text = "x";
|
this.DeleteAccount.Text = "x";
|
||||||
this.DeleteAccount.Width = 44;
|
this.DeleteAccount.Width = 46;
|
||||||
|
//
|
||||||
|
// ExportAccount
|
||||||
|
//
|
||||||
|
this.ExportAccount.HeaderText = "Export";
|
||||||
|
this.ExportAccount.Name = "ExportAccount";
|
||||||
|
this.ExportAccount.Text = "Export to audible-cli";
|
||||||
|
this.ExportAccount.Width = 47;
|
||||||
//
|
//
|
||||||
// LibraryScan
|
// LibraryScan
|
||||||
//
|
//
|
||||||
this.LibraryScan.HeaderText = "Include in library scan?";
|
this.LibraryScan.HeaderText = "Include in library scan?";
|
||||||
this.LibraryScan.Name = "LibraryScan";
|
this.LibraryScan.Name = "LibraryScan";
|
||||||
this.LibraryScan.Width = 83;
|
this.LibraryScan.Width = 94;
|
||||||
//
|
//
|
||||||
// AccountId
|
// AccountId
|
||||||
//
|
//
|
||||||
this.AccountId.HeaderText = "Audible email/login";
|
this.AccountId.HeaderText = "Audible email/login";
|
||||||
this.AccountId.Name = "AccountId";
|
this.AccountId.Name = "AccountId";
|
||||||
this.AccountId.Width = 111;
|
this.AccountId.Width = 125;
|
||||||
//
|
//
|
||||||
// Locale
|
// Locale
|
||||||
//
|
//
|
||||||
this.Locale.HeaderText = "Locale";
|
this.Locale.HeaderText = "Locale";
|
||||||
this.Locale.Name = "Locale";
|
this.Locale.Name = "Locale";
|
||||||
this.Locale.Width = 45;
|
this.Locale.Width = 47;
|
||||||
//
|
//
|
||||||
// AccountName
|
// AccountName
|
||||||
//
|
//
|
||||||
this.AccountName.HeaderText = "Account nickname (optional)";
|
this.AccountName.HeaderText = "Account nickname (optional)";
|
||||||
this.AccountName.Name = "AccountName";
|
this.AccountName.Name = "AccountName";
|
||||||
this.AccountName.Width = 152;
|
this.AccountName.Width = 170;
|
||||||
//
|
//
|
||||||
// AccountsDialog
|
// AccountsDialog
|
||||||
//
|
//
|
||||||
this.AcceptButton = this.saveBtn;
|
this.AcceptButton = this.saveBtn;
|
||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
this.CancelButton = this.cancelBtn;
|
this.CancelButton = this.cancelBtn;
|
||||||
this.ClientSize = new System.Drawing.Size(800, 450);
|
this.ClientSize = new System.Drawing.Size(933, 519);
|
||||||
this.Controls.Add(this.dataGridView1);
|
this.Controls.Add(this.dataGridView1);
|
||||||
|
this.Controls.Add(this.importBtn);
|
||||||
this.Controls.Add(this.saveBtn);
|
this.Controls.Add(this.saveBtn);
|
||||||
this.Controls.Add(this.cancelBtn);
|
this.Controls.Add(this.cancelBtn);
|
||||||
|
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.Name = "AccountsDialog";
|
this.Name = "AccountsDialog";
|
||||||
this.Text = "Audible Accounts";
|
this.Text = "Audible Accounts";
|
||||||
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
|
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
|
||||||
@ -137,7 +164,9 @@
|
|||||||
private System.Windows.Forms.Button cancelBtn;
|
private System.Windows.Forms.Button cancelBtn;
|
||||||
private System.Windows.Forms.Button saveBtn;
|
private System.Windows.Forms.Button saveBtn;
|
||||||
private System.Windows.Forms.DataGridView dataGridView1;
|
private System.Windows.Forms.DataGridView dataGridView1;
|
||||||
|
private System.Windows.Forms.Button importBtn;
|
||||||
private System.Windows.Forms.DataGridViewButtonColumn DeleteAccount;
|
private System.Windows.Forms.DataGridViewButtonColumn DeleteAccount;
|
||||||
|
private System.Windows.Forms.DataGridViewButtonColumn ExportAccount;
|
||||||
private System.Windows.Forms.DataGridViewCheckBoxColumn LibraryScan;
|
private System.Windows.Forms.DataGridViewCheckBoxColumn LibraryScan;
|
||||||
private System.Windows.Forms.DataGridViewTextBoxColumn AccountId;
|
private System.Windows.Forms.DataGridViewTextBoxColumn AccountId;
|
||||||
private System.Windows.Forms.DataGridViewComboBoxColumn Locale;
|
private System.Windows.Forms.DataGridViewComboBoxColumn Locale;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using AudibleApi;
|
using AudibleApi;
|
||||||
@ -10,6 +11,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
public partial class AccountsDialog : Form
|
public partial class AccountsDialog : Form
|
||||||
{
|
{
|
||||||
private const string COL_Delete = nameof(DeleteAccount);
|
private const string COL_Delete = nameof(DeleteAccount);
|
||||||
|
private const string COL_Export = nameof(ExportAccount);
|
||||||
private const string COL_LibraryScan = nameof(LibraryScan);
|
private const string COL_LibraryScan = nameof(LibraryScan);
|
||||||
private const string COL_AccountId = nameof(AccountId);
|
private const string COL_AccountId = nameof(AccountId);
|
||||||
private const string COL_AccountName = nameof(AccountName);
|
private const string COL_AccountName = nameof(AccountName);
|
||||||
@ -44,12 +46,20 @@ namespace LibationWinForms.Dialogs
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var account in accounts)
|
foreach (var account in accounts)
|
||||||
dataGridView1.Rows.Add(
|
AddAccountToGrid(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddAccountToGrid(Account account)
|
||||||
|
{
|
||||||
|
int row = dataGridView1.Rows.Add(
|
||||||
"X",
|
"X",
|
||||||
|
"Export",
|
||||||
account.LibraryScan,
|
account.LibraryScan,
|
||||||
account.AccountId,
|
account.AccountId,
|
||||||
account.Locale.Name,
|
account.Locale.Name,
|
||||||
account.AccountName);
|
account.AccountName);
|
||||||
|
|
||||||
|
dataGridView1[COL_Export, row].ToolTipText = "Export account authorization to audible-cli";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
|
private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
|
||||||
@ -73,6 +83,11 @@ namespace LibationWinForms.Dialogs
|
|||||||
if (e.RowIndex < dgv.RowCount - 1)
|
if (e.RowIndex < dgv.RowCount - 1)
|
||||||
dgv.Rows.Remove(row);
|
dgv.Rows.Remove(row);
|
||||||
break;
|
break;
|
||||||
|
case COL_Export:
|
||||||
|
// if final/edit row: do nothing
|
||||||
|
if (e.RowIndex < dgv.RowCount - 1)
|
||||||
|
Export((string)row.Cells[COL_AccountId].Value, (string)row.Cells[COL_Locale].Value);
|
||||||
|
break;
|
||||||
//case COL_MoveUp:
|
//case COL_MoveUp:
|
||||||
// // if top: do nothing
|
// // if top: do nothing
|
||||||
// if (e.RowIndex < 1)
|
// if (e.RowIndex < 1)
|
||||||
@ -136,13 +151,13 @@ namespace LibationWinForms.Dialogs
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(dto.AccountId))
|
if (string.IsNullOrWhiteSpace(dto.AccountId))
|
||||||
{
|
{
|
||||||
MessageBox.Show("Account id cannot be blank. Please enter an account id for all accounts.", "Blank account", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Show(this, "Account id cannot be blank. Please enter an account id for all accounts.", "Blank account", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(dto.LocaleName))
|
if (string.IsNullOrWhiteSpace(dto.LocaleName))
|
||||||
{
|
{
|
||||||
MessageBox.Show("Please select a locale (i.e.: country or region) for all accounts.", "Blank region", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Show(this, "Please select a locale (i.e.: country or region) for all accounts.", "Blank region", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,5 +209,95 @@ namespace LibationWinForms.Dialogs
|
|||||||
LibraryScan = (bool)r.Cells[COL_LibraryScan].Value
|
LibraryScan = (bool)r.Cells[COL_LibraryScan].Value
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
private string GetAudibleCliAppDataPath()
|
||||||
|
=> Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Audible");
|
||||||
|
|
||||||
|
private void Export(string accountId, string locale)
|
||||||
|
{
|
||||||
|
// without transaction, accounts persister will write ANY EDIT immediately to file
|
||||||
|
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||||
|
|
||||||
|
var account = persister.AccountsSettings.Accounts.FirstOrDefault(a => a.AccountId == accountId && a.Locale.Name == locale);
|
||||||
|
|
||||||
|
if (account is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (account.IdentityTokens?.IsValid != true)
|
||||||
|
{
|
||||||
|
MessageBox.Show(this, "This account hasn't been authenticated yet. First scan your library to log into your account, then try exporting again.", "Account Not Authenticated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveFileDialog sfd = new();
|
||||||
|
sfd.Filter = "JSON File|*.json";
|
||||||
|
|
||||||
|
string audibleAppDataDir = GetAudibleCliAppDataPath();
|
||||||
|
|
||||||
|
if (Directory.Exists(audibleAppDataDir))
|
||||||
|
sfd.InitialDirectory = audibleAppDataDir;
|
||||||
|
|
||||||
|
if (sfd.ShowDialog() != DialogResult.OK) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mkbAuth = Mkb79Auth.FromAccount(account);
|
||||||
|
var jsonText = mkbAuth.ToJson();
|
||||||
|
|
||||||
|
File.WriteAllText(sfd.FileName, jsonText);
|
||||||
|
|
||||||
|
MessageBox.Show(this, $"Successfully exported {account.AccountName} to\r\n\r\n{sfd.FileName}", "Success!");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBoxLib.ShowAdminAlert(
|
||||||
|
this,
|
||||||
|
$"An error occured while exporting account:\r\n{account.AccountName}",
|
||||||
|
"Error Exporting Account",
|
||||||
|
ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void importBtn_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
OpenFileDialog ofd = new();
|
||||||
|
ofd.Filter = "JSON File|*.json";
|
||||||
|
ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||||
|
|
||||||
|
string audibleAppDataDir = GetAudibleCliAppDataPath();
|
||||||
|
|
||||||
|
if (Directory.Exists(audibleAppDataDir))
|
||||||
|
ofd.InitialDirectory = audibleAppDataDir;
|
||||||
|
|
||||||
|
if (ofd.ShowDialog() != DialogResult.OK) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jsonText = File.ReadAllText(ofd.FileName);
|
||||||
|
var mkbAuth = Mkb79Auth.FromJson(jsonText);
|
||||||
|
var account = await mkbAuth.ToAccountAsync();
|
||||||
|
|
||||||
|
// without transaction, accounts persister will write ANY EDIT immediately to file
|
||||||
|
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||||
|
|
||||||
|
if (persister.AccountsSettings.Accounts.Any(a => a.AccountId == account.AccountId && a.IdentityTokens.Locale.Name == account.Locale.Name))
|
||||||
|
{
|
||||||
|
MessageBox.Show(this, $"An account with that account id and country already exists.\r\n\r\nAccount ID: {account.AccountId}\r\nCountry: {account.Locale.Name}", "Cannot Add Duplicate Account");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
persister.AccountsSettings.Add(account);
|
||||||
|
|
||||||
|
AddAccountToGrid(account);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBoxLib.ShowAdminAlert(
|
||||||
|
this,
|
||||||
|
$"An error occured while importing an account from:\r\n{ofd.FileName}\r\n\r\nIs the file encrypted?",
|
||||||
|
"Error Importing Account",
|
||||||
|
ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<root>
|
||||||
<root>
|
|
||||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
<xsd:element name="root" msdata:IsDataSet="true">
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
@ -58,10 +57,10 @@
|
|||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<metadata name="Original.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
<metadata name="DeleteAccount.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
<value>True</value>
|
<value>True</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
<metadata name="DeleteAccount.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
<metadata name="ExportAccount.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
<value>True</value>
|
<value>True</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
<metadata name="LibraryScan.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
<metadata name="LibraryScan.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
@ -70,10 +69,10 @@
|
|||||||
<metadata name="AccountId.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
<metadata name="AccountId.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
<value>True</value>
|
<value>True</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
<metadata name="AccountName.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
|
||||||
<value>True</value>
|
|
||||||
</metadata>
|
|
||||||
<metadata name="Locale.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
<metadata name="Locale.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
<value>True</value>
|
<value>True</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
<metadata name="AccountName.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
|
<value>True</value>
|
||||||
|
</metadata>
|
||||||
</root>
|
</root>
|
||||||
@ -1,189 +0,0 @@
|
|||||||
|
|
||||||
namespace LibationWinForms.Dialogs
|
|
||||||
{
|
|
||||||
partial class RemoveBooksDialog
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Required designer variable.
|
|
||||||
/// </summary>
|
|
||||||
private System.ComponentModel.IContainer components = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clean up any resources being used.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (disposing && (components != null))
|
|
||||||
{
|
|
||||||
components.Dispose();
|
|
||||||
}
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Windows Form Designer generated code
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Required method for Designer support - do not modify
|
|
||||||
/// the contents of this method with the code editor.
|
|
||||||
/// </summary>
|
|
||||||
private void InitializeComponent()
|
|
||||||
{
|
|
||||||
this.components = new System.ComponentModel.Container();
|
|
||||||
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
|
|
||||||
this._dataGridView = new System.Windows.Forms.DataGridView();
|
|
||||||
this.removeDataGridViewCheckBoxColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn();
|
|
||||||
this.coverDataGridViewImageColumn = new System.Windows.Forms.DataGridViewImageColumn();
|
|
||||||
this.titleDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
|
||||||
this.authorsDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
|
||||||
this.miscDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
|
||||||
this.purchaseDateGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
|
||||||
this.gridEntryBindingSource = new LibationWinForms.GridView.SyncBindingSource(this.components);
|
|
||||||
this.btnRemoveBooks = new System.Windows.Forms.Button();
|
|
||||||
this.label1 = new System.Windows.Forms.Label();
|
|
||||||
((System.ComponentModel.ISupportInitialize)(this._dataGridView)).BeginInit();
|
|
||||||
((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).BeginInit();
|
|
||||||
this.SuspendLayout();
|
|
||||||
//
|
|
||||||
// _dataGridView
|
|
||||||
//
|
|
||||||
this._dataGridView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
|
||||||
| System.Windows.Forms.AnchorStyles.Left)
|
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
|
||||||
this._dataGridView.AutoGenerateColumns = false;
|
|
||||||
this._dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
|
||||||
this._dataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
|
||||||
this.removeDataGridViewCheckBoxColumn,
|
|
||||||
this.coverDataGridViewImageColumn,
|
|
||||||
this.titleDataGridViewTextBoxColumn,
|
|
||||||
this.authorsDataGridViewTextBoxColumn,
|
|
||||||
this.miscDataGridViewTextBoxColumn,
|
|
||||||
this.purchaseDateGridViewTextBoxColumn});
|
|
||||||
this._dataGridView.DataSource = this.gridEntryBindingSource;
|
|
||||||
dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
|
|
||||||
dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Window;
|
|
||||||
dataGridViewCellStyle1.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
|
||||||
dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.ControlText;
|
|
||||||
dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight;
|
|
||||||
dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
|
|
||||||
dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
|
|
||||||
this._dataGridView.DefaultCellStyle = dataGridViewCellStyle1;
|
|
||||||
this._dataGridView.Location = new System.Drawing.Point(0, 0);
|
|
||||||
this._dataGridView.Name = "_dataGridView";
|
|
||||||
this._dataGridView.RowHeadersVisible = false;
|
|
||||||
this._dataGridView.RowTemplate.Height = 82;
|
|
||||||
this._dataGridView.Size = new System.Drawing.Size(730, 409);
|
|
||||||
this._dataGridView.TabIndex = 0;
|
|
||||||
//
|
|
||||||
// removeDataGridViewCheckBoxColumn
|
|
||||||
//
|
|
||||||
this.removeDataGridViewCheckBoxColumn.DataPropertyName = "Remove";
|
|
||||||
this.removeDataGridViewCheckBoxColumn.FalseValue = "False";
|
|
||||||
this.removeDataGridViewCheckBoxColumn.Frozen = true;
|
|
||||||
this.removeDataGridViewCheckBoxColumn.HeaderText = "Remove";
|
|
||||||
this.removeDataGridViewCheckBoxColumn.MinimumWidth = 80;
|
|
||||||
this.removeDataGridViewCheckBoxColumn.Name = "removeDataGridViewCheckBoxColumn";
|
|
||||||
this.removeDataGridViewCheckBoxColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
|
|
||||||
this.removeDataGridViewCheckBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
|
|
||||||
this.removeDataGridViewCheckBoxColumn.TrueValue = "True";
|
|
||||||
this.removeDataGridViewCheckBoxColumn.Width = 80;
|
|
||||||
//
|
|
||||||
// coverDataGridViewImageColumn
|
|
||||||
//
|
|
||||||
this.coverDataGridViewImageColumn.DataPropertyName = "Cover";
|
|
||||||
this.coverDataGridViewImageColumn.HeaderText = "Cover";
|
|
||||||
this.coverDataGridViewImageColumn.MinimumWidth = 80;
|
|
||||||
this.coverDataGridViewImageColumn.Name = "coverDataGridViewImageColumn";
|
|
||||||
this.coverDataGridViewImageColumn.ReadOnly = true;
|
|
||||||
this.coverDataGridViewImageColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
|
|
||||||
this.coverDataGridViewImageColumn.Width = 80;
|
|
||||||
//
|
|
||||||
// titleDataGridViewTextBoxColumn
|
|
||||||
//
|
|
||||||
this.titleDataGridViewTextBoxColumn.DataPropertyName = "Title";
|
|
||||||
this.titleDataGridViewTextBoxColumn.HeaderText = "Title";
|
|
||||||
this.titleDataGridViewTextBoxColumn.Name = "titleDataGridViewTextBoxColumn";
|
|
||||||
this.titleDataGridViewTextBoxColumn.ReadOnly = true;
|
|
||||||
this.titleDataGridViewTextBoxColumn.Width = 200;
|
|
||||||
//
|
|
||||||
// authorsDataGridViewTextBoxColumn
|
|
||||||
//
|
|
||||||
this.authorsDataGridViewTextBoxColumn.DataPropertyName = "Authors";
|
|
||||||
this.authorsDataGridViewTextBoxColumn.HeaderText = "Authors";
|
|
||||||
this.authorsDataGridViewTextBoxColumn.Name = "authorsDataGridViewTextBoxColumn";
|
|
||||||
this.authorsDataGridViewTextBoxColumn.ReadOnly = true;
|
|
||||||
//
|
|
||||||
// miscDataGridViewTextBoxColumn
|
|
||||||
//
|
|
||||||
this.miscDataGridViewTextBoxColumn.DataPropertyName = "Misc";
|
|
||||||
this.miscDataGridViewTextBoxColumn.HeaderText = "Misc";
|
|
||||||
this.miscDataGridViewTextBoxColumn.Name = "miscDataGridViewTextBoxColumn";
|
|
||||||
this.miscDataGridViewTextBoxColumn.ReadOnly = true;
|
|
||||||
this.miscDataGridViewTextBoxColumn.Width = 150;
|
|
||||||
//
|
|
||||||
// purchaseDateGridViewTextBoxColumn
|
|
||||||
//
|
|
||||||
this.purchaseDateGridViewTextBoxColumn.DataPropertyName = "PurchaseDate";
|
|
||||||
this.purchaseDateGridViewTextBoxColumn.HeaderText = "Purchase Date";
|
|
||||||
this.purchaseDateGridViewTextBoxColumn.Name = "purchaseDateGridViewTextBoxColumn";
|
|
||||||
this.purchaseDateGridViewTextBoxColumn.ReadOnly = true;
|
|
||||||
this.purchaseDateGridViewTextBoxColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
|
|
||||||
//
|
|
||||||
// gridEntryBindingSource
|
|
||||||
//
|
|
||||||
this.gridEntryBindingSource.AllowNew = false;
|
|
||||||
this.gridEntryBindingSource.DataSource = typeof(LibationWinForms.Dialogs.RemovableGridEntry);
|
|
||||||
//
|
|
||||||
// btnRemoveBooks
|
|
||||||
//
|
|
||||||
this.btnRemoveBooks.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
|
||||||
this.btnRemoveBooks.Location = new System.Drawing.Point(500, 419);
|
|
||||||
this.btnRemoveBooks.Name = "btnRemoveBooks";
|
|
||||||
this.btnRemoveBooks.Size = new System.Drawing.Size(218, 23);
|
|
||||||
this.btnRemoveBooks.TabIndex = 1;
|
|
||||||
this.btnRemoveBooks.Text = "Remove Selected Books from Libation";
|
|
||||||
this.btnRemoveBooks.UseVisualStyleBackColor = true;
|
|
||||||
this.btnRemoveBooks.Click += new System.EventHandler(this.btnRemoveBooks_Click);
|
|
||||||
//
|
|
||||||
// label1
|
|
||||||
//
|
|
||||||
this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
|
||||||
this.label1.AutoSize = true;
|
|
||||||
this.label1.Location = new System.Drawing.Point(12, 423);
|
|
||||||
this.label1.Name = "label1";
|
|
||||||
this.label1.Size = new System.Drawing.Size(178, 15);
|
|
||||||
this.label1.TabIndex = 2;
|
|
||||||
this.label1.Text = "{0} book{1} selected for removal.";
|
|
||||||
//
|
|
||||||
// RemoveBooksDialog
|
|
||||||
//
|
|
||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
|
||||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
|
||||||
this.ClientSize = new System.Drawing.Size(730, 450);
|
|
||||||
this.Controls.Add(this.label1);
|
|
||||||
this.Controls.Add(this.btnRemoveBooks);
|
|
||||||
this.Controls.Add(this._dataGridView);
|
|
||||||
this.Name = "RemoveBooksDialog";
|
|
||||||
this.Text = "Remove Books from Libation's Database";
|
|
||||||
this.Shown += new System.EventHandler(this.RemoveBooksDialog_Shown);
|
|
||||||
((System.ComponentModel.ISupportInitialize)(this._dataGridView)).EndInit();
|
|
||||||
((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).EndInit();
|
|
||||||
this.ResumeLayout(false);
|
|
||||||
this.PerformLayout();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private System.Windows.Forms.DataGridView _dataGridView;
|
|
||||||
private LibationWinForms.GridView.SyncBindingSource gridEntryBindingSource;
|
|
||||||
private System.Windows.Forms.Button btnRemoveBooks;
|
|
||||||
private System.Windows.Forms.Label label1;
|
|
||||||
private System.Windows.Forms.DataGridViewCheckBoxColumn removeDataGridViewCheckBoxColumn;
|
|
||||||
private System.Windows.Forms.DataGridViewImageColumn coverDataGridViewImageColumn;
|
|
||||||
private System.Windows.Forms.DataGridViewTextBoxColumn titleDataGridViewTextBoxColumn;
|
|
||||||
private System.Windows.Forms.DataGridViewTextBoxColumn authorsDataGridViewTextBoxColumn;
|
|
||||||
private System.Windows.Forms.DataGridViewTextBoxColumn miscDataGridViewTextBoxColumn;
|
|
||||||
private System.Windows.Forms.DataGridViewTextBoxColumn purchaseDateGridViewTextBoxColumn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,149 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Data;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using ApplicationServices;
|
|
||||||
using AudibleUtilities;
|
|
||||||
using DataLayer;
|
|
||||||
using Dinah.Core.DataBinding;
|
|
||||||
using LibationFileManager;
|
|
||||||
using LibationWinForms.Login;
|
|
||||||
|
|
||||||
namespace LibationWinForms.Dialogs
|
|
||||||
{
|
|
||||||
public partial class RemoveBooksDialog : Form
|
|
||||||
{
|
|
||||||
private Account[] _accounts { get; }
|
|
||||||
private List<LibraryBook> _libraryBooks { get; }
|
|
||||||
private SortableBindingList<RemovableGridEntry> _removableGridEntries { get; }
|
|
||||||
private string _labelFormat { get; }
|
|
||||||
private int SelectedCount => SelectedEntries?.Count() ?? 0;
|
|
||||||
private IEnumerable<RemovableGridEntry> SelectedEntries => _removableGridEntries?.Where(b => b.Remove);
|
|
||||||
|
|
||||||
public RemoveBooksDialog(params Account[] accounts)
|
|
||||||
{
|
|
||||||
_libraryBooks = DbContexts.GetLibrary_Flat_NoTracking();
|
|
||||||
_accounts = accounts;
|
|
||||||
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
this.Load += (_, _) => this.RestoreSizeAndLocation(Configuration.Instance);
|
|
||||||
this.FormClosing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
|
||||||
|
|
||||||
_labelFormat = label1.Text;
|
|
||||||
|
|
||||||
_dataGridView.CellContentClick += (_, _) => _dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
|
|
||||||
_dataGridView.CellValueChanged += (_, _) => UpdateSelection();
|
|
||||||
_dataGridView.BindingContextChanged += _dataGridView_BindingContextChanged;
|
|
||||||
|
|
||||||
var orderedGridEntries = _libraryBooks
|
|
||||||
.Select(lb => new RemovableGridEntry(lb))
|
|
||||||
.OrderByDescending(ge => (DateTime)ge.GetMemberValue(nameof(ge.PurchaseDate)))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
_removableGridEntries = new SortableBindingList<RemovableGridEntry>(orderedGridEntries);
|
|
||||||
gridEntryBindingSource.DataSource = _removableGridEntries;
|
|
||||||
|
|
||||||
_dataGridView.Enabled = false;
|
|
||||||
this.SetLibationIcon();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _dataGridView_BindingContextChanged(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
_dataGridView.Sort(_dataGridView.Columns[0], ListSortDirection.Descending);
|
|
||||||
UpdateSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void RemoveBooksDialog_Shown(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (_accounts is null || _accounts.Length == 0)
|
|
||||||
return;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var removedBooks = await LibraryCommands.FindInactiveBooks(WinformLoginChoiceEager.ApiExtendedFunc, _libraryBooks, _accounts);
|
|
||||||
|
|
||||||
var removable = _removableGridEntries.Where(rge => removedBooks.Any(rb => rb.Book.AudibleProductId == rge.AudibleProductId)).ToList();
|
|
||||||
|
|
||||||
if (!removable.Any())
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var r in removable)
|
|
||||||
r.Remove = true;
|
|
||||||
|
|
||||||
UpdateSelection();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
MessageBoxLib.ShowAdminAlert(
|
|
||||||
this,
|
|
||||||
"Error scanning library. You may still manually select books to remove from Libation's library.",
|
|
||||||
"Error scanning library",
|
|
||||||
ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_dataGridView.Enabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void btnRemoveBooks_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
var selectedBooks = SelectedEntries.ToList();
|
|
||||||
|
|
||||||
if (selectedBooks.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var libraryBooks = selectedBooks.Select(rge => rge.LibraryBook).ToList();
|
|
||||||
var result = MessageBoxLib.ShowConfirmationDialog(
|
|
||||||
libraryBooks,
|
|
||||||
$"Are you sure you want to remove {0} from Libation's library?",
|
|
||||||
"Remove books from Libation?");
|
|
||||||
|
|
||||||
if (result != DialogResult.Yes)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var idsToRemove = libraryBooks.Select(lb => lb.Book.AudibleProductId).ToList();
|
|
||||||
var removeLibraryBooks = await LibraryCommands.RemoveBooksAsync(idsToRemove);
|
|
||||||
|
|
||||||
foreach (var rEntry in selectedBooks)
|
|
||||||
_removableGridEntries.Remove(rEntry);
|
|
||||||
|
|
||||||
UpdateSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateSelection()
|
|
||||||
{
|
|
||||||
var selectedCount = SelectedCount;
|
|
||||||
label1.Text = string.Format(_labelFormat, selectedCount, selectedCount != 1 ? "s" : string.Empty);
|
|
||||||
btnRemoveBooks.Enabled = selectedCount > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class RemovableGridEntry : GridView.LibraryBookEntry
|
|
||||||
{
|
|
||||||
private bool _remove = false;
|
|
||||||
public RemovableGridEntry(LibraryBook libraryBook) : base(libraryBook) { }
|
|
||||||
|
|
||||||
public bool Remove
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _remove;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_remove = value;
|
|
||||||
NotifyPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override object GetMemberValue(string memberName)
|
|
||||||
{
|
|
||||||
if (memberName == nameof(Remove))
|
|
||||||
return Remove;
|
|
||||||
return base.GetMemberValue(memberName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
<root>
|
|
||||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
|
||||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
|
||||||
<xsd:element name="root" msdata:IsDataSet="true">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:choice maxOccurs="unbounded">
|
|
||||||
<xsd:element name="metadata">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
|
||||||
<xsd:attribute name="type" type="xsd:string" />
|
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
|
||||||
<xsd:attribute ref="xml:space" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="assembly">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:attribute name="alias" type="xsd:string" />
|
|
||||||
<xsd:attribute name="name" type="xsd:string" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="data">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
|
||||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
|
||||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
|
||||||
<xsd:attribute ref="xml:space" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="resheader">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
</xsd:schema>
|
|
||||||
<resheader name="resmimetype">
|
|
||||||
<value>text/microsoft-resx</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="version">
|
|
||||||
<value>2.0</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="reader">
|
|
||||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="writer">
|
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</resheader>
|
|
||||||
<metadata name="gridEntryBindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
|
||||||
<value>17, 17</value>
|
|
||||||
</metadata>
|
|
||||||
</root>
|
|
||||||
57
Source/LibationWinForms/Form1.Designer.cs
generated
@ -74,6 +74,8 @@
|
|||||||
this.panel1 = new System.Windows.Forms.Panel();
|
this.panel1 = new System.Windows.Forms.Panel();
|
||||||
this.productsDisplay = new LibationWinForms.GridView.ProductsDisplay();
|
this.productsDisplay = new LibationWinForms.GridView.ProductsDisplay();
|
||||||
this.toggleQueueHideBtn = new System.Windows.Forms.Button();
|
this.toggleQueueHideBtn = new System.Windows.Forms.Button();
|
||||||
|
this.doneRemovingBtn = new System.Windows.Forms.Button();
|
||||||
|
this.removeBooksBtn = new System.Windows.Forms.Button();
|
||||||
this.processBookQueue1 = new LibationWinForms.ProcessQueue.ProcessQueueControl();
|
this.processBookQueue1 = new LibationWinForms.ProcessQueue.ProcessQueueControl();
|
||||||
this.menuStrip1.SuspendLayout();
|
this.menuStrip1.SuspendLayout();
|
||||||
this.statusStrip1.SuspendLayout();
|
this.statusStrip1.SuspendLayout();
|
||||||
@ -98,7 +100,7 @@
|
|||||||
// filterBtn
|
// filterBtn
|
||||||
//
|
//
|
||||||
this.filterBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
this.filterBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.filterBtn.Location = new System.Drawing.Point(916, 3);
|
this.filterBtn.Location = new System.Drawing.Point(892, 3);
|
||||||
this.filterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
this.filterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.filterBtn.Name = "filterBtn";
|
this.filterBtn.Name = "filterBtn";
|
||||||
this.filterBtn.Size = new System.Drawing.Size(88, 27);
|
this.filterBtn.Size = new System.Drawing.Size(88, 27);
|
||||||
@ -111,10 +113,11 @@
|
|||||||
//
|
//
|
||||||
this.filterSearchTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
this.filterSearchTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.filterSearchTb.Location = new System.Drawing.Point(196, 7);
|
this.filterSearchTb.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||||
|
this.filterSearchTb.Location = new System.Drawing.Point(195, 5);
|
||||||
this.filterSearchTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
this.filterSearchTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.filterSearchTb.Name = "filterSearchTb";
|
this.filterSearchTb.Name = "filterSearchTb";
|
||||||
this.filterSearchTb.Size = new System.Drawing.Size(712, 23);
|
this.filterSearchTb.Size = new System.Drawing.Size(689, 25);
|
||||||
this.filterSearchTb.TabIndex = 1;
|
this.filterSearchTb.TabIndex = 1;
|
||||||
this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress);
|
this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress);
|
||||||
//
|
//
|
||||||
@ -132,7 +135,7 @@
|
|||||||
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
|
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
|
||||||
this.menuStrip1.Name = "menuStrip1";
|
this.menuStrip1.Name = "menuStrip1";
|
||||||
this.menuStrip1.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2);
|
this.menuStrip1.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2);
|
||||||
this.menuStrip1.Size = new System.Drawing.Size(1061, 24);
|
this.menuStrip1.Size = new System.Drawing.Size(1037, 24);
|
||||||
this.menuStrip1.TabIndex = 0;
|
this.menuStrip1.TabIndex = 0;
|
||||||
this.menuStrip1.Text = "menuStrip1";
|
this.menuStrip1.Text = "menuStrip1";
|
||||||
//
|
//
|
||||||
@ -396,7 +399,8 @@
|
|||||||
this.statusStrip1.Location = new System.Drawing.Point(0, 618);
|
this.statusStrip1.Location = new System.Drawing.Point(0, 618);
|
||||||
this.statusStrip1.Name = "statusStrip1";
|
this.statusStrip1.Name = "statusStrip1";
|
||||||
this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0);
|
this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0);
|
||||||
this.statusStrip1.Size = new System.Drawing.Size(1061, 22);
|
this.statusStrip1.ShowItemToolTips = true;
|
||||||
|
this.statusStrip1.Size = new System.Drawing.Size(1037, 22);
|
||||||
this.statusStrip1.TabIndex = 6;
|
this.statusStrip1.TabIndex = 6;
|
||||||
this.statusStrip1.Text = "statusStrip1";
|
this.statusStrip1.Text = "statusStrip1";
|
||||||
//
|
//
|
||||||
@ -410,7 +414,7 @@
|
|||||||
// springLbl
|
// springLbl
|
||||||
//
|
//
|
||||||
this.springLbl.Name = "springLbl";
|
this.springLbl.Name = "springLbl";
|
||||||
this.springLbl.Size = new System.Drawing.Size(547, 17);
|
this.springLbl.Size = new System.Drawing.Size(523, 17);
|
||||||
this.springLbl.Spring = true;
|
this.springLbl.Spring = true;
|
||||||
//
|
//
|
||||||
// backupsCountsLbl
|
// backupsCountsLbl
|
||||||
@ -440,6 +444,7 @@
|
|||||||
// splitContainer1
|
// splitContainer1
|
||||||
//
|
//
|
||||||
this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
|
this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
|
this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2;
|
||||||
this.splitContainer1.Location = new System.Drawing.Point(0, 0);
|
this.splitContainer1.Location = new System.Drawing.Point(0, 0);
|
||||||
this.splitContainer1.Name = "splitContainer1";
|
this.splitContainer1.Name = "splitContainer1";
|
||||||
//
|
//
|
||||||
@ -453,7 +458,7 @@
|
|||||||
//
|
//
|
||||||
this.splitContainer1.Panel2.Controls.Add(this.processBookQueue1);
|
this.splitContainer1.Panel2.Controls.Add(this.processBookQueue1);
|
||||||
this.splitContainer1.Size = new System.Drawing.Size(1463, 640);
|
this.splitContainer1.Size = new System.Drawing.Size(1463, 640);
|
||||||
this.splitContainer1.SplitterDistance = 1061;
|
this.splitContainer1.SplitterDistance = 1037;
|
||||||
this.splitContainer1.SplitterWidth = 8;
|
this.splitContainer1.SplitterWidth = 8;
|
||||||
this.splitContainer1.TabIndex = 7;
|
this.splitContainer1.TabIndex = 7;
|
||||||
//
|
//
|
||||||
@ -462,6 +467,8 @@
|
|||||||
this.panel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
|
this.panel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
|
||||||
this.panel1.Controls.Add(this.productsDisplay);
|
this.panel1.Controls.Add(this.productsDisplay);
|
||||||
this.panel1.Controls.Add(this.toggleQueueHideBtn);
|
this.panel1.Controls.Add(this.toggleQueueHideBtn);
|
||||||
|
this.panel1.Controls.Add(this.doneRemovingBtn);
|
||||||
|
this.panel1.Controls.Add(this.removeBooksBtn);
|
||||||
this.panel1.Controls.Add(this.addQuickFilterBtn);
|
this.panel1.Controls.Add(this.addQuickFilterBtn);
|
||||||
this.panel1.Controls.Add(this.filterHelpBtn);
|
this.panel1.Controls.Add(this.filterHelpBtn);
|
||||||
this.panel1.Controls.Add(this.filterSearchTb);
|
this.panel1.Controls.Add(this.filterSearchTb);
|
||||||
@ -470,7 +477,7 @@
|
|||||||
this.panel1.Location = new System.Drawing.Point(0, 24);
|
this.panel1.Location = new System.Drawing.Point(0, 24);
|
||||||
this.panel1.Margin = new System.Windows.Forms.Padding(0);
|
this.panel1.Margin = new System.Windows.Forms.Padding(0);
|
||||||
this.panel1.Name = "panel1";
|
this.panel1.Name = "panel1";
|
||||||
this.panel1.Size = new System.Drawing.Size(1061, 594);
|
this.panel1.Size = new System.Drawing.Size(1037, 594);
|
||||||
this.panel1.TabIndex = 7;
|
this.panel1.TabIndex = 7;
|
||||||
//
|
//
|
||||||
// productsDisplay
|
// productsDisplay
|
||||||
@ -482,16 +489,17 @@
|
|||||||
this.productsDisplay.Location = new System.Drawing.Point(15, 36);
|
this.productsDisplay.Location = new System.Drawing.Point(15, 36);
|
||||||
this.productsDisplay.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
this.productsDisplay.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.productsDisplay.Name = "productsDisplay";
|
this.productsDisplay.Name = "productsDisplay";
|
||||||
this.productsDisplay.Size = new System.Drawing.Size(1031, 555);
|
this.productsDisplay.Size = new System.Drawing.Size(1007, 555);
|
||||||
this.productsDisplay.TabIndex = 9;
|
this.productsDisplay.TabIndex = 9;
|
||||||
this.productsDisplay.VisibleCountChanged += new System.EventHandler<int>(this.productsDisplay_VisibleCountChanged);
|
this.productsDisplay.VisibleCountChanged += new System.EventHandler<int>(this.productsDisplay_VisibleCountChanged);
|
||||||
|
this.productsDisplay.RemovableCountChanged += new System.EventHandler<int>(this.productsDisplay_RemovableCountChanged);
|
||||||
this.productsDisplay.LiberateClicked += new System.EventHandler<DataLayer.LibraryBook>(this.ProductsDisplay_LiberateClicked);
|
this.productsDisplay.LiberateClicked += new System.EventHandler<DataLayer.LibraryBook>(this.ProductsDisplay_LiberateClicked);
|
||||||
this.productsDisplay.InitialLoaded += new System.EventHandler(this.productsDisplay_InitialLoaded);
|
this.productsDisplay.InitialLoaded += new System.EventHandler(this.productsDisplay_InitialLoaded);
|
||||||
//
|
//
|
||||||
// toggleQueueHideBtn
|
// toggleQueueHideBtn
|
||||||
//
|
//
|
||||||
this.toggleQueueHideBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
this.toggleQueueHideBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.toggleQueueHideBtn.Location = new System.Drawing.Point(1013, 3);
|
this.toggleQueueHideBtn.Location = new System.Drawing.Point(989, 3);
|
||||||
this.toggleQueueHideBtn.Margin = new System.Windows.Forms.Padding(4, 3, 15, 3);
|
this.toggleQueueHideBtn.Margin = new System.Windows.Forms.Padding(4, 3, 15, 3);
|
||||||
this.toggleQueueHideBtn.Name = "toggleQueueHideBtn";
|
this.toggleQueueHideBtn.Name = "toggleQueueHideBtn";
|
||||||
this.toggleQueueHideBtn.Size = new System.Drawing.Size(33, 27);
|
this.toggleQueueHideBtn.Size = new System.Drawing.Size(33, 27);
|
||||||
@ -500,6 +508,31 @@
|
|||||||
this.toggleQueueHideBtn.UseVisualStyleBackColor = true;
|
this.toggleQueueHideBtn.UseVisualStyleBackColor = true;
|
||||||
this.toggleQueueHideBtn.Click += new System.EventHandler(this.ToggleQueueHideBtn_Click);
|
this.toggleQueueHideBtn.Click += new System.EventHandler(this.ToggleQueueHideBtn_Click);
|
||||||
//
|
//
|
||||||
|
// doneRemovingBtn
|
||||||
|
//
|
||||||
|
this.doneRemovingBtn.Location = new System.Drawing.Point(406, 3);
|
||||||
|
this.doneRemovingBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
|
this.doneRemovingBtn.Name = "doneRemovingBtn";
|
||||||
|
this.doneRemovingBtn.Size = new System.Drawing.Size(145, 27);
|
||||||
|
this.doneRemovingBtn.TabIndex = 4;
|
||||||
|
this.doneRemovingBtn.Text = "Done Removing Books";
|
||||||
|
this.doneRemovingBtn.UseVisualStyleBackColor = true;
|
||||||
|
this.doneRemovingBtn.Visible = false;
|
||||||
|
this.doneRemovingBtn.Click += new System.EventHandler(this.doneRemovingBtn_Click);
|
||||||
|
//
|
||||||
|
// removeBooksBtn
|
||||||
|
//
|
||||||
|
this.removeBooksBtn.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
|
||||||
|
this.removeBooksBtn.Location = new System.Drawing.Point(206, 3);
|
||||||
|
this.removeBooksBtn.Margin = new System.Windows.Forms.Padding(15, 3, 4, 3);
|
||||||
|
this.removeBooksBtn.Name = "removeBooksBtn";
|
||||||
|
this.removeBooksBtn.Size = new System.Drawing.Size(192, 27);
|
||||||
|
this.removeBooksBtn.TabIndex = 4;
|
||||||
|
this.removeBooksBtn.Text = "Remove # Books from Libation";
|
||||||
|
this.removeBooksBtn.UseVisualStyleBackColor = true;
|
||||||
|
this.removeBooksBtn.Visible = false;
|
||||||
|
this.removeBooksBtn.Click += new System.EventHandler(this.removeBooksBtn_Click);
|
||||||
|
//
|
||||||
// processBookQueue1
|
// processBookQueue1
|
||||||
//
|
//
|
||||||
this.processBookQueue1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
this.processBookQueue1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||||
@ -507,7 +540,7 @@
|
|||||||
this.processBookQueue1.Location = new System.Drawing.Point(0, 0);
|
this.processBookQueue1.Location = new System.Drawing.Point(0, 0);
|
||||||
this.processBookQueue1.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
this.processBookQueue1.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||||
this.processBookQueue1.Name = "processBookQueue1";
|
this.processBookQueue1.Name = "processBookQueue1";
|
||||||
this.processBookQueue1.Size = new System.Drawing.Size(394, 640);
|
this.processBookQueue1.Size = new System.Drawing.Size(418, 640);
|
||||||
this.processBookQueue1.TabIndex = 0;
|
this.processBookQueue1.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// Form1
|
// Form1
|
||||||
@ -584,5 +617,7 @@
|
|||||||
private System.Windows.Forms.Panel panel1;
|
private System.Windows.Forms.Panel panel1;
|
||||||
private System.Windows.Forms.Button toggleQueueHideBtn;
|
private System.Windows.Forms.Button toggleQueueHideBtn;
|
||||||
private LibationWinForms.GridView.ProductsDisplay productsDisplay;
|
private LibationWinForms.GridView.ProductsDisplay productsDisplay;
|
||||||
|
private System.Windows.Forms.Button removeBooksBtn;
|
||||||
|
private System.Windows.Forms.Button doneRemovingBtn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,9 @@ namespace LibationWinForms
|
|||||||
processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut;
|
processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut;
|
||||||
var coppalseState = Configuration.Instance.GetNonString<bool>(nameof(splitContainer1.Panel2Collapsed));
|
var coppalseState = Configuration.Instance.GetNonString<bool>(nameof(splitContainer1.Panel2Collapsed));
|
||||||
WidthChange = splitContainer1.Panel2.Width + splitContainer1.SplitterWidth;
|
WidthChange = splitContainer1.Panel2.Width + splitContainer1.SplitterWidth;
|
||||||
|
int width = this.Width;
|
||||||
SetQueueCollapseState(coppalseState);
|
SetQueueCollapseState(coppalseState);
|
||||||
|
this.Width = width;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProductsDisplay_LiberateClicked(object sender, LibraryBook e)
|
private void ProductsDisplay_LiberateClicked(object sender, LibraryBook e)
|
||||||
|
|||||||
92
Source/LibationWinForms/Form1.RemoveBooks.cs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
using AudibleUtilities;
|
||||||
|
using LibationWinForms.Dialogs;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace LibationWinForms
|
||||||
|
{
|
||||||
|
public partial class Form1
|
||||||
|
{
|
||||||
|
public void Configure_RemoveBooks() { }
|
||||||
|
|
||||||
|
private async void removeBooksBtn_Click(object sender, EventArgs e)
|
||||||
|
=> await productsDisplay.RemoveCheckedBooksAsync();
|
||||||
|
|
||||||
|
private void doneRemovingBtn_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
removeBooksBtn.Visible = false;
|
||||||
|
doneRemovingBtn.Visible = false;
|
||||||
|
|
||||||
|
productsDisplay.CloseRemoveBooksColumn();
|
||||||
|
|
||||||
|
//Restore the filter
|
||||||
|
filterSearchTb.Enabled = true;
|
||||||
|
filterSearchTb.Visible = true;
|
||||||
|
performFilter(filterSearchTb.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeLibraryBooksToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// if 0 accounts, this will not be visible
|
||||||
|
// if 1 account, run scanLibrariesRemovedBooks() on this account
|
||||||
|
// if multiple accounts, another menu set will open. do not run scanLibrariesRemovedBooks()
|
||||||
|
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||||
|
var accounts = persister.AccountsSettings.GetAll();
|
||||||
|
|
||||||
|
if (accounts.Count != 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var firstAccount = accounts.Single();
|
||||||
|
scanLibrariesRemovedBooks(firstAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// selectively remove books from all accounts
|
||||||
|
private void removeAllAccountsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||||
|
var allAccounts = persister.AccountsSettings.GetAll();
|
||||||
|
scanLibrariesRemovedBooks(allAccounts.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
// selectively remove books from some accounts
|
||||||
|
private void removeSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
using var scanAccountsDialog = new ScanAccountsDialog();
|
||||||
|
|
||||||
|
if (scanAccountsDialog.ShowDialog() != DialogResult.OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!scanAccountsDialog.CheckedAccounts.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void scanLibrariesRemovedBooks(params Account[] accounts)
|
||||||
|
{
|
||||||
|
//This action is meant to operate on the entire library.
|
||||||
|
//For removing books within a filter set, use
|
||||||
|
//Visible Books > Remove from library
|
||||||
|
filterSearchTb.Enabled = false;
|
||||||
|
filterSearchTb.Visible = false;
|
||||||
|
productsDisplay.Filter(null);
|
||||||
|
|
||||||
|
removeBooksBtn.Visible = true;
|
||||||
|
doneRemovingBtn.Visible = true;
|
||||||
|
await productsDisplay.ScanAndRemoveBooksAsync(accounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void productsDisplay_RemovableCountChanged(object sender, int removeCount)
|
||||||
|
{
|
||||||
|
removeBooksBtn.Text = removeCount switch
|
||||||
|
{
|
||||||
|
1 => "Remove 1 Book from Libation",
|
||||||
|
_ => $"Remove {removeCount} Books from Libation"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -69,49 +69,6 @@ namespace LibationWinForms
|
|||||||
await scanLibrariesAsync(scanAccountsDialog.CheckedAccounts);
|
await scanLibrariesAsync(scanAccountsDialog.CheckedAccounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeLibraryBooksToolStripMenuItem_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
// if 0 accounts, this will not be visible
|
|
||||||
// if 1 account, run scanLibrariesRemovedBooks() on this account
|
|
||||||
// if multiple accounts, another menu set will open. do not run scanLibrariesRemovedBooks()
|
|
||||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
|
||||||
var accounts = persister.AccountsSettings.GetAll();
|
|
||||||
|
|
||||||
if (accounts.Count != 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var firstAccount = accounts.Single();
|
|
||||||
scanLibrariesRemovedBooks(firstAccount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// selectively remove books from all accounts
|
|
||||||
private void removeAllAccountsToolStripMenuItem_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
|
||||||
var allAccounts = persister.AccountsSettings.GetAll();
|
|
||||||
scanLibrariesRemovedBooks(allAccounts.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
// selectively remove books from some accounts
|
|
||||||
private void removeSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
using var scanAccountsDialog = new ScanAccountsDialog();
|
|
||||||
|
|
||||||
if (scanAccountsDialog.ShowDialog() != DialogResult.OK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!scanAccountsDialog.CheckedAccounts.Any())
|
|
||||||
return;
|
|
||||||
|
|
||||||
scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void scanLibrariesRemovedBooks(params Account[] accounts)
|
|
||||||
{
|
|
||||||
using var dialog = new RemoveBooksDialog(accounts);
|
|
||||||
dialog.ShowDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task scanLibrariesAsync(IEnumerable<Account> accounts) => await scanLibrariesAsync(accounts.ToArray());
|
private async Task scanLibrariesAsync(IEnumerable<Account> accounts) => await scanLibrariesAsync(accounts.ToArray());
|
||||||
private async Task scanLibrariesAsync(params Account[] accounts)
|
private async Task scanLibrariesAsync(params Account[] accounts)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -14,6 +14,9 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
private void LibraryCommands_ScanBegin(object sender, int accountsLength)
|
private void LibraryCommands_ScanBegin(object sender, int accountsLength)
|
||||||
{
|
{
|
||||||
|
removeLibraryBooksToolStripMenuItem.Enabled = false;
|
||||||
|
removeAllAccountsToolStripMenuItem.Enabled = false;
|
||||||
|
removeSomeAccountsToolStripMenuItem.Enabled = false;
|
||||||
scanLibraryToolStripMenuItem.Enabled = false;
|
scanLibraryToolStripMenuItem.Enabled = false;
|
||||||
scanLibraryOfAllAccountsToolStripMenuItem.Enabled = false;
|
scanLibraryOfAllAccountsToolStripMenuItem.Enabled = false;
|
||||||
scanLibraryOfSomeAccountsToolStripMenuItem.Enabled = false;
|
scanLibraryOfSomeAccountsToolStripMenuItem.Enabled = false;
|
||||||
@ -27,6 +30,9 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
private void LibraryCommands_ScanEnd(object sender, EventArgs e)
|
private void LibraryCommands_ScanEnd(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
removeLibraryBooksToolStripMenuItem.Enabled = true;
|
||||||
|
removeAllAccountsToolStripMenuItem.Enabled = true;
|
||||||
|
removeSomeAccountsToolStripMenuItem.Enabled = true;
|
||||||
scanLibraryToolStripMenuItem.Enabled = true;
|
scanLibraryToolStripMenuItem.Enabled = true;
|
||||||
scanLibraryOfAllAccountsToolStripMenuItem.Enabled = true;
|
scanLibraryOfAllAccountsToolStripMenuItem.Enabled = true;
|
||||||
scanLibraryOfSomeAccountsToolStripMenuItem.Enabled = true;
|
scanLibraryOfSomeAccountsToolStripMenuItem.Enabled = true;
|
||||||
|
|||||||
@ -20,7 +20,7 @@ namespace LibationWinForms
|
|||||||
// Before calling anything else, including subscribing to events, ensure database exists. If we wait and let it happen lazily, race conditions and errors are likely during new installs
|
// Before calling anything else, including subscribing to events, ensure database exists. If we wait and let it happen lazily, race conditions and errors are likely during new installs
|
||||||
using var _ = DbContexts.GetContext();
|
using var _ = DbContexts.GetContext();
|
||||||
|
|
||||||
this.Load += (_, _) => this.RestoreSizeAndLocation(Configuration.Instance);
|
this.RestoreSizeAndLocation(Configuration.Instance);
|
||||||
this.FormClosing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
this.FormClosing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||||
|
|
||||||
// this looks like a perfect opportunity to refactor per below.
|
// this looks like a perfect opportunity to refactor per below.
|
||||||
@ -44,6 +44,7 @@ namespace LibationWinForms
|
|||||||
Configure_VisibleBooks();
|
Configure_VisibleBooks();
|
||||||
Configure_QuickFilters();
|
Configure_QuickFilters();
|
||||||
Configure_ScanManual();
|
Configure_ScanManual();
|
||||||
|
Configure_RemoveBooks();
|
||||||
Configure_Liberate();
|
Configure_Liberate();
|
||||||
Configure_Export();
|
Configure_Export();
|
||||||
Configure_Settings();
|
Configure_Settings();
|
||||||
|
|||||||
@ -93,6 +93,12 @@
|
|||||||
<metadata name="toggleQueueHideBtn.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
<metadata name="toggleQueueHideBtn.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
<value>True</value>
|
<value>True</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
<metadata name="doneRemovingBtn.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
|
<value>True</value>
|
||||||
|
</metadata>
|
||||||
|
<metadata name="removeBooksBtn.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
|
<value>True</value>
|
||||||
|
</metadata>
|
||||||
<metadata name="processBookQueue1.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
<metadata name="processBookQueue1.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
<value>True</value>
|
<value>True</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
|||||||
@ -12,6 +12,12 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace LibationWinForms.GridView
|
namespace LibationWinForms.GridView
|
||||||
{
|
{
|
||||||
|
public enum RemoveStatus
|
||||||
|
{
|
||||||
|
NotRemoved,
|
||||||
|
Removed,
|
||||||
|
SomeRemoved
|
||||||
|
}
|
||||||
/// <summary>The View Model base for the DataGridView</summary>
|
/// <summary>The View Model base for the DataGridView</summary>
|
||||||
public abstract class GridEntry : AsyncNotifyPropertyChanged, IMemberComparable
|
public abstract class GridEntry : AsyncNotifyPropertyChanged, IMemberComparable
|
||||||
{
|
{
|
||||||
@ -24,6 +30,9 @@ namespace LibationWinForms.GridView
|
|||||||
|
|
||||||
#region Model properties exposed to the view
|
#region Model properties exposed to the view
|
||||||
|
|
||||||
|
protected RemoveStatus _remove = RemoveStatus.NotRemoved;
|
||||||
|
public abstract RemoveStatus Remove { get; set; }
|
||||||
|
|
||||||
public abstract LiberateButtonStatus Liberate { get; }
|
public abstract LiberateButtonStatus Liberate { get; }
|
||||||
public Image Cover
|
public Image Cover
|
||||||
{
|
{
|
||||||
|
|||||||
@ -19,18 +19,19 @@ namespace LibationWinForms.GridView
|
|||||||
private static readonly Color SERIES_BG_COLOR = Color.FromArgb(230, 255, 230);
|
private static readonly Color SERIES_BG_COLOR = Color.FromArgb(230, 255, 230);
|
||||||
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
|
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
|
||||||
{
|
{
|
||||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts);
|
|
||||||
|
|
||||||
if (value is LiberateButtonStatus status)
|
if (value is LiberateButtonStatus status)
|
||||||
{
|
{
|
||||||
|
if (status.BookStatus is LiberatedStatus.Error)
|
||||||
|
paintParts ^= DataGridViewPaintParts.ContentBackground | DataGridViewPaintParts.ContentForeground | DataGridViewPaintParts.SelectionBackground;
|
||||||
|
|
||||||
if (rowIndex >= 0 && DataGridView.GetBoundItem<GridEntry>(rowIndex) is LibraryBookEntry lbEntry && lbEntry.Parent is not null)
|
if (rowIndex >= 0 && DataGridView.GetBoundItem<GridEntry>(rowIndex) is LibraryBookEntry lbEntry && lbEntry.Parent is not null)
|
||||||
{
|
|
||||||
DataGridView.Rows[rowIndex].DefaultCellStyle.BackColor = SERIES_BG_COLOR;
|
DataGridView.Rows[rowIndex].DefaultCellStyle.BackColor = SERIES_BG_COLOR;
|
||||||
}
|
|
||||||
|
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts);
|
||||||
|
|
||||||
if (status.IsSeries)
|
if (status.IsSeries)
|
||||||
{
|
{
|
||||||
DrawButtonImage(graphics, status.Expanded ? Properties.Resources.minus: Properties.Resources.plus, cellBounds);
|
DrawButtonImage(graphics, status.Expanded ? Properties.Resources.minus : Properties.Resources.plus, cellBounds);
|
||||||
|
|
||||||
ToolTipText = status.Expanded ? "Click to Collpase" : "Click to Expand";
|
ToolTipText = status.Expanded ? "Click to Collpase" : "Click to Expand";
|
||||||
}
|
}
|
||||||
@ -48,7 +49,7 @@ namespace LibationWinForms.GridView
|
|||||||
private static (string mouseoverText, Bitmap buttonImage) GetLiberateDisplay(LiberatedStatus liberatedStatus, LiberatedStatus? pdfStatus)
|
private static (string mouseoverText, Bitmap buttonImage) GetLiberateDisplay(LiberatedStatus liberatedStatus, LiberatedStatus? pdfStatus)
|
||||||
{
|
{
|
||||||
if (liberatedStatus == LiberatedStatus.Error)
|
if (liberatedStatus == LiberatedStatus.Error)
|
||||||
return ("Book downloaded ERROR", SystemIcons.Error.ToBitmap());
|
return ("Book downloaded ERROR", Properties.Resources.error);
|
||||||
|
|
||||||
(string libState, string image_lib) = liberatedStatus switch
|
(string libState, string image_lib) = liberatedStatus switch
|
||||||
{
|
{
|
||||||
|
|||||||
@ -20,6 +20,20 @@ namespace LibationWinForms.GridView
|
|||||||
private LiberatedStatus _bookStatus;
|
private LiberatedStatus _bookStatus;
|
||||||
private LiberatedStatus? _pdfStatus;
|
private LiberatedStatus? _pdfStatus;
|
||||||
|
|
||||||
|
public override RemoveStatus Remove
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _remove;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_remove = value is RemoveStatus.SomeRemoved ? RemoveStatus.NotRemoved : value;
|
||||||
|
Parent?.ChildRemoveUpdate();
|
||||||
|
NotifyPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override LiberateButtonStatus Liberate
|
public override LiberateButtonStatus Liberate
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@ -39,11 +39,12 @@
|
|||||||
this.productsGrid.Name = "productsGrid";
|
this.productsGrid.Name = "productsGrid";
|
||||||
this.productsGrid.Size = new System.Drawing.Size(1510, 380);
|
this.productsGrid.Size = new System.Drawing.Size(1510, 380);
|
||||||
this.productsGrid.TabIndex = 0;
|
this.productsGrid.TabIndex = 0;
|
||||||
|
this.productsGrid.VisibleCountChanged += new System.EventHandler<int>(this.productsGrid_VisibleCountChanged);
|
||||||
this.productsGrid.LiberateClicked += new LibationWinForms.GridView.LibraryBookEntryClickedEventHandler(this.productsGrid_LiberateClicked);
|
this.productsGrid.LiberateClicked += new LibationWinForms.GridView.LibraryBookEntryClickedEventHandler(this.productsGrid_LiberateClicked);
|
||||||
this.productsGrid.CoverClicked += new LibationWinForms.GridView.GridEntryClickedEventHandler(this.productsGrid_CoverClicked);
|
this.productsGrid.CoverClicked += new LibationWinForms.GridView.GridEntryClickedEventHandler(this.productsGrid_CoverClicked);
|
||||||
this.productsGrid.DetailsClicked += new LibationWinForms.GridView.LibraryBookEntryClickedEventHandler(this.productsGrid_DetailsClicked);
|
this.productsGrid.DetailsClicked += new LibationWinForms.GridView.LibraryBookEntryClickedEventHandler(this.productsGrid_DetailsClicked);
|
||||||
this.productsGrid.DescriptionClicked += new LibationWinForms.GridView.GridEntryRectangleClickedEventHandler(this.productsGrid_DescriptionClicked);
|
this.productsGrid.DescriptionClicked += new LibationWinForms.GridView.GridEntryRectangleClickedEventHandler(this.productsGrid_DescriptionClicked);
|
||||||
this.productsGrid.VisibleCountChanged += new System.EventHandler<int>(this.productsGrid_VisibleCountChanged);
|
this.productsGrid.RemovableCountChanged += new System.EventHandler(this.productsGrid_RemovableCountChanged);
|
||||||
//
|
//
|
||||||
// ProductsDisplay
|
// ProductsDisplay
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using ApplicationServices;
|
using ApplicationServices;
|
||||||
|
using AudibleUtilities;
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
using FileLiberator;
|
using FileLiberator;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
@ -16,6 +17,7 @@ namespace LibationWinForms.GridView
|
|||||||
{
|
{
|
||||||
/// <summary>Number of visible rows has changed</summary>
|
/// <summary>Number of visible rows has changed</summary>
|
||||||
public event EventHandler<int> VisibleCountChanged;
|
public event EventHandler<int> VisibleCountChanged;
|
||||||
|
public event EventHandler<int> RemovableCountChanged;
|
||||||
public event EventHandler<LibraryBook> LiberateClicked;
|
public event EventHandler<LibraryBook> LiberateClicked;
|
||||||
public event EventHandler InitialLoaded;
|
public event EventHandler InitialLoaded;
|
||||||
|
|
||||||
@ -80,6 +82,68 @@ namespace LibationWinForms.GridView
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Scan and Remove Books
|
||||||
|
|
||||||
|
public void CloseRemoveBooksColumn()
|
||||||
|
=> productsGrid.RemoveColumnVisible = false;
|
||||||
|
|
||||||
|
public async Task RemoveCheckedBooksAsync()
|
||||||
|
{
|
||||||
|
var selectedBooks = productsGrid.GetAllBookEntries().Where(lbe => lbe.Remove is RemoveStatus.Removed).ToList();
|
||||||
|
|
||||||
|
if (selectedBooks.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var libraryBooks = selectedBooks.Select(rge => rge.LibraryBook).ToList();
|
||||||
|
var result = MessageBoxLib.ShowConfirmationDialog(
|
||||||
|
libraryBooks,
|
||||||
|
$"Are you sure you want to remove {selectedBooks.Count} books from Libation's library?",
|
||||||
|
"Remove books from Libation?");
|
||||||
|
|
||||||
|
if (result != DialogResult.Yes)
|
||||||
|
return;
|
||||||
|
|
||||||
|
productsGrid.RemoveBooks(selectedBooks);
|
||||||
|
var idsToRemove = libraryBooks.Select(lb => lb.Book.AudibleProductId).ToList();
|
||||||
|
var removeLibraryBooks = await LibraryCommands.RemoveBooksAsync(idsToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ScanAndRemoveBooksAsync(params Account[] accounts)
|
||||||
|
{
|
||||||
|
RemovableCountChanged?.Invoke(this, 0);
|
||||||
|
productsGrid.RemoveColumnVisible = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (accounts is null || accounts.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var allBooks = productsGrid.GetAllBookEntries();
|
||||||
|
var lib = allBooks
|
||||||
|
.Select(lbe => lbe.LibraryBook)
|
||||||
|
.Where(lb => !lb.Book.HasLiberated());
|
||||||
|
|
||||||
|
var removedBooks = await LibraryCommands.FindInactiveBooks(Login.WinformLoginChoiceEager.ApiExtendedFunc, lib, accounts);
|
||||||
|
|
||||||
|
var removable = allBooks.Where(lbe => removedBooks.Any(rb => rb.Book.AudibleProductId == lbe.AudibleProductId)).ToList();
|
||||||
|
|
||||||
|
foreach (var r in removable)
|
||||||
|
r.Remove = RemoveStatus.Removed;
|
||||||
|
|
||||||
|
productsGrid_RemovableCountChanged(this, null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBoxLib.ShowAdminAlert(
|
||||||
|
this,
|
||||||
|
"Error scanning library. You may still manually select books to remove from Libation's library.",
|
||||||
|
"Error scanning library",
|
||||||
|
ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region UI display functions
|
#region UI display functions
|
||||||
|
|
||||||
public void Display()
|
public void Display()
|
||||||
@ -123,7 +187,13 @@ namespace LibationWinForms.GridView
|
|||||||
|
|
||||||
private void productsGrid_LiberateClicked(LibraryBookEntry liveGridEntry)
|
private void productsGrid_LiberateClicked(LibraryBookEntry liveGridEntry)
|
||||||
{
|
{
|
||||||
|
if (liveGridEntry.LibraryBook.Book.UserDefinedItem.BookStatus is not LiberatedStatus.Error)
|
||||||
LiberateClicked?.Invoke(this, liveGridEntry.LibraryBook);
|
LiberateClicked?.Invoke(this, liveGridEntry.LibraryBook);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void productsGrid_RemovableCountChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
RemovableCountChanged?.Invoke(sender, productsGrid.GetAllBookEntries().Count(lbe => lbe.Remove is RemoveStatus.Removed));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,8 +29,9 @@
|
|||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
this.components = new System.ComponentModel.Container();
|
this.components = new System.ComponentModel.Container();
|
||||||
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
|
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
|
||||||
this.gridEntryDataGridView = new System.Windows.Forms.DataGridView();
|
this.gridEntryDataGridView = new System.Windows.Forms.DataGridView();
|
||||||
|
this.removeGVColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn();
|
||||||
this.liberateGVColumn = new LibationWinForms.GridView.LiberateDataGridViewImageButtonColumn();
|
this.liberateGVColumn = new LibationWinForms.GridView.LiberateDataGridViewImageButtonColumn();
|
||||||
this.coverGVColumn = new System.Windows.Forms.DataGridViewImageColumn();
|
this.coverGVColumn = new System.Windows.Forms.DataGridViewImageColumn();
|
||||||
this.titleGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
this.titleGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||||
@ -60,6 +61,7 @@
|
|||||||
this.gridEntryDataGridView.AutoGenerateColumns = false;
|
this.gridEntryDataGridView.AutoGenerateColumns = false;
|
||||||
this.gridEntryDataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
this.gridEntryDataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||||
this.gridEntryDataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
this.gridEntryDataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||||
|
this.removeGVColumn,
|
||||||
this.liberateGVColumn,
|
this.liberateGVColumn,
|
||||||
this.coverGVColumn,
|
this.coverGVColumn,
|
||||||
this.titleGVColumn,
|
this.titleGVColumn,
|
||||||
@ -76,25 +78,39 @@
|
|||||||
this.tagAndDetailsGVColumn});
|
this.tagAndDetailsGVColumn});
|
||||||
this.gridEntryDataGridView.ContextMenuStrip = this.contextMenuStrip1;
|
this.gridEntryDataGridView.ContextMenuStrip = this.contextMenuStrip1;
|
||||||
this.gridEntryDataGridView.DataSource = this.syncBindingSource;
|
this.gridEntryDataGridView.DataSource = this.syncBindingSource;
|
||||||
dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
|
dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
|
||||||
dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Window;
|
dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Window;
|
||||||
dataGridViewCellStyle2.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
dataGridViewCellStyle1.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||||
dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.ControlText;
|
dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.ControlText;
|
||||||
dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight;
|
dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight;
|
||||||
dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
|
dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
|
||||||
dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
|
dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
|
||||||
this.gridEntryDataGridView.DefaultCellStyle = dataGridViewCellStyle2;
|
this.gridEntryDataGridView.DefaultCellStyle = dataGridViewCellStyle1;
|
||||||
this.gridEntryDataGridView.Dock = System.Windows.Forms.DockStyle.Fill;
|
this.gridEntryDataGridView.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
this.gridEntryDataGridView.Location = new System.Drawing.Point(0, 0);
|
this.gridEntryDataGridView.Location = new System.Drawing.Point(0, 0);
|
||||||
this.gridEntryDataGridView.Name = "gridEntryDataGridView";
|
this.gridEntryDataGridView.Name = "gridEntryDataGridView";
|
||||||
this.gridEntryDataGridView.ReadOnly = true;
|
|
||||||
this.gridEntryDataGridView.RowHeadersVisible = false;
|
this.gridEntryDataGridView.RowHeadersVisible = false;
|
||||||
this.gridEntryDataGridView.RowTemplate.Height = 82;
|
this.gridEntryDataGridView.RowTemplate.Height = 82;
|
||||||
this.gridEntryDataGridView.Size = new System.Drawing.Size(1510, 380);
|
this.gridEntryDataGridView.Size = new System.Drawing.Size(1570, 380);
|
||||||
this.gridEntryDataGridView.TabIndex = 0;
|
this.gridEntryDataGridView.TabIndex = 0;
|
||||||
this.gridEntryDataGridView.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.DataGridView_CellContentClick);
|
this.gridEntryDataGridView.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.DataGridView_CellContentClick);
|
||||||
this.gridEntryDataGridView.CellToolTipTextNeeded += new System.Windows.Forms.DataGridViewCellToolTipTextNeededEventHandler(this.gridEntryDataGridView_CellToolTipTextNeeded);
|
this.gridEntryDataGridView.CellToolTipTextNeeded += new System.Windows.Forms.DataGridViewCellToolTipTextNeededEventHandler(this.gridEntryDataGridView_CellToolTipTextNeeded);
|
||||||
//
|
//
|
||||||
|
// removeGVColumn
|
||||||
|
//
|
||||||
|
this.removeGVColumn.DataPropertyName = "Remove";
|
||||||
|
this.removeGVColumn.FalseValue = "";
|
||||||
|
this.removeGVColumn.Frozen = true;
|
||||||
|
this.removeGVColumn.HeaderText = "Remove";
|
||||||
|
this.removeGVColumn.IndeterminateValue = "";
|
||||||
|
this.removeGVColumn.MinimumWidth = 60;
|
||||||
|
this.removeGVColumn.Name = "removeGVColumn";
|
||||||
|
this.removeGVColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
|
||||||
|
this.removeGVColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
|
||||||
|
this.removeGVColumn.ThreeState = true;
|
||||||
|
this.removeGVColumn.TrueValue = "";
|
||||||
|
this.removeGVColumn.Width = 60;
|
||||||
|
//
|
||||||
// liberateGVColumn
|
// liberateGVColumn
|
||||||
//
|
//
|
||||||
this.liberateGVColumn.DataPropertyName = "Liberate";
|
this.liberateGVColumn.DataPropertyName = "Liberate";
|
||||||
@ -223,7 +239,7 @@
|
|||||||
this.AutoScroll = true;
|
this.AutoScroll = true;
|
||||||
this.Controls.Add(this.gridEntryDataGridView);
|
this.Controls.Add(this.gridEntryDataGridView);
|
||||||
this.Name = "ProductsGrid";
|
this.Name = "ProductsGrid";
|
||||||
this.Size = new System.Drawing.Size(1510, 380);
|
this.Size = new System.Drawing.Size(1570, 380);
|
||||||
this.Load += new System.EventHandler(this.ProductsGrid_Load);
|
this.Load += new System.EventHandler(this.ProductsGrid_Load);
|
||||||
((System.ComponentModel.ISupportInitialize)(this.gridEntryDataGridView)).EndInit();
|
((System.ComponentModel.ISupportInitialize)(this.gridEntryDataGridView)).EndInit();
|
||||||
((System.ComponentModel.ISupportInitialize)(this.syncBindingSource)).EndInit();
|
((System.ComponentModel.ISupportInitialize)(this.syncBindingSource)).EndInit();
|
||||||
@ -235,6 +251,8 @@
|
|||||||
#endregion
|
#endregion
|
||||||
private System.Windows.Forms.DataGridView gridEntryDataGridView;
|
private System.Windows.Forms.DataGridView gridEntryDataGridView;
|
||||||
private System.Windows.Forms.ContextMenuStrip contextMenuStrip1;
|
private System.Windows.Forms.ContextMenuStrip contextMenuStrip1;
|
||||||
|
private SyncBindingSource syncBindingSource;
|
||||||
|
private System.Windows.Forms.DataGridViewCheckBoxColumn removeGVColumn;
|
||||||
private LiberateDataGridViewImageButtonColumn liberateGVColumn;
|
private LiberateDataGridViewImageButtonColumn liberateGVColumn;
|
||||||
private System.Windows.Forms.DataGridViewImageColumn coverGVColumn;
|
private System.Windows.Forms.DataGridViewImageColumn coverGVColumn;
|
||||||
private System.Windows.Forms.DataGridViewTextBoxColumn titleGVColumn;
|
private System.Windows.Forms.DataGridViewTextBoxColumn titleGVColumn;
|
||||||
@ -249,6 +267,5 @@
|
|||||||
private System.Windows.Forms.DataGridViewTextBoxColumn myRatingGVColumn;
|
private System.Windows.Forms.DataGridViewTextBoxColumn myRatingGVColumn;
|
||||||
private System.Windows.Forms.DataGridViewTextBoxColumn miscGVColumn;
|
private System.Windows.Forms.DataGridViewTextBoxColumn miscGVColumn;
|
||||||
private EditTagsDataGridViewImageButtonColumn tagAndDetailsGVColumn;
|
private EditTagsDataGridViewImageButtonColumn tagAndDetailsGVColumn;
|
||||||
private SyncBindingSource syncBindingSource;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,12 +23,15 @@ namespace LibationWinForms.GridView
|
|||||||
public event LibraryBookEntryClickedEventHandler DetailsClicked;
|
public event LibraryBookEntryClickedEventHandler DetailsClicked;
|
||||||
public event GridEntryRectangleClickedEventHandler DescriptionClicked;
|
public event GridEntryRectangleClickedEventHandler DescriptionClicked;
|
||||||
public new event EventHandler<ScrollEventArgs> Scroll;
|
public new event EventHandler<ScrollEventArgs> Scroll;
|
||||||
|
public event EventHandler RemovableCountChanged;
|
||||||
|
|
||||||
private GridEntryBindingList bindingList;
|
private GridEntryBindingList bindingList;
|
||||||
internal IEnumerable<LibraryBook> GetVisibleBooks()
|
internal IEnumerable<LibraryBook> GetVisibleBooks()
|
||||||
=> bindingList
|
=> bindingList
|
||||||
.BookEntries()
|
.BookEntries()
|
||||||
.Select(lbe => lbe.LibraryBook);
|
.Select(lbe => lbe.LibraryBook);
|
||||||
|
internal IEnumerable<LibraryBookEntry> GetAllBookEntries()
|
||||||
|
=> bindingList.AllItems().BookEntries();
|
||||||
|
|
||||||
public ProductsGrid()
|
public ProductsGrid()
|
||||||
{
|
{
|
||||||
@ -81,6 +84,12 @@ namespace LibationWinForms.GridView
|
|||||||
else if (e.ColumnIndex == coverGVColumn.Index)
|
else if (e.ColumnIndex == coverGVColumn.Index)
|
||||||
CoverClicked?.Invoke(sEntry);
|
CoverClicked?.Invoke(sEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e.ColumnIndex == removeGVColumn.Index)
|
||||||
|
{
|
||||||
|
gridEntryDataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
|
||||||
|
RemovableCountChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private GridEntry getGridEntry(int rowIndex) => gridEntryDataGridView.GetBoundItem<GridEntry>(rowIndex);
|
private GridEntry getGridEntry(int rowIndex) => gridEntryDataGridView.GetBoundItem<GridEntry>(rowIndex);
|
||||||
@ -89,13 +98,33 @@ namespace LibationWinForms.GridView
|
|||||||
|
|
||||||
#region UI display functions
|
#region UI display functions
|
||||||
|
|
||||||
|
internal bool RemoveColumnVisible
|
||||||
|
{
|
||||||
|
get => removeGVColumn.Visible;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
foreach (var book in bindingList.AllItems())
|
||||||
|
book.Remove = RemoveStatus.NotRemoved;
|
||||||
|
}
|
||||||
|
removeGVColumn.Visible = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal void BindToGrid(List<LibraryBook> dbBooks)
|
internal void BindToGrid(List<LibraryBook> dbBooks)
|
||||||
{
|
{
|
||||||
var geList = dbBooks.Where(lb => lb.Book.IsProduct()).Select(b => new LibraryBookEntry(b)).Cast<GridEntry>().ToList();
|
var geList = dbBooks
|
||||||
|
.Where(lb => lb.Book.IsProduct())
|
||||||
|
.Select(b => new LibraryBookEntry(b))
|
||||||
|
.Cast<GridEntry>()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
var episodes = dbBooks.Where(lb => lb.Book.IsEpisodeChild());
|
var episodes = dbBooks.Where(lb => lb.Book.IsEpisodeChild());
|
||||||
|
|
||||||
foreach (var parent in dbBooks.Where(lb => lb.Book.IsEpisodeParent()))
|
var seriesBooks = dbBooks.Where(lb => lb.Book.IsEpisodeParent()).ToList();
|
||||||
|
|
||||||
|
foreach (var parent in seriesBooks)
|
||||||
{
|
{
|
||||||
var seriesEpisodes = episodes.FindChildren(parent);
|
var seriesEpisodes = episodes.FindChildren(parent);
|
||||||
|
|
||||||
@ -127,6 +156,7 @@ namespace LibationWinForms.GridView
|
|||||||
|
|
||||||
var allEntries = bindingList.AllItems().BookEntries();
|
var allEntries = bindingList.AllItems().BookEntries();
|
||||||
var seriesEntries = bindingList.AllItems().SeriesEntries().ToList();
|
var seriesEntries = bindingList.AllItems().SeriesEntries().ToList();
|
||||||
|
var parentedEpisodes = dbBooks.ParentedEpisodes();
|
||||||
|
|
||||||
foreach (var libraryBook in dbBooks.OrderBy(e => e.DateAdded))
|
foreach (var libraryBook in dbBooks.OrderBy(e => e.DateAdded))
|
||||||
{
|
{
|
||||||
@ -134,7 +164,8 @@ namespace LibationWinForms.GridView
|
|||||||
|
|
||||||
if (libraryBook.Book.IsProduct())
|
if (libraryBook.Book.IsProduct())
|
||||||
AddOrUpdateBook(libraryBook, existingEntry);
|
AddOrUpdateBook(libraryBook, existingEntry);
|
||||||
else if(libraryBook.Book.IsEpisodeChild())
|
else if(parentedEpisodes.Any(lb => lb == libraryBook))
|
||||||
|
//Only try to add or update is this LibraryBook is a know child of a parent
|
||||||
AddOrUpdateEpisode(libraryBook, existingEntry, seriesEntries, dbBooks);
|
AddOrUpdateEpisode(libraryBook, existingEntry, seriesEntries, dbBooks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +184,11 @@ namespace LibationWinForms.GridView
|
|||||||
.BookEntries()
|
.BookEntries()
|
||||||
.ExceptBy(dbBooks.Select(lb => lb.Book.AudibleProductId), ge => ge.AudibleProductId);
|
.ExceptBy(dbBooks.Select(lb => lb.Book.AudibleProductId), ge => ge.AudibleProductId);
|
||||||
|
|
||||||
|
RemoveBooks(removedBooks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveBooks(IEnumerable<LibraryBookEntry> removedBooks)
|
||||||
|
{
|
||||||
//Remove books in series from their parents' Children list
|
//Remove books in series from their parents' Children list
|
||||||
foreach (var removed in removedBooks.Where(b => b.Parent is not null))
|
foreach (var removed in removedBooks.Where(b => b.Parent is not null))
|
||||||
{
|
{
|
||||||
@ -188,6 +224,7 @@ namespace LibationWinForms.GridView
|
|||||||
if (existingEpisodeEntry is null)
|
if (existingEpisodeEntry is null)
|
||||||
{
|
{
|
||||||
LibraryBookEntry episodeEntry;
|
LibraryBookEntry episodeEntry;
|
||||||
|
|
||||||
var seriesEntry = seriesEntries.FindSeriesParent(episodeBook);
|
var seriesEntry = seriesEntries.FindSeriesParent(episodeBook);
|
||||||
|
|
||||||
if (seriesEntry is null)
|
if (seriesEntry is null)
|
||||||
@ -197,13 +234,14 @@ namespace LibationWinForms.GridView
|
|||||||
|
|
||||||
if (seriesBook is null)
|
if (seriesBook is null)
|
||||||
{
|
{
|
||||||
//This should be impossible because the importer ensures every episode has a parent.
|
//This is only possible if the user's db has some malformed
|
||||||
var ex = new ApplicationException($"Episode's series parent not found in database.");
|
//entries from earlier Libation releases that could not be
|
||||||
var seriesLinks = string.Join("\r\n", episodeBook.Book.SeriesLink?.Select(sb => $"{nameof(sb.Series.Name)}={sb.Series.Name}, {nameof(sb.Series.AudibleSeriesId)}={sb.Series.AudibleSeriesId}"));
|
//automatically fixed. Log, but don't throw.
|
||||||
Serilog.Log.Logger.Error(ex, "Episode={episodeBook}, Series: {seriesLinks}", episodeBook, seriesLinks);
|
Serilog.Log.Logger.Error("Episode={0}, Episode Series: {1}", episodeBook, episodeBook.Book.SeriesNames());
|
||||||
throw ex;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
seriesEntry = new SeriesEntry(seriesBook, episodeBook);
|
seriesEntry = new SeriesEntry(seriesBook, episodeBook);
|
||||||
seriesEntries.Add(seriesEntry);
|
seriesEntries.Add(seriesEntry);
|
||||||
|
|
||||||
@ -312,6 +350,14 @@ namespace LibationWinForms.GridView
|
|||||||
|
|
||||||
column.DisplayIndex = displayIndices.GetValueOrDefault(itemName, column.Index);
|
column.DisplayIndex = displayIndices.GetValueOrDefault(itemName, column.Index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Remove column is always first;
|
||||||
|
removeGVColumn.DisplayIndex = 0;
|
||||||
|
removeGVColumn.Visible = false;
|
||||||
|
removeGVColumn.ValueType = typeof(RemoveStatus);
|
||||||
|
removeGVColumn.FalseValue = RemoveStatus.NotRemoved;
|
||||||
|
removeGVColumn.TrueValue = RemoveStatus.Removed;
|
||||||
|
removeGVColumn.IndeterminateValue = RemoveStatus.SomeRemoved;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HideMenuItem_Click(object sender, EventArgs e)
|
private void HideMenuItem_Click(object sender, EventArgs e)
|
||||||
|
|||||||
@ -57,16 +57,13 @@
|
|||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
|
<metadata name="removeGVColumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
|
<value>True</value>
|
||||||
|
</metadata>
|
||||||
<metadata name="contextMenuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
<metadata name="contextMenuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||||
<value>171, 17</value>
|
<value>171, 17</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
<metadata name="syncBindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
<metadata name="syncBindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||||
<value>17, 17</value>
|
<value>17, 17</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
<metadata name="bindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
|
||||||
<value>326, 17</value>
|
|
||||||
</metadata>
|
|
||||||
<metadata name="bindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
|
||||||
<value>326, 17</value>
|
|
||||||
</metadata>
|
|
||||||
</root>
|
</root>
|
||||||
@ -24,6 +24,8 @@ namespace LibationWinForms.GridView
|
|||||||
{
|
{
|
||||||
if (seriesEpisode.Book.SeriesLink is null) return null;
|
if (seriesEpisode.Book.SeriesLink is null) return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
//Parent books will always have exactly 1 SeriesBook due to how
|
//Parent books will always have exactly 1 SeriesBook due to how
|
||||||
//they are imported in ApiExtended.getChildEpisodesAsync()
|
//they are imported in ApiExtended.getChildEpisodesAsync()
|
||||||
return gridEntries.SeriesEntries().FirstOrDefault(
|
return gridEntries.SeriesEntries().FirstOrDefault(
|
||||||
@ -31,6 +33,12 @@ namespace LibationWinForms.GridView
|
|||||||
seriesEpisode.Book.SeriesLink.Any(
|
seriesEpisode.Book.SeriesLink.Any(
|
||||||
s => s.Series.AudibleSeriesId == lb.LibraryBook.Book.SeriesLink.Single().Series.AudibleSeriesId));
|
s => s.Series.AudibleSeriesId == lb.LibraryBook.Book.SeriesLink.Single().Series.AudibleSeriesId));
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Serilog.Log.Error(ex, "Query error in {0}", nameof(FindSeriesParent));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#nullable disable
|
#nullable disable
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,43 @@ namespace LibationWinForms.GridView
|
|||||||
[Browsable(false)] public List<LibraryBookEntry> Children { get; }
|
[Browsable(false)] public List<LibraryBookEntry> Children { get; }
|
||||||
[Browsable(false)] public override DateTime DateAdded => Children.Max(c => c.DateAdded);
|
[Browsable(false)] public override DateTime DateAdded => Children.Max(c => c.DateAdded);
|
||||||
|
|
||||||
|
private bool suspendCounting = false;
|
||||||
|
public void ChildRemoveUpdate()
|
||||||
|
{
|
||||||
|
if (suspendCounting) return;
|
||||||
|
|
||||||
|
var removeCount = Children.Count(c => c.Remove is RemoveStatus.Removed);
|
||||||
|
|
||||||
|
if (removeCount == 0)
|
||||||
|
_remove = RemoveStatus.NotRemoved;
|
||||||
|
else if (removeCount == Children.Count)
|
||||||
|
_remove = RemoveStatus.Removed;
|
||||||
|
else
|
||||||
|
_remove = RemoveStatus.SomeRemoved;
|
||||||
|
NotifyPropertyChanged(nameof(Remove));
|
||||||
|
}
|
||||||
|
|
||||||
#region Model properties exposed to the view
|
#region Model properties exposed to the view
|
||||||
|
public override RemoveStatus Remove
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _remove;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_remove = value is RemoveStatus.SomeRemoved ? RemoveStatus.NotRemoved : value;
|
||||||
|
|
||||||
|
suspendCounting = true;
|
||||||
|
|
||||||
|
foreach (var item in Children)
|
||||||
|
item.Remove = value;
|
||||||
|
|
||||||
|
suspendCounting = false;
|
||||||
|
|
||||||
|
NotifyPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override LiberateButtonStatus Liberate { get; }
|
public override LiberateButtonStatus Liberate { get; }
|
||||||
public override string DisplayTags { get; } = string.Empty;
|
public override string DisplayTags { get; } = string.Empty;
|
||||||
|
|||||||
@ -130,6 +130,16 @@ namespace LibationWinForms.Properties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||||
|
/// </summary>
|
||||||
|
internal static System.Drawing.Bitmap error {
|
||||||
|
get {
|
||||||
|
object obj = ResourceManager.GetObject("error", resourceCulture);
|
||||||
|
return ((System.Drawing.Bitmap)(obj));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -139,6 +139,9 @@
|
|||||||
<data name="edit_tags_50x50" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
<data name="edit_tags_50x50" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||||
<value>..\Resources\edit-tags-50x50.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
<value>..\Resources\edit-tags-50x50.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="error" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||||
|
<value>..\Resources\error.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||||
|
</data>
|
||||||
<data name="import_16x16" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
<data name="import_16x16" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||||
<value>..\Resources\import_16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
<value>..\Resources\import_16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
BIN
Source/LibationWinForms/Resources/Stoplight1 with pdf.psd
Normal file
BIN
Source/LibationWinForms/Resources/Stoplight1.psd
Normal file
BIN
Source/LibationWinForms/Resources/error.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 425 B After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 689 B After Width: | Height: | Size: 6.0 KiB |