mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-09-20 11:32:25 -04:00
It is derived from AccountId, AccountName and Locale, so Newtonsoft was writing a stale copy of three fields that are already in the file. Locale next to it was already ignored for the same reason; this one was missed. Harmless, but it is the sort of thing that gets read back one day as if it meant something. Four tests pin the serialized shape: the two computed properties stay out, the set of persisted members is asserted whole so a new property has to be a decision rather than an accident, and a file already carrying the stray key still loads and is rewritten without it. Verified against the app rather than only the tests - renaming an account and saving no longer adds the key. Co-authored-by: rmcrackan <rmcrackan@gmail.com>
112 lines
2.7 KiB
C#
112 lines
2.7 KiB
C#
using AudibleApi;
|
|
using AudibleApi.Authorization;
|
|
using Dinah.Core;
|
|
using Dinah.Core.Security;
|
|
using LibationFileManager;
|
|
using Newtonsoft.Json;
|
|
using System;
|
|
using System.Diagnostics;
|
|
|
|
namespace AudibleUtilities;
|
|
|
|
[DebuggerDisplay("{AccountId,nq} - {Locale}")]
|
|
public class Account : IUpdatable, ILogMasked
|
|
{
|
|
public event EventHandler? Updated;
|
|
private void update(object? sender = null, EventArgs? e = null)
|
|
=> Updated?.Invoke(this, EventArgs.Empty);
|
|
|
|
// canonical. immutable. email or phone number
|
|
public string AccountId { get; }
|
|
|
|
// user-friendly, non-canonical name. mutable
|
|
public string? AccountName
|
|
{
|
|
get => field;
|
|
set
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
return;
|
|
var v = value.Trim();
|
|
if (v == field)
|
|
return;
|
|
field = 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 => field;
|
|
set
|
|
{
|
|
if (value == field)
|
|
return;
|
|
field = value;
|
|
update();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// aka: activation bytes. A <see cref="SecretString"/> so that no reflective dump - Serilog's structured
|
|
/// logging, or Serilog.Exceptions walking a logged exception - can reach the value. Persists as the same
|
|
/// bare JSON string it always did.
|
|
/// </summary>
|
|
public SecretString DecryptKey
|
|
{
|
|
get => field;
|
|
set
|
|
{
|
|
var v = (value.Reveal() ?? "").Trim();
|
|
if (v == field.Reveal())
|
|
return;
|
|
field = v;
|
|
update();
|
|
}
|
|
}
|
|
|
|
public Identity? IdentityTokens
|
|
{
|
|
get => field;
|
|
set
|
|
{
|
|
if (field is null && value is null)
|
|
return;
|
|
|
|
if (field is not null)
|
|
field.Updated -= update;
|
|
|
|
if (value is not null)
|
|
value.Updated += update;
|
|
|
|
field = value;
|
|
update();
|
|
}
|
|
}
|
|
|
|
[JsonIgnore]
|
|
public Locale? Locale => IdentityTokens?.Locale;
|
|
|
|
public Account(string accountId)
|
|
{
|
|
AccountId = ArgumentValidator.EnsureNotNullOrWhiteSpace(accountId, nameof(accountId)).Trim();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Masked, because this is what interpolation and non-destructured logging reach for. Use
|
|
/// <see cref="AccountCredentialStatus.FormatAccountLabel"/> for dialogs shown to the account's owner, and see
|
|
/// the DebuggerDisplay above for the unmasked form while debugging.
|
|
/// </summary>
|
|
public override string ToString() => MaskedLogEntry;
|
|
|
|
/// <summary>Derived from the fields above, so persisting it would only add a stale copy of them.</summary>
|
|
[JsonIgnore]
|
|
public string MaskedLogEntry => @$"AccountId={mask(AccountId)}|AccountName={mask(AccountName)}|Locale={Locale?.Name ?? "[empty]"}";
|
|
private static string mask(string? str)
|
|
=> str is null ? "[null]"
|
|
: str == string.Empty ? "[empty]"
|
|
: str.ToMask();
|
|
}
|