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; /// /// XFE配置文件,实现配置文件读写自动化 /// public abstract class XFEProfile { private static readonly ConcurrentDictionary 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(); /// /// 配置文件所在的默认目录 /// public static string ProfilesDefaultPath { get; set; } = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Profiles"); /// /// 配置文件加载失败时触发。事件处理程序中的异常不会中断配置回退流程。 /// public static event EventHandler? ProfileLoadFailed; /// /// 自动保存的合并等待时间,默认为100毫秒。等待期间发生的多次变更只会合并为一次写入。 /// /// 设置了负数等待时间 [JsonIgnore] [XmlIgnore] public TimeSpan AutoSaveDelay { get { lock (saveStateSyncRoot) return autoSaveDelay; } set { if (value < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(value), "自动保存等待时间不能小于零"); lock (saveStateSyncRoot) autoSaveDelay = value; } } /// /// 最近一次后台自动保存异常;最近一次成功保存后会被清空。 /// [JsonIgnore] [XmlIgnore] public Exception? LastSaveException { get { lock (saveStateSyncRoot) return lastSaveException; } } /// /// 最近一次配置加载异常;成功加载或配置文件不存在时会被清空。 /// [JsonIgnore] [XmlIgnore] public Exception? LastLoadException { get { lock (loadStateSyncRoot) return lastLoadException; } } /// /// 当前配置格式的版本号。大于零时会写入配置文件;提升版本后必须在 中注册逐版本迁移。 /// [JsonIgnore] [XmlIgnore] protected virtual int ProfileSchemaVersion => 0; /// /// 最近一次成功加载或导入的源配置版本。未带版本信息的旧文件视为版本零。 /// [JsonIgnore] [XmlIgnore] public int LoadedProfileVersion { get { lock (loadStateSyncRoot) return loadedProfileVersion; } private set { lock (loadStateSyncRoot) loadedProfileVersion = value; } } /// /// 当前实例使用的 JSON 序列化选项。适用于 JSON 模式以及 XFE 字典中各属性值的序列化。 /// 可在配置类型构造函数中设置命名策略、类型信息解析器和自定义转换器。 /// [JsonIgnore] [XmlIgnore] public JsonSerializerOptions JsonOptions { get; } = new(); /// /// 当前配置实例使用的 MessagePack 序列化选项。默认启用 ContractlessStandardResolver, /// 因而普通公开属性对象无需添加 MessagePack 特性即可保存。 /// [JsonIgnore] [XmlIgnore] public MessagePackSerializerOptions MessagePackOptions { get => messagePackOptions; set => messagePackOptions = value ?? throw new ArgumentNullException(nameof(value)); } /// /// 当前配置实例用于协调属性访问和序列化快照的同步对象 /// internal protected object ProfileSyncRoot => profileSyncRoot; /// /// 配置文件存储位置 /// internal protected string CurrentProfilePath { get; set; } = string.Empty; /// /// 配置文件扩展名 /// internal protected string CurrentProfileExtension { get; set; } = ".xpf"; /// /// 默认配置文件存储和读取的操作模式 /// internal protected ProfileOperationMode DefaultProfileOperationMode { get; set; } = ProfileOperationMode.XFEDictionary; /// /// 加载操作 /// internal protected ProfileLoadOperation LoadOperation { get; set; } = XFEDictionaryLoadProfileOperation; /// /// 保存操作 /// internal protected ProfileSaveOperation SaveOperation { get; set; } = XFEDictionarySaveProfileOperation; /// /// 配置文件 “属性名称-属性类型” 字典 /// internal protected Dictionary PropertyInfoDictionary { get; set; } = []; /// /// 配置文件 “属性名称-属性设置方法” 字典 /// internal protected Dictionary PropertySetFuncDictionary { get; set; } = []; /// /// 配置文件 “属性名称-属性获取方法” 字典 /// internal protected Dictionary PropertyGetFuncDictionary { get; set; } = []; /// /// 通过XFE字典加载配置文件方法(默认) /// /// 配置文件实例 /// 配置文件字符串 /// 配置文件 “属性名称-属性类型” 字典 /// 配置文件 “属性名称-属性设置方法” 字典 /// 配置文件实例 [RequiresDynamicCode("XFE 字典模式需要为配置属性生成 JSON 序列化代码。Native AOT 应为 JsonOptions 配置源生成的 JsonTypeInfoResolver。")] [RequiresUnreferencedCode("XFE 字典模式按运行时类型序列化属性,裁剪可能移除所需成员。")] public static XFEProfile? XFEDictionaryLoadProfileOperation(XFEProfile profileInstance, string profileString, Dictionary propertyInfoDictionary, Dictionary 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; } /// /// 通过XFE字典保存配置文件方法(默认) /// /// 配置文件实例 /// 配置文件 “属性名称-属性类型” 字典 /// 配置文件 “属性名称-属性值获取方法” 字典 /// 保存内容 [RequiresDynamicCode("XFE 字典模式需要为配置属性生成 JSON 序列化代码。Native AOT 应为 JsonOptions 配置源生成的 JsonTypeInfoResolver。")] [RequiresUnreferencedCode("XFE 字典模式按运行时类型序列化属性,裁剪可能移除所需成员。")] public static string XFEDictionarySaveProfileOperation(XFEProfile profileInstance, Dictionary propertyInfoDictionary, Dictionary 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(); } /// /// 通过Json加载配置文件方法 /// /// 配置文件实例 /// 配置文件字符串 /// 配置文件 “属性名称-属性类型” 字典 /// 配置文件 “属性名称-属性设置方法” 字典 /// 配置文件实例 [RequiresDynamicCode("反射式 JSON 序列化可能需要运行时生成代码。Native AOT 应为 JsonOptions 配置源生成的 JsonTypeInfoResolver。")] [RequiresUnreferencedCode("反射式 JSON 序列化可能访问被裁剪的成员。")] public static XFEProfile? JsonLoadProfileOperation(XFEProfile profileInstance, string profileString, Dictionary propertyInfoDictionary, Dictionary propertySetFuncDictionary) => JsonSerializer.Deserialize(profileString, profileInstance.GetType(), profileInstance.JsonOptions) is XFEProfile xFEProfile ? xFEProfile : null; /// /// 通过Json保存配置文件方法 /// /// 配置文件实例 /// 配置文件 “属性名称-属性类型” 字典 /// 配置文件 “属性名称-属性值获取方法” 字典 /// 保存内容 [RequiresDynamicCode("反射式 JSON 序列化可能需要运行时生成代码。Native AOT 应为 JsonOptions 配置源生成的 JsonTypeInfoResolver。")] [RequiresUnreferencedCode("反射式 JSON 序列化可能访问被裁剪的成员。")] public static string JsonSaveProfileOperation(XFEProfile profileInstance, Dictionary propertyInfoDictionary, Dictionary propertyGetFuncDictionary) => profileInstance is null ? string.Empty : JsonSerializer.Serialize(profileInstance, profileInstance.GetType(), profileInstance.JsonOptions); /// /// 通过XML加载配置文件方法 /// /// 配置文件实例 /// 配置文件字符串 /// 配置文件 “属性名称-属性类型” 字典 /// 配置文件 “属性名称-属性设置方法” 字典 /// 配置文件实例 [RequiresDynamicCode("XmlSerializer 可能在运行时生成序列化程序集,不保证支持 Native AOT。")] [RequiresUnreferencedCode("XmlSerializer 按反射访问成员,裁剪可能移除所需成员。")] public static XFEProfile? XmlLoadProfileOperation(XFEProfile profileInstance, string profileString, Dictionary propertyInfoDictionary, Dictionary propertySetFuncDictionary) => !string.IsNullOrEmpty(profileString) && new XmlSerializer(profileInstance.GetType()).Deserialize(new MemoryStream(Encoding.UTF8.GetBytes(profileString))) is XFEProfile xFEProfile ? xFEProfile : null; /// /// 通过XML保存配置文件方法 /// /// 配置文件实例 /// 配置文件 “属性名称-属性类型” 字典 /// 配置文件 “属性名称-属性值获取方法” 字典 /// 保存内容 [RequiresDynamicCode("XmlSerializer 可能在运行时生成序列化程序集,不保证支持 Native AOT。")] [RequiresUnreferencedCode("XmlSerializer 按反射访问成员,裁剪可能移除所需成员。")] public static string XmlSaveProfileOperation(XFEProfile profileInstance, Dictionary propertyInfoDictionary, Dictionary 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(); } /// /// 加载配置文件 /// /// 配置文件实例 internal protected XFEProfile InstanceLoadProfile(Func 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; } /// /// 请求自动保存配置文件。短时间内的多个请求会合并为一次写入。 /// internal protected void InstanceRequestSaveProfile() { lock (saveStateSyncRoot) { requestedSaveVersion++; if (autoSaveWorkerRunning) return; autoSaveWorkerRunning = true; } _ = Task.Run(AutoSaveWorkerAsync); } /// /// 立即保存配置文件,并等待数据写入完成。 /// internal protected void InstanceSaveProfile() => InstanceSaveProfileAsync().GetAwaiter().GetResult(); /// /// 立即异步保存配置文件,并等待数据写入完成。 /// /// 表示保存操作的任务 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(); } } /// /// 等待并写入调用前已经请求的自动保存。 /// /// 取消令牌 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(); } } /// /// 删除配置文件 /// 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(); } } /// /// 导出配置文件 /// /// internal protected string InstanceExportProfile() { lock (profileSyncRoot) { var content = SerializeProfileBytes(); return DefaultProfileOperationMode == ProfileOperationMode.MessagePack ? Convert.ToBase64String(content) : ProfileFileEncoding.GetString(content); } } /// /// 以原始字节导出配置文件。MessagePack 模式不会产生 Base64 中间文本。 /// internal protected byte[] InstanceExportProfileBytes() { lock (profileSyncRoot) return SerializeProfileBytes(); } /// /// 导入配置文件 /// /// 配置文件字符串 /// 用于创建候选配置的工厂 /// internal protected XFEProfile InstanceImportProfile(string profileString, Func profileFactory) { ArgumentNullException.ThrowIfNull(profileString); var profileContent = DefaultProfileOperationMode == ProfileOperationMode.MessagePack ? Convert.FromBase64String(profileString) : ProfileFileEncoding.GetBytes(profileString); return InstanceImportProfileBytes(profileContent, profileFactory); } /// /// 从原始字节导入配置文件。 /// internal protected XFEProfile InstanceImportProfileBytes(ReadOnlyMemory profileContent, Func 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 profileContent, string profilePath, Func 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 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(); } } } /// /// 设置配置文件加载和存储操作 /// 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; } } /// /// 初始化 /// public virtual void Initialize() => SetProfileOperation(); /// /// 注册从旧版本到 的逐版本迁移步骤。 /// /// 迁移注册器 protected virtual void ConfigureMigrations(ProfileMigrationBuilder migrations) { } /// /// 验证完成反序列化和迁移后的候选配置。返回失败时不会替换当前配置。 /// protected virtual ProfileValidationResult ValidateProfile() => ProfileValidationResult.Success; /// /// Custom 模式读取并移除版本元数据的扩展点。默认把内容视为版本零且不修改内容。 /// protected virtual (int Version, string Content) ReadCustomProfileVersion(string content) => (0, content); /// /// Custom 模式写入版本元数据的扩展点。默认不修改内容。 /// protected virtual string WriteCustomProfileVersion(string content, int version) => content; #region 已过时 [Obsolete("SaveProfilesFunc属性现已过时,对于每个配置文件实例,请使用 XXXProfile.SaveOperation")] private static Func 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 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; }; /// /// 配置文件清单 /// [Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")] public static List Profiles { get; private set; } = []; /// /// 加载配置文件 /// /// 配置文件信息 /// [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; } } } } } } /// /// 加载配置文件 /// /// 配置文件信息 /// [Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")] public static async Task LoadProfilesAsync(params ProfileInfo[] profileInfo) => await Task.Run(() => LoadProfiles(profileInfo)); /// /// 储存指定的配置文件 /// /// 配置文件 /// [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); } /// /// 储存配置文件 /// /// [Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")] public static void SaveProfiles() { foreach (var profile in Profiles) SaveProfile(profile); } /// /// 储存指定的配置文件 /// /// 配置文件 /// [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); } /// /// 储存配置文件 /// /// [Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")] public static async Task SaveProfilesAsync() { foreach (var profile in Profiles) await SaveProfileAsync(profile); } /// /// 删除指定的配置文件 /// /// 指定的配置文件 [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); } /// /// 删除指定的配置文件 /// /// 指定的配置文件 /// [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); }); } /// /// 删除所有配置文件 /// [Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")] public static void DeleteProfiles() { foreach (var profile in Profiles) DeleteProfile(profile); } /// /// 删除所有配置文件 /// /// [Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")] public static async Task DeleteProfilesAsync() { foreach (var profile in Profiles) await DeleteProfileAsync(profile); } /// /// 设置储存配置文件的方法 /// /// 储存方法 [Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")] public static void SetSaveProfilesFunction(Func saveProfilesFunc) => SaveProfilesFunc = saveProfilesFunc; /// /// 设置加载配置文件的方法 /// /// 加载方法 [Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")] public static void SetLoadProfilesFunction(Func loadProfilesFunc) => LoadProfilesFunc = loadProfilesFunc; /// /// 可写在属性的set访问器后,用于自动储存 /// /// /// [Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")] protected static void AutoSave(ProfileInfo profileInfo) => SaveProfile(profileInfo); /// /// 可写在属性的set访问器后,用于自动储存 /// /// /// [Obsolete("XFEProfile现在不再对配置文件实行统一的管理,请对每个配置文件单独操作")] protected static async Task AutoSaveAsync(ProfileInfo profileInfo) => await SaveProfileAsync(profileInfo); /// /// 导出指定的配置文件 /// /// 指定的配置文件 /// [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(); } /// /// 导出所有配置文件 /// /// [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(); } /// /// 导入指定的配置文件
/// 本方法仅支持导入由导出的配置文件

/// 如需导入由导出的配置文件,请使用 ///
/// 指定的配置文件 /// 配置文件字符串 /// 导入后是否自动储存 [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); } /// /// 导入所有配置文件
/// 本方法仅支持导入由导出的配置文件

/// 如需导入由导出的配置文件,请使用 ///
/// 配置文件字符串 /// 导入后是否自动储存 [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 } /// /// 配置文件保存方法 /// /// 配置文件实例 /// 配置文件 “属性名称-属性类型” 字典 /// 配置文件 “属性名称-属性值获取方法” 字典 /// 保存内容 public delegate string ProfileSaveOperation(XFEProfile profileInstance, Dictionary propertyInfoDictionary, Dictionary propertyGetFuncDictionary); /// /// 配置文件加载方法 /// /// 配置文件实例 /// 配置文件字符串 /// 配置文件 “属性名称-属性类型” 字典 /// 配置文件 “属性名称-属性设置方法” 字典 public delegate XFEProfile? ProfileLoadOperation(XFEProfile profileInstance, string profileString, Dictionary propertyInfoDictionary, Dictionary propertySetFuncDictionary); /// /// 设置配置文件属性值委托 /// /// 要设置的值 public delegate void SetValueDelegate(object? value); /// /// 获取配置文件属性值委托 /// /// 获取的属性值 public delegate object? GetValueDelegate();