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 proposedOutputFile = Path.Combine(AudibleFileStorage.DecryptInProgress, $"[{libraryBook.Book.AudibleProductId}].m4b");
|
||||||
|
|
||||||
var account = AudibleApiStorage
|
var account = AudibleApiStorage
|
||||||
.GetAccountsSettings()
|
.GetPersistentAccountsSettings()
|
||||||
.GetAccount(libraryBook.Account, libraryBook.Book.Locale);
|
.GetAccount(libraryBook.Account, libraryBook.Book.Locale);
|
||||||
|
|
||||||
var outputAudioFilename = await aaxToM4bConverterDecrypt(proposedOutputFile, aaxFilename, account);
|
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;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AudibleApi;
|
using AudibleApi;
|
||||||
using AudibleApi.Authorization;
|
using AudibleApi.Authorization;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using Dinah.Core.IO;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace InternalUtilities
|
namespace InternalUtilities
|
||||||
{
|
{
|
||||||
public class AccountsSettingsPersister : JsonFilePersister<AccountsSettings>
|
// 'AccountsSettings' is intentionally NOT IEnumerable<> so that properties can be added/extended
|
||||||
{
|
|
||||||
/// <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
|
|
||||||
// from newtonsoft (https://www.newtonsoft.com/json/help/html/SerializationGuide.htm):
|
// from newtonsoft (https://www.newtonsoft.com/json/help/html/SerializationGuide.htm):
|
||||||
// .NET : IList, IEnumerable, IList<T>, Array
|
// .NET : IList, IEnumerable, IList<T>, Array
|
||||||
// JSON : Array (properties on the collection will not be serialized)
|
// JSON : Array (properties on the collection will not be serialized)
|
||||||
@ -49,7 +31,7 @@ namespace InternalUtilities
|
|||||||
|
|
||||||
#region Accounts
|
#region Accounts
|
||||||
private List<Account> _accounts_backing = new List<Account>();
|
private List<Account> _accounts_backing = new List<Account>();
|
||||||
[JsonProperty(PropertyName = "Accounts")]
|
[JsonProperty(PropertyName = nameof(Accounts))]
|
||||||
private List<Account> _accounts_json
|
private List<Account> _accounts_json
|
||||||
{
|
{
|
||||||
get => _accounts_backing;
|
get => _accounts_backing;
|
||||||
@ -141,84 +123,16 @@ namespace InternalUtilities
|
|||||||
|
|
||||||
var acct = GetAccount(accountId, locale);
|
var acct = GetAccount(accountId, locale);
|
||||||
|
|
||||||
if (acct is null || account is null)
|
// new: ok
|
||||||
|
if (acct is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (acct != account)
|
// same account instance: ok
|
||||||
throw new InvalidOperationException("Cannot add an account with the same account Id and Locale");
|
if (acct == account)
|
||||||
}
|
return;
|
||||||
}
|
|
||||||
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
|
// same account id + locale, different instance: bad
|
||||||
public string AccountId { get; }
|
throw new InvalidOperationException("Cannot add an account with the same account Id and Locale");
|
||||||
|
|
||||||
// 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();
|
=> TEST_GetFirstAccount().GetIdentityTokensJsonPath();
|
||||||
// convenience for for tests and demos. don't use in production Libation
|
// convenience for for tests and demos. don't use in production Libation
|
||||||
public static Account TEST_GetFirstAccount()
|
public static Account TEST_GetFirstAccount()
|
||||||
=> GetAccountsSettings().GetAll().FirstOrDefault();
|
=> GetPersistentAccountsSettings().GetAll().FirstOrDefault();
|
||||||
|
|
||||||
public static AccountsSettings GetAccountsSettings()
|
public static AccountsSettings GetPersistentAccountsSettings()
|
||||||
=> new AccountsSettingsPersister(AccountsSettingsFile).AccountsSettings;
|
=> new AccountsSettingsPersister(AccountsSettingsFile).AccountsSettings;
|
||||||
|
|
||||||
public static string GetIdentityTokensJsonPath(this Account account)
|
public static string GetIdentityTokensJsonPath(this Account account)
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
<!-- <PublishSingleFile>true</PublishSingleFile> -->
|
<!-- <PublishSingleFile>true</PublishSingleFile> -->
|
||||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
|
|
||||||
<Version>3.1.12.255</Version>
|
<Version>3.1.12.260</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -162,7 +162,7 @@ namespace LibationLauncher
|
|||||||
};
|
};
|
||||||
|
|
||||||
// saves to new file
|
// saves to new file
|
||||||
AudibleApiStorage.GetAccountsSettings().Add(account);
|
AudibleApiStorage.GetPersistentAccountsSettings().Add(account);
|
||||||
|
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,6 +28,12 @@ namespace LibationWinForms.Dialogs
|
|||||||
populateGridValues();
|
populateGridValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct OriginalValue
|
||||||
|
{
|
||||||
|
public string AccountId { get; set; }
|
||||||
|
public string LocaleName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
private void populateDropDown()
|
private void populateDropDown()
|
||||||
=> (dataGridView1.Columns[COL_Locale] as DataGridViewComboBoxColumn).DataSource
|
=> (dataGridView1.Columns[COL_Locale] as DataGridViewComboBoxColumn).DataSource
|
||||||
= Localization.Locales
|
= Localization.Locales
|
||||||
@ -36,18 +42,16 @@ namespace LibationWinForms.Dialogs
|
|||||||
|
|
||||||
private void populateGridValues()
|
private void populateGridValues()
|
||||||
{
|
{
|
||||||
// WARNING
|
// WARNING: accounts persister will write ANY EDIT to object immediately to file
|
||||||
// behind the scenes this returns a AccountsSettings
|
|
||||||
// accounts persister will write ANY EDIT to object immediately to file
|
|
||||||
// here: copy strings
|
// here: copy strings
|
||||||
// only persist in 'save' step
|
// only persist in 'save' step
|
||||||
var accounts = AudibleApiStorage.GetAccountsSettings().Accounts;
|
var accounts = AudibleApiStorage.GetPersistentAccountsSettings().Accounts;
|
||||||
if (!accounts.Any())
|
if (!accounts.Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var account in accounts)
|
foreach (var account in accounts)
|
||||||
dataGridView1.Rows.Add(
|
dataGridView1.Rows.Add(
|
||||||
new { account.AccountId, account.Locale.Name },
|
new OriginalValue { AccountId = account.AccountId, LocaleName = account.Locale.Name },
|
||||||
"X",
|
"X",
|
||||||
account.LibraryScan,
|
account.LibraryScan,
|
||||||
account.AccountId,
|
account.AccountId,
|
||||||
@ -57,6 +61,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
|
|
||||||
private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
|
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_Delete].Value = "X";
|
||||||
e.Row.Cells[COL_LibraryScan].Value = true;
|
e.Row.Cells[COL_LibraryScan].Value = true;
|
||||||
}
|
}
|
||||||
@ -98,16 +103,39 @@ namespace LibationWinForms.Dialogs
|
|||||||
|
|
||||||
private void saveBtn_Click(object sender, EventArgs e)
|
private void saveBtn_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var accounts = AudibleApiStorage.GetAccountsSettings()
|
foreach (DataGridViewRow row in this.dataGridView1.Rows)
|
||||||
.Accounts;
|
{
|
||||||
|
if (row.IsNewRow)
|
||||||
|
continue;
|
||||||
|
|
||||||
// find added
|
var original = (OriginalValue)row.Cells[COL_Original].Value;
|
||||||
// validate
|
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
|
// WARNING: accounts persister will write ANY EDIT immediately to file.
|
||||||
// validate
|
// 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
|
// persist
|
||||||
|
|
||||||
|
|||||||
@ -115,7 +115,7 @@ namespace AccountsTests
|
|||||||
{
|
{
|
||||||
""AccountId"": ""cng"",
|
""AccountId"": ""cng"",
|
||||||
""AccountName"": ""my main login"",
|
""AccountName"": ""my main login"",
|
||||||
""LibraryScan"": false,
|
""LibraryScan"": true,
|
||||||
""DecryptKey"": ""asdfasdf"",
|
""DecryptKey"": ""asdfasdf"",
|
||||||
""IdentityTokens"": {
|
""IdentityTokens"": {
|
||||||
""LocaleName"": ""[empty]"",
|
""LocaleName"": ""[empty]"",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user