diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs index 02cf401e..b4617047 100644 --- a/Ink Canvas/MainWindow_cs/MW_Settings.cs +++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs @@ -1,11 +1,9 @@ using Hardcodet.Wpf.TaskbarNotification; using Ink_Canvas.Helpers; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using OSVersionExtension; using System; using System.Collections.Generic; -using System.Globalization; using System.Diagnostics; using System.IO; using System.Linq; @@ -1211,14 +1209,12 @@ namespace Ink_Canvas return; } - // 构建 API URL:仅用于本次请求;null/空列表时本地采用默认全部分类,不写回 Settings,避免启动或拉取一言时触发无意义的配置持久化 - var stored = Settings.Appearance.HitokotoCategories; - var categoriesForRequest = (stored == null || stored.Count == 0) - ? new List { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l" } - : stored; + var cats = Settings.Appearance.HitokotoCategories; + if (cats == null || cats.Count == 0) + cats = new List { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l" }; var urlBuilder = new StringBuilder("https://v1.hitokoto.cn/?encode=text"); - foreach (var category in categoriesForRequest) + foreach (var category in cats) { urlBuilder.Append($"&c={category}"); } @@ -1267,8 +1263,6 @@ namespace Ink_Canvas { if (!isLoaded) return; int idx = ComboBoxChickenSoupSource.SelectedIndex; - if (Settings.Appearance.ChickenSoupSource == idx) - return; Settings.Appearance.ChickenSoupSource = idx; @@ -5143,142 +5137,14 @@ namespace Ink_Canvas #endregion - /// - /// 将 JSON 树归一化后再比较:统一数值为 double、对象属性按名称排序、纯字符串数组按字典序排序(如 hitokotoCategories 顺序与文件不一致时仍视为相同)。 - /// - private static JToken NormalizeJsonForSettingsCompare(JToken token) - { - if (token == null || token.Type == JTokenType.Null) - return JValue.CreateNull(); - - switch (token.Type) - { - case JTokenType.Integer: - case JTokenType.Float: - return new JValue(token.Value()); - case JTokenType.Date: - return new JValue(token.Value().ToUniversalTime().ToString("o", CultureInfo.InvariantCulture)); - case JTokenType.Object: - var o = (JObject)token; - var sorted = new JObject(); - foreach (var p in o.Properties().OrderBy(x => x.Name, StringComparer.Ordinal)) - sorted[p.Name] = NormalizeJsonForSettingsCompare(p.Value); - return sorted; - case JTokenType.Array: - var arr = (JArray)token; - var items = arr.Select(NormalizeJsonForSettingsCompare).ToList(); - if (items.Count > 0) - { - if (items.TrueForAll(x => x.Type == JTokenType.String)) - return new JArray(items.Cast().OrderBy(x => x.Value(), StringComparer.Ordinal)); - if (items.TrueForAll(x => x.Type == JTokenType.Integer || x.Type == JTokenType.Float)) - return new JArray(items.OrderBy(x => x.Value())); - } - return new JArray(items); - default: - return token.DeepClone(); - } - } - - private static bool SettingsFileContentSemanticallyEquals(string newJson, string existingJson) - { - if (string.IsNullOrWhiteSpace(existingJson)) - return false; - var a = NormalizeJsonForSettingsCompare(JToken.Parse(newJson)); - var b = NormalizeJsonForSettingsCompare(JToken.Parse(existingJson)); - return JToken.DeepEquals(a, b); - } - - /// 一言 API 官方分类字母,顺序固定,与自定义对话框一致。 - private static readonly string[] HitokotoCategoryCanonicalOrder = - { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l" }; - - private static readonly HashSet HitokotoCategoryKnownKeys = - new HashSet(HitokotoCategoryCanonicalOrder, StringComparer.Ordinal); - - /// - /// 将 规范为固定顺序并去重,避免 JSON 仅因数组顺序/重复项变化而反复重写。 - /// null 或空列表表示「未持久化、运行时按全部分类」语义,不改为非空列表。 - /// - private static void StabilizeAppearanceHitokotoCategories() - { - var appearance = Settings?.Appearance; - if (appearance == null) - return; - - var list = appearance.HitokotoCategories; - if (list == null || list.Count == 0) - return; - - var normalizedTokens = list - .Select(x => x?.Trim()) - .Where(x => !string.IsNullOrEmpty(x)) - .ToList(); - - var canonical = new List(); - foreach (var key in HitokotoCategoryCanonicalOrder) - { - if (normalizedTokens.Any(x => string.Equals(x, key, StringComparison.Ordinal))) - canonical.Add(key); - } - - foreach (var key in normalizedTokens - .Where(x => !HitokotoCategoryKnownKeys.Contains(x)) - .Distinct(StringComparer.Ordinal) - .OrderBy(x => x, StringComparer.Ordinal)) - { - canonical.Add(key); - } - - if (canonical.Count == 0) - return; - - if (list.Count != canonical.Count || !list.SequenceEqual(canonical, StringComparer.Ordinal)) - appearance.HitokotoCategories = canonical; - } - /// /// 将当前内存中的 Settings 序列化为格式化的 JSON 并写入应用程序配置文件(位于 App.RootPath 下的 Configs 目录或根设置文件)。 /// /// - /// 在写入前会确保目标目录/文件具有写入权限(使用 ProcessProtectionManager)。若与磁盘已有内容语义一致则跳过写入,避免启动或其它路径多次调用时重复刷盘。 - /// 在 LoadSettings 执行期间会延迟到加载结束再统一写盘,避免启动流程中多次保存。 - /// 任何写入失败或异常都会被吞掉,调用方不会收到异常抛出。 + /// 在写入前会确保目标目录/文件具有写入权限(使用 ProcessProtectionManager)。任何写入失败或异常都会被吞掉,调用方不会收到异常抛出。 /// - private static int _settingsLoadReentrancyDepth; - private static bool _settingsSavePendingDuringLoad; - - /// 配对,在 LoadSettings 的 try/finally 中使用。 - private static void BeginDeferredSettingsSaveDuringLoad() - { - _settingsLoadReentrancyDepth++; - } - - private static void EndDeferredSettingsSaveDuringLoad() - { - if (_settingsLoadReentrancyDepth > 0) - _settingsLoadReentrancyDepth--; - if (_settingsLoadReentrancyDepth == 0 && _settingsSavePendingDuringLoad) - { - _settingsSavePendingDuringLoad = false; - SaveSettingsToFileCore(); - } - } - public static void SaveSettingsToFile() { - if (_settingsLoadReentrancyDepth > 0) - { - _settingsSavePendingDuringLoad = true; - return; - } - - SaveSettingsToFileCore(); - } - - private static void SaveSettingsToFileCore() - { - StabilizeAppearanceHitokotoCategories(); var text = JsonConvert.SerializeObject(Settings, Formatting.Indented); try { @@ -5289,22 +5155,6 @@ namespace Ink_Canvas } var path = App.RootPath + settingsFileName; - if (File.Exists(path)) - { - try - { - string existing = File.ReadAllText(path); - if (existing.Length > 0 && existing[0] == '\uFEFF') - existing = existing.TrimStart('\uFEFF'); - if (!string.IsNullOrWhiteSpace(existing) && SettingsFileContentSemanticallyEquals(text, existing)) - return; - } - catch - { - // 无法比较或解析失败时仍写入,避免丢失修复机会 - } - } - ProcessProtectionManager.WithWriteAccess(path, () => File.WriteAllText(path, text)); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs index e4227ac1..c9616ec1 100644 --- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs +++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs @@ -37,9 +37,6 @@ namespace Ink_Canvas /// 指示是否跳过自动更新检查;为 true 时不会在加载设置后执行自动更新检测。 private void LoadSettings(bool isStartup = false, bool skipAutoUpdateCheck = false) { - BeginDeferredSettingsSaveDuringLoad(); - try - { AppVersionTextBlock.Text = Assembly.GetExecutingAssembly().GetName().Version.ToString(); try { @@ -453,6 +450,12 @@ namespace Ink_Canvas : Visibility.Collapsed; } + // 初始化HitokotoCategories,如果为空则默认全选 + if (Settings.Appearance.HitokotoCategories == null || Settings.Appearance.HitokotoCategories.Count == 0) + { + Settings.Appearance.HitokotoCategories = new List { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l" }; + } + ToggleSwitchEnableQuickPanel.IsOn = Settings.Appearance.IsShowQuickPanel; ToggleSwitchEnableSplashScreen.IsOn = Settings.Appearance.EnableSplashScreen; @@ -1319,11 +1322,6 @@ namespace Ink_Canvas // 刷新配置文件列表 try { RefreshConfigProfileList(); } catch (Exception ex) { LogHelper.WriteLogToFile($"刷新配置文件列表失败: {ex.Message}", LogHelper.LogType.Warning); } - } - finally - { - EndDeferredSettingsSaveDuringLoad(); - } } ///