XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEExtension.NetCore.AutoConfig

【DLL】自动实现配置文件的存储

公开
关注 0 Fork 0 Star 0
UTF-8
namespace XFEExtension.NetCore.AutoConfig;

/// <summary>
/// 为同一种配置类型创建相互独立的实例和文件存储。适用于多租户、多账号或多工作区场景。
/// </summary>
/// <typeparam name="TProfile">配置类型</typeparam>
public sealed class ProfileStore<TProfile> where TProfile : XFEProfile, new()
{
    private readonly object syncRoot = new();
    private readonly Func<TProfile> profileFactory;
    private readonly string profilePath;
    private TProfile current;

    /// <summary>
    /// 创建配置存储。
    /// </summary>
    /// <param name="profilePath">包含扩展名的完整配置文件路径</param>
    /// <param name="autoLoad">是否在创建时读取文件</param>
    /// <param name="profileFactory">可选实例工厂;省略时调用公共无参构造函数</param>
    public ProfileStore(string profilePath, bool autoLoad = true, Func<TProfile>? profileFactory = null)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(profilePath);
        this.profilePath = Path.GetFullPath(profilePath);
        this.profileFactory = profileFactory ?? (static () => new TProfile());
        current = CreateProfile();
        if (autoLoad)
            current = (TProfile)current.InstanceLoadProfile(CreateProfile);
    }

    /// <summary>实际使用的完整配置文件路径。</summary>
    public string ProfilePath => profilePath;

    /// <summary>当前实例。加载或导入只有在迁移和验证成功后才会替换它。</summary>
    public TProfile Current
    {
        get
        {
            lock (syncRoot)
                return current;
        }
    }

    /// <summary>重新从文件加载配置。</summary>
    public void Load()
    {
        lock (syncRoot)
            current = (TProfile)current.InstanceLoadProfile(CreateProfile);
    }

    /// <summary>立即保存当前配置。</summary>
    public void Save()
    {
        lock (syncRoot)
            current.InstanceSaveProfile();
    }

    /// <summary>异步保存当前配置。</summary>
    public Task SaveAsync(CancellationToken cancellationToken = default)
    {
        TProfile snapshot;
        lock (syncRoot)
            snapshot = current;
        return snapshot.InstanceSaveProfileAsync(cancellationToken);
    }

    /// <summary>等待调用前已经请求的自动保存完成。</summary>
    public Task FlushAsync(CancellationToken cancellationToken = default)
    {
        TProfile snapshot;
        lock (syncRoot)
            snapshot = current;
        return snapshot.InstanceFlushProfileAsync(cancellationToken);
    }

    /// <summary>删除此存储对应的配置文件。</summary>
    public void Delete()
    {
        lock (syncRoot)
            current.InstanceDeleteProfile();
    }

    /// <summary>导出包含版本元数据的配置文本。</summary>
    public string Export()
    {
        lock (syncRoot)
            return current.InstanceExportProfile();
    }

    /// <summary>以原始字节导出包含版本元数据的配置;MessagePack 模式不会产生 Base64 中间文本。</summary>
    public byte[] ExportBytes()
    {
        lock (syncRoot)
            return current.InstanceExportProfileBytes();
    }

    /// <summary>导入配置文本;迁移或验证失败时保持当前实例不变。</summary>
    public void Import(string profileContent)
    {
        ArgumentNullException.ThrowIfNull(profileContent);
        lock (syncRoot)
            current = (TProfile)current.InstanceImportProfile(profileContent, CreateProfile);
    }

    /// <summary>从原始字节导入配置;迁移或验证失败时保持当前实例不变。</summary>
    public void ImportBytes(ReadOnlyMemory<byte> profileContent)
    {
        lock (syncRoot)
            current = (TProfile)current.InstanceImportProfileBytes(profileContent, CreateProfile);
    }

    /// <summary>
    /// 在实例锁内修改配置,并在修改成功后请求一次合并自动保存。
    /// </summary>
    public void Update(Action<TProfile> update)
    {
        ArgumentNullException.ThrowIfNull(update);
        lock (syncRoot)
            lock (current.ProfileSyncRoot)
            {
                update(current);
                current.InstanceRequestSaveProfile();
            }
    }

    /// <summary>在实例锁内读取配置。</summary>
    public TResult Read<TResult>(Func<TProfile, TResult> read)
    {
        ArgumentNullException.ThrowIfNull(read);
        lock (syncRoot)
            lock (current.ProfileSyncRoot)
                return read(current);
    }

    private TProfile CreateProfile()
    {
        var profile = profileFactory() ?? throw new InvalidOperationException("配置实例工厂返回了 null");
        profile.Initialize();
        profile.CurrentProfilePath = profilePath;
        return profile;
    }
}