mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Reworking the task manager & using unity containers
This commit is contained in:
parent
0221cb7873
commit
b7294114b9
39
Kyoo.Common/Controllers/IPlugin.cs
Normal file
39
Kyoo.Common/Controllers/IPlugin.cs
Normal 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<MyTask>()
|
||||
/// }
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
static void Configure() { }
|
||||
}
|
||||
}
|
188
Kyoo.Common/Controllers/ITask.cs
Normal file
188
Kyoo.Common/Controllers/ITask.cs
Normal 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();
|
||||
}
|
||||
}
|
@ -1,13 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Models;
|
||||
using Kyoo.Models.Exceptions;
|
||||
|
||||
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
|
||||
{
|
||||
bool StartTask(string taskSlug, string arguments = null);
|
||||
ITask GetRunningTask();
|
||||
void ReloadTask();
|
||||
IEnumerable<ITask> GetAllTasks();
|
||||
/// <summary>
|
||||
/// Start a new task (or queue it).
|
||||
/// </summary>
|
||||
/// <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();
|
||||
}
|
||||
}
|
@ -24,6 +24,13 @@
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.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="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>
|
||||
|
||||
</Project>
|
||||
|
14
Kyoo.Common/Models/Attributes/InjectedAttribute.cs
Normal file
14
Kyoo.Common/Models/Attributes/InjectedAttribute.cs
Normal 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 { }
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Kyoo.Models
|
||||
{
|
||||
public interface IPlugin
|
||||
{
|
||||
public string Name { get; }
|
||||
public ICollection<ITask> Tasks { get; }
|
||||
}
|
||||
}
|
@ -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
24
Kyoo.Common/Module.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -529,9 +529,9 @@ namespace Kyoo
|
||||
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.GetGenericArguments().Length == generics.Length)
|
||||
.Where(x => x.GetParameters().Length == args.Length)
|
||||
|
@ -1,56 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Models;
|
||||
using Kyoo.Tasks;
|
||||
using Kyoo.Models.Attributes;
|
||||
using Kyoo.Models.Exceptions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Unity;
|
||||
|
||||
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
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IPluginManager _pluginManager;
|
||||
/// <summary>
|
||||
/// 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;
|
||||
/// <summary>
|
||||
/// The logger instance.
|
||||
/// </summary>
|
||||
private readonly ILogger<TaskManager> _logger;
|
||||
|
||||
private List<(ITask task, DateTime scheduledDate)> _tasks = new List<(ITask, DateTime)>();
|
||||
private CancellationTokenSource _taskToken = new CancellationTokenSource();
|
||||
/// <summary>
|
||||
/// 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 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;
|
||||
_pluginManager = pluginManager;
|
||||
_configuration = configuration;
|
||||
_tasks = tasks.Select(x => (x, DateTime.Now + GetTaskDelay(x.Slug))).ToList();
|
||||
_container = container;
|
||||
_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)
|
||||
{
|
||||
ReloadTask();
|
||||
|
||||
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));
|
||||
EnqueueStartupTasks();
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (_queuedTasks.Any())
|
||||
{
|
||||
(ITask task, string arguments) = _queuedTasks.Dequeue();
|
||||
(ITask task, Dictionary<string, object> arguments) = _queuedTasks.Dequeue();
|
||||
_runningTask = task;
|
||||
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)
|
||||
{
|
||||
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
|
||||
@ -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()
|
||||
{
|
||||
IEnumerable<string> tasksToQueue = _tasks.Where(x => x.scheduledDate <= DateTime.Now)
|
||||
.Select(x => x.task.Slug);
|
||||
foreach (string task in tasksToQueue)
|
||||
StartTask(task);
|
||||
}
|
||||
|
||||
public override Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Task.Run(() => base.StartAsync(cancellationToken));
|
||||
return Task.CompletedTask;
|
||||
_logger.LogDebug("Queuing task scheduled for running: {Task}", task);
|
||||
StartTask(task, new Dictionary<string, object>());
|
||||
}
|
||||
}
|
||||
|
||||
public override Task StopAsync(CancellationToken cancellationToken)
|
||||
/// <summary>
|
||||
/// Queue startup tasks with respect to the priority rules.
|
||||
/// </summary>
|
||||
private void EnqueueStartupTasks()
|
||||
{
|
||||
_taskToken.Cancel();
|
||||
return base.StopAsync(cancellationToken);
|
||||
IEnumerable<ITask> startupTasks = _tasks.Select(x => x.task)
|
||||
.Where(x => x.RunOnStartup && x.Priority != int.MaxValue)
|
||||
.OrderByDescending(x => x.Priority);
|
||||
foreach (ITask task in startupTasks)
|
||||
_queuedTasks.Enqueue((task, null));
|
||||
}
|
||||
|
||||
public bool StartTask(string taskSlug, string arguments = null)
|
||||
/// <inheritdoc />
|
||||
public void StartTask(string taskSlug, Dictionary<string, object> arguments)
|
||||
{
|
||||
int index = _tasks.FindIndex(x => x.task.Slug == taskSlug);
|
||||
if (index == -1)
|
||||
return false;
|
||||
throw new ItemNotFound($"No task found with the slug {taskSlug}");
|
||||
_queuedTasks.Enqueue((_tasks[index].task, arguments));
|
||||
_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)
|
||||
delay = TimeSpan.FromDays(365);
|
||||
delay = TimeSpan.MaxValue;
|
||||
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();
|
||||
_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))));
|
||||
return _tasks.Select(x => x.task).ToArray();
|
||||
}
|
||||
|
||||
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
26
Kyoo/CoreModule.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
}
|
@ -33,6 +33,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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.CommonAPI/Kyoo.CommonAPI.csproj" />
|
||||
<PackageReference Include="IdentityServer4" Version="4.1.1" />
|
||||
@ -56,7 +58,6 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="5.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
|
@ -1,13 +1,14 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.StaticWebAssets;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Unity;
|
||||
using Unity.Microsoft.DependencyInjection;
|
||||
|
||||
namespace Kyoo
|
||||
{
|
||||
@ -39,6 +40,7 @@ namespace Kyoo
|
||||
|
||||
if (debug == null && Environment.GetEnvironmentVariable("ENVIRONMENT") != null)
|
||||
Console.WriteLine($"Invalid ENVIRONMENT variable. Supported values are \"debug\" and \"prod\". Ignoring...");
|
||||
|
||||
#if DEBUG
|
||||
debug ??= true;
|
||||
#endif
|
||||
@ -70,7 +72,8 @@ namespace Kyoo
|
||||
/// <returns>A new web host instance</returns>
|
||||
private static IWebHostBuilder CreateWebHostBuilder(string[] args)
|
||||
{
|
||||
WebHost.CreateDefaultBuilder(args);
|
||||
UnityContainer container = new();
|
||||
container.EnableDebugDiagnostic();
|
||||
|
||||
return new WebHostBuilder()
|
||||
.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory)
|
||||
@ -89,6 +92,7 @@ namespace Kyoo
|
||||
if (context.HostingEnvironment.IsDevelopment())
|
||||
StaticWebAssetsLoader.UseStaticWebAssets(context.HostingEnvironment, context.Configuration);
|
||||
})
|
||||
.UseUnityServiceProvider(container)
|
||||
.ConfigureServices(x => x.AddRouting())
|
||||
.UseKestrel(options => { options.AddServerHeader = false; })
|
||||
.UseIIS()
|
||||
|
@ -20,6 +20,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Unity;
|
||||
|
||||
namespace Kyoo
|
||||
{
|
||||
@ -185,7 +186,7 @@ namespace Kyoo
|
||||
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())
|
||||
{
|
||||
@ -247,6 +248,9 @@ namespace Kyoo
|
||||
if (env.IsDevelopment())
|
||||
spa.UseAngularCliServer("start");
|
||||
});
|
||||
|
||||
CoreModule.Configure(container);
|
||||
container.Resolve<ITaskManager>().ReloadTasks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
};
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Models.Attributes;
|
||||
using Kyoo.Models.Exceptions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@ -21,19 +22,20 @@ namespace Kyoo.Controllers
|
||||
public bool RunOnStartup => true;
|
||||
public int Priority => 0;
|
||||
|
||||
private IServiceProvider _serviceProvider;
|
||||
private IThumbnailsManager _thumbnailsManager;
|
||||
private IProviderManager _metadataProvider;
|
||||
private ITranscoder _transcoder;
|
||||
private IConfiguration _config;
|
||||
[Injected] public IServiceProvider ServiceProvider { private get; set; }
|
||||
[Injected] public IThumbnailsManager ThumbnailsManager { private get; set; }
|
||||
[Injected] public IProviderManager MetadataProvider { private get; set; }
|
||||
[Injected] public ITranscoder Transcoder { private get; set; }
|
||||
[Injected] public IConfiguration Config { private get; set; }
|
||||
|
||||
private int _parallelTasks;
|
||||
|
||||
public async Task<IEnumerable<string>> GetPossibleParameters()
|
||||
public TaskParameters GetParameters()
|
||||
{
|
||||
using IServiceScope serviceScope = _serviceProvider.CreateScope();
|
||||
ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
|
||||
return (await libraryManager!.GetAll<Library>()).Select(x => x.Slug);
|
||||
return new()
|
||||
{
|
||||
TaskParameter.Create<string>("slug", "A library slug to restrict the scan to this library.")
|
||||
};
|
||||
}
|
||||
|
||||
public int? Progress()
|
||||
@ -42,20 +44,16 @@ namespace Kyoo.Controllers
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task Run(IServiceProvider serviceProvider,
|
||||
CancellationToken cancellationToken,
|
||||
string argument = null)
|
||||
public async Task Run(TaskParameters parameters,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_thumbnailsManager = serviceProvider.GetService<IThumbnailsManager>();
|
||||
_metadataProvider = serviceProvider.GetService<IProviderManager>();
|
||||
_transcoder = serviceProvider.GetService<ITranscoder>();
|
||||
_config = serviceProvider.GetService<IConfiguration>();
|
||||
_parallelTasks = _config.GetValue<int>("parallelTasks");
|
||||
string argument = parameters["slug"].As<string>();
|
||||
|
||||
_parallelTasks = Config.GetValue<int>("parallelTasks");
|
||||
if (_parallelTasks <= 0)
|
||||
_parallelTasks = 30;
|
||||
|
||||
using IServiceScope serviceScope = _serviceProvider.CreateScope();
|
||||
using IServiceScope serviceScope = ServiceProvider.CreateScope();
|
||||
ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
|
||||
|
||||
foreach (Show show in await libraryManager!.GetAll<Show>())
|
||||
@ -148,10 +146,10 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
if (token.IsCancellationRequested || path.Split(Path.DirectorySeparatorChar).Contains("Subtitles"))
|
||||
return;
|
||||
using IServiceScope serviceScope = _serviceProvider.CreateScope();
|
||||
using IServiceScope serviceScope = ServiceProvider.CreateScope();
|
||||
ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
|
||||
|
||||
string patern = _config.GetValue<string>("subtitleRegex");
|
||||
string patern = Config.GetValue<string>("subtitleRegex");
|
||||
Regex regex = new(patern, RegexOptions.IgnoreCase);
|
||||
Match match = regex.Match(path);
|
||||
|
||||
@ -195,10 +193,10 @@ namespace Kyoo.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
using IServiceScope serviceScope = _serviceProvider.CreateScope();
|
||||
using IServiceScope serviceScope = ServiceProvider.CreateScope();
|
||||
ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
|
||||
|
||||
string patern = _config.GetValue<string>("regex");
|
||||
string patern = Config.GetValue<string>("regex");
|
||||
Regex regex = new(patern, RegexOptions.IgnoreCase);
|
||||
Match match = regex.Match(relativePath);
|
||||
|
||||
@ -257,7 +255,7 @@ namespace Kyoo.Controllers
|
||||
Collection collection = await libraryManager.Get<Collection>(Utility.ToSlug(collectionName));
|
||||
if (collection != null)
|
||||
return collection;
|
||||
collection = await _metadataProvider.GetCollectionFromName(collectionName, library);
|
||||
collection = await MetadataProvider.GetCollectionFromName(collectionName, library);
|
||||
|
||||
try
|
||||
{
|
||||
@ -282,9 +280,9 @@ namespace Kyoo.Controllers
|
||||
await libraryManager.Load(old, x => x.ExternalIDs);
|
||||
return old;
|
||||
}
|
||||
Show show = await _metadataProvider.SearchShow(showTitle, isMovie, library);
|
||||
Show show = await MetadataProvider.SearchShow(showTitle, isMovie, library);
|
||||
show.Path = showPath;
|
||||
show.People = await _metadataProvider.GetPeople(show, library);
|
||||
show.People = await MetadataProvider.GetPeople(show, library);
|
||||
|
||||
try
|
||||
{
|
||||
@ -301,7 +299,7 @@ namespace Kyoo.Controllers
|
||||
show.Slug += $"-{show.StartYear}";
|
||||
await libraryManager.Create(show);
|
||||
}
|
||||
await _thumbnailsManager.Validate(show);
|
||||
await ThumbnailsManager.Validate(show);
|
||||
return show;
|
||||
}
|
||||
|
||||
@ -320,9 +318,9 @@ namespace Kyoo.Controllers
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
{
|
||||
Season season = await _metadataProvider.GetSeason(show, seasonNumber, library);
|
||||
Season season = await MetadataProvider.GetSeason(show, seasonNumber, library);
|
||||
await libraryManager.CreateIfNotExists(season);
|
||||
await _thumbnailsManager.Validate(season);
|
||||
await ThumbnailsManager.Validate(season);
|
||||
season.Show = show;
|
||||
return season;
|
||||
}
|
||||
@ -336,7 +334,7 @@ namespace Kyoo.Controllers
|
||||
string episodePath,
|
||||
Library library)
|
||||
{
|
||||
Episode episode = await _metadataProvider.GetEpisode(show,
|
||||
Episode episode = await MetadataProvider.GetEpisode(show,
|
||||
episodePath,
|
||||
season?.SeasonNumber ?? -1,
|
||||
episodeNumber,
|
||||
@ -346,7 +344,7 @@ namespace Kyoo.Controllers
|
||||
season ??= await GetSeason(libraryManager, show, episode.SeasonNumber, library);
|
||||
episode.Season = season;
|
||||
episode.SeasonID = season?.ID;
|
||||
await _thumbnailsManager.Validate(episode);
|
||||
await ThumbnailsManager.Validate(episode);
|
||||
await GetTracks(episode);
|
||||
return episode;
|
||||
}
|
||||
@ -367,7 +365,7 @@ namespace Kyoo.Controllers
|
||||
|
||||
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)
|
||||
.ToArray();
|
||||
return episode.Tracks;
|
||||
|
@ -1,66 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using IdentityServer4.EntityFramework.DbContexts;
|
||||
using IdentityServer4.EntityFramework.Mappers;
|
||||
using IdentityServer4.Models;
|
||||
using Kyoo.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo.Tasks
|
||||
{
|
||||
public class CreateDatabase : ITask
|
||||
{
|
||||
public string Slug => "create-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 HelpMessage => null;
|
||||
public bool RunOnStartup => true;
|
||||
public int Priority => int.MaxValue;
|
||||
|
||||
public Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null)
|
||||
{
|
||||
using IServiceScope serviceScope = serviceProvider.CreateScope();
|
||||
DatabaseContext databaseContext = serviceScope.ServiceProvider.GetService<DatabaseContext>();
|
||||
IdentityDatabase identityDatabase = serviceScope.ServiceProvider.GetService<IdentityDatabase>();
|
||||
ConfigurationDbContext identityContext = serviceScope.ServiceProvider.GetService<ConfigurationDbContext>();
|
||||
|
||||
databaseContext!.Database.Migrate();
|
||||
identityDatabase!.Database.Migrate();
|
||||
identityContext!.Database.Migrate();
|
||||
|
||||
if (!identityContext.Clients.Any())
|
||||
{
|
||||
foreach (Client client in IdentityContext.GetClients())
|
||||
identityContext.Clients.Add(client.ToEntity());
|
||||
identityContext.SaveChanges();
|
||||
}
|
||||
if (!identityContext.IdentityResources.Any())
|
||||
{
|
||||
foreach (IdentityResource resource in IdentityContext.GetIdentityResources())
|
||||
identityContext.IdentityResources.Add(resource.ToEntity());
|
||||
identityContext.SaveChanges();
|
||||
}
|
||||
if (!identityContext.ApiResources.Any())
|
||||
{
|
||||
foreach (ApiResource resource in IdentityContext.GetApis())
|
||||
identityContext.ApiResources.Add(resource.ToEntity());
|
||||
identityContext.SaveChanges();
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<string>> GetPossibleParameters()
|
||||
{
|
||||
return Task.FromResult<IEnumerable<string>>(null);
|
||||
}
|
||||
|
||||
public int? Progress()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// using System;
|
||||
// using System.Collections.Generic;
|
||||
// using System.Linq;
|
||||
// using System.Threading;
|
||||
// using System.Threading.Tasks;
|
||||
// using IdentityServer4.EntityFramework.DbContexts;
|
||||
// using IdentityServer4.EntityFramework.Mappers;
|
||||
// using IdentityServer4.Models;
|
||||
// using Kyoo.Models;
|
||||
// using Microsoft.EntityFrameworkCore;
|
||||
// using Microsoft.Extensions.DependencyInjection;
|
||||
//
|
||||
// namespace Kyoo.Tasks
|
||||
// {
|
||||
// public class CreateDatabase : ITask
|
||||
// {
|
||||
// public string Slug => "create-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 HelpMessage => null;
|
||||
// public bool RunOnStartup => true;
|
||||
// public int Priority => int.MaxValue;
|
||||
//
|
||||
// public Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null)
|
||||
// {
|
||||
// using IServiceScope serviceScope = serviceProvider.CreateScope();
|
||||
// DatabaseContext databaseContext = serviceScope.ServiceProvider.GetService<DatabaseContext>();
|
||||
// IdentityDatabase identityDatabase = serviceScope.ServiceProvider.GetService<IdentityDatabase>();
|
||||
// ConfigurationDbContext identityContext = serviceScope.ServiceProvider.GetService<ConfigurationDbContext>();
|
||||
//
|
||||
// databaseContext!.Database.Migrate();
|
||||
// identityDatabase!.Database.Migrate();
|
||||
// identityContext!.Database.Migrate();
|
||||
//
|
||||
// if (!identityContext.Clients.Any())
|
||||
// {
|
||||
// foreach (Client client in IdentityContext.GetClients())
|
||||
// identityContext.Clients.Add(client.ToEntity());
|
||||
// identityContext.SaveChanges();
|
||||
// }
|
||||
// if (!identityContext.IdentityResources.Any())
|
||||
// {
|
||||
// foreach (IdentityResource resource in IdentityContext.GetIdentityResources())
|
||||
// identityContext.IdentityResources.Add(resource.ToEntity());
|
||||
// identityContext.SaveChanges();
|
||||
// }
|
||||
// if (!identityContext.ApiResources.Any())
|
||||
// {
|
||||
// foreach (ApiResource resource in IdentityContext.GetApis())
|
||||
// identityContext.ApiResources.Add(resource.ToEntity());
|
||||
// identityContext.SaveChanges();
|
||||
// }
|
||||
// return Task.CompletedTask;
|
||||
// }
|
||||
//
|
||||
// public Task<IEnumerable<string>> GetPossibleParameters()
|
||||
// {
|
||||
// return Task.FromResult<IEnumerable<string>>(null);
|
||||
// }
|
||||
//
|
||||
// public int? Progress()
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
// }
|
@ -1,120 +1,120 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Controllers;
|
||||
using Kyoo.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo.Tasks
|
||||
{
|
||||
public class ExtractMetadata : ITask
|
||||
{
|
||||
public string Slug => "extract";
|
||||
public string Name => "Metadata Extractor";
|
||||
public string Description => "Extract subtitles or download thumbnails for a show/episode.";
|
||||
public string HelpMessage => null;
|
||||
public bool RunOnStartup => false;
|
||||
public int Priority => 0;
|
||||
|
||||
|
||||
private ILibraryManager _library;
|
||||
private IThumbnailsManager _thumbnails;
|
||||
private ITranscoder _transcoder;
|
||||
|
||||
public async Task Run(IServiceProvider serviceProvider, CancellationToken token, string arguments = null)
|
||||
{
|
||||
string[] args = arguments?.Split('/');
|
||||
|
||||
if (args == null || args.Length < 2)
|
||||
return;
|
||||
|
||||
string slug = args[1];
|
||||
bool thumbs = args.Length < 3 || string.Equals(args[2], "thumbnails", StringComparison.InvariantCultureIgnoreCase);
|
||||
bool subs = args.Length < 3 || string.Equals(args[2], "subs", StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
using IServiceScope serviceScope = serviceProvider.CreateScope();
|
||||
_library = serviceScope.ServiceProvider.GetService<ILibraryManager>();
|
||||
_thumbnails = serviceScope.ServiceProvider.GetService<IThumbnailsManager>();
|
||||
_transcoder = serviceScope.ServiceProvider.GetService<ITranscoder>();
|
||||
int id;
|
||||
|
||||
switch (args[0].ToLowerInvariant())
|
||||
{
|
||||
case "show":
|
||||
case "shows":
|
||||
Show show = await (int.TryParse(slug, out id)
|
||||
? _library!.Get<Show>(id)
|
||||
: _library!.Get<Show>(slug));
|
||||
await ExtractShow(show, thumbs, subs, token);
|
||||
break;
|
||||
case "season":
|
||||
case "seasons":
|
||||
Season season = await (int.TryParse(slug, out id)
|
||||
? _library!.Get<Season>(id)
|
||||
: _library!.Get<Season>(slug));
|
||||
await ExtractSeason(season, thumbs, subs, token);
|
||||
break;
|
||||
case "episode":
|
||||
case "episodes":
|
||||
Episode episode = await (int.TryParse(slug, out id)
|
||||
? _library!.Get<Episode>(id)
|
||||
: _library!.Get<Episode>(slug));
|
||||
await ExtractEpisode(episode, thumbs, subs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExtractShow(Show show, bool thumbs, bool subs, CancellationToken token)
|
||||
{
|
||||
if (thumbs)
|
||||
await _thumbnails!.Validate(show, true);
|
||||
await _library.Load(show, x => x.Seasons);
|
||||
foreach (Season season in show.Seasons)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
await ExtractSeason(season, thumbs, subs, token);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExtractSeason(Season season, bool thumbs, bool subs, CancellationToken token)
|
||||
{
|
||||
if (thumbs)
|
||||
await _thumbnails!.Validate(season, true);
|
||||
await _library.Load(season, x => x.Episodes);
|
||||
foreach (Episode episode in season.Episodes)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
await ExtractEpisode(episode, thumbs, subs);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExtractEpisode(Episode episode, bool thumbs, bool subs)
|
||||
{
|
||||
if (thumbs)
|
||||
await _thumbnails!.Validate(episode, true);
|
||||
if (subs)
|
||||
{
|
||||
await _library.Load(episode, x => x.Tracks);
|
||||
episode.Tracks = (await _transcoder!.ExtractInfos(episode, true))
|
||||
.Where(x => x.Type != StreamType.Attachment)
|
||||
.Concat(episode.Tracks.Where(x => x.IsExternal))
|
||||
.ToList();
|
||||
await _library.Edit(episode, false);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IEnumerable<string>> GetPossibleParameters()
|
||||
{
|
||||
return Task.FromResult<IEnumerable<string>>(null);
|
||||
}
|
||||
|
||||
public int? Progress()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// using System;
|
||||
// using System.Collections.Generic;
|
||||
// using System.Linq;
|
||||
// using System.Threading;
|
||||
// using System.Threading.Tasks;
|
||||
// using Kyoo.Controllers;
|
||||
// using Kyoo.Models;
|
||||
// using Microsoft.Extensions.DependencyInjection;
|
||||
//
|
||||
// namespace Kyoo.Tasks
|
||||
// {
|
||||
// public class ExtractMetadata : ITask
|
||||
// {
|
||||
// public string Slug => "extract";
|
||||
// public string Name => "Metadata Extractor";
|
||||
// public string Description => "Extract subtitles or download thumbnails for a show/episode.";
|
||||
// public string HelpMessage => null;
|
||||
// public bool RunOnStartup => false;
|
||||
// public int Priority => 0;
|
||||
//
|
||||
//
|
||||
// private ILibraryManager _library;
|
||||
// private IThumbnailsManager _thumbnails;
|
||||
// private ITranscoder _transcoder;
|
||||
//
|
||||
// public async Task Run(IServiceProvider serviceProvider, CancellationToken token, string arguments = null)
|
||||
// {
|
||||
// string[] args = arguments?.Split('/');
|
||||
//
|
||||
// if (args == null || args.Length < 2)
|
||||
// return;
|
||||
//
|
||||
// string slug = args[1];
|
||||
// bool thumbs = args.Length < 3 || string.Equals(args[2], "thumbnails", StringComparison.InvariantCultureIgnoreCase);
|
||||
// bool subs = args.Length < 3 || string.Equals(args[2], "subs", StringComparison.InvariantCultureIgnoreCase);
|
||||
//
|
||||
// using IServiceScope serviceScope = serviceProvider.CreateScope();
|
||||
// _library = serviceScope.ServiceProvider.GetService<ILibraryManager>();
|
||||
// _thumbnails = serviceScope.ServiceProvider.GetService<IThumbnailsManager>();
|
||||
// _transcoder = serviceScope.ServiceProvider.GetService<ITranscoder>();
|
||||
// int id;
|
||||
//
|
||||
// switch (args[0].ToLowerInvariant())
|
||||
// {
|
||||
// case "show":
|
||||
// case "shows":
|
||||
// Show show = await (int.TryParse(slug, out id)
|
||||
// ? _library!.Get<Show>(id)
|
||||
// : _library!.Get<Show>(slug));
|
||||
// await ExtractShow(show, thumbs, subs, token);
|
||||
// break;
|
||||
// case "season":
|
||||
// case "seasons":
|
||||
// Season season = await (int.TryParse(slug, out id)
|
||||
// ? _library!.Get<Season>(id)
|
||||
// : _library!.Get<Season>(slug));
|
||||
// await ExtractSeason(season, thumbs, subs, token);
|
||||
// break;
|
||||
// case "episode":
|
||||
// case "episodes":
|
||||
// Episode episode = await (int.TryParse(slug, out id)
|
||||
// ? _library!.Get<Episode>(id)
|
||||
// : _library!.Get<Episode>(slug));
|
||||
// await ExtractEpisode(episode, thumbs, subs);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private async Task ExtractShow(Show show, bool thumbs, bool subs, CancellationToken token)
|
||||
// {
|
||||
// if (thumbs)
|
||||
// await _thumbnails!.Validate(show, true);
|
||||
// await _library.Load(show, x => x.Seasons);
|
||||
// foreach (Season season in show.Seasons)
|
||||
// {
|
||||
// if (token.IsCancellationRequested)
|
||||
// return;
|
||||
// await ExtractSeason(season, thumbs, subs, token);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private async Task ExtractSeason(Season season, bool thumbs, bool subs, CancellationToken token)
|
||||
// {
|
||||
// if (thumbs)
|
||||
// await _thumbnails!.Validate(season, true);
|
||||
// await _library.Load(season, x => x.Episodes);
|
||||
// foreach (Episode episode in season.Episodes)
|
||||
// {
|
||||
// if (token.IsCancellationRequested)
|
||||
// return;
|
||||
// await ExtractEpisode(episode, thumbs, subs);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private async Task ExtractEpisode(Episode episode, bool thumbs, bool subs)
|
||||
// {
|
||||
// if (thumbs)
|
||||
// await _thumbnails!.Validate(episode, true);
|
||||
// if (subs)
|
||||
// {
|
||||
// await _library.Load(episode, x => x.Tracks);
|
||||
// episode.Tracks = (await _transcoder!.ExtractInfos(episode, true))
|
||||
// .Where(x => x.Type != StreamType.Attachment)
|
||||
// .Concat(episode.Tracks.Where(x => x.IsExternal))
|
||||
// .ToList();
|
||||
// await _library.Edit(episode, false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public Task<IEnumerable<string>> GetPossibleParameters()
|
||||
// {
|
||||
// return Task.FromResult<IEnumerable<string>>(null);
|
||||
// }
|
||||
//
|
||||
// public int? Progress()
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
// }
|
@ -1,46 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Controllers;
|
||||
using Kyoo.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo.Tasks
|
||||
{
|
||||
public class MetadataProviderLoader : ITask
|
||||
{
|
||||
public string Slug => "reload-metdata";
|
||||
public string Name => "Reload Metadata Providers";
|
||||
public string Description => "Add every loaded metadata provider to the database.";
|
||||
public string HelpMessage => null;
|
||||
public bool RunOnStartup => true;
|
||||
public int Priority => 1000;
|
||||
|
||||
public async Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null)
|
||||
{
|
||||
using IServiceScope serviceScope = serviceProvider.CreateScope();
|
||||
IProviderRepository providers = serviceScope.ServiceProvider.GetService<IProviderRepository>();
|
||||
IThumbnailsManager thumbnails = serviceScope.ServiceProvider.GetService<IThumbnailsManager>();
|
||||
IPluginManager pluginManager = serviceScope.ServiceProvider.GetService<IPluginManager>();
|
||||
|
||||
foreach (IMetadataProvider provider in pluginManager!.GetPlugins<IMetadataProvider>())
|
||||
{
|
||||
if (string.IsNullOrEmpty(provider.Provider.Slug))
|
||||
throw new ArgumentException($"Empty provider slug (name: {provider.Provider.Name}).");
|
||||
await providers!.CreateIfNotExists(provider.Provider);
|
||||
await thumbnails!.Validate(provider.Provider);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IEnumerable<string>> GetPossibleParameters()
|
||||
{
|
||||
return Task.FromResult<IEnumerable<string>>(null);
|
||||
}
|
||||
|
||||
public int? Progress()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// using System;
|
||||
// using System.Collections.Generic;
|
||||
// using System.Threading;
|
||||
// using System.Threading.Tasks;
|
||||
// using Kyoo.Controllers;
|
||||
// using Kyoo.Models;
|
||||
// using Microsoft.Extensions.DependencyInjection;
|
||||
//
|
||||
// namespace Kyoo.Tasks
|
||||
// {
|
||||
// public class MetadataProviderLoader : ITask
|
||||
// {
|
||||
// public string Slug => "reload-metdata";
|
||||
// public string Name => "Reload Metadata Providers";
|
||||
// public string Description => "Add every loaded metadata provider to the database.";
|
||||
// public string HelpMessage => null;
|
||||
// public bool RunOnStartup => true;
|
||||
// public int Priority => 1000;
|
||||
//
|
||||
// public async Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null)
|
||||
// {
|
||||
// using IServiceScope serviceScope = serviceProvider.CreateScope();
|
||||
// IProviderRepository providers = serviceScope.ServiceProvider.GetService<IProviderRepository>();
|
||||
// IThumbnailsManager thumbnails = serviceScope.ServiceProvider.GetService<IThumbnailsManager>();
|
||||
// IPluginManager pluginManager = serviceScope.ServiceProvider.GetService<IPluginManager>();
|
||||
//
|
||||
// foreach (IMetadataProvider provider in pluginManager!.GetPlugins<IMetadataProvider>())
|
||||
// {
|
||||
// if (string.IsNullOrEmpty(provider.Provider.Slug))
|
||||
// throw new ArgumentException($"Empty provider slug (name: {provider.Provider.Name}).");
|
||||
// await providers!.CreateIfNotExists(provider.Provider);
|
||||
// await thumbnails!.Validate(provider.Provider);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public Task<IEnumerable<string>> GetPossibleParameters()
|
||||
// {
|
||||
// return Task.FromResult<IEnumerable<string>>(null);
|
||||
// }
|
||||
//
|
||||
// public int? Progress()
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
// }
|
@ -1,37 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Controllers;
|
||||
using Kyoo.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo.Tasks
|
||||
{
|
||||
public class PluginLoader : ITask
|
||||
{
|
||||
public string Slug => "reload-plugin";
|
||||
public string Name => "Reload plugins";
|
||||
public string Description => "Reload all plugins from the plugin folder.";
|
||||
public string HelpMessage => null;
|
||||
public bool RunOnStartup => true;
|
||||
public int Priority => Int32.MaxValue;
|
||||
public Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null)
|
||||
{
|
||||
using IServiceScope serviceScope = serviceProvider.CreateScope();
|
||||
IPluginManager pluginManager = serviceScope.ServiceProvider.GetService<IPluginManager>();
|
||||
pluginManager.ReloadPlugins();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<string>> GetPossibleParameters()
|
||||
{
|
||||
return Task.FromResult<IEnumerable<string>>(null);
|
||||
}
|
||||
|
||||
public int? Progress()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// using System;
|
||||
// using System.Collections.Generic;
|
||||
// using System.Threading;
|
||||
// using System.Threading.Tasks;
|
||||
// using Kyoo.Controllers;
|
||||
// using Kyoo.Models;
|
||||
// using Microsoft.Extensions.DependencyInjection;
|
||||
//
|
||||
// namespace Kyoo.Tasks
|
||||
// {
|
||||
// public class PluginLoader : ITask
|
||||
// {
|
||||
// public string Slug => "reload-plugin";
|
||||
// public string Name => "Reload plugins";
|
||||
// public string Description => "Reload all plugins from the plugin folder.";
|
||||
// public string HelpMessage => null;
|
||||
// public bool RunOnStartup => true;
|
||||
// public int Priority => Int32.MaxValue;
|
||||
// public Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null)
|
||||
// {
|
||||
// using IServiceScope serviceScope = serviceProvider.CreateScope();
|
||||
// IPluginManager pluginManager = serviceScope.ServiceProvider.GetService<IPluginManager>();
|
||||
// pluginManager.ReloadPlugins();
|
||||
// return Task.CompletedTask;
|
||||
// }
|
||||
//
|
||||
// public Task<IEnumerable<string>> GetPossibleParameters()
|
||||
// {
|
||||
// return Task.FromResult<IEnumerable<string>>(null);
|
||||
// }
|
||||
//
|
||||
// public int? Progress()
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
// }
|
@ -32,7 +32,7 @@ namespace Kyoo.Api
|
||||
{
|
||||
ActionResult<Library> result = await base.Create(resource);
|
||||
if (result.Value != null)
|
||||
_taskManager.StartTask("scan", result.Value.Slug);
|
||||
_taskManager.StartTask("scan", new Dictionary<string, object> {{"slug", result.Value.Slug}});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -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
49
Kyoo/Views/TaskApi.cs
Normal 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});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,10 +17,12 @@
|
||||
"logLevel": {
|
||||
"default": "Warning",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
"Microsoft.Hosting.Lifetime": "Information",
|
||||
"Kyoo": "Trace"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
"parallelTasks": "1",
|
||||
|
||||
"scheduledTasks": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user