mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-04 00:34:26 -05:00 
			
		
		
		
	Fully moved to Ninject, fixed issues with medusa (un)loadabaility
This commit is contained in:
		@@ -8,7 +8,9 @@ using NadekoBot.Modules.Utility;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using Ninject;
 | 
			
		||||
using Ninject.Extensions.Conventions;
 | 
			
		||||
using Ninject.Extensions.Conventions.Syntax;
 | 
			
		||||
using Ninject.Infrastructure.Language;
 | 
			
		||||
using Ninject.Planning;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
@@ -102,7 +104,14 @@ public sealed class Bot
 | 
			
		||||
            AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var kernel = new StandardKernel();
 | 
			
		||||
        var kernel = new StandardKernel(new NinjectSettings()
 | 
			
		||||
        {
 | 
			
		||||
            ThrowOnGetServiceNotFound = true,
 | 
			
		||||
            ActivationCacheDisabled = true,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        kernel.Components.Remove<IPlanner, Planner>();
 | 
			
		||||
        kernel.Components.Add<IPlanner, RemovablePlanner>();
 | 
			
		||||
 | 
			
		||||
        kernel.Bind<IBotCredentials>().ToMethod(_ => _credsProvider.GetCreds()).InTransientScope();
 | 
			
		||||
 | 
			
		||||
@@ -122,7 +131,6 @@ public sealed class Bot
 | 
			
		||||
              .AddCache(_creds)
 | 
			
		||||
              .AddHttpClients();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1")
 | 
			
		||||
        {
 | 
			
		||||
            kernel.Bind<ICoordinator>().To<SingleProcessCoordinator>().InSingletonScope();
 | 
			
		||||
@@ -134,35 +142,25 @@ public sealed class Bot
 | 
			
		||||
 | 
			
		||||
        kernel.Bind(scan =>
 | 
			
		||||
        {
 | 
			
		||||
            var classes = scan.FromThisAssembly()
 | 
			
		||||
                              .SelectAllClasses()
 | 
			
		||||
                              .Where(c => (c.IsAssignableTo(typeof(INService))
 | 
			
		||||
                                           || c.IsAssignableTo(typeof(IExecOnMessage))
 | 
			
		||||
                                           || c.IsAssignableTo(typeof(IInputTransformer))
 | 
			
		||||
                                           || c.IsAssignableTo(typeof(IExecPreCommand))
 | 
			
		||||
                                           || c.IsAssignableTo(typeof(IExecPostCommand))
 | 
			
		||||
                                           || c.IsAssignableTo(typeof(IExecNoCommand)))
 | 
			
		||||
                                          && !c.HasAttribute<DontAddToIocContainerAttribute>()
 | 
			
		||||
#if GLOBAL_NADEKO
 | 
			
		||||
                                                && !c.HasAttribute<NoPublicBotAttribute>()
 | 
			
		||||
            scan.FromThisAssembly()
 | 
			
		||||
                .SelectAllClasses()
 | 
			
		||||
                .Where(c => (c.IsAssignableTo(typeof(INService))
 | 
			
		||||
                             || c.IsAssignableTo(typeof(IExecOnMessage))
 | 
			
		||||
                             || c.IsAssignableTo(typeof(IInputTransformer))
 | 
			
		||||
                             || c.IsAssignableTo(typeof(IExecPreCommand))
 | 
			
		||||
                             || c.IsAssignableTo(typeof(IExecPostCommand))
 | 
			
		||||
                             || c.IsAssignableTo(typeof(IExecNoCommand)))
 | 
			
		||||
                            && !c.HasAttribute<DontAddToIocContainerAttribute>()
 | 
			
		||||
#if GLOBAL_NADEK
 | 
			
		||||
                            && !c.HasAttribute<NoPublicBotAttribute>()
 | 
			
		||||
#endif
 | 
			
		||||
                              );
 | 
			
		||||
            classes
 | 
			
		||||
                .BindAllInterfaces()
 | 
			
		||||
                )
 | 
			
		||||
                .BindToSelfWithInterfaces()
 | 
			
		||||
                .Configure(c => c.InSingletonScope());
 | 
			
		||||
 | 
			
		||||
            classes.BindToSelf()
 | 
			
		||||
                   .Configure(c => c.InSingletonScope());
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        kernel.Bind<IServiceProvider>().ToConstant(kernel).InSingletonScope();
 | 
			
		||||
 | 
			
		||||
        var services = kernel.GetServices(typeof(INService));
 | 
			
		||||
        foreach (var s in services)
 | 
			
		||||
        {
 | 
			
		||||
            Console.WriteLine(s.GetType().FullName);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //initialize Services
 | 
			
		||||
        Services = kernel;
 | 
			
		||||
        Services.GetRequiredService<IBehaviorHandler>().Initialize();
 | 
			
		||||
@@ -187,16 +185,7 @@ public sealed class Bot
 | 
			
		||||
 | 
			
		||||
    private IEnumerable<object> LoadTypeReaders(Assembly assembly)
 | 
			
		||||
    {
 | 
			
		||||
        Type[] allTypes;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            allTypes = assembly.GetTypes();
 | 
			
		||||
        }
 | 
			
		||||
        catch (ReflectionTypeLoadException ex)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Warning(ex.LoaderExceptions[0], "Error getting types");
 | 
			
		||||
            return Enumerable.Empty<object>();
 | 
			
		||||
        }
 | 
			
		||||
        var allTypes = assembly.GetTypes();
 | 
			
		||||
 | 
			
		||||
        var filteredTypes = allTypes.Where(x => x.IsSubclassOf(typeof(TypeReader))
 | 
			
		||||
                                                && x.BaseType?.GetGenericArguments().Length > 0
 | 
			
		||||
@@ -205,10 +194,12 @@ public sealed class Bot
 | 
			
		||||
        var toReturn = new List<object>();
 | 
			
		||||
        foreach (var ft in filteredTypes)
 | 
			
		||||
        {
 | 
			
		||||
            var x = (TypeReader)ActivatorUtilities.CreateInstance(Services, ft);
 | 
			
		||||
            var baseType = ft.BaseType;
 | 
			
		||||
            if (baseType is null)
 | 
			
		||||
                continue;
 | 
			
		||||
            
 | 
			
		||||
            var x = (TypeReader)ActivatorUtilities.CreateInstance(Services, ft);
 | 
			
		||||
 | 
			
		||||
            var typeArgs = baseType.GetGenericArguments();
 | 
			
		||||
            _commandService.AddTypeReader(typeArgs[0], x);
 | 
			
		||||
            toReturn.Add(x);
 | 
			
		||||
 
 | 
			
		||||
@@ -3,34 +3,33 @@ using System.Runtime.Loader;
 | 
			
		||||
 | 
			
		||||
namespace Nadeko.Medusa;
 | 
			
		||||
 | 
			
		||||
public sealed class MedusaAssemblyLoadContext : AssemblyLoadContext
 | 
			
		||||
public class MedusaAssemblyLoadContext : AssemblyLoadContext
 | 
			
		||||
{
 | 
			
		||||
    private readonly AssemblyDependencyResolver _depResolver;
 | 
			
		||||
    private readonly AssemblyDependencyResolver _resolver;
 | 
			
		||||
    
 | 
			
		||||
    public MedusaAssemblyLoadContext(string folderPath) : base(isCollectible: true)
 | 
			
		||||
        => _resolver = new(folderPath);
 | 
			
		||||
 | 
			
		||||
    public MedusaAssemblyLoadContext(string pluginPath) : base(isCollectible: true)
 | 
			
		||||
    {
 | 
			
		||||
        _depResolver = new(pluginPath);
 | 
			
		||||
    }
 | 
			
		||||
    // public Assembly MainAssembly { get; private set; }
 | 
			
		||||
 | 
			
		||||
    protected override Assembly? Load(AssemblyName assemblyName)
 | 
			
		||||
    {
 | 
			
		||||
        var assemblyPath = _depResolver.ResolveAssemblyToPath(assemblyName);
 | 
			
		||||
        var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
 | 
			
		||||
        if (assemblyPath != null)
 | 
			
		||||
        {
 | 
			
		||||
            return LoadFromAssemblyPath(assemblyPath);
 | 
			
		||||
            Assembly assembly = LoadFromAssemblyPath(assemblyPath);
 | 
			
		||||
            LoadDependencies(assembly);
 | 
			
		||||
            return assembly;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
 | 
			
		||||
    public void LoadDependencies(Assembly assembly)
 | 
			
		||||
    {
 | 
			
		||||
        var libraryPath = _depResolver.ResolveUnmanagedDllToPath(unmanagedDllName);
 | 
			
		||||
        if (libraryPath != null)
 | 
			
		||||
        foreach (var reference in assembly.GetReferencedAssemblies())
 | 
			
		||||
        {
 | 
			
		||||
            return LoadUnmanagedDllFromPath(libraryPath);
 | 
			
		||||
            Load(reference);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return IntPtr.Zero;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,50 +1,95 @@
 | 
			
		||||
using Ninject.Modules;
 | 
			
		||||
using Ninject.Extensions.Conventions;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using Ninject;
 | 
			
		||||
using Ninject.Activation;
 | 
			
		||||
using Ninject.Activation.Caching;
 | 
			
		||||
using Ninject.Modules;
 | 
			
		||||
using Ninject.Planning;
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
 | 
			
		||||
namespace Nadeko.Medusa;
 | 
			
		||||
 | 
			
		||||
public sealed class MedusaIoCKernelModule : NinjectModule
 | 
			
		||||
public sealed class MedusaNinjectModule : NinjectModule
 | 
			
		||||
{
 | 
			
		||||
    private Assembly _a;
 | 
			
		||||
    public override string Name { get; }
 | 
			
		||||
    private volatile bool _isLoaded = false;
 | 
			
		||||
    private readonly Dictionary<Type, Type[]> _types;
 | 
			
		||||
 | 
			
		||||
    public MedusaIoCKernelModule(string name, Assembly a)
 | 
			
		||||
    public MedusaNinjectModule(Assembly assembly, string name)
 | 
			
		||||
    {
 | 
			
		||||
        Name = name;
 | 
			
		||||
        _a = a;
 | 
			
		||||
        _types = assembly.GetExportedTypes()
 | 
			
		||||
            .Where(t => t.IsClass)
 | 
			
		||||
            .Where(t => t.GetCustomAttribute<svcAttribute>() is not null)
 | 
			
		||||
            .ToDictionary(x => x,
 | 
			
		||||
                type => type.GetInterfaces().ToArray());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override void Load()
 | 
			
		||||
    {
 | 
			
		||||
        // todo cehck for duplicate registrations with ninject.extensions.convention
 | 
			
		||||
        Kernel.Bind(conf =>
 | 
			
		||||
        {
 | 
			
		||||
            var transient = conf.From(_a)
 | 
			
		||||
                                .SelectAllClasses()
 | 
			
		||||
                                .WithAttribute<svcAttribute>(x => x.Lifetime == Lifetime.Transient);
 | 
			
		||||
            
 | 
			
		||||
            transient.BindAllInterfaces().Configure(x => x.InTransientScope());
 | 
			
		||||
            transient.BindToSelf().Configure(x => x.InTransientScope());
 | 
			
		||||
        if (_isLoaded)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
            var singleton = conf.From(_a)
 | 
			
		||||
                                .SelectAllClasses()
 | 
			
		||||
                                .WithAttribute<svcAttribute>(x => x.Lifetime == Lifetime.Singleton);
 | 
			
		||||
        foreach (var (type, data) in _types)
 | 
			
		||||
        {
 | 
			
		||||
            var attribute = type.GetCustomAttribute<svcAttribute>()!;
 | 
			
		||||
            var scope = GetScope(attribute.Lifetime);
 | 
			
		||||
 | 
			
		||||
            Bind(type)
 | 
			
		||||
                .ToSelf()
 | 
			
		||||
                .InScope(scope);
 | 
			
		||||
            
 | 
			
		||||
            singleton.BindAllInterfaces().Configure(x => x.InSingletonScope());
 | 
			
		||||
            singleton.BindToSelf().Configure(x => x.InSingletonScope());
 | 
			
		||||
        });
 | 
			
		||||
            foreach (var inter in data)
 | 
			
		||||
            {
 | 
			
		||||
                Bind(inter)
 | 
			
		||||
                    .ToMethod(x => x.Kernel.Get(type))
 | 
			
		||||
                    .InScope(scope);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        _isLoaded = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Func<IContext, object?> GetScope(Lifetime lt)
 | 
			
		||||
        => _ => lt switch
 | 
			
		||||
        {
 | 
			
		||||
            Lifetime.Singleton => this,
 | 
			
		||||
            Lifetime.Transient => null,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    public override void Unload()
 | 
			
		||||
    {
 | 
			
		||||
        // todo implement unload
 | 
			
		||||
        // Kernel.Unbind();
 | 
			
		||||
    }
 | 
			
		||||
        if (!_isLoaded)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
    public override void Dispose(bool disposing)
 | 
			
		||||
    {
 | 
			
		||||
        _a = null!;
 | 
			
		||||
        base.Dispose(disposing);
 | 
			
		||||
        var planner = (RemovablePlanner)Kernel.Components.Get<IPlanner>();
 | 
			
		||||
        var cache = Kernel.Components.Get<ICache>();
 | 
			
		||||
        foreach (var binding in this.Bindings)
 | 
			
		||||
        {
 | 
			
		||||
            Kernel.RemoveBinding(binding);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach (var type in _types.SelectMany(x => x.Value).Concat(_types.Keys))
 | 
			
		||||
        {
 | 
			
		||||
            var binds = Kernel.GetBindings(type);
 | 
			
		||||
 | 
			
		||||
            if (!binds.Any())
 | 
			
		||||
            {
 | 
			
		||||
                Unbind(type);
 | 
			
		||||
                
 | 
			
		||||
                planner.RemovePlan(type);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        Bindings.Clear();
 | 
			
		||||
 | 
			
		||||
        cache.Clear(this);
 | 
			
		||||
        _types.Clear();
 | 
			
		||||
        
 | 
			
		||||
        // in case the library uses System.Text.Json
 | 
			
		||||
        var assembly = typeof(JsonSerializerOptions).Assembly;
 | 
			
		||||
        var updateHandlerType = assembly.GetType("System.Text.Json.JsonSerializerOptionsUpdateHandler");
 | 
			
		||||
        var clearCacheMethod = updateHandlerType?.GetMethod("ClearCache", BindingFlags.Static | BindingFlags.Public);
 | 
			
		||||
        clearCacheMethod?.Invoke(null, new object?[] { null }); 
 | 
			
		||||
        
 | 
			
		||||
        _isLoaded = false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -237,8 +237,10 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
                    SnekInfos: snekData.ToImmutableArray(),
 | 
			
		||||
                    strings,
 | 
			
		||||
                    typeReaders,
 | 
			
		||||
                    execs,
 | 
			
		||||
                    kernelModule);
 | 
			
		||||
                    execs)
 | 
			
		||||
                {
 | 
			
		||||
                    KernelModule = kernelModule
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                _medusaConfig.AddLoadedMedusa(safeName);
 | 
			
		||||
@@ -319,18 +321,28 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
        ctxWr = null;
 | 
			
		||||
        snekData = null;
 | 
			
		||||
 | 
			
		||||
        var path = $"{BASE_DIR}/{safeName}/{safeName}.dll";
 | 
			
		||||
        strings = MedusaStrings.CreateDefault($"{BASE_DIR}/{safeName}");
 | 
			
		||||
        var path = Path.GetFullPath($"{BASE_DIR}/{safeName}/{safeName}.dll");
 | 
			
		||||
        var dir = Path.GetFullPath($"{BASE_DIR}/{safeName}");
 | 
			
		||||
 | 
			
		||||
        if (!Directory.Exists(dir))
 | 
			
		||||
            throw new DirectoryNotFoundException($"Medusa folder not found: {dir}");
 | 
			
		||||
        
 | 
			
		||||
        if (!File.Exists(path))
 | 
			
		||||
            throw new FileNotFoundException($"Medusa dll not found: {path}");
 | 
			
		||||
 | 
			
		||||
        strings = MedusaStrings.CreateDefault(dir);
 | 
			
		||||
        var ctx = new MedusaAssemblyLoadContext(Path.GetDirectoryName(path)!);
 | 
			
		||||
        var a = ctx.LoadFromAssemblyPath(Path.GetFullPath(path));
 | 
			
		||||
        ctx.LoadDependencies(a);
 | 
			
		||||
 | 
			
		||||
        // load services
 | 
			
		||||
        ninjectModule = new MedusaIoCKernelModule(safeName, a);
 | 
			
		||||
        ninjectModule = new MedusaNinjectModule(a, safeName);
 | 
			
		||||
        _kernel.Load(ninjectModule);
 | 
			
		||||
        
 | 
			
		||||
        var sis = LoadSneksFromAssembly(safeName, a);
 | 
			
		||||
        typeReaders = LoadTypeReadersFromAssembly(a, strings);
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        // todo allow this
 | 
			
		||||
        if (sis.Count == 0)
 | 
			
		||||
        {
 | 
			
		||||
            _kernel.Unload(safeName);
 | 
			
		||||
@@ -590,8 +602,14 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
            await DisposeSnekInstances(lsi);
 | 
			
		||||
 | 
			
		||||
            var lc = lsi.LoadContext;
 | 
			
		||||
 | 
			
		||||
            // lsi.KernelModule = null!;
 | 
			
		||||
            var km = lsi.KernelModule;
 | 
			
		||||
            lsi.KernelModule = null!;
 | 
			
		||||
           
 | 
			
		||||
            _kernel.Unload(km.Name);
 | 
			
		||||
            
 | 
			
		||||
            if (km is IDisposable d)
 | 
			
		||||
                d.Dispose();
 | 
			
		||||
            
 | 
			
		||||
            lsi = null;
 | 
			
		||||
 | 
			
		||||
            _medusaConfig.RemoveLoadedMedusa(name);
 | 
			
		||||
@@ -650,7 +668,9 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
    private void UnloadContext(WeakReference<MedusaAssemblyLoadContext> lsiLoadContext)
 | 
			
		||||
    {
 | 
			
		||||
        if (lsiLoadContext.TryGetTarget(out var ctx))
 | 
			
		||||
        {
 | 
			
		||||
            ctx.Unload();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void GcCleanup()
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,8 @@ public sealed record ResolvedMedusa(
 | 
			
		||||
    IImmutableList<SnekInfo> SnekInfos,
 | 
			
		||||
    IMedusaStrings Strings,
 | 
			
		||||
    Dictionary<Type, TypeReader> TypeReaders,
 | 
			
		||||
    IReadOnlyCollection<ICustomBehavior> Execs,
 | 
			
		||||
    INinjectModule KernelModule)
 | 
			
		||||
    IReadOnlyCollection<ICustomBehavior> Execs
 | 
			
		||||
)
 | 
			
		||||
{
 | 
			
		||||
    public INinjectModule KernelModule { get; set; }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										122
									
								
								src/NadekoBot/Common/Medusa/RemovablePlanner.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/NadekoBot/Common/Medusa/RemovablePlanner.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
			
		||||
//-------------------------------------------------------------------------------
 | 
			
		||||
// <copyright file="Planner.cs" company="Ninject Project Contributors">
 | 
			
		||||
//   Copyright (c) 2007-2009, Enkari, Ltd.
 | 
			
		||||
//   Copyright (c) 2009-2011 Ninject Project Contributors
 | 
			
		||||
//   Authors: Nate Kohari (nate@enkari.com)
 | 
			
		||||
//            Remo Gloor (remo.gloor@gmail.com)
 | 
			
		||||
//           
 | 
			
		||||
//   Dual-licensed under the Apache License, Version 2.0, and the Microsoft Public License (Ms-PL).
 | 
			
		||||
//   you may not use this file except in compliance with one of the Licenses.
 | 
			
		||||
//   You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//   or
 | 
			
		||||
//       http://www.microsoft.com/opensource/licenses.mspx
 | 
			
		||||
//
 | 
			
		||||
//   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
//   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
//   See the License for the specific language governing permissions and
 | 
			
		||||
//   limitations under the License.
 | 
			
		||||
// </copyright>
 | 
			
		||||
//-------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
// ReSharper disable all
 | 
			
		||||
#pragma warning disable
 | 
			
		||||
 | 
			
		||||
namespace Ninject.Planning;
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using Ninject.Components;
 | 
			
		||||
using Ninject.Infrastructure.Language;
 | 
			
		||||
using Ninject.Planning.Strategies;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// Generates plans for how to activate instances.
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class RemovablePlanner : NinjectComponent, IPlanner
 | 
			
		||||
{
 | 
			
		||||
    private readonly ReaderWriterLock plannerLock = new ReaderWriterLock();
 | 
			
		||||
    private readonly Dictionary<Type, IPlan> plans = new Dictionary<Type, IPlan>();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Initializes a new instance of the <see cref="RemovablePlanner"/> class.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="strategies">The strategies to execute during planning.</param>
 | 
			
		||||
    public RemovablePlanner(IEnumerable<IPlanningStrategy> strategies)
 | 
			
		||||
    {
 | 
			
		||||
        this.Strategies = strategies.ToList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Gets the strategies that contribute to the planning process.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public IList<IPlanningStrategy> Strategies { get; private set; }
 | 
			
		||||
        
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Gets or creates an activation plan for the specified type.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="type">The type for which a plan should be created.</param>
 | 
			
		||||
    /// <returns>The type's activation plan.</returns>
 | 
			
		||||
    public IPlan GetPlan(Type type)
 | 
			
		||||
    {
 | 
			
		||||
        this.plannerLock.AcquireReaderLock(Timeout.Infinite);
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            IPlan plan;
 | 
			
		||||
            return this.plans.TryGetValue(type, out plan) ? plan : this.CreateNewPlan(type);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            this.plannerLock.ReleaseReaderLock();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Creates an empty plan for the specified type.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="type">The type for which a plan should be created.</param>
 | 
			
		||||
    /// <returns>The created plan.</returns>
 | 
			
		||||
    protected virtual IPlan CreateEmptyPlan(Type type)
 | 
			
		||||
    {
 | 
			
		||||
        return new Plan(type);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Creates a new plan for the specified type.
 | 
			
		||||
    /// This method requires an active reader lock!
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="type">The type.</param>
 | 
			
		||||
    /// <returns>The newly created plan.</returns>
 | 
			
		||||
    private IPlan CreateNewPlan(Type type)
 | 
			
		||||
    {
 | 
			
		||||
        var lockCooki = this.plannerLock.UpgradeToWriterLock(Timeout.Infinite);
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            IPlan plan;
 | 
			
		||||
            if (this.plans.TryGetValue(type, out plan))
 | 
			
		||||
            {
 | 
			
		||||
                return plan;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            plan = this.CreateEmptyPlan(type);
 | 
			
		||||
            this.plans.Add(type, plan);
 | 
			
		||||
            this.Strategies.Map(s => s.Execute(plan));
 | 
			
		||||
 | 
			
		||||
            return plan;
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            this.plannerLock.DowngradeFromWriterLock(ref lockCooki);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void RemovePlan(Type type)
 | 
			
		||||
    {
 | 
			
		||||
        plans.Remove(type);
 | 
			
		||||
        plans.TrimExcess();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -482,10 +482,10 @@ public abstract class NadekoContext : DbContext
 | 
			
		||||
        #endregion
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#if DEBUG
 | 
			
		||||
    private static readonly ILoggerFactory _debugLoggerFactory = LoggerFactory.Create(x => x.AddConsole());
 | 
			
		||||
 | 
			
		||||
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
 | 
			
		||||
        => optionsBuilder.UseLoggerFactory(_debugLoggerFactory);
 | 
			
		||||
#endif
 | 
			
		||||
// #if DEBUG
 | 
			
		||||
//     private static readonly ILoggerFactory _debugLoggerFactory = LoggerFactory.Create(x => x.AddConsole());
 | 
			
		||||
//
 | 
			
		||||
//     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
 | 
			
		||||
//         => optionsBuilder.UseLoggerFactory(_debugLoggerFactory);
 | 
			
		||||
// #endif
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
<Project>
 | 
			
		||||
    <ItemDefinitionGroup>
 | 
			
		||||
        <ProjectReference>
 | 
			
		||||
            <PrivateAssets>all</PrivateAssets>
 | 
			
		||||
        </ProjectReference>
 | 
			
		||||
    </ItemDefinitionGroup>
 | 
			
		||||
</Project>
 | 
			
		||||
@@ -55,6 +55,7 @@ public class CommandHandler : INService, IReadyExecutor
 | 
			
		||||
        _prefixes = bot.AllGuildConfigs.Where(x => x.Prefix is not null)
 | 
			
		||||
                       .ToDictionary(x => x.GuildId, x => x.Prefix)
 | 
			
		||||
                       .ToConcurrent();
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task OnReadyAsync()
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ using NadekoBot.Modules.Music.Resolvers;
 | 
			
		||||
using NadekoBot.Modules.Music.Services;
 | 
			
		||||
using Ninject;
 | 
			
		||||
using Ninject.Extensions.Conventions;
 | 
			
		||||
using Ninject.Extensions.Conventions.Syntax;
 | 
			
		||||
using StackExchange.Redis;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
@@ -39,10 +40,7 @@ public static class ServiceCollectionExtensions
 | 
			
		||||
                           .SelectAllClasses()
 | 
			
		||||
                           .Where(f => f.IsAssignableToGenericType(typeof(ConfigServiceBase<>)));
 | 
			
		||||
 | 
			
		||||
            // todo check for duplicates
 | 
			
		||||
            configs.BindToSelf()
 | 
			
		||||
                   .Configure(c => c.InSingletonScope());
 | 
			
		||||
            configs.BindAllInterfaces()
 | 
			
		||||
            configs.BindToSelfWithInterfaces()
 | 
			
		||||
                   .Configure(c => c.InSingletonScope());
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@@ -64,7 +62,7 @@ public static class ServiceCollectionExtensions
 | 
			
		||||
        kernel.Bind<ILocalTrackResolver>().To<LocalTrackResolver>().InSingletonScope();
 | 
			
		||||
        kernel.Bind<IRadioResolver>().To<RadioResolver>().InSingletonScope();
 | 
			
		||||
        kernel.Bind<ITrackCacher>().To<TrackCacher>().InSingletonScope();
 | 
			
		||||
        kernel.Bind<YtLoader>().ToSelf().InSingletonScope();
 | 
			
		||||
        // kernel.Bind<YtLoader>().ToSelf().InSingletonScope();
 | 
			
		||||
 | 
			
		||||
        return kernel;
 | 
			
		||||
    }
 | 
			
		||||
@@ -77,8 +75,7 @@ public static class ServiceCollectionExtensions
 | 
			
		||||
                           .SelectAllClasses()
 | 
			
		||||
                           .Where(c => c.IsPublic && c.IsNested && baseType.IsAssignableFrom(baseType));
 | 
			
		||||
 | 
			
		||||
            classes.BindAllInterfaces().Configure(x => x.InSingletonScope());
 | 
			
		||||
            classes.BindToSelf().Configure(x => x.InSingletonScope());
 | 
			
		||||
            classes.BindToSelfWithInterfaces().Configure(x => x.InSingletonScope());
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return kernel;
 | 
			
		||||
@@ -125,4 +122,7 @@ public static class ServiceCollectionExtensions
 | 
			
		||||
 | 
			
		||||
        return kernel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static IConfigureSyntax BindToSelfWithInterfaces(this IJoinExcludeIncludeBindSyntax matcher)
 | 
			
		||||
        => matcher.BindSelection((type, types) => types.Append(type));
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user