mirror of
https://github.com/BeyondDimension/SteamTools.git
synced 2026-10-28 14:08:16 +08:00
🐛 fix SettingsProperty
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user