Add PropertyChanged detection for Dictionary type settings
This commit is contained in:
parent
1f7000c2c9
commit
5c73beff4b
@ -48,18 +48,12 @@ namespace AaxDecrypter
|
|||||||
TempFilePath = Path.ChangeExtension(jsonDownloadState, ".aaxc");
|
TempFilePath = Path.ChangeExtension(jsonDownloadState, ".aaxc");
|
||||||
|
|
||||||
DownloadOptions = ArgumentValidator.EnsureNotNull(dlOptions, nameof(dlOptions));
|
DownloadOptions = ArgumentValidator.EnsureNotNull(dlOptions, nameof(dlOptions));
|
||||||
DownloadOptions.PropertyChanged += DownloadOptions_PropertyChanged;
|
DownloadOptions.DownloadSpeedChanged += (_, speed) => InputFileStream.SpeedLimit = speed;
|
||||||
|
|
||||||
// delete file after validation is complete
|
// delete file after validation is complete
|
||||||
FileUtility.SaferDelete(OutputFileName);
|
FileUtility.SaferDelete(OutputFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DownloadOptions_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.PropertyName == nameof(DownloadOptions.DownloadSpeedBps))
|
|
||||||
InputFileStream.SpeedLimit = DownloadOptions.DownloadSpeedBps;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract Task CancelAsync();
|
public abstract Task CancelAsync();
|
||||||
|
|
||||||
public virtual void SetCoverArt(byte[] coverArt)
|
public virtual void SetCoverArt(byte[] coverArt)
|
||||||
@ -173,7 +167,7 @@ namespace AaxDecrypter
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (nfsp is not null)
|
if (nfsp?.NetworkFileStream is not null)
|
||||||
nfsp.NetworkFileStream.SpeedLimit = DownloadOptions.DownloadSpeedBps;
|
nfsp.NetworkFileStream.SpeedLimit = DownloadOptions.DownloadSpeedBps;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
using AAXClean;
|
using AAXClean;
|
||||||
using System.ComponentModel;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace AaxDecrypter
|
namespace AaxDecrypter
|
||||||
{
|
{
|
||||||
public interface IDownloadOptions : INotifyPropertyChanged
|
public interface IDownloadOptions
|
||||||
{
|
{
|
||||||
|
event EventHandler<long> DownloadSpeedChanged;
|
||||||
FileManager.ReplacementCharacters ReplacementCharacters { get; }
|
FileManager.ReplacementCharacters ReplacementCharacters { get; }
|
||||||
string DownloadUrl { get; }
|
string DownloadUrl { get; }
|
||||||
string UserAgent { get; }
|
string UserAgent { get; }
|
||||||
|
|||||||
@ -5,7 +5,6 @@ using DataLayer;
|
|||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
using FileManager;
|
using FileManager;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.ComponentModel;
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using ApplicationServices;
|
using ApplicationServices;
|
||||||
@ -14,9 +13,7 @@ namespace FileLiberator
|
|||||||
{
|
{
|
||||||
public class DownloadOptions : IDownloadOptions, IDisposable
|
public class DownloadOptions : IDownloadOptions, IDisposable
|
||||||
{
|
{
|
||||||
|
public event EventHandler<long> DownloadSpeedChanged;
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
private readonly IDisposable cancellation;
|
|
||||||
public LibraryBook LibraryBook { get; }
|
public LibraryBook LibraryBook { get; }
|
||||||
public LibraryBookDto LibraryBookDto { get; }
|
public LibraryBookDto LibraryBookDto { get; }
|
||||||
public string DownloadUrl { get; }
|
public string DownloadUrl { get; }
|
||||||
@ -29,7 +26,7 @@ namespace FileLiberator
|
|||||||
public bool StripUnabridged { get; init; }
|
public bool StripUnabridged { get; init; }
|
||||||
public bool CreateCueSheet { get; init; }
|
public bool CreateCueSheet { get; init; }
|
||||||
public bool DownloadClipsBookmarks { get; init; }
|
public bool DownloadClipsBookmarks { get; init; }
|
||||||
public long DownloadSpeedBps { get; set; }
|
public long DownloadSpeedBps { get; init; }
|
||||||
public ChapterInfo ChapterInfo { get; init; }
|
public ChapterInfo ChapterInfo { get; init; }
|
||||||
public bool FixupFile { get; init; }
|
public bool FixupFile { get; init; }
|
||||||
public NAudio.Lame.LameConfig LameConfig { get; init; }
|
public NAudio.Lame.LameConfig LameConfig { get; init; }
|
||||||
@ -72,27 +69,23 @@ namespace FileLiberator
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DownloadSpeedChanged(string propertyName, long speed)
|
private readonly IDisposable cancellation;
|
||||||
{
|
public void Dispose() => cancellation?.Dispose();
|
||||||
DownloadSpeedBps = speed;
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DownloadSpeedBps)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
cancellation?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public DownloadOptions(LibraryBook libraryBook, string downloadUrl, string userAgent)
|
public DownloadOptions(LibraryBook libraryBook, string downloadUrl, string userAgent)
|
||||||
{
|
{
|
||||||
LibraryBook = ArgumentValidator.EnsureNotNull(libraryBook, nameof(libraryBook));
|
LibraryBook = ArgumentValidator.EnsureNotNull(libraryBook, nameof(libraryBook));
|
||||||
DownloadUrl = ArgumentValidator.EnsureNotNullOrEmpty(downloadUrl, nameof(downloadUrl));
|
DownloadUrl = ArgumentValidator.EnsureNotNullOrEmpty(downloadUrl, nameof(downloadUrl));
|
||||||
UserAgent = ArgumentValidator.EnsureNotNullOrEmpty(userAgent, nameof(userAgent));
|
UserAgent = ArgumentValidator.EnsureNotNullOrEmpty(userAgent, nameof(userAgent));
|
||||||
|
// no null/empty check for key/iv. unencrypted files do not have them
|
||||||
|
|
||||||
LibraryBookDto = LibraryBook.ToDto();
|
LibraryBookDto = LibraryBook.ToDto();
|
||||||
|
|
||||||
cancellation = Configuration.Instance.SubscribeToPropertyChanged<long>(nameof(Configuration.DownloadSpeedLimit), DownloadSpeedChanged);
|
cancellation =
|
||||||
// no null/empty check for key/iv. unencrypted files do not have them
|
Configuration.Instance
|
||||||
|
.SubscribeToPropertyChanged<long>(
|
||||||
|
nameof(Configuration.DownloadSpeedLimit),
|
||||||
|
(_, s) => DownloadSpeedChanged?.Invoke(this, s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,8 +49,10 @@ namespace FileManager
|
|||||||
public T GetNonString<T>(string propertyName)
|
public T GetNonString<T>(string propertyName)
|
||||||
{
|
{
|
||||||
var obj = GetObject(propertyName);
|
var obj = GetObject(propertyName);
|
||||||
if (obj is null) return default;
|
|
||||||
|
|
||||||
|
if (obj is null) return default;
|
||||||
|
if (obj.GetType().IsAssignableTo(typeof(T))) return (T)obj;
|
||||||
|
if (obj is JObject jObject) return jObject.ToObject<T>();
|
||||||
if (obj is JValue jValue)
|
if (obj is JValue jValue)
|
||||||
{
|
{
|
||||||
if (jValue.Type == JTokenType.String && typeof(T).IsAssignableTo(typeof(Enum)))
|
if (jValue.Type == JTokenType.String && typeof(T).IsAssignableTo(typeof(Enum)))
|
||||||
@ -62,8 +64,7 @@ namespace FileManager
|
|||||||
}
|
}
|
||||||
return jValue.Value<T>();
|
return jValue.Value<T>();
|
||||||
}
|
}
|
||||||
if (obj is JObject jObject) return jObject.ToObject<T>();
|
throw new InvalidCastException($"{obj.GetType()} is not convertible to {typeof(T)}");
|
||||||
return (T)obj;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public object GetObject(string propertyName)
|
public object GetObject(string propertyName)
|
||||||
|
|||||||
@ -7,7 +7,7 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace FileManager
|
namespace FileManager
|
||||||
{
|
{
|
||||||
public class Replacement : ICloneable
|
public record Replacement
|
||||||
{
|
{
|
||||||
public const int FIXED_COUNT = 6;
|
public const int FIXED_COUNT = 6;
|
||||||
|
|
||||||
@ -30,8 +30,6 @@ namespace FileManager
|
|||||||
Mandatory = mandatory;
|
Mandatory = mandatory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Clone() => new Replacement(CharacterToReplace, ReplacementString, Description, Mandatory);
|
|
||||||
|
|
||||||
public void Update(char charToReplace, string replacementString, string description)
|
public void Update(char charToReplace, string replacementString, string description)
|
||||||
{
|
{
|
||||||
ReplacementString = replacementString;
|
ReplacementString = replacementString;
|
||||||
@ -61,10 +59,20 @@ namespace FileManager
|
|||||||
[JsonConverter(typeof(ReplacementCharactersConverter))]
|
[JsonConverter(typeof(ReplacementCharactersConverter))]
|
||||||
public class ReplacementCharacters
|
public class ReplacementCharacters
|
||||||
{
|
{
|
||||||
static ReplacementCharacters()
|
public override bool Equals(object obj)
|
||||||
{
|
{
|
||||||
|
if (obj is ReplacementCharacters second && Replacements.Count == second.Replacements.Count)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Replacements.Count; i++)
|
||||||
|
if (Replacements[i] != second.Replacements[i])
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public override int GetHashCode() => Replacements.GetHashCode();
|
||||||
|
|
||||||
public static readonly ReplacementCharacters Default
|
public static readonly ReplacementCharacters Default
|
||||||
= IsWindows
|
= IsWindows
|
||||||
? new()
|
? new()
|
||||||
|
|||||||
@ -101,7 +101,7 @@ namespace LibationAvalonia
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FormSizeAndPosition
|
private record FormSizeAndPosition
|
||||||
{
|
{
|
||||||
public int X;
|
public int X;
|
||||||
public int Y;
|
public int Y;
|
||||||
@ -110,7 +110,6 @@ namespace LibationAvalonia
|
|||||||
public bool IsMaximized;
|
public bool IsMaximized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void HideMinMaxBtns(this Window form)
|
public static void HideMinMaxBtns(this Window form)
|
||||||
{
|
{
|
||||||
if (Design.IsDesignMode || !Configuration.IsWindows)
|
if (Design.IsDesignMode || !Configuration.IsWindows)
|
||||||
|
|||||||
@ -18,23 +18,24 @@ namespace LibationFileManager
|
|||||||
// config class is only responsible for path. not responsible for setting defaults, dir validation, or dir creation
|
// config class is only responsible for path. not responsible for setting defaults, dir validation, or dir creation
|
||||||
// exceptions: appsettings.json, LibationFiles dir, Settings.json
|
// exceptions: appsettings.json, LibationFiles dir, Settings.json
|
||||||
|
|
||||||
private PersistentDictionary persistentDictionary { get; set; }
|
private PersistentDictionary persistentDictionary;
|
||||||
|
|
||||||
public T GetNonString<T>([CallerMemberName] string propertyName = "") => persistentDictionary.GetNonString<T>(propertyName);
|
public T GetNonString<T>([CallerMemberName] string propertyName = "") => persistentDictionary.GetNonString<T>(propertyName);
|
||||||
public object GetObject([CallerMemberName] string propertyName = "") => persistentDictionary.GetObject(propertyName);
|
public object GetObject([CallerMemberName] string propertyName = "") => persistentDictionary.GetObject(propertyName);
|
||||||
public string GetString([CallerMemberName] string propertyName = "") => persistentDictionary.GetString(propertyName);
|
public string GetString([CallerMemberName] string propertyName = "") => persistentDictionary.GetString(propertyName);
|
||||||
public void SetNonString(object newValue, [CallerMemberName] string propertyName = "")
|
public void SetNonString(object newValue, [CallerMemberName] string propertyName = "")
|
||||||
{
|
{
|
||||||
var existing = GetType().GetProperty(propertyName)?.GetValue(this);
|
var existing = getExistingValue(propertyName);
|
||||||
if (existing?.Equals(newValue) is true) return;
|
if (existing?.Equals(newValue) is true) return;
|
||||||
|
|
||||||
PropertyChanging?.Invoke(this, new PropertyChangingEventArgsEx(propertyName, existing, newValue));
|
PropertyChanging?.Invoke(this, new PropertyChangingEventArgsEx(propertyName, existing, newValue));
|
||||||
persistentDictionary.SetNonString(propertyName, newValue);
|
persistentDictionary.SetNonString(propertyName, newValue);
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgsEx(propertyName, newValue));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgsEx(propertyName, newValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetString(string newValue, [CallerMemberName] string propertyName = "")
|
public void SetString(string newValue, [CallerMemberName] string propertyName = "")
|
||||||
{
|
{
|
||||||
var existing = GetType().GetProperty(propertyName)?.GetValue(this);
|
var existing = getExistingValue(propertyName);
|
||||||
if (existing?.Equals(newValue) is true) return;
|
if (existing?.Equals(newValue) is true) return;
|
||||||
|
|
||||||
PropertyChanging?.Invoke(this, new PropertyChangingEventArgsEx(propertyName, existing, newValue));
|
PropertyChanging?.Invoke(this, new PropertyChangingEventArgsEx(propertyName, existing, newValue));
|
||||||
@ -42,6 +43,13 @@ namespace LibationFileManager
|
|||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgsEx(propertyName, newValue));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgsEx(propertyName, newValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object getExistingValue(string propertyName)
|
||||||
|
{
|
||||||
|
var property = GetType().GetProperty(propertyName);
|
||||||
|
if (property is not null) return property.GetValue(this);
|
||||||
|
return GetObject(propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>WILL ONLY set if already present. WILL NOT create new</summary>
|
/// <summary>WILL ONLY set if already present. WILL NOT create new</summary>
|
||||||
public void SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false)
|
public void SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false)
|
||||||
{
|
{
|
||||||
@ -121,13 +129,13 @@ namespace LibationFileManager
|
|||||||
public int LameVBRQuality { get => GetNonString<int>(); set => SetNonString(value); }
|
public int LameVBRQuality { get => GetNonString<int>(); set => SetNonString(value); }
|
||||||
|
|
||||||
[Description("A Dictionary of GridView data property names and bool indicating its column's visibility in ProductsGrid")]
|
[Description("A Dictionary of GridView data property names and bool indicating its column's visibility in ProductsGrid")]
|
||||||
public Dictionary<string, bool> GridColumnsVisibilities { get => GetNonString<Dictionary<string, bool>>(); set => SetNonString(value); }
|
public Dictionary<string, bool> GridColumnsVisibilities { get => GetNonString<EquatableDictionary<string, bool>>().Clone(); set => SetNonString(value); }
|
||||||
|
|
||||||
[Description("A Dictionary of GridView data property names and int indicating its column's display index in ProductsGrid")]
|
[Description("A Dictionary of GridView data property names and int indicating its column's display index in ProductsGrid")]
|
||||||
public Dictionary<string, int> GridColumnsDisplayIndices { get => GetNonString<Dictionary<string, int>>(); set => SetNonString(value); }
|
public Dictionary<string, int> GridColumnsDisplayIndices { get => GetNonString<EquatableDictionary<string, int>>().Clone(); set => SetNonString(value); }
|
||||||
|
|
||||||
[Description("A Dictionary of GridView data property names and int indicating its column's width in ProductsGrid")]
|
[Description("A Dictionary of GridView data property names and int indicating its column's width in ProductsGrid")]
|
||||||
public Dictionary<string, int> GridColumnsWidths { get => GetNonString<Dictionary<string, int>>(); set => SetNonString(value); }
|
public Dictionary<string, int> GridColumnsWidths { get => GetNonString<EquatableDictionary<string, int>>().Clone(); set => SetNonString(value); }
|
||||||
|
|
||||||
[Description("Save cover image alongside audiobook?")]
|
[Description("Save cover image alongside audiobook?")]
|
||||||
public bool DownloadCoverArt { get => GetNonString<bool>(); set => SetNonString(value); }
|
public bool DownloadCoverArt { get => GetNonString<bool>(); set => SetNonString(value); }
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@ -151,5 +152,29 @@ namespace LibationFileManager
|
|||||||
_observers.Remove(_observer);
|
_observers.Remove(_observer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use this type in the getter for any Dictionary<TKey, TValue> settings,
|
||||||
|
* and be sure to clone it before returning. This allows Configuration to
|
||||||
|
* accurately detect if an of the Dictionary's elements have changed.
|
||||||
|
*/
|
||||||
|
private class EquatableDictionary<TKey, TValue> : Dictionary<TKey, TValue>
|
||||||
|
{
|
||||||
|
public EquatableDictionary(IEnumerable<KeyValuePair<TKey, TValue>> keyValuePairs) : base(keyValuePairs) { }
|
||||||
|
public EquatableDictionary<TKey, TValue> Clone() => new(this);
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj is Dictionary<TKey, TValue> dic && Count == dic.Count)
|
||||||
|
{
|
||||||
|
foreach (var pair in this)
|
||||||
|
if (!dic.TryGetValue(pair.Key, out var value) || !pair.Value.Equals(value))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public override int GetHashCode() => base.GetHashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
@ -33,7 +32,11 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
#region singleton stuff
|
#region singleton stuff
|
||||||
public static Configuration Instance { get; } = new Configuration();
|
public static Configuration Instance { get; } = new Configuration();
|
||||||
private Configuration() { }
|
private Configuration()
|
||||||
|
{
|
||||||
|
PropertyChanging += Configuration_PropertyChanging;
|
||||||
|
PropertyChanged += Configuration_PropertyChanged;
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
var r = replacements[i];
|
var r = replacements[i];
|
||||||
|
|
||||||
int row = dataGridView1.Rows.Add(r.CharacterToReplace.ToString(), r.ReplacementString, r.Description);
|
int row = dataGridView1.Rows.Add(r.CharacterToReplace.ToString(), r.ReplacementString, r.Description);
|
||||||
dataGridView1.Rows[row].Tag = r.Clone();
|
dataGridView1.Rows[row].Tag = r with { };
|
||||||
|
|
||||||
|
|
||||||
if (r.Mandatory)
|
if (r.Mandatory)
|
||||||
|
|||||||
@ -90,8 +90,7 @@ namespace LibationWinForms
|
|||||||
config.SetNonString(saveState, form.Name);
|
config.SetNonString(saveState, form.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
private record FormSizeAndPosition
|
||||||
class FormSizeAndPosition
|
|
||||||
{
|
{
|
||||||
public int X;
|
public int X;
|
||||||
public int Y;
|
public int Y;
|
||||||
@ -99,4 +98,5 @@ namespace LibationWinForms
|
|||||||
public int Width;
|
public int Width;
|
||||||
public bool IsMaximized;
|
public bool IsMaximized;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user