diff --git a/Kyoo.Common/Controllers/IPlugin.cs b/Kyoo.Common/Controllers/IPlugin.cs new file mode 100644 index 00000000..a6949083 --- /dev/null +++ b/Kyoo.Common/Controllers/IPlugin.cs @@ -0,0 +1,39 @@ +namespace Kyoo.Controllers +{ + /// + /// A common interface used to discord plugins + /// + public interface IPlugin + { + /// + /// A slug to identify this plugin in queries. + /// + string Slug { get; } + + /// + /// The name of the plugin + /// + string Name { get; } + + /// + /// The description of this plugin. This will be displayed on the "installed plugins" page. + /// + string Description { get; } + + + /// + /// A configure method that will be runned on plugin's startup. + /// + /// + /// 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: + /// + /// public static void Configure(IUnityContainer services) + /// { + /// services.AddTask<MyTask>() + /// } + /// + /// + static void Configure() { } + } +} \ No newline at end of file diff --git a/Kyoo.Common/Controllers/ITask.cs b/Kyoo.Common/Controllers/ITask.cs new file mode 100644 index 00000000..6ac064e9 --- /dev/null +++ b/Kyoo.Common/Controllers/ITask.cs @@ -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 +{ + /// + /// A single task parameter. This struct contains metadata to display and utility functions to get them in the taks. + /// + /// This struct will be used to generate the swagger documentation of the task. + public record TaskParameter + { + /// + /// The name of this parameter. + /// + public string Name { get; init; } + + /// + /// The description of this parameter. + /// + public string Description { get; init; } + + /// + /// The type of this parameter. + /// + public Type Type { get; init; } + + /// + /// Is this parameter required or can it be ignored? + /// + public bool IsRequired { get; init; } + + /// + /// The default value of this object. + /// + public object DefaultValue { get; init; } + + /// + /// The value of the parameter. + /// + private object Value { get; init; } + + /// + /// Create a new task parameter. + /// + /// The name of the parameter + /// The description of the parameter + /// The type of the parameter. + /// A new task parameter. + public static TaskParameter Create(string name, string description) + { + return new() + { + Name = name, + Description = description, + Type = typeof(T) + }; + } + + /// + /// Create a parameter's value to give to a task. + /// + /// The name of the parameter + /// The value of the parameter. It's type will be used as parameter's type. + /// The type of the parameter + /// A TaskParameter that can be used as value. + public static TaskParameter CreateValue(string name, T value) + { + return new() + { + Name = name, + Type = typeof(T), + Value = value + }; + } + + /// + /// Create a parameter's value for the current parameter. + /// + /// The value to use + /// A new parameter's value for this current parameter + public TaskParameter CreateValue(object value) + { + return this with {Value = value}; + } + + /// + /// Get the value of this parameter. If the value is of the wrong type, it will be converted. + /// + /// The type of this parameter + /// The value of this parameter. + public T As() + { + return (T)Convert.ChangeType(Value, typeof(T)); + } + } + + /// + /// A parameters container implementing an indexer to allow simple usage of parameters. + /// + public class TaskParameters : List + { + /// + /// An indexer that return the parameter with the specified name. + /// + /// The name of the task (case sensitive) + public TaskParameter this[string name] => this.FirstOrDefault(x => x.Name == name); + + + /// + /// Create a new, empty, + /// + public TaskParameters() {} + + /// + /// Create a with an initial parameters content + /// + /// The list of parameters + public TaskParameters(IEnumerable parameters) + { + AddRange(parameters); + } + } + + /// + /// A common interface that tasks should implement. + /// + public interface ITask + { + /// + /// The slug of the task, used to start it. + /// + public string Slug { get; } + + /// + /// The name of the task that will be displayed to the user. + /// + public string Name { get; } + + /// + /// A quick description of what this task will do. + /// + public string Description { get; } + + /// + /// An optional message to display to help the user. + /// + public string HelpMessage { get; } + + /// + /// Should this task be automatically runned at app startup? + /// + public bool RunOnStartup { get; } + + /// + /// The priority of this task. Only used if is true. + /// It allow one to specify witch task will be started first as tasked are run on a Priority's descending order. + /// + public int Priority { get; } + + /// + /// Start this task. + /// + /// The list of parameters. + /// A token to request the task's cancelation. + /// If this task is not cancelled quickly, it might be killed by the runner. + /// + /// Your task can have any service as a public field and use the , + /// they will be set to an available service from the service container before calling this method. + /// + public Task Run(TaskParameters arguments, CancellationToken cancellationToken); + + /// + /// The list of parameters + /// + /// All parameters that this task as. Every one of them will be given to the run function with a value. + public TaskParameters GetParameters(); + + /// + /// If this task is running, return the percentage of completion of this task or null if no percentage can be given. + /// + /// The percentage of completion of the task. + public int? Progress(); + } +} \ No newline at end of file diff --git a/Kyoo.Common/Controllers/ITaskManager.cs b/Kyoo.Common/Controllers/ITaskManager.cs index 5c926eeb..7f3b6013 100644 --- a/Kyoo.Common/Controllers/ITaskManager.cs +++ b/Kyoo.Common/Controllers/ITaskManager.cs @@ -1,13 +1,40 @@ +using System; using System.Collections.Generic; using Kyoo.Models; +using Kyoo.Models.Exceptions; namespace Kyoo.Controllers { + /// + /// A service to handle long running tasks. + /// + /// The concurrent number of running tasks is implementation dependent. public interface ITaskManager { - bool StartTask(string taskSlug, string arguments = null); - ITask GetRunningTask(); - void ReloadTask(); - IEnumerable GetAllTasks(); + /// + /// Start a new task (or queue it). + /// + /// The slug of the task to run + /// A list of arguments to pass to the task. An automatic conversion will be made if arguments to not fit. + /// If the number of arguments is invalid or if an argument can't be converted. + /// The task could not be found. + void StartTask(string taskSlug, Dictionary arguments); + + /// + /// Get all currently running tasks + /// + /// A list of currently running tasks. + ICollection GetRunningTasks(); + + /// + /// Get all availables tasks + /// + /// A list of every tasks that this instance know. + ICollection GetAllTasks(); + + /// + /// Reload tasks and run startup tasks. + /// + void ReloadTasks(); } } \ No newline at end of file diff --git a/Kyoo.Common/Kyoo.Common.csproj b/Kyoo.Common/Kyoo.Common.csproj index 21d71761..3f4fdbba 100644 --- a/Kyoo.Common/Kyoo.Common.csproj +++ b/Kyoo.Common/Kyoo.Common.csproj @@ -24,6 +24,13 @@ + + + + + + ..\..\..\..\..\..\usr\share\dotnet\shared\Microsoft.AspNetCore.App\5.0.5\Microsoft.Extensions.DependencyInjection.Abstractions.dll + diff --git a/Kyoo.Common/Models/Attributes/InjectedAttribute.cs b/Kyoo.Common/Models/Attributes/InjectedAttribute.cs new file mode 100644 index 00000000..af76eb88 --- /dev/null +++ b/Kyoo.Common/Models/Attributes/InjectedAttribute.cs @@ -0,0 +1,14 @@ +using System; +using Kyoo.Controllers; + +namespace Kyoo.Models.Attributes +{ + /// + /// An attribute to inform that the service will be injected automatically by a service provider. + /// + /// + /// It should only be used on and will be injected before calling + /// + [AttributeUsage(AttributeTargets.Property)] + public class InjectedAttribute : Attribute { } +} \ No newline at end of file diff --git a/Kyoo.Common/Models/Plugin.cs b/Kyoo.Common/Models/Plugin.cs deleted file mode 100644 index 7947d542..00000000 --- a/Kyoo.Common/Models/Plugin.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; - -namespace Kyoo.Models -{ - public interface IPlugin - { - public string Name { get; } - public ICollection Tasks { get; } - } -} \ No newline at end of file diff --git a/Kyoo.Common/Models/Task.cs b/Kyoo.Common/Models/Task.cs deleted file mode 100644 index 76cfbc52..00000000 --- a/Kyoo.Common/Models/Task.cs +++ /dev/null @@ -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> GetPossibleParameters(); - public int? Progress(); - } -} \ No newline at end of file diff --git a/Kyoo.Common/Module.cs b/Kyoo.Common/Module.cs new file mode 100644 index 00000000..41b5859c --- /dev/null +++ b/Kyoo.Common/Module.cs @@ -0,0 +1,24 @@ +using Kyoo.Controllers; +using Unity; + +namespace Kyoo +{ + /// + /// A static class with helper functions to setup external modules + /// + public static class Module + { + /// + /// Register a new task to the container. + /// + /// The container + /// The type of the task + /// The initial container. + public static IUnityContainer AddTask(this IUnityContainer services) + where T : class, ITask + { + services.RegisterSingleton(); + return services; + } + } +} \ No newline at end of file diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 220b2003..3e4bba15 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -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) diff --git a/Kyoo/Controllers/TaskManager.cs b/Kyoo/Controllers/TaskManager.cs index 519fb43f..e93c050f 100644 --- a/Kyoo/Controllers/TaskManager.cs +++ b/Kyoo/Controllers/TaskManager.cs @@ -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 { + /// + /// A service to handle long running tasks and a background runner. + /// + /// Task will be queued, only one can run simultaneously. public class TaskManager : BackgroundService, ITaskManager { - private readonly IServiceProvider _serviceProvider; - private readonly IPluginManager _pluginManager; + /// + /// The service provider used to activate + /// + private readonly IUnityContainer _container; + /// + /// The configuration instance used to get schedule informations + /// private readonly IConfiguration _configuration; - - private List<(ITask task, DateTime scheduledDate)> _tasks = new List<(ITask, DateTime)>(); - private CancellationTokenSource _taskToken = new CancellationTokenSource(); + /// + /// The logger instance. + /// + private readonly ILogger _logger; + + /// + /// The list of tasks and their next scheduled run. + /// + private List<(ITask task, DateTime scheduledDate)> _tasks; + /// + /// The queue of tasks that should be runned as soon as possible. + /// + private readonly Queue<(ITask, Dictionary)> _queuedTasks = new(); + /// + /// The currently running task. + /// private ITask _runningTask; - private Queue<(ITask, string)> _queuedTasks = new Queue<(ITask, string)>(); + /// + /// The cancellation token used to cancel the running task when the runner should shutdown. + /// + private readonly CancellationTokenSource _taskToken = new(); - public TaskManager(IServiceProvider serviceProvider, IPluginManager pluginManager, IConfiguration configuration) + + /// + /// Create a new . + /// + /// The list of tasks to manage + /// The service provider to request services for tasks + /// The configuration to load schedule information. + /// The logger. + public TaskManager(IEnumerable tasks, + IUnityContainer container, + IConfiguration configuration, + ILogger 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; } + + /// + /// Triggered when the application host is ready to start the service. + /// + /// Start the runner in another thread. + /// Indicates that the start process has been aborted. + public override Task StartAsync(CancellationToken cancellationToken) + { + Task.Run(() => base.StartAsync(cancellationToken), CancellationToken.None); + return Task.CompletedTask; + } + + /// + public override Task StopAsync(CancellationToken cancellationToken) + { + _taskToken.Cancel(); + return base.StopAsync(cancellationToken); + } + + /// + /// The runner that will host tasks and run queued tasks. + /// + /// A token to stop the runner protected override async Task ExecuteAsync(CancellationToken cancellationToken) { - ReloadTask(); - - IEnumerable 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 arguments) = _queuedTasks.Dequeue(); _runningTask = task; try { - await task.Run(_serviceProvider, _taskToken.Token, arguments); + ICollection 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 } } + /// + /// Inject services into the marked properties of the given object. + /// + /// The object to inject + /// The type of the object. + private void InjectServices(T obj) + { + IEnumerable properties = typeof(T).GetProperties() + .Where(x => x.GetCustomAttribute() != null) + .Where(x => x.CanWrite); + + foreach (PropertyInfo property in properties) + { + object value = _container.Resolve(property.PropertyType); + property.SetValue(obj, value); + } + } + + /// + /// Start tasks that are scheduled for start. + /// private void QueueScheduledTasks() { IEnumerable tasksToQueue = _tasks.Where(x => x.scheduledDate <= DateTime.Now) .Select(x => x.task.Slug); foreach (string task in tasksToQueue) - StartTask(task); + { + _logger.LogDebug("Queuing task scheduled for running: {Task}", task); + StartTask(task, new Dictionary()); + } } - public override Task StartAsync(CancellationToken cancellationToken) + /// + /// Queue startup tasks with respect to the priority rules. + /// + private void EnqueueStartupTasks() { - Task.Run(() => base.StartAsync(cancellationToken)); - return Task.CompletedTask; + IEnumerable 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 override Task StopAsync(CancellationToken cancellationToken) - { - _taskToken.Cancel(); - return base.StopAsync(cancellationToken); - } - - public bool StartTask(string taskSlug, string arguments = null) + /// + public void StartTask(string taskSlug, Dictionary 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) + /// + /// Get the delay of a task + /// + /// The slug of the task + /// The delay of the task. + private TimeSpan GetTaskDelay(string taskSlug) { - TimeSpan delay = _configuration.GetSection("scheduledTasks").GetValue(taskSlug); + TimeSpan delay = _configuration.GetValue(taskSlug); if (delay == default) - delay = TimeSpan.FromDays(365); + delay = TimeSpan.MaxValue; return delay; } - public ITask GetRunningTask() + /// + public ICollection GetRunningTasks() { - return _runningTask; + return new[] {_runningTask}; } - public void ReloadTask() + /// + public ICollection GetAllTasks() { - _tasks.Clear(); - _tasks.AddRange(CoreTaskHolder.Tasks.Select(x => (x, DateTime.Now + GetTaskDelay(x.Slug)))); - - IEnumerable 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 GetAllTasks() + /// + public void ReloadTasks() { - return _tasks.Select(x => x.task); + _tasks = _container.ResolveAll().Select(x => (x, DateTime.Now + GetTaskDelay(x.Slug))).ToList(); + EnqueueStartupTasks(); } } } \ No newline at end of file diff --git a/Kyoo/CoreModule.cs b/Kyoo/CoreModule.cs new file mode 100644 index 00000000..d194a267 --- /dev/null +++ b/Kyoo/CoreModule.cs @@ -0,0 +1,26 @@ +using Kyoo.Controllers; +using Unity; + +namespace Kyoo +{ + /// + /// The core module ccontaining default implementations + /// + public class CoreModule : IPlugin + { + /// + public string Slug => "core"; + + /// + public string Name => "Core"; + + /// + public string Description => "The core module containing default implementations."; + + /// + public static void Configure(IUnityContainer container) + { + container.AddTask(); + } + } +} \ No newline at end of file diff --git a/Kyoo/Kyoo.csproj b/Kyoo/Kyoo.csproj index 2d624b9c..92c27dba 100644 --- a/Kyoo/Kyoo.csproj +++ b/Kyoo/Kyoo.csproj @@ -33,6 +33,8 @@ + + @@ -56,7 +58,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - diff --git a/Kyoo/Program.cs b/Kyoo/Program.cs index 1fa2b7c3..ee13bcdb 100644 --- a/Kyoo/Program.cs +++ b/Kyoo/Program.cs @@ -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 /// A new web host instance 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() diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 8e1485e3..5f259d24 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -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()); } - 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().ReloadTasks(); } } } diff --git a/Kyoo/Tasks/CoreTaskHolder.cs b/Kyoo/Tasks/CoreTaskHolder.cs deleted file mode 100644 index 867c83f4..00000000 --- a/Kyoo/Tasks/CoreTaskHolder.cs +++ /dev/null @@ -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() - }; - } -} \ No newline at end of file diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index 47c931c9..d86f54d9 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -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> GetPossibleParameters() + public TaskParameters GetParameters() { - using IServiceScope serviceScope = _serviceProvider.CreateScope(); - ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); - return (await libraryManager!.GetAll()).Select(x => x.Slug); + return new() + { + TaskParameter.Create("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(); - _metadataProvider = serviceProvider.GetService(); - _transcoder = serviceProvider.GetService(); - _config = serviceProvider.GetService(); - _parallelTasks = _config.GetValue("parallelTasks"); + string argument = parameters["slug"].As(); + + _parallelTasks = Config.GetValue("parallelTasks"); if (_parallelTasks <= 0) _parallelTasks = 30; - using IServiceScope serviceScope = _serviceProvider.CreateScope(); + using IServiceScope serviceScope = ServiceProvider.CreateScope(); ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); foreach (Show show in await libraryManager!.GetAll()) @@ -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(); - string patern = _config.GetValue("subtitleRegex"); + string patern = Config.GetValue("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(); - string patern = _config.GetValue("regex"); + string patern = Config.GetValue("regex"); Regex regex = new(patern, RegexOptions.IgnoreCase); Match match = regex.Match(relativePath); @@ -257,7 +255,7 @@ namespace Kyoo.Controllers Collection collection = await libraryManager.Get(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> 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; diff --git a/Kyoo/Tasks/CreateDatabase.cs b/Kyoo/Tasks/CreateDatabase.cs index 2264b116..8d54cd87 100644 --- a/Kyoo/Tasks/CreateDatabase.cs +++ b/Kyoo/Tasks/CreateDatabase.cs @@ -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(); - IdentityDatabase identityDatabase = serviceScope.ServiceProvider.GetService(); - ConfigurationDbContext identityContext = serviceScope.ServiceProvider.GetService(); - - 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> GetPossibleParameters() - { - return Task.FromResult>(null); - } - - public int? Progress() - { - return null; - } - } -} \ No newline at end of file +// 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(); +// IdentityDatabase identityDatabase = serviceScope.ServiceProvider.GetService(); +// ConfigurationDbContext identityContext = serviceScope.ServiceProvider.GetService(); +// +// 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> GetPossibleParameters() +// { +// return Task.FromResult>(null); +// } +// +// public int? Progress() +// { +// return null; +// } +// } +// } \ No newline at end of file diff --git a/Kyoo/Tasks/ExtractMetadata.cs b/Kyoo/Tasks/ExtractMetadata.cs index d5513c97..d3e339cb 100644 --- a/Kyoo/Tasks/ExtractMetadata.cs +++ b/Kyoo/Tasks/ExtractMetadata.cs @@ -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(); - _thumbnails = serviceScope.ServiceProvider.GetService(); - _transcoder = serviceScope.ServiceProvider.GetService(); - int id; - - switch (args[0].ToLowerInvariant()) - { - case "show": - case "shows": - Show show = await (int.TryParse(slug, out id) - ? _library!.Get(id) - : _library!.Get(slug)); - await ExtractShow(show, thumbs, subs, token); - break; - case "season": - case "seasons": - Season season = await (int.TryParse(slug, out id) - ? _library!.Get(id) - : _library!.Get(slug)); - await ExtractSeason(season, thumbs, subs, token); - break; - case "episode": - case "episodes": - Episode episode = await (int.TryParse(slug, out id) - ? _library!.Get(id) - : _library!.Get(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> GetPossibleParameters() - { - return Task.FromResult>(null); - } - - public int? Progress() - { - return null; - } - } -} \ No newline at end of file +// 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(); +// _thumbnails = serviceScope.ServiceProvider.GetService(); +// _transcoder = serviceScope.ServiceProvider.GetService(); +// int id; +// +// switch (args[0].ToLowerInvariant()) +// { +// case "show": +// case "shows": +// Show show = await (int.TryParse(slug, out id) +// ? _library!.Get(id) +// : _library!.Get(slug)); +// await ExtractShow(show, thumbs, subs, token); +// break; +// case "season": +// case "seasons": +// Season season = await (int.TryParse(slug, out id) +// ? _library!.Get(id) +// : _library!.Get(slug)); +// await ExtractSeason(season, thumbs, subs, token); +// break; +// case "episode": +// case "episodes": +// Episode episode = await (int.TryParse(slug, out id) +// ? _library!.Get(id) +// : _library!.Get(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> GetPossibleParameters() +// { +// return Task.FromResult>(null); +// } +// +// public int? Progress() +// { +// return null; +// } +// } +// } \ No newline at end of file diff --git a/Kyoo/Tasks/MetadataProviderLoader.cs b/Kyoo/Tasks/MetadataProviderLoader.cs index 5811775e..899a2657 100644 --- a/Kyoo/Tasks/MetadataProviderLoader.cs +++ b/Kyoo/Tasks/MetadataProviderLoader.cs @@ -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(); - IThumbnailsManager thumbnails = serviceScope.ServiceProvider.GetService(); - IPluginManager pluginManager = serviceScope.ServiceProvider.GetService(); - - foreach (IMetadataProvider provider in pluginManager!.GetPlugins()) - { - 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> GetPossibleParameters() - { - return Task.FromResult>(null); - } - - public int? Progress() - { - return null; - } - } -} \ No newline at end of file +// 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(); +// IThumbnailsManager thumbnails = serviceScope.ServiceProvider.GetService(); +// IPluginManager pluginManager = serviceScope.ServiceProvider.GetService(); +// +// foreach (IMetadataProvider provider in pluginManager!.GetPlugins()) +// { +// 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> GetPossibleParameters() +// { +// return Task.FromResult>(null); +// } +// +// public int? Progress() +// { +// return null; +// } +// } +// } \ No newline at end of file diff --git a/Kyoo/Tasks/PluginLoader.cs b/Kyoo/Tasks/PluginLoader.cs index b3f1c064..839e2f1e 100644 --- a/Kyoo/Tasks/PluginLoader.cs +++ b/Kyoo/Tasks/PluginLoader.cs @@ -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(); - pluginManager.ReloadPlugins(); - return Task.CompletedTask; - } - - public Task> GetPossibleParameters() - { - return Task.FromResult>(null); - } - - public int? Progress() - { - return null; - } - } -} \ No newline at end of file +// 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(); +// pluginManager.ReloadPlugins(); +// return Task.CompletedTask; +// } +// +// public Task> GetPossibleParameters() +// { +// return Task.FromResult>(null); +// } +// +// public int? Progress() +// { +// return null; +// } +// } +// } \ No newline at end of file diff --git a/Kyoo/Views/LibraryApi.cs b/Kyoo/Views/LibraryApi.cs index 93081db6..4ec44ea1 100644 --- a/Kyoo/Views/LibraryApi.cs +++ b/Kyoo/Views/LibraryApi.cs @@ -32,7 +32,7 @@ namespace Kyoo.Api { ActionResult result = await base.Create(resource); if (result.Value != null) - _taskManager.StartTask("scan", result.Value.Slug); + _taskManager.StartTask("scan", new Dictionary {{"slug", result.Value.Slug}}); return result; } diff --git a/Kyoo/Views/TaskAPI.cs b/Kyoo/Views/TaskAPI.cs deleted file mode 100644 index 8e531bf9..00000000 --- a/Kyoo/Views/TaskAPI.cs +++ /dev/null @@ -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(); - } - } -} diff --git a/Kyoo/Views/TaskApi.cs b/Kyoo/Views/TaskApi.cs new file mode 100644 index 00000000..56b37b3d --- /dev/null +++ b/Kyoo/Views/TaskApi.cs @@ -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> GetTasks() + { + return Ok(_taskManager.GetAllTasks()); + } + + [HttpGet("{taskSlug}")] + [HttpPut("{taskSlug}")] + public IActionResult RunTask(string taskSlug, [FromQuery] Dictionary args) + { + try + { + _taskManager.StartTask(taskSlug, args); + return Ok(); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + } +} diff --git a/Kyoo/settings.json b/Kyoo/settings.json index bdd2f362..ef2aeb46 100644 --- a/Kyoo/settings.json +++ b/Kyoo/settings.json @@ -17,10 +17,12 @@ "logLevel": { "default": "Warning", "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" + "Microsoft.Hosting.Lifetime": "Information", + "Kyoo": "Trace" } }, + "parallelTasks": "1", "scheduledTasks": {