diff --git a/Kyoo.Authentication/Class1.cs b/Kyoo.Authentication/Class1.cs
new file mode 100644
index 00000000..18aefd85
--- /dev/null
+++ b/Kyoo.Authentication/Class1.cs
@@ -0,0 +1,6 @@
+using System;
+
+namespace Kyoo.Authentication
+{
+ public class Class1 { }
+}
\ No newline at end of file
diff --git a/Kyoo.Authentication/Kyoo.Authentication.csproj b/Kyoo.Authentication/Kyoo.Authentication.csproj
new file mode 100644
index 00000000..75bd3077
--- /dev/null
+++ b/Kyoo.Authentication/Kyoo.Authentication.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net5.0
+ ../Kyoo/bin/$(Configuration)/$(TargetFramework)/plugins/postgresql
+ false
+ false
+ false
+ false
+
+ SDG
+ Zoe Roux
+ https://github.com/AnonymusRaccoon/Kyoo
+ default
+
+
+
+
+ all
+ false
+
+
+
diff --git a/Kyoo.Common/Controllers/IPlugin.cs b/Kyoo.Common/Controllers/IPlugin.cs
index a2f9d1b0..9f0e68e0 100644
--- a/Kyoo.Common/Controllers/IPlugin.cs
+++ b/Kyoo.Common/Controllers/IPlugin.cs
@@ -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; }
///
- /// 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
///
///
/// You should put the type's interface that will be register in configure.
///
- Type[] Provides { get; }
+ ICollection Provides { get; }
///
- /// 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 .
+ ///
+ ICollection ConditionalProvides { get; }
+
+ ///
+ /// 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.
///
///
/// Put here the most complete type that are needed for your plugin to work. If you need a LibraryManager,
/// put typeof(ILibraryManager).
///
- Type[] Requires { get; }
-
- ///
- /// 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.
- ///
- bool IsRequired { get; }
+ ICollection Requires { get; }
///
/// A configure method that will be run on plugin's startup.
///
/// A unity container to register new services.
- void Configure(IUnityContainer container);
+ /// The list of types that are available for this instance. This can be used
+ /// for conditional type. See
+ /// or >
+ void Configure(IUnityContainer container, ICollection availableTypes);
///
/// An optional configuration step to allow a plugin to change asp net configurations.
@@ -64,6 +70,144 @@ namespace Kyoo.Controllers
///
/// The Asp.Net application builder. On most case it is not needed but you can use it to add asp net functionalities.
void ConfigureAspNet(IApplicationBuilder app) {}
+ }
+
+ ///
+ /// A type that will only be provided if a special condition is met. To check that your condition is met,
+ /// you can check the class.
+ ///
+ public class ConditionalProvide : Tuple
+ {
+ ///
+ /// Get the type that may be provided
+ ///
+ public Type Type => Item1;
+
+ ///
+ /// Get the condition.
+ ///
+ public ProviderCondition Condition => Item2;
+ ///
+ /// Create a from a type and a condition.
+ ///
+ /// The type to provide
+ /// The condition
+ public ConditionalProvide(Type type, ProviderCondition condition)
+ : base(type, condition)
+ { }
+
+ ///
+ /// Create a from a tuple of (Type, ProviderCondition).
+ ///
+ /// The tuple to convert
+ public ConditionalProvide((Type type, ProviderCondition condition) tuple)
+ : base(tuple.type, tuple.condition)
+ { }
+
+ ///
+ /// Implicitly convert a tuple to a .
+ ///
+ /// The tuple to convert
+ /// A new based on the given tuple.
+ public static implicit operator ConditionalProvide((Type, Type) tuple) => new (tuple);
+ }
+
+ ///
+ /// A condition for a conditional type.
+ ///
+ public class ProviderCondition
+ {
+ ///
+ /// The condition as a method. If true is returned, the type will be provided.
+ ///
+ public Func Condition { get; } = () => true;
+ ///
+ /// The list of types that this method needs.
+ ///
+ public ICollection Needed { get; } = ArraySegment.Empty;
+
+
+ ///
+ /// Create a new from a raw function.
+ ///
+ /// The predicate that will be used as condition
+ public ProviderCondition(Func condition)
+ {
+ Condition = condition;
+ }
+
+ ///
+ /// Create a new from a type. This allow you to inform that a type will
+ /// only be available if a dependency is met.
+ ///
+ /// The type that you need
+ public ProviderCondition(Type needed)
+ {
+ Needed = new[] {needed};
+ }
+
+ ///
+ /// Create a new from a list of type. This allow you to inform that a type will
+ /// only be available if a list of dependencies are met.
+ ///
+ /// The types that you need
+ public ProviderCondition(ICollection needed)
+ {
+ Needed = needed;
+ }
+
+ ///
+ /// Create a new with a list of types as dependencies and a predicate
+ /// for arbitrary conditions.
+ ///
+ /// The list of dependencies
+ /// An arbitrary condition
+ public ProviderCondition(ICollection needed, Func condition)
+ {
+ Needed = needed;
+ Condition = condition;
+ }
+
+
+ ///
+ /// Implicitly convert a type to a .
+ ///
+ /// The type dependency
+ /// A that will return true if the given type is available.
+ public static implicit operator ProviderCondition(Type type) => new(type);
+
+ ///
+ /// Implicitly convert a list of type to a .
+ ///
+ /// The list of type dependencies
+ /// A that will return true if the given types are available.
+ public static implicit operator ProviderCondition(Type[] types) => new(types);
+
+ ///
+ public static implicit operator ProviderCondition(List types) => new(types);
+
+
+ ///
+ /// Check if a type is available.
+ ///
+ /// The type to check
+ /// The list of types
+ /// True if the dependency is met, false otherwise
+ public static bool Has(Type needed, ICollection available)
+ {
+ return available.Contains(needed);
+ }
+
+ ///
+ /// Check if a list of type are available.
+ ///
+ /// The list of types to check
+ /// The list of types
+ /// True if the dependencies are met, false otherwise
+ public static bool Has(ICollection needed, ICollection available)
+ {
+ return needed.All(x => Has(x, available));
+ }
}
}
\ No newline at end of file
diff --git a/Kyoo.Postgresql/PostgresModule.cs b/Kyoo.Postgresql/PostgresModule.cs
index e54f0a45..f3eecff0 100644
--- a/Kyoo.Postgresql/PostgresModule.cs
+++ b/Kyoo.Postgresql/PostgresModule.cs
@@ -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.";
///
- public Type[] Provides => new[]
+ public ICollection Provides => new[]
{
typeof(PostgresContext)
};
///
- public Type[] Requires => Array.Empty();
-
+ public ICollection ConditionalProvides => ArraySegment.Empty;
+
///
- public bool IsRequired => true;
+ public ICollection Requires => ArraySegment.Empty;
///
@@ -56,7 +57,7 @@ namespace Kyoo.Postgresql
}
///
- public void Configure(IUnityContainer container)
+ public void Configure(IUnityContainer container, ICollection availableTypes)
{
container.RegisterFactory(_ => new PostgresContext(
_configuration.GetDatabaseConnection("postgres"),
diff --git a/Kyoo.sln b/Kyoo.sln
index 79ee6e7d..3f814bd3 100644
--- a/Kyoo.sln
+++ b/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
diff --git a/Kyoo/Controllers/PluginManager.cs b/Kyoo/Controllers/PluginManager.cs
index d2945b0c..d2df582e 100644
--- a/Kyoo/Controllers/PluginManager.cs
+++ b/Kyoo/Controllers/PluginManager.cs
@@ -101,7 +101,7 @@ namespace Kyoo.Controllers
newPlugins.Add(new CoreModule());
_plugins.AddRange(newPlugins);
- ICollection available = _plugins.SelectMany(x => x.Provides).ToArray();
+ ICollection 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));
}
+ ///
+ /// Get the list of types provided by the currently loaded plugins.
+ ///
+ /// The list of types available.
+ private ICollection GetProvidedTypes()
+ {
+ List available = _plugins.SelectMany(x => x.Provides).ToList();
+ List 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 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;
+ }
+
///
/// A custom to load plugin's dependency if they are on the same folder.
diff --git a/Kyoo/CoreModule.cs b/Kyoo/CoreModule.cs
index a32689a5..9f9810cf 100644
--- a/Kyoo/CoreModule.cs
+++ b/Kyoo/CoreModule.cs
@@ -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.";
///
- public Type[] Provides => new[]
+ public ICollection 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)
};
///
- public Type[] Requires => new[]
+ public ICollection 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))
};
///
- public bool IsRequired => true;
-
- ///
- public void Configure(IUnityContainer container)
+ public ICollection Requires => ArraySegment.Empty;
+
+ ///
+ public void Configure(IUnityContainer container, ICollection availableTypes)
{
container.RegisterType(new SingletonLifetimeManager());
container.RegisterType(new SingletonLifetimeManager());
@@ -61,19 +61,22 @@ namespace Kyoo
container.RegisterType(new SingletonLifetimeManager());
container.RegisterType(new HierarchicalLifetimeManager());
-
- container.RegisterRepository();
- container.RegisterRepository();
- container.RegisterRepository();
- container.RegisterRepository();
- container.RegisterRepository();
- container.RegisterRepository();
- container.RegisterRepository();
- container.RegisterRepository();
- container.RegisterRepository();
- container.RegisterRepository();
- container.RegisterRepository();
-
+
+ if (ProviderCondition.Has(typeof(DatabaseContext), availableTypes))
+ {
+ container.RegisterRepository();
+ container.RegisterRepository();
+ container.RegisterRepository();
+ container.RegisterRepository();
+ container.RegisterRepository();
+ container.RegisterRepository();
+ container.RegisterRepository();
+ container.RegisterRepository();
+ container.RegisterRepository();
+ container.RegisterRepository();
+ container.RegisterRepository();
+ }
+
container.RegisterTask();
}
}
diff --git a/Kyoo/Program.cs b/Kyoo/Program.cs
index 4d749a0f..beba3b0d 100644
--- a/Kyoo/Program.cs
+++ b/Kyoo/Program.cs
@@ -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)
diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs
index 1bdf23b2..d747c960 100644
--- a/Kyoo/Startup.cs
+++ b/Kyoo/Startup.cs
@@ -147,14 +147,7 @@ namespace Kyoo
services.AddHostedService(x => x.GetService() 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(c => c.Resolve(), new SingletonLifetimeManager());
- }
+ public void ConfigureContainer(UnityContainer container) { }
public void Configure(IUnityContainer container, IApplicationBuilder app, IWebHostEnvironment env)
{