Explorar el Código

Config 的初步实现

龙腾猫跃 hace 4 meses
padre
commit
9ff4fe1114
Se han modificado 2 ficheros con 64 adiciones y 46 borrados
  1. 61 43
      Shared/General/Config.cs
  2. 3 3
      Tests/General/ConfigTest.cs

+ 61 - 43
Shared/General/Config.cs

@@ -5,19 +5,24 @@ namespace MeloongCore;
 
 public interface IConfigProvider {
     /// <summary>
-    /// 将原始数据存入缓存。
+    /// 将数据写入缓存。
+    /// <para/> 这不会写入数据,还需要调用 <see cref="Save"/> 以保存。
     /// </summary>
-    void Set(string key, string value, bool encrypted = false);
+    void SetToCache(string key, string? value, bool encrypted = false);
+    /// <summary>
+    /// 将指定数据从缓存中移除。
+    /// <para/> 这不会写入数据,还需要调用 <see cref="Save"/> 以保存。
+    /// </summary>
+    void RemoveFromCache(string key);
     string? Read(string key, string? defaultValue = null, bool encrypted = false);
     bool HasValue(string key);
-    void Remove(string key);
-    void Save();
     void ClearCache();
+    void Save();
 }
 
 public class JsonConfigProvider : IConfigProvider {
     private readonly string filePath;
-    public readonly ConcurrentDictionary<string, string?> DecryptedContent = new();
+    private readonly ConcurrentDictionary<string, string?> decryptedContentCache = new();
 
     public JsonConfigProvider(string filePath) {
         this.filePath = filePath;
@@ -33,32 +38,32 @@ public class JsonConfigProvider : IConfigProvider {
             try {
                 return FileUtils.Exists(filePath) ? ((JObject?) FileUtils.ReadAsJson(filePath) ?? []) : [];
             } catch (Exception ex) {
-                Logger.Error(ex, $"读取配置文件失败:{filePath}", LogBehavior.Toast);
+                Logger.Error(ex, $"读取配置文件失败({filePath})", LogBehavior.Toast);
                 return [];
             }
         });
     }
 
-    public void Set(string key, string? value, bool encrypted = false) {
+    public void SetToCache(string key, string? value, bool encrypted = false) {
         if (encrypted) {
             Content.Value[key] = CryptographyUtils.AesEncrypt(value); // UNDONE: 改为识别码
-            DecryptedContent[key] = value;
+            decryptedContentCache[key] = value;
         } else {
             Content.Value[key] = value;
         }
     }
     public string? Read(string key, string? defaultValue = null, bool encrypted = false) {
         if (!Content.Value.ContainsKey(key)) return defaultValue;
-        if (encrypted && DecryptedContent.TryGetValue(key, out var cachedValue)) return cachedValue; // 读取缓存中已经解密的值
+        if (encrypted && decryptedContentCache.TryGetValue(key, out var cachedValue)) return cachedValue; // 读取缓存中已经解密的值
         // 读取值
-        string value = Content.Value[key]!.ToString();
+        string? value = Content.Value[key]?.ToString();
         if (!encrypted) return value;
         // 解密读取的数据
         try {
-            return DecryptedContent.GetOrAdd(key, _ => CryptographyUtils.AesDecrypt(value));
+            return decryptedContentCache.GetOrAdd(key, _ => CryptographyUtils.AesDecrypt(value));
         } catch (CryptographicException ex) {
-            Logger.Error(ex, $"解密设置失败,该设置将被重置:{key}", LogBehavior.Toast);
-            Remove(key);
+            Logger.Error(ex, $"解密配置失败({key})", LogBehavior.Toast);
+            RemoveFromCache(key);
             Save();
             return defaultValue;
         }
@@ -66,16 +71,16 @@ public class JsonConfigProvider : IConfigProvider {
 
     public bool HasValue(string key)
         => Content.Value.ContainsKey(key);
-    public void Remove(string key) {
+    public void RemoveFromCache(string key) {
         Content.Value.Remove(key);
-        DecryptedContent.TryRemove(key, out _);
+        decryptedContentCache.TryRemove(key, out _);
     }
-    public void Save()
-        => FileUtils.Write(filePath, Content.Value.ToString(Formatting.Indented));
     public void ClearCache() {
         ResetContent();
-        DecryptedContent.Clear();
+        decryptedContentCache.Clear();
     }
+    public void Save()
+        => FileUtils.Write(filePath, Content.Value.ToString(Formatting.Indented));
 }
 
 public static class Configs {
@@ -88,7 +93,7 @@ public static class Configs {
     private static readonly JsonConfigProvider secret = new(Path.Combine(Paths.AppData, "secret.json"));
 }
 
-public class ConfigEntry<T>(string key, T? defaultValue, IConfigProvider? defaultProvider = null, bool encrypted = false) {
+public class ConfigEntry<T>(string key, T? defaultValue, IConfigProvider? defaultProvider = null, bool encrypted = false) where T : IEquatable<T> {
     private IConfigProvider GetProvider(IConfigProvider? provider)
         => provider ?? defaultProvider ?? Configs.AppData;
 
@@ -97,29 +102,42 @@ public class ConfigEntry<T>(string key, T? defaultValue, IConfigProvider? defaul
     /// </summary>
     public event Action<T?>? OnChanged;
 
-
-    /*public T? Value {
-        get {
-            return GetProvider(null).Read(key, JToken.FromObject(defaultValue))?.ToObject<T>();
-        }
-        set {
-            var provider = GetProvider(null);
-            var oldValue = Value;
-            if (!Equals(oldValue, value)) {
-                provider.Write(key, JToken.FromObject(value));
-                OnChanged?.Invoke(value);
-            }
+    public T? Get(IConfigProvider? providerOverride = null) {
+        var provider = GetProvider(providerOverride);
+        if (!provider.HasValue(key)) return defaultValue;
+        string? value = provider.Read(key, defaultValue?.ToString(), encrypted);
+        try {
+            if (value is null) return default;
+            var type = typeof(T);
+            if (type == typeof(string)) return (T) (object) value;
+            if (typeof(JToken).IsAssignableFrom(type)) return (T) (object) value.DeserializeJson()!;
+            return (T) Convert.ChangeType(value, type);
+        } catch (Exception ex) {
+            Logger.Error(ex, $"读取配置项失败({key})", LogBehavior.Toast);
+            return defaultValue;
         }
     }
-
-    public T? GetValue(IConfigProvider? provider = null) {
-        return GetProvider(provider).Read(key, JToken.FromObject(defaultValue))?.ToObject<T>();
-    }*/
-
-    public bool HasValue(IConfigProvider? provider = null)
-        => GetProvider(provider).HasValue(key);
-    public void Remove(IConfigProvider? provider = null)
-        => GetProvider(provider).Remove(key);
-    public void Save(IConfigProvider? provider = null)
-        => GetProvider(provider).Save();
-}
+    public void Set(T? value, IConfigProvider? providerOverride = null) {
+        var provider = GetProvider(providerOverride);
+        T? current = Get(provider);
+        if ((current is null && value is null) || (current is not null && value is not null && current.Equals(value))) return; // 值未改变,无需更新
+        provider.SetToCache(key, value?.ToString(), encrypted);
+        provider.Save();
+        OnChanged?.Invoke(value);
+    }
+    public bool HasValue(IConfigProvider? providerOverride = null) {
+        var provider = GetProvider(providerOverride);
+        return provider.HasValue(key);
+    }
+    public void Reset(IConfigProvider? providerOverride = null) {
+        var provider = GetProvider(providerOverride);
+        if (!provider.HasValue(key)) return; // 值未设置,无需重置
+        provider.RemoveFromCache(key);
+        provider.Save();
+        OnChanged?.Invoke(defaultValue);
+    }
+    public void Save(IConfigProvider? providerOverride = null) {
+        var provider = GetProvider(providerOverride);
+        provider.Save();
+    }
+}

+ 3 - 3
Tests/General/ConfigTest.cs

@@ -7,7 +7,7 @@ public class ConfigTest : TestWithFolder {
         var filePath = Path.Combine(tempFolder, "config.json");
         var provider = new JsonConfigProvider(filePath);
 
-        provider.Set("token", "plain token", encrypted: true);
+        provider.SetToCache("token", "plain token", encrypted: true);
         provider.Save();
 
         await Assert.That(FileUtils.ReadAsString(filePath).Contains("plain token")).IsFalse();
@@ -20,12 +20,12 @@ public class ConfigTest : TestWithFolder {
         var filePath = Path.Combine(tempFolder, "config.json");
         var provider = new JsonConfigProvider(filePath);
 
-        provider.Set("token", "old token", encrypted: true);
+        provider.SetToCache("token", "old token", encrypted: true);
         provider.Save();
         await Assert.That(provider.Read("token", encrypted: true)).IsEqualTo("old token");
 
         var secondProvider = new JsonConfigProvider(filePath);
-        secondProvider.Set("token", "new token", encrypted: true);
+        secondProvider.SetToCache("token", "new token", encrypted: true);
         secondProvider.Save();
         provider.ClearCache();