🐛 fix SettingsProperty

This commit is contained in:
Aigio Liu
2023-05-23 13:18:17 +08:00
parent 51738f8093
commit 962aa5ae36
4 changed files with 180 additions and 47 deletions
@@ -1,4 +1,6 @@
// ReSharper disable once CheckNamespace
using Polly;
namespace BD.WTTS.Settings.Abstractions;
public interface ISettings
@@ -51,6 +53,13 @@ public interface ISettings
protected static readonly HashSet<Type> types = new();
private static readonly Lazy<ConcurrentDictionary<Type, bool>> lazy_save_status = new(() =>
{
return new(types.Select(x => new KeyValuePair<Type, bool>(x, false)));
});
static ConcurrentDictionary<Type, bool> SaveStatus => lazy_save_status.Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void SaveSettings([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type)
{
@@ -257,41 +266,103 @@ internal static class SettingsExtensions
var settings = optionsMonitor.CurrentValue;
var settingsFilePath = ISettings.GetFilePath(TSettings.Name);
if (notRead != bool.TrueString) // 通过读取配置文件与内存中的配置进行比较
lock (TSettings.Name)
{
ISettings.SaveStatus[typeof(TSettings)] = true;
try
{
if (File.Exists(settingsFilePath))
if (notRead != bool.TrueString) // 通过读取配置文件与内存中的配置进行比较
{
JsonObject? jobj;
var options = ISettings.GetDefaultOptions();
using var readStream = File.OpenRead(settingsFilePath);
jobj = JsonSerializer.Deserialize<JsonObject>(readStream, options);
if (jobj != null)
try
{
var jnode = jobj[TSettings.Name];
if (jnode != null)
if (File.Exists(settingsFilePath))
{
options = ISettings.GetDefaultOptions();
options.TypeInfoResolver = ISettings.JsonTypeInfoResolver.Instance;
var settingsByRead = JsonSerializer.Deserialize<TSettings>(jnode, options);
JsonObject? jobj;
var options = ISettings.GetDefaultOptions();
using var readStream = new FileStream(
settingsFilePath,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite | FileShare.Delete);
jobj = JsonSerializer.Deserialize<JsonObject>(readStream, options);
if (jobj != null)
{
var jnode = jobj[TSettings.Name];
if (jnode != null)
{
options = ISettings.GetDefaultOptions();
options.TypeInfoResolver = ISettings.JsonTypeInfoResolver.Instance;
var settingsByRead = JsonSerializer.Deserialize<TSettings>(jnode, options);
// 使用 MemoryPack 序列化两者比较相等
var right = MemoryPackSerializer.Serialize(settingsByRead);
var left = MemoryPackSerializer.Serialize(settings);
if (left.SequenceEqual(right))
return;
// 使用 MemoryPack 序列化两者比较相等
var right = MemoryPackSerializer.Serialize(settingsByRead);
var left = MemoryPackSerializer.Serialize(settings);
if (left.SequenceEqual(right))
return;
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
static FileStream? OpenOrCreate(string path)
{
try
{
return new FileStream(
path,
FileMode.OpenOrCreate,
FileAccess.Write,
FileShare.ReadWrite | FileShare.Delete);
}
catch (Exception ex)
{
#if DEBUG
Console.WriteLine(ex);
#endif
return default;
}
}
try
{
var settingsFilePath2 = $"{settingsFilePath}.bak";
IOPath.FileTryDelete(settingsFilePath2);
File.Move(settingsFilePath, settingsFilePath2);
}
catch (Exception ex)
{
#if DEBUG
Console.WriteLine("bak settings file fail, ex: " + ex);
#endif
}
Policy.Handle<Exception>().Retry(3).Execute(() =>
{
using var writeStream = OpenOrCreate(settingsFilePath);
if (writeStream == null)
{
using var memoryStream = new MemoryStream();
Save_____(settings, memoryStream);
var bytes = memoryStream.ToByteArray();
File.WriteAllBytes(settingsFilePath, bytes);
}
else
{
Save_____(settings, writeStream);
}
});
}
catch (Exception ex)
finally
{
Debug.WriteLine(ex);
ISettings.SaveStatus[typeof(TSettings)] = false;
}
}
using var writeStream = File.Open(settingsFilePath, FileMode.OpenOrCreate);
Save_____(settings, writeStream);
}
}
@@ -92,7 +92,7 @@ public static class GeneralSettings
public static SettingsProperty<bool, GeneralSettings_> GameListUseLocalCache { get; } = new();
/// <inheritdoc cref="IGeneralSettings.TextReaderProvider"/>
public static SettingsProperty<Dictionary<Platform, string>, GeneralSettings_> TextReaderProvider { get; } = new();
public static SettingsProperty<KeyValuePair<Platform, string>, Dictionary<Platform, string>, GeneralSettings_> TextReaderProvider { get; } = new();
/// <inheritdoc cref="IGeneralSettings.HostsFileEncodingType"/>
public static SettingsProperty<EncodingType, GeneralSettings_> HostsFileEncodingType { get; } = new();
+84 -22
View File
@@ -2,15 +2,14 @@
namespace BD.WTTS.Settings;
[DebuggerDisplay("Value={Value}, PropertyName={PropertyName}, AutoSave={AutoSave}")]
public sealed class SettingsProperty<TValue, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TSettings> : IDisposable, INotifyPropertyChanged
public class SettingsProperty<TValue, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TSettings> : IDisposable, INotifyPropertyChanged
{
readonly Action<TSettings, TValue?> setter;
readonly Func<TSettings, TValue?> getter;
readonly IDisposable? disposable;
readonly IOptionsMonitor<TSettings> monitor;
TValue? value;
void IDisposable.Dispose() => disposable?.Dispose();
bool disposedValue;
public SettingsProperty([CallerMemberName] string? propertyName = null)
{
@@ -26,7 +25,13 @@ public sealed class SettingsProperty<TValue, [DynamicallyAccessedMembers(Dynamic
disposable = monitor.OnChange(OnChange);
}
void OnChange(TSettings settings) => Value = getter(settings);
void OnChange(TSettings settings)
{
if (ISettings.SaveStatus[typeof(TSettings)])
return;
SetValue(getter(settings), false);
}
public string PropertyName { get; }
@@ -43,32 +48,36 @@ public sealed class SettingsProperty<TValue, [DynamicallyAccessedMembers(Dynamic
get => value;
set
{
if (EqualityComparer<TValue>.Default.Equals(value, this.value))
return; // 值相同无变化
var oldValue = this.value;
this.value = value; // 赋值当前字段
setter(monitor.CurrentValue, value); // 赋值模型类属性
OnValueChanged(oldValue, value); // 调用变更事件
if (AutoSave) // 自动保存
{
Save();
}
SetValue(value);
}
}
static readonly object lockSave = new();
void Save()
protected virtual bool Equals(TValue? left, TValue? right)
{
lock (lockSave)
return EqualityComparer<TValue>.Default.Equals(left, right);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void SetValue(TValue? value, bool save = true)
{
if (Equals(value, this.value))
return; // 值相同无变化
var oldValue = this.value;
this.value = value; // 赋值当前字段
setter(monitor.CurrentValue, value); // 赋值模型类属性
OnValueChanged(oldValue, value); // 调用变更事件
if (save && AutoSave) // 自动保存
{
ISettings.TrySave(typeof(TSettings), monitor, true);
Save();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void Save() => ISettings.TrySave(typeof(TSettings), monitor, true);
public void RaiseValueChanged(bool notSave = false)
{
setter(monitor.CurrentValue, value); // 赋值模型类属性
@@ -111,6 +120,7 @@ public sealed class SettingsProperty<TValue, [DynamicallyAccessedMembers(Dynamic
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator TValue?(SettingsProperty<TValue, TSettings> property)
=> property.Value;
@@ -148,5 +158,57 @@ public sealed class SettingsProperty<TValue, [DynamicallyAccessedMembers(Dynamic
}
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// 释放托管状态(托管对象)
disposable?.Dispose();
}
// 释放未托管的资源(未托管的对象)并重写终结器
// 将大型字段设置为 null
disposable = null;
disposedValue = true;
}
}
public void Dispose()
{
// 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endregion
}
public class SettingsProperty<TValue, TEnumerable, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TSettings> : SettingsProperty<TEnumerable, TSettings>
where TEnumerable : IEnumerable<TValue>
{
public SettingsProperty([CallerMemberName] string? propertyName = null) : base(propertyName)
{
}
protected override bool Equals(TEnumerable? left, TEnumerable? right)
{
if (left == null)
{
return right == null;
}
else if (right == null)
{
return left == null;
}
if (EqualityComparer<TEnumerable>.Default.Equals(left, right))
{
return true;
}
return left.SequenceEqual(right);
}
}
+2 -2
View File
@@ -106,13 +106,13 @@ public static class UISettings
public static SettingsProperty<string, UISettings_> Language { get; } = new();
/// <inheritdoc cref="IUISettings.MessageBoxDontPrompts"/>
public static SettingsProperty<HashSet<MessageBox.DontPromptType>, UISettings_> MessageBoxDontPrompts { get; } = new();
public static SettingsProperty<MessageBox.DontPromptType, HashSet<MessageBox.DontPromptType>, UISettings_> MessageBoxDontPrompts { get; } = new();
/// <inheritdoc cref="IUISettings.IsShowAdvertisement"/>
public static SettingsProperty<bool, UISettings_> IsShowAdvertisement { get; } = new();
/// <inheritdoc cref="IUISettings.WindowSizePositions"/>
public static SettingsProperty<ConcurrentDictionary<string, SizePosition>, UISettings_> WindowSizePositions { get; } = new();
public static SettingsProperty<KeyValuePair<string, SizePosition>, ConcurrentDictionary<string, SizePosition>, UISettings_> WindowSizePositions { get; } = new();
/// <inheritdoc cref="IUISettings.FontName"/>
public static SettingsProperty<string, UISettings_> FontName { get; } = new();