Plugins: Removing provide and require

This commit is contained in:
Zoe Roux 2021-08-13 11:25:16 +02:00
parent 6d1e1261f8
commit 2e0c96b228
10 changed files with 30 additions and 361 deletions

View File

@ -37,20 +37,8 @@ namespace Kyoo.Authentication
/// <inheritdoc />
public string Description => "Enable OpenID authentication for Kyoo.";
/// <inheritdoc />
public ICollection<Type> Provides => ArraySegment<Type>.Empty;
/// <inheritdoc />
public ICollection<ConditionalProvide> ConditionalProvides => ArraySegment<ConditionalProvide>.Empty;
/// <inheritdoc />
public ICollection<Type> Requires => new []
{
typeof(IUserRepository)
};
/// <summary>
/// The configuration to use.
/// </summary>
@ -88,7 +76,7 @@ namespace Kyoo.Authentication
}
/// <inheritdoc />
public void Configure(IServiceCollection services, ICollection<Type> availableTypes)
public void Configure(IServiceCollection services)
{
string publicUrl = _configuration.GetPublicUrl();

View File

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Autofac;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
@ -34,29 +32,15 @@ namespace Kyoo.Controllers
string Description { get; }
/// <summary>
/// A list of services that are provided by this service. This allow other plugins to declare dependencies
/// <c>true</c> if the plugin should be enabled, <c>false</c> otherwise.
/// If a plugin is not enabled, no configure method will be called.
/// This allow one to enable a plugin if a specific configuration value is set or if the environment contains
/// the right settings.
/// </summary>
/// <remarks>
/// You should put the type's interface that will be register in configure.
/// By default, a plugin is always enabled. This method can be overriden to change this behavior.
/// </remarks>
ICollection<Type> Provides { get; }
/// <summary>
/// A list of types that will be provided only if a condition is met. The condition can be an arbitrary method or
/// a condition based on other type availability. For more information, see <see cref="ConditionalProvides"/>.
/// </summary>
ICollection<ConditionalProvide> ConditionalProvides { get; }
/// <summary>
/// A list of services that are required by this plugin.
/// You can put services that you provide conditionally here if you want.
/// Kyoo will warn the user that this plugin can't be loaded if a required service is not found.
/// </summary>
/// <remarks>
/// Put here the most complete type that are needed for your plugin to work. If you need a LibraryManager,
/// put typeof(ILibraryManager).
/// </remarks>
ICollection<Type> Requires { get; }
virtual bool Enabled => true;
/// <summary>
/// A configure method that will be run on plugin's startup.
@ -69,14 +53,11 @@ namespace Kyoo.Controllers
/// <summary>
/// A configure method that will be run on plugin's startup.
/// This is available for libraries that build upon a <see cref="IServiceCollection"/>, for more precise
/// configuration use <see cref="Configure(Autofac.ContainerBuilder)"/>.
/// </summary>
/// <param name="services">A service container to register new services.</param>
/// <param name="availableTypes">The list of types that are available for this instance. This can be used
/// for conditional type. See <see cref="ProviderCondition.Has(System.Type,System.Collections.Generic.ICollection{System.Type})"/>
/// or <see cref="ProviderCondition.Has(System.Collections.Generic.ICollection{System.Type},System.Collections.Generic.ICollection{System.Type})"/>>
/// You can't simply check on the service collection because some dependencies might be registered after your plugin.
/// </param>
void Configure(IServiceCollection services, ICollection<Type> availableTypes)
void Configure(IServiceCollection services)
{
// Skipped
}
@ -105,143 +86,4 @@ namespace Kyoo.Controllers
// Skipped
}
}
/// <summary>
/// A type that will only be provided if a special condition is met. To check that your condition is met,
/// you can check the <see cref="ProviderCondition"/> class.
/// </summary>
public class ConditionalProvide : Tuple<Type, ProviderCondition>
{
/// <summary>
/// Get the type that may be provided
/// </summary>
public Type Type => Item1;
/// <summary>
/// Get the condition.
/// </summary>
public ProviderCondition Condition => Item2;
/// <summary>
/// Create a <see cref="ConditionalProvide"/> from a type and a condition.
/// </summary>
/// <param name="type">The type to provide</param>
/// <param name="condition">The condition</param>
public ConditionalProvide(Type type, ProviderCondition condition)
: base(type, condition)
{ }
/// <summary>
/// Create a <see cref="ConditionalProvide"/> from a tuple of (Type, ProviderCondition).
/// </summary>
/// <param name="tuple">The tuple to convert</param>
public ConditionalProvide((Type type, ProviderCondition condition) tuple)
: base(tuple.type, tuple.condition)
{ }
/// <summary>
/// Implicitly convert a tuple to a <see cref="ConditionalProvide"/>.
/// </summary>
/// <param name="tuple">The tuple to convert</param>
/// <returns>A new <see cref="ConditionalProvide"/> based on the given tuple.</returns>
public static implicit operator ConditionalProvide((Type, Type) tuple) => new (tuple);
}
/// <summary>
/// A condition for a conditional type.
/// </summary>
public class ProviderCondition
{
/// <summary>
/// The condition as a method. If true is returned, the type will be provided.
/// </summary>
public Func<bool> Condition { get; } = () => true;
/// <summary>
/// The list of types that this method needs.
/// </summary>
public ICollection<Type> Needed { get; } = ArraySegment<Type>.Empty;
/// <summary>
/// Create a new <see cref="ProviderCondition"/> from a raw function.
/// </summary>
/// <param name="condition">The predicate that will be used as condition</param>
public ProviderCondition(Func<bool> condition)
{
Condition = condition;
}
/// <summary>
/// Create a new <see cref="ProviderCondition"/> from a type. This allow you to inform that a type will
/// only be available if a dependency is met.
/// </summary>
/// <param name="needed">The type that you need</param>
public ProviderCondition(Type needed)
{
Needed = new[] {needed};
}
/// <summary>
/// Create a new <see cref="ProviderCondition"/> from a list of type. This allow you to inform that a type will
/// only be available if a list of dependencies are met.
/// </summary>
/// <param name="needed">The types that you need</param>
public ProviderCondition(ICollection<Type> needed)
{
Needed = needed;
}
/// <summary>
/// Create a new <see cref="ProviderCondition"/> with a list of types as dependencies and a predicate
/// for arbitrary conditions.
/// </summary>
/// <param name="needed">The list of dependencies</param>
/// <param name="condition">An arbitrary condition</param>
public ProviderCondition(ICollection<Type> needed, Func<bool> condition)
{
Needed = needed;
Condition = condition;
}
/// <summary>
/// Implicitly convert a type to a <see cref="ProviderCondition"/>.
/// </summary>
/// <param name="type">The type dependency</param>
/// <returns>A <see cref="ProviderCondition"/> that will return true if the given type is available.</returns>
public static implicit operator ProviderCondition(Type type) => new(type);
/// <summary>
/// Implicitly convert a list of type to a <see cref="ProviderCondition"/>.
/// </summary>
/// <param name="types">The list of type dependencies</param>
/// <returns>A <see cref="ProviderCondition"/> that will return true if the given types are available.</returns>
public static implicit operator ProviderCondition(Type[] types) => new(types);
/// <inheritdoc cref="op_Implicit(System.Type[])"/>
public static implicit operator ProviderCondition(List<Type> types) => new(types);
/// <summary>
/// Check if a type is available.
/// </summary>
/// <param name="needed">The type to check</param>
/// <param name="available">The list of types</param>
/// <returns>True if the dependency is met, false otherwise</returns>
public static bool Has(Type needed, ICollection<Type> available)
{
return available.Contains(needed);
}
/// <summary>
/// Check if a list of type are available.
/// </summary>
/// <param name="needed">The list of types to check</param>
/// <param name="available">The list of types</param>
/// <returns>True if the dependencies are met, false otherwise</returns>
public static bool Has(ICollection<Type> needed, ICollection<Type> available)
{
return needed.All(x => Has(x, available));
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using Kyoo.Controllers;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
@ -24,19 +23,6 @@ namespace Kyoo.Postgresql
/// <inheritdoc />
public string Description => "A database context for postgresql.";
/// <inheritdoc />
public ICollection<Type> Provides => new[]
{
typeof(DatabaseContext)
};
/// <inheritdoc />
public ICollection<ConditionalProvide> ConditionalProvides => ArraySegment<ConditionalProvide>.Empty;
/// <inheritdoc />
public ICollection<Type> Requires => ArraySegment<Type>.Empty;
/// <summary>
/// The configuration to use. The database connection string is pulled from it.
/// </summary>
@ -59,7 +45,7 @@ namespace Kyoo.Postgresql
}
/// <inheritdoc />
public void Configure(IServiceCollection services, ICollection<Type> availableTypes)
public void Configure(IServiceCollection services)
{
services.AddDbContext<DatabaseContext, PostgresContext>(x =>
{

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using Kyoo.Controllers;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
@ -23,19 +22,7 @@ namespace Kyoo.SqLite
/// <inheritdoc />
public string Description => "A database context for sqlite.";
/// <inheritdoc />
public ICollection<Type> Provides => new[]
{
typeof(DatabaseContext)
};
/// <inheritdoc />
public ICollection<ConditionalProvide> ConditionalProvides => ArraySegment<ConditionalProvide>.Empty;
/// <inheritdoc />
public ICollection<Type> Requires => ArraySegment<Type>.Empty;
/// <summary>
/// The configuration to use. The database connection string is pulled from it.
/// </summary>
@ -59,7 +46,7 @@ namespace Kyoo.SqLite
/// <inheritdoc />
public void Configure(IServiceCollection services, ICollection<Type> availableTypes)
public void Configure(IServiceCollection services)
{
services.AddDbContext<DatabaseContext, SqLiteContext>(x =>
{

View File

@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using Autofac;
using Kyoo.Controllers;
using Kyoo.Models.Attributes;
@ -23,20 +21,8 @@ namespace Kyoo.TheMovieDb
/// <inheritdoc />
public string Description => "A metadata provider for TheMovieDB.";
/// <inheritdoc />
public ICollection<Type> Provides => new []
{
typeof(IMetadataProvider)
};
/// <inheritdoc />
public ICollection<ConditionalProvide> ConditionalProvides => ArraySegment<ConditionalProvide>.Empty;
/// <inheritdoc />
public ICollection<Type> Requires => ArraySegment<Type>.Empty;
/// <summary>
/// The configuration to use.
/// </summary>
@ -65,7 +51,7 @@ namespace Kyoo.TheMovieDb
}
/// <inheritdoc />
public void Configure(IServiceCollection services, ICollection<Type> availableTypes)
public void Configure(IServiceCollection services)
{
services.Configure<TheMovieDbOptions>(_configuration.GetSection(TheMovieDbOptions.Path));
}

View File

@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using Autofac;
using Kyoo.Controllers;
using Kyoo.Models.Attributes;
@ -24,20 +22,7 @@ namespace Kyoo.TheTvdb
/// <inheritdoc />
public string Description => "A metadata provider for The TVDB.";
/// <inheritdoc />
public ICollection<Type> Provides => new []
{
typeof(IMetadataProvider)
};
/// <inheritdoc />
public ICollection<ConditionalProvide> ConditionalProvides => ArraySegment<ConditionalProvide>.Empty;
/// <inheritdoc />
public ICollection<Type> Requires => ArraySegment<Type>.Empty;
/// <summary>
/// The configuration to use.
/// </summary>
@ -67,7 +52,7 @@ namespace Kyoo.TheTvdb
}
/// <inheritdoc />
public void Configure(IServiceCollection services, ICollection<Type> availableTypes)
public void Configure(IServiceCollection services)
{
services.Configure<TvdbOption>(_configuration.GetSection(TvdbOption.Path));
}

View File

@ -112,22 +112,11 @@ namespace Kyoo.Controllers
_logger.LogTrace("Loading new plugins...");
string[] pluginsPaths = Directory.GetFiles(pluginFolder, "*.dll", SearchOption.AllDirectories);
plugins = plugins.Concat(pluginsPaths.SelectMany(LoadPlugin))
_plugins.AddRange(plugins
.Concat(pluginsPaths.SelectMany(LoadPlugin))
.GroupBy(x => x.Name)
.Select(x => x.First())
.ToList();
ICollection<Type> available = GetProvidedTypes(plugins);
_plugins.AddRange(plugins.Where(plugin =>
{
Type missing = plugin.Requires.FirstOrDefault(x => available.All(y => !y.IsAssignableTo(x)));
if (missing == null)
return true;
_logger.LogCritical("No {Dependency} available in Kyoo but the plugin {Plugin} requires it",
missing.Name, plugin.Name);
return false;
}));
);
if (!_plugins.Any())
_logger.LogInformation("No plugin enabled");
@ -138,7 +127,10 @@ namespace Kyoo.Controllers
/// <inheritdoc />
public void LoadPlugins(params Type[] plugins)
{
throw new NotImplementedException();
LoadPlugins(plugins
.Select(x => (IPlugin)ActivatorUtilities.CreateInstance(_provider, x))
.ToArray()
);
}
/// <inheritdoc />
@ -151,9 +143,8 @@ namespace Kyoo.Controllers
/// <inheritdoc />
public void ConfigureServices(IServiceCollection services)
{
ICollection<Type> available = GetProvidedTypes(_plugins);
foreach (IPlugin plugin in _plugins)
plugin.Configure(services, available);
plugin.Configure(services);
}
/// <inheritdoc />
@ -168,53 +159,6 @@ namespace Kyoo.Controllers
}
}
/// <summary>
/// Get the list of types provided by the currently loaded plugins.
/// </summary>
/// <param name="plugins">The list of plugins that will be used as a plugin pool to get provided types.</param>
/// <returns>The list of types available.</returns>
private ICollection<Type> GetProvidedTypes(ICollection<IPlugin> plugins)
{
List<Type> available = plugins.SelectMany(x => x.Provides).ToList();
List<ConditionalProvide> conditionals = plugins
.SelectMany(x => x.ConditionalProvides)
.Where(x => x.Condition.Condition())
.ToList();
bool IsAvailable(ConditionalProvide conditional, bool log = false)
{
if (!conditional.Condition.Condition())
return false;
ICollection<Type> needed = conditional.Condition.Needed
.Where(y => !available.Contains(y))
.ToList();
// TODO handle circular dependencies, actually it might stack overflow.
needed = needed.Where(x => !conditionals
.Where(y => y.Type == x)
.Any(y => IsAvailable(y)))
.ToList();
if (!needed.Any())
return true;
if (log && available.All(x => x != conditional.Type))
{
_logger.LogWarning("The type {Type} is not available, {Dependencies} could not be met",
conditional.Type.Name,
needed.Select(x => x.Name));
}
return false;
}
// ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
foreach (ConditionalProvide conditional in conditionals)
{
if (IsAvailable(conditional, true))
available.Add(conditional.Type);
}
return available;
}
/// <summary>
/// A custom <see cref="AssemblyLoadContext"/> to load plugin's dependency if they are on the same folder.
/// </summary>

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using Autofac;
using Autofac.Core;
@ -31,53 +30,7 @@ namespace Kyoo
/// <inheritdoc />
public string Description => "The core module containing default implementations.";
/// <inheritdoc />
public ICollection<Type> Provides => new[]
{
typeof(IFileSystem),
typeof(ITranscoder),
typeof(IThumbnailsManager),
typeof(IMetadataProvider),
typeof(ITaskManager),
typeof(ILibraryManager),
typeof(IIdentifier),
typeof(AProviderComposite)
};
/// <inheritdoc />
public ICollection<ConditionalProvide> ConditionalProvides => new ConditionalProvide[]
{
(typeof(ILibraryRepository), typeof(DatabaseContext)),
(typeof(ILibraryItemRepository), typeof(DatabaseContext)),
(typeof(ICollectionRepository), typeof(DatabaseContext)),
(typeof(IShowRepository), typeof(DatabaseContext)),
(typeof(ISeasonRepository), typeof(DatabaseContext)),
(typeof(IEpisodeRepository), typeof(DatabaseContext)),
(typeof(ITrackRepository), typeof(DatabaseContext)),
(typeof(IPeopleRepository), typeof(DatabaseContext)),
(typeof(IStudioRepository), typeof(DatabaseContext)),
(typeof(IGenreRepository), typeof(DatabaseContext)),
(typeof(IProviderRepository), typeof(DatabaseContext)),
(typeof(IUserRepository), typeof(DatabaseContext))
};
/// <inheritdoc />
public ICollection<Type> Requires => new []
{
typeof(ILibraryRepository),
typeof(ILibraryItemRepository),
typeof(ICollectionRepository),
typeof(IShowRepository),
typeof(ISeasonRepository),
typeof(IEpisodeRepository),
typeof(ITrackRepository),
typeof(IPeopleRepository),
typeof(IStudioRepository),
typeof(IGenreRepository),
typeof(IProviderRepository)
};
/// <summary>
/// The configuration to use.
/// </summary>
@ -142,7 +95,7 @@ namespace Kyoo
}
/// <inheritdoc />
public void Configure(IServiceCollection services, ICollection<Type> availableTypes)
public void Configure(IServiceCollection services)
{
string publicUrl = _configuration.GetPublicUrl();

View File

@ -43,7 +43,7 @@ namespace Kyoo
typeof(CoreModule),
typeof(AuthenticationModule),
typeof(PostgresModule),
typeof(SqLiteModule),
// typeof(SqLiteModule),
typeof(PluginTvdb),
typeof(PluginTmdb)
);

View File

@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;
using Autofac.Extensions.DependencyInjection;
using Kyoo.Controllers;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;