using System.IO; using System.Text.Json; using Microsoft.Extensions.Options; using SSPCTester.Devices.Config; namespace SSPCTester.UI.Services; /// 将机器本地配置保存到 LocalAppData,不修改发布目录。 public sealed class UserSettingsStore : IDisposable { private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; private readonly DeviceOptions _devices; private readonly StorageOptions _storage; private readonly ReportOptions _report; private readonly ConsoleOptions _console; private readonly SemaphoreSlim _writeLock = new(1, 1); public UserSettingsStore( IOptions devices, IOptions storage, IOptions report, IOptions console) { _devices = devices.Value; _storage = storage.Value; _report = report.Value; _console = console.Value; } public static string SettingsPath => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "SSPCTester", "settings.json"); /// /// Loads console presets directly from the user settings JSON so preset arrays /// replace the defaults as a whole. IConfiguration merges arrays by index and /// cannot represent a deleted item or an explicitly empty preset list. /// public static ConsoleOptions? LoadConsoleOptions() { try { string path = SettingsPath; if (!File.Exists(path)) return null; using var document = JsonDocument.Parse(File.ReadAllText(path)); if (!document.RootElement.TryGetProperty(ConsoleOptions.SectionName, out var consoleElement)) return null; var console = consoleElement.Deserialize(JsonOptions); if (console is null) return null; EnsureConsoleDefaults(console); return console; } catch { return null; } } public async Task SaveAsync() { await SaveAsync(_devices, _storage, _report, _console).ConfigureAwait(false); } public async Task SaveAsync(DeviceOptions devices, StorageOptions storage, ReportOptions report) { await SaveAsync(devices, storage, report, _console).ConfigureAwait(false); } public async Task SaveAsync(DeviceOptions devices, StorageOptions storage, ReportOptions report, ConsoleOptions console) { await _writeLock.WaitAsync().ConfigureAwait(false); try { string path = SettingsPath; Directory.CreateDirectory(Path.GetDirectoryName(path)!); string temp = path + ".tmp"; var snapshot = CreateSnapshot(devices, storage, report, console); var model = new { Devices = snapshot.Devices, Storage = snapshot.Storage, Report = snapshot.Report, Console = snapshot.Console }; await File.WriteAllTextAsync(temp, JsonSerializer.Serialize(model, JsonOptions)).ConfigureAwait(false); File.Move(temp, path, true); WriteDaqSaveTrace(path, snapshot.Devices); } finally { _writeLock.Release(); } } public SettingsSnapshot? LoadSnapshot() { try { string path = SettingsPath; if (!File.Exists(path)) return null; var json = File.ReadAllText(path); var payload = JsonSerializer.Deserialize(json, JsonOptions); if (payload is null) return null; return CreateSnapshot(payload.Devices, payload.Storage, payload.Report, payload.Console); } catch { return null; } } private static SettingsSnapshot CreateSnapshot(DeviceOptions devices, StorageOptions storage, ReportOptions report, ConsoleOptions console) { var copiedDevices = JsonSerializer.Deserialize(JsonSerializer.Serialize(devices, JsonOptions)) ?? new DeviceOptions(); var copiedStorage = JsonSerializer.Deserialize(JsonSerializer.Serialize(storage, JsonOptions)) ?? new StorageOptions(); var copiedReport = JsonSerializer.Deserialize(JsonSerializer.Serialize(report, JsonOptions)) ?? new ReportOptions(); var copiedConsole = JsonSerializer.Deserialize(JsonSerializer.Serialize(console, JsonOptions)) ?? new ConsoleOptions(); copiedDevices.Daq.Channels = DaqChannelOptionsNormalizer.Normalize(copiedDevices.Daq.Channels); EnsureConsoleDefaults(copiedConsole); return new SettingsSnapshot(copiedDevices, copiedStorage, copiedReport, copiedConsole); } private static void EnsureConsoleDefaults(ConsoleOptions console) { if (console.PowerPresets == null) console.PowerPresets = ConsoleOptions.DefaultPowerPresets(); if (console.LoadPresets == null) console.LoadPresets = ConsoleOptions.DefaultLoadPresets(); } private void WriteDaqSaveTrace(string settingsPath, DeviceOptions devices) { try { string logDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "SSPCTester", "Logs"); Directory.CreateDirectory(logDir); string channels = string.Join(", ", devices.Daq.Channels .OrderBy(x => x.PhysicalChannel) .Select(x => $"CH{x.PhysicalChannel}:{x.Role}/{x.IsCalibrated}")); File.AppendAllText( Path.Combine(logDir, "settings-save.log"), $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} {settingsPath} Pre={devices.Daq.PreTriggerMs:R} Post={devices.Daq.PostTriggerMs:R} Channels=[{channels}]{Environment.NewLine}"); } catch { } } public void Dispose() => _writeLock.Dispose(); } public sealed record SettingsSnapshot(DeviceOptions Devices, StorageOptions Storage, ReportOptions Report, ConsoleOptions Console); internal sealed class SettingsPayload { public DeviceOptions Devices { get; set; } = new(); public StorageOptions Storage { get; set; } = new(); public ReportOptions Report { get; set; } = new(); public ConsoleOptions Console { get; set; } = new(); }