diff --git a/src/BD.WTTS.Client/Settings/Abstractions/ISettings.cs b/src/BD.WTTS.Client/Settings/Abstractions/ISettings.cs index eed206c3a..32f90dcf3 100644 --- a/src/BD.WTTS.Client/Settings/Abstractions/ISettings.cs +++ b/src/BD.WTTS.Client/Settings/Abstractions/ISettings.cs @@ -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 types = new(); + private static readonly Lazy> lazy_save_status = new(() => + { + return new(types.Select(x => new KeyValuePair(x, false))); + }); + + static ConcurrentDictionary 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(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(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(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(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().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); } } \ No newline at end of file diff --git a/src/BD.WTTS.Client/Settings/GeneralSettings.cs b/src/BD.WTTS.Client/Settings/GeneralSettings.cs index 7cf4856bd..7eb3271e7 100644 --- a/src/BD.WTTS.Client/Settings/GeneralSettings.cs +++ b/src/BD.WTTS.Client/Settings/GeneralSettings.cs @@ -92,7 +92,7 @@ public static class GeneralSettings public static SettingsProperty GameListUseLocalCache { get; } = new(); /// - public static SettingsProperty, GeneralSettings_> TextReaderProvider { get; } = new(); + public static SettingsProperty, Dictionary, GeneralSettings_> TextReaderProvider { get; } = new(); /// public static SettingsProperty HostsFileEncodingType { get; } = new(); diff --git a/src/BD.WTTS.Client/Settings/SettingsProperty.cs b/src/BD.WTTS.Client/Settings/SettingsProperty.cs index 7584501ac..19186cf5d 100644 --- a/src/BD.WTTS.Client/Settings/SettingsProperty.cs +++ b/src/BD.WTTS.Client/Settings/SettingsProperty.cs @@ -2,15 +2,14 @@ namespace BD.WTTS.Settings; [DebuggerDisplay("Value={Value}, PropertyName={PropertyName}, AutoSave={AutoSave}")] -public sealed class SettingsProperty : IDisposable, INotifyPropertyChanged +public class SettingsProperty : IDisposable, INotifyPropertyChanged { readonly Action setter; readonly Func getter; readonly IDisposable? disposable; readonly IOptionsMonitor monitor; TValue? value; - - void IDisposable.Dispose() => disposable?.Dispose(); + bool disposedValue; public SettingsProperty([CallerMemberName] string? propertyName = null) { @@ -26,7 +25,13 @@ public sealed class SettingsProperty 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 value; set { - if (EqualityComparer.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.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 property) => property.Value; @@ -148,5 +158,57 @@ public sealed class SettingsProperty : SettingsProperty + where TEnumerable : IEnumerable +{ + 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.Default.Equals(left, right)) + { + return true; + } + + return left.SequenceEqual(right); + } +} \ No newline at end of file diff --git a/src/BD.WTTS.Client/Settings/UISettings.cs b/src/BD.WTTS.Client/Settings/UISettings.cs index 58c138077..5ffeab87a 100644 --- a/src/BD.WTTS.Client/Settings/UISettings.cs +++ b/src/BD.WTTS.Client/Settings/UISettings.cs @@ -106,13 +106,13 @@ public static class UISettings public static SettingsProperty Language { get; } = new(); /// - public static SettingsProperty, UISettings_> MessageBoxDontPrompts { get; } = new(); + public static SettingsProperty, UISettings_> MessageBoxDontPrompts { get; } = new(); /// public static SettingsProperty IsShowAdvertisement { get; } = new(); /// - public static SettingsProperty, UISettings_> WindowSizePositions { get; } = new(); + public static SettingsProperty, ConcurrentDictionary, UISettings_> WindowSizePositions { get; } = new(); /// public static SettingsProperty FontName { get; } = new();