[fix]修正UdpServer在接收广播时连续启动接收的错误,在StarAgent中,此时可能收到广播包,SocketFlags是Broadcast,需要清空,否则报错“参考的对象类型不支持尝试的操作”; 无需设置SocketOptionName.PacketInformation,在ReceiveMessageFromAsync时会自动设置,并且支持ipv6;
石头 编写于 2024-10-10 00:36:00 石头 提交于 2024-10-10 00:45:43
X
using System.Collections.Concurrent;

namespace NewLife.Configuration;

/// <summary>复合配置提供者。常用于本地配置与网络配置的混合</summary>
public class CompositeConfigProvider : IConfigProvider
{
    #region 属性
    /// <summary>日志提供者集合</summary>
    /// <remarks>为了线程安全,使用数组</remarks>
    public IConfigProvider[] Configs { get; set; } //= new IConfigProvider[0];

    /// <summary>名称</summary>
    public String Name { get; set; }

    /// <summary>根元素</summary>
    public IConfigSection Root { get => Configs[0].Root; set => throw new NotImplementedException(); }

    /// <summary>所有键</summary>
    public ICollection<String> Keys
    {
        get
        {
            var ks = new List<String>();
            foreach (var cfg in Configs)
            {
                if (cfg.Keys != null)
                {
                    foreach (var item in cfg.Keys)
                    {
                        if (!ks.Contains(item)) ks.Add(item);
                    }
                }
            }

            return ks;
        }
    }

    /// <summary>是否新的配置文件</summary>
    public Boolean IsNew { get => Configs[0].IsNew; set => Configs[0].IsNew = value; }

    /// <summary>返回获取配置的委托</summary>
    public GetConfigCallback GetConfig => key => GetSection(key)?.Value;
    #endregion

    #region 构造
    ///// <summary>实例化</summary>
    //public CompositeConfigProvider() => Name = GetType().Name.TrimEnd("ConfigProvider");

    /// <summary>实例化</summary>
    /// <param name="configProvider1"></param>
    /// <param name="configProvider2"></param>
    public CompositeConfigProvider(IConfigProvider configProvider1, IConfigProvider configProvider2)
    {
        Name = GetType().Name.TrimEnd("ConfigProvider");

        Configs = [configProvider1, configProvider2];
    }

    /// <summary>添加</summary>
    /// <param name="configProviders"></param>
    public void Add(params IConfigProvider[] configProviders)
    {
        var list = new List<IConfigProvider>(Configs);
        list.AddRange(configProviders);

        Configs = list.ToArray();
    }
    #endregion

    #region 取值
    /// <summary>获取 或 设置 配置值</summary>
    /// <param name="key">键</param>
    /// <returns></returns>
    public String? this[String key]
    {
        get
        {
            foreach (var cfg in Configs)
            {
                var value = cfg[key];
                if (value != null) return value;
            }

            return null;
        }
        set
        {
            foreach (var cfg in Configs)
            {
                //cfg[key] = value;
                var section = cfg.GetSection(key);
                if (section != null) section.Value = value;
            }
        }
    }

    /// <summary>查找配置项。可得到子级和配置</summary>
    /// <param name="key">配置名</param>
    /// <returns></returns>
    public IConfigSection? GetSection(String key)
    {
        foreach (var cfg in Configs)
        {
            var section = cfg.GetSection(key);
            if (section != null) return section;
        }

        return null;
    }
    #endregion

    #region 方法
    /// <summary>从数据源加载数据到配置树</summary>
    public Boolean LoadAll()
    {
        var rs = false;
        foreach (var cfg in Configs)
        {
            rs |= cfg.LoadAll();
        }

        return rs;
    }

    /// <summary>保存配置树到数据源</summary>
    public Boolean SaveAll()
    {
        var rs = false;
        foreach (var cfg in Configs)
        {
            rs |= cfg.SaveAll();
        }

        return rs;
    }

    /// <summary>加载配置到模型</summary>
    /// <typeparam name="T">模型。可通过实现IConfigMapping接口来自定义映射配置到模型实例</typeparam>
    /// <param name="path">路径。配置树位置,配置中心等多对象混合使用时</param>
    /// <returns></returns>
    public T? Load<T>(String? path = null) where T : new()
    {
        foreach (var cfg in Configs)
        {
            var model = cfg.Load<T>(path);
            if (model != null) return model;
        }

        return default;
    }

    /// <summary>保存模型实例</summary>
    /// <typeparam name="T">模型</typeparam>
    /// <param name="model">模型实例</param>
    /// <param name="path">路径。配置树位置</param>
    public Boolean Save<T>(T model, String? path = null)
    {
        foreach (var cfg in Configs)
        {
            var rs = cfg.Save(model, path);
            if (rs) return true;
        }

        return false;
    }
    #endregion

    #region 绑定
    private readonly ConcurrentDictionary<Object, String> _models = [];
    private readonly ConcurrentDictionary<Object, ModelWrap> _models2 = [];
    /// <summary>绑定模型,使能热更新,配置存储数据改变时同步修改模型属性</summary>
    /// <typeparam name="T">模型。可通过实现IConfigMapping接口来自定义映射配置到模型实例</typeparam>
    /// <param name="model">模型实例</param>
    /// <param name="autoReload">是否自动更新。默认true</param>
    /// <param name="path">命名空间。配置树位置,配置中心等多对象混合使用时</param>
    public virtual void Bind<T>(T model, Boolean autoReload = true, String? path = null)
    {
        if (model == null) return;

        // 如果有命名空间则使用指定层级数据源
        path ??= String.Empty;
        var source = GetSection(path);
        if (source != null)
        {
            if (model is IConfigMapping map)
                map.MapConfig(this, source);
            else
                source.MapTo(model, this);
        }

        if (autoReload)
        {
            _models.TryAdd(model, path);
        }

        AddChanged();
    }

    /// <summary>绑定模型,使能热更新,配置存储数据改变时同步修改模型属性</summary>
    /// <typeparam name="T">模型。可通过实现IConfigMapping接口来自定义映射配置到模型实例</typeparam>
    /// <param name="model">模型实例</param>
    /// <param name="path">命名空间。配置树位置,配置中心等多对象混合使用时</param>
    /// <param name="onChange">配置改变时执行的委托</param>
    public virtual void Bind<T>(T model, String? path, Action<IConfigSection> onChange)
    {
        if (model == null) return;

        // 如果有命名空间则使用指定层级数据源
        path ??= String.Empty;
        var source = GetSection(path);
        if (source != null)
        {
            if (model is IConfigMapping map)
                map.MapConfig(this, source);
            else
                source.MapTo(model, this);
        }

        if (onChange != null)
        {
            _models2.TryAdd(model, new ModelWrap(path, onChange));
        }

        AddChanged();
    }

    private record ModelWrap(String Path, Action<IConfigSection> OnChange);

    /// <summary>通知绑定对象,配置数据有改变</summary>
    protected virtual void NotifyChange()
    {
        foreach (var item in _models)
        {
            var model = item.Key;
            var source = GetSection(item.Value);
            if (source != null)
            {
                if (model is IConfigMapping map)
                    map.MapConfig(this, source);
                else
                    source.MapTo(model, this);
            }
        }
        foreach (var item in _models2)
        {
            var model = item.Key;
            var source = GetSection(item.Value.Path);
            if (source != null) item.Value.OnChange(source);
        }

        // 通过事件通知外部
        _Changed?.Invoke(this, EventArgs.Empty);
    }
    #endregion

    #region 配置变化
    private Int32 _count;

    private event EventHandler? _Changed;
    /// <summary>配置改变事件。执行了某些动作,可能导致配置数据发生改变时触发</summary>
    public event EventHandler Changed
    {
        add
        {
            _Changed += value;

            // 首次注册事件时,向内部提供者注册事件
            AddChanged();
        }
        remove
        {
            // 最后一次取消注册时,向内部提供者取消注册
            if (Interlocked.Decrement(ref _count) == 0)
            {
                foreach (var cfg in Configs)
                {
                    cfg.Changed -= OnChange;
                }
            }

            _Changed -= value;
        }
    }

    private void AddChanged()
    {
        if (Interlocked.Increment(ref _count) == 1)
        {
            foreach (var cfg in Configs)
            {
                cfg.Changed += OnChange;
            }
        }
    }

    private void OnChange(Object? sender, EventArgs e) => NotifyChange();
    #endregion
}