Reworking the task manager & using unity containers

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

View File

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

View File

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

View File

@ -1,13 +1,40 @@
using System;
using System.Collections.Generic;
using 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();
}
}

View File

@ -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>

View File

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

View File

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

View File

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

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

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

View File

@ -529,9 +529,9 @@ namespace Kyoo
await action(i);
}
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)

View File

@ -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;
private List<(ITask task, DateTime scheduledDate)> _tasks = new List<(ITask, DateTime)>();
private CancellationTokenSource _taskToken = new CancellationTokenSource();
/// <summary>
/// The logger instance.
/// </summary>
private readonly ILogger<TaskManager> _logger;
/// <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);
{
_logger.LogDebug("Queuing task scheduled for running: {Task}", task);
StartTask(task, new Dictionary<string, object>());
}
}
public override Task StartAsync(CancellationToken cancellationToken)
/// <summary>
/// Queue startup tasks with respect to the priority rules.
/// </summary>
private void EnqueueStartupTasks()
{
Task.Run(() => base.StartAsync(cancellationToken));
return Task.CompletedTask;
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 override Task StopAsync(CancellationToken cancellationToken)
{
_taskToken.Cancel();
return base.StopAsync(cancellationToken);
}
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
View File

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

View File

@ -33,6 +33,8 @@
</PropertyGroup>
<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>

View File

@ -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()

View File

@ -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();
}
}
}

View File

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

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.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;

View File

@ -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;
// }
// }
// }

View File

@ -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;
// }
// }
// }

View File

@ -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;
// }
// }
// }

View File

@ -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;
// }
// }
// }

View File

@ -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;
}

View File

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

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

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

View File

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