Add property changed filtering events to Configuration
This commit is contained in:
parent
5c73beff4b
commit
4725fe36d1
@ -81,11 +81,10 @@ namespace FileLiberator
|
|||||||
|
|
||||||
LibraryBookDto = LibraryBook.ToDto();
|
LibraryBookDto = LibraryBook.ToDto();
|
||||||
|
|
||||||
cancellation =
|
cancellation = Configuration.Instance
|
||||||
Configuration.Instance
|
.ObservePropertyChanged<long>(
|
||||||
.SubscribeToPropertyChanged<long>(
|
nameof(Configuration.DownloadSpeedLimit),
|
||||||
nameof(Configuration.DownloadSpeedLimit),
|
(_, s) => DownloadSpeedChanged?.Invoke(this, s));
|
||||||
(_, s) => DownloadSpeedChanged?.Invoke(this, s));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="620"
|
mc:Ignorable="d" d:DesignWidth="850" d:DesignHeight="620"
|
||||||
MinWidth="800" MinHeight="620"
|
MinWidth="800" MinHeight="620"
|
||||||
x:Class="LibationAvalonia.Dialogs.SettingsDialog"
|
x:Class="LibationAvalonia.Dialogs.SettingsDialog"
|
||||||
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
|
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
|
||||||
@ -102,7 +102,7 @@
|
|||||||
Click="OpenLogFolderButton_Click" />
|
Click="OpenLogFolderButton_Click" />
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<!--
|
<!--
|
||||||
<CheckBox
|
<CheckBox
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
@ -357,20 +357,20 @@
|
|||||||
BorderWidth="1"
|
BorderWidth="1"
|
||||||
Label="Temporary Files Location">
|
Label="Temporary Files Location">
|
||||||
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Margin="5" >
|
Margin="5" >
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="0,0,0,10"
|
Margin="0,0,0,10"
|
||||||
Text="{Binding DownloadDecryptSettings.InProgressDescriptionText}" />
|
Text="{Binding DownloadDecryptSettings.InProgressDescriptionText}" />
|
||||||
|
|
||||||
<controls:DirectoryOrCustomSelectControl
|
<controls:DirectoryOrCustomSelectControl
|
||||||
SubDirectory="Libation"
|
SubDirectory="Libation"
|
||||||
Directory="{Binding DownloadDecryptSettings.InProgressDirectory, Mode=TwoWay}"
|
Directory="{Binding DownloadDecryptSettings.InProgressDirectory, Mode=TwoWay}"
|
||||||
KnownDirectories="{Binding DownloadDecryptSettings.KnownDirectories}" />
|
KnownDirectories="{Binding DownloadDecryptSettings.KnownDirectories}" />
|
||||||
|
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</controls:GroupBox>
|
</controls:GroupBox>
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
@ -436,6 +436,26 @@
|
|||||||
|
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
Margin="0,0,0,5"
|
||||||
|
IsChecked="{Binding AudioSettings.DownloadClipsBookmarks, Mode=TwoWay}">
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Text="Download Clips, Notes and Bookmarks as" />
|
||||||
|
|
||||||
|
</CheckBox>
|
||||||
|
|
||||||
|
<controls:WheelComboBox
|
||||||
|
Margin="5,0,0,0"
|
||||||
|
IsEnabled="{Binding AudioSettings.DownloadClipsBookmarks}"
|
||||||
|
Items="{Binding AudioSettings.ClipBookmarkFormats}"
|
||||||
|
SelectedItem="{Binding AudioSettings.ClipBookmarkFormat}"/>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
Margin="0,0,0,5"
|
Margin="0,0,0,5"
|
||||||
IsChecked="{Binding AudioSettings.RetainAaxFile, Mode=TwoWay}">
|
IsChecked="{Binding AudioSettings.RetainAaxFile, Mode=TwoWay}">
|
||||||
|
|||||||
@ -10,6 +10,7 @@ using Dinah.Core;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FileManager;
|
using FileManager;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Avalonia.Collections;
|
||||||
|
|
||||||
namespace LibationAvalonia.Dialogs
|
namespace LibationAvalonia.Dialogs
|
||||||
{
|
{
|
||||||
@ -381,6 +382,7 @@ namespace LibationAvalonia.Dialogs
|
|||||||
public class AudioSettings : ViewModels.ViewModelBase, ISettingsDisplay
|
public class AudioSettings : ViewModels.ViewModelBase, ISettingsDisplay
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private bool _downloadClipsBookmarks;
|
||||||
private bool _splitFilesByChapter;
|
private bool _splitFilesByChapter;
|
||||||
private bool _allowLibationFixup;
|
private bool _allowLibationFixup;
|
||||||
private bool _lameTargetBitrate;
|
private bool _lameTargetBitrate;
|
||||||
@ -401,6 +403,8 @@ namespace LibationAvalonia.Dialogs
|
|||||||
AllowLibationFixup = config.AllowLibationFixup;
|
AllowLibationFixup = config.AllowLibationFixup;
|
||||||
DownloadCoverArt = config.DownloadCoverArt;
|
DownloadCoverArt = config.DownloadCoverArt;
|
||||||
RetainAaxFile = config.RetainAaxFile;
|
RetainAaxFile = config.RetainAaxFile;
|
||||||
|
DownloadClipsBookmarks = config.DownloadClipsBookmarks;
|
||||||
|
ClipBookmarkFormat = config.ClipsBookmarksFileFormat;
|
||||||
SplitFilesByChapter = config.SplitFilesByChapter;
|
SplitFilesByChapter = config.SplitFilesByChapter;
|
||||||
MergeOpeningAndEndCredits = config.MergeOpeningAndEndCredits;
|
MergeOpeningAndEndCredits = config.MergeOpeningAndEndCredits;
|
||||||
StripAudibleBrandAudio = config.StripAudibleBrandAudio;
|
StripAudibleBrandAudio = config.StripAudibleBrandAudio;
|
||||||
@ -421,6 +425,8 @@ namespace LibationAvalonia.Dialogs
|
|||||||
config.AllowLibationFixup = AllowLibationFixup;
|
config.AllowLibationFixup = AllowLibationFixup;
|
||||||
config.DownloadCoverArt = DownloadCoverArt;
|
config.DownloadCoverArt = DownloadCoverArt;
|
||||||
config.RetainAaxFile = RetainAaxFile;
|
config.RetainAaxFile = RetainAaxFile;
|
||||||
|
config.DownloadClipsBookmarks = DownloadClipsBookmarks;
|
||||||
|
config.ClipsBookmarksFileFormat = ClipBookmarkFormat;
|
||||||
config.SplitFilesByChapter = SplitFilesByChapter;
|
config.SplitFilesByChapter = SplitFilesByChapter;
|
||||||
config.MergeOpeningAndEndCredits = MergeOpeningAndEndCredits;
|
config.MergeOpeningAndEndCredits = MergeOpeningAndEndCredits;
|
||||||
config.StripAudibleBrandAudio = StripAudibleBrandAudio;
|
config.StripAudibleBrandAudio = StripAudibleBrandAudio;
|
||||||
@ -437,6 +443,7 @@ namespace LibationAvalonia.Dialogs
|
|||||||
return Task.FromResult(true);
|
return Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AvaloniaList<Configuration.ClipBookmarkFormat> ClipBookmarkFormats { get; } = new(Enum<Configuration.ClipBookmarkFormat>.GetValues());
|
||||||
public string CreateCueSheetText { get; } = Configuration.GetDescription(nameof(Configuration.CreateCueSheet));
|
public string CreateCueSheetText { get; } = Configuration.GetDescription(nameof(Configuration.CreateCueSheet));
|
||||||
public string AllowLibationFixupText { get; } = Configuration.GetDescription(nameof(Configuration.AllowLibationFixup));
|
public string AllowLibationFixupText { get; } = Configuration.GetDescription(nameof(Configuration.AllowLibationFixup));
|
||||||
public string DownloadCoverArtText { get; } = Configuration.GetDescription(nameof(Configuration.DownloadCoverArt));
|
public string DownloadCoverArtText { get; } = Configuration.GetDescription(nameof(Configuration.DownloadCoverArt));
|
||||||
@ -450,6 +457,8 @@ namespace LibationAvalonia.Dialogs
|
|||||||
public bool CreateCueSheet { get; set; }
|
public bool CreateCueSheet { get; set; }
|
||||||
public bool DownloadCoverArt { get; set; }
|
public bool DownloadCoverArt { get; set; }
|
||||||
public bool RetainAaxFile { get; set; }
|
public bool RetainAaxFile { get; set; }
|
||||||
|
public bool DownloadClipsBookmarks { get => _downloadClipsBookmarks; set => this.RaiseAndSetIfChanged(ref _downloadClipsBookmarks, value); }
|
||||||
|
public Configuration.ClipBookmarkFormat ClipBookmarkFormat { get; set; }
|
||||||
public bool MergeOpeningAndEndCredits { get; set; }
|
public bool MergeOpeningAndEndCredits { get; set; }
|
||||||
public bool StripAudibleBrandAudio { get; set; }
|
public bool StripAudibleBrandAudio { get; set; }
|
||||||
public bool StripUnabridged { get; set; }
|
public bool StripUnabridged { get; set; }
|
||||||
|
|||||||
@ -55,11 +55,7 @@ namespace LibationAvalonia.Views
|
|||||||
AccountsSettingsPersister.Saved += accountsPostSave;
|
AccountsSettingsPersister.Saved += accountsPostSave;
|
||||||
|
|
||||||
// when autoscan setting is changed, update menu checkbox and run autoscan
|
// when autoscan setting is changed, update menu checkbox and run autoscan
|
||||||
Configuration.Instance.PropertyChanged += (_, e) =>
|
Configuration.Instance.PropertyChanged += startAutoScan;
|
||||||
{
|
|
||||||
if (e.PropertyName == nameof(Configuration.Instance.AutoScan))
|
|
||||||
startAutoScan();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts;
|
private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts;
|
||||||
@ -83,9 +79,11 @@ namespace LibationAvalonia.Views
|
|||||||
startAutoScan();
|
startAutoScan();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[PropertyChangeFilter(nameof(Configuration.AutoScan))]
|
||||||
private void startAutoScan(object sender = null, EventArgs e = null)
|
private void startAutoScan(object sender = null, EventArgs e = null)
|
||||||
{
|
{
|
||||||
if (Configuration.Instance.AutoScan)
|
_viewModel.AutoScanChecked = Configuration.Instance.AutoScan;
|
||||||
|
if (_viewModel.AutoScanChecked)
|
||||||
autoScanTimer.PerformNow();
|
autoScanTimer.PerformNow();
|
||||||
else
|
else
|
||||||
autoScanTimer.Stop();
|
autoScanTimer.Stop();
|
||||||
|
|||||||
@ -36,7 +36,7 @@ namespace LibationFileManager
|
|||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
PropertyChanging?.Invoke(this, new PropertyChangingEventArgsEx(nameof(LogLevel), LogLevel, value));
|
OnPropertyChanging(nameof(LogLevel), LogLevel, value);
|
||||||
var valueWasChanged = persistentDictionary.SetWithJsonPath("Serilog", "MinimumLevel", value.ToString());
|
var valueWasChanged = persistentDictionary.SetWithJsonPath("Serilog", "MinimumLevel", value.ToString());
|
||||||
if (!valueWasChanged)
|
if (!valueWasChanged)
|
||||||
{
|
{
|
||||||
@ -46,7 +46,7 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
configuration.Reload();
|
configuration.Reload();
|
||||||
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgsEx(nameof(LogLevel), value));
|
OnPropertyChanged(nameof(LogLevel), value);
|
||||||
|
|
||||||
Log.Logger.Information("Updated LogLevel MinimumLevel. {@DebugInfo}", new
|
Log.Logger.Information("Updated LogLevel MinimumLevel. {@DebugInfo}", new
|
||||||
{
|
{
|
||||||
|
|||||||
@ -28,9 +28,9 @@ namespace LibationFileManager
|
|||||||
var existing = getExistingValue(propertyName);
|
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));
|
OnPropertyChanging(propertyName, existing, newValue);
|
||||||
persistentDictionary.SetNonString(propertyName, newValue);
|
persistentDictionary.SetNonString(propertyName, newValue);
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgsEx(propertyName, newValue));
|
OnPropertyChanged(propertyName, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetString(string newValue, [CallerMemberName] string propertyName = "")
|
public void SetString(string newValue, [CallerMemberName] string propertyName = "")
|
||||||
@ -38,9 +38,9 @@ namespace LibationFileManager
|
|||||||
var existing = getExistingValue(propertyName);
|
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));
|
OnPropertyChanging(propertyName, existing, newValue);
|
||||||
persistentDictionary.SetString(propertyName, newValue);
|
persistentDictionary.SetString(propertyName, newValue);
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgsEx(propertyName, newValue));
|
OnPropertyChanged(propertyName, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private object getExistingValue(string propertyName)
|
private object getExistingValue(string propertyName)
|
||||||
|
|||||||
@ -1,158 +1,9 @@
|
|||||||
using Dinah.Core;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public partial class Configuration : INotifyPropertyChanging, INotifyPropertyChanged
|
public partial class Configuration : PropertyChangeFilter
|
||||||
{
|
{
|
||||||
public event PropertyChangingEventHandler PropertyChanging;
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
private readonly Dictionary<string, List<MulticastDelegate>> propertyChangedActions = new();
|
|
||||||
private readonly Dictionary<string, List<MulticastDelegate>> propertyChangingActions = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clear all subscriptions to Property<b>Changed</b> for <paramref name="propertyName"/>
|
|
||||||
/// </summary>
|
|
||||||
public void ClearChangedSubscriptions(string propertyName)
|
|
||||||
{
|
|
||||||
if (propertyChangedActions.ContainsKey(propertyName)
|
|
||||||
&& propertyChangedActions[propertyName] is not null)
|
|
||||||
propertyChangedActions[propertyName].Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clear all subscriptions to Property<b>Changing</b> for <paramref name="propertyName"/>
|
|
||||||
/// </summary>
|
|
||||||
public void ClearChangingSubscriptions(string propertyName)
|
|
||||||
{
|
|
||||||
if (propertyChangingActions.ContainsKey(propertyName)
|
|
||||||
&& propertyChangingActions[propertyName] is not null)
|
|
||||||
propertyChangingActions[propertyName].Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add an action to be executed when a property's value has changed
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The <paramref name="propertyName"/>'s <see cref="Type"/></typeparam>
|
|
||||||
/// <param name="propertyName">Name of the property whose change triggers the <paramref name="action"/></param>
|
|
||||||
/// <param name="action">Action to be executed with parameters: <paramref name="propertyName"/> and <strong>NewValue</strong></param>
|
|
||||||
/// <returns>A reference to an interface that allows observers to stop receiving notifications before the provider has finished sending them.</returns>
|
|
||||||
public IDisposable SubscribeToPropertyChanged<T>(string propertyName, Action<string, T> action)
|
|
||||||
{
|
|
||||||
validateSubscriber<T>(propertyName, action);
|
|
||||||
|
|
||||||
if (!propertyChangedActions.ContainsKey(propertyName))
|
|
||||||
propertyChangedActions.Add(propertyName, new List<MulticastDelegate>());
|
|
||||||
|
|
||||||
var actionlist = propertyChangedActions[propertyName];
|
|
||||||
|
|
||||||
if (!actionlist.Contains(action))
|
|
||||||
actionlist.Add(action);
|
|
||||||
|
|
||||||
return new Unsubscriber(actionlist, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add an action to be executed when a property's value is changing
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The <paramref name="propertyName"/>'s <see cref="Type"/></typeparam>
|
|
||||||
/// <param name="propertyName">Name of the property whose change triggers the <paramref name="action"/></param>
|
|
||||||
/// <param name="action">Action to be executed with parameters: <paramref name="propertyName"/>, <b>OldValue</b>, and <b>NewValue</b></param>
|
|
||||||
/// <returns>A reference to an interface that allows observers to stop receiving notifications before the provider has finished sending them.</returns>
|
|
||||||
public IDisposable SubscribeToPropertyChanging<T>(string propertyName, Action<string, T, T> action)
|
|
||||||
{
|
|
||||||
validateSubscriber<T>(propertyName, action);
|
|
||||||
|
|
||||||
if (!propertyChangingActions.ContainsKey(propertyName))
|
|
||||||
propertyChangingActions.Add(propertyName, new List<MulticastDelegate>());
|
|
||||||
|
|
||||||
var actionlist = propertyChangingActions[propertyName];
|
|
||||||
|
|
||||||
if (!actionlist.Contains(action))
|
|
||||||
actionlist.Add(action);
|
|
||||||
|
|
||||||
return new Unsubscriber(actionlist, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateSubscriber<T>(string propertyName, MulticastDelegate action)
|
|
||||||
{
|
|
||||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(propertyName, nameof(propertyName));
|
|
||||||
ArgumentValidator.EnsureNotNull(action, nameof(action));
|
|
||||||
|
|
||||||
var propertyInfo = GetType().GetProperty(propertyName);
|
|
||||||
|
|
||||||
if (propertyInfo is null)
|
|
||||||
throw new MissingMemberException($"{nameof(Configuration)}.{propertyName} does not exist.");
|
|
||||||
|
|
||||||
if (propertyInfo.PropertyType != typeof(T))
|
|
||||||
throw new InvalidCastException($"{nameof(Configuration)}.{propertyName} is {propertyInfo.PropertyType}, but parameter is {typeof(T)}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Configuration_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (e is PropertyChangedEventArgsEx args && propertyChangedActions.ContainsKey(args.PropertyName))
|
|
||||||
{
|
|
||||||
foreach (var action in propertyChangedActions[args.PropertyName])
|
|
||||||
{
|
|
||||||
action.DynamicInvoke(args.PropertyName, args.NewValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Configuration_PropertyChanging(object sender, PropertyChangingEventArgs e)
|
|
||||||
{
|
|
||||||
if (e is PropertyChangingEventArgsEx args && propertyChangingActions.ContainsKey(args.PropertyName))
|
|
||||||
{
|
|
||||||
foreach (var action in propertyChangingActions[args.PropertyName])
|
|
||||||
{
|
|
||||||
action.DynamicInvoke(args.PropertyName, args.OldValue, args.NewValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PropertyChangingEventArgsEx : PropertyChangingEventArgs
|
|
||||||
{
|
|
||||||
public object OldValue { get; }
|
|
||||||
public object NewValue { get; }
|
|
||||||
|
|
||||||
public PropertyChangingEventArgsEx(string propertyName, object oldValue, object newValue) : base(propertyName)
|
|
||||||
{
|
|
||||||
OldValue = oldValue;
|
|
||||||
NewValue = newValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PropertyChangedEventArgsEx : PropertyChangedEventArgs
|
|
||||||
{
|
|
||||||
public object NewValue { get; }
|
|
||||||
|
|
||||||
public PropertyChangedEventArgsEx(string propertyName, object newValue) : base(propertyName)
|
|
||||||
{
|
|
||||||
NewValue = newValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Unsubscriber : IDisposable
|
|
||||||
{
|
|
||||||
private List<MulticastDelegate> _observers;
|
|
||||||
private MulticastDelegate _observer;
|
|
||||||
|
|
||||||
internal Unsubscriber(List<MulticastDelegate> observers, MulticastDelegate observer)
|
|
||||||
{
|
|
||||||
_observers = observers;
|
|
||||||
_observer = observer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_observers.Contains(_observer))
|
|
||||||
_observers.Remove(_observer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Use this type in the getter for any Dictionary<TKey, TValue> settings,
|
* Use this type in the getter for any Dictionary<TKey, TValue> settings,
|
||||||
* and be sure to clone it before returning. This allows Configuration to
|
* and be sure to clone it before returning. This allows Configuration to
|
||||||
|
|||||||
@ -32,11 +32,7 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
331
Source/LibationFileManager/PropertyChangeFilter.cs
Normal file
331
Source/LibationFileManager/PropertyChangeFilter.cs
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
using Dinah.Core;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace LibationFileManager
|
||||||
|
{
|
||||||
|
#region Useage
|
||||||
|
|
||||||
|
/*
|
||||||
|
* USEAGE
|
||||||
|
|
||||||
|
*************************
|
||||||
|
* *
|
||||||
|
* Event Filter Mode *
|
||||||
|
* *
|
||||||
|
*************************
|
||||||
|
|
||||||
|
|
||||||
|
propertyChangeFilter.PropertyChanged += MyPropertiesChanged;
|
||||||
|
|
||||||
|
[PropertyChangeFilter("MyProperty1")]
|
||||||
|
[PropertyChangeFilter("MyProperty2")]
|
||||||
|
void MyPropertiesChanged(object sender, PropertyChangedEventArgsEx e)
|
||||||
|
{
|
||||||
|
// Only properties whose names match either "MyProperty1"
|
||||||
|
// or "MyProperty2" will fire this event handler.
|
||||||
|
}
|
||||||
|
|
||||||
|
******
|
||||||
|
* OR *
|
||||||
|
******
|
||||||
|
|
||||||
|
propertyChangeFilter.PropertyChanged +=
|
||||||
|
[PropertyChangeFilter("MyProperty1")]
|
||||||
|
[PropertyChangeFilter("MyProperty2")]
|
||||||
|
(_, _) =>
|
||||||
|
{
|
||||||
|
// Only properties whose names match either "MyProperty1"
|
||||||
|
// or "MyProperty2" will fire this event handler.
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
*************************
|
||||||
|
* *
|
||||||
|
* Observable Mode *
|
||||||
|
* *
|
||||||
|
*************************
|
||||||
|
|
||||||
|
using var cancellation = propertyChangeFilter.ObservePropertyChanging<int>("MyProperty", MyPropertyChanging);
|
||||||
|
|
||||||
|
void MyPropertyChanging(string propertyName, int oldValue, int newValue)
|
||||||
|
{
|
||||||
|
// Only the property whose name match
|
||||||
|
// "MyProperty" will fire this method.
|
||||||
|
}
|
||||||
|
|
||||||
|
//The observer is delisted when cancellation is disposed
|
||||||
|
|
||||||
|
******
|
||||||
|
* OR *
|
||||||
|
******
|
||||||
|
|
||||||
|
using var cancellation = propertyChangeFilter.ObservePropertyChanged<bool>("MyProperty", (_, s) =>
|
||||||
|
{
|
||||||
|
// Only the property whose name match
|
||||||
|
// "MyProperty" will fire this action.
|
||||||
|
});
|
||||||
|
|
||||||
|
//The observer is delisted when cancellation is disposed
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public abstract class PropertyChangeFilter
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, List<Delegate>> propertyChangedActions = new();
|
||||||
|
private readonly Dictionary<string, List<Delegate>> propertyChangingActions = new();
|
||||||
|
|
||||||
|
private readonly List<KeyValuePair<PropertyChangedEventHandlerEx, PropertyChangedEventHandlerEx>> changedFilters = new();
|
||||||
|
private readonly List<KeyValuePair<PropertyChangingEventHandlerEx, PropertyChangingEventHandlerEx>> changingFilters = new();
|
||||||
|
|
||||||
|
public PropertyChangeFilter()
|
||||||
|
{
|
||||||
|
PropertyChanging += Configuration_PropertyChanging;
|
||||||
|
PropertyChanged += Configuration_PropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Events
|
||||||
|
|
||||||
|
protected void OnPropertyChanged(string propertyName, object newValue)
|
||||||
|
=> _propertyChanged?.Invoke(this, new(propertyName, newValue));
|
||||||
|
protected void OnPropertyChanging(string propertyName, object oldValue, object newValue)
|
||||||
|
=> _propertyChanging?.Invoke(this, new(propertyName, oldValue, newValue));
|
||||||
|
|
||||||
|
private PropertyChangedEventHandlerEx _propertyChanged;
|
||||||
|
private PropertyChangingEventHandlerEx _propertyChanging;
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandlerEx PropertyChanged
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
var attributes = Attribute.GetCustomAttributes(value.Method, typeof(PropertyChangeFilterAttribute)) as PropertyChangeFilterAttribute[];
|
||||||
|
|
||||||
|
if (attributes.Any())
|
||||||
|
{
|
||||||
|
var matches = attributes.Select(a => a.PropertyName).ToArray();
|
||||||
|
|
||||||
|
void filterer(object s, PropertyChangedEventArgsEx e)
|
||||||
|
{
|
||||||
|
if (e.PropertyName.In(matches)) value(s, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
changedFilters.Add(new(value, filterer));
|
||||||
|
|
||||||
|
_propertyChanged += filterer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_propertyChanged += value;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
var del = changedFilters.LastOrDefault(d => d.Key == value);
|
||||||
|
if (del.Key is null)
|
||||||
|
_propertyChanged -= value;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_propertyChanged -= del.Value;
|
||||||
|
changedFilters.Remove(del);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangingEventHandlerEx PropertyChanging
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
var attributes = Attribute.GetCustomAttributes(value.Method, typeof(PropertyChangeFilterAttribute)) as PropertyChangeFilterAttribute[];
|
||||||
|
|
||||||
|
if (attributes.Any())
|
||||||
|
{
|
||||||
|
var matches = attributes.Select(a => a.PropertyName).ToArray();
|
||||||
|
|
||||||
|
void filterer(object s, PropertyChangingEventArgsEx e)
|
||||||
|
{
|
||||||
|
if (e.PropertyName.In(matches)) value(s, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
changingFilters.Add(new(value, filterer));
|
||||||
|
|
||||||
|
_propertyChanging += filterer;
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_propertyChanging += value;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
var del = changingFilters.LastOrDefault(d => d.Key == value);
|
||||||
|
if (del.Key is null)
|
||||||
|
_propertyChanging -= value;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_propertyChanging -= del.Value;
|
||||||
|
changingFilters.Remove(del);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Observables
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear all subscriptions to Property<b>Changed</b> for <paramref name="propertyName"/>
|
||||||
|
/// </summary>
|
||||||
|
public void ClearChangedSubscriptions(string propertyName)
|
||||||
|
{
|
||||||
|
if (propertyChangedActions.ContainsKey(propertyName)
|
||||||
|
&& propertyChangedActions[propertyName] is not null)
|
||||||
|
propertyChangedActions[propertyName].Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear all subscriptions to Property<b>Changing</b> for <paramref name="propertyName"/>
|
||||||
|
/// </summary>
|
||||||
|
public void ClearChangingSubscriptions(string propertyName)
|
||||||
|
{
|
||||||
|
if (propertyChangingActions.ContainsKey(propertyName)
|
||||||
|
&& propertyChangingActions[propertyName] is not null)
|
||||||
|
propertyChangingActions[propertyName].Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an action to be executed when a property's value has changed
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The <paramref name="propertyName"/>'s <see cref="Type"/></typeparam>
|
||||||
|
/// <param name="propertyName">Name of the property whose change triggers the <paramref name="action"/></param>
|
||||||
|
/// <param name="action">Action to be executed with parameters: <paramref name="propertyName"/> and <strong>NewValue</strong></param>
|
||||||
|
/// <returns>A reference to an interface that allows observers to stop receiving notifications before the provider has finished sending them.</returns>
|
||||||
|
public IDisposable ObservePropertyChanged<T>(string propertyName, Action<string, T> action)
|
||||||
|
{
|
||||||
|
validateSubscriber<T>(propertyName, action);
|
||||||
|
|
||||||
|
if (!propertyChangedActions.ContainsKey(propertyName))
|
||||||
|
propertyChangedActions.Add(propertyName, new List<Delegate>());
|
||||||
|
|
||||||
|
var actionlist = propertyChangedActions[propertyName];
|
||||||
|
|
||||||
|
if (!actionlist.Contains(action))
|
||||||
|
actionlist.Add(action);
|
||||||
|
|
||||||
|
return new Unsubscriber(actionlist, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an action to be executed when a property's value is changing
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The <paramref name="propertyName"/>'s <see cref="Type"/></typeparam>
|
||||||
|
/// <param name="propertyName">Name of the property whose change triggers the <paramref name="action"/></param>
|
||||||
|
/// <param name="action">Action to be executed with parameters: <paramref name="propertyName"/>, <b>OldValue</b>, and <b>NewValue</b></param>
|
||||||
|
/// <returns>A reference to an interface that allows observers to stop receiving notifications before the provider has finished sending them.</returns>
|
||||||
|
public IDisposable ObservePropertyChanging<T>(string propertyName, Action<string, T, T> action)
|
||||||
|
{
|
||||||
|
validateSubscriber<T>(propertyName, action);
|
||||||
|
|
||||||
|
if (!propertyChangingActions.ContainsKey(propertyName))
|
||||||
|
propertyChangingActions.Add(propertyName, new List<Delegate>());
|
||||||
|
|
||||||
|
var actionlist = propertyChangingActions[propertyName];
|
||||||
|
|
||||||
|
if (!actionlist.Contains(action))
|
||||||
|
actionlist.Add(action);
|
||||||
|
|
||||||
|
return new Unsubscriber(actionlist, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateSubscriber<T>(string propertyName, Delegate action)
|
||||||
|
{
|
||||||
|
ArgumentValidator.EnsureNotNullOrWhiteSpace(propertyName, nameof(propertyName));
|
||||||
|
ArgumentValidator.EnsureNotNull(action, nameof(action));
|
||||||
|
|
||||||
|
var propertyInfo = GetType().GetProperty(propertyName);
|
||||||
|
|
||||||
|
if (propertyInfo is null)
|
||||||
|
throw new MissingMemberException($"{nameof(Configuration)}.{propertyName} does not exist.");
|
||||||
|
|
||||||
|
if (propertyInfo.PropertyType != typeof(T))
|
||||||
|
throw new InvalidCastException($"{nameof(Configuration)}.{propertyName} is {propertyInfo.PropertyType}, but parameter is {typeof(T)}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Configuration_PropertyChanged(object sender, PropertyChangedEventArgsEx e)
|
||||||
|
{
|
||||||
|
if (propertyChangedActions.ContainsKey(e.PropertyName))
|
||||||
|
{
|
||||||
|
foreach (var action in propertyChangedActions[e.PropertyName])
|
||||||
|
{
|
||||||
|
action.DynamicInvoke(e.PropertyName, e.NewValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Configuration_PropertyChanging(object sender, PropertyChangingEventArgsEx e)
|
||||||
|
{
|
||||||
|
if (propertyChangingActions.ContainsKey(e.PropertyName))
|
||||||
|
{
|
||||||
|
foreach (var action in propertyChangingActions[e.PropertyName])
|
||||||
|
{
|
||||||
|
action.DynamicInvoke(e.PropertyName, e.OldValue, e.NewValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Unsubscriber : IDisposable
|
||||||
|
{
|
||||||
|
private List<Delegate> _observers;
|
||||||
|
private Delegate _observer;
|
||||||
|
|
||||||
|
internal Unsubscriber(List<Delegate> observers, Delegate observer)
|
||||||
|
{
|
||||||
|
_observers = observers;
|
||||||
|
_observer = observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_observers.Contains(_observer))
|
||||||
|
_observers.Remove(_observer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void PropertyChangedEventHandlerEx(object sender, PropertyChangedEventArgsEx e);
|
||||||
|
public delegate void PropertyChangingEventHandlerEx(object sender, PropertyChangingEventArgsEx e);
|
||||||
|
|
||||||
|
public class PropertyChangedEventArgsEx : PropertyChangedEventArgs
|
||||||
|
{
|
||||||
|
public object NewValue { get; }
|
||||||
|
|
||||||
|
public PropertyChangedEventArgsEx(string propertyName, object newValue) : base(propertyName)
|
||||||
|
{
|
||||||
|
NewValue = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropertyChangingEventArgsEx : PropertyChangingEventArgs
|
||||||
|
{
|
||||||
|
public object OldValue { get; }
|
||||||
|
public object NewValue { get; }
|
||||||
|
|
||||||
|
public PropertyChangingEventArgsEx(string propertyName, object oldValue, object newValue) : base(propertyName)
|
||||||
|
{
|
||||||
|
OldValue = oldValue;
|
||||||
|
NewValue = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||||
|
public class PropertyChangeFilterAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string PropertyName { get; }
|
||||||
|
public PropertyChangeFilterAttribute(string propertyName)
|
||||||
|
{
|
||||||
|
PropertyName = propertyName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -59,14 +59,13 @@ namespace LibationWinForms
|
|||||||
Configuration.Instance.PropertyChanged += Configuration_PropertyChanged;
|
Configuration.Instance.PropertyChanged += Configuration_PropertyChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Configuration_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
|
||||||
|
[PropertyChangeFilter(nameof(Configuration.AutoScan))]
|
||||||
|
private void Configuration_PropertyChanged(object sender, PropertyChangedEventArgsEx e)
|
||||||
{
|
{
|
||||||
if (e.PropertyName == nameof(Configuration.Instance.AutoScan))
|
// when autoscan setting is changed, update menu checkbox and run autoscan
|
||||||
{
|
updateAutoScanLibraryToolStripMenuItem(sender, e);
|
||||||
// when autoscan setting is changed, update menu checkbox and run autoscan
|
startAutoScan(sender, e);
|
||||||
updateAutoScanLibraryToolStripMenuItem(sender, e);
|
|
||||||
startAutoScan(sender, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts;
|
private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user