mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Using task metadata as an attribute
This commit is contained in:
parent
fd37cede9d
commit
48e81dfd92
@ -18,7 +18,7 @@ namespace Kyoo.Controllers
|
|||||||
/// <param name="relativePath">
|
/// <param name="relativePath">
|
||||||
/// The path of the episode file relative to the library root. It starts with a <c>/</c>.
|
/// The path of the episode file relative to the library root. It starts with a <c>/</c>.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <exception cref="IdentificationFailed">The identifier could not work for the given path.</exception>
|
/// <exception cref="IdentificationFailedException">The identifier could not work for the given path.</exception>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A tuple of models representing parsed metadata.
|
/// A tuple of models representing parsed metadata.
|
||||||
/// If no metadata could be parsed for a type, null can be returned.
|
/// If no metadata could be parsed for a type, null can be returned.
|
||||||
@ -34,7 +34,7 @@ namespace Kyoo.Controllers
|
|||||||
/// <param name="relativePath">
|
/// <param name="relativePath">
|
||||||
/// The path of the episode file relative to the library root. It starts with a <c>/</c>.
|
/// The path of the episode file relative to the library root. It starts with a <c>/</c>.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <exception cref="IdentificationFailed">The identifier could not work for the given path.</exception>
|
/// <exception cref="IdentificationFailedException">The identifier could not work for the given path.</exception>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// The metadata of the track identified.
|
/// The metadata of the track identified.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
|
@ -114,6 +114,8 @@ namespace Kyoo.Controllers
|
|||||||
/// <returns>The value of this parameter.</returns>
|
/// <returns>The value of this parameter.</returns>
|
||||||
public T As<T>()
|
public T As<T>()
|
||||||
{
|
{
|
||||||
|
if (typeof(T) == typeof(object))
|
||||||
|
return (T)Value;
|
||||||
return (T)Convert.ChangeType(Value, typeof(T));
|
return (T)Convert.ChangeType(Value, typeof(T));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,42 +152,6 @@ namespace Kyoo.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ITask
|
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 run 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>
|
|
||||||
/// <c>true</c> if this task should not be displayed to the user, <c>false</c> otherwise.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsHidden { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of parameters
|
/// The list of parameters
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Kyoo.Common.Models.Attributes;
|
||||||
using Kyoo.Models.Exceptions;
|
using Kyoo.Models.Exceptions;
|
||||||
|
|
||||||
namespace Kyoo.Controllers
|
namespace Kyoo.Controllers
|
||||||
@ -38,7 +39,7 @@ namespace Kyoo.Controllers
|
|||||||
[NotNull] IProgress<float> progress,
|
[NotNull] IProgress<float> progress,
|
||||||
Dictionary<string, object> arguments = null,
|
Dictionary<string, object> arguments = null,
|
||||||
CancellationToken? cancellationToken = null);
|
CancellationToken? cancellationToken = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start a new task (or queue it).
|
/// Start a new task (or queue it).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -61,21 +62,21 @@ namespace Kyoo.Controllers
|
|||||||
/// <exception cref="ItemNotFoundException">
|
/// <exception cref="ItemNotFoundException">
|
||||||
/// The task could not be found.
|
/// The task could not be found.
|
||||||
/// </exception>
|
/// </exception>
|
||||||
void StartTask<T>([NotNull] IProgress<float> progress,
|
void StartTask<T>([NotNull] IProgress<float> progress,
|
||||||
Dictionary<string, object> arguments = null,
|
Dictionary<string, object> arguments = null,
|
||||||
CancellationToken? cancellationToken = null)
|
CancellationToken? cancellationToken = null)
|
||||||
where T : ITask, new();
|
where T : ITask;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all currently running tasks
|
/// Get all currently running tasks
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A list of currently running tasks.</returns>
|
/// <returns>A list of currently running tasks.</returns>
|
||||||
ICollection<ITask> GetRunningTasks();
|
ICollection<(TaskMetadataAttribute, ITask)> GetRunningTasks();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all available tasks
|
/// Get all available tasks
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A list of every tasks that this instance know.</returns>
|
/// <returns>A list of every tasks that this instance know.</returns>
|
||||||
ICollection<ITask> GetAllTasks();
|
ICollection<TaskMetadataAttribute> GetAllTasks();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,8 +8,8 @@ namespace Kyoo.Models.Attributes
|
|||||||
/// An attribute to inform that the service will be injected automatically by a service provider.
|
/// An attribute to inform that the service will be injected automatically by a service provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// It should only be used on <see cref="ITask"/> and will be injected before calling <see cref="ITask.Run"/>.
|
/// It should only be used on <see cref="IPlugin"/> and it will be injected before
|
||||||
/// It can also be used on <see cref="IPlugin"/> and it will be injected before calling <see cref="IPlugin.ConfigureAspNet"/>.
|
/// calling <see cref="IPlugin.ConfigureAspNet"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[AttributeUsage(AttributeTargets.Property)]
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
[MeansImplicitUse(ImplicitUseKindFlags.Assign)]
|
[MeansImplicitUse(ImplicitUseKindFlags.Assign)]
|
||||||
|
77
Kyoo.Common/Models/Attributes/TaskMetadataAttribute.cs
Normal file
77
Kyoo.Common/Models/Attributes/TaskMetadataAttribute.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.Composition;
|
||||||
|
using Kyoo.Controllers;
|
||||||
|
|
||||||
|
namespace Kyoo.Common.Models.Attributes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An attribute to inform how a <see cref="IFileSystem"/> works.
|
||||||
|
/// </summary>
|
||||||
|
[MetadataAttribute]
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class TaskMetadataAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <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>
|
||||||
|
/// Should this task be automatically run at app startup?
|
||||||
|
/// </summary>
|
||||||
|
public bool RunOnStartup { get; set; }
|
||||||
|
|
||||||
|
/// <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; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <c>true</c> if this task should not be displayed to the user, <c>false</c> otherwise.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsHidden { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="TaskMetadataAttribute"/> with the given slug, name and description.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slug">The slug of the task, used to start it.</param>
|
||||||
|
/// <param name="name">The name of the task that will be displayed to the user.</param>
|
||||||
|
/// <param name="description">A quick description of what this task will do.</param>
|
||||||
|
public TaskMetadataAttribute(string slug, string name, string description)
|
||||||
|
{
|
||||||
|
Slug = slug;
|
||||||
|
Name = name;
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="TaskMetadataAttribute"/> using a dictionary of metadata.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="metadata">
|
||||||
|
/// The dictionary of metadata. This method expect the dictionary to contain a field
|
||||||
|
/// per property in this attribute, with the same types as the properties of this attribute.
|
||||||
|
/// </param>
|
||||||
|
public TaskMetadataAttribute(IDictionary<string, object> metadata)
|
||||||
|
{
|
||||||
|
Slug = (string)metadata[nameof(Slug)];
|
||||||
|
Name = (string)metadata[nameof(Name)];
|
||||||
|
Description = (string)metadata[nameof(Description)];
|
||||||
|
RunOnStartup = (bool)metadata[nameof(RunOnStartup)];
|
||||||
|
Priority = (int)metadata[nameof(Priority)];
|
||||||
|
IsHidden = (bool)metadata[nameof(IsHidden)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,20 +8,20 @@ namespace Kyoo.Models.Exceptions
|
|||||||
/// An exception raised when an <see cref="IIdentifier"/> failed.
|
/// An exception raised when an <see cref="IIdentifier"/> failed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class IdentificationFailed : Exception
|
public class IdentificationFailedException : Exception
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="IdentificationFailed"/> with a default message.
|
/// Create a new <see cref="IdentificationFailedException"/> with a default message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IdentificationFailed()
|
public IdentificationFailedException()
|
||||||
: base("An identification failed.")
|
: base("An identification failed.")
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="IdentificationFailed"/> with a custom message.
|
/// Create a new <see cref="IdentificationFailedException"/> with a custom message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message">The message to use.</param>
|
/// <param name="message">The message to use.</param>
|
||||||
public IdentificationFailed(string message)
|
public IdentificationFailedException(string message)
|
||||||
: base(message)
|
: base(message)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ namespace Kyoo.Models.Exceptions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="info">Serialization infos</param>
|
/// <param name="info">Serialization infos</param>
|
||||||
/// <param name="context">The serialization context</param>
|
/// <param name="context">The serialization context</param>
|
||||||
protected IdentificationFailed(SerializationInfo info, StreamingContext context)
|
protected IdentificationFailedException(SerializationInfo info, StreamingContext context)
|
||||||
: base(info, context)
|
: base(info, context)
|
||||||
{ }
|
{ }
|
||||||
}
|
}
|
@ -75,7 +75,7 @@ namespace Kyoo.Postgresql
|
|||||||
DatabaseContext context = provider.GetRequiredService<DatabaseContext>();
|
DatabaseContext context = provider.GetRequiredService<DatabaseContext>();
|
||||||
context.Database.Migrate();
|
context.Database.Migrate();
|
||||||
|
|
||||||
using NpgsqlConnection conn = (NpgsqlConnection)context.Database.GetDbConnection();
|
NpgsqlConnection conn = (NpgsqlConnection)context.Database.GetDbConnection();
|
||||||
conn.Open();
|
conn.Open();
|
||||||
conn.ReloadTypes();
|
conn.ReloadTypes();
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ namespace Kyoo.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IActionResult FileResult(string path, bool range = false, string type = null)
|
public IActionResult FileResult(string path, bool rangeSupport = false, string type = null)
|
||||||
{
|
{
|
||||||
if (path == null)
|
if (path == null)
|
||||||
return new NotFoundResult();
|
return new NotFoundResult();
|
||||||
@ -51,7 +51,7 @@ namespace Kyoo.Controllers
|
|||||||
return new NotFoundResult();
|
return new NotFoundResult();
|
||||||
return new PhysicalFileResult(Path.GetFullPath(path), type ?? _GetContentType(path))
|
return new PhysicalFileResult(Path.GetFullPath(path), type ?? _GetContentType(path))
|
||||||
{
|
{
|
||||||
EnableRangeProcessing = range
|
EnableRangeProcessing = rangeSupport
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,6 +158,7 @@ namespace Kyoo.Controllers
|
|||||||
using IServiceScope scope = _provider.CreateScope();
|
using IServiceScope scope = _provider.CreateScope();
|
||||||
Helper.InjectServices(plugin, x => scope.ServiceProvider.GetRequiredService(x));
|
Helper.InjectServices(plugin, x => scope.ServiceProvider.GetRequiredService(x));
|
||||||
plugin.ConfigureAspNet(app);
|
plugin.ConfigureAspNet(app);
|
||||||
|
Helper.InjectServices(plugin, _ => null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ namespace Kyoo.Controllers
|
|||||||
Match match = regex.Match(relativePath);
|
Match match = regex.Match(relativePath);
|
||||||
|
|
||||||
if (!match.Success)
|
if (!match.Success)
|
||||||
throw new IdentificationFailed($"The episode at {path} does not match the episode's regex.");
|
throw new IdentificationFailedException($"The episode at {path} does not match the episode's regex.");
|
||||||
|
|
||||||
(Collection collection, Show show, Season season, Episode episode) ret = (
|
(Collection collection, Show show, Season season, Episode episode) ret = (
|
||||||
collection: new Collection
|
collection: new Collection
|
||||||
@ -90,7 +90,7 @@ namespace Kyoo.Controllers
|
|||||||
Match match = regex.Match(path);
|
Match match = regex.Match(path);
|
||||||
|
|
||||||
if (!match.Success)
|
if (!match.Success)
|
||||||
throw new IdentificationFailed($"The subtitle at {path} does not match the subtitle's regex.");
|
throw new IdentificationFailedException($"The subtitle at {path} does not match the subtitle's regex.");
|
||||||
|
|
||||||
string episodePath = match.Groups["Episode"].Value;
|
string episodePath = match.Groups["Episode"].Value;
|
||||||
return Task.FromResult(new Track
|
return Task.FromResult(new Track
|
||||||
|
@ -114,9 +114,6 @@ namespace Kyoo.Controllers
|
|||||||
obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added);
|
obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added);
|
||||||
await _database.SaveChangesAsync($"Trying to insert a duplicated episode (slug {obj.Slug} already exists).");
|
await _database.SaveChangesAsync($"Trying to insert a duplicated episode (slug {obj.Slug} already exists).");
|
||||||
return await ValidateTracks(obj);
|
return await ValidateTracks(obj);
|
||||||
// TODO check if this is needed
|
|
||||||
// obj.Slug = await _database.Entry(obj).Property(x => x.Slug).
|
|
||||||
// return obj;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -81,17 +81,28 @@ namespace Kyoo.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override Task<LibraryItem> Create(LibraryItem obj) => throw new InvalidOperationException();
|
public override Task<LibraryItem> Create(LibraryItem obj)
|
||||||
|
=> throw new InvalidOperationException();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override Task<LibraryItem> CreateIfNotExists(LibraryItem obj) => throw new InvalidOperationException();
|
public override Task<LibraryItem> CreateIfNotExists(LibraryItem obj)
|
||||||
|
=> throw new InvalidOperationException();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override Task<LibraryItem> Edit(LibraryItem obj, bool reset) => throw new InvalidOperationException();
|
public override Task<LibraryItem> Edit(LibraryItem obj, bool resetOld)
|
||||||
|
=> throw new InvalidOperationException();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override Task Delete(int id) => throw new InvalidOperationException();
|
public override Task Delete(int id)
|
||||||
|
=> throw new InvalidOperationException();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override Task Delete(string slug) => throw new InvalidOperationException();
|
public override Task Delete(string slug)
|
||||||
|
=> throw new InvalidOperationException();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override Task Delete(LibraryItem obj) => throw new InvalidOperationException();
|
public override Task Delete(LibraryItem obj)
|
||||||
|
=> throw new InvalidOperationException();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a basic queryable for a library with the right mapping from shows & collections.
|
/// Get a basic queryable for a library with the right mapping from shows & collections.
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Autofac.Features.Metadata;
|
||||||
|
using Autofac.Features.OwnedInstances;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using Kyoo.Common.Models.Attributes;
|
||||||
using Kyoo.Models.Exceptions;
|
using Kyoo.Models.Exceptions;
|
||||||
using Kyoo.Models.Options;
|
using Kyoo.Models.Options;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
@ -20,9 +23,52 @@ namespace Kyoo.Controllers
|
|||||||
public class TaskManager : BackgroundService, ITaskManager
|
public class TaskManager : BackgroundService, ITaskManager
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The service provider used to activate
|
/// The class representing task under this <see cref="TaskManager"/> jurisdiction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly IServiceProvider _provider;
|
private class ManagedTask
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The metadata for this task (the slug, and other useful information).
|
||||||
|
/// </summary>
|
||||||
|
public TaskMetadataAttribute Metadata { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The function used to create the task object.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Owned<ITask>> Factory { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The next scheduled date for this task
|
||||||
|
/// </summary>
|
||||||
|
public DateTime ScheduledDate { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A class representing a task inside the <see cref="TaskManager._queuedTasks"/> list.
|
||||||
|
/// </summary>
|
||||||
|
private class QueuedTask
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The task currently queued.
|
||||||
|
/// </summary>
|
||||||
|
public ManagedTask Task { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The progress reporter that this task should use.
|
||||||
|
/// </summary>
|
||||||
|
public IProgress<float> ProgressReporter { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The arguments to give to run the task with.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, object> Arguments { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A token informing the task that it should be cancelled or not.
|
||||||
|
/// </summary>
|
||||||
|
public CancellationToken? CancellationToken { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The configuration instance used to get schedule information
|
/// The configuration instance used to get schedule information
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -35,15 +81,15 @@ namespace Kyoo.Controllers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of tasks and their next scheduled run.
|
/// The list of tasks and their next scheduled run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly List<(ITask task, DateTime scheduledDate)> _tasks;
|
private readonly List<ManagedTask> _tasks;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The queue of tasks that should be run as soon as possible.
|
/// The queue of tasks that should be run as soon as possible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Queue<(ITask, IProgress<float>, Dictionary<string, object>)> _queuedTasks = new();
|
private readonly Queue<QueuedTask> _queuedTasks = new();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The currently running task.
|
/// The currently running task.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private ITask _runningTask;
|
private (TaskMetadataAttribute, ITask)? _runningTask;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The cancellation token used to cancel the running task when the runner should shutdown.
|
/// The cancellation token used to cancel the running task when the runner should shutdown.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -53,22 +99,24 @@ namespace Kyoo.Controllers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="TaskManager"/>.
|
/// Create a new <see cref="TaskManager"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tasks">The list of tasks to manage</param>
|
/// <param name="tasks">The list of tasks to manage with their metadata</param>
|
||||||
/// <param name="provider">The service provider to request services for tasks</param>
|
|
||||||
/// <param name="options">The configuration to load schedule information.</param>
|
/// <param name="options">The configuration to load schedule information.</param>
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
public TaskManager(IEnumerable<ITask> tasks,
|
public TaskManager(IEnumerable<Meta<Func<Owned<ITask>>, TaskMetadataAttribute>> tasks,
|
||||||
IServiceProvider provider,
|
|
||||||
IOptionsMonitor<TaskOptions> options,
|
IOptionsMonitor<TaskOptions> options,
|
||||||
ILogger<TaskManager> logger)
|
ILogger<TaskManager> logger)
|
||||||
{
|
{
|
||||||
_provider = provider;
|
|
||||||
_options = options;
|
_options = options;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_tasks = tasks.Select(x => (x, GetNextTaskDate(x.Slug))).ToList();
|
_tasks = tasks.Select(x => new ManagedTask
|
||||||
|
{
|
||||||
|
Factory = x.Value,
|
||||||
|
Metadata = x.Metadata,
|
||||||
|
ScheduledDate = GetNextTaskDate(x.Metadata.Slug)
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
if (_tasks.Any())
|
if (_tasks.Any())
|
||||||
_logger.LogTrace("Task manager initiated with: {Tasks}", _tasks.Select(x => x.task.Name));
|
_logger.LogTrace("Task manager initiated with: {Tasks}", _tasks.Select(x => x.Metadata.Name));
|
||||||
else
|
else
|
||||||
_logger.LogInformation("Task manager initiated without any tasks");
|
_logger.LogInformation("Task manager initiated without any tasks");
|
||||||
}
|
}
|
||||||
@ -98,25 +146,26 @@ namespace Kyoo.Controllers
|
|||||||
/// <param name="cancellationToken">A token to stop the runner</param>
|
/// <param name="cancellationToken">A token to stop the runner</param>
|
||||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
EnqueueStartupTasks();
|
_EnqueueStartupTasks();
|
||||||
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
if (_queuedTasks.Any())
|
if (_queuedTasks.Any())
|
||||||
{
|
{
|
||||||
(ITask task, IProgress<float> progress, Dictionary<string, object> args) = _queuedTasks.Dequeue();
|
QueuedTask task = _queuedTasks.Dequeue();
|
||||||
_runningTask = task;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await RunTask(task, progress, args);
|
await _RunTask(task.Task, task.ProgressReporter, task.Arguments, task.CancellationToken);
|
||||||
}
|
}
|
||||||
catch (TaskFailedException ex)
|
catch (TaskFailedException ex)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("The task \"{Task}\" failed: {Message}", task.Name, ex.Message);
|
_logger.LogWarning("The task \"{Task}\" failed: {Message}",
|
||||||
|
task.Task.Metadata.Name, ex.Message);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError(e, "An unhandled exception occured while running the task {Task}", task.Name);
|
_logger.LogError(e, "An unhandled exception occured while running the task {Task}",
|
||||||
|
task.Task.Metadata.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -133,44 +182,54 @@ namespace Kyoo.Controllers
|
|||||||
/// <param name="task">The task to run</param>
|
/// <param name="task">The task to run</param>
|
||||||
/// <param name="progress">A progress reporter to know the percentage of completion of the task.</param>
|
/// <param name="progress">A progress reporter to know the percentage of completion of the task.</param>
|
||||||
/// <param name="arguments">The arguments to pass to the function</param>
|
/// <param name="arguments">The arguments to pass to the function</param>
|
||||||
|
/// <param name="cancellationToken">An optional cancellation token that will be passed to the task.</param>
|
||||||
/// <exception cref="ArgumentException">
|
/// <exception cref="ArgumentException">
|
||||||
/// If the number of arguments is invalid, if an argument can't be converted or if the task finds the argument
|
/// If the number of arguments is invalid, if an argument can't be converted or if the task finds the argument
|
||||||
/// invalid.
|
/// invalid.
|
||||||
/// </exception>
|
/// </exception>
|
||||||
private async Task RunTask(ITask task,
|
private async Task _RunTask(ManagedTask task,
|
||||||
[NotNull] IProgress<float> progress,
|
[NotNull] IProgress<float> progress,
|
||||||
Dictionary<string, object> arguments)
|
Dictionary<string, object> arguments,
|
||||||
|
CancellationToken? cancellationToken = null)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Task starting: {Task}", task.Name);
|
using (_logger.BeginScope("Task: {Task}", task.Metadata.Name))
|
||||||
|
|
||||||
ICollection<TaskParameter> all = task.GetParameters();
|
|
||||||
|
|
||||||
ICollection<string> invalids = arguments.Keys
|
|
||||||
.Where(x => all.All(y => x != y.Name))
|
|
||||||
.ToArray();
|
|
||||||
if (invalids.Any())
|
|
||||||
{
|
{
|
||||||
string invalidsStr = string.Join(", ", invalids);
|
await using Owned<ITask> taskObj = task.Factory.Invoke();
|
||||||
throw new ArgumentException($"{invalidsStr} are invalid arguments for the task {task.Name}");
|
ICollection<TaskParameter> all = taskObj.Value.GetParameters();
|
||||||
}
|
|
||||||
|
|
||||||
TaskParameters args = new(all
|
|
||||||
.Select(x =>
|
|
||||||
{
|
|
||||||
object value = arguments
|
|
||||||
.FirstOrDefault(y => string.Equals(y.Key, x.Name, StringComparison.OrdinalIgnoreCase))
|
|
||||||
.Value;
|
|
||||||
if (value == null && x.IsRequired)
|
|
||||||
throw new ArgumentException($"The argument {x.Name} is required to run {task.Name}" +
|
|
||||||
" but it was not specified.");
|
|
||||||
return x.CreateValue(value ?? x.DefaultValue);
|
|
||||||
}));
|
|
||||||
|
|
||||||
using IServiceScope scope = _provider.CreateScope();
|
_runningTask = (task.Metadata, taskObj.Value);
|
||||||
Helper.InjectServices(task, x => scope.ServiceProvider.GetRequiredService(x));
|
ICollection<string> invalids = arguments.Keys
|
||||||
await task.Run(args, progress, _taskToken.Token);
|
.Where(x => all.All(y => x != y.Name))
|
||||||
Helper.InjectServices(task, _ => null);
|
.ToArray();
|
||||||
_logger.LogInformation("Task finished: {Task}", task.Name);
|
if (invalids.Any())
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"{string.Join(", ", invalids)} are " +
|
||||||
|
$"invalid arguments for the task {task.Metadata.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskParameters args = new(all
|
||||||
|
.Select(x =>
|
||||||
|
{
|
||||||
|
object value = arguments
|
||||||
|
.FirstOrDefault(y => string.Equals(y.Key, x.Name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Value;
|
||||||
|
if (value == null && x.IsRequired)
|
||||||
|
throw new ArgumentException($"The argument {x.Name} is required to run " +
|
||||||
|
$"{task.Metadata.Name} but it was not specified.");
|
||||||
|
return x.CreateValue(value ?? x.DefaultValue);
|
||||||
|
}));
|
||||||
|
|
||||||
|
_logger.LogInformation("Task starting: {Task} ({Parameters})",
|
||||||
|
task.Metadata.Name, args.ToDictionary(x => x.Name, x => x.As<object>()));
|
||||||
|
|
||||||
|
CancellationToken token = cancellationToken != null
|
||||||
|
? CancellationTokenSource.CreateLinkedTokenSource(_taskToken.Token, cancellationToken.Value).Token
|
||||||
|
: _taskToken.Token;
|
||||||
|
await taskObj.Value.Run(args, progress, token);
|
||||||
|
|
||||||
|
_logger.LogInformation("Task finished: {Task}", task.Metadata.Name);
|
||||||
|
_runningTask = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -178,8 +237,8 @@ namespace Kyoo.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void QueueScheduledTasks()
|
private void QueueScheduledTasks()
|
||||||
{
|
{
|
||||||
IEnumerable<string> tasksToQueue = _tasks.Where(x => x.scheduledDate <= DateTime.Now)
|
IEnumerable<string> tasksToQueue = _tasks.Where(x => x.ScheduledDate <= DateTime.Now)
|
||||||
.Select(x => x.task.Slug);
|
.Select(x => x.Metadata.Slug);
|
||||||
foreach (string task in tasksToQueue)
|
foreach (string task in tasksToQueue)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Queuing task scheduled for running: {Task}", task);
|
_logger.LogDebug("Queuing task scheduled for running: {Task}", task);
|
||||||
@ -190,13 +249,14 @@ namespace Kyoo.Controllers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queue startup tasks with respect to the priority rules.
|
/// Queue startup tasks with respect to the priority rules.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void EnqueueStartupTasks()
|
private void _EnqueueStartupTasks()
|
||||||
{
|
{
|
||||||
IEnumerable<ITask> startupTasks = _tasks.Select(x => x.task)
|
IEnumerable<string> startupTasks = _tasks
|
||||||
.Where(x => x.RunOnStartup)
|
.Where(x => x.Metadata.RunOnStartup)
|
||||||
.OrderByDescending(x => x.Priority);
|
.OrderByDescending(x => x.Metadata.Priority)
|
||||||
foreach (ITask task in startupTasks)
|
.Select(x => x.Metadata.Slug);
|
||||||
_queuedTasks.Enqueue((task, new Progress<float>(), new Dictionary<string, object>()));
|
foreach (string task in startupTasks)
|
||||||
|
StartTask(task, new Progress<float>(), new Dictionary<string, object>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -207,20 +267,29 @@ namespace Kyoo.Controllers
|
|||||||
{
|
{
|
||||||
arguments ??= new Dictionary<string, object>();
|
arguments ??= new Dictionary<string, object>();
|
||||||
|
|
||||||
int index = _tasks.FindIndex(x => x.task.Slug == taskSlug);
|
int index = _tasks.FindIndex(x => x.Metadata.Slug == taskSlug);
|
||||||
if (index == -1)
|
if (index == -1)
|
||||||
throw new ItemNotFoundException($"No task found with the slug {taskSlug}");
|
throw new ItemNotFoundException($"No task found with the slug {taskSlug}");
|
||||||
_queuedTasks.Enqueue((_tasks[index].task, progress, arguments));
|
_queuedTasks.Enqueue(new QueuedTask
|
||||||
_tasks[index] = (_tasks[index].task, GetNextTaskDate(taskSlug));
|
{
|
||||||
|
Task = _tasks[index],
|
||||||
|
ProgressReporter = progress,
|
||||||
|
Arguments = arguments,
|
||||||
|
CancellationToken = cancellationToken
|
||||||
|
});
|
||||||
|
_tasks[index].ScheduledDate = GetNextTaskDate(taskSlug);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void StartTask<T>(IProgress<float> progress,
|
public void StartTask<T>(IProgress<float> progress,
|
||||||
Dictionary<string, object> arguments = null,
|
Dictionary<string, object> arguments = null,
|
||||||
CancellationToken? cancellationToken = null)
|
CancellationToken? cancellationToken = null)
|
||||||
where T : ITask, new()
|
where T : ITask
|
||||||
{
|
{
|
||||||
StartTask(new T().Slug, progress, arguments, cancellationToken);
|
TaskMetadataAttribute metadata = typeof(T).GetCustomAttribute<TaskMetadataAttribute>();
|
||||||
|
if (metadata == null)
|
||||||
|
throw new ArgumentException($"No metadata found on the given task (type: {typeof(T).Name}).");
|
||||||
|
StartTask(metadata.Slug, progress, arguments, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -236,15 +305,17 @@ namespace Kyoo.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ICollection<ITask> GetRunningTasks()
|
public ICollection<(TaskMetadataAttribute, ITask)> GetRunningTasks()
|
||||||
{
|
{
|
||||||
return new[] {_runningTask};
|
return _runningTask == null
|
||||||
|
? ArraySegment<(TaskMetadataAttribute, ITask)>.Empty
|
||||||
|
: new[] { _runningTask.Value };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ICollection<ITask> GetAllTasks()
|
public ICollection<TaskMetadataAttribute> GetAllTasks()
|
||||||
{
|
{
|
||||||
return _tasks.Select(x => x.task).ToArray();
|
return _tasks.Select(x => x.Metadata).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,8 +12,7 @@ namespace Kyoo.Models.Watch
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of known video extensions
|
/// The list of known video extensions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly ImmutableArray<string> VideoExtensions = new()
|
public static readonly ImmutableArray<string> VideoExtensions = ImmutableArray.Create(
|
||||||
{
|
|
||||||
".webm",
|
".webm",
|
||||||
".mkv",
|
".mkv",
|
||||||
".flv",
|
".flv",
|
||||||
@ -38,7 +37,7 @@ namespace Kyoo.Models.Watch
|
|||||||
".m2v",
|
".m2v",
|
||||||
".3gp",
|
".3gp",
|
||||||
".3g2"
|
".3g2"
|
||||||
};
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if a file represent a video file (only by checking the extension of the file)
|
/// Check if a file represent a video file (only by checking the extension of the file)
|
||||||
|
@ -5,8 +5,8 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Common.Models.Attributes;
|
||||||
using Kyoo.Controllers;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models.Attributes;
|
|
||||||
using Kyoo.Models.Watch;
|
using Kyoo.Models.Watch;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@ -15,46 +15,43 @@ namespace Kyoo.Tasks
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A task to add new video files.
|
/// A task to add new video files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[TaskMetadata("scan", "Scan libraries", "Scan your libraries and load data for new shows.", RunOnStartup = true)]
|
||||||
public class Crawler : ITask
|
public class Crawler : ITask
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
|
||||||
public string Slug => "scan";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Name => "Scan libraries";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Description => "Scan your libraries and load data for new shows.";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string HelpMessage => "Reloading all libraries is a long process and may take up to" +
|
|
||||||
" 24 hours if it is the first scan in a while.";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool RunOnStartup => true;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public int Priority => 0;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsHidden => false;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The library manager used to get libraries and providers to use.
|
/// The library manager used to get libraries and providers to use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public ILibraryManager LibraryManager { private get; set; }
|
private readonly ILibraryManager _libraryManager;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The file manager used walk inside directories and check they existences.
|
/// The file manager used walk inside directories and check they existences.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public IFileSystem FileSystem { private get; set; }
|
private readonly IFileSystem _fileSystem;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A task manager used to create sub tasks for each episode to add to the database.
|
/// A task manager used to create sub tasks for each episode to add to the database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public ITaskManager TaskManager { private get; set; }
|
private readonly ITaskManager _taskManager;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The logger used to inform the current status to the console.
|
/// The logger used to inform the current status to the console.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public ILogger<Crawler> Logger { private get; set; }
|
private readonly ILogger<Crawler> _logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="Crawler"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">The library manager to retrieve existing episodes/library/tracks</param>
|
||||||
|
/// <param name="fileSystem">The file system to glob files</param>
|
||||||
|
/// <param name="taskManager">The task manager used to start <see cref="RegisterEpisode"/>.</param>
|
||||||
|
/// <param name="logger">The logger used print messages.</param>
|
||||||
|
public Crawler(ILibraryManager libraryManager,
|
||||||
|
IFileSystem fileSystem,
|
||||||
|
ITaskManager taskManager,
|
||||||
|
ILogger<Crawler> logger)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_taskManager = taskManager;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -71,20 +68,20 @@ namespace Kyoo.Tasks
|
|||||||
{
|
{
|
||||||
string argument = arguments["slug"].As<string>();
|
string argument = arguments["slug"].As<string>();
|
||||||
ICollection<Library> libraries = argument == null
|
ICollection<Library> libraries = argument == null
|
||||||
? await LibraryManager.GetAll<Library>()
|
? await _libraryManager.GetAll<Library>()
|
||||||
: new [] { await LibraryManager.GetOrDefault<Library>(argument)};
|
: new [] { await _libraryManager.GetOrDefault<Library>(argument)};
|
||||||
|
|
||||||
if (argument != null && libraries.First() == null)
|
if (argument != null && libraries.First() == null)
|
||||||
throw new ArgumentException($"No library found with the name {argument}");
|
throw new ArgumentException($"No library found with the name {argument}");
|
||||||
|
|
||||||
foreach (Library library in libraries)
|
foreach (Library library in libraries)
|
||||||
await LibraryManager.Load(library, x => x.Providers);
|
await _libraryManager.Load(library, x => x.Providers);
|
||||||
|
|
||||||
progress.Report(0);
|
progress.Report(0);
|
||||||
float percent = 0;
|
float percent = 0;
|
||||||
|
|
||||||
ICollection<Episode> episodes = await LibraryManager.GetAll<Episode>();
|
ICollection<Episode> episodes = await _libraryManager.GetAll<Episode>();
|
||||||
ICollection<Track> tracks = await LibraryManager.GetAll<Track>();
|
ICollection<Track> tracks = await _libraryManager.GetAll<Track>();
|
||||||
foreach (Library library in libraries)
|
foreach (Library library in libraries)
|
||||||
{
|
{
|
||||||
IProgress<float> reporter = new Progress<float>(x =>
|
IProgress<float> reporter = new Progress<float>(x =>
|
||||||
@ -108,10 +105,10 @@ namespace Kyoo.Tasks
|
|||||||
IProgress<float> progress,
|
IProgress<float> progress,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Logger.LogInformation("Scanning library {Library} at {Paths}", library.Name, library.Paths);
|
_logger.LogInformation("Scanning library {Library} at {Paths}", library.Name, library.Paths);
|
||||||
foreach (string path in library.Paths)
|
foreach (string path in library.Paths)
|
||||||
{
|
{
|
||||||
ICollection<string> files = await FileSystem.ListFiles(path, SearchOption.AllDirectories);
|
ICollection<string> files = await _fileSystem.ListFiles(path, SearchOption.AllDirectories);
|
||||||
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
@ -138,7 +135,7 @@ namespace Kyoo.Tasks
|
|||||||
|
|
||||||
foreach (string episodePath in paths)
|
foreach (string episodePath in paths)
|
||||||
{
|
{
|
||||||
TaskManager.StartTask<RegisterEpisode>(reporter, new Dictionary<string, object>
|
_taskManager.StartTask<RegisterEpisode>(reporter, new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
["path"] = episodePath,
|
["path"] = episodePath,
|
||||||
["relativePath"] = episodePath[path.Length..],
|
["relativePath"] = episodePath[path.Length..],
|
||||||
@ -162,7 +159,7 @@ namespace Kyoo.Tasks
|
|||||||
|
|
||||||
foreach (string trackPath in subtitles)
|
foreach (string trackPath in subtitles)
|
||||||
{
|
{
|
||||||
TaskManager.StartTask<RegisterSubtitle>(reporter, new Dictionary<string, object>
|
_taskManager.StartTask<RegisterSubtitle>(reporter, new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
["path"] = trackPath,
|
["path"] = trackPath,
|
||||||
["relativePath"] = trackPath[path.Length..]
|
["relativePath"] = trackPath[path.Length..]
|
||||||
|
@ -1,83 +1,43 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Common.Models.Attributes;
|
||||||
using Kyoo.Controllers;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using Kyoo.Models.Attributes;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Kyoo.Tasks
|
namespace Kyoo.Tasks
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A task to remove orphaned episode and series.
|
||||||
|
/// </summary>
|
||||||
|
[TaskMetadata("housekeeping", "Housekeeping", "Remove orphaned episode and series.", RunOnStartup = true)]
|
||||||
public class Housekeeping : ITask
|
public class Housekeeping : ITask
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
|
||||||
public string Slug => "housekeeping";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Name => "Housekeeping";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Description => "Remove orphaned episode and series.";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string HelpMessage => null;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool RunOnStartup => true;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public int Priority => 0;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsHidden => false;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The library manager used to get libraries or remove deleted episodes
|
/// The library manager used to get libraries or remove deleted episodes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public ILibraryManager LibraryManager { private get; set; }
|
private readonly ILibraryManager _libraryManager;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The file manager used walk inside directories and check they existences.
|
/// The file manager used walk inside directories and check they existences.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public IFileSystem FileSystem { private get; set; }
|
private readonly IFileSystem _fileSystem;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The logger used to inform the user that episodes has been removed.
|
/// The logger used to inform the user that episodes has been removed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public ILogger<Housekeeping> Logger { private get; set; }
|
private readonly ILogger<Housekeeping> _logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
/// <inheritdoc />
|
/// Create a new <see cref="Housekeeping"/> task.
|
||||||
public async Task Run(TaskParameters arguments, IProgress<float> progress, CancellationToken cancellationToken)
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">The library manager used to get libraries or remove deleted episodes.</param>
|
||||||
|
/// <param name="fileSystem">The file manager used walk inside directories and check they existences.</param>
|
||||||
|
/// <param name="logger">The logger used to inform the user that episodes has been removed.</param>
|
||||||
|
public Housekeeping(ILibraryManager libraryManager, IFileSystem fileSystem, ILogger<Housekeeping> logger)
|
||||||
{
|
{
|
||||||
int count = 0;
|
_libraryManager = libraryManager;
|
||||||
int delCount = await LibraryManager.GetCount<Show>() + await LibraryManager.GetCount<Episode>();
|
_fileSystem = fileSystem;
|
||||||
progress.Report(0);
|
_logger = logger;
|
||||||
|
|
||||||
foreach (Show show in await LibraryManager.GetAll<Show>())
|
|
||||||
{
|
|
||||||
progress.Report(count / delCount * 100);
|
|
||||||
count++;
|
|
||||||
|
|
||||||
if (await FileSystem.Exists(show.Path))
|
|
||||||
continue;
|
|
||||||
Logger.LogWarning("Show {Name}'s folder has been deleted (was {Path}), removing it from kyoo",
|
|
||||||
show.Title, show.Path);
|
|
||||||
await LibraryManager.Delete(show);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Episode episode in await LibraryManager.GetAll<Episode>())
|
|
||||||
{
|
|
||||||
progress.Report(count / delCount * 100);
|
|
||||||
count++;
|
|
||||||
|
|
||||||
if (await FileSystem.Exists(episode.Path))
|
|
||||||
continue;
|
|
||||||
Logger.LogWarning("Episode {Slug}'s file has been deleted (was {Path}), removing it from kyoo",
|
|
||||||
episode.Slug, episode.Path);
|
|
||||||
await LibraryManager.Delete(episode);
|
|
||||||
}
|
|
||||||
|
|
||||||
progress.Report(100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -85,5 +45,39 @@ namespace Kyoo.Tasks
|
|||||||
{
|
{
|
||||||
return new();
|
return new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task Run(TaskParameters arguments, IProgress<float> progress, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
int delCount = await _libraryManager.GetCount<Show>() + await _libraryManager.GetCount<Episode>();
|
||||||
|
progress.Report(0);
|
||||||
|
|
||||||
|
foreach (Show show in await _libraryManager.GetAll<Show>())
|
||||||
|
{
|
||||||
|
progress.Report(count / delCount * 100);
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (await _fileSystem.Exists(show.Path))
|
||||||
|
continue;
|
||||||
|
_logger.LogWarning("Show {Name}'s folder has been deleted (was {Path}), removing it from kyoo",
|
||||||
|
show.Title, show.Path);
|
||||||
|
await _libraryManager.Delete(show);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Episode episode in await _libraryManager.GetAll<Episode>())
|
||||||
|
{
|
||||||
|
progress.Report(count / delCount * 100);
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (await _fileSystem.Exists(episode.Path))
|
||||||
|
continue;
|
||||||
|
_logger.LogWarning("Episode {Slug}'s file has been deleted (was {Path}), removing it from kyoo",
|
||||||
|
episode.Slug, episode.Path);
|
||||||
|
await _libraryManager.Delete(episode);
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.Report(100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,8 +2,8 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Common.Models.Attributes;
|
||||||
using Kyoo.Controllers;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models.Attributes;
|
|
||||||
using Kyoo.Models.Exceptions;
|
using Kyoo.Models.Exceptions;
|
||||||
|
|
||||||
namespace Kyoo.Tasks
|
namespace Kyoo.Tasks
|
||||||
@ -11,43 +11,45 @@ namespace Kyoo.Tasks
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A task that download metadata providers images.
|
/// A task that download metadata providers images.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[TaskMetadata("reload-metadata", "Reload Metadata Providers", "Add every loaded metadata provider to the database.",
|
||||||
|
RunOnStartup = true, Priority = 1000, IsHidden = true)]
|
||||||
public class MetadataProviderLoader : ITask
|
public class MetadataProviderLoader : ITask
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
|
||||||
public string Slug => "reload-metdata";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Name => "Reload Metadata Providers";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Description => "Add every loaded metadata provider to the database.";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string HelpMessage => null;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool RunOnStartup => true;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public int Priority => 1000;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsHidden => true;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The provider repository used to create in-db providers from metadata providers.
|
/// The provider repository used to create in-db providers from metadata providers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public IProviderRepository Providers { private get; set; }
|
private readonly IProviderRepository _providers;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The thumbnail manager used to download providers logo.
|
/// The thumbnail manager used to download providers logo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public IThumbnailsManager Thumbnails { private get; set; }
|
private readonly IThumbnailsManager _thumbnails;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of metadata providers to register.
|
/// The list of metadata providers to register.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public ICollection<IMetadataProvider> MetadataProviders { private get; set; }
|
private readonly ICollection<IMetadataProvider> _metadataProviders;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="MetadataProviderLoader"/> task.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="providers">
|
||||||
|
/// The provider repository used to create in-db providers from metadata providers.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="thumbnails">
|
||||||
|
/// The thumbnail manager used to download providers logo.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="metadataProviders">
|
||||||
|
/// The list of metadata providers to register.
|
||||||
|
/// </param>
|
||||||
|
public MetadataProviderLoader(IProviderRepository providers,
|
||||||
|
IThumbnailsManager thumbnails,
|
||||||
|
ICollection<IMetadataProvider> metadataProviders)
|
||||||
|
{
|
||||||
|
_providers = providers;
|
||||||
|
_thumbnails = thumbnails;
|
||||||
|
_metadataProviders = metadataProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public TaskParameters GetParameters()
|
public TaskParameters GetParameters()
|
||||||
{
|
{
|
||||||
@ -60,13 +62,13 @@ namespace Kyoo.Tasks
|
|||||||
float percent = 0;
|
float percent = 0;
|
||||||
progress.Report(0);
|
progress.Report(0);
|
||||||
|
|
||||||
foreach (IMetadataProvider provider in MetadataProviders)
|
foreach (IMetadataProvider provider in _metadataProviders)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(provider.Provider.Slug))
|
if (string.IsNullOrEmpty(provider.Provider.Slug))
|
||||||
throw new TaskFailedException($"Empty provider slug (name: {provider.Provider.Name}).");
|
throw new TaskFailedException($"Empty provider slug (name: {provider.Provider.Name}).");
|
||||||
await Providers.CreateIfNotExists(provider.Provider);
|
await _providers.CreateIfNotExists(provider.Provider);
|
||||||
await Thumbnails.DownloadImages(provider.Provider);
|
await _thumbnails.DownloadImages(provider.Provider);
|
||||||
percent += 100f / MetadataProviders.Count;
|
percent += 100f / _metadataProviders.Count;
|
||||||
progress.Report(percent);
|
progress.Report(percent);
|
||||||
}
|
}
|
||||||
progress.Report(100);
|
progress.Report(100);
|
||||||
|
@ -2,57 +2,55 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Common.Models.Attributes;
|
||||||
using Kyoo.Controllers;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models.Attributes;
|
|
||||||
|
|
||||||
namespace Kyoo.Tasks
|
namespace Kyoo.Tasks
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A task run on Kyoo's startup to initialize plugins
|
/// A task run on Kyoo's startup to initialize plugins
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[TaskMetadata("plugin-init", "Plugin Initializer", "A task to initialize plugins.",
|
||||||
|
RunOnStartup = true, Priority = int.MaxValue, IsHidden = true)]
|
||||||
public class PluginInitializer : ITask
|
public class PluginInitializer : ITask
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
|
||||||
public string Slug => "plugin-init";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Name => "PluginInitializer";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Description => "A task to initialize plugins.";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string HelpMessage => null;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool RunOnStartup => true;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public int Priority => int.MaxValue;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsHidden => true;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The plugin manager used to retrieve plugins to initialize them.
|
/// The plugin manager used to retrieve plugins to initialize them.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public IPluginManager PluginManager { private get; set; }
|
private readonly IPluginManager _pluginManager;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The service provider given to each <see cref="IPlugin.Initialize"/> method.
|
/// The service provider given to each <see cref="IPlugin.Initialize"/> method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public IServiceProvider Provider { private get; set; }
|
private readonly IServiceProvider _provider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="PluginInitializer"/> task
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pluginManager">The plugin manager used to retrieve plugins to initialize them.</param>
|
||||||
|
/// <param name="provider">The service provider given to each <see cref="IPlugin.Initialize"/> method.</param>
|
||||||
|
public PluginInitializer(IPluginManager pluginManager, IServiceProvider provider)
|
||||||
|
{
|
||||||
|
_pluginManager = pluginManager;
|
||||||
|
_provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public TaskParameters GetParameters()
|
||||||
|
{
|
||||||
|
return new();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task Run(TaskParameters arguments, IProgress<float> progress, CancellationToken cancellationToken)
|
public Task Run(TaskParameters arguments, IProgress<float> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
ICollection<IPlugin> plugins = PluginManager.GetAllPlugins();
|
ICollection<IPlugin> plugins = _pluginManager.GetAllPlugins();
|
||||||
int count = 0;
|
int count = 0;
|
||||||
progress.Report(0);
|
progress.Report(0);
|
||||||
|
|
||||||
foreach (IPlugin plugin in plugins)
|
foreach (IPlugin plugin in plugins)
|
||||||
{
|
{
|
||||||
plugin.Initialize(Provider);
|
plugin.Initialize(_provider);
|
||||||
|
|
||||||
progress.Report(count / plugins.Count * 100);
|
progress.Report(count / plugins.Count * 100);
|
||||||
count++;
|
count++;
|
||||||
@ -61,11 +59,5 @@ namespace Kyoo.Tasks
|
|||||||
progress.Report(100);
|
progress.Report(100);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public TaskParameters GetParameters()
|
|
||||||
{
|
|
||||||
return new();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,9 +2,9 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Common.Models.Attributes;
|
||||||
using Kyoo.Controllers;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using Kyoo.Models.Attributes;
|
|
||||||
using Kyoo.Models.Exceptions;
|
using Kyoo.Models.Exceptions;
|
||||||
|
|
||||||
namespace Kyoo.Tasks
|
namespace Kyoo.Tasks
|
||||||
@ -12,49 +12,60 @@ namespace Kyoo.Tasks
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A task to register a new episode
|
/// A task to register a new episode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[TaskMetadata("register", "Register episode", "Register a new episode")]
|
||||||
public class RegisterEpisode : ITask
|
public class RegisterEpisode : ITask
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
|
||||||
public string Slug => "register";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Name => "Register episode";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Description => "Register a new episode";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string HelpMessage => null;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool RunOnStartup => false;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public int Priority => 0;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsHidden => false;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An identifier to extract metadata from paths.
|
/// An identifier to extract metadata from paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public IIdentifier Identifier { private get; set; }
|
private readonly IIdentifier _identifier;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The library manager used to register the episode
|
/// The library manager used to register the episode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public ILibraryManager LibraryManager { private get; set; }
|
private readonly ILibraryManager _libraryManager;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A metadata provider to retrieve the metadata of the new episode (and related items if they do not exist).
|
/// A metadata provider to retrieve the metadata of the new episode (and related items if they do not exist).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public AProviderComposite MetadataProvider { private get; set; }
|
private readonly AProviderComposite _metadataProvider;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The thumbnail manager used to download images.
|
/// The thumbnail manager used to download images.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public IThumbnailsManager ThumbnailsManager { private get; set; }
|
private readonly IThumbnailsManager _thumbnailsManager;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The transcoder used to extract subtitles and metadata.
|
/// The transcoder used to extract subtitles and metadata.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public ITranscoder Transcoder { private get; set; }
|
private readonly ITranscoder _transcoder;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="RegisterEpisode"/> task.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier">
|
||||||
|
/// An identifier to extract metadata from paths.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="libraryManager">
|
||||||
|
/// The library manager used to register the episode.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="metadataProvider">
|
||||||
|
/// A metadata provider to retrieve the metadata of the new episode (and related items if they do not exist).
|
||||||
|
/// </param>
|
||||||
|
/// <param name="thumbnailsManager">
|
||||||
|
/// The thumbnail manager used to download images.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="transcoder">
|
||||||
|
/// The transcoder used to extract subtitles and metadata.
|
||||||
|
/// </param>
|
||||||
|
public RegisterEpisode(IIdentifier identifier,
|
||||||
|
ILibraryManager libraryManager,
|
||||||
|
AProviderComposite metadataProvider,
|
||||||
|
IThumbnailsManager thumbnailsManager,
|
||||||
|
ITranscoder transcoder)
|
||||||
|
{
|
||||||
|
_identifier = identifier;
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
_metadataProvider = metadataProvider;
|
||||||
|
_thumbnailsManager = thumbnailsManager;
|
||||||
|
_transcoder = transcoder;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public TaskParameters GetParameters()
|
public TaskParameters GetParameters()
|
||||||
@ -77,11 +88,11 @@ namespace Kyoo.Tasks
|
|||||||
progress.Report(0);
|
progress.Report(0);
|
||||||
|
|
||||||
if (library.Providers == null)
|
if (library.Providers == null)
|
||||||
await LibraryManager.Load(library, x => x.Providers);
|
await _libraryManager.Load(library, x => x.Providers);
|
||||||
MetadataProvider.UseProviders(library.Providers);
|
_metadataProvider.UseProviders(library.Providers);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
(Collection collection, Show show, Season season, Episode episode) = await Identifier.Identify(path,
|
(Collection collection, Show show, Season season, Episode episode) = await _identifier.Identify(path,
|
||||||
relativePath);
|
relativePath);
|
||||||
progress.Report(15);
|
progress.Report(15);
|
||||||
|
|
||||||
@ -94,7 +105,7 @@ namespace Kyoo.Tasks
|
|||||||
if (show.StartAir.HasValue)
|
if (show.StartAir.HasValue)
|
||||||
{
|
{
|
||||||
show.Slug += $"-{show.StartAir.Value.Year}";
|
show.Slug += $"-{show.StartAir.Value.Year}";
|
||||||
show = await LibraryManager.Create(show);
|
show = await _libraryManager.Create(show);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -107,7 +118,7 @@ namespace Kyoo.Tasks
|
|||||||
|
|
||||||
// If they are not already loaded, load external ids to allow metadata providers to use them.
|
// If they are not already loaded, load external ids to allow metadata providers to use them.
|
||||||
if (show.ExternalIDs == null)
|
if (show.ExternalIDs == null)
|
||||||
await LibraryManager.Load(show, x => x.ExternalIDs);
|
await _libraryManager.Load(show, x => x.ExternalIDs);
|
||||||
progress.Report(50);
|
progress.Report(50);
|
||||||
|
|
||||||
if (season != null)
|
if (season != null)
|
||||||
@ -119,20 +130,20 @@ namespace Kyoo.Tasks
|
|||||||
|
|
||||||
episode.Show = show;
|
episode.Show = show;
|
||||||
episode.Season = season;
|
episode.Season = season;
|
||||||
episode = await MetadataProvider.Get(episode);
|
episode = await _metadataProvider.Get(episode);
|
||||||
progress.Report(70);
|
progress.Report(70);
|
||||||
episode.Tracks = (await Transcoder.ExtractInfos(episode, false))
|
episode.Tracks = (await _transcoder.ExtractInfos(episode, false))
|
||||||
.Where(x => x.Type != StreamType.Attachment)
|
.Where(x => x.Type != StreamType.Attachment)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
await ThumbnailsManager.DownloadImages(episode);
|
await _thumbnailsManager.DownloadImages(episode);
|
||||||
progress.Report(90);
|
progress.Report(90);
|
||||||
|
|
||||||
await LibraryManager.Create(episode);
|
await _libraryManager.Create(episode);
|
||||||
progress.Report(95);
|
progress.Report(95);
|
||||||
await LibraryManager.AddShowLink(show, library, collection);
|
await _libraryManager.AddShowLink(show, library, collection);
|
||||||
progress.Report(100);
|
progress.Report(100);
|
||||||
}
|
}
|
||||||
catch (IdentificationFailed ex)
|
catch (IdentificationFailedException ex)
|
||||||
{
|
{
|
||||||
throw new TaskFailedException(ex);
|
throw new TaskFailedException(ex);
|
||||||
}
|
}
|
||||||
@ -156,12 +167,12 @@ namespace Kyoo.Tasks
|
|||||||
if (item == null || string.IsNullOrEmpty(item.Slug))
|
if (item == null || string.IsNullOrEmpty(item.Slug))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
T existing = await LibraryManager.GetOrDefault<T>(item.Slug);
|
T existing = await _libraryManager.GetOrDefault<T>(item.Slug);
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
return existing;
|
return existing;
|
||||||
item = await MetadataProvider.Get(item);
|
item = await _metadataProvider.Get(item);
|
||||||
await ThumbnailsManager.DownloadImages(item);
|
await _thumbnailsManager.DownloadImages(item);
|
||||||
return await LibraryManager.CreateIfNotExists(item);
|
return await _libraryManager.CreateIfNotExists(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Common.Models.Attributes;
|
||||||
using Kyoo.Controllers;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using Kyoo.Models.Attributes;
|
|
||||||
using Kyoo.Models.Exceptions;
|
using Kyoo.Models.Exceptions;
|
||||||
|
|
||||||
namespace Kyoo.Tasks
|
namespace Kyoo.Tasks
|
||||||
@ -11,37 +11,28 @@ namespace Kyoo.Tasks
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A task to register a new episode
|
/// A task to register a new episode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[TaskMetadata("register-sub", "Register subtitle", "Register a new subtitle")]
|
||||||
public class RegisterSubtitle : ITask
|
public class RegisterSubtitle : ITask
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
|
||||||
public string Slug => "register-sub";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Name => "Register subtitle";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Description => "Register a new subtitle";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string HelpMessage => null;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool RunOnStartup => false;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public int Priority => 0;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsHidden => false;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An identifier to extract metadata from paths.
|
/// An identifier to extract metadata from paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public IIdentifier Identifier { private get; set; }
|
private readonly IIdentifier _identifier;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The library manager used to register the episode
|
/// The library manager used to register the episode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injected] public ILibraryManager LibraryManager { private get; set; }
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="RegisterSubtitle"/> task.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier">An identifier to extract metadata from paths.</param>
|
||||||
|
/// <param name="libraryManager">The library manager used to register the episode.</param>
|
||||||
|
public RegisterSubtitle(IIdentifier identifier, ILibraryManager libraryManager)
|
||||||
|
{
|
||||||
|
_identifier = identifier;
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public TaskParameters GetParameters()
|
public TaskParameters GetParameters()
|
||||||
@ -63,7 +54,7 @@ namespace Kyoo.Tasks
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
progress.Report(0);
|
progress.Report(0);
|
||||||
Track track = await Identifier.IdentifyTrack(path, relativePath);
|
Track track = await _identifier.IdentifyTrack(path, relativePath);
|
||||||
progress.Report(25);
|
progress.Report(25);
|
||||||
|
|
||||||
if (track.Episode == null)
|
if (track.Episode == null)
|
||||||
@ -71,10 +62,10 @@ namespace Kyoo.Tasks
|
|||||||
if (track.Episode.ID == 0)
|
if (track.Episode.ID == 0)
|
||||||
{
|
{
|
||||||
if (track.Episode.Slug != null)
|
if (track.Episode.Slug != null)
|
||||||
track.Episode = await LibraryManager.Get<Episode>(track.Episode.Slug);
|
track.Episode = await _libraryManager.Get<Episode>(track.Episode.Slug);
|
||||||
else if (track.Episode.Path != null)
|
else if (track.Episode.Path != null)
|
||||||
{
|
{
|
||||||
track.Episode = await LibraryManager.GetOrDefault<Episode>(x => x.Path.StartsWith(track.Episode.Path));
|
track.Episode = await _libraryManager.GetOrDefault<Episode>(x => x.Path.StartsWith(track.Episode.Path));
|
||||||
if (track.Episode == null)
|
if (track.Episode == null)
|
||||||
throw new TaskFailedException($"No episode found for the track at: {path}.");
|
throw new TaskFailedException($"No episode found for the track at: {path}.");
|
||||||
}
|
}
|
||||||
@ -83,10 +74,10 @@ namespace Kyoo.Tasks
|
|||||||
}
|
}
|
||||||
|
|
||||||
progress.Report(50);
|
progress.Report(50);
|
||||||
await LibraryManager.Create(track);
|
await _libraryManager.Create(track);
|
||||||
progress.Report(100);
|
progress.Report(100);
|
||||||
}
|
}
|
||||||
catch (IdentificationFailed ex)
|
catch (IdentificationFailedException ex)
|
||||||
{
|
{
|
||||||
throw new TaskFailedException(ex);
|
throw new TaskFailedException(ex);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user