Make AccountsSettings and Persister more clear
This commit is contained in:
parent
84a847a838
commit
4b31207f91
@ -59,7 +59,7 @@ namespace FileLiberator
|
||||
var proposedOutputFile = Path.Combine(AudibleFileStorage.DecryptInProgress, $"[{libraryBook.Book.AudibleProductId}].m4b");
|
||||
|
||||
var account = AudibleApiStorage
|
||||
.GetAccountsSettings()
|
||||
.GetPersistentAccountsSettings()
|
||||
.GetAccount(libraryBook.Account, libraryBook.Book.Locale);
|
||||
|
||||
var outputAudioFilename = await aaxToM4bConverterDecrypt(proposedOutputFile, aaxFilename, account);
|
||||
|
||||
95
InternalUtilities/Account.cs
Normal file
95
InternalUtilities/Account.cs
Normal file
@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AudibleApi;
|
||||
using AudibleApi.Authorization;
|
||||
using Dinah.Core;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace InternalUtilities
|
||||
{
|
||||
public class Account : IUpdatable
|
||||
{
|
||||
public event EventHandler Updated;
|
||||
private void update(object sender = null, EventArgs e = null)
|
||||
=> Updated?.Invoke(this, new EventArgs());
|
||||
|
||||
// canonical. immutable. email or phone number
|
||||
public string AccountId { get; }
|
||||
|
||||
// user-friendly, non-canonical name. mutable
|
||||
private string _accountName;
|
||||
public string AccountName
|
||||
{
|
||||
get => _accountName;
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return;
|
||||
var v = value.Trim();
|
||||
if (v == _accountName)
|
||||
return;
|
||||
_accountName = v;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
// whether to include this account when scanning libraries.
|
||||
// technically this is an app setting; not an attribute of account. but since it's managed with accounts, it makes sense to put this exception-to-the-rule here
|
||||
private bool _libraryScan = true;
|
||||
public bool LibraryScan
|
||||
{
|
||||
get => _libraryScan;
|
||||
set
|
||||
{
|
||||
if (value == _libraryScan)
|
||||
return;
|
||||
_libraryScan = value;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
private string _decryptKey = "";
|
||||
public string DecryptKey
|
||||
{
|
||||
get => _decryptKey;
|
||||
set
|
||||
{
|
||||
var v = (value ?? "").Trim();
|
||||
if (v == _decryptKey)
|
||||
return;
|
||||
_decryptKey = v;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
private Identity _identity;
|
||||
public Identity IdentityTokens
|
||||
{
|
||||
get => _identity;
|
||||
set
|
||||
{
|
||||
if (_identity is null && value is null)
|
||||
return;
|
||||
|
||||
if (_identity != null)
|
||||
_identity.Updated -= update;
|
||||
|
||||
if (value != null)
|
||||
value.Updated += update;
|
||||
|
||||
_identity = value;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public Locale Locale => IdentityTokens?.Locale;
|
||||
|
||||
public Account(string accountId)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(accountId, nameof(accountId));
|
||||
AccountId = accountId.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,32 +1,14 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AudibleApi;
|
||||
using AudibleApi.Authorization;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace InternalUtilities
|
||||
{
|
||||
public class AccountsSettingsPersister : JsonFilePersister<AccountsSettings>
|
||||
{
|
||||
/// <summary>Alias for Target </summary>
|
||||
public AccountsSettings AccountsSettings => Target;
|
||||
|
||||
/// <summary>uses path. create file if doesn't yet exist</summary>
|
||||
public AccountsSettingsPersister(AccountsSettings target, string path, string jsonPath = null)
|
||||
: base(target, path, jsonPath) { }
|
||||
|
||||
/// <summary>load from existing file</summary>
|
||||
public AccountsSettingsPersister(string path, string jsonPath = null)
|
||||
: base(path, jsonPath) { }
|
||||
|
||||
protected override JsonSerializerSettings GetSerializerSettings()
|
||||
=> Identity.GetJsonSerializerSettings();
|
||||
}
|
||||
// 'AccountsSettings' is intentionally not IEnumerable<> so that properties can be added/extended
|
||||
// 'AccountsSettings' is intentionally NOT IEnumerable<> so that properties can be added/extended
|
||||
// from newtonsoft (https://www.newtonsoft.com/json/help/html/SerializationGuide.htm):
|
||||
// .NET : IList, IEnumerable, IList<T>, Array
|
||||
// JSON : Array (properties on the collection will not be serialized)
|
||||
@ -49,7 +31,7 @@ namespace InternalUtilities
|
||||
|
||||
#region Accounts
|
||||
private List<Account> _accounts_backing = new List<Account>();
|
||||
[JsonProperty(PropertyName = "Accounts")]
|
||||
[JsonProperty(PropertyName = nameof(Accounts))]
|
||||
private List<Account> _accounts_json
|
||||
{
|
||||
get => _accounts_backing;
|
||||
@ -141,84 +123,16 @@ namespace InternalUtilities
|
||||
|
||||
var acct = GetAccount(accountId, locale);
|
||||
|
||||
if (acct is null || account is null)
|
||||
// new: ok
|
||||
if (acct is null)
|
||||
return;
|
||||
|
||||
if (acct != account)
|
||||
// same account instance: ok
|
||||
if (acct == account)
|
||||
return;
|
||||
|
||||
// same account id + locale, different instance: bad
|
||||
throw new InvalidOperationException("Cannot add an account with the same account Id and Locale");
|
||||
}
|
||||
}
|
||||
public class Account : IUpdatable
|
||||
{
|
||||
public event EventHandler Updated;
|
||||
private void update(object sender = null, EventArgs e = null)
|
||||
=> Updated?.Invoke(this, new EventArgs());
|
||||
|
||||
// canonical. immutable. email or phone number
|
||||
public string AccountId { get; }
|
||||
|
||||
// user-friendly, non-canonical name. mutable
|
||||
private string _accountName;
|
||||
public string AccountName
|
||||
{
|
||||
get => _accountName;
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return;
|
||||
var v = value.Trim();
|
||||
if (v == _accountName)
|
||||
return;
|
||||
_accountName = v;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
// whether to include this account when scanning libraries.
|
||||
// technically this is an app setting; not an attribute of account. but since it's managed with accounts, it makes sense to put this exception-to-the-rule here
|
||||
public bool LibraryScan { get; set; }
|
||||
|
||||
private string _decryptKey = "";
|
||||
public string DecryptKey
|
||||
{
|
||||
get => _decryptKey;
|
||||
set
|
||||
{
|
||||
var v = (value ?? "").Trim();
|
||||
if (v == _decryptKey)
|
||||
return;
|
||||
_decryptKey = v;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
private Identity _identity;
|
||||
public Identity IdentityTokens
|
||||
{
|
||||
get => _identity;
|
||||
set
|
||||
{
|
||||
if (_identity is null && value is null)
|
||||
return;
|
||||
|
||||
if (_identity != null)
|
||||
_identity.Updated -= update;
|
||||
|
||||
if (value != null)
|
||||
value.Updated += update;
|
||||
|
||||
_identity = value;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public Locale Locale => IdentityTokens?.Locale;
|
||||
|
||||
public Account(string accountId)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(accountId, nameof(accountId));
|
||||
AccountId = accountId.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
24
InternalUtilities/AccountsSettingsPersister.cs
Normal file
24
InternalUtilities/AccountsSettingsPersister.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using AudibleApi.Authorization;
|
||||
using Dinah.Core.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace InternalUtilities
|
||||
{
|
||||
public class AccountsSettingsPersister : JsonFilePersister<AccountsSettings>
|
||||
{
|
||||
/// <summary>Alias for Target </summary>
|
||||
public AccountsSettings AccountsSettings => Target;
|
||||
|
||||
/// <summary>uses path. create file if doesn't yet exist</summary>
|
||||
public AccountsSettingsPersister(AccountsSettings target, string path, string jsonPath = null)
|
||||
: base(target, path, jsonPath) { }
|
||||
|
||||
/// <summary>load from existing file</summary>
|
||||
public AccountsSettingsPersister(string path, string jsonPath = null)
|
||||
: base(path, jsonPath) { }
|
||||
|
||||
protected override JsonSerializerSettings GetSerializerSettings()
|
||||
=> Identity.GetJsonSerializerSettings();
|
||||
}
|
||||
}
|
||||
@ -24,9 +24,9 @@ namespace InternalUtilities
|
||||
=> TEST_GetFirstAccount().GetIdentityTokensJsonPath();
|
||||
// convenience for for tests and demos. don't use in production Libation
|
||||
public static Account TEST_GetFirstAccount()
|
||||
=> GetAccountsSettings().GetAll().FirstOrDefault();
|
||||
=> GetPersistentAccountsSettings().GetAll().FirstOrDefault();
|
||||
|
||||
public static AccountsSettings GetAccountsSettings()
|
||||
public static AccountsSettings GetPersistentAccountsSettings()
|
||||
=> new AccountsSettingsPersister(AccountsSettingsFile).AccountsSettings;
|
||||
|
||||
public static string GetIdentityTokensJsonPath(this Account account)
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
<!-- <PublishSingleFile>true</PublishSingleFile> -->
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
|
||||
<Version>3.1.12.255</Version>
|
||||
<Version>3.1.12.260</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -162,7 +162,7 @@ namespace LibationLauncher
|
||||
};
|
||||
|
||||
// saves to new file
|
||||
AudibleApiStorage.GetAccountsSettings().Add(account);
|
||||
AudibleApiStorage.GetPersistentAccountsSettings().Add(account);
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
@ -28,6 +28,12 @@ namespace LibationWinForms.Dialogs
|
||||
populateGridValues();
|
||||
}
|
||||
|
||||
struct OriginalValue
|
||||
{
|
||||
public string AccountId { get; set; }
|
||||
public string LocaleName { get; set; }
|
||||
}
|
||||
|
||||
private void populateDropDown()
|
||||
=> (dataGridView1.Columns[COL_Locale] as DataGridViewComboBoxColumn).DataSource
|
||||
= Localization.Locales
|
||||
@ -36,18 +42,16 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
private void populateGridValues()
|
||||
{
|
||||
// WARNING
|
||||
// behind the scenes this returns a AccountsSettings
|
||||
// accounts persister will write ANY EDIT to object immediately to file
|
||||
// WARNING: accounts persister will write ANY EDIT to object immediately to file
|
||||
// here: copy strings
|
||||
// only persist in 'save' step
|
||||
var accounts = AudibleApiStorage.GetAccountsSettings().Accounts;
|
||||
var accounts = AudibleApiStorage.GetPersistentAccountsSettings().Accounts;
|
||||
if (!accounts.Any())
|
||||
return;
|
||||
|
||||
foreach (var account in accounts)
|
||||
dataGridView1.Rows.Add(
|
||||
new { account.AccountId, account.Locale.Name },
|
||||
new OriginalValue { AccountId = account.AccountId, LocaleName = account.Locale.Name },
|
||||
"X",
|
||||
account.LibraryScan,
|
||||
account.AccountId,
|
||||
@ -57,6 +61,7 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
|
||||
{
|
||||
e.Row.Cells[COL_Original].Value = new OriginalValue();
|
||||
e.Row.Cells[COL_Delete].Value = "X";
|
||||
e.Row.Cells[COL_LibraryScan].Value = true;
|
||||
}
|
||||
@ -98,16 +103,39 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
private void saveBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
var accounts = AudibleApiStorage.GetAccountsSettings()
|
||||
.Accounts;
|
||||
foreach (DataGridViewRow row in this.dataGridView1.Rows)
|
||||
{
|
||||
if (row.IsNewRow)
|
||||
continue;
|
||||
|
||||
// find added
|
||||
// validate
|
||||
var original = (OriginalValue)row.Cells[COL_Original].Value;
|
||||
var originalAccountId = original.AccountId;
|
||||
var originalLocaleName = original.LocaleName;
|
||||
|
||||
// find deleted
|
||||
var libraryScan = (bool)row.Cells[COL_LibraryScan].Value;
|
||||
var accountId = (string)row.Cells[COL_AccountId].Value;
|
||||
var localeName = (string)row.Cells[COL_Locale].Value;
|
||||
var accountName = (string)row.Cells[COL_AccountName].Value;
|
||||
}
|
||||
|
||||
// find edited
|
||||
// validate
|
||||
// WARNING: accounts persister will write ANY EDIT immediately to file.
|
||||
// Take NO action on persistent objects until the end
|
||||
var accountsSettings = AudibleApiStorage.GetPersistentAccountsSettings();
|
||||
|
||||
var existingAccounts = accountsSettings.Accounts;
|
||||
|
||||
foreach (var account in existingAccounts)
|
||||
{
|
||||
}
|
||||
|
||||
// editing account id is a special case
|
||||
// an account is defined by its account id, therefore this is really a different account. the user won't care about this distinction though
|
||||
|
||||
// added: find and validate
|
||||
|
||||
// edited: find and validate
|
||||
|
||||
// deleted: find
|
||||
|
||||
// persist
|
||||
|
||||
|
||||
@ -115,7 +115,7 @@ namespace AccountsTests
|
||||
{
|
||||
""AccountId"": ""cng"",
|
||||
""AccountName"": ""my main login"",
|
||||
""LibraryScan"": false,
|
||||
""LibraryScan"": true,
|
||||
""DecryptKey"": ""asdfasdf"",
|
||||
""IdentityTokens"": {
|
||||
""LocaleName"": ""[empty]"",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user