100 lines
2.9 KiB
C#
100 lines
2.9 KiB
C#
using System.IO;
|
||
using Newtonsoft.Json;
|
||
using Prism.Mvvm;
|
||
using Store.Management.Framework.Configs;
|
||
|
||
namespace Store.Management.Framework.Services;
|
||
|
||
public interface ISettingsServiceBase<T> where T : class
|
||
{
|
||
T Model { get; }
|
||
string FilePath { get; }
|
||
T GetDefaultModel();
|
||
void LoadSettings();
|
||
Task LoadSettingsAsync();
|
||
void SaveSettings();
|
||
Task SaveSettingsAsync();
|
||
}
|
||
|
||
/// <summary>
|
||
/// JSON 配置服务。缺失文件保留当前配置,加载失败保留旧模型并抛出异常。
|
||
/// 保存通过同目录临时文件替换目标;模型变更应在所属 UI 线程调用。
|
||
/// </summary>
|
||
public abstract class JsonSettingsServiceBase<T> : BindableBase, ISettingsServiceBase<T> where T : class
|
||
{
|
||
private readonly SemaphoreSlim _gate = new(1, 1);
|
||
private T? _model;
|
||
/// <summary>默认按模型类型名保存到用户配置目录,派生类可覆盖文件名或完整路径。</summary>
|
||
public virtual string FilePath => FolderConfig.GetSettingsFilePath(typeof(T).Name + ".json");
|
||
public abstract T GetDefaultModel();
|
||
public T Model
|
||
{
|
||
get => _model ??= GetDefaultModel() ?? throw new InvalidOperationException("默认配置不能为空。");
|
||
protected set
|
||
{
|
||
ArgumentNullException.ThrowIfNull(value);
|
||
SetProperty(ref _model, value);
|
||
}
|
||
}
|
||
|
||
public void LoadSettings()
|
||
{
|
||
T? loaded = LoadModelAsync().GetAwaiter().GetResult();
|
||
if (loaded is not null) Model = loaded;
|
||
}
|
||
public void SaveSettings() => SaveSettingsAsync().GetAwaiter().GetResult();
|
||
|
||
public async Task LoadSettingsAsync()
|
||
{
|
||
T? loaded = await LoadModelAsync();
|
||
if (loaded is not null) Model = loaded;
|
||
}
|
||
|
||
private async Task<T?> LoadModelAsync()
|
||
{
|
||
T? loaded = null;
|
||
await _gate.WaitAsync().ConfigureAwait(false);
|
||
try
|
||
{
|
||
string path = GetFullPath();
|
||
try
|
||
{
|
||
string json = await File.ReadAllTextAsync(path).ConfigureAwait(false);
|
||
loaded = JsonConvert.DeserializeObject<T>(json)
|
||
?? throw new JsonSerializationException($"配置文件不能为 null:{path}");
|
||
}
|
||
catch (FileNotFoundException) { }
|
||
catch (DirectoryNotFoundException) { }
|
||
}
|
||
finally { _gate.Release(); }
|
||
return loaded;
|
||
}
|
||
|
||
public async Task SaveSettingsAsync()
|
||
{
|
||
// 在首次等待前读取模型,避免在后台线程初始化默认模型。
|
||
string json = JsonConvert.SerializeObject(Model, Formatting.Indented);
|
||
await _gate.WaitAsync().ConfigureAwait(false);
|
||
string? temporaryPath = null;
|
||
try
|
||
{
|
||
string path = GetFullPath();
|
||
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
||
temporaryPath = path + "." + Guid.NewGuid().ToString("N") + ".tmp";
|
||
await File.WriteAllTextAsync(temporaryPath, json).ConfigureAwait(false);
|
||
File.Move(temporaryPath, path, overwrite: true);
|
||
}
|
||
finally
|
||
{
|
||
try { if (temporaryPath is not null && File.Exists(temporaryPath)) File.Delete(temporaryPath); }
|
||
finally { _gate.Release(); }
|
||
}
|
||
}
|
||
|
||
private string GetFullPath()
|
||
{
|
||
ArgumentException.ThrowIfNullOrWhiteSpace(FilePath);
|
||
return Path.GetFullPath(FilePath);
|
||
}
|
||
}
|