SSPC-Tester/SSPCTester.UI/Services/UserSettingsStore.cs
2026-07-12 12:07:41 +08:00

166 lines
6.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.IO;
using System.Text.Json;
using Microsoft.Extensions.Options;
using SSPCTester.Devices.Config;
namespace SSPCTester.UI.Services;
/// <summary>将机器本地配置保存到 LocalAppData不修改发布目录。</summary>
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<DeviceOptions> devices,
IOptions<StorageOptions> storage,
IOptions<ReportOptions> report,
IOptions<ConsoleOptions> 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");
/// <summary>
/// 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.
/// </summary>
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<ConsoleOptions>(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<SettingsPayload>(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<DeviceOptions>(JsonSerializer.Serialize(devices, JsonOptions)) ?? new DeviceOptions();
var copiedStorage = JsonSerializer.Deserialize<StorageOptions>(JsonSerializer.Serialize(storage, JsonOptions)) ?? new StorageOptions();
var copiedReport = JsonSerializer.Deserialize<ReportOptions>(JsonSerializer.Serialize(report, JsonOptions)) ?? new ReportOptions();
var copiedConsole = JsonSerializer.Deserialize<ConsoleOptions>(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();
}