mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Suporting optional dependencies
This commit is contained in:
parent
36220613ab
commit
97511d5988
6
Kyoo.Authentication/Class1.cs
Normal file
6
Kyoo.Authentication/Class1.cs
Normal file
@ -0,0 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Kyoo.Authentication
|
||||
{
|
||||
public class Class1 { }
|
||||
}
|
23
Kyoo.Authentication/Kyoo.Authentication.csproj
Normal file
23
Kyoo.Authentication/Kyoo.Authentication.csproj
Normal file
@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<OutputPath>../Kyoo/bin/$(Configuration)/$(TargetFramework)/plugins/postgresql</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
<GenerateDependencyFile>false</GenerateDependencyFile>
|
||||
<GenerateRuntimeConfigurationFiles>false</GenerateRuntimeConfigurationFiles>
|
||||
|
||||
<Company>SDG</Company>
|
||||
<Authors>Zoe Roux</Authors>
|
||||
<RepositoryUrl>https://github.com/AnonymusRaccoon/Kyoo</RepositoryUrl>
|
||||
<LangVersion>default</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../Kyoo.Common/Kyoo.Common.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Unity;
|
||||
@ -29,34 +31,38 @@ namespace Kyoo.Controllers
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of services that are provided by this service. This allow other plugins to declare dependencies.
|
||||
/// A list of services that are provided by this service. This allow other plugins to declare dependencies
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You should put the type's interface that will be register in configure.
|
||||
/// </remarks>
|
||||
Type[] Provides { get; }
|
||||
ICollection<Type> Provides { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of services that are required by this service.
|
||||
/// The Core will warn the user that this plugin can't be loaded if a required service is not found.
|
||||
/// 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>
|
||||
Type[] Requires { get; }
|
||||
|
||||
/// <summary>
|
||||
/// True if this plugin is needed to start Kyoo. If this is true and a dependency could not be met, app startup
|
||||
/// will be canceled. If this is false, Kyoo's startup will continue without enabling this plugin.
|
||||
/// </summary>
|
||||
bool IsRequired { get; }
|
||||
ICollection<Type> Requires { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A configure method that will be run on plugin's startup.
|
||||
/// </summary>
|
||||
/// <param name="container">A unity container to register new services.</param>
|
||||
void Configure(IUnityContainer container);
|
||||
/// <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})"/>></param>
|
||||
void Configure(IUnityContainer container, ICollection<Type> availableTypes);
|
||||
|
||||
/// <summary>
|
||||
/// An optional configuration step to allow a plugin to change asp net configurations.
|
||||
@ -64,6 +70,144 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="app">The Asp.Net application builder. On most case it is not needed but you can use it to add asp net functionalities.</param>
|
||||
void ConfigureAspNet(IApplicationBuilder app) {}
|
||||
}
|
||||
|
||||
/// <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));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Controllers;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@ -22,16 +23,16 @@ namespace Kyoo.Postgresql
|
||||
public string Description => "A database context for postgresql.";
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type[] Provides => new[]
|
||||
public ICollection<Type> Provides => new[]
|
||||
{
|
||||
typeof(PostgresContext)
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type[] Requires => Array.Empty<Type>();
|
||||
public ICollection<ConditionalProvide> ConditionalProvides => ArraySegment<ConditionalProvide>.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsRequired => true;
|
||||
public ICollection<Type> Requires => ArraySegment<Type>.Empty;
|
||||
|
||||
|
||||
/// <summary>
|
||||
@ -56,7 +57,7 @@ namespace Kyoo.Postgresql
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Configure(IUnityContainer container)
|
||||
public void Configure(IUnityContainer container, ICollection<Type> availableTypes)
|
||||
{
|
||||
container.RegisterFactory<DatabaseContext>(_ => new PostgresContext(
|
||||
_configuration.GetDatabaseConnection("postgres"),
|
||||
|
6
Kyoo.sln
6
Kyoo.sln
@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Tests", "Kyoo.Tests\Ky
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Postgresql", "Kyoo.Postgresql\Kyoo.Postgresql.csproj", "{3213C96D-0BF3-460B-A8B5-B9977229408A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Authentication", "Kyoo.Authentication\Kyoo.Authentication.csproj", "{7A841335-6523-47DB-9717-80AA7BD943FD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -35,5 +37,9 @@ Global
|
||||
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7A841335-6523-47DB-9717-80AA7BD943FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7A841335-6523-47DB-9717-80AA7BD943FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7A841335-6523-47DB-9717-80AA7BD943FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7A841335-6523-47DB-9717-80AA7BD943FD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
@ -101,7 +101,7 @@ namespace Kyoo.Controllers
|
||||
newPlugins.Add(new CoreModule());
|
||||
_plugins.AddRange(newPlugins);
|
||||
|
||||
ICollection<Type> available = _plugins.SelectMany(x => x.Provides).ToArray();
|
||||
ICollection<Type> available = GetProvidedTypes();
|
||||
foreach (IPlugin plugin in newPlugins)
|
||||
{
|
||||
Type missing = plugin.Requires.FirstOrDefault(x => available.All(y => !y.IsAssignableTo(x)));
|
||||
@ -109,11 +109,9 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
Exception error = new MissingDependencyException(plugin.Name, missing.Name);
|
||||
_logger.LogCritical(error, "A plugin's dependency could not be met");
|
||||
if (plugin.IsRequired)
|
||||
Environment.Exit(1);
|
||||
}
|
||||
else
|
||||
plugin.Configure(_container);
|
||||
plugin.Configure(_container, available);
|
||||
}
|
||||
|
||||
if (!_plugins.Any())
|
||||
@ -122,6 +120,46 @@ namespace Kyoo.Controllers
|
||||
_logger.LogInformation("Plugin enabled: {Plugins}", _plugins.Select(x => x.Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the list of types provided by the currently loaded plugins.
|
||||
/// </summary>
|
||||
/// <returns>The list of types available.</returns>
|
||||
private ICollection<Type> GetProvidedTypes()
|
||||
{
|
||||
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();
|
||||
needed = needed.Where(x => !conditionals
|
||||
.Where(y => y.Type == x)
|
||||
.Any(y => IsAvailable(y)))
|
||||
.ToList();
|
||||
if (!needed.Any())
|
||||
return true;
|
||||
_logger.LogWarning("The type {Type} is not available, {Dependencies} could not be met",
|
||||
conditional.Type.Name,
|
||||
needed.Select(x => x.Name));
|
||||
return false;
|
||||
}
|
||||
|
||||
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.
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Controllers;
|
||||
using Kyoo.Tasks;
|
||||
using Unity;
|
||||
@ -21,38 +22,37 @@ namespace Kyoo
|
||||
public string Description => "The core module containing default implementations.";
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type[] Provides => new[]
|
||||
public ICollection<Type> Provides => new[]
|
||||
{
|
||||
typeof(IFileManager),
|
||||
typeof(ITranscoder),
|
||||
typeof(IThumbnailsManager),
|
||||
typeof(IProviderManager),
|
||||
typeof(ITaskManager),
|
||||
typeof(ILibraryManager),
|
||||
typeof(ILibraryRepository),
|
||||
typeof(ILibraryItemRepository),
|
||||
typeof(ICollectionRepository),
|
||||
typeof(IShowRepository),
|
||||
typeof(ISeasonRepository),
|
||||
typeof(IEpisodeRepository),
|
||||
typeof(ITrackRepository),
|
||||
typeof(IPeopleRepository),
|
||||
typeof(IStudioRepository),
|
||||
typeof(IGenreRepository),
|
||||
typeof(IProviderRepository)
|
||||
typeof(ILibraryManager)
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type[] Requires => new[]
|
||||
public ICollection<ConditionalProvide> ConditionalProvides => new ConditionalProvide[]
|
||||
{
|
||||
typeof(DatabaseContext)
|
||||
(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))
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsRequired => true;
|
||||
public ICollection<Type> Requires => ArraySegment<Type>.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Configure(IUnityContainer container)
|
||||
/// <inheritdoc />
|
||||
public void Configure(IUnityContainer container, ICollection<Type> availableTypes)
|
||||
{
|
||||
container.RegisterType<IFileManager, FileManager>(new SingletonLifetimeManager());
|
||||
container.RegisterType<ITranscoder, Transcoder>(new SingletonLifetimeManager());
|
||||
@ -62,17 +62,20 @@ namespace Kyoo
|
||||
|
||||
container.RegisterType<ILibraryManager, LibraryManager>(new HierarchicalLifetimeManager());
|
||||
|
||||
container.RegisterRepository<ILibraryRepository, LibraryRepository>();
|
||||
container.RegisterRepository<ILibraryItemRepository, LibraryItemRepository>();
|
||||
container.RegisterRepository<ICollectionRepository, CollectionRepository>();
|
||||
container.RegisterRepository<IShowRepository, ShowRepository>();
|
||||
container.RegisterRepository<ISeasonRepository, SeasonRepository>();
|
||||
container.RegisterRepository<IEpisodeRepository, EpisodeRepository>();
|
||||
container.RegisterRepository<ITrackRepository, TrackRepository>();
|
||||
container.RegisterRepository<IPeopleRepository, PeopleRepository>();
|
||||
container.RegisterRepository<IStudioRepository, StudioRepository>();
|
||||
container.RegisterRepository<IGenreRepository, GenreRepository>();
|
||||
container.RegisterRepository<IProviderRepository, ProviderRepository>();
|
||||
if (ProviderCondition.Has(typeof(DatabaseContext), availableTypes))
|
||||
{
|
||||
container.RegisterRepository<ILibraryRepository, LibraryRepository>();
|
||||
container.RegisterRepository<ILibraryItemRepository, LibraryItemRepository>();
|
||||
container.RegisterRepository<ICollectionRepository, CollectionRepository>();
|
||||
container.RegisterRepository<IShowRepository, ShowRepository>();
|
||||
container.RegisterRepository<ISeasonRepository, SeasonRepository>();
|
||||
container.RegisterRepository<IEpisodeRepository, EpisodeRepository>();
|
||||
container.RegisterRepository<ITrackRepository, TrackRepository>();
|
||||
container.RegisterRepository<IPeopleRepository, PeopleRepository>();
|
||||
container.RegisterRepository<IStudioRepository, StudioRepository>();
|
||||
container.RegisterRepository<IGenreRepository, GenreRepository>();
|
||||
container.RegisterRepository<IProviderRepository, ProviderRepository>();
|
||||
}
|
||||
|
||||
container.RegisterTask<Crawler>();
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ namespace Kyoo
|
||||
private static IWebHostBuilder CreateWebHostBuilder(string[] args)
|
||||
{
|
||||
UnityContainer container = new();
|
||||
// container.EnableDebugDiagnostic();
|
||||
container.EnableDebugDiagnostic();
|
||||
|
||||
return new WebHostBuilder()
|
||||
.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory)
|
||||
|
@ -147,14 +147,7 @@ namespace Kyoo
|
||||
services.AddHostedService(x => x.GetService<ITaskManager>() as TaskManager);
|
||||
}
|
||||
|
||||
public void ConfigureContainer(UnityContainer container)
|
||||
{
|
||||
// TODO move this to the configure section and figure out a way to reload ControllerActivators with the updated unity container
|
||||
|
||||
// TODO the reload should re inject components from the constructor.
|
||||
// TODO fin a way to inject tasks without a IUnityContainer.
|
||||
// container.RegisterFactory<IHostedService>(c => c.Resolve<ITaskManager>(), new SingletonLifetimeManager());
|
||||
}
|
||||
public void ConfigureContainer(UnityContainer container) { }
|
||||
|
||||
public void Configure(IUnityContainer container, IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user