using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.Service.Interface; using BetterGenshinImpact.View.Windows; using OpenCvSharp; using System; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using Application = System.Windows.Application; namespace BetterGenshinImpact.Service; public class ConfigService : IConfigService { private readonly object _locker = new(); // 只有UI线程会调用这个方法,lock好像意义不大,而且浪费了下面的读写锁hhh private readonly ReaderWriterLockSlim _rwLock = new(); private const string ConfigRelativePath = @"User/config.json"; private const string BackupFolderName = "backup"; public static readonly JsonSerializerOptions JsonOptions = new() { NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Converters = { new OpenCvPointJsonConverter(), new OpenCvRectJsonConverter(), }, WriteIndented = true, AllowTrailingCommas = true, ReadCommentHandling = JsonCommentHandling.Skip, }; /// /// 写入只有UI线程会调用 /// 多线程只会读,放心用static,不会丢失数据 /// public static AllConfig? Config { get; private set; } public AllConfig Get() { lock (_locker) { if (Config == null) { Config = Read(); Config.OnAnyChangedAction = Save; // 略微影响性能 Config.InitEvent(); } return Config; } } public void Save() { if (Config != null) { Write(Config); } } public AllConfig Read() { _rwLock.EnterReadLock(); var filePath = Global.Absolute(ConfigRelativePath); try { if (!File.Exists(filePath)) { return new AllConfig(); } var json = File.ReadAllText(filePath); var config = JsonSerializer.Deserialize(json, JsonOptions); if (config == null) { return new AllConfig(); } Config = config; return config; } catch (Exception e) { Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); BackupConfigFile(filePath); ShowConfigExceptionDialog("读取", e); return new AllConfig(); } finally { _rwLock.ExitReadLock(); } } public void Write(AllConfig config) { _rwLock.EnterWriteLock(); var file = Global.Absolute(ConfigRelativePath); try { var path = Global.Absolute("User"); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } File.WriteAllText(file, JsonSerializer.Serialize(config, JsonOptions)); } catch (Exception e) { Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); ShowConfigExceptionDialog("写入", e); } finally { _rwLock.ExitWriteLock(); } } private static void BackupConfigFile(string filePath) { try { if (!File.Exists(filePath)) { return; } var directoryPath = Path.GetDirectoryName(filePath); if (string.IsNullOrWhiteSpace(directoryPath)) { return; } var backupDirectory = Path.Combine(directoryPath, BackupFolderName); Directory.CreateDirectory(backupDirectory); var backupFileName = $"config_{DateTime.Now:yyyyMMdd_HHmmss_fff}.json.bak"; var backupFilePath = Path.Combine(backupDirectory, backupFileName); File.Copy(filePath, backupFilePath, false); } catch (Exception ex) { Console.WriteLine(ex.Message); Console.WriteLine(ex.StackTrace); } } private static void ShowConfigExceptionDialog(string operation, Exception exception) { var current = Application.Current; if (current?.Dispatcher == null) { return; } var coreException = exception.GetBaseException(); var coreStack = string.IsNullOrWhiteSpace(coreException.StackTrace) ? "无可用堆栈信息" : coreException.StackTrace; var message = $"配置文件{operation}失败\n错误:{coreException.Message}\n堆栈:\n{coreStack}"; _ = ThemedMessageBox.ErrorAsync(message, "配置文件异常"); } } public class OpenCvRectJsonConverter : JsonConverter { public override unsafe Rect Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { RectHelper helper = JsonSerializer.Deserialize(ref reader, options); return *(Rect*)&helper; } public override unsafe void Write(Utf8JsonWriter writer, Rect value, JsonSerializerOptions options) { RectHelper helper = *(RectHelper*)&value; JsonSerializer.Serialize(writer, helper, options); } // DO NOT MODIFY: Keep the layout same as OpenCvSharp.Rect private struct RectHelper { public int X { get; set; } public int Y { get; set; } public int Width { get; set; } public int Height { get; set; } } } public class OpenCvPointJsonConverter : JsonConverter { public override unsafe Point Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { PointHelper helper = JsonSerializer.Deserialize(ref reader, options); return *(Point*)&helper; } public override unsafe void Write(Utf8JsonWriter writer, Point value, JsonSerializerOptions options) { PointHelper helper = *(PointHelper*)&value; JsonSerializer.Serialize(writer, helper, options); } // DO NOT MODIFY: Keep the layout same as OpenCvSharp.Point private struct PointHelper { public int X { get; set; } public int Y { get; set; } } }