CodingStyle: Fixing most style issues

This commit is contained in:
Zoe Roux 2021-09-05 17:13:20 +02:00
parent ad0235307f
commit 2bb40874b3
49 changed files with 425 additions and 334 deletions

View File

@ -14,6 +14,7 @@
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)Kyoo.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)Kyoo.ruleset</CodeAnalysisRuleSet>
<!-- <AnalysisMode>AllEnabledByDefault</AnalysisMode>--> <!-- <AnalysisMode>AllEnabledByDefault</AnalysisMode>-->

View File

@ -221,7 +221,7 @@ namespace Kyoo.Abstractions.Controllers
/// Create a new resource. /// Create a new resource.
/// </summary> /// </summary>
/// <param name="obj">The item to register</param> /// <param name="obj">The item to register</param>
/// <returns>The resource registers and completed by database's information (related items & so on)</returns> /// <returns>The resource registers and completed by database's information (related items and so on)</returns>
[ItemNotNull] [ItemNotNull]
Task<T> Create([NotNull] T obj); Task<T> Create([NotNull] T obj);
@ -239,7 +239,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="edited">The resource to edit, it's ID can't change.</param> /// <param name="edited">The resource to edit, it's ID can't change.</param>
/// <param name="resetOld">Should old properties of the resource be discarded or should null values considered as not changed?</param> /// <param name="resetOld">Should old properties of the resource be discarded or should null values considered as not changed?</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception> /// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The resource edited and completed by database's information (related items & so on)</returns> /// <returns>The resource edited and completed by database's information (related items and so on)</returns>
[ItemNotNull] [ItemNotNull]
Task<T> Edit([NotNull] T edited, bool resetOld); Task<T> Edit([NotNull] T edited, bool resetOld);
@ -422,7 +422,7 @@ namespace Kyoo.Abstractions.Controllers
/// </summary> /// </summary>
/// <param name="id">The ID of the library</param> /// <param name="id">The ID of the library</param>
/// <param name="where">A filter function</param> /// <param name="where">A filter function</param>
/// <param name="sort">Sort information (sort order & sort by)</param> /// <param name="sort">Sort information (sort order and sort by)</param>
/// <param name="limit">How many items to return and where to start</param> /// <param name="limit">How many items to return and where to start</param>
/// <returns>A list of items that match every filters</returns> /// <returns>A list of items that match every filters</returns>
public Task<ICollection<LibraryItem>> GetFromLibrary(int id, public Task<ICollection<LibraryItem>> GetFromLibrary(int id,
@ -449,7 +449,7 @@ namespace Kyoo.Abstractions.Controllers
/// </summary> /// </summary>
/// <param name="slug">The slug of the library</param> /// <param name="slug">The slug of the library</param>
/// <param name="where">A filter function</param> /// <param name="where">A filter function</param>
/// <param name="sort">Sort information (sort order & sort by)</param> /// <param name="sort">Sort information (sort order and sort by)</param>
/// <param name="limit">How many items to return and where to start</param> /// <param name="limit">How many items to return and where to start</param>
/// <returns>A list of items that match every filters</returns> /// <returns>A list of items that match every filters</returns>
public Task<ICollection<LibraryItem>> GetFromLibrary(string slug, public Task<ICollection<LibraryItem>> GetFromLibrary(string slug,
@ -497,7 +497,7 @@ namespace Kyoo.Abstractions.Controllers
/// </summary> /// </summary>
/// <param name="showID">The ID of the show</param> /// <param name="showID">The ID of the show</param>
/// <param name="where">A filter function</param> /// <param name="where">A filter function</param>
/// <param name="sort">Sort information (sort order & sort by)</param> /// <param name="sort">Sort information (sort order and sort by)</param>
/// <param name="limit">How many items to return and where to start</param> /// <param name="limit">How many items to return and where to start</param>
/// <returns>A list of items that match every filters</returns> /// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetFromShow(int showID, Task<ICollection<PeopleRole>> GetFromShow(int showID,
@ -524,7 +524,7 @@ namespace Kyoo.Abstractions.Controllers
/// </summary> /// </summary>
/// <param name="showSlug">The slug of the show</param> /// <param name="showSlug">The slug of the show</param>
/// <param name="where">A filter function</param> /// <param name="where">A filter function</param>
/// <param name="sort">Sort information (sort order & sort by)</param> /// <param name="sort">Sort information (sort order and sort by)</param>
/// <param name="limit">How many items to return and where to start</param> /// <param name="limit">How many items to return and where to start</param>
/// <returns>A list of items that match every filters</returns> /// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetFromShow(string showSlug, Task<ICollection<PeopleRole>> GetFromShow(string showSlug,
@ -551,7 +551,7 @@ namespace Kyoo.Abstractions.Controllers
/// </summary> /// </summary>
/// <param name="id">The id of the person</param> /// <param name="id">The id of the person</param>
/// <param name="where">A filter function</param> /// <param name="where">A filter function</param>
/// <param name="sort">Sort information (sort order & sort by)</param> /// <param name="sort">Sort information (sort order and sort by)</param>
/// <param name="limit">How many items to return and where to start</param> /// <param name="limit">How many items to return and where to start</param>
/// <returns>A list of items that match every filters</returns> /// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetFromPeople(int id, Task<ICollection<PeopleRole>> GetFromPeople(int id,
@ -578,7 +578,7 @@ namespace Kyoo.Abstractions.Controllers
/// </summary> /// </summary>
/// <param name="slug">The slug of the person</param> /// <param name="slug">The slug of the person</param>
/// <param name="where">A filter function</param> /// <param name="where">A filter function</param>
/// <param name="sort">Sort information (sort order & sort by)</param> /// <param name="sort">Sort information (sort order and sort by)</param>
/// <param name="limit">How many items to return and where to start</param> /// <param name="limit">How many items to return and where to start</param>
/// <returns>A list of items that match every filters</returns> /// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetFromPeople(string slug, Task<ICollection<PeopleRole>> GetFromPeople(string slug,
@ -610,7 +610,7 @@ namespace Kyoo.Abstractions.Controllers
/// Get a list of external ids that match all filters /// Get a list of external ids that match all filters
/// </summary> /// </summary>
/// <param name="where">A predicate to add arbitrary filter</param> /// <param name="where">A predicate to add arbitrary filter</param>
/// <param name="sort">Sort information (sort order & sort by)</param> /// <param name="sort">Sort information (sort order and sort by)</param>
/// <param name="limit">Pagination information (where to start and how many to get)</param> /// <param name="limit">Pagination information (where to start and how many to get)</param>
/// <typeparam name="T">The type of metadata to retrieve</typeparam> /// <typeparam name="T">The type of metadata to retrieve</typeparam>
/// <returns>A filtered list of external ids.</returns> /// <returns>A filtered list of external ids.</returns>

View File

@ -0,0 +1,30 @@
using System.Collections.Generic;
namespace Kyoo.Abstractions.Models
{
/// <summary>
/// Metadata of episode currently watching by an user
/// </summary>
public class WatchedEpisode
{
/// <summary>
/// The ID of the user that started watching this episode.
/// </summary>
public int UserID { get; set; }
/// <summary>
/// The ID of the episode started.
/// </summary>
public int EpisodeID { get; set; }
/// <summary>
/// The <see cref="Episode"/> started.
/// </summary>
public Episode Episode { get; set; }
/// <summary>
/// Where the player has stopped watching the episode (between 0 and 100).
/// </summary>
public int WatchedPercentage { get; set; }
}
}

View File

@ -190,13 +190,13 @@ namespace Kyoo.Abstractions.Models
Subtitles = ep.Tracks.Where(x => x.Type == StreamType.Subtitle).ToArray(), Subtitles = ep.Tracks.Where(x => x.Type == StreamType.Subtitle).ToArray(),
PreviousEpisode = previous, PreviousEpisode = previous,
NextEpisode = next, NextEpisode = next,
Chapters = await GetChapters(ep.Path) Chapters = await _GetChapters(ep.Path)
}; };
} }
// TODO move this method in a controller to support abstraction. // TODO move this method in a controller to support abstraction.
// TODO use a IFileManager to retrieve and read files. // TODO use a IFileManager to retrieve and read files.
private static async Task<ICollection<Chapter>> GetChapters(string episodePath) private static async Task<ICollection<Chapter>> _GetChapters(string episodePath)
{ {
string path = PathIO.Combine( string path = PathIO.Combine(
PathIO.GetDirectoryName(episodePath)!, PathIO.GetDirectoryName(episodePath)!,

View File

@ -21,6 +21,7 @@ namespace Kyoo.Utils
/// <param name="first">The first enumerable to merge</param> /// <param name="first">The first enumerable to merge</param>
/// <param name="second">The second enumerable to merge, if items from this list are equals to one from the first, they are not kept</param> /// <param name="second">The second enumerable to merge, if items from this list are equals to one from the first, they are not kept</param>
/// <param name="isEqual">Equality function to compare items. If this is null, duplicated elements are kept</param> /// <param name="isEqual">Equality function to compare items. If this is null, duplicated elements are kept</param>
/// <typeparam name="T">The type of items in the lists to merge.</typeparam>
/// <returns>The two list merged as an array</returns> /// <returns>The two list merged as an array</returns>
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
public static T[] MergeLists<T>([CanBeNull] IEnumerable<T> first, public static T[] MergeLists<T>([CanBeNull] IEnumerable<T> first,
@ -219,7 +220,8 @@ namespace Kyoo.Utils
{ {
Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>)) Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>))
.GenericTypeArguments; .GenericTypeArguments;
object[] parameters = { object[] parameters =
{
property.GetValue(first), property.GetValue(first),
value, value,
false false
@ -290,7 +292,8 @@ namespace Kyoo.Utils
{ {
Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>)) Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>))
.GenericTypeArguments; .GenericTypeArguments;
object[] parameters = { object[] parameters =
{
oldValue, oldValue,
newValue, newValue,
false false

View File

@ -26,7 +26,7 @@ namespace Kyoo.Utils
if (ex == null) if (ex == null)
return false; return false;
return ex.Body is MemberExpression || return ex.Body is MemberExpression ||
ex.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)ex.Body).Operand is MemberExpression; (ex.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)ex.Body).Operand is MemberExpression);
} }
/// <summary> /// <summary>
@ -51,7 +51,7 @@ namespace Kyoo.Utils
/// <param name="member">The member value</param> /// <param name="member">The member value</param>
/// <param name="obj">The owner of this member</param> /// <param name="obj">The owner of this member</param>
/// <returns>The value boxed as an object</returns> /// <returns>The value boxed as an object</returns>
/// <exception cref="ArgumentNullException">if <see cref="member"/> or <see cref="obj"/> is null.</exception> /// <exception cref="ArgumentNullException">if <paramref name="member"/> or <paramref name="obj"/> is null.</exception>
/// <exception cref="ArgumentException">The member is not a field or a property.</exception> /// <exception cref="ArgumentException">The member is not a field or a property.</exception>
public static object GetValue([NotNull] this MemberInfo member, [NotNull] object obj) public static object GetValue([NotNull] this MemberInfo member, [NotNull] object obj)
{ {
@ -90,7 +90,7 @@ namespace Kyoo.Utils
str = stringBuilder.ToString().Normalize(NormalizationForm.FormC); str = stringBuilder.ToString().Normalize(NormalizationForm.FormC);
str = Regex.Replace(str, @"\s", "-", RegexOptions.Compiled); str = Regex.Replace(str, @"\s", "-", RegexOptions.Compiled);
str = Regex.Replace(str, @"[^\w\s\p{Pd}]", "", RegexOptions.Compiled); str = Regex.Replace(str, @"[^\w\s\p{Pd}]", string.Empty, RegexOptions.Compiled);
str = str.Trim('-', '_'); str = str.Trim('-', '_');
str = Regex.Replace(str, @"([-_]){2,}", "$1", RegexOptions.Compiled); str = Regex.Replace(str, @"([-_]){2,}", "$1", RegexOptions.Compiled);
return str; return str;
@ -113,7 +113,7 @@ namespace Kyoo.Utils
/// </summary> /// </summary>
/// <param name="type">The starting type</param> /// <param name="type">The starting type</param>
/// <returns>A list of types</returns> /// <returns>A list of types</returns>
/// <exception cref="ArgumentNullException"><see cref="type"/> can't be null</exception> /// <exception cref="ArgumentNullException"><paramref name="type"/> can't be null</exception>
public static IEnumerable<Type> GetInheritanceTree([NotNull] this Type type) public static IEnumerable<Type> GetInheritanceTree([NotNull] this Type type)
{ {
if (type == null) if (type == null)
@ -123,9 +123,9 @@ namespace Kyoo.Utils
} }
/// <summary> /// <summary>
/// Check if <see cref="obj"/> inherit from a generic type <see cref="genericType"/>. /// Check if <paramref name="obj"/> inherit from a generic type <paramref name="genericType"/>.
/// </summary> /// </summary>
/// <param name="obj">Does this object's type is a <see cref="genericType"/></param> /// <param name="obj">Does this object's type is a <paramref name="genericType"/></param>
/// <param name="genericType">The generic type to check against (Only generic types are supported like typeof(IEnumerable&lt;&gt;).</param> /// <param name="genericType">The generic type to check against (Only generic types are supported like typeof(IEnumerable&lt;&gt;).</param>
/// <returns>True if obj inherit from genericType. False otherwise</returns> /// <returns>True if obj inherit from genericType. False otherwise</returns>
/// <exception cref="ArgumentNullException">obj and genericType can't be null</exception> /// <exception cref="ArgumentNullException">obj and genericType can't be null</exception>
@ -137,7 +137,7 @@ namespace Kyoo.Utils
} }
/// <summary> /// <summary>
/// Check if <see cref="type"/> inherit from a generic type <see cref="genericType"/>. /// Check if <paramref name="type"/> inherit from a generic type <paramref name="genericType"/>.
/// </summary> /// </summary>
/// <param name="type">The type to check</param> /// <param name="type">The type to check</param>
/// <param name="genericType">The generic type to check against (Only generic types are supported like typeof(IEnumerable&lt;&gt;).</param> /// <param name="genericType">The generic type to check against (Only generic types are supported like typeof(IEnumerable&lt;&gt;).</param>
@ -160,14 +160,14 @@ namespace Kyoo.Utils
} }
/// <summary> /// <summary>
/// Get the generic definition of <see cref="genericType"/>. /// Get the generic definition of <paramref name="genericType"/>.
/// For example, calling this function with List&lt;string&gt; and typeof(IEnumerable&lt;&gt;) will return IEnumerable&lt;string&gt; /// For example, calling this function with List&lt;string&gt; and typeof(IEnumerable&lt;&gt;) will return IEnumerable&lt;string&gt;
/// </summary> /// </summary>
/// <param name="type">The type to check</param> /// <param name="type">The type to check</param>
/// <param name="genericType">The generic type to check against (Only generic types are supported like typeof(IEnumerable&lt;&gt;).</param> /// <param name="genericType">The generic type to check against (Only generic types are supported like typeof(IEnumerable&lt;&gt;).</param>
/// <returns>The generic definition of genericType that type inherit or null if type does not implement the generic type.</returns> /// <returns>The generic definition of genericType that type inherit or null if type does not implement the generic type.</returns>
/// <exception cref="ArgumentNullException"><see cref="type"/> and <see cref="genericType"/> can't be null</exception> /// <exception cref="ArgumentNullException"><paramref name="type"/> and <paramref name="genericType"/> can't be null</exception>
/// <exception cref="ArgumentException"><see cref="genericType"/> must be a generic type</exception> /// <exception cref="ArgumentException"><paramref name="genericType"/> must be a generic type</exception>
public static Type GetGenericDefinition([NotNull] Type type, [NotNull] Type genericType) public static Type GetGenericDefinition([NotNull] Type type, [NotNull] Type genericType)
{ {
if (type == null) if (type == null)

View File

@ -103,9 +103,7 @@ namespace Kyoo.Authentication
services.AddControllers(); services.AddControllers();
// TODO handle direct-videos with bearers (probably add a cookie and a app.Use to translate that for videos) // TODO handle direct-videos with bearers (probably add a cookie and a app.Use to translate that for videos)
// TODO Check if tokens should be stored. // TODO Check if tokens should be stored.
List<Client> clients = new(); List<Client> clients = new();
_configuration.GetSection("authentication:clients").Bind(clients); _configuration.GetSection("authentication:clients").Bind(clients);
CertificateOption certificateOptions = new(); CertificateOption certificateOptions = new();

View File

@ -27,11 +27,11 @@ namespace Kyoo.Authentication
/// </summary> /// </summary>
/// <param name="builder">The identity server that will be modified.</param> /// <param name="builder">The identity server that will be modified.</param>
/// <param name="options">The certificate options</param> /// <param name="options">The certificate options</param>
/// <returns></returns> /// <returns>The initial builder to allow chain-calls.</returns>
public static IIdentityServerBuilder AddSigninKeys(this IIdentityServerBuilder builder, public static IIdentityServerBuilder AddSigninKeys(this IIdentityServerBuilder builder,
CertificateOption options) CertificateOption options)
{ {
X509Certificate2 certificate = GetCertificate(options); X509Certificate2 certificate = _GetCertificate(options);
builder.AddSigningCredential(certificate); builder.AddSigningCredential(certificate);
if (certificate.NotAfter.AddDays(-7) <= DateTime.UtcNow) if (certificate.NotAfter.AddDays(-7) <= DateTime.UtcNow)
@ -40,10 +40,10 @@ namespace Kyoo.Authentication
if (File.Exists(options.OldFile)) if (File.Exists(options.OldFile))
File.Delete(options.OldFile); File.Delete(options.OldFile);
File.Move(options.File, options.OldFile); File.Move(options.File, options.OldFile);
builder.AddValidationKey(GenerateCertificate(options.File, options.Password)); builder.AddValidationKey(_GenerateCertificate(options.File, options.Password));
} }
else if (File.Exists(options.OldFile)) else if (File.Exists(options.OldFile))
builder.AddValidationKey(GetExistingCredential(options.OldFile, options.Password)); builder.AddValidationKey(_GetExistingCredential(options.OldFile, options.Password));
return builder; return builder;
} }
@ -52,11 +52,11 @@ namespace Kyoo.Authentication
/// </summary> /// </summary>
/// <param name="options">The certificate options</param> /// <param name="options">The certificate options</param>
/// <returns>A valid certificate</returns> /// <returns>A valid certificate</returns>
private static X509Certificate2 GetCertificate(CertificateOption options) private static X509Certificate2 _GetCertificate(CertificateOption options)
{ {
return File.Exists(options.File) return File.Exists(options.File)
? GetExistingCredential(options.File, options.Password) ? _GetExistingCredential(options.File, options.Password)
: GenerateCertificate(options.File, options.Password); : _GenerateCertificate(options.File, options.Password);
} }
/// <summary> /// <summary>
@ -65,13 +65,12 @@ namespace Kyoo.Authentication
/// <param name="file">The path of the certificate</param> /// <param name="file">The path of the certificate</param>
/// <param name="password">The password of the certificate</param> /// <param name="password">The password of the certificate</param>
/// <returns>The loaded certificate</returns> /// <returns>The loaded certificate</returns>
private static X509Certificate2 GetExistingCredential(string file, string password) private static X509Certificate2 _GetExistingCredential(string file, string password)
{ {
return new X509Certificate2(file, password, X509KeyStorageFlags storeFlags = X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.PersistKeySet |
X509KeyStorageFlags.Exportable X509KeyStorageFlags.Exportable;
); return new X509Certificate2(file, password, storeFlags);
} }
/// <summary> /// <summary>
@ -80,7 +79,7 @@ namespace Kyoo.Authentication
/// <param name="file">The path of the output file</param> /// <param name="file">The path of the output file</param>
/// <param name="password">The password of the new certificate</param> /// <param name="password">The password of the new certificate</param>
/// <returns>The generated certificate</returns> /// <returns>The generated certificate</returns>
private static X509Certificate2 GenerateCertificate(string file, string password) private static X509Certificate2 _GenerateCertificate(string file, string password)
{ {
SecureRandom random = new(); SecureRandom random = new();

View File

@ -24,7 +24,7 @@ namespace Kyoo.Authentication
} }
/// <summary> /// <summary>
/// The list of officially supported clients. /// Get the list of officially supported clients.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// You can add custom clients in the settings.json file. /// You can add custom clients in the settings.json file.
@ -48,7 +48,7 @@ namespace Kyoo.Authentication
RequireConsent = false, RequireConsent = false,
AllowedScopes = { "openid", "profile", "kyoo.read", "kyoo.write", "kyoo.play", "kyoo.admin" }, AllowedScopes = { "openid", "profile", "kyoo.read", "kyoo.write", "kyoo.play", "kyoo.admin" },
RedirectUris = { "/", "/silent.html" }, RedirectUris = { "/", "/silent.html" },
PostLogoutRedirectUris = { "/logout" } PostLogoutRedirectUris = { "/logout" }
} }
}; };

View File

@ -89,7 +89,7 @@ namespace Kyoo.Authentication.Views
/// </summary> /// </summary>
/// <param name="stayLogged">Should the user stay logged</param> /// <param name="stayLogged">Should the user stay logged</param>
/// <returns>Authentication properties based on a stay login</returns> /// <returns>Authentication properties based on a stay login</returns>
private static AuthenticationProperties StayLogged(bool stayLogged) private static AuthenticationProperties _StayLogged(bool stayLogged)
{ {
if (!stayLogged) if (!stayLogged)
return null; return null;
@ -114,7 +114,7 @@ namespace Kyoo.Authentication.Views
if (!PasswordUtils.CheckPassword(login.Password, user.Password)) if (!PasswordUtils.CheckPassword(login.Password, user.Password))
return Unauthorized(); return Unauthorized();
await HttpContext.SignInAsync(user.ToIdentityUser(), StayLogged(login.StayLoggedIn)); await HttpContext.SignInAsync(user.ToIdentityUser(), _StayLogged(login.StayLoggedIn));
return Ok(new { RedirectUrl = login.ReturnURL, IsOk = true }); return Ok(new { RedirectUrl = login.ReturnURL, IsOk = true });
} }
@ -140,7 +140,7 @@ namespace Kyoo.Authentication.Views
}); });
} }
await HttpContext.SignInAsync(user.ToIdentityUser(), StayLogged(otac.StayLoggedIn)); await HttpContext.SignInAsync(user.ToIdentityUser(), _StayLogged(otac.StayLoggedIn));
return Ok(); return Ok();
} }

View File

@ -44,6 +44,25 @@ namespace Kyoo.Core.Controllers
_references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase); _references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase);
} }
/// <summary>
/// Transform the configuration section in nested expando objects.
/// </summary>
/// <param name="config">The section to convert</param>
/// <returns>The converted section</returns>
private static object _ToUntyped(IConfigurationSection config)
{
ExpandoObject obj = new();
foreach (IConfigurationSection section in config.GetChildren())
{
obj.TryAdd(section.Key, _ToUntyped(section));
}
if (!obj.Any())
return config.Value;
return obj;
}
/// <inheritdoc /> /// <inheritdoc />
public void AddTyped<T>(string path) public void AddTyped<T>(string path)
{ {
@ -119,8 +138,10 @@ namespace Kyoo.Core.Controllers
// TODO handle lists and dictionaries. // TODO handle lists and dictionaries.
Type type = _GetType(path); Type type = _GetType(path);
if (typeof(T).IsAssignableFrom(type)) if (typeof(T).IsAssignableFrom(type))
{
throw new InvalidCastException($"The type {typeof(T).Name} is not valid for " + throw new InvalidCastException($"The type {typeof(T).Name} is not valid for " +
$"a resource of type {type.Name}."); $"a resource of type {type.Name}.");
}
return (T)GetValue(path); return (T)GetValue(path);
} }
@ -171,24 +192,5 @@ namespace Kyoo.Core.Controllers
return obj; return obj;
} }
/// <summary>
/// Transform the configuration section in nested expando objects.
/// </summary>
/// <param name="config">The section to convert</param>
/// <returns>The converted section</returns>
private static object _ToUntyped(IConfigurationSection config)
{
ExpandoObject obj = new();
foreach (IConfigurationSection section in config.GetChildren())
{
obj.TryAdd(section.Key, _ToUntyped(section));
}
if (!obj.Any())
return config.Value;
return obj;
}
} }
} }

View File

@ -7,8 +7,8 @@ using System.Threading.Tasks;
using Autofac.Features.Metadata; using Autofac.Features.Metadata;
using JetBrains.Annotations; using JetBrains.Annotations;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Core.Models.Options; using Kyoo.Core.Models.Options;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -75,7 +75,7 @@ namespace Kyoo.Core.Controllers
{ {
usablePath = path; usablePath = path;
Meta<Func<IFileSystem>, FileSystemMetadataAttribute> defaultFs = _fileSystems Meta<Func<IFileSystem>, FileSystemMetadataAttribute> defaultFs = _fileSystems
.SingleOrDefault(x => x.Metadata.Scheme.Contains("")); .SingleOrDefault(x => x.Metadata.Scheme.Contains(string.Empty));
if (defaultFs == null) if (defaultFs == null)
throw new ArgumentException($"No file system registered for the default scheme."); throw new ArgumentException($"No file system registered for the default scheme.");
return defaultFs.Value.Invoke(); return defaultFs.Value.Invoke();

View File

@ -91,48 +91,48 @@ namespace Kyoo.Core.Controllers
{ {
throw new NotSupportedException("Extras can not be stored inside an http filesystem."); throw new NotSupportedException("Extras can not be stored inside an http filesystem.");
} }
}
/// <summary>
/// An <see cref="IActionResult"/> to proxy an http request.
/// </summary>
// TODO remove this suppress message once the class has been implemented.
[SuppressMessage("ReSharper", "NotAccessedField.Local")]
public class HttpForwardResult : IActionResult
{
/// <summary>
/// The path of the request to forward.
/// </summary>
private readonly Uri _path;
/// <summary> /// <summary>
/// Should the proxied result support ranges requests? /// An <see cref="IActionResult"/> to proxy an http request.
/// </summary> /// </summary>
private readonly bool _rangeSupport; // TODO remove this suppress message once the class has been implemented.
[SuppressMessage("ReSharper", "NotAccessedField.Local", Justification = "Not Implemented Yet.")]
/// <summary> public class HttpForwardResult : IActionResult
/// If not null, override the content type of the resulting request.
/// </summary>
private readonly string _type;
/// <summary>
/// Create a new <see cref="HttpForwardResult"/>.
/// </summary>
/// <param name="path">The path of the request to forward.</param>
/// <param name="rangeSupport">Should the proxied result support ranges requests?</param>
/// <param name="type">If not null, override the content type of the resulting request.</param>
public HttpForwardResult(Uri path, bool rangeSupport, string type = null)
{ {
_path = path; /// <summary>
_rangeSupport = rangeSupport; /// The path of the request to forward.
_type = type; /// </summary>
} private readonly Uri _path;
/// <inheritdoc /> /// <summary>
public Task ExecuteResultAsync(ActionContext context) /// Should the proxied result support ranges requests?
{ /// </summary>
// TODO implement that, example: https://github.com/twitchax/AspNetCore.Proxy/blob/14dd0f212d7abb43ca1bf8c890d5efb95db66acb/src/Core/Extensions/Http.cs#L15 private readonly bool _rangeSupport;
throw new NotImplementedException();
/// <summary>
/// If not null, override the content type of the resulting request.
/// </summary>
private readonly string _type;
/// <summary>
/// Create a new <see cref="HttpForwardResult"/>.
/// </summary>
/// <param name="path">The path of the request to forward.</param>
/// <param name="rangeSupport">Should the proxied result support ranges requests?</param>
/// <param name="type">If not null, override the content type of the resulting request.</param>
public HttpForwardResult(Uri path, bool rangeSupport, string type = null)
{
_path = path;
_rangeSupport = rangeSupport;
_type = type;
}
/// <inheritdoc />
public Task ExecuteResultAsync(ActionContext context)
{
// TODO implement that, example: https://github.com/twitchax/AspNetCore.Proxy/blob/14dd0f212d7abb43ca1bf8c890d5efb95db66acb/src/Core/Extensions/Http.cs#L15
throw new NotImplementedException();
}
} }
} }
} }

View File

@ -226,6 +226,8 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc /> /// <inheritdoc />
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1507:CodeMustNotContainMultipleBlankLinesInARow", [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1507:CodeMustNotContainMultipleBlankLinesInARow",
Justification = "Separate the code by semantics and simplify the code read.")] Justification = "Separate the code by semantics and simplify the code read.")]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1107:Code should not contain multiple statements on one line",
Justification = "Assing IDs and Values in the same line.")]
public Task Load(IResource obj, string memberName, bool force = false) public Task Load(IResource obj, string memberName, bool force = false)
{ {
if (obj == null) if (obj == null)

View File

@ -76,7 +76,7 @@ namespace Kyoo.Core.Controllers
/// </summary> /// </summary>
/// <param name="path">The path of the dll</param> /// <param name="path">The path of the dll</param>
/// <returns>The list of dlls in hte assembly</returns> /// <returns>The list of dlls in hte assembly</returns>
private IPlugin[] LoadPlugin(string path) private IPlugin[] _LoadPlugin(string path)
{ {
path = Path.GetFullPath(path); path = Path.GetFullPath(path);
try try
@ -106,7 +106,7 @@ namespace Kyoo.Core.Controllers
_logger.LogTrace("Loading new plugins..."); _logger.LogTrace("Loading new plugins...");
string[] pluginsPaths = Directory.GetFiles(pluginFolder, "*.dll", SearchOption.AllDirectories); string[] pluginsPaths = Directory.GetFiles(pluginFolder, "*.dll", SearchOption.AllDirectories);
_plugins.AddRange(plugins _plugins.AddRange(plugins
.Concat(pluginsPaths.SelectMany(LoadPlugin)) .Concat(pluginsPaths.SelectMany(_LoadPlugin))
.Where(x => x.Enabled) .Where(x => x.Enabled)
.GroupBy(x => x.Name) .GroupBy(x => x.Name)
.Select(x => x.First()) .Select(x => x.First())

View File

@ -19,16 +19,16 @@ namespace Kyoo.Core.Controllers
/// </summary> /// </summary>
private readonly ICollection<IMetadataProvider> _providers; private readonly ICollection<IMetadataProvider> _providers;
/// <summary>
/// The list of selected providers. If no provider has been selected, this is null.
/// </summary>
private ICollection<Provider> _selectedProviders;
/// <summary> /// <summary>
/// The logger used to print errors. /// The logger used to print errors.
/// </summary> /// </summary>
private readonly ILogger<ProviderComposite> _logger; private readonly ILogger<ProviderComposite> _logger;
/// <summary>
/// The list of selected providers. If no provider has been selected, this is null.
/// </summary>
private ICollection<Provider> _selectedProviders;
/// <summary> /// <summary>
/// Create a new <see cref="ProviderComposite"/> with a list of available providers. /// Create a new <see cref="ProviderComposite"/> with a list of available providers.
/// </summary> /// </summary>

View File

@ -116,7 +116,7 @@ namespace Kyoo.Core.Controllers
await base.Create(obj); await base.Create(obj);
_database.Entry(obj).State = EntityState.Added; _database.Entry(obj).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);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -128,7 +128,7 @@ namespace Kyoo.Core.Controllers
{ {
await _tracks.DeleteAll(x => x.EpisodeID == resource.ID); await _tracks.DeleteAll(x => x.EpisodeID == resource.ID);
resource.Tracks = changed.Tracks; resource.Tracks = changed.Tracks;
await ValidateTracks(resource); await _ValidateTracks(resource);
} }
if (changed.ExternalIDs != null || resetOld) if (changed.ExternalIDs != null || resetOld)
@ -143,7 +143,7 @@ namespace Kyoo.Core.Controllers
/// </summary> /// </summary>
/// <param name="resource">The resource to fix.</param> /// <param name="resource">The resource to fix.</param>
/// <returns>The <see cref="resource"/> parameter is returned.</returns> /// <returns>The <see cref="resource"/> parameter is returned.</returns>
private async Task<Episode> ValidateTracks(Episode resource) private async Task<Episode> _ValidateTracks(Episode resource)
{ {
if (resource.Tracks == null) if (resource.Tracks == null)
return resource; return resource;
@ -165,8 +165,10 @@ namespace Kyoo.Core.Controllers
if (resource.ShowID <= 0) if (resource.ShowID <= 0)
{ {
if (resource.Show == null) if (resource.Show == null)
{
throw new ArgumentException($"Can't store an episode not related " + throw new ArgumentException($"Can't store an episode not related " +
$"to any show (showID: {resource.ShowID})."); $"to any show (showID: {resource.ShowID}).");
}
resource.ShowID = resource.Show.ID; resource.ShowID = resource.Show.ID;
} }

View File

@ -111,7 +111,7 @@ namespace Kyoo.Core.Controllers
/// </summary> /// </summary>
/// <param name="selector">Only items that are part of a library that match this predicate will be returned.</param> /// <param name="selector">Only items that are part of a library that match this predicate will be returned.</param>
/// <returns>A queryable containing items that are part of a library matching the selector.</returns> /// <returns>A queryable containing items that are part of a library matching the selector.</returns>
private IQueryable<LibraryItem> LibraryRelatedQuery(Expression<Func<Library, bool>> selector) private IQueryable<LibraryItem> _LibraryRelatedQuery(Expression<Func<Library, bool>> selector)
=> _database.Libraries => _database.Libraries
.Where(selector) .Where(selector)
.SelectMany(x => x.Shows) .SelectMany(x => x.Shows)
@ -128,7 +128,7 @@ namespace Kyoo.Core.Controllers
Sort<LibraryItem> sort = default, Sort<LibraryItem> sort = default,
Pagination limit = default) Pagination limit = default)
{ {
ICollection<LibraryItem> items = await ApplyFilters(LibraryRelatedQuery(x => x.ID == id), ICollection<LibraryItem> items = await ApplyFilters(_LibraryRelatedQuery(x => x.ID == id),
where, where,
sort, sort,
limit); limit);
@ -143,7 +143,7 @@ namespace Kyoo.Core.Controllers
Sort<LibraryItem> sort = default, Sort<LibraryItem> sort = default,
Pagination limit = default) Pagination limit = default)
{ {
ICollection<LibraryItem> items = await ApplyFilters(LibraryRelatedQuery(x => x.Slug == slug), ICollection<LibraryItem> items = await ApplyFilters(_LibraryRelatedQuery(x => x.Slug == slug),
where, where,
sort, sort,
limit); limit);

View File

@ -8,8 +8,8 @@ using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes; using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Utils;
using Kyoo.Core.Api; using Kyoo.Core.Api;
using Kyoo.Utils;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Kyoo.Core.Controllers namespace Kyoo.Core.Controllers
@ -133,12 +133,13 @@ namespace Kyoo.Core.Controllers
/// Apply filters to a query to ease sort, pagination & where queries for any resources types. /// Apply filters to a query to ease sort, pagination & where queries for any resources types.
/// For resources of type <see cref="T"/>, see <see cref="ApplyFilters"/> /// For resources of type <see cref="T"/>, see <see cref="ApplyFilters"/>
/// </summary> /// </summary>
/// <param name="query">The base query to filter.</param>
/// <param name="get">A function to asynchronously get a resource from the database using it's ID.</param> /// <param name="get">A function to asynchronously get a resource from the database using it's ID.</param>
/// <param name="defaultSort">The default sort order of this resource's type.</param> /// <param name="defaultSort">The default sort order of this resource's type.</param>
/// <param name="query">The base query to filter.</param>
/// <param name="where">An expression to filter based on arbitrary conditions</param> /// <param name="where">An expression to filter based on arbitrary conditions</param>
/// <param name="sort">The sort settings (sort order & sort by)</param> /// <param name="sort">The sort settings (sort order & sort by)</param>
/// <param name="limit">Pagination information (where to start and how many to get)</param> /// <param name="limit">Pagination information (where to start and how many to get)</param>
/// <typeparam name="TValue">The type of items to query.</typeparam>
/// <returns>The filtered query</returns> /// <returns>The filtered query</returns>
protected async Task<ICollection<TValue>> ApplyFilters<TValue>(IQueryable<TValue> query, protected async Task<ICollection<TValue>> ApplyFilters<TValue>(IQueryable<TValue> query,
Func<int, Task<TValue>> get, Func<int, Task<TValue>> get,
@ -252,6 +253,7 @@ namespace Kyoo.Core.Controllers
/// <param name="resetOld"> /// <param name="resetOld">
/// A boolean to indicate if all values of resource should be discarded or not. /// A boolean to indicate if all values of resource should be discarded or not.
/// </param> /// </param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected virtual Task EditRelations(T resource, T changed, bool resetOld) protected virtual Task EditRelations(T resource, T changed, bool resetOld)
{ {
return Validate(resource); return Validate(resource);
@ -265,6 +267,7 @@ namespace Kyoo.Core.Controllers
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// You can throw this if the resource is illegal and should not be saved. /// You can throw this if the resource is illegal and should not be saved.
/// </exception> /// </exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected virtual Task Validate(T resource) protected virtual Task Validate(T resource)
{ {
if (typeof(T).GetProperty(nameof(resource.Slug))!.GetCustomAttribute<ComputedAttribute>() != null) if (typeof(T).GetProperty(nameof(resource.Slug))!.GetCustomAttribute<ComputedAttribute>() != null)

View File

@ -100,8 +100,10 @@ namespace Kyoo.Core.Controllers
if (resource.ShowID <= 0) if (resource.ShowID <= 0)
{ {
if (resource.Show == null) if (resource.Show == null)
throw new ArgumentException( {
$"Can't store a season not related to any show (showID: {resource.ShowID})."); throw new ArgumentException($"Can't store a season not related to any show " +
$"(showID: {resource.ShowID}).");
}
resource.ShowID = resource.Show.ID; resource.ShowID = resource.Show.ID;
} }

View File

@ -5,8 +5,8 @@ using System.Linq.Expressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Utils;
using Kyoo.Database; using Kyoo.Database;
using Kyoo.Utils;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Kyoo.Core.Controllers namespace Kyoo.Core.Controllers

View File

@ -46,8 +46,10 @@ namespace Kyoo.Core.Controllers
{ {
resource.EpisodeID = resource.Episode?.ID ?? 0; resource.EpisodeID = resource.Episode?.ID ?? 0;
if (resource.EpisodeID <= 0) if (resource.EpisodeID <= 0)
{
throw new ArgumentException("Can't store a track not related to any episode " + throw new ArgumentException("Can't store a track not related to any episode " +
$"(episodeID: {resource.EpisodeID})."); $"(episodeID: {resource.EpisodeID}).");
}
} }
} }

View File

@ -90,16 +90,16 @@ namespace Kyoo.Core.Controllers
/// </summary> /// </summary>
private readonly Queue<QueuedTask> _queuedTasks = new(); private readonly Queue<QueuedTask> _queuedTasks = new();
/// <summary>
/// The currently running task.
/// </summary>
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>
private readonly CancellationTokenSource _taskToken = new(); private readonly CancellationTokenSource _taskToken = new();
/// <summary>
/// The currently running task.
/// </summary>
private (TaskMetadataAttribute, ITask)? _runningTask;
/// <summary> /// <summary>
/// Create a new <see cref="TaskManager"/>. /// Create a new <see cref="TaskManager"/>.
/// </summary> /// </summary>
@ -116,7 +116,7 @@ namespace Kyoo.Core.Controllers
{ {
Factory = x.Value, Factory = x.Value,
Metadata = x.Metadata, Metadata = x.Metadata,
ScheduledDate = GetNextTaskDate(x.Metadata.Slug) ScheduledDate = _GetNextTaskDate(x.Metadata.Slug)
}).ToList(); }).ToList();
if (_tasks.Any()) if (_tasks.Any())
@ -130,6 +130,7 @@ namespace Kyoo.Core.Controllers
/// </summary> /// </summary>
/// <remarks>Start the runner in another thread.</remarks> /// <remarks>Start the runner in another thread.</remarks>
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param> /// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public override Task StartAsync(CancellationToken cancellationToken) public override Task StartAsync(CancellationToken cancellationToken)
{ {
Task.Run(() => base.StartAsync(cancellationToken), CancellationToken.None); Task.Run(() => base.StartAsync(cancellationToken), CancellationToken.None);
@ -147,6 +148,7 @@ namespace Kyoo.Core.Controllers
/// The runner that will host tasks and run queued tasks. /// The runner that will host tasks and run queued tasks.
/// </summary> /// </summary>
/// <param name="cancellationToken">A token to stop the runner</param> /// <param name="cancellationToken">A token to stop the runner</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected override async Task ExecuteAsync(CancellationToken cancellationToken) protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{ {
_EnqueueStartupTasks(); _EnqueueStartupTasks();
@ -174,7 +176,7 @@ namespace Kyoo.Core.Controllers
else else
{ {
await Task.Delay(1000, cancellationToken); await Task.Delay(1000, cancellationToken);
QueueScheduledTasks(); _QueueScheduledTasks();
} }
} }
} }
@ -217,8 +219,10 @@ namespace Kyoo.Core.Controllers
.FirstOrDefault(y => string.Equals(y.Key, x.Name, StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(y => string.Equals(y.Key, x.Name, StringComparison.OrdinalIgnoreCase))
.Value; .Value;
if (value == null && x.IsRequired) if (value == null && x.IsRequired)
{
throw new ArgumentException($"The argument {x.Name} is required to run " + throw new ArgumentException($"The argument {x.Name} is required to run " +
$"{task.Metadata.Name} but it was not specified."); $"{task.Metadata.Name} but it was not specified.");
}
return x.CreateValue(value ?? x.DefaultValue); return x.CreateValue(value ?? x.DefaultValue);
})); }));
@ -238,7 +242,7 @@ namespace Kyoo.Core.Controllers
/// <summary> /// <summary>
/// Start tasks that are scheduled for start. /// Start tasks that are scheduled for start.
/// </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.Metadata.Slug); .Select(x => x.Metadata.Slug);
@ -280,7 +284,7 @@ namespace Kyoo.Core.Controllers
Arguments = arguments, Arguments = arguments,
CancellationToken = cancellationToken CancellationToken = cancellationToken
}); });
_tasks[index].ScheduledDate = GetNextTaskDate(taskSlug); _tasks[index].ScheduledDate = _GetNextTaskDate(taskSlug);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -300,7 +304,7 @@ namespace Kyoo.Core.Controllers
/// </summary> /// </summary>
/// <param name="taskSlug">The slug of the task</param> /// <param name="taskSlug">The slug of the task</param>
/// <returns>The next date.</returns> /// <returns>The next date.</returns>
private DateTime GetNextTaskDate(string taskSlug) private DateTime _GetNextTaskDate(string taskSlug)
{ {
if (_options.CurrentValue.Scheduled.TryGetValue(taskSlug, out TimeSpan delay)) if (_options.CurrentValue.Scheduled.TryGetValue(taskSlug, out TimeSpan delay))
return DateTime.Now + delay; return DateTime.Now + delay;

View File

@ -134,7 +134,7 @@ namespace Kyoo.Core.Controllers
{ {
transmuxFailed = TranscoderAPI.Transmux(episode.Path, manifest, out playableDuration) != 0; transmuxFailed = TranscoderAPI.Transmux(episode.Path, manifest, out playableDuration) != 0;
}, TaskCreationOptions.LongRunning); }, TaskCreationOptions.LongRunning);
while (playableDuration < 10 || !File.Exists(manifest) && !transmuxFailed) while (playableDuration < 10 || (!File.Exists(manifest) && !transmuxFailed))
await Task.Delay(10); await Task.Delay(10);
return transmuxFailed ? null : manifest; return transmuxFailed ? null : manifest;
} }

View File

@ -54,8 +54,8 @@ namespace Kyoo.Core.Models.Watch
/// </summary> /// </summary>
public static readonly ImmutableDictionary<string, string> SubtitleExtensions = new Dictionary<string, string> public static readonly ImmutableDictionary<string, string> SubtitleExtensions = new Dictionary<string, string>
{ {
{".ass", "ass"}, { ".ass", "ass" },
{".str", "subrip"} { ".str", "subrip" }
}.ToImmutableDictionary(); }.ToImmutableDictionary();
/// <summary> /// <summary>

View File

@ -1,5 +1,5 @@
using Kyoo.Abstractions.Models;
using System; using System;
using Kyoo.Abstractions.Models;
namespace Kyoo.Core.Models.Options namespace Kyoo.Core.Models.Options
{ {

View File

@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes; using Kyoo.Abstractions.Models.Attributes;
@ -8,22 +9,22 @@ namespace Kyoo.Core.Models.Watch
/// The unmanaged stream that the transcoder will return. /// The unmanaged stream that the transcoder will return.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class Stream public struct Stream
{ {
/// <summary> /// <summary>
/// The title of the stream. /// The title of the stream.
/// </summary> /// </summary>
public string Title { get; set; } public string Title;
/// <summary> /// <summary>
/// The language of this stream (as a ISO-639-2 language code) /// The language of this stream (as a ISO-639-2 language code)
/// </summary> /// </summary>
public string Language { get; set; } public string Language;
/// <summary> /// <summary>
/// The codec of this stream. /// The codec of this stream.
/// </summary> /// </summary>
public string Codec { get; set; } public string Codec;
/// <summary> /// <summary>
/// Is this stream the default one of it's type? /// Is this stream the default one of it's type?
@ -38,12 +39,12 @@ namespace Kyoo.Core.Models.Watch
/// <summary> /// <summary>
/// The path of this track. /// The path of this track.
/// </summary> /// </summary>
[SerializeIgnore] public string Path { get; set; } public string Path;
/// <summary> /// <summary>
/// The type of this stream. /// The type of this stream.
/// </summary> /// </summary>
[SerializeIgnore] public StreamType Type { get; set; } public StreamType Type;
/// <summary> /// <summary>
/// Create a track from this stream. /// Create a track from this stream.

View File

@ -61,6 +61,27 @@ namespace Kyoo.Core
); );
} }
/// <summary>
/// Create a new <see cref="PluginsStartup"/> from a webhost.
/// This is meant to be used from <see cref="WebHostBuilderExtensions.UseStartup"/>.
/// </summary>
/// <param name="host">The context of the web host.</param>
/// <param name="logger">
/// The logger factory used to log while the application is setting itself up.
/// </param>
/// <returns>A new <see cref="PluginsStartup"/>.</returns>
public static PluginsStartup FromWebHost(WebHostBuilderContext host,
ILoggerFactory logger)
{
HostServiceProvider hostProvider = new(host.HostingEnvironment, host.Configuration, logger);
PluginManager plugins = new(
hostProvider,
Options.Create(host.Configuration.GetSection(BasicOptions.Path).Get<BasicOptions>()),
logger.CreateLogger<PluginManager>()
);
return new PluginsStartup(plugins, host.Configuration);
}
/// <summary> /// <summary>
/// Configure the services context via the <see cref="PluginManager"/>. /// Configure the services context via the <see cref="PluginManager"/>.
/// </summary> /// </summary>
@ -126,27 +147,6 @@ namespace Kyoo.Core
config.Register(path, type); config.Register(path, type);
} }
/// <summary>
/// Create a new <see cref="PluginsStartup"/> from a webhost.
/// This is meant to be used from <see cref="WebHostBuilderExtensions.UseStartup"/>.
/// </summary>
/// <param name="host">The context of the web host.</param>
/// <param name="logger">
/// The logger factory used to log while the application is setting itself up.
/// </param>
/// <returns>A new <see cref="PluginsStartup"/>.</returns>
public static PluginsStartup FromWebHost(WebHostBuilderContext host,
ILoggerFactory logger)
{
HostServiceProvider hostProvider = new(host.HostingEnvironment, host.Configuration, logger);
PluginManager plugins = new(
hostProvider,
Options.Create(host.Configuration.GetSection(BasicOptions.Path).Get<BasicOptions>()),
logger.CreateLogger<PluginManager>()
);
return new PluginsStartup(plugins, host.Configuration);
}
/// <summary> /// <summary>
/// A simple host service provider used to activate plugins instance. /// A simple host service provider used to activate plugins instance.
/// The same services as a generic host are available and an <see cref="ILoggerFactory"/> has been added. /// The same services as a generic host are available and an <see cref="ILoggerFactory"/> has been added.

View File

@ -89,9 +89,9 @@ namespace Kyoo.Core.Tasks
IProgress<float> reporter = new Progress<float>(x => IProgress<float> reporter = new Progress<float>(x =>
{ {
// ReSharper disable once AccessToModifiedClosure // ReSharper disable once AccessToModifiedClosure
progress.Report(percent + x / libraries.Count); progress.Report(percent + (x / libraries.Count));
}); });
await Scan(library, episodes, tracks, reporter, cancellationToken); await _Scan(library, episodes, tracks, reporter, cancellationToken);
percent += 100f / libraries.Count; percent += 100f / libraries.Count;
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
@ -101,7 +101,7 @@ namespace Kyoo.Core.Tasks
progress.Report(100); progress.Report(100);
} }
private async Task Scan(Library library, private async Task _Scan(Library library,
IEnumerable<Episode> episodes, IEnumerable<Episode> episodes,
IEnumerable<Track> tracks, IEnumerable<Track> tracks,
IProgress<float> progress, IProgress<float> progress,
@ -131,7 +131,7 @@ namespace Kyoo.Core.Tasks
IProgress<float> reporter = new Progress<float>(x => IProgress<float> reporter = new Progress<float>(x =>
{ {
// ReSharper disable once AccessToModifiedClosure // ReSharper disable once AccessToModifiedClosure
progress.Report((percent + x / paths.Length - 10) / library.Paths.Length); progress.Report((percent + (x / paths.Length) - 10) / library.Paths.Length);
}); });
foreach (string episodePath in paths) foreach (string episodePath in paths)
@ -153,7 +153,7 @@ namespace Kyoo.Core.Tasks
reporter = new Progress<float>(x => reporter = new Progress<float>(x =>
{ {
// ReSharper disable once AccessToModifiedClosure // ReSharper disable once AccessToModifiedClosure
progress.Report((90 + (percent + x / subtitles.Length)) / library.Paths.Length); progress.Report((90 + (percent + (x / subtitles.Length))) / library.Paths.Length);
}); });
foreach (string trackPath in subtitles) foreach (string trackPath in subtitles)

View File

@ -1,13 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Core.Models.Options; using Kyoo.Core.Models.Options;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Kyoo.Core.Api namespace Kyoo.Core.Api

View File

@ -1,5 +1,4 @@
using System; using System;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -8,6 +7,7 @@ using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Core.Models.Options; using Kyoo.Core.Models.Options;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Kyoo.Core.Api namespace Kyoo.Core.Api

View File

@ -71,11 +71,11 @@ namespace Kyoo.Core.Api
Expression condition = operand switch Expression condition = operand switch
{ {
"eq" when isList => ContainsResourceExpression(propertyExpr, value), "eq" when isList => _ContainsResourceExpression(propertyExpr, value),
"ctn" => ContainsResourceExpression(propertyExpr, value), "ctn" => _ContainsResourceExpression(propertyExpr, value),
"eq" when valueExpr == null => ResourceEqual(propertyExpr, value), "eq" when valueExpr == null => _ResourceEqual(propertyExpr, value),
"not" when valueExpr == null => ResourceEqual(propertyExpr, value, true), "not" when valueExpr == null => _ResourceEqual(propertyExpr, value, true),
"eq" => Expression.Equal(propertyExpr, valueExpr), "eq" => Expression.Equal(propertyExpr, valueExpr),
"not" => Expression.NotEqual(propertyExpr, valueExpr!), "not" => Expression.NotEqual(propertyExpr, valueExpr!),
@ -95,7 +95,7 @@ namespace Kyoo.Core.Api
return Expression.Lambda<Func<T, bool>>(expression!, param); return Expression.Lambda<Func<T, bool>>(expression!, param);
} }
private static Expression ResourceEqual(Expression parameter, string value, bool notEqual = false) private static Expression _ResourceEqual(Expression parameter, string value, bool notEqual = false)
{ {
MemberExpression field; MemberExpression field;
ConstantExpression valueConst; ConstantExpression valueConst;
@ -115,14 +115,14 @@ namespace Kyoo.Core.Api
: Expression.Equal(field, valueConst); : Expression.Equal(field, valueConst);
} }
private static Expression ContainsResourceExpression(MemberExpression xProperty, string value) private static Expression _ContainsResourceExpression(MemberExpression xProperty, string value)
{ {
// x => x.PROPERTY.Any(y => y.Slug == value) // x => x.PROPERTY.Any(y => y.Slug == value)
Expression ret = null; Expression ret = null;
ParameterExpression y = Expression.Parameter(xProperty.Type.GenericTypeArguments.First(), "y"); ParameterExpression y = Expression.Parameter(xProperty.Type.GenericTypeArguments.First(), "y");
foreach (string val in value.Split(',')) foreach (string val in value.Split(','))
{ {
LambdaExpression lambda = Expression.Lambda(ResourceEqual(y, val), y); LambdaExpression lambda = Expression.Lambda(_ResourceEqual(y, val), y);
Expression iteration = Expression.Call(typeof(Enumerable), "Any", xProperty.Type.GenericTypeArguments, Expression iteration = Expression.Call(typeof(Enumerable), "Any", xProperty.Type.GenericTypeArguments,
xProperty, lambda); xProperty, lambda);

View File

@ -12,7 +12,8 @@ namespace Kyoo.Core.Api
{ {
[ApiController] [ApiController]
[ResourceView] [ResourceView]
public class CrudApi<T> : ControllerBase where T : class, IResource public class CrudApi<T> : ControllerBase
where T : class, IResource
{ {
private readonly IRepository<T> _repository; private readonly IRepository<T> _repository;
protected readonly Uri BaseURL; protected readonly Uri BaseURL;

View File

@ -77,11 +77,11 @@ namespace Kyoo.Core.Api
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{ {
if (context.Result is ObjectResult result) if (context.Result is ObjectResult result)
await LoadResultRelations(context, result); await _LoadResultRelations(context, result);
await base.OnResultExecutionAsync(context, next); await base.OnResultExecutionAsync(context, next);
} }
private static async Task LoadResultRelations(ActionContext context, ObjectResult result) private static async Task _LoadResultRelations(ActionContext context, ObjectResult result)
{ {
if (result.DeclaredType == null) if (result.DeclaredType == null)
return; return;

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -33,6 +33,7 @@ namespace Kyoo.Core.Api
if (relation.RelationID == null) if (relation.RelationID == null)
property.ShouldSerialize = x => _depth == 0 && member.GetValue(x) != null; property.ShouldSerialize = x => _depth == 0 && member.GetValue(x) != null;
else else
{
property.ShouldSerialize = x => property.ShouldSerialize = x =>
{ {
if (_depth != 0) if (_depth != 0)
@ -41,6 +42,7 @@ namespace Kyoo.Core.Api
return true; return true;
return x.GetType().GetProperty(relation.RelationID)?.GetValue(x) != null; return x.GetType().GetProperty(relation.RelationID)?.GetValue(x) != null;
}; };
}
} }
if (member.GetCustomAttribute<SerializeIgnoreAttribute>() != null) if (member.GetCustomAttribute<SerializeIgnoreAttribute>() != null)
@ -70,87 +72,4 @@ namespace Kyoo.Core.Api
return contract; return contract;
} }
} }
public class PeopleRoleConverter : JsonConverter<PeopleRole>
{
public override void WriteJson(JsonWriter writer, PeopleRole value, JsonSerializer serializer)
{
ICollection<PeopleRole> oldPeople = value.Show?.People;
ICollection<PeopleRole> oldRoles = value.People?.Roles;
if (value.Show != null)
value.Show.People = null;
if (value.People != null)
value.People.Roles = null;
JObject obj = JObject.FromObject((value.ForPeople ? value.People : value.Show)!, serializer);
obj.Add("role", value.Role);
obj.Add("type", value.Type);
obj.WriteTo(writer);
if (value.Show != null)
value.Show.People = oldPeople;
if (value.People != null)
value.People.Roles = oldRoles;
}
public override PeopleRole ReadJson(JsonReader reader,
Type objectType,
PeopleRole existingValue,
bool hasExistingValue,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public class SerializeAsProvider : IValueProvider
{
private string _format;
private string _host;
public SerializeAsProvider(string format, string host)
{
_format = format;
_host = host.TrimEnd('/');
}
public object GetValue(object target)
{
return Regex.Replace(_format, @"(?<!{){(\w+)(:(\w+))?}", x =>
{
string value = x.Groups[1].Value;
string modifier = x.Groups[3].Value;
if (value == "HOST")
return _host;
PropertyInfo properties = target.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.FirstOrDefault(y => y.Name == value);
if (properties == null)
return null;
object objValue = properties.GetValue(target);
if (objValue is not string ret)
ret = objValue?.ToString();
if (ret == null)
throw new ArgumentException($"Invalid serializer replacement {value}");
foreach (char modification in modifier)
{
ret = modification switch
{
'l' => ret.ToLowerInvariant(),
'u' => ret.ToUpperInvariant(),
_ => throw new ArgumentException($"Invalid serializer modificator {modification}.")
};
}
return ret;
});
}
public void SetValue(object target, object value)
{
// Values are ignored and should not be editable, except if the internal value is set.
}
}
} }

View File

@ -0,0 +1,48 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
namespace Kyoo.Core.Api
{
public class PeopleRoleConverter : JsonConverter<PeopleRole>
{
public override void WriteJson(JsonWriter writer, PeopleRole value, JsonSerializer serializer)
{
ICollection<PeopleRole> oldPeople = value.Show?.People;
ICollection<PeopleRole> oldRoles = value.People?.Roles;
if (value.Show != null)
value.Show.People = null;
if (value.People != null)
value.People.Roles = null;
JObject obj = JObject.FromObject((value.ForPeople ? value.People : value.Show)!, serializer);
obj.Add("role", value.Role);
obj.Add("type", value.Type);
obj.WriteTo(writer);
if (value.Show != null)
value.Show.People = oldPeople;
if (value.People != null)
value.People.Roles = oldRoles;
}
public override PeopleRole ReadJson(JsonReader reader,
Type objectType,
PeopleRole existingValue,
bool hasExistingValue,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,67 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
namespace Kyoo.Core.Api
{
public class SerializeAsProvider : IValueProvider
{
private string _format;
private string _host;
public SerializeAsProvider(string format, string host)
{
_format = format;
_host = host.TrimEnd('/');
}
public object GetValue(object target)
{
return Regex.Replace(_format, @"(?<!{){(\w+)(:(\w+))?}", x =>
{
string value = x.Groups[1].Value;
string modifier = x.Groups[3].Value;
if (value == "HOST")
return _host;
PropertyInfo properties = target.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.FirstOrDefault(y => y.Name == value);
if (properties == null)
return null;
object objValue = properties.GetValue(target);
if (objValue is not string ret)
ret = objValue?.ToString();
if (ret == null)
throw new ArgumentException($"Invalid serializer replacement {value}");
foreach (char modification in modifier)
{
ret = modification switch
{
'l' => ret.ToLowerInvariant(),
'u' => ret.ToUpperInvariant(),
_ => throw new ArgumentException($"Invalid serializer modificator {modification}.")
};
}
return ret;
});
}
public void SetValue(object target, object value)
{
// Values are ignored and should not be editable, except if the internal value is set.
}
}
}

View File

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

View File

@ -1,12 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Core.Models.Options; using Kyoo.Core.Models.Options;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Kyoo.Core.Api namespace Kyoo.Core.Api

View File

@ -1,5 +1,4 @@
using System; using System;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -9,6 +8,7 @@ using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Core.Models.Options; using Kyoo.Core.Models.Options;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Kyoo.Core.Api namespace Kyoo.Core.Api

View File

@ -1,4 +1,3 @@
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -6,6 +5,7 @@ using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Permissions;
using Microsoft.AspNetCore.Mvc;
namespace Kyoo.Core.Api namespace Kyoo.Core.Api
{ {
@ -64,78 +64,78 @@ namespace Kyoo.Core.Api
return new ConvertSubripToVtt(subtitle.Path, _files); return new ConvertSubripToVtt(subtitle.Path, _files);
return _files.FileResult(subtitle.Path); return _files.FileResult(subtitle.Path);
} }
}
public class ConvertSubripToVtt : IActionResult public class ConvertSubripToVtt : IActionResult
{
private readonly string _path;
private readonly IFileSystem _files;
public ConvertSubripToVtt(string subtitlePath, IFileSystem files)
{ {
_path = subtitlePath; private readonly string _path;
_files = files; private readonly IFileSystem _files;
}
public async Task ExecuteResultAsync(ActionContext context) public ConvertSubripToVtt(string subtitlePath, IFileSystem files)
{
List<string> lines = new();
context.HttpContext.Response.StatusCode = 200;
context.HttpContext.Response.Headers.Add("Content-Type", "text/vtt");
await using (StreamWriter writer = new(context.HttpContext.Response.Body))
{ {
await writer.WriteLineAsync("WEBVTT"); _path = subtitlePath;
await writer.WriteLineAsync(""); _files = files;
await writer.WriteLineAsync(""); }
using StreamReader reader = new(await _files.GetReader(_path)); public async Task ExecuteResultAsync(ActionContext context)
string line; {
while ((line = await reader.ReadLineAsync()) != null) List<string> lines = new();
context.HttpContext.Response.StatusCode = 200;
context.HttpContext.Response.Headers.Add("Content-Type", "text/vtt");
await using (StreamWriter writer = new(context.HttpContext.Response.Body))
{ {
if (line == "") await writer.WriteLineAsync("WEBVTT");
await writer.WriteLineAsync(string.Empty);
await writer.WriteLineAsync(string.Empty);
using StreamReader reader = new(await _files.GetReader(_path));
string line;
while ((line = await reader.ReadLineAsync()) != null)
{ {
lines.Add(""); if (line == string.Empty)
IEnumerable<string> processedBlock = ConvertBlock(lines); {
foreach (string t in processedBlock) lines.Add(string.Empty);
await writer.WriteLineAsync(t); IEnumerable<string> processedBlock = _ConvertBlock(lines);
lines.Clear(); foreach (string t in processedBlock)
await writer.WriteLineAsync(t);
lines.Clear();
}
else
lines.Add(line);
} }
else
lines.Add(line);
} }
await context.HttpContext.Response.Body.FlushAsync();
} }
await context.HttpContext.Response.Body.FlushAsync(); private static IEnumerable<string> _ConvertBlock(IList<string> lines)
}
private static IEnumerable<string> ConvertBlock(IList<string> lines)
{
if (lines.Count < 3)
return lines;
lines[1] = lines[1].Replace(',', '.');
if (lines[2].Length > 5)
{ {
lines[1] += lines[2].Substring(0, 6) switch if (lines.Count < 3)
return lines;
lines[1] = lines[1].Replace(',', '.');
if (lines[2].Length > 5)
{ {
"{\\an1}" => " line:93% position:15%", lines[1] += lines[2].Substring(0, 6) switch
"{\\an2}" => " line:93%", {
"{\\an3}" => " line:93% position:85%", "{\\an1}" => " line:93% position:15%",
"{\\an4}" => " line:50% position:15%", "{\\an2}" => " line:93%",
"{\\an5}" => " line:50%", "{\\an3}" => " line:93% position:85%",
"{\\an6}" => " line:50% position:85%", "{\\an4}" => " line:50% position:15%",
"{\\an7}" => " line:7% position:15%", "{\\an5}" => " line:50%",
"{\\an8}" => " line:7%", "{\\an6}" => " line:50% position:85%",
"{\\an9}" => " line:7% position:85%", "{\\an7}" => " line:7% position:15%",
_ => " line:93%" "{\\an8}" => " line:7%",
}; "{\\an9}" => " line:7% position:85%",
_ => " line:93%"
};
}
if (lines[2].StartsWith("{\\an"))
lines[2] = lines[2].Substring(6);
return lines;
} }
if (lines[2].StartsWith("{\\an"))
lines[2] = lines[2].Substring(6);
return lines;
} }
} }
} }

View File

@ -1,11 +1,11 @@
using System.IO; using System.IO;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Core.Models.Options; using Kyoo.Core.Models.Options;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;

View File

@ -114,6 +114,7 @@ namespace Kyoo.Database
/// <param name="second">The ID of the second resource.</param> /// <param name="second">The ID of the second resource.</param>
/// <typeparam name="T1">The first resource type of the relation. It is the owner of the second</typeparam> /// <typeparam name="T1">The first resource type of the relation. It is the owner of the second</typeparam>
/// <typeparam name="T2">The second resource type of the relation. It is the contained resource.</typeparam> /// <typeparam name="T2">The second resource type of the relation. It is the contained resource.</typeparam>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task AddLinks<T1, T2>(int first, int second) public async Task AddLinks<T1, T2>(int first, int second)
where T1 : class, IResource where T1 : class, IResource
where T2 : class, IResource where T2 : class, IResource
@ -433,7 +434,7 @@ namespace Kyoo.Database
/// <exception cref="DuplicatedItemException">A duplicated item has been found.</exception> /// <exception cref="DuplicatedItemException">A duplicated item has been found.</exception>
/// <returns>The number of state entries written to the database.</returns> /// <returns>The number of state entries written to the database.</returns>
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess,
CancellationToken cancellationToken = new()) CancellationToken cancellationToken = default)
{ {
try try
{ {
@ -454,7 +455,7 @@ namespace Kyoo.Database
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param> /// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <exception cref="DuplicatedItemException">A duplicated item has been found.</exception> /// <exception cref="DuplicatedItemException">A duplicated item has been found.</exception>
/// <returns>The number of state entries written to the database.</returns> /// <returns>The number of state entries written to the database.</returns>
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new()) public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{ {
try try
{ {
@ -538,7 +539,7 @@ namespace Kyoo.Database
/// <summary> /// <summary>
/// Delete every changes that are on this context. /// Delete every changes that are on this context.
/// </summary> /// </summary>
private void DiscardChanges() public void DiscardChanges()
{ {
foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Detached)) foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Detached))
{ {

View File

@ -166,8 +166,8 @@ namespace Kyoo.SqLite
/// <inheritdoc /> /// <inheritdoc />
protected override bool IsDuplicateException(Exception ex) protected override bool IsDuplicateException(Exception ex)
{ {
return ex.InnerException is SqliteException { SqliteExtendedErrorCode: 2067 /*SQLITE_CONSTRAINT_UNIQUE*/} return ex.InnerException is SqliteException { SqliteExtendedErrorCode: 2067 /* SQLITE_CONSTRAINT_UNIQUE */ }
or SqliteException { SqliteExtendedErrorCode: 1555 /*SQLITE_CONSTRAINT_PRIMARYKEY*/}; or SqliteException { SqliteExtendedErrorCode: 1555 /* SQLITE_CONSTRAINT_PRIMARYKEY */ };
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -47,8 +47,10 @@ namespace Kyoo.TheTvdb
{ {
_configuration = configuration; _configuration = configuration;
if (!Enabled) if (!Enabled)
{
logger.LogWarning("No API key configured for TVDB provider. " + logger.LogWarning("No API key configured for TVDB provider. " +
"To enable TVDB, specify one in the setting TVDB:APIKEY "); "To enable TVDB, specify one in the setting TVDB:APIKEY ");
}
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -11,6 +11,7 @@
<Rule Id="SA1309" Action="None" /> <!-- FieldNamesMustNotBeginWithUnderscore --> <Rule Id="SA1309" Action="None" /> <!-- FieldNamesMustNotBeginWithUnderscore -->
<Rule Id="SX1309" Action="Warning" /> <!-- FieldNamesMustBeginWithUnderscore --> <Rule Id="SX1309" Action="Warning" /> <!-- FieldNamesMustBeginWithUnderscore -->
<Rule Id="SA1300" Action="None" /> <!-- ElementMustBeginWithUpperCaseLetter (this conflict with the _ prefix for privates, enforced by an IDE rule) --> <Rule Id="SA1300" Action="None" /> <!-- ElementMustBeginWithUpperCaseLetter (this conflict with the _ prefix for privates, enforced by an IDE rule) -->
<Rule Id="SA1316" Action="None" /> <!-- TupleElementNamesShouldUseCorrectCasing (should be camels when deconstructing but pascal otherwise. -->
</Rules> </Rules>
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.ReadabilityRules"> <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.ReadabilityRules">
<Rule Id="SA1101" Action="None" /> <!-- PrefixLocalCallsWithThis --> <Rule Id="SA1101" Action="None" /> <!-- PrefixLocalCallsWithThis -->
@ -40,5 +41,6 @@
<Rule Id="SA1633" Action="None" /> <!-- FileMustHaveHeader TODO remove this, this is only temporary --> <Rule Id="SA1633" Action="None" /> <!-- FileMustHaveHeader TODO remove this, this is only temporary -->
<Rule Id="SA1629" Action="None" /> <!-- DocumentationTextMustEndWithAPeriod TODO remove this, this is only temporary --> <Rule Id="SA1629" Action="None" /> <!-- DocumentationTextMustEndWithAPeriod TODO remove this, this is only temporary -->
<Rule Id="SA1600" Action="None" /> <!-- ElementsMustBeDocumented TODO remove this, this is only temporary -->
</Rules> </Rules>
</RuleSet> </RuleSet>

View File

@ -44,7 +44,7 @@ namespace Kyoo.Tests.Database
public async Task CreateWithEmptySlugTest() public async Task CreateWithEmptySlugTest()
{ {
Collection collection = TestSample.GetNew<Collection>(); Collection collection = TestSample.GetNew<Collection>();
collection.Slug = ""; collection.Slug = string.Empty;
await Assert.ThrowsAsync<ArgumentException>(() => _repository.Create(collection)); await Assert.ThrowsAsync<ArgumentException>(() => _repository.Create(collection));
} }

View File

@ -53,7 +53,7 @@ namespace Kyoo.Tests.Database
public async Task CreateWithEmptySlugTest() public async Task CreateWithEmptySlugTest()
{ {
Library library = TestSample.GetNew<Library>(); Library library = TestSample.GetNew<Library>();
library.Slug = ""; library.Slug = string.Empty;
await Assert.ThrowsAsync<ArgumentException>(() => _repository.Create(library)); await Assert.ThrowsAsync<ArgumentException>(() => _repository.Create(library));
} }

View File

@ -85,7 +85,7 @@ namespace Kyoo.Tests.Identifier
Genres = new[] { new Genre("genre") } Genres = new[] { new Genre("genre") }
}; };
Mock<IMetadataProvider> mock = new(); Mock<IMetadataProvider> mock = new();
mock.Setup(x => x.Provider).Returns(new Provider("mock", "")); mock.Setup(x => x.Provider).Returns(new Provider("mock", string.Empty));
mock.Setup(x => x.Get(show)).ReturnsAsync(new Show mock.Setup(x => x.Get(show)).ReturnsAsync(new Show
{ {
Title = "title", Title = "title",
@ -93,7 +93,7 @@ namespace Kyoo.Tests.Identifier
}); });
Mock<IMetadataProvider> mockTwo = new(); Mock<IMetadataProvider> mockTwo = new();
mockTwo.Setup(x => x.Provider).Returns(new Provider("mockTwo", "")); mockTwo.Setup(x => x.Provider).Returns(new Provider("mockTwo", string.Empty));
mockTwo.Setup(x => x.Get(show)).ReturnsAsync(new Show mockTwo.Setup(x => x.Get(show)).ReturnsAsync(new Show
{ {
Title = "title2", Title = "title2",
@ -102,7 +102,7 @@ namespace Kyoo.Tests.Identifier
}); });
Mock<IMetadataProvider> mockFailing = new(); Mock<IMetadataProvider> mockFailing = new();
mockFailing.Setup(x => x.Provider).Returns(new Provider("mockFail", "")); mockFailing.Setup(x => x.Provider).Returns(new Provider("mockFail", string.Empty));
mockFailing.Setup(x => x.Get(show)).Throws<ArgumentException>(); mockFailing.Setup(x => x.Get(show)).Throws<ArgumentException>();
AProviderComposite provider = new ProviderComposite(new[] AProviderComposite provider = new ProviderComposite(new[]