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 SemaphoreSlim _writeLock = new(1, 1); private readonly object _timerLock = new(); private Timer? _timer; public UserSettingsStore( IOptions devices, IOptions storage, IOptions report) { _devices = devices.Value; _storage = storage.Value; _report = report.Value; } public static string SettingsPath => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "SSPCTester", "settings.json"); public void ScheduleSave() { lock (_timerLock) { _timer?.Dispose(); _timer = new Timer(async _ => { try { await SaveAsync().ConfigureAwait(false); } catch { /* 后续显式保存或退出时会再次尝试 */ } }, null, 600, Timeout.Infinite); } } public async Task SaveAsync() { await _writeLock.WaitAsync().ConfigureAwait(false); try { string path = SettingsPath; Directory.CreateDirectory(Path.GetDirectoryName(path)!); string temp = path + ".tmp"; _devices.Daq.Channels = DaqChannelOptionsNormalizer.Normalize(_devices.Daq.Channels); var model = new { Devices = _devices, Storage = _storage, Report = _report }; await File.WriteAllTextAsync(temp, JsonSerializer.Serialize(model, JsonOptions)).ConfigureAwait(false); File.Move(temp, path, true); WriteDaqSaveTrace(path); } finally { _writeLock.Release(); } } private void WriteDaqSaveTrace(string settingsPath) { 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() { lock (_timerLock) { _timer?.Dispose(); } _writeLock.Dispose(); } }