Add PropertyChanged detection for Dictionary type settings

This commit is contained in:
Michael Bucari-Tovo 2023-01-06 19:46:55 -07:00
parent 1f7000c2c9
commit 5c73beff4b
11 changed files with 211 additions and 179 deletions

View File

@ -48,18 +48,12 @@ namespace AaxDecrypter
TempFilePath = Path.ChangeExtension(jsonDownloadState, ".aaxc");
DownloadOptions = ArgumentValidator.EnsureNotNull(dlOptions, nameof(dlOptions));
DownloadOptions.PropertyChanged += DownloadOptions_PropertyChanged;
DownloadOptions.DownloadSpeedChanged += (_, speed) => InputFileStream.SpeedLimit = speed;
// delete file after validation is complete
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 virtual void SetCoverArt(byte[] coverArt)
@ -173,7 +167,7 @@ namespace AaxDecrypter
}
finally
{
if (nfsp is not null)
if (nfsp?.NetworkFileStream is not null)
nfsp.NetworkFileStream.SpeedLimit = DownloadOptions.DownloadSpeedBps;
}
}

View File

@ -1,11 +1,12 @@
using AAXClean;
using System.ComponentModel;
using System;
using System.Threading.Tasks;
namespace AaxDecrypter
{
public interface IDownloadOptions : INotifyPropertyChanged
public interface IDownloadOptions
{
event EventHandler<long> DownloadSpeedChanged;
FileManager.ReplacementCharacters ReplacementCharacters { get; }
string DownloadUrl { get; }
string UserAgent { get; }

View File

@ -5,7 +5,6 @@ using DataLayer;
using LibationFileManager;
using FileManager;
using System.Threading.Tasks;
using System.ComponentModel;
using System;
using System.IO;
using ApplicationServices;
@ -14,9 +13,7 @@ namespace FileLiberator
{
public class DownloadOptions : IDownloadOptions, IDisposable
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly IDisposable cancellation;
public event EventHandler<long> DownloadSpeedChanged;
public LibraryBook LibraryBook { get; }
public LibraryBookDto LibraryBookDto { get; }
public string DownloadUrl { get; }
@ -29,7 +26,7 @@ namespace FileLiberator
public bool StripUnabridged { get; init; }
public bool CreateCueSheet { get; init; }
public bool DownloadClipsBookmarks { get; init; }
public long DownloadSpeedBps { get; set; }
public long DownloadSpeedBps { get; init; }
public ChapterInfo ChapterInfo { get; init; }
public bool FixupFile { get; init; }
public NAudio.Lame.LameConfig LameConfig { get; init; }
@ -72,27 +69,23 @@ namespace FileLiberator
return string.Empty;
}
private void DownloadSpeedChanged(string propertyName, long speed)
{
DownloadSpeedBps = speed;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DownloadSpeedBps)));
}
public void Dispose()
{
cancellation?.Dispose();
}
private readonly IDisposable cancellation;
public void Dispose() => cancellation?.Dispose();
public DownloadOptions(LibraryBook libraryBook, string downloadUrl, string userAgent)
{
LibraryBook = ArgumentValidator.EnsureNotNull(libraryBook, nameof(libraryBook));
DownloadUrl = ArgumentValidator.EnsureNotNullOrEmpty(downloadUrl, nameof(downloadUrl));
UserAgent = ArgumentValidator.EnsureNotNullOrEmpty(userAgent, nameof(userAgent));
// no null/empty check for key/iv. unencrypted files do not have them
LibraryBookDto = LibraryBook.ToDto();
cancellation = Configuration.Instance.SubscribeToPropertyChanged<long>(nameof(Configuration.DownloadSpeedLimit), DownloadSpeedChanged);
// no null/empty check for key/iv. unencrypted files do not have them
cancellation =
Configuration.Instance
.SubscribeToPropertyChanged<long>(
nameof(Configuration.DownloadSpeedLimit),
(_, s) => DownloadSpeedChanged?.Invoke(this, s));
}
}
}
}

View File

@ -49,8 +49,10 @@ namespace FileManager
public T GetNonString<T>(string 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 (jValue.Type == JTokenType.String && typeof(T).IsAssignableTo(typeof(Enum)))
@ -62,8 +64,7 @@ namespace FileManager
}
return jValue.Value<T>();
}
if (obj is JObject jObject) return jObject.ToObject<T>();
return (T)obj;
throw new InvalidCastException($"{obj.GetType()} is not convertible to {typeof(T)}");
}
public object GetObject(string propertyName)

View File

@ -7,7 +7,7 @@ using System.Linq;
namespace FileManager
{
public class Replacement : ICloneable
public record Replacement
{
public const int FIXED_COUNT = 6;
@ -30,8 +30,6 @@ namespace FileManager
Mandatory = mandatory;
}
public object Clone() => new Replacement(CharacterToReplace, ReplacementString, Description, Mandatory);
public void Update(char charToReplace, string replacementString, string description)
{
ReplacementString = replacementString;
@ -61,10 +59,20 @@ namespace FileManager
[JsonConverter(typeof(ReplacementCharactersConverter))]
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
= IsWindows
? new()

View File

@ -101,7 +101,7 @@ namespace LibationAvalonia
}
}
class FormSizeAndPosition
private record FormSizeAndPosition
{
public int X;
public int Y;
@ -110,7 +110,6 @@ namespace LibationAvalonia
public bool IsMaximized;
}
public static void HideMinMaxBtns(this Window form)
{
if (Design.IsDesignMode || !Configuration.IsWindows)

View File

@ -10,31 +10,32 @@ using Newtonsoft.Json.Converters;
namespace LibationFileManager
{
public partial class Configuration
public partial class Configuration
{
// note: any potential file manager static ctors can't compensate if storage dir is changed at run time via settings. this is partly bad architecture. but the side effect is desirable. if changing LibationFiles location: restart app
// note: any potential file manager static ctors can't compensate if storage dir is changed at run time via settings. this is partly bad architecture. but the side effect is desirable. if changing LibationFiles location: restart app
// default setting and directory creation occur in class responsible for files.
// config class is only responsible for path. not responsible for setting defaults, dir validation, or dir creation
// exceptions: appsettings.json, LibationFiles dir, Settings.json
// default setting and directory creation occur in class responsible for files.
// config class is only responsible for path. not responsible for setting defaults, dir validation, or dir creation
// 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 object GetObject([CallerMemberName] string propertyName = "") => persistentDictionary.GetObject(propertyName);
public string GetString([CallerMemberName] string propertyName = "") => persistentDictionary.GetString(propertyName);
public void SetNonString(object newValue, [CallerMemberName] string propertyName = "")
public T GetNonString<T>([CallerMemberName] string propertyName = "") => persistentDictionary.GetNonString<T>(propertyName);
public object GetObject([CallerMemberName] string propertyName = "") => persistentDictionary.GetObject(propertyName);
public string GetString([CallerMemberName] string propertyName = "") => persistentDictionary.GetString(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;
PropertyChanging?.Invoke(this, new PropertyChangingEventArgsEx(propertyName, existing, newValue));
persistentDictionary.SetNonString(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;
PropertyChanging?.Invoke(this, new PropertyChangingEventArgsEx(propertyName, existing, newValue));
@ -42,101 +43,108 @@ namespace LibationFileManager
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>
public void SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false)
{
{
var settingWasChanged = persistentDictionary.SetWithJsonPath(jsonPath, propertyName, newValue, suppressLogging);
if (settingWasChanged)
if (settingWasChanged)
configuration?.Reload();
}
}
public string SettingsFilePath => Path.Combine(LibationFiles, "Settings.json");
public string SettingsFilePath => Path.Combine(LibationFiles, "Settings.json");
public static string GetDescription(string propertyName)
{
var attribute = typeof(Configuration)
.GetProperty(propertyName)
?.GetCustomAttributes(typeof(DescriptionAttribute), true)
.SingleOrDefault()
as DescriptionAttribute;
public static string GetDescription(string propertyName)
{
var attribute = typeof(Configuration)
.GetProperty(propertyName)
?.GetCustomAttributes(typeof(DescriptionAttribute), true)
.SingleOrDefault()
as DescriptionAttribute;
return attribute?.Description;
}
return attribute?.Description;
}
public bool Exists(string propertyName) => persistentDictionary.Exists(propertyName);
public bool Exists(string propertyName) => persistentDictionary.Exists(propertyName);
[Description("Set cover art as the folder's icon. (Windows only)")]
public bool UseCoverAsFolderIcon { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Set cover art as the folder's icon. (Windows only)")]
public bool UseCoverAsFolderIcon { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Use the beta version of Libation\r\nNew and experimental features, but probably buggy.\r\n(requires restart to take effect)")]
public bool BetaOptIn { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Use the beta version of Libation\r\nNew and experimental features, but probably buggy.\r\n(requires restart to take effect)")]
public bool BetaOptIn { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Location for book storage. Includes destination of newly liberated books")]
public string Books { get => GetString(); set => SetString(value); }
[Description("Location for book storage. Includes destination of newly liberated books")]
public string Books { get => GetString(); set => SetString(value); }
// temp/working dir(s) should be outside of dropbox
[Description("Temporary location of files while they're in process of being downloaded and decrypted.\r\nWhen decryption is complete, the final file will be in Books location\r\nRecommend not using a folder which is backed up real time. Eg: Dropbox, iCloud, Google Drive")]
public string InProgress { get => GetString(); set => SetString(value); }
// temp/working dir(s) should be outside of dropbox
[Description("Temporary location of files while they're in process of being downloaded and decrypted.\r\nWhen decryption is complete, the final file will be in Books location\r\nRecommend not using a folder which is backed up real time. Eg: Dropbox, iCloud, Google Drive")]
public string InProgress { get => GetString(); set => SetString(value); }
[Description("Allow Libation to fix up audiobook metadata")]
public bool AllowLibationFixup { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Allow Libation to fix up audiobook metadata")]
public bool AllowLibationFixup { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Create a cue sheet (.cue)")]
public bool CreateCueSheet { get => GetNonString<bool>(); set => SetNonString(value); }
public bool CreateCueSheet { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Retain the Aax file after successfully decrypting")]
public bool RetainAaxFile { get => GetNonString<bool>(); set => SetNonString(value); }
public bool RetainAaxFile { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Split my books into multiple files by chapter")]
public bool SplitFilesByChapter { get => GetNonString<bool>(); set => SetNonString(value); }
public bool SplitFilesByChapter { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Merge Opening/End Credits into the following/preceding chapters")]
public bool MergeOpeningAndEndCredits { get => GetNonString<bool>(); set => SetNonString(value); }
public bool MergeOpeningAndEndCredits { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Strip \"(Unabridged)\" from audiobook metadata tags")]
public bool StripUnabridged { get => GetNonString<bool>(); set => SetNonString(value); }
public bool StripUnabridged { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Strip audible branding from the start and end of audiobooks.\r\n(e.g. \"This is Audible\")")]
public bool StripAudibleBrandAudio { get => GetNonString<bool>(); set => SetNonString(value); }
public bool StripAudibleBrandAudio { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Decrypt to lossy format?")]
public bool DecryptToLossy { get => GetNonString<bool>(); set => SetNonString(value); }
public bool DecryptToLossy { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Lame encoder target. true = Bitrate, false = Quality")]
public bool LameTargetBitrate { get => GetNonString<bool>(); set => SetNonString(value); }
public bool LameTargetBitrate { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Lame encoder downsamples to mono")]
public bool LameDownsampleMono { get => GetNonString<bool>(); set => SetNonString(value); }
public bool LameDownsampleMono { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Lame target bitrate [16,320]")]
public int LameBitrate { get => GetNonString<int>(); set => SetNonString(value); }
[Description("Lame target bitrate [16,320]")]
public int LameBitrate { get => GetNonString<int>(); set => SetNonString(value); }
[Description("Restrict encoder to constant bitrate?")]
public bool LameConstantBitrate { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Restrict encoder to constant bitrate?")]
public bool LameConstantBitrate { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Match the source bitrate?")]
public bool LameMatchSourceBR { get => GetNonString<bool>(); set => SetNonString(value); }
public bool LameMatchSourceBR { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Lame target VBR quality [10,100]")]
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")]
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")]
public Dictionary<string, int> GridColumnsDisplayIndices { get => GetNonString<Dictionary<string, int>>(); set => SetNonString(value); }
[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<EquatableDictionary<string, int>>().Clone(); set => SetNonString(value); }
[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); }
[Description("A Dictionary of GridView data property names and int indicating its column's width in ProductsGrid")]
public Dictionary<string, int> GridColumnsWidths { get => GetNonString<EquatableDictionary<string, int>>().Clone(); set => SetNonString(value); }
[Description("Save cover image alongside audiobook?")]
public bool DownloadCoverArt { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Save cover image alongside audiobook?")]
public bool DownloadCoverArt { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Download clips and bookmarks?")]
public bool DownloadClipsBookmarks { get => GetNonString<bool>(); set => SetNonString(value); }
public bool DownloadClipsBookmarks { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("File format to save clips and bookmarks")]
public ClipBookmarkFormat ClipsBookmarksFileFormat { get => GetNonString<ClipBookmarkFormat>(); set => SetNonString(value); }
[Description("File format to save clips and bookmarks")]
public ClipBookmarkFormat ClipsBookmarksFileFormat { get => GetNonString<ClipBookmarkFormat>(); set => SetNonString(value); }
[JsonConverter(typeof(StringEnumConverter))]
public enum ClipBookmarkFormat
@ -145,100 +153,100 @@ namespace LibationFileManager
CSV,
[Description("Microsoft Excel Spreadsheet")]
Xlsx,
[Description("JavaScript Object Notation (JSON)")]
Json
}
[Description("JavaScript Object Notation (JSON)")]
Json
}
[JsonConverter(typeof(StringEnumConverter))]
public enum BadBookAction
{
[Description("Ask each time what action to take.")]
Ask = 0,
[Description("Stop processing books.")]
Abort = 1,
[Description("Retry book later. Skip for now. Continue processing books.")]
Retry = 2,
[Description("Permanently ignore book. Continue processing books. Do not try book again.")]
Ignore = 3
}
{
[Description("Ask each time what action to take.")]
Ask = 0,
[Description("Stop processing books.")]
Abort = 1,
[Description("Retry book later. Skip for now. Continue processing books.")]
Retry = 2,
[Description("Permanently ignore book. Continue processing books. Do not try book again.")]
Ignore = 3
}
[Description("When liberating books and there is an error, Libation should:")]
public BadBookAction BadBook { get => GetNonString<BadBookAction>(); set => SetNonString(value); }
[Description("When liberating books and there is an error, Libation should:")]
public BadBookAction BadBook { get => GetNonString<BadBookAction>(); set => SetNonString(value); }
[Description("Show number of newly imported titles? When unchecked, no pop-up will appear after library scan.")]
public bool ShowImportedStats { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Show number of newly imported titles? When unchecked, no pop-up will appear after library scan.")]
public bool ShowImportedStats { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Import episodes? (eg: podcasts) When unchecked, episodes will not be imported into Libation.")]
public bool ImportEpisodes { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Import episodes? (eg: podcasts) When unchecked, episodes will not be imported into Libation.")]
public bool ImportEpisodes { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Download episodes? (eg: podcasts). When unchecked, episodes already in Libation will not be downloaded.")]
public bool DownloadEpisodes { get => GetNonString<bool>(); set => SetNonString(value); }
public bool DownloadEpisodes { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Automatically run periodic scans in the background?")]
public bool AutoScan { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Automatically run periodic scans in the background?")]
public bool AutoScan { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Auto download books? After scan, download new books in 'checked' accounts.")]
// poorly named setting. Should just be 'AutoDownload'. It is NOT episode specific
public bool AutoDownloadEpisodes { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Auto download books? After scan, download new books in 'checked' accounts.")]
// poorly named setting. Should just be 'AutoDownload'. It is NOT episode specific
public bool AutoDownloadEpisodes { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Save all podcast episodes in a series to the series parent folder?")]
public bool SavePodcastsToParentFolder { get => GetNonString<bool>(); set => SetNonString(value); }
public bool SavePodcastsToParentFolder { get => GetNonString<bool>(); set => SetNonString(value); }
[Description("Global download speed limit in bytes per second.")]
public long DownloadSpeedLimit
{
get
{
var limit = GetNonString<long>();
return limit <= 0 ? 0 : Math.Max(limit, AaxDecrypter.NetworkFileStream.MIN_BYTES_PER_SECOND);
get
{
var limit = GetNonString<long>();
return limit <= 0 ? 0 : Math.Max(limit, AaxDecrypter.NetworkFileStream.MIN_BYTES_PER_SECOND);
}
set
{
set
{
var limit = value <= 0 ? 0 : Math.Max(value, AaxDecrypter.NetworkFileStream.MIN_BYTES_PER_SECOND);
SetNonString(limit);
}
}
}
#region templates: custom file naming
[Description("Edit how filename characters are replaced")]
public ReplacementCharacters ReplacementCharacters { get => GetNonString<ReplacementCharacters>(); set => SetNonString(value); }
public ReplacementCharacters ReplacementCharacters { get => GetNonString<ReplacementCharacters>(); set => SetNonString(value); }
[Description("How to format the folders in which files will be saved")]
public string FolderTemplate
{
get => getTemplate(nameof(FolderTemplate), Templates.Folder);
set => setTemplate(nameof(FolderTemplate), Templates.Folder, value);
}
[Description("How to format the folders in which files will be saved")]
public string FolderTemplate
{
get => getTemplate(nameof(FolderTemplate), Templates.Folder);
set => setTemplate(nameof(FolderTemplate), Templates.Folder, value);
}
[Description("How to format the saved pdf and audio files")]
public string FileTemplate
{
get => getTemplate(nameof(FileTemplate), Templates.File);
set => setTemplate(nameof(FileTemplate), Templates.File, value);
}
[Description("How to format the saved pdf and audio files")]
public string FileTemplate
{
get => getTemplate(nameof(FileTemplate), Templates.File);
set => setTemplate(nameof(FileTemplate), Templates.File, value);
}
[Description("How to format the saved audio files when split by chapters")]
public string ChapterFileTemplate
{
get => getTemplate(nameof(ChapterFileTemplate), Templates.ChapterFile);
set => setTemplate(nameof(ChapterFileTemplate), Templates.ChapterFile, value);
}
[Description("How to format the saved audio files when split by chapters")]
public string ChapterFileTemplate
{
get => getTemplate(nameof(ChapterFileTemplate), Templates.ChapterFile);
set => setTemplate(nameof(ChapterFileTemplate), Templates.ChapterFile, value);
}
[Description("How to format the file's Tile stored in metadata")]
public string ChapterTitleTemplate
{
get => getTemplate(nameof(ChapterTitleTemplate), Templates.ChapterTitle);
set => setTemplate(nameof(ChapterTitleTemplate), Templates.ChapterTitle, value);
}
[Description("How to format the file's Tile stored in metadata")]
public string ChapterTitleTemplate
{
get => getTemplate(nameof(ChapterTitleTemplate), Templates.ChapterTitle);
set => setTemplate(nameof(ChapterTitleTemplate), Templates.ChapterTitle, value);
}
private string getTemplate(string settingName, Templates templ) => templ.GetValid(GetString(settingName));
private void setTemplate(string settingName, Templates templ, string newValue)
{
var template = newValue?.Trim();
if (templ.IsValid(template))
SetString(template, settingName);
}
#endregion
}
private string getTemplate(string settingName, Templates templ) => templ.GetValid(GetString(settingName));
private void setTemplate(string settingName, Templates templ, string newValue)
{
var template = newValue?.Trim();
if (templ.IsValid(template))
SetString(template, settingName);
}
#endregion
}
}

View File

@ -1,4 +1,5 @@
using Dinah.Core;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@ -151,5 +152,29 @@ namespace LibationFileManager
_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();
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dinah.Core;
@ -33,7 +32,11 @@ namespace LibationFileManager
#region singleton stuff
public static Configuration Instance { get; } = new Configuration();
private Configuration() { }
private Configuration()
{
PropertyChanging += Configuration_PropertyChanging;
PropertyChanged += Configuration_PropertyChanged;
}
#endregion
}
}

View File

@ -30,7 +30,7 @@ namespace LibationWinForms.Dialogs
var r = replacements[i];
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)

View File

@ -90,13 +90,13 @@ namespace LibationWinForms
config.SetNonString(saveState, form.Name);
}
}
class FormSizeAndPosition
{
public int X;
public int Y;
public int Height;
public int Width;
public bool IsMaximized;
private record FormSizeAndPosition
{
public int X;
public int Y;
public int Height;
public int Width;
public bool IsMaximized;
}
}
}