// Kyoo - A portable and vast media library solution. // Copyright (c) Kyoo. // // See AUTHORS.md and LICENSE file in the project root for full license information. // // Kyoo is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // Kyoo is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Loader; using Kyoo.Abstractions.Controllers; using Kyoo.Core.Models.Options; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Kyoo.Core.Controllers { /// /// An implementation of . /// This is used to load plugins and retrieve information from them. /// public class PluginManager : IPluginManager { /// /// The service provider. It allow plugin's activation. /// private readonly IServiceProvider _provider; /// /// The configuration to get the plugin's directory. /// private readonly IOptions _options; /// /// The logger used by this class. /// private readonly ILogger _logger; /// /// The list of plugins that are currently loaded. /// private readonly List _plugins = new(); /// /// Create a new instance. /// /// A service container to allow initialization of plugins /// The configuration instance, to get the plugin's directory path. /// The logger used by this class. public PluginManager(IServiceProvider provider, IOptions options, ILogger logger) { _provider = provider; _options = options; _logger = logger; } /// public T GetPlugin(string name) { return (T)_plugins?.FirstOrDefault(x => x.Name == name && x is T); } /// public ICollection GetPlugins() { return _plugins?.OfType().ToArray(); } /// public ICollection GetAllPlugins() { return _plugins; } /// /// Load a single plugin and return all IPlugin implementations contained in the Assembly. /// /// The path of the dll /// The list of dlls in hte assembly private IPlugin[] _LoadPlugin(string path) { path = Path.GetFullPath(path); try { PluginDependencyLoader loader = new(path); Assembly assembly = loader.LoadFromAssemblyPath(path); return assembly.GetTypes() .Where(x => typeof(IPlugin).IsAssignableFrom(x)) .Where(x => _plugins.All(y => y.GetType() != x)) .Select(x => (IPlugin)ActivatorUtilities.CreateInstance(_provider, x)) .ToArray(); } catch (Exception ex) { _logger.LogError(ex, "Could not load the plugin at {Path}", path); return Array.Empty(); } } /// public void LoadPlugins(ICollection plugins) { string pluginFolder = _options.Value.PluginPath; if (!Directory.Exists(pluginFolder)) Directory.CreateDirectory(pluginFolder); _logger.LogTrace("Loading new plugins..."); string[] pluginsPaths = Directory.GetFiles(pluginFolder, "*.dll", SearchOption.AllDirectories); _plugins.AddRange(plugins .Concat(pluginsPaths.SelectMany(_LoadPlugin)) .Where(x => x.Enabled) .GroupBy(x => x.Name) .Select(x => x.First()) ); if (!_plugins.Any()) _logger.LogInformation("No plugin enabled"); else _logger.LogInformation("Plugin enabled: {Plugins}", _plugins.Select(x => x.Name)); } /// public void LoadPlugins(params Type[] plugins) { LoadPlugins(plugins .Select(x => (IPlugin)ActivatorUtilities.CreateInstance(_provider, x)) .ToArray() ); } /// /// A custom to load plugin's dependency if they are on the same folder. /// private class PluginDependencyLoader : AssemblyLoadContext { /// /// The basic resolver that will be used to load dlls. /// private readonly AssemblyDependencyResolver _resolver; /// /// Create a new for the given path. /// /// The path of the plugin and it's dependencies public PluginDependencyLoader(string pluginPath) { _resolver = new AssemblyDependencyResolver(pluginPath); } /// protected override Assembly Load(AssemblyName assemblyName) { Assembly existing = AppDomain.CurrentDomain.GetAssemblies() .FirstOrDefault(x => { AssemblyName name = x.GetName(); return name.Name == assemblyName.Name && name.Version == assemblyName.Version; }); if (existing != null) return existing; // TODO load the assembly from the common folder if the file exists (this would allow shared libraries) string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); if (assemblyPath != null) return LoadFromAssemblyPath(assemblyPath); return base.Load(assemblyName); } /// protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) { string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); if (libraryPath != null) return LoadUnmanagedDllFromPath(libraryPath); return base.LoadUnmanagedDll(unmanagedDllName); } } } }