Reworking the task manager & using unity containers

This commit is contained in:
Zoe Roux 2021-04-26 01:07:05 +02:00
parent 0221cb7873
commit b7294114b9
24 changed files with 846 additions and 444 deletions

View File

@ -0,0 +1,39 @@
namespace Kyoo.Controllers
{
/// <summary>
/// A common interface used to discord plugins
/// </summary>
public interface IPlugin
{
/// <summary>
/// A slug to identify this plugin in queries.
/// </summary>
string Slug { get; }
/// <summary>
/// The name of the plugin
/// </summary>
string Name { get; }
/// <summary>
/// The description of this plugin. This will be displayed on the "installed plugins" page.
/// </summary>
string Description { get; }
/// <summary>
/// A configure method that will be runned on plugin's startup.
/// </summary>
/// <remarks>
/// You can have use any services as parameter, they will be injected from the service provider
/// You can add managed types or any type you like using the IUnityContainer like so:
/// <code>
/// public static void Configure(IUnityContainer services)
/// {
/// services.AddTask&lt;MyTask&gt;()
/// }
/// </code>
/// </remarks>
static void Configure() { }
}
}

View File

@ -0,0 +1,188 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Kyoo.Models.Attributes;
namespace Kyoo.Controllers
{
/// <summary>
/// A single task parameter. This struct contains metadata to display and utility functions to get them in the taks.
/// </summary>
/// <remarks>This struct will be used to generate the swagger documentation of the task.</remarks>
public record TaskParameter
{
/// <summary>
/// The name of this parameter.
/// </summary>
public string Name { get; init; }
/// <summary>
/// The description of this parameter.
/// </summary>
public string Description { get; init; }
/// <summary>
/// The type of this parameter.
/// </summary>
public Type Type { get; init; }
/// <summary>
/// Is this parameter required or can it be ignored?
/// </summary>
public bool IsRequired { get; init; }
/// <summary>
/// The default value of this object.
/// </summary>
public object DefaultValue { get; init; }
/// <summary>
/// The value of the parameter.
/// </summary>
private object Value { get; init; }
/// <summary>
/// Create a new task parameter.
/// </summary>
/// <param name="name">The name of the parameter</param>
/// <param name="description">The description of the parameter</param>
/// <typeparam name="T">The type of the parameter.</typeparam>
/// <returns>A new task parameter.</returns>
public static TaskParameter Create<T>(string name, string description)
{
return new()
{
Name = name,
Description = description,
Type = typeof(T)
};
}
/// <summary>
/// Create a parameter's value to give to a task.
/// </summary>
/// <param name="name">The name of the parameter</param>
/// <param name="value">The value of the parameter. It's type will be used as parameter's type.</param>
/// <typeparam name="T">The type of the parameter</typeparam>
/// <returns>A TaskParameter that can be used as value.</returns>
public static TaskParameter CreateValue<T>(string name, T value)
{
return new()
{
Name = name,
Type = typeof(T),
Value = value
};
}
/// <summary>
/// Create a parameter's value for the current parameter.
/// </summary>
/// <param name="value">The value to use</param>
/// <returns>A new parameter's value for this current parameter</returns>
public TaskParameter CreateValue(object value)
{
return this with {Value = value};
}
/// <summary>
/// Get the value of this parameter. If the value is of the wrong type, it will be converted.
/// </summary>
/// <typeparam name="T">The type of this parameter</typeparam>
/// <returns>The value of this parameter.</returns>
public T As<T>()
{
return (T)Convert.ChangeType(Value, typeof(T));
}
}
/// <summary>
/// A parameters container implementing an indexer to allow simple usage of parameters.
/// </summary>
public class TaskParameters : List<TaskParameter>
{
/// <summary>
/// An indexer that return the parameter with the specified name.
/// </summary>
/// <param name="name">The name of the task (case sensitive)</param>
public TaskParameter this[string name] => this.FirstOrDefault(x => x.Name == name);
/// <summary>
/// Create a new, empty, <see cref="TaskParameters"/>
/// </summary>
public TaskParameters() {}
/// <summary>
/// Create a <see cref="TaskParameters"/> with an initial parameters content
/// </summary>
/// <param name="parameters">The list of parameters</param>
public TaskParameters(IEnumerable<TaskParameter> parameters)
{
AddRange(parameters);
}
}
/// <summary>
/// A common interface that tasks should implement.
/// </summary>
public interface ITask
{
/// <summary>
/// The slug of the task, used to start it.
/// </summary>
public string Slug { get; }
/// <summary>
/// The name of the task that will be displayed to the user.
/// </summary>
public string Name { get; }
/// <summary>
/// A quick description of what this task will do.
/// </summary>
public string Description { get; }
/// <summary>
/// An optional message to display to help the user.
/// </summary>
public string HelpMessage { get; }
/// <summary>
/// Should this task be automatically runned at app startup?
/// </summary>
public bool RunOnStartup { get; }
/// <summary>
/// The priority of this task. Only used if <see cref="RunOnStartup"/> is true.
/// It allow one to specify witch task will be started first as tasked are run on a Priority's descending order.
/// </summary>
public int Priority { get; }
/// <summary>
/// Start this task.
/// </summary>
/// <param name="arguments">The list of parameters.</param>
/// <param name="cancellationToken">A token to request the task's cancelation.
/// If this task is not cancelled quickly, it might be killed by the runner.</param>
/// <remarks>
/// Your task can have any service as a public field and use the <see cref="InjectedAttribute"/>,
/// they will be set to an available service from the service container before calling this method.
/// </remarks>
public Task Run(TaskParameters arguments, CancellationToken cancellationToken);
/// <summary>
/// The list of parameters
/// </summary>
/// <returns>All parameters that this task as. Every one of them will be given to the run function with a value.</returns>
public TaskParameters GetParameters();
/// <summary>
/// If this task is running, return the percentage of completion of this task or null if no percentage can be given.
/// </summary>
/// <returns>The percentage of completion of the task.</returns>
public int? Progress();
}
}

View File

@ -1,13 +1,40 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Kyoo.Models; using Kyoo.Models;
using Kyoo.Models.Exceptions;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
/// <summary>
/// A service to handle long running tasks.
/// </summary>
/// <remarks>The concurrent number of running tasks is implementation dependent.</remarks>
public interface ITaskManager public interface ITaskManager
{ {
bool StartTask(string taskSlug, string arguments = null); /// <summary>
ITask GetRunningTask(); /// Start a new task (or queue it).
void ReloadTask(); /// </summary>
IEnumerable<ITask> GetAllTasks(); /// <param name="taskSlug">The slug of the task to run</param>
/// <param name="arguments">A list of arguments to pass to the task. An automatic conversion will be made if arguments to not fit.</param>
/// <exception cref="ArgumentException">If the number of arguments is invalid or if an argument can't be converted.</exception>
/// <exception cref="ItemNotFound">The task could not be found.</exception>
void StartTask(string taskSlug, Dictionary<string, object> arguments);
/// <summary>
/// Get all currently running tasks
/// </summary>
/// <returns>A list of currently running tasks.</returns>
ICollection<ITask> GetRunningTasks();
/// <summary>
/// Get all availables tasks
/// </summary>
/// <returns>A list of every tasks that this instance know.</returns>
ICollection<ITask> GetAllTasks();
/// <summary>
/// Reload tasks and run startup tasks.
/// </summary>
void ReloadTasks();
} }
} }

View File

@ -24,6 +24,13 @@
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" /> <PackageReference Include="JetBrains.Annotations" Version="2020.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0-beta-20204-02" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0-beta-20204-02" PrivateAssets="All" />
<PackageReference Include="Unity.Abstractions" Version="5.11.7" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
<HintPath>..\..\..\..\..\..\usr\share\dotnet\shared\Microsoft.AspNetCore.App\5.0.5\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,14 @@
using System;
using Kyoo.Controllers;
namespace Kyoo.Models.Attributes
{
/// <summary>
/// An attribute to inform that the service will be injected automatically by a service provider.
/// </summary>
/// <remarks>
/// It should only be used on <see cref="ITask"/> and will be injected before calling <see cref="ITask.Run"/>
/// </remarks>
[AttributeUsage(AttributeTargets.Property)]
public class InjectedAttribute : Attribute { }
}

View File

@ -1,10 +0,0 @@
using System.Collections.Generic;
namespace Kyoo.Models
{
public interface IPlugin
{
public string Name { get; }
public ICollection<ITask> Tasks { get; }
}
}

View File

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Kyoo.Models
{
public interface ITask
{
public string Slug { get; }
public string Name { get; }
public string Description { get; }
public string HelpMessage { get; }
public bool RunOnStartup { get; }
public int Priority { get; }
public Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null);
public Task<IEnumerable<string>> GetPossibleParameters();
public int? Progress();
}
}

24
Kyoo.Common/Module.cs Normal file
View File

@ -0,0 +1,24 @@
using Kyoo.Controllers;
using Unity;
namespace Kyoo
{
/// <summary>
/// A static class with helper functions to setup external modules
/// </summary>
public static class Module
{
/// <summary>
/// Register a new task to the container.
/// </summary>
/// <param name="services">The container</param>
/// <typeparam name="T">The type of the task</typeparam>
/// <returns>The initial container.</returns>
public static IUnityContainer AddTask<T>(this IUnityContainer services)
where T : class, ITask
{
services.RegisterSingleton<ITask, T>();
return services;
}
}
}

View File

@ -529,9 +529,9 @@ namespace Kyoo
await action(i); await action(i);
} }
private static MethodInfo GetMethod(Type type, BindingFlags flag, string name, Type[] generics, object[] args) public static MethodInfo GetMethod(Type type, BindingFlags flag, string name, Type[] generics, object[] args)
{ {
MethodInfo[] methods = type.GetMethods(flag | BindingFlags.Public | BindingFlags.NonPublic) MethodInfo[] methods = type.GetMethods(flag | BindingFlags.Public)
.Where(x => x.Name == name) .Where(x => x.Name == name)
.Where(x => x.GetGenericArguments().Length == generics.Length) .Where(x => x.GetGenericArguments().Length == generics.Length)
.Where(x => x.GetParameters().Length == args.Length) .Where(x => x.GetParameters().Length == args.Length)

View File

@ -1,56 +1,129 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Models; using Kyoo.Models.Attributes;
using Kyoo.Tasks; using Kyoo.Models.Exceptions;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Unity;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
/// <summary>
/// A service to handle long running tasks and a background runner.
/// </summary>
/// <remarks>Task will be queued, only one can run simultaneously.</remarks>
public class TaskManager : BackgroundService, ITaskManager public class TaskManager : BackgroundService, ITaskManager
{ {
private readonly IServiceProvider _serviceProvider; /// <summary>
private readonly IPluginManager _pluginManager; /// The service provider used to activate
/// </summary>
private readonly IUnityContainer _container;
/// <summary>
/// The configuration instance used to get schedule informations
/// </summary>
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
/// <summary>
/// The logger instance.
/// </summary>
private readonly ILogger<TaskManager> _logger;
private List<(ITask task, DateTime scheduledDate)> _tasks = new List<(ITask, DateTime)>(); /// <summary>
private CancellationTokenSource _taskToken = new CancellationTokenSource(); /// The list of tasks and their next scheduled run.
/// </summary>
private List<(ITask task, DateTime scheduledDate)> _tasks;
/// <summary>
/// The queue of tasks that should be runned as soon as possible.
/// </summary>
private readonly Queue<(ITask, Dictionary<string, object>)> _queuedTasks = new();
/// <summary>
/// The currently running task.
/// </summary>
private ITask _runningTask; private ITask _runningTask;
private Queue<(ITask, string)> _queuedTasks = new Queue<(ITask, string)>(); /// <summary>
/// The cancellation token used to cancel the running task when the runner should shutdown.
/// </summary>
private readonly CancellationTokenSource _taskToken = new();
public TaskManager(IServiceProvider serviceProvider, IPluginManager pluginManager, IConfiguration configuration)
/// <summary>
/// Create a new <see cref="TaskManager"/>.
/// </summary>
/// <param name="tasks">The list of tasks to manage</param>
/// <param name="container">The service provider to request services for tasks</param>
/// <param name="configuration">The configuration to load schedule information.</param>
/// <param name="logger">The logger.</param>
public TaskManager(IEnumerable<ITask> tasks,
IUnityContainer container,
IConfiguration configuration,
ILogger<TaskManager> logger)
{ {
_serviceProvider = serviceProvider; _tasks = tasks.Select(x => (x, DateTime.Now + GetTaskDelay(x.Slug))).ToList();
_pluginManager = pluginManager; _container = container;
_configuration = configuration; _configuration = configuration.GetSection("scheduledTasks");
_logger = logger;
} }
/// <summary>
/// Triggered when the application host is ready to start the service.
/// </summary>
/// <remarks>Start the runner in another thread.</remarks>
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
public override Task StartAsync(CancellationToken cancellationToken)
{
Task.Run(() => base.StartAsync(cancellationToken), CancellationToken.None);
return Task.CompletedTask;
}
/// <inheritdoc />
public override Task StopAsync(CancellationToken cancellationToken)
{
_taskToken.Cancel();
return base.StopAsync(cancellationToken);
}
/// <summary>
/// The runner that will host tasks and run queued tasks.
/// </summary>
/// <param name="cancellationToken">A token to stop the runner</param>
protected override async Task ExecuteAsync(CancellationToken cancellationToken) protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{ {
ReloadTask(); EnqueueStartupTasks();
IEnumerable<ITask> startupTasks = _tasks.Select(x => x.task)
.Where(x => x.RunOnStartup && x.Priority != Int32.MaxValue)
.OrderByDescending(x => x.Priority);
foreach (ITask task in startupTasks)
_queuedTasks.Enqueue((task, null));
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
if (_queuedTasks.Any()) if (_queuedTasks.Any())
{ {
(ITask task, string arguments) = _queuedTasks.Dequeue(); (ITask task, Dictionary<string, object> arguments) = _queuedTasks.Dequeue();
_runningTask = task; _runningTask = task;
try try
{ {
await task.Run(_serviceProvider, _taskToken.Token, arguments); ICollection<TaskParameter> all = task.GetParameters();
TaskParameters args = new(arguments
.Select(x => (value: x, arg: all
.FirstOrDefault(y => string.Equals(y.Name, x.Key, StringComparison.OrdinalIgnoreCase))))
.Select(x =>
{
if (x.arg == null)
throw new ArgumentException($"Invalid argument name: {x.value.Key}");
return x.arg.CreateValue(x.value.Value);
}));
_logger.LogInformation("Task starting: {Task}", task.Name);
InjectServices(task);
await task.Run(args, _taskToken.Token);
_logger.LogInformation("Task finished: {Task}", task.Name);
} }
catch (Exception e) catch (Exception e)
{ {
Console.Error.WriteLine($"An unhandled exception occured while running the task {task.Name}.\nInner exception: {e.Message}\n\n"); _logger.LogError("An unhandled exception occured while running the task {Task}.\n" +
"Inner exception: {Exception}\n\n", task.Name, e.Message);
} }
} }
else else
@ -61,67 +134,90 @@ namespace Kyoo.Controllers
} }
} }
/// <summary>
/// Inject services into the <see cref="InjectedAttribute"/> marked properties of the given object.
/// </summary>
/// <param name="obj">The object to inject</param>
/// <typeparam name="T">The type of the object.</typeparam>
private void InjectServices<T>(T obj)
{
IEnumerable<PropertyInfo> properties = typeof(T).GetProperties()
.Where(x => x.GetCustomAttribute<InjectedAttribute>() != null)
.Where(x => x.CanWrite);
foreach (PropertyInfo property in properties)
{
object value = _container.Resolve(property.PropertyType);
property.SetValue(obj, value);
}
}
/// <summary>
/// Start tasks that are scheduled for start.
/// </summary>
private void QueueScheduledTasks() private void QueueScheduledTasks()
{ {
IEnumerable<string> tasksToQueue = _tasks.Where(x => x.scheduledDate <= DateTime.Now) IEnumerable<string> tasksToQueue = _tasks.Where(x => x.scheduledDate <= DateTime.Now)
.Select(x => x.task.Slug); .Select(x => x.task.Slug);
foreach (string task in tasksToQueue) foreach (string task in tasksToQueue)
StartTask(task); {
_logger.LogDebug("Queuing task scheduled for running: {Task}", task);
StartTask(task, new Dictionary<string, object>());
}
} }
public override Task StartAsync(CancellationToken cancellationToken) /// <summary>
/// Queue startup tasks with respect to the priority rules.
/// </summary>
private void EnqueueStartupTasks()
{ {
Task.Run(() => base.StartAsync(cancellationToken)); IEnumerable<ITask> startupTasks = _tasks.Select(x => x.task)
return Task.CompletedTask; .Where(x => x.RunOnStartup && x.Priority != int.MaxValue)
.OrderByDescending(x => x.Priority);
foreach (ITask task in startupTasks)
_queuedTasks.Enqueue((task, null));
} }
public override Task StopAsync(CancellationToken cancellationToken) /// <inheritdoc />
{ public void StartTask(string taskSlug, Dictionary<string, object> arguments)
_taskToken.Cancel();
return base.StopAsync(cancellationToken);
}
public bool StartTask(string taskSlug, string arguments = null)
{ {
int index = _tasks.FindIndex(x => x.task.Slug == taskSlug); int index = _tasks.FindIndex(x => x.task.Slug == taskSlug);
if (index == -1) if (index == -1)
return false; throw new ItemNotFound($"No task found with the slug {taskSlug}");
_queuedTasks.Enqueue((_tasks[index].task, arguments)); _queuedTasks.Enqueue((_tasks[index].task, arguments));
_tasks[index] = (_tasks[index].task, DateTime.Now + GetTaskDelay(taskSlug)); _tasks[index] = (_tasks[index].task, DateTime.Now + GetTaskDelay(taskSlug));
return true;
} }
public TimeSpan GetTaskDelay(string taskSlug) /// <summary>
/// Get the delay of a task
/// </summary>
/// <param name="taskSlug">The slug of the task</param>
/// <returns>The delay of the task.</returns>
private TimeSpan GetTaskDelay(string taskSlug)
{ {
TimeSpan delay = _configuration.GetSection("scheduledTasks").GetValue<TimeSpan>(taskSlug); TimeSpan delay = _configuration.GetValue<TimeSpan>(taskSlug);
if (delay == default) if (delay == default)
delay = TimeSpan.FromDays(365); delay = TimeSpan.MaxValue;
return delay; return delay;
} }
public ITask GetRunningTask() /// <inheritdoc />
public ICollection<ITask> GetRunningTasks()
{ {
return _runningTask; return new[] {_runningTask};
} }
public void ReloadTask() /// <inheritdoc />
public ICollection<ITask> GetAllTasks()
{ {
_tasks.Clear(); return _tasks.Select(x => x.task).ToArray();
_tasks.AddRange(CoreTaskHolder.Tasks.Select(x => (x, DateTime.Now + GetTaskDelay(x.Slug))));
IEnumerable<ITask> prerunTasks = _tasks.Select(x => x.task)
.Where(x => x.RunOnStartup && x.Priority == int.MaxValue);
foreach (ITask task in prerunTasks)
task.Run(_serviceProvider, _taskToken.Token);
foreach (IPlugin plugin in _pluginManager.GetAllPlugins())
if (plugin.Tasks != null)
_tasks.AddRange(plugin.Tasks.Select(x => (x, DateTime.Now + GetTaskDelay(x.Slug))));
} }
public IEnumerable<ITask> GetAllTasks() /// <inheritdoc />
public void ReloadTasks()
{ {
return _tasks.Select(x => x.task); _tasks = _container.ResolveAll<ITask>().Select(x => (x, DateTime.Now + GetTaskDelay(x.Slug))).ToList();
EnqueueStartupTasks();
} }
} }
} }

26
Kyoo/CoreModule.cs Normal file
View File

@ -0,0 +1,26 @@
using Kyoo.Controllers;
using Unity;
namespace Kyoo
{
/// <summary>
/// The core module ccontaining default implementations
/// </summary>
public class CoreModule : IPlugin
{
/// <inheritdoc />
public string Slug => "core";
/// <inheritdoc />
public string Name => "Core";
/// <inheritdoc />
public string Description => "The core module containing default implementations.";
/// <inheritdoc cref="IPlugin.Configure" />
public static void Configure(IUnityContainer container)
{
container.AddTask<Crawler>();
}
}
}

View File

@ -33,6 +33,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Unity.Container" Version="5.11.11" />
<PackageReference Include="Unity.Microsoft.DependencyInjection" Version="5.11.5" />
<ProjectReference Include="../Kyoo.Common/Kyoo.Common.csproj" /> <ProjectReference Include="../Kyoo.Common/Kyoo.Common.csproj" />
<ProjectReference Include="../Kyoo.CommonAPI/Kyoo.CommonAPI.csproj" /> <ProjectReference Include="../Kyoo.CommonAPI/Kyoo.CommonAPI.csproj" />
<PackageReference Include="IdentityServer4" Version="4.1.1" /> <PackageReference Include="IdentityServer4" Version="4.1.1" />
@ -56,7 +58,6 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="5.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup> </ItemGroup>

View File

@ -1,13 +1,14 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.StaticWebAssets; using Microsoft.AspNetCore.Hosting.StaticWebAssets;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Unity;
using Unity.Microsoft.DependencyInjection;
namespace Kyoo namespace Kyoo
{ {
@ -39,6 +40,7 @@ namespace Kyoo
if (debug == null && Environment.GetEnvironmentVariable("ENVIRONMENT") != null) if (debug == null && Environment.GetEnvironmentVariable("ENVIRONMENT") != null)
Console.WriteLine($"Invalid ENVIRONMENT variable. Supported values are \"debug\" and \"prod\". Ignoring..."); Console.WriteLine($"Invalid ENVIRONMENT variable. Supported values are \"debug\" and \"prod\". Ignoring...");
#if DEBUG #if DEBUG
debug ??= true; debug ??= true;
#endif #endif
@ -70,7 +72,8 @@ namespace Kyoo
/// <returns>A new web host instance</returns> /// <returns>A new web host instance</returns>
private static IWebHostBuilder CreateWebHostBuilder(string[] args) private static IWebHostBuilder CreateWebHostBuilder(string[] args)
{ {
WebHost.CreateDefaultBuilder(args); UnityContainer container = new();
container.EnableDebugDiagnostic();
return new WebHostBuilder() return new WebHostBuilder()
.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory) .UseContentRoot(AppDomain.CurrentDomain.BaseDirectory)
@ -89,6 +92,7 @@ namespace Kyoo
if (context.HostingEnvironment.IsDevelopment()) if (context.HostingEnvironment.IsDevelopment())
StaticWebAssetsLoader.UseStaticWebAssets(context.HostingEnvironment, context.Configuration); StaticWebAssetsLoader.UseStaticWebAssets(context.HostingEnvironment, context.Configuration);
}) })
.UseUnityServiceProvider(container)
.ConfigureServices(x => x.AddRouting()) .ConfigureServices(x => x.AddRouting())
.UseKestrel(options => { options.AddServerHeader = false; }) .UseKestrel(options => { options.AddServerHeader = false; })
.UseIIS() .UseIIS()

View File

@ -20,6 +20,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Unity;
namespace Kyoo namespace Kyoo
{ {
@ -185,7 +186,7 @@ namespace Kyoo
services.AddHostedService(provider => (TaskManager)provider.GetService<ITaskManager>()); services.AddHostedService(provider => (TaskManager)provider.GetService<ITaskManager>());
} }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IUnityContainer container)
{ {
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {
@ -247,6 +248,9 @@ namespace Kyoo
if (env.IsDevelopment()) if (env.IsDevelopment())
spa.UseAngularCliServer("start"); spa.UseAngularCliServer("start");
}); });
CoreModule.Configure(container);
container.Resolve<ITaskManager>().ReloadTasks();
} }
} }
} }

View File

@ -1,18 +0,0 @@
using Kyoo.Controllers;
using Kyoo.Models;
namespace Kyoo.Tasks
{
public static class CoreTaskHolder
{
public static readonly ITask[] Tasks =
{
new CreateDatabase(),
new PluginLoader(),
new Crawler(),
new MetadataProviderLoader(),
// new ReScan(),
new ExtractMetadata()
};
}
}

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Models.Attributes;
using Kyoo.Models.Exceptions; using Kyoo.Models.Exceptions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -21,19 +22,20 @@ namespace Kyoo.Controllers
public bool RunOnStartup => true; public bool RunOnStartup => true;
public int Priority => 0; public int Priority => 0;
private IServiceProvider _serviceProvider; [Injected] public IServiceProvider ServiceProvider { private get; set; }
private IThumbnailsManager _thumbnailsManager; [Injected] public IThumbnailsManager ThumbnailsManager { private get; set; }
private IProviderManager _metadataProvider; [Injected] public IProviderManager MetadataProvider { private get; set; }
private ITranscoder _transcoder; [Injected] public ITranscoder Transcoder { private get; set; }
private IConfiguration _config; [Injected] public IConfiguration Config { private get; set; }
private int _parallelTasks; private int _parallelTasks;
public async Task<IEnumerable<string>> GetPossibleParameters() public TaskParameters GetParameters()
{ {
using IServiceScope serviceScope = _serviceProvider.CreateScope(); return new()
ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>(); {
return (await libraryManager!.GetAll<Library>()).Select(x => x.Slug); TaskParameter.Create<string>("slug", "A library slug to restrict the scan to this library.")
};
} }
public int? Progress() public int? Progress()
@ -42,20 +44,16 @@ namespace Kyoo.Controllers
return null; return null;
} }
public async Task Run(IServiceProvider serviceProvider, public async Task Run(TaskParameters parameters,
CancellationToken cancellationToken, CancellationToken cancellationToken)
string argument = null)
{ {
_serviceProvider = serviceProvider; string argument = parameters["slug"].As<string>();
_thumbnailsManager = serviceProvider.GetService<IThumbnailsManager>();
_metadataProvider = serviceProvider.GetService<IProviderManager>(); _parallelTasks = Config.GetValue<int>("parallelTasks");
_transcoder = serviceProvider.GetService<ITranscoder>();
_config = serviceProvider.GetService<IConfiguration>();
_parallelTasks = _config.GetValue<int>("parallelTasks");
if (_parallelTasks <= 0) if (_parallelTasks <= 0)
_parallelTasks = 30; _parallelTasks = 30;
using IServiceScope serviceScope = _serviceProvider.CreateScope(); using IServiceScope serviceScope = ServiceProvider.CreateScope();
ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>(); ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
foreach (Show show in await libraryManager!.GetAll<Show>()) foreach (Show show in await libraryManager!.GetAll<Show>())
@ -148,10 +146,10 @@ namespace Kyoo.Controllers
{ {
if (token.IsCancellationRequested || path.Split(Path.DirectorySeparatorChar).Contains("Subtitles")) if (token.IsCancellationRequested || path.Split(Path.DirectorySeparatorChar).Contains("Subtitles"))
return; return;
using IServiceScope serviceScope = _serviceProvider.CreateScope(); using IServiceScope serviceScope = ServiceProvider.CreateScope();
ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>(); ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
string patern = _config.GetValue<string>("subtitleRegex"); string patern = Config.GetValue<string>("subtitleRegex");
Regex regex = new(patern, RegexOptions.IgnoreCase); Regex regex = new(patern, RegexOptions.IgnoreCase);
Match match = regex.Match(path); Match match = regex.Match(path);
@ -195,10 +193,10 @@ namespace Kyoo.Controllers
try try
{ {
using IServiceScope serviceScope = _serviceProvider.CreateScope(); using IServiceScope serviceScope = ServiceProvider.CreateScope();
ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>(); ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
string patern = _config.GetValue<string>("regex"); string patern = Config.GetValue<string>("regex");
Regex regex = new(patern, RegexOptions.IgnoreCase); Regex regex = new(patern, RegexOptions.IgnoreCase);
Match match = regex.Match(relativePath); Match match = regex.Match(relativePath);
@ -257,7 +255,7 @@ namespace Kyoo.Controllers
Collection collection = await libraryManager.Get<Collection>(Utility.ToSlug(collectionName)); Collection collection = await libraryManager.Get<Collection>(Utility.ToSlug(collectionName));
if (collection != null) if (collection != null)
return collection; return collection;
collection = await _metadataProvider.GetCollectionFromName(collectionName, library); collection = await MetadataProvider.GetCollectionFromName(collectionName, library);
try try
{ {
@ -282,9 +280,9 @@ namespace Kyoo.Controllers
await libraryManager.Load(old, x => x.ExternalIDs); await libraryManager.Load(old, x => x.ExternalIDs);
return old; return old;
} }
Show show = await _metadataProvider.SearchShow(showTitle, isMovie, library); Show show = await MetadataProvider.SearchShow(showTitle, isMovie, library);
show.Path = showPath; show.Path = showPath;
show.People = await _metadataProvider.GetPeople(show, library); show.People = await MetadataProvider.GetPeople(show, library);
try try
{ {
@ -301,7 +299,7 @@ namespace Kyoo.Controllers
show.Slug += $"-{show.StartYear}"; show.Slug += $"-{show.StartYear}";
await libraryManager.Create(show); await libraryManager.Create(show);
} }
await _thumbnailsManager.Validate(show); await ThumbnailsManager.Validate(show);
return show; return show;
} }
@ -320,9 +318,9 @@ namespace Kyoo.Controllers
} }
catch (ItemNotFound) catch (ItemNotFound)
{ {
Season season = await _metadataProvider.GetSeason(show, seasonNumber, library); Season season = await MetadataProvider.GetSeason(show, seasonNumber, library);
await libraryManager.CreateIfNotExists(season); await libraryManager.CreateIfNotExists(season);
await _thumbnailsManager.Validate(season); await ThumbnailsManager.Validate(season);
season.Show = show; season.Show = show;
return season; return season;
} }
@ -336,7 +334,7 @@ namespace Kyoo.Controllers
string episodePath, string episodePath,
Library library) Library library)
{ {
Episode episode = await _metadataProvider.GetEpisode(show, Episode episode = await MetadataProvider.GetEpisode(show,
episodePath, episodePath,
season?.SeasonNumber ?? -1, season?.SeasonNumber ?? -1,
episodeNumber, episodeNumber,
@ -346,7 +344,7 @@ namespace Kyoo.Controllers
season ??= await GetSeason(libraryManager, show, episode.SeasonNumber, library); season ??= await GetSeason(libraryManager, show, episode.SeasonNumber, library);
episode.Season = season; episode.Season = season;
episode.SeasonID = season?.ID; episode.SeasonID = season?.ID;
await _thumbnailsManager.Validate(episode); await ThumbnailsManager.Validate(episode);
await GetTracks(episode); await GetTracks(episode);
return episode; return episode;
} }
@ -367,7 +365,7 @@ namespace Kyoo.Controllers
private async Task<ICollection<Track>> GetTracks(Episode episode) private async Task<ICollection<Track>> GetTracks(Episode episode)
{ {
episode.Tracks = (await _transcoder.ExtractInfos(episode, false)) episode.Tracks = (await Transcoder.ExtractInfos(episode, false))
.Where(x => x.Type != StreamType.Attachment) .Where(x => x.Type != StreamType.Attachment)
.ToArray(); .ToArray();
return episode.Tracks; return episode.Tracks;

View File

@ -1,66 +1,66 @@
using System; // using System;
using System.Collections.Generic; // using System.Collections.Generic;
using System.Linq; // using System.Linq;
using System.Threading; // using System.Threading;
using System.Threading.Tasks; // using System.Threading.Tasks;
using IdentityServer4.EntityFramework.DbContexts; // using IdentityServer4.EntityFramework.DbContexts;
using IdentityServer4.EntityFramework.Mappers; // using IdentityServer4.EntityFramework.Mappers;
using IdentityServer4.Models; // using IdentityServer4.Models;
using Kyoo.Models; // using Kyoo.Models;
using Microsoft.EntityFrameworkCore; // using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; // using Microsoft.Extensions.DependencyInjection;
//
namespace Kyoo.Tasks // namespace Kyoo.Tasks
{ // {
public class CreateDatabase : ITask // public class CreateDatabase : ITask
{ // {
public string Slug => "create-database"; // public string Slug => "create-database";
public string Name => "Create the database"; // public string Name => "Create the database";
public string Description => "Create the database if it does not exit and initialize it with defaults value."; // public string Description => "Create the database if it does not exit and initialize it with defaults value.";
public string HelpMessage => null; // public string HelpMessage => null;
public bool RunOnStartup => true; // public bool RunOnStartup => true;
public int Priority => int.MaxValue; // public int Priority => int.MaxValue;
//
public Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null) // public Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null)
{ // {
using IServiceScope serviceScope = serviceProvider.CreateScope(); // using IServiceScope serviceScope = serviceProvider.CreateScope();
DatabaseContext databaseContext = serviceScope.ServiceProvider.GetService<DatabaseContext>(); // DatabaseContext databaseContext = serviceScope.ServiceProvider.GetService<DatabaseContext>();
IdentityDatabase identityDatabase = serviceScope.ServiceProvider.GetService<IdentityDatabase>(); // IdentityDatabase identityDatabase = serviceScope.ServiceProvider.GetService<IdentityDatabase>();
ConfigurationDbContext identityContext = serviceScope.ServiceProvider.GetService<ConfigurationDbContext>(); // ConfigurationDbContext identityContext = serviceScope.ServiceProvider.GetService<ConfigurationDbContext>();
//
databaseContext!.Database.Migrate(); // databaseContext!.Database.Migrate();
identityDatabase!.Database.Migrate(); // identityDatabase!.Database.Migrate();
identityContext!.Database.Migrate(); // identityContext!.Database.Migrate();
//
if (!identityContext.Clients.Any()) // if (!identityContext.Clients.Any())
{ // {
foreach (Client client in IdentityContext.GetClients()) // foreach (Client client in IdentityContext.GetClients())
identityContext.Clients.Add(client.ToEntity()); // identityContext.Clients.Add(client.ToEntity());
identityContext.SaveChanges(); // identityContext.SaveChanges();
} // }
if (!identityContext.IdentityResources.Any()) // if (!identityContext.IdentityResources.Any())
{ // {
foreach (IdentityResource resource in IdentityContext.GetIdentityResources()) // foreach (IdentityResource resource in IdentityContext.GetIdentityResources())
identityContext.IdentityResources.Add(resource.ToEntity()); // identityContext.IdentityResources.Add(resource.ToEntity());
identityContext.SaveChanges(); // identityContext.SaveChanges();
} // }
if (!identityContext.ApiResources.Any()) // if (!identityContext.ApiResources.Any())
{ // {
foreach (ApiResource resource in IdentityContext.GetApis()) // foreach (ApiResource resource in IdentityContext.GetApis())
identityContext.ApiResources.Add(resource.ToEntity()); // identityContext.ApiResources.Add(resource.ToEntity());
identityContext.SaveChanges(); // identityContext.SaveChanges();
} // }
return Task.CompletedTask; // return Task.CompletedTask;
} // }
//
public Task<IEnumerable<string>> GetPossibleParameters() // public Task<IEnumerable<string>> GetPossibleParameters()
{ // {
return Task.FromResult<IEnumerable<string>>(null); // return Task.FromResult<IEnumerable<string>>(null);
} // }
//
public int? Progress() // public int? Progress()
{ // {
return null; // return null;
} // }
} // }
} // }

View File

@ -1,120 +1,120 @@
using System; // using System;
using System.Collections.Generic; // using System.Collections.Generic;
using System.Linq; // using System.Linq;
using System.Threading; // using System.Threading;
using System.Threading.Tasks; // using System.Threading.Tasks;
using Kyoo.Controllers; // using Kyoo.Controllers;
using Kyoo.Models; // using Kyoo.Models;
using Microsoft.Extensions.DependencyInjection; // using Microsoft.Extensions.DependencyInjection;
//
namespace Kyoo.Tasks // namespace Kyoo.Tasks
{ // {
public class ExtractMetadata : ITask // public class ExtractMetadata : ITask
{ // {
public string Slug => "extract"; // public string Slug => "extract";
public string Name => "Metadata Extractor"; // public string Name => "Metadata Extractor";
public string Description => "Extract subtitles or download thumbnails for a show/episode."; // public string Description => "Extract subtitles or download thumbnails for a show/episode.";
public string HelpMessage => null; // public string HelpMessage => null;
public bool RunOnStartup => false; // public bool RunOnStartup => false;
public int Priority => 0; // public int Priority => 0;
//
//
private ILibraryManager _library; // private ILibraryManager _library;
private IThumbnailsManager _thumbnails; // private IThumbnailsManager _thumbnails;
private ITranscoder _transcoder; // private ITranscoder _transcoder;
//
public async Task Run(IServiceProvider serviceProvider, CancellationToken token, string arguments = null) // public async Task Run(IServiceProvider serviceProvider, CancellationToken token, string arguments = null)
{ // {
string[] args = arguments?.Split('/'); // string[] args = arguments?.Split('/');
//
if (args == null || args.Length < 2) // if (args == null || args.Length < 2)
return; // return;
//
string slug = args[1]; // string slug = args[1];
bool thumbs = args.Length < 3 || string.Equals(args[2], "thumbnails", StringComparison.InvariantCultureIgnoreCase); // bool thumbs = args.Length < 3 || string.Equals(args[2], "thumbnails", StringComparison.InvariantCultureIgnoreCase);
bool subs = args.Length < 3 || string.Equals(args[2], "subs", StringComparison.InvariantCultureIgnoreCase); // bool subs = args.Length < 3 || string.Equals(args[2], "subs", StringComparison.InvariantCultureIgnoreCase);
//
using IServiceScope serviceScope = serviceProvider.CreateScope(); // using IServiceScope serviceScope = serviceProvider.CreateScope();
_library = serviceScope.ServiceProvider.GetService<ILibraryManager>(); // _library = serviceScope.ServiceProvider.GetService<ILibraryManager>();
_thumbnails = serviceScope.ServiceProvider.GetService<IThumbnailsManager>(); // _thumbnails = serviceScope.ServiceProvider.GetService<IThumbnailsManager>();
_transcoder = serviceScope.ServiceProvider.GetService<ITranscoder>(); // _transcoder = serviceScope.ServiceProvider.GetService<ITranscoder>();
int id; // int id;
//
switch (args[0].ToLowerInvariant()) // switch (args[0].ToLowerInvariant())
{ // {
case "show": // case "show":
case "shows": // case "shows":
Show show = await (int.TryParse(slug, out id) // Show show = await (int.TryParse(slug, out id)
? _library!.Get<Show>(id) // ? _library!.Get<Show>(id)
: _library!.Get<Show>(slug)); // : _library!.Get<Show>(slug));
await ExtractShow(show, thumbs, subs, token); // await ExtractShow(show, thumbs, subs, token);
break; // break;
case "season": // case "season":
case "seasons": // case "seasons":
Season season = await (int.TryParse(slug, out id) // Season season = await (int.TryParse(slug, out id)
? _library!.Get<Season>(id) // ? _library!.Get<Season>(id)
: _library!.Get<Season>(slug)); // : _library!.Get<Season>(slug));
await ExtractSeason(season, thumbs, subs, token); // await ExtractSeason(season, thumbs, subs, token);
break; // break;
case "episode": // case "episode":
case "episodes": // case "episodes":
Episode episode = await (int.TryParse(slug, out id) // Episode episode = await (int.TryParse(slug, out id)
? _library!.Get<Episode>(id) // ? _library!.Get<Episode>(id)
: _library!.Get<Episode>(slug)); // : _library!.Get<Episode>(slug));
await ExtractEpisode(episode, thumbs, subs); // await ExtractEpisode(episode, thumbs, subs);
break; // break;
} // }
} // }
//
private async Task ExtractShow(Show show, bool thumbs, bool subs, CancellationToken token) // private async Task ExtractShow(Show show, bool thumbs, bool subs, CancellationToken token)
{ // {
if (thumbs) // if (thumbs)
await _thumbnails!.Validate(show, true); // await _thumbnails!.Validate(show, true);
await _library.Load(show, x => x.Seasons); // await _library.Load(show, x => x.Seasons);
foreach (Season season in show.Seasons) // foreach (Season season in show.Seasons)
{ // {
if (token.IsCancellationRequested) // if (token.IsCancellationRequested)
return; // return;
await ExtractSeason(season, thumbs, subs, token); // await ExtractSeason(season, thumbs, subs, token);
} // }
} // }
//
private async Task ExtractSeason(Season season, bool thumbs, bool subs, CancellationToken token) // private async Task ExtractSeason(Season season, bool thumbs, bool subs, CancellationToken token)
{ // {
if (thumbs) // if (thumbs)
await _thumbnails!.Validate(season, true); // await _thumbnails!.Validate(season, true);
await _library.Load(season, x => x.Episodes); // await _library.Load(season, x => x.Episodes);
foreach (Episode episode in season.Episodes) // foreach (Episode episode in season.Episodes)
{ // {
if (token.IsCancellationRequested) // if (token.IsCancellationRequested)
return; // return;
await ExtractEpisode(episode, thumbs, subs); // await ExtractEpisode(episode, thumbs, subs);
} // }
} // }
//
private async Task ExtractEpisode(Episode episode, bool thumbs, bool subs) // private async Task ExtractEpisode(Episode episode, bool thumbs, bool subs)
{ // {
if (thumbs) // if (thumbs)
await _thumbnails!.Validate(episode, true); // await _thumbnails!.Validate(episode, true);
if (subs) // if (subs)
{ // {
await _library.Load(episode, x => x.Tracks); // await _library.Load(episode, x => x.Tracks);
episode.Tracks = (await _transcoder!.ExtractInfos(episode, true)) // episode.Tracks = (await _transcoder!.ExtractInfos(episode, true))
.Where(x => x.Type != StreamType.Attachment) // .Where(x => x.Type != StreamType.Attachment)
.Concat(episode.Tracks.Where(x => x.IsExternal)) // .Concat(episode.Tracks.Where(x => x.IsExternal))
.ToList(); // .ToList();
await _library.Edit(episode, false); // await _library.Edit(episode, false);
} // }
} // }
//
public Task<IEnumerable<string>> GetPossibleParameters() // public Task<IEnumerable<string>> GetPossibleParameters()
{ // {
return Task.FromResult<IEnumerable<string>>(null); // return Task.FromResult<IEnumerable<string>>(null);
} // }
//
public int? Progress() // public int? Progress()
{ // {
return null; // return null;
} // }
} // }
} // }

View File

@ -1,46 +1,46 @@
using System; // using System;
using System.Collections.Generic; // using System.Collections.Generic;
using System.Threading; // using System.Threading;
using System.Threading.Tasks; // using System.Threading.Tasks;
using Kyoo.Controllers; // using Kyoo.Controllers;
using Kyoo.Models; // using Kyoo.Models;
using Microsoft.Extensions.DependencyInjection; // using Microsoft.Extensions.DependencyInjection;
//
namespace Kyoo.Tasks // namespace Kyoo.Tasks
{ // {
public class MetadataProviderLoader : ITask // public class MetadataProviderLoader : ITask
{ // {
public string Slug => "reload-metdata"; // public string Slug => "reload-metdata";
public string Name => "Reload Metadata Providers"; // public string Name => "Reload Metadata Providers";
public string Description => "Add every loaded metadata provider to the database."; // public string Description => "Add every loaded metadata provider to the database.";
public string HelpMessage => null; // public string HelpMessage => null;
public bool RunOnStartup => true; // public bool RunOnStartup => true;
public int Priority => 1000; // public int Priority => 1000;
//
public async Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null) // public async Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null)
{ // {
using IServiceScope serviceScope = serviceProvider.CreateScope(); // using IServiceScope serviceScope = serviceProvider.CreateScope();
IProviderRepository providers = serviceScope.ServiceProvider.GetService<IProviderRepository>(); // IProviderRepository providers = serviceScope.ServiceProvider.GetService<IProviderRepository>();
IThumbnailsManager thumbnails = serviceScope.ServiceProvider.GetService<IThumbnailsManager>(); // IThumbnailsManager thumbnails = serviceScope.ServiceProvider.GetService<IThumbnailsManager>();
IPluginManager pluginManager = serviceScope.ServiceProvider.GetService<IPluginManager>(); // IPluginManager pluginManager = serviceScope.ServiceProvider.GetService<IPluginManager>();
//
foreach (IMetadataProvider provider in pluginManager!.GetPlugins<IMetadataProvider>()) // foreach (IMetadataProvider provider in pluginManager!.GetPlugins<IMetadataProvider>())
{ // {
if (string.IsNullOrEmpty(provider.Provider.Slug)) // if (string.IsNullOrEmpty(provider.Provider.Slug))
throw new ArgumentException($"Empty provider slug (name: {provider.Provider.Name})."); // throw new ArgumentException($"Empty provider slug (name: {provider.Provider.Name}).");
await providers!.CreateIfNotExists(provider.Provider); // await providers!.CreateIfNotExists(provider.Provider);
await thumbnails!.Validate(provider.Provider); // await thumbnails!.Validate(provider.Provider);
} // }
} // }
//
public Task<IEnumerable<string>> GetPossibleParameters() // public Task<IEnumerable<string>> GetPossibleParameters()
{ // {
return Task.FromResult<IEnumerable<string>>(null); // return Task.FromResult<IEnumerable<string>>(null);
} // }
//
public int? Progress() // public int? Progress()
{ // {
return null; // return null;
} // }
} // }
} // }

View File

@ -1,37 +1,37 @@
using System; // using System;
using System.Collections.Generic; // using System.Collections.Generic;
using System.Threading; // using System.Threading;
using System.Threading.Tasks; // using System.Threading.Tasks;
using Kyoo.Controllers; // using Kyoo.Controllers;
using Kyoo.Models; // using Kyoo.Models;
using Microsoft.Extensions.DependencyInjection; // using Microsoft.Extensions.DependencyInjection;
//
namespace Kyoo.Tasks // namespace Kyoo.Tasks
{ // {
public class PluginLoader : ITask // public class PluginLoader : ITask
{ // {
public string Slug => "reload-plugin"; // public string Slug => "reload-plugin";
public string Name => "Reload plugins"; // public string Name => "Reload plugins";
public string Description => "Reload all plugins from the plugin folder."; // public string Description => "Reload all plugins from the plugin folder.";
public string HelpMessage => null; // public string HelpMessage => null;
public bool RunOnStartup => true; // public bool RunOnStartup => true;
public int Priority => Int32.MaxValue; // public int Priority => Int32.MaxValue;
public Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null) // public Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null)
{ // {
using IServiceScope serviceScope = serviceProvider.CreateScope(); // using IServiceScope serviceScope = serviceProvider.CreateScope();
IPluginManager pluginManager = serviceScope.ServiceProvider.GetService<IPluginManager>(); // IPluginManager pluginManager = serviceScope.ServiceProvider.GetService<IPluginManager>();
pluginManager.ReloadPlugins(); // pluginManager.ReloadPlugins();
return Task.CompletedTask; // return Task.CompletedTask;
} // }
//
public Task<IEnumerable<string>> GetPossibleParameters() // public Task<IEnumerable<string>> GetPossibleParameters()
{ // {
return Task.FromResult<IEnumerable<string>>(null); // return Task.FromResult<IEnumerable<string>>(null);
} // }
//
public int? Progress() // public int? Progress()
{ // {
return null; // return null;
} // }
} // }
} // }

View File

@ -32,7 +32,7 @@ namespace Kyoo.Api
{ {
ActionResult<Library> result = await base.Create(resource); ActionResult<Library> result = await base.Create(resource);
if (result.Value != null) if (result.Value != null)
_taskManager.StartTask("scan", result.Value.Slug); _taskManager.StartTask("scan", new Dictionary<string, object> {{"slug", result.Value.Slug}});
return result; return result;
} }

View File

@ -1,29 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Kyoo.Controllers;
using Microsoft.AspNetCore.Authorization;
namespace Kyoo.Api
{
[Route("api/[controller]")]
[ApiController]
public class TaskController : ControllerBase
{
private readonly ITaskManager _taskManager;
public TaskController(ITaskManager taskManager)
{
_taskManager = taskManager;
}
[HttpGet("{taskSlug}/{*args}")]
[HttpPut("{taskSlug}/{*args}")]
[Authorize(Policy="Admin")]
public IActionResult RunTask(string taskSlug, string args = null)
{
if (_taskManager.StartTask(taskSlug, args))
return Ok();
return NotFound();
}
}
}

49
Kyoo/Views/TaskApi.cs Normal file
View File

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Kyoo.Controllers;
using Kyoo.Models.Exceptions;
using Microsoft.AspNetCore.Authorization;
namespace Kyoo.Api
{
[Route("api/task")]
[Route("api/tasks")]
[ApiController]
[Authorize(Policy="Admin")]
public class TaskApi : ControllerBase
{
private readonly ITaskManager _taskManager;
public TaskApi(ITaskManager taskManager)
{
_taskManager = taskManager;
}
[HttpGet]
public ActionResult<ICollection<ITask>> GetTasks()
{
return Ok(_taskManager.GetAllTasks());
}
[HttpGet("{taskSlug}")]
[HttpPut("{taskSlug}")]
public IActionResult RunTask(string taskSlug, [FromQuery] Dictionary<string, object> args)
{
try
{
_taskManager.StartTask(taskSlug, args);
return Ok();
}
catch (ItemNotFound)
{
return NotFound();
}
catch (ArgumentException ex)
{
return BadRequest(new {Error = ex.Message});
}
}
}
}

View File

@ -17,10 +17,12 @@
"logLevel": { "logLevel": {
"default": "Warning", "default": "Warning",
"Microsoft": "Warning", "Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information" "Microsoft.Hosting.Lifetime": "Information",
"Kyoo": "Trace"
} }
}, },
"parallelTasks": "1", "parallelTasks": "1",
"scheduledTasks": { "scheduledTasks": {