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>
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)Kyoo.ruleset</CodeAnalysisRuleSet>
<!-- <AnalysisMode>AllEnabledByDefault</AnalysisMode>-->

View File

@ -221,7 +221,7 @@ namespace Kyoo.Abstractions.Controllers
/// Create a new resource.
/// </summary>
/// <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]
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="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>
/// <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]
Task<T> Edit([NotNull] T edited, bool resetOld);
@ -422,7 +422,7 @@ namespace Kyoo.Abstractions.Controllers
/// </summary>
/// <param name="id">The ID of the library</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>
/// <returns>A list of items that match every filters</returns>
public Task<ICollection<LibraryItem>> GetFromLibrary(int id,
@ -449,7 +449,7 @@ namespace Kyoo.Abstractions.Controllers
/// </summary>
/// <param name="slug">The slug of the library</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>
/// <returns>A list of items that match every filters</returns>
public Task<ICollection<LibraryItem>> GetFromLibrary(string slug,
@ -497,7 +497,7 @@ namespace Kyoo.Abstractions.Controllers
/// </summary>
/// <param name="showID">The ID of the show</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>
/// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetFromShow(int showID,
@ -524,7 +524,7 @@ namespace Kyoo.Abstractions.Controllers
/// </summary>
/// <param name="showSlug">The slug of the show</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>
/// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetFromShow(string showSlug,
@ -551,7 +551,7 @@ namespace Kyoo.Abstractions.Controllers
/// </summary>
/// <param name="id">The id of the person</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>
/// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetFromPeople(int id,
@ -578,7 +578,7 @@ namespace Kyoo.Abstractions.Controllers
/// </summary>
/// <param name="slug">The slug of the person</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>
/// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetFromPeople(string slug,
@ -610,7 +610,7 @@ namespace Kyoo.Abstractions.Controllers
/// Get a list of external ids that match all filters
/// </summary>
/// <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>
/// <typeparam name="T">The type of metadata to retrieve</typeparam>
/// <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(),
PreviousEpisode = previous,
NextEpisode = next,
Chapters = await GetChapters(ep.Path)
Chapters = await _GetChapters(ep.Path)
};
}
// TODO move this method in a controller to support abstraction.
// 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(
PathIO.GetDirectoryName(episodePath)!,

View File

@ -21,6 +21,7 @@ namespace Kyoo.Utils
/// <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="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>
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
public static T[] MergeLists<T>([CanBeNull] IEnumerable<T> first,
@ -219,7 +220,8 @@ namespace Kyoo.Utils
{
Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>))
.GenericTypeArguments;
object[] parameters = {
object[] parameters =
{
property.GetValue(first),
value,
false
@ -290,7 +292,8 @@ namespace Kyoo.Utils
{
Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>))
.GenericTypeArguments;
object[] parameters = {
object[] parameters =
{
oldValue,
newValue,
false

View File

@ -26,7 +26,7 @@ namespace Kyoo.Utils
if (ex == null)
return false;
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>
@ -51,7 +51,7 @@ namespace Kyoo.Utils
/// <param name="member">The member value</param>
/// <param name="obj">The owner of this member</param>
/// <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>
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 = 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 = Regex.Replace(str, @"([-_]){2,}", "$1", RegexOptions.Compiled);
return str;
@ -113,7 +113,7 @@ namespace Kyoo.Utils
/// </summary>
/// <param name="type">The starting type</param>
/// <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)
{
if (type == null)
@ -123,9 +123,9 @@ namespace Kyoo.Utils
}
/// <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>
/// <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>
/// <returns>True if obj inherit from genericType. False otherwise</returns>
/// <exception cref="ArgumentNullException">obj and genericType can't be null</exception>
@ -137,7 +137,7 @@ namespace Kyoo.Utils
}
/// <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>
/// <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>
@ -160,14 +160,14 @@ namespace Kyoo.Utils
}
/// <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;
/// </summary>
/// <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>
/// <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="ArgumentException"><see cref="genericType"/> must be a generic type</exception>
/// <exception cref="ArgumentNullException"><paramref name="type"/> and <paramref name="genericType"/> can't be null</exception>
/// <exception cref="ArgumentException"><paramref name="genericType"/> must be a generic type</exception>
public static Type GetGenericDefinition([NotNull] Type type, [NotNull] Type genericType)
{
if (type == null)

View File

@ -103,9 +103,7 @@ namespace Kyoo.Authentication
services.AddControllers();
// 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.
List<Client> clients = new();
_configuration.GetSection("authentication:clients").Bind(clients);
CertificateOption certificateOptions = new();

View File

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

View File

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

View File

@ -89,7 +89,7 @@ namespace Kyoo.Authentication.Views
/// </summary>
/// <param name="stayLogged">Should the user stay logged</param>
/// <returns>Authentication properties based on a stay login</returns>
private static AuthenticationProperties StayLogged(bool stayLogged)
private static AuthenticationProperties _StayLogged(bool stayLogged)
{
if (!stayLogged)
return null;
@ -114,7 +114,7 @@ namespace Kyoo.Authentication.Views
if (!PasswordUtils.CheckPassword(login.Password, user.Password))
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 });
}
@ -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();
}

View File

@ -44,6 +44,25 @@ namespace Kyoo.Core.Controllers
_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 />
public void AddTyped<T>(string path)
{
@ -119,8 +138,10 @@ namespace Kyoo.Core.Controllers
// TODO handle lists and dictionaries.
Type type = _GetType(path);
if (typeof(T).IsAssignableFrom(type))
{
throw new InvalidCastException($"The type {typeof(T).Name} is not valid for " +
$"a resource of type {type.Name}.");
}
return (T)GetValue(path);
}
@ -171,24 +192,5 @@ namespace Kyoo.Core.Controllers
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 JetBrains.Annotations;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Core.Models.Options;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
@ -75,7 +75,7 @@ namespace Kyoo.Core.Controllers
{
usablePath = path;
Meta<Func<IFileSystem>, FileSystemMetadataAttribute> defaultFs = _fileSystems
.SingleOrDefault(x => x.Metadata.Scheme.Contains(""));
.SingleOrDefault(x => x.Metadata.Scheme.Contains(string.Empty));
if (defaultFs == null)
throw new ArgumentException($"No file system registered for the default scheme.");
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.");
}
}
/// <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>
/// Should the proxied result support ranges requests?
/// An <see cref="IActionResult"/> to proxy an http request.
/// </summary>
private readonly bool _rangeSupport;
/// <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)
// TODO remove this suppress message once the class has been implemented.
[SuppressMessage("ReSharper", "NotAccessedField.Local", Justification = "Not Implemented Yet.")]
public class HttpForwardResult : IActionResult
{
_path = path;
_rangeSupport = rangeSupport;
_type = type;
}
/// <summary>
/// The path of the request to forward.
/// </summary>
private readonly Uri _path;
/// <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();
/// <summary>
/// Should the proxied result support ranges requests?
/// </summary>
private readonly bool _rangeSupport;
/// <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 />
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1507:CodeMustNotContainMultipleBlankLinesInARow",
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)
{
if (obj == null)

View File

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

View File

@ -19,16 +19,16 @@ namespace Kyoo.Core.Controllers
/// </summary>
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>
/// The logger used to print errors.
/// </summary>
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>
/// Create a new <see cref="ProviderComposite"/> with a list of available providers.
/// </summary>

View File

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

View File

@ -111,7 +111,7 @@ namespace Kyoo.Core.Controllers
/// </summary>
/// <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>
private IQueryable<LibraryItem> LibraryRelatedQuery(Expression<Func<Library, bool>> selector)
private IQueryable<LibraryItem> _LibraryRelatedQuery(Expression<Func<Library, bool>> selector)
=> _database.Libraries
.Where(selector)
.SelectMany(x => x.Shows)
@ -128,7 +128,7 @@ namespace Kyoo.Core.Controllers
Sort<LibraryItem> sort = 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,
sort,
limit);
@ -143,7 +143,7 @@ namespace Kyoo.Core.Controllers
Sort<LibraryItem> sort = 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,
sort,
limit);

View File

@ -8,8 +8,8 @@ using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Utils;
using Kyoo.Core.Api;
using Kyoo.Utils;
using Microsoft.EntityFrameworkCore;
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.
/// For resources of type <see cref="T"/>, see <see cref="ApplyFilters"/>
/// </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="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="sort">The sort settings (sort order & sort by)</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>
protected async Task<ICollection<TValue>> ApplyFilters<TValue>(IQueryable<TValue> query,
Func<int, Task<TValue>> get,
@ -252,6 +253,7 @@ namespace Kyoo.Core.Controllers
/// <param name="resetOld">
/// A boolean to indicate if all values of resource should be discarded or not.
/// </param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected virtual Task EditRelations(T resource, T changed, bool resetOld)
{
return Validate(resource);
@ -265,6 +267,7 @@ namespace Kyoo.Core.Controllers
/// <exception cref="ArgumentException">
/// You can throw this if the resource is illegal and should not be saved.
/// </exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected virtual Task Validate(T resource)
{
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.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;
}

View File

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

View File

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

View File

@ -90,16 +90,16 @@ namespace Kyoo.Core.Controllers
/// </summary>
private readonly Queue<QueuedTask> _queuedTasks = new();
/// <summary>
/// The currently running task.
/// </summary>
private (TaskMetadataAttribute, ITask)? _runningTask;
/// <summary>
/// The cancellation token used to cancel the running task when the runner should shutdown.
/// </summary>
private readonly CancellationTokenSource _taskToken = new();
/// <summary>
/// The currently running task.
/// </summary>
private (TaskMetadataAttribute, ITask)? _runningTask;
/// <summary>
/// Create a new <see cref="TaskManager"/>.
/// </summary>
@ -116,7 +116,7 @@ namespace Kyoo.Core.Controllers
{
Factory = x.Value,
Metadata = x.Metadata,
ScheduledDate = GetNextTaskDate(x.Metadata.Slug)
ScheduledDate = _GetNextTaskDate(x.Metadata.Slug)
}).ToList();
if (_tasks.Any())
@ -130,6 +130,7 @@ namespace Kyoo.Core.Controllers
/// </summary>
/// <remarks>Start the runner in another thread.</remarks>
/// <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)
{
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.
/// </summary>
/// <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)
{
_EnqueueStartupTasks();
@ -174,7 +176,7 @@ namespace Kyoo.Core.Controllers
else
{
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))
.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);
}));
@ -238,7 +242,7 @@ namespace Kyoo.Core.Controllers
/// <summary>
/// Start tasks that are scheduled for start.
/// </summary>
private void QueueScheduledTasks()
private void _QueueScheduledTasks()
{
IEnumerable<string> tasksToQueue = _tasks.Where(x => x.ScheduledDate <= DateTime.Now)
.Select(x => x.Metadata.Slug);
@ -280,7 +284,7 @@ namespace Kyoo.Core.Controllers
Arguments = arguments,
CancellationToken = cancellationToken
});
_tasks[index].ScheduledDate = GetNextTaskDate(taskSlug);
_tasks[index].ScheduledDate = _GetNextTaskDate(taskSlug);
}
/// <inheritdoc />
@ -300,7 +304,7 @@ namespace Kyoo.Core.Controllers
/// </summary>
/// <param name="taskSlug">The slug of the task</param>
/// <returns>The next date.</returns>
private DateTime GetNextTaskDate(string taskSlug)
private DateTime _GetNextTaskDate(string taskSlug)
{
if (_options.CurrentValue.Scheduled.TryGetValue(taskSlug, out TimeSpan delay))
return DateTime.Now + delay;

View File

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

View File

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

View File

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

View File

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

View File

@ -89,9 +89,9 @@ namespace Kyoo.Core.Tasks
IProgress<float> reporter = new Progress<float>(x =>
{
// 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;
if (cancellationToken.IsCancellationRequested)
@ -101,7 +101,7 @@ namespace Kyoo.Core.Tasks
progress.Report(100);
}
private async Task Scan(Library library,
private async Task _Scan(Library library,
IEnumerable<Episode> episodes,
IEnumerable<Track> tracks,
IProgress<float> progress,
@ -131,7 +131,7 @@ namespace Kyoo.Core.Tasks
IProgress<float> reporter = new Progress<float>(x =>
{
// 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)
@ -153,7 +153,7 @@ namespace Kyoo.Core.Tasks
reporter = new Progress<float>(x =>
{
// 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)

View File

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

View File

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

View File

@ -71,11 +71,11 @@ namespace Kyoo.Core.Api
Expression condition = operand switch
{
"eq" when isList => ContainsResourceExpression(propertyExpr, value),
"ctn" => ContainsResourceExpression(propertyExpr, value),
"eq" when isList => _ContainsResourceExpression(propertyExpr, value),
"ctn" => _ContainsResourceExpression(propertyExpr, value),
"eq" when valueExpr == null => ResourceEqual(propertyExpr, value),
"not" when valueExpr == null => ResourceEqual(propertyExpr, value, true),
"eq" when valueExpr == null => _ResourceEqual(propertyExpr, value),
"not" when valueExpr == null => _ResourceEqual(propertyExpr, value, true),
"eq" => Expression.Equal(propertyExpr, valueExpr),
"not" => Expression.NotEqual(propertyExpr, valueExpr!),
@ -95,7 +95,7 @@ namespace Kyoo.Core.Api
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;
ConstantExpression valueConst;
@ -115,14 +115,14 @@ namespace Kyoo.Core.Api
: 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)
Expression ret = null;
ParameterExpression y = Expression.Parameter(xProperty.Type.GenericTypeArguments.First(), "y");
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,
xProperty, lambda);

View File

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

View File

@ -77,11 +77,11 @@ namespace Kyoo.Core.Api
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
if (context.Result is ObjectResult result)
await LoadResultRelations(context, result);
await _LoadResultRelations(context, result);
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)
return;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@ -33,6 +33,7 @@ namespace Kyoo.Core.Api
if (relation.RelationID == null)
property.ShouldSerialize = x => _depth == 0 && member.GetValue(x) != null;
else
{
property.ShouldSerialize = x =>
{
if (_depth != 0)
@ -41,6 +42,7 @@ namespace Kyoo.Core.Api
return true;
return x.GetType().GetProperty(relation.RelationID)?.GetValue(x) != null;
};
}
}
if (member.GetCustomAttribute<SerializeIgnoreAttribute>() != null)
@ -70,87 +72,4 @@ namespace Kyoo.Core.Api
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);
if (result.Value != null)
{
_taskManager.StartTask("scan",
new Progress<float>(),
new Dictionary<string, object> { { "slug", result.Value.Slug } });
}
return result;
}

View File

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

View File

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

View File

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

View File

@ -114,6 +114,7 @@ namespace Kyoo.Database
/// <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="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)
where T1 : class, IResource
where T2 : class, IResource
@ -433,7 +434,7 @@ namespace Kyoo.Database
/// <exception cref="DuplicatedItemException">A duplicated item has been found.</exception>
/// <returns>The number of state entries written to the database.</returns>
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess,
CancellationToken cancellationToken = new())
CancellationToken cancellationToken = default)
{
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>
/// <exception cref="DuplicatedItemException">A duplicated item has been found.</exception>
/// <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
{
@ -538,7 +539,7 @@ namespace Kyoo.Database
/// <summary>
/// Delete every changes that are on this context.
/// </summary>
private void DiscardChanges()
public void DiscardChanges()
{
foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Detached))
{

View File

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

View File

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

View File

@ -11,6 +11,7 @@
<Rule Id="SA1309" Action="None" /> <!-- FieldNamesMustNotBeginWithUnderscore -->
<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="SA1316" Action="None" /> <!-- TupleElementNamesShouldUseCorrectCasing (should be camels when deconstructing but pascal otherwise. -->
</Rules>
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.ReadabilityRules">
<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="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>
</RuleSet>

View File

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

View File

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

View File

@ -85,7 +85,7 @@ namespace Kyoo.Tests.Identifier
Genres = new[] { new Genre("genre") }
};
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
{
Title = "title",
@ -93,7 +93,7 @@ namespace Kyoo.Tests.Identifier
});
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
{
Title = "title2",
@ -102,7 +102,7 @@ namespace Kyoo.Tests.Identifier
});
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>();
AProviderComposite provider = new ProviderComposite(new[]