using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Xml.Serialization;
using MessagePack;
using MessagePack.Resolvers;
using XFEExtension.NetCore.FormatExtension;
namespace XFEExtension.NetCore.AutoConfig;
/// <summary>
/// XFE配置文件,实现配置文件读写自动化
/// </summary>
public abstract class XFEProfile
{
private static readonly ConcurrentDictionary<string, SemaphoreSlim> ProfilePathLocks = new(OperatingSystem.IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
private static readonly Encoding ProfileFileEncoding = new UTF8Encoding(false);
private readonly object profileSyncRoot = new();
private readonly object saveStateSyncRoot = new();
private readonly object loadStateSyncRoot = new();
private readonly SemaphoreSlim saveSemaphore = new(1, 1);
private TimeSpan autoSaveDelay = TimeSpan.FromMilliseconds(100);
private long requestedSaveVersion;
private long savedSaveVersion;
private bool autoSaveWorkerRunning;
private Exception? lastSaveException;
private Exception? lastLoadException;
private MessagePackSerializerOptions messagePackOptions = MessagePackSerializerOptions.Standard
.WithResolver(ContractlessStandardResolver.Instance)
.WithSecurity(MessagePackSecurity.UntrustedData);
private int loadedProfileVersion;
private string id = Guid.NewGuid().ToString();
/// <summary>
/// 配置文件所在的默认目录
/// </summary>
public static string ProfilesDefaultPath { get; set; } = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Profiles");
/// <summary>
/// 配置文件加载失败时触发。事件处理程序中的异常不会中断配置回退流程。
/// </summary>
public static event EventHandler<ProfileLoadFailedEventArgs>? ProfileLoadFailed;
/// <summary>
/// 自动保存的合并等待时间,默认为100毫秒。等待期间发生的多次变更只会合并为一次写入。
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">设置了负数等待时间</exception>
[JsonIgnore]
[XmlIgnore]
public TimeSpan AutoSaveDelay
{
get
{
lock (saveStateSyncRoot)
return autoSaveDelay;
}
set
{
if (value < TimeSpan.Zero)
throw new ArgumentOutOfRangeException(nameof(value), "自动保存等待时间不能小于零");
lock (saveStateSyncRoot)
autoSaveDelay = value;
}
}
/// <summary>
/// 最近一次后台自动保存异常;最近一次成功保存后会被清空。
/// </summary>
[JsonIgnore]
[XmlIgnore]
public Exception? LastSaveException
{
get
{
lock (saveStateSyncRoot)
return lastSaveException;
}
}
/// <summary>
/// 最近一次配置加载异常;成功加载或配置文件不存在时会被清空。
/// </summary>
[JsonIgnore]
[XmlIgnore]
public Exception? LastLoadException
{
get
{
lock (loadStateSyncRoot)
return lastLoadException;
}
}
/// <summary>
/// 当前配置格式的版本号。大于零时会写入配置文件;提升版本后必须在 <see cref="ConfigureMigrations"/> 中注册逐版本迁移。
/// </summary>
[JsonIgnore]
[XmlIgnore]
protected virtual int ProfileSchemaVersion => 0;
/// <summary>
/// 最近一次成功加载或导入的源配置版本。未带版本信息的旧文件视为版本零。
/// </summary>
[JsonIgnore]
[XmlIgnore]
public int LoadedProfileVersion
{
get
{
lock (loadStateSyncRoot)
return loadedProfileVersion;
}
private set
{
lock (loadStateSyncRoot)
loadedProfileVersion = value;
}
}
/// <summary>
/// 当前实例使用的 JSON 序列化选项。适用于 JSON 模式以及 XFE 字典中各属性值的序列化。
/// 可在配置类型构造函数中设置命名策略、类型信息解析器和自定义转换器。
/// </summary>
[JsonIgnore]
[XmlIgnore]
public JsonSerializerOptions JsonOptions { get; } = new();
/// <summary>
/// 当前配置实例使用的 MessagePack 序列化选项。默认启用 ContractlessStandardResolver,
/// 因而普通公开属性对象无需添加 MessagePack 特性即可保存。
/// </summary>
[JsonIgnore]
[XmlIgnore]
public MessagePackSerializerOptions MessagePackOptions
{
get => messagePackOptions;
set => messagePackOptions = value ?? throw new ArgumentNullException(nameof(value));
}
/// <summary>
/// 当前配置实例用于协调属性访问和序列化快照的同步对象
/// </summary>
internal protected object ProfileSyncRoot => profileSyncRoot;
/// <summary>
/// 配置文件存储位置
/// </summary>
internal protected string CurrentProfilePath { get; set; } = string.Empty;
/// <summary>
/// 配置文件扩展名
/// </summary>
internal protected string CurrentProfileExtension { get; set; } = ".xpf";
/// <summary>
/// 默认配置文件存储和读取的操作模式
/// </summary>
internal protected ProfileOperationMode DefaultProfileOperationMode { get; set; } = ProfileOperationMode.XFEDictionary;
/// <summary>
/// 加载操作
/// </summary>
internal protected ProfileLoadOperation LoadOperation { get; set; } = XFEDictionaryLoadProfileOperation;
/// <summary>
/// 保存操作
/// </summary>
internal protected ProfileSaveOperation SaveOperation { get; set; } = XFEDictionarySaveProfileOperation;
/// <summary>
/// 配置文件 “属性名称-属性类型” 字典
/// </summary>
internal protected Dictionary<string, Type> PropertyInfoDictionary { get; set; } = [];
/// <summary>
/// 配置文件 “属性名称-属性设置方法” 字典
/// </summary>
internal protected Dictionary<string, SetValueDelegate> PropertySetFuncDictionary { get; set; } = [];
/// <summary>
/// 配置文件 “属性名称-属性获取方法” 字典
/// </summary>
internal protected Dictionary<string, GetValueDelegate> PropertyGetFuncDictionary { get; set; } = [];
/// <summary>
/// 通过XFE字典加载配置文件方法(默认)
/// </summary>
/// <param name="profileInstance">配置文件实例</param>
/// <param name="profileString">配置文件字符串</param>
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertySetFuncDictionary">配置文件 “属性名称-属性设置方法” 字典</param>
/// <returns>配置文件实例</returns>
[RequiresDynamicCode("XFE 字典模式需要为配置属性生成 JSON 序列化代码。Native AOT 应为 JsonOptions 配置源生成的 JsonTypeInfoResolver。")]
[RequiresUnreferencedCode("XFE 字典模式按运行时类型序列化属性,裁剪可能移除所需成员。")]
public static XFEProfile? XFEDictionaryLoadProfileOperation(XFEProfile profileInstance, string profileString, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, SetValueDelegate> propertySetFuncDictionary)
{
XFEDictionary propertyFileContent = profileString;
foreach (var property in propertyFileContent)
if (propertySetFuncDictionary.TryGetValue(property.Header, out var setValueDelegate) && propertyInfoDictionary.TryGetValue(property.Header, out var type))
setValueDelegate(JsonSerializer.Deserialize(property.Content, type, profileInstance.JsonOptions));
return null;
}
/// <summary>
/// 通过XFE字典保存配置文件方法(默认)
/// </summary>
/// <param name="profileInstance">配置文件实例</param>
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertyGetFuncDictionary">配置文件 “属性名称-属性值获取方法” 字典</param>
/// <returns>保存内容</returns>
[RequiresDynamicCode("XFE 字典模式需要为配置属性生成 JSON 序列化代码。Native AOT 应为 JsonOptions 配置源生成的 JsonTypeInfoResolver。")]
[RequiresUnreferencedCode("XFE 字典模式按运行时类型序列化属性,裁剪可能移除所需成员。")]
public static string XFEDictionarySaveProfileOperation(XFEProfile profileInstance, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, GetValueDelegate> propertyGetFuncDictionary)
{
if (profileInstance is null)
return string.Empty;
var saveProfileDictionary = new XFEDictionary();
foreach (var property in propertyGetFuncDictionary)
saveProfileDictionary.Add(property.Key, JsonSerializer.Serialize(property.Value(), propertyInfoDictionary[property.Key], profileInstance.JsonOptions));
return saveProfileDictionary.ToString();
}
/// <summary>
/// 通过Json加载配置文件方法
/// </summary>
/// <param name="profileInstance">配置文件实例</param>
/// <param name="profileString">配置文件字符串</param>
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertySetFuncDictionary">配置文件 “属性名称-属性设置方法” 字典</param>
/// <returns>配置文件实例</returns>
[RequiresDynamicCode("反射式 JSON 序列化可能需要运行时生成代码。Native AOT 应为 JsonOptions 配置源生成的 JsonTypeInfoResolver。")]
[RequiresUnreferencedCode("反射式 JSON 序列化可能访问被裁剪的成员。")]
public static XFEProfile? JsonLoadProfileOperation(XFEProfile profileInstance, string profileString, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, SetValueDelegate> propertySetFuncDictionary) => JsonSerializer.Deserialize(profileString, profileInstance.GetType(), profileInstance.JsonOptions) is XFEProfile xFEProfile ? xFEProfile : null;
/// <summary>
/// 通过Json保存配置文件方法
/// </summary>
/// <param name="profileInstance">配置文件实例</param>
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertyGetFuncDictionary">配置文件 “属性名称-属性值获取方法” 字典</param>
/// <returns>保存内容</returns>
[RequiresDynamicCode("反射式 JSON 序列化可能需要运行时生成代码。Native AOT 应为 JsonOptions 配置源生成的 JsonTypeInfoResolver。")]
[RequiresUnreferencedCode("反射式 JSON 序列化可能访问被裁剪的成员。")]
public static string JsonSaveProfileOperation(XFEProfile profileInstance, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, GetValueDelegate> propertyGetFuncDictionary) => profileInstance is null ? string.Empty : JsonSerializer.Serialize(profileInstance, profileInstance.GetType(), profileInstance.JsonOptions);
/// <summary>
/// 通过XML加载配置文件方法
/// </summary>
/// <param name="profileInstance">配置文件实例</param>
/// <param name="profileString">配置文件字符串</param>
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertySetFuncDictionary">配置文件 “属性名称-属性设置方法” 字典</param>
/// <returns>配置文件实例</returns>
[RequiresDynamicCode("XmlSerializer 可能在运行时生成序列化程序集,不保证支持 Native AOT。")]
[RequiresUnreferencedCode("XmlSerializer 按反射访问成员,裁剪可能移除所需成员。")]
public static XFEProfile? XmlLoadProfileOperation(XFEProfile profileInstance, string profileString, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, SetValueDelegate> propertySetFuncDictionary) => !string.IsNullOrEmpty(profileString) && new XmlSerializer(profileInstance.GetType()).Deserialize(new MemoryStream(Encoding.UTF8.GetBytes(profileString))) is XFEProfile xFEProfile ? xFEProfile : null;
/// <summary>
/// 通过XML保存配置文件方法
/// </summary>
/// <param name="profileInstance">配置文件实例</param>
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertyGetFuncDictionary">配置文件 “属性名称-属性值获取方法” 字典</param>
/// <returns>保存内容</returns>
[RequiresDynamicCode("XmlSerializer 可能在运行时生成序列化程序集,不保证支持 Native AOT。")]
[RequiresUnreferencedCode("XmlSerializer 按反射访问成员,裁剪可能移除所需成员。")]
public static string XmlSaveProfileOperation(XFEProfile profileInstance, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, GetValueDelegate> propertyGetFuncDictionary)
{
if (profileInstance is null)
return string.Empty;
using var stream = new MemoryStream();
new XmlSerializer(profileInstance.GetType()).Serialize(stream, profileInstance);
stream.Position = 0;
return new StreamReader(stream).ReadToEnd();
}
/// <summary>
/// 加载配置文件
/// </summary>
/// <returns>配置文件实例</returns>
internal protected XFEProfile InstanceLoadProfile(Func<XFEProfile> profileFactory)
{
ArgumentNullException.ThrowIfNull(profileFactory);
CancelPendingAutoSave();
saveSemaphore.Wait();
ProfileLoadFailedEventArgs? loadFailure = null;
XFEProfile loadedProfile = this;
var wasMigrated = false;
try
{
string profilePath;
lock (profileSyncRoot)
profilePath = GetFullProfilePath(CurrentProfilePath);
var pathLock = GetProfilePathLock(profilePath);
pathLock.Wait();
try
{
if (!File.Exists(profilePath))
{
ClearLastLoadException();
}
else
{
try
{
var profileContent = File.ReadAllBytes(profilePath);
(loadedProfile, wasMigrated) = CreateLoadedCandidate(profileContent, profilePath, profileFactory);
ClearLastLoadException();
}
catch (Exception exception)
{
var backupPath = ShouldPreserveCorruptProfile(exception) ? PreserveCorruptProfile(profilePath) : null;
SetLastLoadException(exception);
loadFailure = new ProfileLoadFailedEventArgs(GetType(), profilePath, backupPath, exception, DateTimeOffset.UtcNow);
}
}
}
finally
{
pathLock.Release();
}
}
finally
{
saveSemaphore.Release();
}
if (loadFailure is not null)
RaiseProfileLoadFailed(this, loadFailure);
else if (wasMigrated)
loadedProfile.InstanceRequestSaveProfile();
return loadedProfile;
}
/// <summary>
/// 请求自动保存配置文件。短时间内的多个请求会合并为一次写入。
/// </summary>
internal protected void InstanceRequestSaveProfile()
{
lock (saveStateSyncRoot)
{
requestedSaveVersion++;
if (autoSaveWorkerRunning)
return;
autoSaveWorkerRunning = true;
}
_ = Task.Run(AutoSaveWorkerAsync);
}
/// <summary>
/// 立即保存配置文件,并等待数据写入完成。
/// </summary>
internal protected void InstanceSaveProfile() => InstanceSaveProfileAsync().GetAwaiter().GetResult();
/// <summary>
/// 立即异步保存配置文件,并等待数据写入完成。
/// </summary>
/// <returns>表示保存操作的任务</returns>
internal protected async Task InstanceSaveProfileAsync(CancellationToken cancellationToken = default)
{
await saveSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var snapshot = CreateSaveSnapshot();
await WriteProfileAtomicallyAsync(snapshot.Path, snapshot.Content, cancellationToken).ConfigureAwait(false);
MarkSaveSucceeded(snapshot.Version);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch (Exception exception)
{
MarkSaveFailed(exception);
throw;
}
finally
{
saveSemaphore.Release();
}
}
/// <summary>
/// 等待并写入调用前已经请求的自动保存。
/// </summary>
/// <param name="cancellationToken">取消令牌</param>
internal protected async Task InstanceFlushProfileAsync(CancellationToken cancellationToken = default)
{
await saveSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
lock (saveStateSyncRoot)
{
if (savedSaveVersion >= requestedSaveVersion)
return;
}
var snapshot = CreateSaveSnapshot();
await WriteProfileAtomicallyAsync(snapshot.Path, snapshot.Content, cancellationToken).ConfigureAwait(false);
MarkSaveSucceeded(snapshot.Version);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch (Exception exception)
{
MarkSaveFailed(exception);
throw;
}
finally
{
saveSemaphore.Release();
}
}
/// <summary>
/// 删除配置文件
/// </summary>
internal protected void InstanceDeleteProfile()
{
saveSemaphore.Wait();
try
{
string profilePath;
lock (profileSyncRoot)
{
CancelPendingAutoSave();
profilePath = GetFullProfilePath(CurrentProfilePath);
}
var pathLock = GetProfilePathLock(profilePath);
pathLock.Wait();
try
{
if (File.Exists(profilePath))
File.Delete(profilePath);
}
finally
{
pathLock.Release();
}
}
finally
{
saveSemaphore.Release();
}
}
/// <summary>
/// 导出配置文件
/// </summary>
/// <returns></returns>
internal protected string InstanceExportProfile()
{
lock (profileSyncRoot)
{
var content = SerializeProfileBytes();
return DefaultProfileOperationMode == ProfileOperationMode.MessagePack
? Convert.ToBase64String(content)
: ProfileFileEncoding.GetString(content);
}
}
/// <summary>
/// 以原始字节导出配置文件。MessagePack 模式不会产生 Base64 中间文本。
/// </summary>
internal protected byte[] InstanceExportProfileBytes()
{
lock (profileSyncRoot)
return SerializeProfileBytes();
}
/// <summary>
/// 导入配置文件
/// </summary>
/// <param name="profileString">配置文件字符串</param>
/// <param name="profileFactory">用于创建候选配置的工厂</param>
/// <returns></returns>
internal protected XFEProfile InstanceImportProfile(string profileString, Func<XFEProfile> profileFactory)
{
ArgumentNullException.ThrowIfNull(profileString);
var profileContent = DefaultProfileOperationMode == ProfileOperationMode.MessagePack
? Convert.FromBase64String(profileString)
: ProfileFileEncoding.GetBytes(profileString);
return InstanceImportProfileBytes(profileContent, profileFactory);
}
/// <summary>
/// 从原始字节导入配置文件。
/// </summary>
internal protected XFEProfile InstanceImportProfileBytes(ReadOnlyMemory<byte> profileContent, Func<XFEProfile> profileFactory)
{
ArgumentNullException.ThrowIfNull(profileFactory);
CancelPendingAutoSave();
saveSemaphore.Wait();
try
{
string profilePath;
lock (profileSyncRoot)
profilePath = GetFullProfilePath(CurrentProfilePath);
var (importedProfile, _) = CreateLoadedCandidate(profileContent, profilePath, profileFactory);
return importedProfile;
}
finally
{
saveSemaphore.Release();
}
}
private async Task AutoSaveWorkerAsync()
{
try
{
while (true)
{
TimeSpan delay;
lock (saveStateSyncRoot)
delay = autoSaveDelay;
if (delay > TimeSpan.Zero)
await Task.Delay(delay).ConfigureAwait(false);
await saveSemaphore.WaitAsync().ConfigureAwait(false);
try
{
lock (saveStateSyncRoot)
{
if (savedSaveVersion >= requestedSaveVersion)
{
autoSaveWorkerRunning = false;
return;
}
}
var snapshot = CreateSaveSnapshot();
await WriteProfileAtomicallyAsync(snapshot.Path, snapshot.Content, CancellationToken.None).ConfigureAwait(false);
MarkSaveSucceeded(snapshot.Version);
}
finally
{
saveSemaphore.Release();
}
lock (saveStateSyncRoot)
{
if (savedSaveVersion >= requestedSaveVersion)
{
autoSaveWorkerRunning = false;
return;
}
}
}
}
catch (Exception exception)
{
lock (saveStateSyncRoot)
{
lastSaveException = exception;
autoSaveWorkerRunning = false;
}
}
}
private (string Path, byte[] Content, long Version) CreateSaveSnapshot()
{
lock (profileSyncRoot)
{
long version;
lock (saveStateSyncRoot)
version = requestedSaveVersion;
return (GetFullProfilePath(CurrentProfilePath), SerializeProfileBytes(), version);
}
}
private byte[] SerializeProfileBytes()
{
if (ProfileSchemaVersion < 0)
throw new InvalidOperationException($"{nameof(ProfileSchemaVersion)} 不能小于零");
if (DefaultProfileOperationMode == ProfileOperationMode.MessagePack)
return MessagePackProfileSerializer.Serialize(this, PropertyInfoDictionary, PropertyGetFuncDictionary, ProfileSchemaVersion);
var content = SaveOperation(this, PropertyInfoDictionary, PropertyGetFuncDictionary);
var versionedContent = DefaultProfileOperationMode == ProfileOperationMode.Custom
? WriteCustomProfileVersion(content, ProfileSchemaVersion)
: ProfileVersionMetadata.Write(DefaultProfileOperationMode, content, ProfileSchemaVersion, JsonOptions);
return ProfileFileEncoding.GetBytes(versionedContent);
}
private (XFEProfile Profile, bool WasMigrated) CreateLoadedCandidate(ReadOnlyMemory<byte> profileContent, string profilePath, Func<XFEProfile> profileFactory)
{
var candidate = profileFactory() ?? throw new InvalidOperationException("配置实例工厂返回了 null");
candidate.Initialize();
candidate.CurrentProfilePath = profilePath;
if (candidate.ProfileSchemaVersion < 0)
throw new InvalidOperationException($"{nameof(ProfileSchemaVersion)} 不能小于零");
var migrations = new ProfileMigrationBuilder();
candidate.ConfigureMigrations(migrations);
int storedVersion;
bool wasMigrated;
XFEProfile loadedProfile;
if (candidate.DefaultProfileOperationMode == ProfileOperationMode.MessagePack)
{
var document = MessagePackProfileSerializer.Deserialize(profileContent);
var migration = migrations.ApplyMessagePack(
document.Version,
candidate.ProfileSchemaVersion,
document.Properties,
candidate.MessagePackOptions);
MessagePackProfileSerializer.Populate(
candidate,
migration.Properties,
candidate.PropertyInfoDictionary,
candidate.PropertySetFuncDictionary);
storedVersion = document.Version;
wasMigrated = migration.WasMigrated;
loadedProfile = candidate;
}
else
{
var textContent = ProfileFileEncoding.GetString(profileContent.Span);
var versionedContent = candidate.DefaultProfileOperationMode == ProfileOperationMode.Custom
? candidate.ReadCustomProfileVersion(textContent)
: ProfileVersionMetadata.ReadAndStrip(candidate.DefaultProfileOperationMode, textContent, candidate.JsonOptions);
var migration = migrations.Apply(versionedContent.Version, candidate.ProfileSchemaVersion, candidate.DefaultProfileOperationMode, versionedContent.Content, candidate.JsonOptions);
loadedProfile = candidate.LoadOperation(candidate, migration.Content, candidate.PropertyInfoDictionary, candidate.PropertySetFuncDictionary) ?? candidate;
storedVersion = versionedContent.Version;
wasMigrated = migration.WasMigrated;
}
if (!ReferenceEquals(loadedProfile, candidate))
{
loadedProfile.Initialize();
loadedProfile.CurrentProfilePath = profilePath;
}
loadedProfile.LoadedProfileVersion = storedVersion;
var validation = loadedProfile.ValidateProfile();
if (!validation.IsValid)
throw new ProfileValidationException(loadedProfile.GetType(), validation.ErrorMessage ?? "未提供验证失败原因");
loadedProfile.ClearLastLoadException();
return (loadedProfile, wasMigrated);
}
private void CancelPendingAutoSave()
{
lock (saveStateSyncRoot)
savedSaveVersion = requestedSaveVersion;
}
private void MarkSaveSucceeded(long version)
{
lock (saveStateSyncRoot)
{
savedSaveVersion = Math.Max(savedSaveVersion, version);
lastSaveException = null;
}
}
private void MarkSaveFailed(Exception exception)
{
lock (saveStateSyncRoot)
lastSaveException = exception;
}
private void ClearLastLoadException()
{
lock (loadStateSyncRoot)
lastLoadException = null;
}
private void SetLastLoadException(Exception exception)
{
lock (loadStateSyncRoot)
lastLoadException = exception;
}
private static string? PreserveCorruptProfile(string profilePath)
{
try
{
if (!File.Exists(profilePath))
return null;
var backupPath = $"{profilePath}.corrupt-{DateTimeOffset.UtcNow:yyyyMMddHHmmssfff}-{Guid.NewGuid():N}";
File.Move(profilePath, backupPath);
return backupPath;
}
catch
{
return null;
}
}
private static bool ShouldPreserveCorruptProfile(Exception exception) => exception is not IOException
and not UnauthorizedAccessException
and not ProfileMigrationException
and not ProfileValidationException;
private static void RaiseProfileLoadFailed(XFEProfile profile, ProfileLoadFailedEventArgs eventArgs)
{
var handlers = ProfileLoadFailed;
if (handlers is null)
return;
foreach (var eventHandler in handlers.GetInvocationList())
{
if (eventHandler is not EventHandler<ProfileLoadFailedEventArgs> handler)
continue;
try
{
handler(profile, eventArgs);
}
catch
{
// 加载失败后的回退流程不能被观察者异常中断。
}
}
}
private static string GetFullProfilePath(string profilePath)
{
if (string.IsNullOrWhiteSpace(profilePath))
throw new InvalidOperationException("配置文件路径不能为空");
return Path.GetFullPath(profilePath);
}
private static SemaphoreSlim GetProfilePathLock(string profilePath) => ProfilePathLocks.GetOrAdd(profilePath, static _ => new SemaphoreSlim(1, 1));
private static async Task WriteProfileAtomicallyAsync(string profilePath, byte[] saveContent, CancellationToken cancellationToken)
{
var pathLock = GetProfilePathLock(profilePath);
await pathLock.WaitAsync(cancellationToken).ConfigureAwait(false);
string? tempPath = null;
try
{
var directoryPath = Path.GetDirectoryName(profilePath) ?? throw new InvalidOperationException("无法确定配置文件目录");
Directory.CreateDirectory(directoryPath);
tempPath = Path.Combine(directoryPath, $".{Path.GetFileName(profilePath)}.{Guid.NewGuid():N}.tmp");
await File.WriteAllBytesAsync(tempPath, saveContent, cancellationToken).ConfigureAwait(false);
File.Move(tempPath, profilePath, true);
tempPath = null;
}
finally
{
try
{
if (tempPath is not null && File.Exists(tempPath))
File.Delete(tempPath);
}
finally
{
pathLock.Release();
}
}
}
/// <summary>
/// 设置配置文件加载和存储操作
/// </summary>
internal protected void SetProfileOperation()
{
switch (DefaultProfileOperationMode)
{
case ProfileOperationMode.XFEDictionary:
LoadOperation = XFEDictionaryLoadProfileOperation;
SaveOperation = XFEDictionarySaveProfileOperation;
CurrentProfileExtension = ".xpf";
break;
case ProfileOperationMode.Json:
LoadOperation = JsonLoadProfileOperation;
SaveOperation = JsonSaveProfileOperation;
CurrentProfileExtension = ".json";
break;
case ProfileOperationMode.Xml:
LoadOperation = XmlLoadProfileOperation;
SaveOperation = XmlSaveProfileOperation;
CurrentProfileExtension = ".xml";
break;
case ProfileOperationMode.MessagePack:
CurrentProfileExtension = ".mpk";
break;
case ProfileOperationMode.Custom:
break;
default:
break;
}
}
/// <summary>
/// 初始化
/// </summary>
public virtual void Initialize() => SetProfileOperation();
/// <summary>
/// 注册从旧版本到 <see cref="ProfileSchemaVersion"/> 的逐版本迁移步骤。
/// </summary>
/// <param name="migrations">迁移注册器</param>
protected virtual void ConfigureMigrations(ProfileMigrationBuilder migrations) { }
/// <summary>
/// 验证完成反序列化和迁移后的候选配置。返回失败时不会替换当前配置。
/// </summary>
protected virtual ProfileValidationResult ValidateProfile() => ProfileValidationResult.Success;
/// <summary>
/// Custom 模式读取并移除版本元数据的扩展点。默认把内容视为版本零且不修改内容。
/// </summary>
protected virtual (int Version, string Content) ReadCustomProfileVersion(string content) => (0, content);
/// <summary>
/// Custom 模式写入版本元数据的扩展点。默认不修改内容。
/// </summary>
protected virtual string WriteCustomProfileVersion(string content, int version) => content;
#region 已过时
[Obsolete("SaveProfilesFunc属性现已过时,对于每个配置文件实例,请使用 XXXProfile.SaveOperation")]
private static Func<object?, ProfileEntryInfo, string> SaveProfilesFunc { get; set; } = (i, p) =>
{
if (p.MemberInfo is FieldInfo fieldInfo)
return JsonSerializer.Serialize(fieldInfo.GetValue(i));
else if (p.MemberInfo is PropertyInfo propertyInfo)
return JsonSerializer.Serialize(propertyInfo.GetValue(i));
else
return string.Empty;
};
[Obsolete("SaveProfilesFunc属性现已过时,对于每个配置文件实例,请使用 XXXProfile.LoadOperation")]
private static Func<string, ProfileEntryInfo, object?> LoadProfilesFunc { get; set; } = (x, p) =>
{
if (p.MemberInfo is FieldInfo fieldInfo)
return JsonSerializer.Deserialize(x, fieldInfo.FieldType);
else if (p.MemberInfo is PropertyInfo propertyInfo)
return JsonSerializer.Deserialize(x, propertyInfo.PropertyType);
else
return null;
};
/// <summary>
/// 配置文件清单
/// </summary>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static List<ProfileInfo> Profiles { get; private set; } = [];
/// <summary>
/// 加载配置文件
/// </summary>
/// <param name="profileInfo">配置文件信息</param>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void LoadProfiles(params ProfileInfo[] profileInfo)
{
Profiles.AddRange(profileInfo);
foreach (var profile in Profiles)
{
var instance = profile.GetProfileInstance();
if (!File.Exists(profile.Path))
continue;
XFEDictionary propertyFileContent = File.ReadAllText(profile.Path);
for (int i = 0; i < profile.MemberInfo.Count; i++)
{
for (int j = 0; j < propertyFileContent.Count; j++)
{
var memberInfo = profile.MemberInfo[i];
var property = propertyFileContent.ElementAt(j);
if (property.Header == memberInfo.Name)
{
if (memberInfo.MemberInfo is FieldInfo fieldInfo)
fieldInfo.SetValue(instance, LoadProfilesFunc(property.Content, memberInfo));
else if (memberInfo.MemberInfo is PropertyInfo propertyInfo)
propertyInfo.SetValue(instance, LoadProfilesFunc(property.Content, memberInfo));
continue;
}
foreach (var propertySecFind in propertyFileContent)
{
if (propertySecFind.Header == memberInfo.Name)
{
if (profile.MemberInfo[i].MemberInfo is FieldInfo fieldInfo)
fieldInfo.SetValue(instance, LoadProfilesFunc(propertySecFind.Content, memberInfo));
else if (profile.MemberInfo[i].MemberInfo is PropertyInfo propertyInfo)
propertyInfo.SetValue(instance, LoadProfilesFunc(propertySecFind.Content, memberInfo));
break;
}
}
}
}
}
}
/// <summary>
/// 加载配置文件
/// </summary>
/// <param name="profileInfo">配置文件信息</param>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static async Task LoadProfilesAsync(params ProfileInfo[] profileInfo) => await Task.Run(() => LoadProfiles(profileInfo));
/// <summary>
/// 储存指定的配置文件
/// </summary>
/// <param name="profileInfo">配置文件</param>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void SaveProfile(ProfileInfo profileInfo)
{
var waitSaveProfile = Profiles.Find(x => x.Profile == profileInfo.Profile);
if (waitSaveProfile is null)
return;
var saveProfileDictionary = new XFEDictionary();
var instance = profileInfo.GetProfileInstance();
foreach (var property in waitSaveProfile.MemberInfo)
{
saveProfileDictionary.Add(property.Name, SaveProfilesFunc(instance, property));
}
var fileSavePath = Path.GetDirectoryName(waitSaveProfile.Path);
if (!Directory.Exists(fileSavePath) && fileSavePath is not null && fileSavePath != string.Empty)
Directory.CreateDirectory(fileSavePath);
File.WriteAllText(waitSaveProfile.Path, saveProfileDictionary);
}
/// <summary>
/// 储存配置文件
/// </summary>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void SaveProfiles()
{
foreach (var profile in Profiles)
SaveProfile(profile);
}
/// <summary>
/// 储存指定的配置文件
/// </summary>
/// <param name="profileInfo">配置文件</param>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static async Task SaveProfileAsync(ProfileInfo profileInfo)
{
var waitSaveProfile = Profiles.Find(x => x.Profile == profileInfo.Profile);
if (waitSaveProfile is null)
return;
var saveProfileDictionary = new XFEDictionary();
var instance = profileInfo.GetProfileInstance();
foreach (var property in waitSaveProfile.MemberInfo)
saveProfileDictionary.Add(property.Name, SaveProfilesFunc(instance, property));
var fileSavePath = Path.GetDirectoryName(waitSaveProfile.Path);
if (!Directory.Exists(fileSavePath) && fileSavePath is not null && fileSavePath != string.Empty)
Directory.CreateDirectory(fileSavePath);
await File.WriteAllTextAsync(waitSaveProfile.Path, saveProfileDictionary);
}
/// <summary>
/// 储存配置文件
/// </summary>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static async Task SaveProfilesAsync()
{
foreach (var profile in Profiles)
await SaveProfileAsync(profile);
}
/// <summary>
/// 删除指定的配置文件
/// </summary>
/// <param name="profileInfo">指定的配置文件</param>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void DeleteProfile(ProfileInfo profileInfo)
{
var waitSaveProfile = Profiles.Find(x => x.Profile == profileInfo.Profile);
if (waitSaveProfile is null)
return;
if (File.Exists(waitSaveProfile.Path))
File.Delete(waitSaveProfile.Path);
}
/// <summary>
/// 删除指定的配置文件
/// </summary>
/// <param name="profileInfo">指定的配置文件</param>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static async Task DeleteProfileAsync(ProfileInfo profileInfo)
{
await Task.Run(() =>
{
var waitSaveProfile = Profiles.Find(x => x.Profile == profileInfo.Profile);
if (waitSaveProfile is null)
return;
if (File.Exists(waitSaveProfile.Path))
File.Delete(waitSaveProfile.Path);
});
}
/// <summary>
/// 删除所有配置文件
/// </summary>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void DeleteProfiles()
{
foreach (var profile in Profiles)
DeleteProfile(profile);
}
/// <summary>
/// 删除所有配置文件
/// </summary>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static async Task DeleteProfilesAsync()
{
foreach (var profile in Profiles)
await DeleteProfileAsync(profile);
}
/// <summary>
/// 设置储存配置文件的方法
/// </summary>
/// <param name="saveProfilesFunc">储存方法</param>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void SetSaveProfilesFunction(Func<object?, ProfileEntryInfo, string> saveProfilesFunc) => SaveProfilesFunc = saveProfilesFunc;
/// <summary>
/// 设置加载配置文件的方法
/// </summary>
/// <param name="loadProfilesFunc">加载方法</param>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void SetLoadProfilesFunction(Func<string, ProfileEntryInfo, object?> loadProfilesFunc) => LoadProfilesFunc = loadProfilesFunc;
/// <summary>
/// 可写在属性的set访问器后,用于自动储存
/// </summary>
/// <param name="profileInfo"></param>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
protected static void AutoSave(ProfileInfo profileInfo) => SaveProfile(profileInfo);
/// <summary>
/// 可写在属性的set访问器后,用于自动储存
/// </summary>
/// <param name="profileInfo"></param>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
protected static async Task AutoSaveAsync(ProfileInfo profileInfo) => await SaveProfileAsync(profileInfo);
/// <summary>
/// 导出指定的配置文件
/// </summary>
/// <param name="profileInfo">指定的配置文件</param>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static string ExportProfile(ProfileInfo profileInfo)
{
var waitSaveProfile = Profiles.Find(x => x.Profile == profileInfo.Profile);
if (waitSaveProfile is null)
return string.Empty;
var saveProfileDictionary = new XFEDictionary();
var instance = profileInfo.GetProfileInstance();
foreach (var property in waitSaveProfile.MemberInfo)
saveProfileDictionary.Add(property.Name, SaveProfilesFunc(instance, property));
return saveProfileDictionary.ToString();
}
/// <summary>
/// 导出所有配置文件
/// </summary>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static string ExportProfiles()
{
var exportProfiles = new XFEDictionary();
foreach (var profile in Profiles)
exportProfiles.Add(profile.Profile.Name, ExportProfile(profile));
return exportProfiles.ToString();
}
/// <summary>
/// 导入指定的配置文件<br/>
/// 本方法仅支持导入由<seealso cref="ExportProfile(ProfileInfo)"/>导出的配置文件<br/><br/>
/// 如需导入由<seealso cref="ExportProfiles"/>导出的配置文件,请使用<seealso cref="ImportProfiles(string,bool)"/>
/// </summary>
/// <param name="profileInfo">指定的配置文件</param>
/// <param name="profileString">配置文件字符串</param>
/// <param name="autoSave">导入后是否自动储存</param>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void ImportProfile(ProfileInfo profileInfo, string profileString, bool autoSave = true)
{
var instance = profileInfo.GetProfileInstance();
var waitSaveProfile = Profiles.Find(x => x.Profile == profileInfo.Profile);
if (waitSaveProfile is null)
return;
var importProfileDictionary = new XFEDictionary(profileString);
foreach (var property in waitSaveProfile.MemberInfo)
{
if (importProfileDictionary[property.Name] is not null)
{
if (property.MemberInfo is FieldInfo fieldInfo)
fieldInfo.SetValue(instance, LoadProfilesFunc(importProfileDictionary[property.Name]!, property));
else if (property.MemberInfo is PropertyInfo propertyInfo)
propertyInfo.SetValue(instance, LoadProfilesFunc(importProfileDictionary[property.Name]!, property));
}
}
if (autoSave)
SaveProfile(profileInfo);
}
/// <summary>
/// 导入所有配置文件<br/>
/// 本方法仅支持导入由<seealso cref="ExportProfiles"/>导出的配置文件<br/><br/>
/// 如需导入由<seealso cref="ExportProfile(ProfileInfo)"/>导出的配置文件,请使用<seealso cref="ImportProfile(ProfileInfo, string,bool)"/>
/// </summary>
/// <param name="profileString">配置文件字符串</param>
/// <param name="autoSave">导入后是否自动储存</param>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void ImportProfiles(string profileString, bool autoSave = true)
{
var importProfiles = new XFEDictionary(profileString);
foreach (var profile in Profiles)
{
if (importProfiles[profile.Profile.Name] is not null)
ImportProfile(profile, importProfiles[profile.Profile.Name]!, autoSave);
}
}
#endregion
}
/// <summary>
/// 配置文件保存方法
/// </summary>
/// <param name="profileInstance">配置文件实例</param>
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertyGetFuncDictionary">配置文件 “属性名称-属性值获取方法” 字典</param>
/// <returns>保存内容</returns>
public delegate string ProfileSaveOperation(XFEProfile profileInstance, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, GetValueDelegate> propertyGetFuncDictionary);
/// <summary>
/// 配置文件加载方法
/// </summary>
/// <param name="profileInstance">配置文件实例</param>
/// <param name="profileString">配置文件字符串</param>
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertySetFuncDictionary">配置文件 “属性名称-属性设置方法” 字典</param>
public delegate XFEProfile? ProfileLoadOperation(XFEProfile profileInstance, string profileString, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, SetValueDelegate> propertySetFuncDictionary);
/// <summary>
/// 设置配置文件属性值委托
/// </summary>
/// <param name="value">要设置的值</param>
public delegate void SetValueDelegate(object? value);
/// <summary>
/// 获取配置文件属性值委托
/// </summary>
/// <returns>获取的属性值</returns>
public delegate object? GetValueDelegate();
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Xml.Serialization;
using MessagePack;
using MessagePack.Resolvers;
using XFEExtension.NetCore.FormatExtension;
namespace XFEExtension.NetCore.AutoConfig;
/// <summary>
/// XFE配置文件,实现配置文件读写自动化
/// </summary>
public abstract class XFEProfile
{
private static readonly ConcurrentDictionary<string, SemaphoreSlim> ProfilePathLocks = new(OperatingSystem.IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
private static readonly Encoding ProfileFileEncoding = new UTF8Encoding(false);
private readonly object profileSyncRoot = new();
private readonly object saveStateSyncRoot = new();
private readonly object loadStateSyncRoot = new();
private readonly SemaphoreSlim saveSemaphore = new(1, 1);
private TimeSpan autoSaveDelay = TimeSpan.FromMilliseconds(100);
private long requestedSaveVersion;
private long savedSaveVersion;
private bool autoSaveWorkerRunning;
private Exception? lastSaveException;
private Exception? lastLoadException;
private MessagePackSerializerOptions messagePackOptions = MessagePackSerializerOptions.Standard
.WithResolver(ContractlessStandardResolver.Instance)
.WithSecurity(MessagePackSecurity.UntrustedData);
private int loadedProfileVersion;
private string id = Guid.NewGuid().ToString();
/// <summary>
/// 配置文件所在的默认目录
/// </summary>
public static string ProfilesDefaultPath { get; set; } = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Profiles");
/// <summary>
/// 配置文件加载失败时触发。事件处理程序中的异常不会中断配置回退流程。
/// </summary>
public static event EventHandler<ProfileLoadFailedEventArgs>? ProfileLoadFailed;
/// <summary>
/// 自动保存的合并等待时间,默认为100毫秒。等待期间发生的多次变更只会合并为一次写入。
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">设置了负数等待时间</exception>
[JsonIgnore]
[XmlIgnore]
public TimeSpan AutoSaveDelay
{
get
{
lock (saveStateSyncRoot)
return autoSaveDelay;
}
set
{
if (value < TimeSpan.Zero)
throw new ArgumentOutOfRangeException(nameof(value), "自动保存等待时间不能小于零");
lock (saveStateSyncRoot)
autoSaveDelay = value;
}
}
/// <summary>
/// 最近一次后台自动保存异常;最近一次成功保存后会被清空。
/// </summary>
[JsonIgnore]
[XmlIgnore]
public Exception? LastSaveException
{
get
{
lock (saveStateSyncRoot)
return lastSaveException;
}
}
/// <summary>
/// 最近一次配置加载异常;成功加载或配置文件不存在时会被清空。
/// </summary>
[JsonIgnore]
[XmlIgnore]
public Exception? LastLoadException
{
get
{
lock (loadStateSyncRoot)
return lastLoadException;
}
}
/// <summary>
/// 当前配置格式的版本号。大于零时会写入配置文件;提升版本后必须在 <see cref="ConfigureMigrations"/> 中注册逐版本迁移。
/// </summary>
[JsonIgnore]
[XmlIgnore]
protected virtual int ProfileSchemaVersion => 0;
/// <summary>
/// 最近一次成功加载或导入的源配置版本。未带版本信息的旧文件视为版本零。
/// </summary>
[JsonIgnore]
[XmlIgnore]
public int LoadedProfileVersion
{
get
{
lock (loadStateSyncRoot)
return loadedProfileVersion;
}
private set
{
lock (loadStateSyncRoot)
loadedProfileVersion = value;
}
}
/// <summary>
/// 当前实例使用的 JSON 序列化选项。适用于 JSON 模式以及 XFE 字典中各属性值的序列化。
/// 可在配置类型构造函数中设置命名策略、类型信息解析器和自定义转换器。
/// </summary>
[JsonIgnore]
[XmlIgnore]
public JsonSerializerOptions JsonOptions { get; } = new();
/// <summary>
/// 当前配置实例使用的 MessagePack 序列化选项。默认启用 ContractlessStandardResolver,
/// 因而普通公开属性对象无需添加 MessagePack 特性即可保存。
/// </summary>
[JsonIgnore]
[XmlIgnore]
public MessagePackSerializerOptions MessagePackOptions
{
get => messagePackOptions;
set => messagePackOptions = value ?? throw new ArgumentNullException(nameof(value));
}
/// <summary>
/// 当前配置实例用于协调属性访问和序列化快照的同步对象
/// </summary>
internal protected object ProfileSyncRoot => profileSyncRoot;
/// <summary>
/// 配置文件存储位置
/// </summary>
internal protected string CurrentProfilePath { get; set; } = string.Empty;
/// <summary>
/// 配置文件扩展名
/// </summary>
internal protected string CurrentProfileExtension { get; set; } = ".xpf";
/// <summary>
/// 默认配置文件存储和读取的操作模式
/// </summary>
internal protected ProfileOperationMode DefaultProfileOperationMode { get; set; } = ProfileOperationMode.XFEDictionary;
/// <summary>
/// 加载操作
/// </summary>
internal protected ProfileLoadOperation LoadOperation { get; set; } = XFEDictionaryLoadProfileOperation;
/// <summary>
/// 保存操作
/// </summary>
internal protected ProfileSaveOperation SaveOperation { get; set; } = XFEDictionarySaveProfileOperation;
/// <summary>
/// 配置文件 “属性名称-属性类型” 字典
/// </summary>
internal protected Dictionary<string, Type> PropertyInfoDictionary { get; set; } = [];
/// <summary>
/// 配置文件 “属性名称-属性设置方法” 字典
/// </summary>
internal protected Dictionary<string, SetValueDelegate> PropertySetFuncDictionary { get; set; } = [];
/// <summary>
/// 配置文件 “属性名称-属性获取方法” 字典
/// </summary>
internal protected Dictionary<string, GetValueDelegate> PropertyGetFuncDictionary { get; set; } = [];
/// <summary>
/// 通过XFE字典加载配置文件方法(默认)
/// </summary>
/// <param name="profileInstance">配置文件实例</param>
/// <param name="profileString">配置文件字符串</param>
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertySetFuncDictionary">配置文件 “属性名称-属性设置方法” 字典</param>
/// <returns>配置文件实例</returns>
[RequiresDynamicCode("XFE 字典模式需要为配置属性生成 JSON 序列化代码。Native AOT 应为 JsonOptions 配置源生成的 JsonTypeInfoResolver。")]
[RequiresUnreferencedCode("XFE 字典模式按运行时类型序列化属性,裁剪可能移除所需成员。")]
public static XFEProfile? XFEDictionaryLoadProfileOperation(XFEProfile profileInstance, string profileString, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, SetValueDelegate> propertySetFuncDictionary)
{
XFEDictionary propertyFileContent = profileString;
foreach (var property in propertyFileContent)
if (propertySetFuncDictionary.TryGetValue(property.Header, out var setValueDelegate) && propertyInfoDictionary.TryGetValue(property.Header, out var type))
setValueDelegate(JsonSerializer.Deserialize(property.Content, type, profileInstance.JsonOptions));
return null;
}
/// <summary>
/// 通过XFE字典保存配置文件方法(默认)
/// </summary>
/// <param name="profileInstance">配置文件实例</param>
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertyGetFuncDictionary">配置文件 “属性名称-属性值获取方法” 字典</param>
/// <returns>保存内容</returns>
[RequiresDynamicCode("XFE 字典模式需要为配置属性生成 JSON 序列化代码。Native AOT 应为 JsonOptions 配置源生成的 JsonTypeInfoResolver。")]
[RequiresUnreferencedCode("XFE 字典模式按运行时类型序列化属性,裁剪可能移除所需成员。")]
public static string XFEDictionarySaveProfileOperation(XFEProfile profileInstance, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, GetValueDelegate> propertyGetFuncDictionary)
{
if (profileInstance is null)
return string.Empty;
var saveProfileDictionary = new XFEDictionary();
foreach (var property in propertyGetFuncDictionary)
saveProfileDictionary.Add(property.Key, JsonSerializer.Serialize(property.Value(), propertyInfoDictionary[property.Key], profileInstance.JsonOptions));
return saveProfileDictionary.ToString();
}
/// <summary>
/// 通过Json加载配置文件方法
/// </summary>
/// <param name="profileInstance">配置文件实例</param>
/// <param name="profileString">配置文件字符串</param>
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertySetFuncDictionary">配置文件 “属性名称-属性设置方法” 字典</param>
/// <returns>配置文件实例</returns>
[RequiresDynamicCode("反射式 JSON 序列化可能需要运行时生成代码。Native AOT 应为 JsonOptions 配置源生成的 JsonTypeInfoResolver。")]
[RequiresUnreferencedCode("反射式 JSON 序列化可能访问被裁剪的成员。")]
public static XFEProfile? JsonLoadProfileOperation(XFEProfile profileInstance, string profileString, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, SetValueDelegate> propertySetFuncDictionary) => JsonSerializer.Deserialize(profileString, profileInstance.GetType(), profileInstance.JsonOptions) is XFEProfile xFEProfile ? xFEProfile : null;
/// <summary>
/// 通过Json保存配置文件方法
/// </summary>
/// <param name="profileInstance">配置文件实例</param>
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertyGetFuncDictionary">配置文件 “属性名称-属性值获取方法” 字典</param>
/// <returns>保存内容</returns>
[RequiresDynamicCode("反射式 JSON 序列化可能需要运行时生成代码。Native AOT 应为 JsonOptions 配置源生成的 JsonTypeInfoResolver。")]
[RequiresUnreferencedCode("反射式 JSON 序列化可能访问被裁剪的成员。")]
public static string JsonSaveProfileOperation(XFEProfile profileInstance, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, GetValueDelegate> propertyGetFuncDictionary) => profileInstance is null ? string.Empty : JsonSerializer.Serialize(profileInstance, profileInstance.GetType(), profileInstance.JsonOptions);
/// <summary>
/// 通过XML加载配置文件方法
/// </summary>
/// <param name="profileInstance">配置文件实例</param>
/// <param name="profileString">配置文件字符串</param>
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertySetFuncDictionary">配置文件 “属性名称-属性设置方法” 字典</param>
/// <returns>配置文件实例</returns>
[RequiresDynamicCode("XmlSerializer 可能在运行时生成序列化程序集,不保证支持 Native AOT。")]
[RequiresUnreferencedCode("XmlSerializer 按反射访问成员,裁剪可能移除所需成员。")]
public static XFEProfile? XmlLoadProfileOperation(XFEProfile profileInstance, string profileString, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, SetValueDelegate> propertySetFuncDictionary) => !string.IsNullOrEmpty(profileString) && new XmlSerializer(profileInstance.GetType()).Deserialize(new MemoryStream(Encoding.UTF8.GetBytes(profileString))) is XFEProfile xFEProfile ? xFEProfile : null;
/// <summary>
/// 通过XML保存配置文件方法
/// </summary>
/// <param name="profileInstance">配置文件实例</param>
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertyGetFuncDictionary">配置文件 “属性名称-属性值获取方法” 字典</param>
/// <returns>保存内容</returns>
[RequiresDynamicCode("XmlSerializer 可能在运行时生成序列化程序集,不保证支持 Native AOT。")]
[RequiresUnreferencedCode("XmlSerializer 按反射访问成员,裁剪可能移除所需成员。")]
public static string XmlSaveProfileOperation(XFEProfile profileInstance, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, GetValueDelegate> propertyGetFuncDictionary)
{
if (profileInstance is null)
return string.Empty;
using var stream = new MemoryStream();
new XmlSerializer(profileInstance.GetType()).Serialize(stream, profileInstance);
stream.Position = 0;
return new StreamReader(stream).ReadToEnd();
}
/// <summary>
/// 加载配置文件
/// </summary>
/// <returns>配置文件实例</returns>
internal protected XFEProfile InstanceLoadProfile(Func<XFEProfile> profileFactory)
{
ArgumentNullException.ThrowIfNull(profileFactory);
CancelPendingAutoSave();
saveSemaphore.Wait();
ProfileLoadFailedEventArgs? loadFailure = null;
XFEProfile loadedProfile = this;
var wasMigrated = false;
try
{
string profilePath;
lock (profileSyncRoot)
profilePath = GetFullProfilePath(CurrentProfilePath);
var pathLock = GetProfilePathLock(profilePath);
pathLock.Wait();
try
{
if (!File.Exists(profilePath))
{
ClearLastLoadException();
}
else
{
try
{
var profileContent = File.ReadAllBytes(profilePath);
(loadedProfile, wasMigrated) = CreateLoadedCandidate(profileContent, profilePath, profileFactory);
ClearLastLoadException();
}
catch (Exception exception)
{
var backupPath = ShouldPreserveCorruptProfile(exception) ? PreserveCorruptProfile(profilePath) : null;
SetLastLoadException(exception);
loadFailure = new ProfileLoadFailedEventArgs(GetType(), profilePath, backupPath, exception, DateTimeOffset.UtcNow);
}
}
}
finally
{
pathLock.Release();
}
}
finally
{
saveSemaphore.Release();
}
if (loadFailure is not null)
RaiseProfileLoadFailed(this, loadFailure);
else if (wasMigrated)
loadedProfile.InstanceRequestSaveProfile();
return loadedProfile;
}
/// <summary>
/// 请求自动保存配置文件。短时间内的多个请求会合并为一次写入。
/// </summary>
internal protected void InstanceRequestSaveProfile()
{
lock (saveStateSyncRoot)
{
requestedSaveVersion++;
if (autoSaveWorkerRunning)
return;
autoSaveWorkerRunning = true;
}
_ = Task.Run(AutoSaveWorkerAsync);
}
/// <summary>
/// 立即保存配置文件,并等待数据写入完成。
/// </summary>
internal protected void InstanceSaveProfile() => InstanceSaveProfileAsync().GetAwaiter().GetResult();
/// <summary>
/// 立即异步保存配置文件,并等待数据写入完成。
/// </summary>
/// <returns>表示保存操作的任务</returns>
internal protected async Task InstanceSaveProfileAsync(CancellationToken cancellationToken = default)
{
await saveSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var snapshot = CreateSaveSnapshot();
await WriteProfileAtomicallyAsync(snapshot.Path, snapshot.Content, cancellationToken).ConfigureAwait(false);
MarkSaveSucceeded(snapshot.Version);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch (Exception exception)
{
MarkSaveFailed(exception);
throw;
}
finally
{
saveSemaphore.Release();
}
}
/// <summary>
/// 等待并写入调用前已经请求的自动保存。
/// </summary>
/// <param name="cancellationToken">取消令牌</param>
internal protected async Task InstanceFlushProfileAsync(CancellationToken cancellationToken = default)
{
await saveSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
lock (saveStateSyncRoot)
{
if (savedSaveVersion >= requestedSaveVersion)
return;
}
var snapshot = CreateSaveSnapshot();
await WriteProfileAtomicallyAsync(snapshot.Path, snapshot.Content, cancellationToken).ConfigureAwait(false);
MarkSaveSucceeded(snapshot.Version);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch (Exception exception)
{
MarkSaveFailed(exception);
throw;
}
finally
{
saveSemaphore.Release();
}
}
/// <summary>
/// 删除配置文件
/// </summary>
internal protected void InstanceDeleteProfile()
{
saveSemaphore.Wait();
try
{
string profilePath;
lock (profileSyncRoot)
{
CancelPendingAutoSave();
profilePath = GetFullProfilePath(CurrentProfilePath);
}
var pathLock = GetProfilePathLock(profilePath);
pathLock.Wait();
try
{
if (File.Exists(profilePath))
File.Delete(profilePath);
}
finally
{
pathLock.Release();
}
}
finally
{
saveSemaphore.Release();
}
}
/// <summary>
/// 导出配置文件
/// </summary>
/// <returns></returns>
internal protected string InstanceExportProfile()
{
lock (profileSyncRoot)
{
var content = SerializeProfileBytes();
return DefaultProfileOperationMode == ProfileOperationMode.MessagePack
? Convert.ToBase64String(content)
: ProfileFileEncoding.GetString(content);
}
}
/// <summary>
/// 以原始字节导出配置文件。MessagePack 模式不会产生 Base64 中间文本。
/// </summary>
internal protected byte[] InstanceExportProfileBytes()
{
lock (profileSyncRoot)
return SerializeProfileBytes();
}
/// <summary>
/// 导入配置文件
/// </summary>
/// <param name="profileString">配置文件字符串</param>
/// <param name="profileFactory">用于创建候选配置的工厂</param>
/// <returns></returns>
internal protected XFEProfile InstanceImportProfile(string profileString, Func<XFEProfile> profileFactory)
{
ArgumentNullException.ThrowIfNull(profileString);
var profileContent = DefaultProfileOperationMode == ProfileOperationMode.MessagePack
? Convert.FromBase64String(profileString)
: ProfileFileEncoding.GetBytes(profileString);
return InstanceImportProfileBytes(profileContent, profileFactory);
}
/// <summary>
/// 从原始字节导入配置文件。
/// </summary>
internal protected XFEProfile InstanceImportProfileBytes(ReadOnlyMemory<byte> profileContent, Func<XFEProfile> profileFactory)
{
ArgumentNullException.ThrowIfNull(profileFactory);
CancelPendingAutoSave();
saveSemaphore.Wait();
try
{
string profilePath;
lock (profileSyncRoot)
profilePath = GetFullProfilePath(CurrentProfilePath);
var (importedProfile, _) = CreateLoadedCandidate(profileContent, profilePath, profileFactory);
return importedProfile;
}
finally
{
saveSemaphore.Release();
}
}
private async Task AutoSaveWorkerAsync()
{
try
{
while (true)
{
TimeSpan delay;
lock (saveStateSyncRoot)
delay = autoSaveDelay;
if (delay > TimeSpan.Zero)
await Task.Delay(delay).ConfigureAwait(false);
await saveSemaphore.WaitAsync().ConfigureAwait(false);
try
{
lock (saveStateSyncRoot)
{
if (savedSaveVersion >= requestedSaveVersion)
{
autoSaveWorkerRunning = false;
return;
}
}
var snapshot = CreateSaveSnapshot();
await WriteProfileAtomicallyAsync(snapshot.Path, snapshot.Content, CancellationToken.None).ConfigureAwait(false);
MarkSaveSucceeded(snapshot.Version);
}
finally
{
saveSemaphore.Release();
}
lock (saveStateSyncRoot)
{
if (savedSaveVersion >= requestedSaveVersion)
{
autoSaveWorkerRunning = false;
return;
}
}
}
}
catch (Exception exception)
{
lock (saveStateSyncRoot)
{
lastSaveException = exception;
autoSaveWorkerRunning = false;
}
}
}
private (string Path, byte[] Content, long Version) CreateSaveSnapshot()
{
lock (profileSyncRoot)
{
long version;
lock (saveStateSyncRoot)
version = requestedSaveVersion;
return (GetFullProfilePath(CurrentProfilePath), SerializeProfileBytes(), version);
}
}
private byte[] SerializeProfileBytes()
{
if (ProfileSchemaVersion < 0)
throw new InvalidOperationException($"{nameof(ProfileSchemaVersion)} 不能小于零");
if (DefaultProfileOperationMode == ProfileOperationMode.MessagePack)
return MessagePackProfileSerializer.Serialize(this, PropertyInfoDictionary, PropertyGetFuncDictionary, ProfileSchemaVersion);
var content = SaveOperation(this, PropertyInfoDictionary, PropertyGetFuncDictionary);
var versionedContent = DefaultProfileOperationMode == ProfileOperationMode.Custom
? WriteCustomProfileVersion(content, ProfileSchemaVersion)
: ProfileVersionMetadata.Write(DefaultProfileOperationMode, content, ProfileSchemaVersion, JsonOptions);
return ProfileFileEncoding.GetBytes(versionedContent);
}
private (XFEProfile Profile, bool WasMigrated) CreateLoadedCandidate(ReadOnlyMemory<byte> profileContent, string profilePath, Func<XFEProfile> profileFactory)
{
var candidate = profileFactory() ?? throw new InvalidOperationException("配置实例工厂返回了 null");
candidate.Initialize();
candidate.CurrentProfilePath = profilePath;
if (candidate.ProfileSchemaVersion < 0)
throw new InvalidOperationException($"{nameof(ProfileSchemaVersion)} 不能小于零");
var migrations = new ProfileMigrationBuilder();
candidate.ConfigureMigrations(migrations);
int storedVersion;
bool wasMigrated;
XFEProfile loadedProfile;
if (candidate.DefaultProfileOperationMode == ProfileOperationMode.MessagePack)
{
var document = MessagePackProfileSerializer.Deserialize(profileContent);
var migration = migrations.ApplyMessagePack(
document.Version,
candidate.ProfileSchemaVersion,
document.Properties,
candidate.MessagePackOptions);
MessagePackProfileSerializer.Populate(
candidate,
migration.Properties,
candidate.PropertyInfoDictionary,
candidate.PropertySetFuncDictionary);
storedVersion = document.Version;
wasMigrated = migration.WasMigrated;
loadedProfile = candidate;
}
else
{
var textContent = ProfileFileEncoding.GetString(profileContent.Span);
var versionedContent = candidate.DefaultProfileOperationMode == ProfileOperationMode.Custom
? candidate.ReadCustomProfileVersion(textContent)
: ProfileVersionMetadata.ReadAndStrip(candidate.DefaultProfileOperationMode, textContent, candidate.JsonOptions);
var migration = migrations.Apply(versionedContent.Version, candidate.ProfileSchemaVersion, candidate.DefaultProfileOperationMode, versionedContent.Content, candidate.JsonOptions);
loadedProfile = candidate.LoadOperation(candidate, migration.Content, candidate.PropertyInfoDictionary, candidate.PropertySetFuncDictionary) ?? candidate;
storedVersion = versionedContent.Version;
wasMigrated = migration.WasMigrated;
}
if (!ReferenceEquals(loadedProfile, candidate))
{
loadedProfile.Initialize();
loadedProfile.CurrentProfilePath = profilePath;
}
loadedProfile.LoadedProfileVersion = storedVersion;
var validation = loadedProfile.ValidateProfile();
if (!validation.IsValid)
throw new ProfileValidationException(loadedProfile.GetType(), validation.ErrorMessage ?? "未提供验证失败原因");
loadedProfile.ClearLastLoadException();
return (loadedProfile, wasMigrated);
}
private void CancelPendingAutoSave()
{
lock (saveStateSyncRoot)
savedSaveVersion = requestedSaveVersion;
}
private void MarkSaveSucceeded(long version)
{
lock (saveStateSyncRoot)
{
savedSaveVersion = Math.Max(savedSaveVersion, version);
lastSaveException = null;
}
}
private void MarkSaveFailed(Exception exception)
{
lock (saveStateSyncRoot)
lastSaveException = exception;
}
private void ClearLastLoadException()
{
lock (loadStateSyncRoot)
lastLoadException = null;
}
private void SetLastLoadException(Exception exception)
{
lock (loadStateSyncRoot)
lastLoadException = exception;
}
private static string? PreserveCorruptProfile(string profilePath)
{
try
{
if (!File.Exists(profilePath))
return null;
var backupPath = $"{profilePath}.corrupt-{DateTimeOffset.UtcNow:yyyyMMddHHmmssfff}-{Guid.NewGuid():N}";
File.Move(profilePath, backupPath);
return backupPath;
}
catch
{
return null;
}
}
private static bool ShouldPreserveCorruptProfile(Exception exception) => exception is not IOException
and not UnauthorizedAccessException
and not ProfileMigrationException
and not ProfileValidationException;
private static void RaiseProfileLoadFailed(XFEProfile profile, ProfileLoadFailedEventArgs eventArgs)
{
var handlers = ProfileLoadFailed;
if (handlers is null)
return;
foreach (var eventHandler in handlers.GetInvocationList())
{
if (eventHandler is not EventHandler<ProfileLoadFailedEventArgs> handler)
continue;
try
{
handler(profile, eventArgs);
}
catch
{
// 加载失败后的回退流程不能被观察者异常中断。
}
}
}
private static string GetFullProfilePath(string profilePath)
{
if (string.IsNullOrWhiteSpace(profilePath))
throw new InvalidOperationException("配置文件路径不能为空");
return Path.GetFullPath(profilePath);
}
private static SemaphoreSlim GetProfilePathLock(string profilePath) => ProfilePathLocks.GetOrAdd(profilePath, static _ => new SemaphoreSlim(1, 1));
private static async Task WriteProfileAtomicallyAsync(string profilePath, byte[] saveContent, CancellationToken cancellationToken)
{
var pathLock = GetProfilePathLock(profilePath);
await pathLock.WaitAsync(cancellationToken).ConfigureAwait(false);
string? tempPath = null;
try
{
var directoryPath = Path.GetDirectoryName(profilePath) ?? throw new InvalidOperationException("无法确定配置文件目录");
Directory.CreateDirectory(directoryPath);
tempPath = Path.Combine(directoryPath, $".{Path.GetFileName(profilePath)}.{Guid.NewGuid():N}.tmp");
await File.WriteAllBytesAsync(tempPath, saveContent, cancellationToken).ConfigureAwait(false);
File.Move(tempPath, profilePath, true);
tempPath = null;
}
finally
{
try
{
if (tempPath is not null && File.Exists(tempPath))
File.Delete(tempPath);
}
finally
{
pathLock.Release();
}
}
}
/// <summary>
/// 设置配置文件加载和存储操作
/// </summary>
internal protected void SetProfileOperation()
{
switch (DefaultProfileOperationMode)
{
case ProfileOperationMode.XFEDictionary:
LoadOperation = XFEDictionaryLoadProfileOperation;
SaveOperation = XFEDictionarySaveProfileOperation;
CurrentProfileExtension = ".xpf";
break;
case ProfileOperationMode.Json:
LoadOperation = JsonLoadProfileOperation;
SaveOperation = JsonSaveProfileOperation;
CurrentProfileExtension = ".json";
break;
case ProfileOperationMode.Xml:
LoadOperation = XmlLoadProfileOperation;
SaveOperation = XmlSaveProfileOperation;
CurrentProfileExtension = ".xml";
break;
case ProfileOperationMode.MessagePack:
CurrentProfileExtension = ".mpk";
break;
case ProfileOperationMode.Custom:
break;
default:
break;
}
}
/// <summary>
/// 初始化
/// </summary>
public virtual void Initialize() => SetProfileOperation();
/// <summary>
/// 注册从旧版本到 <see cref="ProfileSchemaVersion"/> 的逐版本迁移步骤。
/// </summary>
/// <param name="migrations">迁移注册器</param>
protected virtual void ConfigureMigrations(ProfileMigrationBuilder migrations) { }
/// <summary>
/// 验证完成反序列化和迁移后的候选配置。返回失败时不会替换当前配置。
/// </summary>
protected virtual ProfileValidationResult ValidateProfile() => ProfileValidationResult.Success;
/// <summary>
/// Custom 模式读取并移除版本元数据的扩展点。默认把内容视为版本零且不修改内容。
/// </summary>
protected virtual (int Version, string Content) ReadCustomProfileVersion(string content) => (0, content);
/// <summary>
/// Custom 模式写入版本元数据的扩展点。默认不修改内容。
/// </summary>
protected virtual string WriteCustomProfileVersion(string content, int version) => content;
#region 已过时
[Obsolete("SaveProfilesFunc属性现已过时,对于每个配置文件实例,请使用 XXXProfile.SaveOperation")]
private static Func<object?, ProfileEntryInfo, string> SaveProfilesFunc { get; set; } = (i, p) =>
{
if (p.MemberInfo is FieldInfo fieldInfo)
return JsonSerializer.Serialize(fieldInfo.GetValue(i));
else if (p.MemberInfo is PropertyInfo propertyInfo)
return JsonSerializer.Serialize(propertyInfo.GetValue(i));
else
return string.Empty;
};
[Obsolete("SaveProfilesFunc属性现已过时,对于每个配置文件实例,请使用 XXXProfile.LoadOperation")]
private static Func<string, ProfileEntryInfo, object?> LoadProfilesFunc { get; set; } = (x, p) =>
{
if (p.MemberInfo is FieldInfo fieldInfo)
return JsonSerializer.Deserialize(x, fieldInfo.FieldType);
else if (p.MemberInfo is PropertyInfo propertyInfo)
return JsonSerializer.Deserialize(x, propertyInfo.PropertyType);
else
return null;
};
/// <summary>
/// 配置文件清单
/// </summary>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static List<ProfileInfo> Profiles { get; private set; } = [];
/// <summary>
/// 加载配置文件
/// </summary>
/// <param name="profileInfo">配置文件信息</param>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void LoadProfiles(params ProfileInfo[] profileInfo)
{
Profiles.AddRange(profileInfo);
foreach (var profile in Profiles)
{
var instance = profile.GetProfileInstance();
if (!File.Exists(profile.Path))
continue;
XFEDictionary propertyFileContent = File.ReadAllText(profile.Path);
for (int i = 0; i < profile.MemberInfo.Count; i++)
{
for (int j = 0; j < propertyFileContent.Count; j++)
{
var memberInfo = profile.MemberInfo[i];
var property = propertyFileContent.ElementAt(j);
if (property.Header == memberInfo.Name)
{
if (memberInfo.MemberInfo is FieldInfo fieldInfo)
fieldInfo.SetValue(instance, LoadProfilesFunc(property.Content, memberInfo));
else if (memberInfo.MemberInfo is PropertyInfo propertyInfo)
propertyInfo.SetValue(instance, LoadProfilesFunc(property.Content, memberInfo));
continue;
}
foreach (var propertySecFind in propertyFileContent)
{
if (propertySecFind.Header == memberInfo.Name)
{
if (profile.MemberInfo[i].MemberInfo is FieldInfo fieldInfo)
fieldInfo.SetValue(instance, LoadProfilesFunc(propertySecFind.Content, memberInfo));
else if (profile.MemberInfo[i].MemberInfo is PropertyInfo propertyInfo)
propertyInfo.SetValue(instance, LoadProfilesFunc(propertySecFind.Content, memberInfo));
break;
}
}
}
}
}
}
/// <summary>
/// 加载配置文件
/// </summary>
/// <param name="profileInfo">配置文件信息</param>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static async Task LoadProfilesAsync(params ProfileInfo[] profileInfo) => await Task.Run(() => LoadProfiles(profileInfo));
/// <summary>
/// 储存指定的配置文件
/// </summary>
/// <param name="profileInfo">配置文件</param>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void SaveProfile(ProfileInfo profileInfo)
{
var waitSaveProfile = Profiles.Find(x => x.Profile == profileInfo.Profile);
if (waitSaveProfile is null)
return;
var saveProfileDictionary = new XFEDictionary();
var instance = profileInfo.GetProfileInstance();
foreach (var property in waitSaveProfile.MemberInfo)
{
saveProfileDictionary.Add(property.Name, SaveProfilesFunc(instance, property));
}
var fileSavePath = Path.GetDirectoryName(waitSaveProfile.Path);
if (!Directory.Exists(fileSavePath) && fileSavePath is not null && fileSavePath != string.Empty)
Directory.CreateDirectory(fileSavePath);
File.WriteAllText(waitSaveProfile.Path, saveProfileDictionary);
}
/// <summary>
/// 储存配置文件
/// </summary>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void SaveProfiles()
{
foreach (var profile in Profiles)
SaveProfile(profile);
}
/// <summary>
/// 储存指定的配置文件
/// </summary>
/// <param name="profileInfo">配置文件</param>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static async Task SaveProfileAsync(ProfileInfo profileInfo)
{
var waitSaveProfile = Profiles.Find(x => x.Profile == profileInfo.Profile);
if (waitSaveProfile is null)
return;
var saveProfileDictionary = new XFEDictionary();
var instance = profileInfo.GetProfileInstance();
foreach (var property in waitSaveProfile.MemberInfo)
saveProfileDictionary.Add(property.Name, SaveProfilesFunc(instance, property));
var fileSavePath = Path.GetDirectoryName(waitSaveProfile.Path);
if (!Directory.Exists(fileSavePath) && fileSavePath is not null && fileSavePath != string.Empty)
Directory.CreateDirectory(fileSavePath);
await File.WriteAllTextAsync(waitSaveProfile.Path, saveProfileDictionary);
}
/// <summary>
/// 储存配置文件
/// </summary>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static async Task SaveProfilesAsync()
{
foreach (var profile in Profiles)
await SaveProfileAsync(profile);
}
/// <summary>
/// 删除指定的配置文件
/// </summary>
/// <param name="profileInfo">指定的配置文件</param>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void DeleteProfile(ProfileInfo profileInfo)
{
var waitSaveProfile = Profiles.Find(x => x.Profile == profileInfo.Profile);
if (waitSaveProfile is null)
return;
if (File.Exists(waitSaveProfile.Path))
File.Delete(waitSaveProfile.Path);
}
/// <summary>
/// 删除指定的配置文件
/// </summary>
/// <param name="profileInfo">指定的配置文件</param>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static async Task DeleteProfileAsync(ProfileInfo profileInfo)
{
await Task.Run(() =>
{
var waitSaveProfile = Profiles.Find(x => x.Profile == profileInfo.Profile);
if (waitSaveProfile is null)
return;
if (File.Exists(waitSaveProfile.Path))
File.Delete(waitSaveProfile.Path);
});
}
/// <summary>
/// 删除所有配置文件
/// </summary>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void DeleteProfiles()
{
foreach (var profile in Profiles)
DeleteProfile(profile);
}
/// <summary>
/// 删除所有配置文件
/// </summary>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static async Task DeleteProfilesAsync()
{
foreach (var profile in Profiles)
await DeleteProfileAsync(profile);
}
/// <summary>
/// 设置储存配置文件的方法
/// </summary>
/// <param name="saveProfilesFunc">储存方法</param>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void SetSaveProfilesFunction(Func<object?, ProfileEntryInfo, string> saveProfilesFunc) => SaveProfilesFunc = saveProfilesFunc;
/// <summary>
/// 设置加载配置文件的方法
/// </summary>
/// <param name="loadProfilesFunc">加载方法</param>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void SetLoadProfilesFunction(Func<string, ProfileEntryInfo, object?> loadProfilesFunc) => LoadProfilesFunc = loadProfilesFunc;
/// <summary>
/// 可写在属性的set访问器后,用于自动储存
/// </summary>
/// <param name="profileInfo"></param>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
protected static void AutoSave(ProfileInfo profileInfo) => SaveProfile(profileInfo);
/// <summary>
/// 可写在属性的set访问器后,用于自动储存
/// </summary>
/// <param name="profileInfo"></param>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
protected static async Task AutoSaveAsync(ProfileInfo profileInfo) => await SaveProfileAsync(profileInfo);
/// <summary>
/// 导出指定的配置文件
/// </summary>
/// <param name="profileInfo">指定的配置文件</param>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static string ExportProfile(ProfileInfo profileInfo)
{
var waitSaveProfile = Profiles.Find(x => x.Profile == profileInfo.Profile);
if (waitSaveProfile is null)
return string.Empty;
var saveProfileDictionary = new XFEDictionary();
var instance = profileInfo.GetProfileInstance();
foreach (var property in waitSaveProfile.MemberInfo)
saveProfileDictionary.Add(property.Name, SaveProfilesFunc(instance, property));
return saveProfileDictionary.ToString();
}
/// <summary>
/// 导出所有配置文件
/// </summary>
/// <returns></returns>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static string ExportProfiles()
{
var exportProfiles = new XFEDictionary();
foreach (var profile in Profiles)
exportProfiles.Add(profile.Profile.Name, ExportProfile(profile));
return exportProfiles.ToString();
}
/// <summary>
/// 导入指定的配置文件<br/>
/// 本方法仅支持导入由<seealso cref="ExportProfile(ProfileInfo)"/>导出的配置文件<br/><br/>
/// 如需导入由<seealso cref="ExportProfiles"/>导出的配置文件,请使用<seealso cref="ImportProfiles(string,bool)"/>
/// </summary>
/// <param name="profileInfo">指定的配置文件</param>
/// <param name="profileString">配置文件字符串</param>
/// <param name="autoSave">导入后是否自动储存</param>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void ImportProfile(ProfileInfo profileInfo, string profileString, bool autoSave = true)
{
var instance = profileInfo.GetProfileInstance();
var waitSaveProfile = Profiles.Find(x => x.Profile == profileInfo.Profile);
if (waitSaveProfile is null)
return;
var importProfileDictionary = new XFEDictionary(profileString);
foreach (var property in waitSaveProfile.MemberInfo)
{
if (importProfileDictionary[property.Name] is not null)
{
if (property.MemberInfo is FieldInfo fieldInfo)
fieldInfo.SetValue(instance, LoadProfilesFunc(importProfileDictionary[property.Name]!, property));
else if (property.MemberInfo is PropertyInfo propertyInfo)
propertyInfo.SetValue(instance, LoadProfilesFunc(importProfileDictionary[property.Name]!, property));
}
}
if (autoSave)
SaveProfile(profileInfo);
}
/// <summary>
/// 导入所有配置文件<br/>
/// 本方法仅支持导入由<seealso cref="ExportProfiles"/>导出的配置文件<br/><br/>
/// 如需导入由<seealso cref="ExportProfile(ProfileInfo)"/>导出的配置文件,请使用<seealso cref="ImportProfile(ProfileInfo, string,bool)"/>
/// </summary>
/// <param name="profileString">配置文件字符串</param>
/// <param name="autoSave">导入后是否自动储存</param>
[Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")]
public static void ImportProfiles(string profileString, bool autoSave = true)
{
var importProfiles = new XFEDictionary(profileString);
foreach (var profile in Profiles)
{
if (importProfiles[profile.Profile.Name] is not null)
ImportProfile(profile, importProfiles[profile.Profile.Name]!, autoSave);
}
}
#endregion
}
/// <summary>
/// 配置文件保存方法
/// </summary>
/// <param name="profileInstance">配置文件实例</param>
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertyGetFuncDictionary">配置文件 “属性名称-属性值获取方法” 字典</param>
/// <returns>保存内容</returns>
public delegate string ProfileSaveOperation(XFEProfile profileInstance, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, GetValueDelegate> propertyGetFuncDictionary);
/// <summary>
/// 配置文件加载方法
/// </summary>
/// <param name="profileInstance">配置文件实例</param>
/// <param name="profileString">配置文件字符串</param>
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertySetFuncDictionary">配置文件 “属性名称-属性设置方法” 字典</param>
public delegate XFEProfile? ProfileLoadOperation(XFEProfile profileInstance, string profileString, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, SetValueDelegate> propertySetFuncDictionary);
/// <summary>
/// 设置配置文件属性值委托
/// </summary>
/// <param name="value">要设置的值</param>
public delegate void SetValueDelegate(object? value);
/// <summary>
/// 获取配置文件属性值委托
/// </summary>
/// <returns>获取的属性值</returns>
public delegate object? GetValueDelegate();