diff --git a/Directory.Build.props b/Directory.Build.props index 6f794a9f..c109a6e1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,6 +14,7 @@ + true true $(MSBuildThisFileDirectory)Kyoo.ruleset diff --git a/Kyoo.Abstractions/Controllers/IRepository.cs b/Kyoo.Abstractions/Controllers/IRepository.cs index 65fc9286..68bf5737 100644 --- a/Kyoo.Abstractions/Controllers/IRepository.cs +++ b/Kyoo.Abstractions/Controllers/IRepository.cs @@ -221,7 +221,7 @@ namespace Kyoo.Abstractions.Controllers /// Create a new resource. /// /// The item to register - /// The resource registers and completed by database's information (related items & so on) + /// The resource registers and completed by database's information (related items and so on) [ItemNotNull] Task Create([NotNull] T obj); @@ -239,7 +239,7 @@ namespace Kyoo.Abstractions.Controllers /// The resource to edit, it's ID can't change. /// Should old properties of the resource be discarded or should null values considered as not changed? /// If the item is not found - /// The resource edited and completed by database's information (related items & so on) + /// The resource edited and completed by database's information (related items and so on) [ItemNotNull] Task Edit([NotNull] T edited, bool resetOld); @@ -422,7 +422,7 @@ namespace Kyoo.Abstractions.Controllers /// /// The ID of the library /// A filter function - /// Sort information (sort order & sort by) + /// Sort information (sort order and sort by) /// How many items to return and where to start /// A list of items that match every filters public Task> GetFromLibrary(int id, @@ -449,7 +449,7 @@ namespace Kyoo.Abstractions.Controllers /// /// The slug of the library /// A filter function - /// Sort information (sort order & sort by) + /// Sort information (sort order and sort by) /// How many items to return and where to start /// A list of items that match every filters public Task> GetFromLibrary(string slug, @@ -497,7 +497,7 @@ namespace Kyoo.Abstractions.Controllers /// /// The ID of the show /// A filter function - /// Sort information (sort order & sort by) + /// Sort information (sort order and sort by) /// How many items to return and where to start /// A list of items that match every filters Task> GetFromShow(int showID, @@ -524,7 +524,7 @@ namespace Kyoo.Abstractions.Controllers /// /// The slug of the show /// A filter function - /// Sort information (sort order & sort by) + /// Sort information (sort order and sort by) /// How many items to return and where to start /// A list of items that match every filters Task> GetFromShow(string showSlug, @@ -551,7 +551,7 @@ namespace Kyoo.Abstractions.Controllers /// /// The id of the person /// A filter function - /// Sort information (sort order & sort by) + /// Sort information (sort order and sort by) /// How many items to return and where to start /// A list of items that match every filters Task> GetFromPeople(int id, @@ -578,7 +578,7 @@ namespace Kyoo.Abstractions.Controllers /// /// The slug of the person /// A filter function - /// Sort information (sort order & sort by) + /// Sort information (sort order and sort by) /// How many items to return and where to start /// A list of items that match every filters Task> GetFromPeople(string slug, @@ -610,7 +610,7 @@ namespace Kyoo.Abstractions.Controllers /// Get a list of external ids that match all filters /// /// A predicate to add arbitrary filter - /// Sort information (sort order & sort by) + /// Sort information (sort order and sort by) /// Pagination information (where to start and how many to get) /// The type of metadata to retrieve /// A filtered list of external ids. diff --git a/Kyoo.Abstractions/Models/Resources/WatchedEpisode.cs b/Kyoo.Abstractions/Models/Resources/WatchedEpisode.cs new file mode 100644 index 00000000..8f17dd1a --- /dev/null +++ b/Kyoo.Abstractions/Models/Resources/WatchedEpisode.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace Kyoo.Abstractions.Models +{ + /// + /// Metadata of episode currently watching by an user + /// + public class WatchedEpisode + { + /// + /// The ID of the user that started watching this episode. + /// + public int UserID { get; set; } + + /// + /// The ID of the episode started. + /// + public int EpisodeID { get; set; } + + /// + /// The started. + /// + public Episode Episode { get; set; } + + /// + /// Where the player has stopped watching the episode (between 0 and 100). + /// + public int WatchedPercentage { get; set; } + } +} diff --git a/Kyoo.Abstractions/Models/WatchItem.cs b/Kyoo.Abstractions/Models/WatchItem.cs index 0692aa90..14c5eabe 100644 --- a/Kyoo.Abstractions/Models/WatchItem.cs +++ b/Kyoo.Abstractions/Models/WatchItem.cs @@ -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> GetChapters(string episodePath) + private static async Task> _GetChapters(string episodePath) { string path = PathIO.Combine( PathIO.GetDirectoryName(episodePath)!, diff --git a/Kyoo.Abstractions/Utility/Merger.cs b/Kyoo.Abstractions/Utility/Merger.cs index 17b21bd5..bb391c56 100644 --- a/Kyoo.Abstractions/Utility/Merger.cs +++ b/Kyoo.Abstractions/Utility/Merger.cs @@ -21,6 +21,7 @@ namespace Kyoo.Utils /// The first enumerable to merge /// The second enumerable to merge, if items from this list are equals to one from the first, they are not kept /// Equality function to compare items. If this is null, duplicated elements are kept + /// The type of items in the lists to merge. /// The two list merged as an array [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] public static T[] MergeLists([CanBeNull] IEnumerable 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 diff --git a/Kyoo.Abstractions/Utility/Utility.cs b/Kyoo.Abstractions/Utility/Utility.cs index 0b263dde..6e30f1e0 100644 --- a/Kyoo.Abstractions/Utility/Utility.cs +++ b/Kyoo.Abstractions/Utility/Utility.cs @@ -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); } /// @@ -51,7 +51,7 @@ namespace Kyoo.Utils /// The member value /// The owner of this member /// The value boxed as an object - /// if or is null. + /// if or is null. /// The member is not a field or a property. 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 /// /// The starting type /// A list of types - /// can't be null + /// can't be null public static IEnumerable GetInheritanceTree([NotNull] this Type type) { if (type == null) @@ -123,9 +123,9 @@ namespace Kyoo.Utils } /// - /// Check if inherit from a generic type . + /// Check if inherit from a generic type . /// - /// Does this object's type is a + /// Does this object's type is a /// The generic type to check against (Only generic types are supported like typeof(IEnumerable<>). /// True if obj inherit from genericType. False otherwise /// obj and genericType can't be null @@ -137,7 +137,7 @@ namespace Kyoo.Utils } /// - /// Check if inherit from a generic type . + /// Check if inherit from a generic type . /// /// The type to check /// The generic type to check against (Only generic types are supported like typeof(IEnumerable<>). @@ -160,14 +160,14 @@ namespace Kyoo.Utils } /// - /// Get the generic definition of . + /// Get the generic definition of . /// For example, calling this function with List<string> and typeof(IEnumerable<>) will return IEnumerable<string> /// /// The type to check /// The generic type to check against (Only generic types are supported like typeof(IEnumerable<>). /// The generic definition of genericType that type inherit or null if type does not implement the generic type. - /// and can't be null - /// must be a generic type + /// and can't be null + /// must be a generic type public static Type GetGenericDefinition([NotNull] Type type, [NotNull] Type genericType) { if (type == null) diff --git a/Kyoo.Authentication/AuthenticationModule.cs b/Kyoo.Authentication/AuthenticationModule.cs index 7ab35374..a75d3c9a 100644 --- a/Kyoo.Authentication/AuthenticationModule.cs +++ b/Kyoo.Authentication/AuthenticationModule.cs @@ -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 clients = new(); _configuration.GetSection("authentication:clients").Bind(clients); CertificateOption certificateOptions = new(); diff --git a/Kyoo.Authentication/Controllers/Certificates.cs b/Kyoo.Authentication/Controllers/Certificates.cs index 454962d0..498d5ba9 100644 --- a/Kyoo.Authentication/Controllers/Certificates.cs +++ b/Kyoo.Authentication/Controllers/Certificates.cs @@ -27,11 +27,11 @@ namespace Kyoo.Authentication /// /// The identity server that will be modified. /// The certificate options - /// + /// The initial builder to allow chain-calls. 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 /// /// The certificate options /// A valid certificate - 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); } /// @@ -65,13 +65,12 @@ namespace Kyoo.Authentication /// The path of the certificate /// The password of the certificate /// The loaded certificate - 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); } /// @@ -80,7 +79,7 @@ namespace Kyoo.Authentication /// The path of the output file /// The password of the new certificate /// The generated certificate - private static X509Certificate2 GenerateCertificate(string file, string password) + private static X509Certificate2 _GenerateCertificate(string file, string password) { SecureRandom random = new(); diff --git a/Kyoo.Authentication/Models/IdentityContext.cs b/Kyoo.Authentication/Models/IdentityContext.cs index 5422f5e3..51659965 100644 --- a/Kyoo.Authentication/Models/IdentityContext.cs +++ b/Kyoo.Authentication/Models/IdentityContext.cs @@ -24,7 +24,7 @@ namespace Kyoo.Authentication } /// - /// The list of officially supported clients. + /// Get the list of officially supported clients. /// /// /// 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" } } }; diff --git a/Kyoo.Authentication/Views/AccountApi.cs b/Kyoo.Authentication/Views/AccountApi.cs index 67b34240..513cb450 100644 --- a/Kyoo.Authentication/Views/AccountApi.cs +++ b/Kyoo.Authentication/Views/AccountApi.cs @@ -89,7 +89,7 @@ namespace Kyoo.Authentication.Views /// /// Should the user stay logged /// Authentication properties based on a stay login - 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(); } diff --git a/Kyoo.Core/Controllers/ConfigurationManager.cs b/Kyoo.Core/Controllers/ConfigurationManager.cs index 8a5c50d1..93a64c75 100644 --- a/Kyoo.Core/Controllers/ConfigurationManager.cs +++ b/Kyoo.Core/Controllers/ConfigurationManager.cs @@ -44,6 +44,25 @@ namespace Kyoo.Core.Controllers _references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase); } + /// + /// Transform the configuration section in nested expando objects. + /// + /// The section to convert + /// The converted section + 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; + } + /// public void AddTyped(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; } - - /// - /// Transform the configuration section in nested expando objects. - /// - /// The section to convert - /// The converted section - 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; - } } } diff --git a/Kyoo.Core/Controllers/FileSystems/FileSystemComposite.cs b/Kyoo.Core/Controllers/FileSystems/FileSystemComposite.cs index 3b5578ac..97ec0bdd 100644 --- a/Kyoo.Core/Controllers/FileSystems/FileSystemComposite.cs +++ b/Kyoo.Core/Controllers/FileSystems/FileSystemComposite.cs @@ -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, 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(); diff --git a/Kyoo.Core/Controllers/FileSystems/HttpFileSystem.cs b/Kyoo.Core/Controllers/FileSystems/HttpFileSystem.cs index d4df77dc..6712b6e5 100644 --- a/Kyoo.Core/Controllers/FileSystems/HttpFileSystem.cs +++ b/Kyoo.Core/Controllers/FileSystems/HttpFileSystem.cs @@ -91,48 +91,48 @@ namespace Kyoo.Core.Controllers { throw new NotSupportedException("Extras can not be stored inside an http filesystem."); } - } - - /// - /// An to proxy an http request. - /// - // TODO remove this suppress message once the class has been implemented. - [SuppressMessage("ReSharper", "NotAccessedField.Local")] - public class HttpForwardResult : IActionResult - { - /// - /// The path of the request to forward. - /// - private readonly Uri _path; /// - /// Should the proxied result support ranges requests? + /// An to proxy an http request. /// - private readonly bool _rangeSupport; - - /// - /// If not null, override the content type of the resulting request. - /// - private readonly string _type; - - /// - /// Create a new . - /// - /// The path of the request to forward. - /// Should the proxied result support ranges requests? - /// If not null, override the content type of the resulting request. - 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; - } + /// + /// The path of the request to forward. + /// + private readonly Uri _path; - /// - 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(); + /// + /// Should the proxied result support ranges requests? + /// + private readonly bool _rangeSupport; + + /// + /// If not null, override the content type of the resulting request. + /// + private readonly string _type; + + /// + /// Create a new . + /// + /// The path of the request to forward. + /// Should the proxied result support ranges requests? + /// If not null, override the content type of the resulting request. + public HttpForwardResult(Uri path, bool rangeSupport, string type = null) + { + _path = path; + _rangeSupport = rangeSupport; + _type = type; + } + + /// + 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(); + } } } } diff --git a/Kyoo.Core/Controllers/LibraryManager.cs b/Kyoo.Core/Controllers/LibraryManager.cs index 46ed2d44..4ec51117 100644 --- a/Kyoo.Core/Controllers/LibraryManager.cs +++ b/Kyoo.Core/Controllers/LibraryManager.cs @@ -226,6 +226,8 @@ namespace Kyoo.Core.Controllers /// [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) diff --git a/Kyoo.Core/Controllers/PluginManager.cs b/Kyoo.Core/Controllers/PluginManager.cs index fca45aa3..49808c48 100644 --- a/Kyoo.Core/Controllers/PluginManager.cs +++ b/Kyoo.Core/Controllers/PluginManager.cs @@ -76,7 +76,7 @@ namespace Kyoo.Core.Controllers /// /// The path of the dll /// The list of dlls in hte assembly - 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()) diff --git a/Kyoo.Core/Controllers/ProviderComposite.cs b/Kyoo.Core/Controllers/ProviderComposite.cs index 58b018f1..9d541e2e 100644 --- a/Kyoo.Core/Controllers/ProviderComposite.cs +++ b/Kyoo.Core/Controllers/ProviderComposite.cs @@ -19,16 +19,16 @@ namespace Kyoo.Core.Controllers /// private readonly ICollection _providers; - /// - /// The list of selected providers. If no provider has been selected, this is null. - /// - private ICollection _selectedProviders; - /// /// The logger used to print errors. /// private readonly ILogger _logger; + /// + /// The list of selected providers. If no provider has been selected, this is null. + /// + private ICollection _selectedProviders; + /// /// Create a new with a list of available providers. /// diff --git a/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs b/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs index b09abc10..0d780ea9 100644 --- a/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs @@ -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); } /// @@ -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 /// /// The resource to fix. /// The parameter is returned. - private async Task ValidateTracks(Episode resource) + private async Task _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; } diff --git a/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs b/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs index 8528b140..f9b20b19 100644 --- a/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs @@ -111,7 +111,7 @@ namespace Kyoo.Core.Controllers /// /// Only items that are part of a library that match this predicate will be returned. /// A queryable containing items that are part of a library matching the selector. - private IQueryable LibraryRelatedQuery(Expression> selector) + private IQueryable _LibraryRelatedQuery(Expression> selector) => _database.Libraries .Where(selector) .SelectMany(x => x.Shows) @@ -128,7 +128,7 @@ namespace Kyoo.Core.Controllers Sort sort = default, Pagination limit = default) { - ICollection items = await ApplyFilters(LibraryRelatedQuery(x => x.ID == id), + ICollection items = await ApplyFilters(_LibraryRelatedQuery(x => x.ID == id), where, sort, limit); @@ -143,7 +143,7 @@ namespace Kyoo.Core.Controllers Sort sort = default, Pagination limit = default) { - ICollection items = await ApplyFilters(LibraryRelatedQuery(x => x.Slug == slug), + ICollection items = await ApplyFilters(_LibraryRelatedQuery(x => x.Slug == slug), where, sort, limit); diff --git a/Kyoo.Core/Controllers/Repositories/LocalRepository.cs b/Kyoo.Core/Controllers/Repositories/LocalRepository.cs index b2dcfa62..f3e52ccb 100644 --- a/Kyoo.Core/Controllers/Repositories/LocalRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/LocalRepository.cs @@ -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 /// + /// The base query to filter. /// A function to asynchronously get a resource from the database using it's ID. /// The default sort order of this resource's type. - /// The base query to filter. /// An expression to filter based on arbitrary conditions /// The sort settings (sort order & sort by) /// Pagination information (where to start and how many to get) + /// The type of items to query. /// The filtered query protected async Task> ApplyFilters(IQueryable query, Func> get, @@ -252,6 +253,7 @@ namespace Kyoo.Core.Controllers /// /// A boolean to indicate if all values of resource should be discarded or not. /// + /// A representing the asynchronous operation. protected virtual Task EditRelations(T resource, T changed, bool resetOld) { return Validate(resource); @@ -265,6 +267,7 @@ namespace Kyoo.Core.Controllers /// /// You can throw this if the resource is illegal and should not be saved. /// + /// A representing the asynchronous operation. protected virtual Task Validate(T resource) { if (typeof(T).GetProperty(nameof(resource.Slug))!.GetCustomAttribute() != null) diff --git a/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs b/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs index bfc1ae16..e0d30763 100644 --- a/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs @@ -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; } diff --git a/Kyoo.Core/Controllers/Repositories/ShowRepository.cs b/Kyoo.Core/Controllers/Repositories/ShowRepository.cs index 1d0a3a5b..b3df89b3 100644 --- a/Kyoo.Core/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/ShowRepository.cs @@ -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 diff --git a/Kyoo.Core/Controllers/Repositories/TrackRepository.cs b/Kyoo.Core/Controllers/Repositories/TrackRepository.cs index 769e5fc3..c53710c0 100644 --- a/Kyoo.Core/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/TrackRepository.cs @@ -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})."); + } } } diff --git a/Kyoo.Core/Controllers/TaskManager.cs b/Kyoo.Core/Controllers/TaskManager.cs index 5e9f688e..49fbd45d 100644 --- a/Kyoo.Core/Controllers/TaskManager.cs +++ b/Kyoo.Core/Controllers/TaskManager.cs @@ -90,16 +90,16 @@ namespace Kyoo.Core.Controllers /// private readonly Queue _queuedTasks = new(); - /// - /// The currently running task. - /// - private (TaskMetadataAttribute, ITask)? _runningTask; - /// /// The cancellation token used to cancel the running task when the runner should shutdown. /// private readonly CancellationTokenSource _taskToken = new(); + /// + /// The currently running task. + /// + private (TaskMetadataAttribute, ITask)? _runningTask; + /// /// Create a new . /// @@ -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 /// /// Start the runner in another thread. /// Indicates that the start process has been aborted. + /// A representing the asynchronous operation. 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. /// /// A token to stop the runner + /// A representing the asynchronous operation. 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 /// /// Start tasks that are scheduled for start. /// - private void QueueScheduledTasks() + private void _QueueScheduledTasks() { IEnumerable 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); } /// @@ -300,7 +304,7 @@ namespace Kyoo.Core.Controllers /// /// The slug of the task /// The next date. - private DateTime GetNextTaskDate(string taskSlug) + private DateTime _GetNextTaskDate(string taskSlug) { if (_options.CurrentValue.Scheduled.TryGetValue(taskSlug, out TimeSpan delay)) return DateTime.Now + delay; diff --git a/Kyoo.Core/Controllers/Transcoder.cs b/Kyoo.Core/Controllers/Transcoder.cs index 176e429c..ca5e1e9d 100644 --- a/Kyoo.Core/Controllers/Transcoder.cs +++ b/Kyoo.Core/Controllers/Transcoder.cs @@ -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; } diff --git a/Kyoo.Core/Models/FileExtensions.cs b/Kyoo.Core/Models/FileExtensions.cs index e481406b..cb13a585 100644 --- a/Kyoo.Core/Models/FileExtensions.cs +++ b/Kyoo.Core/Models/FileExtensions.cs @@ -54,8 +54,8 @@ namespace Kyoo.Core.Models.Watch /// public static readonly ImmutableDictionary SubtitleExtensions = new Dictionary { - {".ass", "ass"}, - {".str", "subrip"} + { ".ass", "ass" }, + { ".str", "subrip" } }.ToImmutableDictionary(); /// diff --git a/Kyoo.Core/Models/Options/BasicOptions.cs b/Kyoo.Core/Models/Options/BasicOptions.cs index 4355f12e..7a116128 100644 --- a/Kyoo.Core/Models/Options/BasicOptions.cs +++ b/Kyoo.Core/Models/Options/BasicOptions.cs @@ -1,5 +1,5 @@ -using Kyoo.Abstractions.Models; using System; +using Kyoo.Abstractions.Models; namespace Kyoo.Core.Models.Options { diff --git a/Kyoo.Core/Models/Stream.cs b/Kyoo.Core/Models/Stream.cs index 00deaccc..40f9ca44 100644 --- a/Kyoo.Core/Models/Stream.cs +++ b/Kyoo.Core/Models/Stream.cs @@ -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. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - public class Stream + public struct Stream { /// /// The title of the stream. /// - public string Title { get; set; } + public string Title; /// /// The language of this stream (as a ISO-639-2 language code) /// - public string Language { get; set; } + public string Language; /// /// The codec of this stream. /// - public string Codec { get; set; } + public string Codec; /// /// Is this stream the default one of it's type? @@ -38,12 +39,12 @@ namespace Kyoo.Core.Models.Watch /// /// The path of this track. /// - [SerializeIgnore] public string Path { get; set; } + public string Path; /// /// The type of this stream. /// - [SerializeIgnore] public StreamType Type { get; set; } + public StreamType Type; /// /// Create a track from this stream. diff --git a/Kyoo.Core/PluginsStartup.cs b/Kyoo.Core/PluginsStartup.cs index 1e7d62b5..fb1d9e7f 100644 --- a/Kyoo.Core/PluginsStartup.cs +++ b/Kyoo.Core/PluginsStartup.cs @@ -61,6 +61,27 @@ namespace Kyoo.Core ); } + /// + /// Create a new from a webhost. + /// This is meant to be used from . + /// + /// The context of the web host. + /// + /// The logger factory used to log while the application is setting itself up. + /// + /// A new . + 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()), + logger.CreateLogger() + ); + return new PluginsStartup(plugins, host.Configuration); + } + /// /// Configure the services context via the . /// @@ -126,27 +147,6 @@ namespace Kyoo.Core config.Register(path, type); } - /// - /// Create a new from a webhost. - /// This is meant to be used from . - /// - /// The context of the web host. - /// - /// The logger factory used to log while the application is setting itself up. - /// - /// A new . - 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()), - logger.CreateLogger() - ); - return new PluginsStartup(plugins, host.Configuration); - } - /// /// A simple host service provider used to activate plugins instance. /// The same services as a generic host are available and an has been added. diff --git a/Kyoo.Core/Tasks/Crawler.cs b/Kyoo.Core/Tasks/Crawler.cs index 41711abf..a26776e9 100644 --- a/Kyoo.Core/Tasks/Crawler.cs +++ b/Kyoo.Core/Tasks/Crawler.cs @@ -89,9 +89,9 @@ namespace Kyoo.Core.Tasks IProgress reporter = new Progress(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 episodes, IEnumerable tracks, IProgress progress, @@ -131,7 +131,7 @@ namespace Kyoo.Core.Tasks IProgress reporter = new Progress(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(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) diff --git a/Kyoo.Core/Views/CollectionApi.cs b/Kyoo.Core/Views/CollectionApi.cs index 77de65e9..7bb125c1 100644 --- a/Kyoo.Core/Views/CollectionApi.cs +++ b/Kyoo.Core/Views/CollectionApi.cs @@ -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 diff --git a/Kyoo.Core/Views/EpisodeApi.cs b/Kyoo.Core/Views/EpisodeApi.cs index 941a9591..a83a134d 100644 --- a/Kyoo.Core/Views/EpisodeApi.cs +++ b/Kyoo.Core/Views/EpisodeApi.cs @@ -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 diff --git a/Kyoo.Core/Views/Helper/ApiHelper.cs b/Kyoo.Core/Views/Helper/ApiHelper.cs index f471cee8..cce54166 100644 --- a/Kyoo.Core/Views/Helper/ApiHelper.cs +++ b/Kyoo.Core/Views/Helper/ApiHelper.cs @@ -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>(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); diff --git a/Kyoo.Core/Views/Helper/CrudApi.cs b/Kyoo.Core/Views/Helper/CrudApi.cs index 4838be44..385ac276 100644 --- a/Kyoo.Core/Views/Helper/CrudApi.cs +++ b/Kyoo.Core/Views/Helper/CrudApi.cs @@ -12,7 +12,8 @@ namespace Kyoo.Core.Api { [ApiController] [ResourceView] - public class CrudApi : ControllerBase where T : class, IResource + public class CrudApi : ControllerBase + where T : class, IResource { private readonly IRepository _repository; protected readonly Uri BaseURL; diff --git a/Kyoo.Core/Views/Helper/ResourceViewAttribute.cs b/Kyoo.Core/Views/Helper/ResourceViewAttribute.cs index 6a89c8d8..60dc13a2 100644 --- a/Kyoo.Core/Views/Helper/ResourceViewAttribute.cs +++ b/Kyoo.Core/Views/Helper/ResourceViewAttribute.cs @@ -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; diff --git a/Kyoo.Core/Views/Helper/JsonSerializer.cs b/Kyoo.Core/Views/Helper/Serializers/JsonPropertyIgnorer.cs similarity index 50% rename from Kyoo.Core/Views/Helper/JsonSerializer.cs rename to Kyoo.Core/Views/Helper/Serializers/JsonPropertyIgnorer.cs index e35551fa..fe8a13e9 100644 --- a/Kyoo.Core/Views/Helper/JsonSerializer.cs +++ b/Kyoo.Core/Views/Helper/Serializers/JsonPropertyIgnorer.cs @@ -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() != null) @@ -70,87 +72,4 @@ namespace Kyoo.Core.Api return contract; } } - - public class PeopleRoleConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, PeopleRole value, JsonSerializer serializer) - { - ICollection oldPeople = value.Show?.People; - ICollection 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, @"(? - { - 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. - } - } } diff --git a/Kyoo.Core/Views/Helper/Serializers/PeopleRoleConverter.cs b/Kyoo.Core/Views/Helper/Serializers/PeopleRoleConverter.cs new file mode 100644 index 00000000..c376f4c4 --- /dev/null +++ b/Kyoo.Core/Views/Helper/Serializers/PeopleRoleConverter.cs @@ -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 + { + public override void WriteJson(JsonWriter writer, PeopleRole value, JsonSerializer serializer) + { + ICollection oldPeople = value.Show?.People; + ICollection 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(); + } + } +} diff --git a/Kyoo.Core/Views/Helper/Serializers/SerializeAsProvider.cs b/Kyoo.Core/Views/Helper/Serializers/SerializeAsProvider.cs new file mode 100644 index 00000000..479ea8ac --- /dev/null +++ b/Kyoo.Core/Views/Helper/Serializers/SerializeAsProvider.cs @@ -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, @"(? + { + 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. + } + } +} diff --git a/Kyoo.Core/Views/LibraryApi.cs b/Kyoo.Core/Views/LibraryApi.cs index 4289a512..62c3225a 100644 --- a/Kyoo.Core/Views/LibraryApi.cs +++ b/Kyoo.Core/Views/LibraryApi.cs @@ -32,9 +32,11 @@ namespace Kyoo.Core.Api { ActionResult result = await base.Create(resource); if (result.Value != null) + { _taskManager.StartTask("scan", new Progress(), new Dictionary { { "slug", result.Value.Slug } }); + } return result; } diff --git a/Kyoo.Core/Views/SeasonApi.cs b/Kyoo.Core/Views/SeasonApi.cs index d0ab4f16..370c3e52 100644 --- a/Kyoo.Core/Views/SeasonApi.cs +++ b/Kyoo.Core/Views/SeasonApi.cs @@ -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 diff --git a/Kyoo.Core/Views/ShowApi.cs b/Kyoo.Core/Views/ShowApi.cs index 27323bd1..7209010a 100644 --- a/Kyoo.Core/Views/ShowApi.cs +++ b/Kyoo.Core/Views/ShowApi.cs @@ -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 diff --git a/Kyoo.Core/Views/SubtitleApi.cs b/Kyoo.Core/Views/SubtitleApi.cs index 6c1efdd2..2f7a9ab3 100644 --- a/Kyoo.Core/Views/SubtitleApi.cs +++ b/Kyoo.Core/Views/SubtitleApi.cs @@ -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 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 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 processedBlock = ConvertBlock(lines); - foreach (string t in processedBlock) - await writer.WriteLineAsync(t); - lines.Clear(); + if (line == string.Empty) + { + lines.Add(string.Empty); + IEnumerable 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 ConvertBlock(IList lines) - { - if (lines.Count < 3) - return lines; - lines[1] = lines[1].Replace(',', '.'); - if (lines[2].Length > 5) + private static IEnumerable _ConvertBlock(IList 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; } } } diff --git a/Kyoo.Core/Views/VideoApi.cs b/Kyoo.Core/Views/VideoApi.cs index f99f3c2d..bd4d7ca0 100644 --- a/Kyoo.Core/Views/VideoApi.cs +++ b/Kyoo.Core/Views/VideoApi.cs @@ -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; diff --git a/Kyoo.Database/DatabaseContext.cs b/Kyoo.Database/DatabaseContext.cs index 6b00b963..c70d41a7 100644 --- a/Kyoo.Database/DatabaseContext.cs +++ b/Kyoo.Database/DatabaseContext.cs @@ -114,6 +114,7 @@ namespace Kyoo.Database /// The ID of the second resource. /// The first resource type of the relation. It is the owner of the second /// The second resource type of the relation. It is the contained resource. + /// A representing the asynchronous operation. public async Task AddLinks(int first, int second) where T1 : class, IResource where T2 : class, IResource @@ -433,7 +434,7 @@ namespace Kyoo.Database /// A duplicated item has been found. /// The number of state entries written to the database. public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, - CancellationToken cancellationToken = new()) + CancellationToken cancellationToken = default) { try { @@ -454,7 +455,7 @@ namespace Kyoo.Database /// A to observe while waiting for the task to complete /// A duplicated item has been found. /// The number of state entries written to the database. - public override async Task SaveChangesAsync(CancellationToken cancellationToken = new()) + public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) { try { @@ -538,7 +539,7 @@ namespace Kyoo.Database /// /// Delete every changes that are on this context. /// - private void DiscardChanges() + public void DiscardChanges() { foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Detached)) { diff --git a/Kyoo.SqLite/SqLiteContext.cs b/Kyoo.SqLite/SqLiteContext.cs index 32379ed8..d8b32a99 100644 --- a/Kyoo.SqLite/SqLiteContext.cs +++ b/Kyoo.SqLite/SqLiteContext.cs @@ -166,8 +166,8 @@ namespace Kyoo.SqLite /// 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 */ }; } /// diff --git a/Kyoo.TheTvdb/PluginTvdb.cs b/Kyoo.TheTvdb/PluginTvdb.cs index 341f4a5a..6dcbe7bd 100644 --- a/Kyoo.TheTvdb/PluginTvdb.cs +++ b/Kyoo.TheTvdb/PluginTvdb.cs @@ -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 "); + } } /// diff --git a/Kyoo.ruleset b/Kyoo.ruleset index 4d53cf94..89b71c1b 100644 --- a/Kyoo.ruleset +++ b/Kyoo.ruleset @@ -11,6 +11,7 @@ + @@ -40,5 +41,6 @@ + diff --git a/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs index 7be629a3..75d45e9c 100644 --- a/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs @@ -44,7 +44,7 @@ namespace Kyoo.Tests.Database public async Task CreateWithEmptySlugTest() { Collection collection = TestSample.GetNew(); - collection.Slug = ""; + collection.Slug = string.Empty; await Assert.ThrowsAsync(() => _repository.Create(collection)); } diff --git a/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs index 19ac1e6f..f2da2464 100644 --- a/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs @@ -53,7 +53,7 @@ namespace Kyoo.Tests.Database public async Task CreateWithEmptySlugTest() { Library library = TestSample.GetNew(); - library.Slug = ""; + library.Slug = string.Empty; await Assert.ThrowsAsync(() => _repository.Create(library)); } diff --git a/tests/Kyoo.Tests/Identifier/ProviderTests.cs b/tests/Kyoo.Tests/Identifier/ProviderTests.cs index 411e962f..2b4498b0 100644 --- a/tests/Kyoo.Tests/Identifier/ProviderTests.cs +++ b/tests/Kyoo.Tests/Identifier/ProviderTests.cs @@ -85,7 +85,7 @@ namespace Kyoo.Tests.Identifier Genres = new[] { new Genre("genre") } }; Mock 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 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 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(); AProviderComposite provider = new ProviderComposite(new[]