diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 8983bf4e..b2300460 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -45,7 +45,6 @@ true - CS1591;SA1600;SA1601 true $(MSBuildThisFileDirectory)../Kyoo.ruleset diff --git a/src/Kyoo.Abstractions/Models/ConfigurationReference.cs b/src/Kyoo.Abstractions/Models/ConfigurationReference.cs index 2272612f..635bbb97 100644 --- a/src/Kyoo.Abstractions/Models/ConfigurationReference.cs +++ b/src/Kyoo.Abstractions/Models/ConfigurationReference.cs @@ -105,9 +105,17 @@ namespace Kyoo.Abstractions.Models return CreateReference(path, typeof(T)); } + /// + /// Return a meaning that the given path is of any type. + /// It means that the type can't be edited. + /// + /// + /// The path that will be untyped (separated by ':' or "__". If empty, it will start at root). + /// + /// A configuration reference representing a path of any type. public static ConfigurationReference CreateUntyped(string path) { - return new(path, null); + return new ConfigurationReference(path, null); } } } diff --git a/src/Kyoo.Abstractions/Utility/Utility.cs b/src/Kyoo.Abstractions/Utility/Utility.cs index 5c0dcc80..04a1f948 100644 --- a/src/Kyoo.Abstractions/Utility/Utility.cs +++ b/src/Kyoo.Abstractions/Utility/Utility.cs @@ -425,6 +425,11 @@ namespace Kyoo.Utils return (T)method.MakeGenericMethod(types).Invoke(instance, args.ToArray()); } + /// + /// Convert a dictionary to a query string. + /// + /// The list of query parameters. + /// A valid query string with all items in the dictionary. public static string ToQueryString(this Dictionary query) { if (!query.Any()) @@ -432,6 +437,11 @@ namespace Kyoo.Utils return "?" + string.Join('&', query.Select(x => $"{x.Key}={x.Value}")); } + /// + /// Rethrow the exception without modifying the stack trace. + /// This is similar to the rethrow; code but is useful when the exception is not in a catch block. + /// + /// The exception to rethrow. [System.Diagnostics.CodeAnalysis.DoesNotReturn] public static void ReThrow([NotNull] this Exception ex) { diff --git a/src/Kyoo.Authentication/Controllers/PasswordUtils.cs b/src/Kyoo.Authentication/Controllers/PasswordUtils.cs index fb2982ba..5c071a4e 100644 --- a/src/Kyoo.Authentication/Controllers/PasswordUtils.cs +++ b/src/Kyoo.Authentication/Controllers/PasswordUtils.cs @@ -23,6 +23,9 @@ using IdentityModel; namespace Kyoo.Authentication { + /// + /// Some functions to handle password management. + /// public static class PasswordUtils { /// diff --git a/src/Kyoo.Core/Controllers/ConfigurationManager.cs b/src/Kyoo.Core/Controllers/ConfigurationManager.cs index 760e3a95..10e0843b 100644 --- a/src/Kyoo.Core/Controllers/ConfigurationManager.cs +++ b/src/Kyoo.Core/Controllers/ConfigurationManager.cs @@ -32,6 +32,11 @@ using Newtonsoft.Json.Linq; namespace Kyoo.Core.Controllers { + /// + /// A class to ease configuration management. This work WITH Microsoft's package, you can still use IOptions patterns + /// to access your options, this manager ease dynamic work and editing. + /// It works with . + /// public class ConfigurationManager : IConfigurationManager { /// diff --git a/src/Kyoo.Core/Controllers/FileSystems/LocalFileSystem.cs b/src/Kyoo.Core/Controllers/FileSystems/LocalFileSystem.cs index 74d3a7c6..44fe5154 100644 --- a/src/Kyoo.Core/Controllers/FileSystems/LocalFileSystem.cs +++ b/src/Kyoo.Core/Controllers/FileSystems/LocalFileSystem.cs @@ -165,11 +165,13 @@ namespace Kyoo.Core.Controllers }); } + /// public Task> ExtractInfos(Episode episode, bool reExtract) { return _transcoder.ExtractInfos(episode, reExtract); } + /// public IActionResult Transmux(Episode episode) { return _transcoder.Transmux(episode); diff --git a/src/Kyoo.Core/Controllers/LibraryManager.cs b/src/Kyoo.Core/Controllers/LibraryManager.cs index 5243ee36..d07a5da3 100644 --- a/src/Kyoo.Core/Controllers/LibraryManager.cs +++ b/src/Kyoo.Core/Controllers/LibraryManager.cs @@ -29,6 +29,9 @@ using Kyoo.Utils; namespace Kyoo.Core.Controllers { + /// + /// An class to interact with the database. Every repository is mapped through here. + /// public class LibraryManager : ILibraryManager { /// diff --git a/src/Kyoo.Core/Controllers/PassthroughPermissionValidator.cs b/src/Kyoo.Core/Controllers/PassthroughPermissionValidator.cs index 97b73ec2..2c5ae6aa 100644 --- a/src/Kyoo.Core/Controllers/PassthroughPermissionValidator.cs +++ b/src/Kyoo.Core/Controllers/PassthroughPermissionValidator.cs @@ -29,6 +29,10 @@ namespace Kyoo.Core.Controllers /// public class PassthroughPermissionValidator : IPermissionValidator { + /// + /// Create a new . + /// + /// The logger used to warn that no real permission validator exists. [SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor", Justification = "ILogger should include the typeparam for context.")] public PassthroughPermissionValidator(ILogger logger) diff --git a/src/Kyoo.Core/Helper.cs b/src/Kyoo.Core/Helper.cs index 42f767b7..ad45835d 100644 --- a/src/Kyoo.Core/Helper.cs +++ b/src/Kyoo.Core/Helper.cs @@ -22,6 +22,9 @@ using Newtonsoft.Json; namespace Kyoo.Core { + /// + /// A class containing helper methods. + /// public static class Helper { /// diff --git a/src/Kyoo.Core/Views/Helper/ApiHelper.cs b/src/Kyoo.Core/Views/Helper/ApiHelper.cs index 7365c5a7..a1e2c617 100644 --- a/src/Kyoo.Core/Views/Helper/ApiHelper.cs +++ b/src/Kyoo.Core/Views/Helper/ApiHelper.cs @@ -22,24 +22,53 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; +using JetBrains.Annotations; using Kyoo.Abstractions.Models; namespace Kyoo.Core.Api { + /// + /// A static class containing methods to parse the where query string. + /// public static class ApiHelper { - public static Expression StringCompatibleExpression(Func operand, - Expression left, - Expression right) + /// + /// Make an expression (like + /// + /// compatible with strings). If the expressions are not strings, the given is + /// constructed but if the expressions are strings, this method make the compatible with + /// strings. + /// + /// + /// The expression to make compatible. It should be something like + /// or + /// . + /// + /// The first parameter to compare. + /// The second parameter to compare. + /// A comparison expression compatible with strings + public static BinaryExpression StringCompatibleExpression( + [NotNull] Func operand, + [NotNull] Expression left, + [NotNull] Expression right) { - if (left is MemberExpression member && ((PropertyInfo)member.Member).PropertyType == typeof(string)) - { - MethodCallExpression call = Expression.Call(typeof(string), "Compare", null, left, right); - return operand(call, Expression.Constant(0)); - } - return operand(left, right); + if (left is not MemberExpression member || ((PropertyInfo)member.Member).PropertyType != typeof(string)) + return operand(left, right); + MethodCallExpression call = Expression.Call(typeof(string), "Compare", null, left, right); + return operand(call, Expression.Constant(0)); } + /// + /// Parse a where query for the given . Items can be filtered by any property + /// of the given type. + /// + /// The list of filters. + /// + /// A custom expression to initially filter a collection. It will be combined with the parsed expression. + /// + /// The type to create filters for. + /// A filter is invalid. + /// An expression representing the filters that can be used anywhere or compiled public static Expression> ParseWhere(Dictionary where, Expression> defaultWhere = null) { @@ -96,18 +125,17 @@ namespace Kyoo.Core.Api "not" when valueExpr == null => _ResourceEqual(propertyExpr, value, true), "eq" => Expression.Equal(propertyExpr, valueExpr), - "not" => Expression.NotEqual(propertyExpr, valueExpr!), - "lt" => StringCompatibleExpression(Expression.LessThan, propertyExpr, valueExpr), - "lte" => StringCompatibleExpression(Expression.LessThanOrEqual, propertyExpr, valueExpr), - "gt" => StringCompatibleExpression(Expression.GreaterThan, propertyExpr, valueExpr), - "gte" => StringCompatibleExpression(Expression.GreaterThanOrEqual, propertyExpr, valueExpr), + "not" => Expression.NotEqual(propertyExpr, valueExpr), + "lt" => StringCompatibleExpression(Expression.LessThan, propertyExpr, valueExpr!), + "lte" => StringCompatibleExpression(Expression.LessThanOrEqual, propertyExpr, valueExpr!), + "gt" => StringCompatibleExpression(Expression.GreaterThan, propertyExpr, valueExpr!), + "gte" => StringCompatibleExpression(Expression.GreaterThanOrEqual, propertyExpr, valueExpr!), _ => throw new ArgumentException($"Invalid operand: {operand}") }; - if (expression != null) - expression = Expression.AndAlso(expression, condition); - else - expression = condition; + expression = expression != null + ? Expression.AndAlso(expression, condition) + : condition; } return Expression.Lambda>(expression!, param); diff --git a/src/Kyoo.Core/Views/Helper/BaseApi.cs b/src/Kyoo.Core/Views/Helper/BaseApi.cs index 315e92fd..0629faa5 100644 --- a/src/Kyoo.Core/Views/Helper/BaseApi.cs +++ b/src/Kyoo.Core/Views/Helper/BaseApi.cs @@ -27,7 +27,10 @@ using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Core.Api { - public class BaseApi : ControllerBase + /// + /// A common API containing custom methods to help inheritors. + /// + public abstract class BaseApi : ControllerBase { /// /// Construct and return a page from an api. diff --git a/src/Kyoo.Core/Views/Helper/ResourceViewAttribute.cs b/src/Kyoo.Core/Views/Helper/ResourceViewAttribute.cs index abf215b7..6cc70187 100644 --- a/src/Kyoo.Core/Views/Helper/ResourceViewAttribute.cs +++ b/src/Kyoo.Core/Views/Helper/ResourceViewAttribute.cs @@ -32,8 +32,13 @@ using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Core.Api { + /// + /// An attribute to put on most controllers. It handle fields loading (only retuning fields requested and if they + /// are requested, load them) and help for the where query parameter. + /// public class ResourceViewAttribute : ActionFilterAttribute { + /// public override void OnActionExecuting(ActionExecutingContext context) { if (context.ActionArguments.TryGetValue("where", out object dic) && dic is Dictionary where) @@ -92,6 +97,7 @@ namespace Kyoo.Core.Api base.OnActionExecuting(context); } + /// public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { if (context.Result is ObjectResult result) diff --git a/src/Kyoo.Core/Views/Helper/Serializers/PeopleRoleConverter.cs b/src/Kyoo.Core/Views/Helper/Serializers/PeopleRoleConverter.cs index 447efccd..f4db9075 100644 --- a/src/Kyoo.Core/Views/Helper/Serializers/PeopleRoleConverter.cs +++ b/src/Kyoo.Core/Views/Helper/Serializers/PeopleRoleConverter.cs @@ -24,8 +24,13 @@ using Newtonsoft.Json.Linq; namespace Kyoo.Core.Api { + /// + /// A custom role's convertor to inline the person or the show depending on the value of + /// . + /// public class PeopleRoleConverter : JsonConverter { + /// public override void WriteJson(JsonWriter writer, PeopleRole value, JsonSerializer serializer) { ICollection oldPeople = value.Show?.People; @@ -46,6 +51,7 @@ namespace Kyoo.Core.Api value.People.Roles = oldRoles; } + /// public override PeopleRole ReadJson(JsonReader reader, Type objectType, PeopleRole existingValue, diff --git a/src/Kyoo.Core/Views/Watch/VideoApi.cs b/src/Kyoo.Core/Views/Watch/VideoApi.cs index 03d5ed31..40f8c881 100644 --- a/src/Kyoo.Core/Views/Watch/VideoApi.cs +++ b/src/Kyoo.Core/Views/Watch/VideoApi.cs @@ -41,9 +41,21 @@ namespace Kyoo.Core.Api [ApiDefinition("Videos", Group = WatchGroup)] public class VideoApi : Controller { + /// + /// The library manager used to modify or retrieve information in the data store. + /// private readonly ILibraryManager _libraryManager; + + /// + /// The file system used to send video files. + /// private readonly IFileSystem _files; + /// + /// Create a new . + /// + /// The library manager used to retrieve episodes. + /// The file manager used to send video files. public VideoApi(ILibraryManager libraryManager, IFileSystem files) { diff --git a/src/Kyoo.Postgresql/Migrations/20210801171613_Initial.cs b/src/Kyoo.Postgresql/Migrations/20210801171613_Initial.cs index 47e1e42e..0724cfd8 100644 --- a/src/Kyoo.Postgresql/Migrations/20210801171613_Initial.cs +++ b/src/Kyoo.Postgresql/Migrations/20210801171613_Initial.cs @@ -24,8 +24,12 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Postgresql.Migrations { + /// + /// The initial migration that build most of the database. + /// public partial class Initial : Migration { + /// protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AlterDatabase() @@ -783,6 +787,7 @@ namespace Kyoo.Postgresql.Migrations column: "episode_id"); } + /// protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( diff --git a/src/Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs b/src/Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs index c1723610..19cf8ce5 100644 --- a/src/Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs +++ b/src/Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs @@ -20,8 +20,12 @@ using Microsoft.EntityFrameworkCore.Migrations; namespace Kyoo.Postgresql.Migrations { + /// + /// A migration that adds postgres triggers to update slugs. + /// public partial class Triggers : Migration { + /// protected override void Up(MigrationBuilder migrationBuilder) { // language=PostgreSQL @@ -42,7 +46,7 @@ namespace Kyoo.Postgresql.Migrations // language=PostgreSQL migrationBuilder.Sql(@" - CREATE TRIGGER season_slug_trigger BEFORE INSERT OR UPDATE OF season_number, show_id ON seasons + CREATE TRIGGER season_slug_trigger BEFORE INSERT OR UPDATE OF season_number, show_id ON seasons FOR EACH ROW EXECUTE PROCEDURE season_slug_update();"); // language=PostgreSQL @@ -66,7 +70,7 @@ namespace Kyoo.Postgresql.Migrations // language=PostgreSQL migrationBuilder.Sql(@" - CREATE TRIGGER episode_slug_trigger + CREATE TRIGGER episode_slug_trigger BEFORE INSERT OR UPDATE OF absolute_number, episode_number, season_number, show_id ON episodes FOR EACH ROW EXECUTE PROCEDURE episode_slug_update();"); @@ -80,7 +84,7 @@ namespace Kyoo.Postgresql.Migrations UPDATE seasons SET slug = CONCAT(NEW.slug, '-s', season_number) WHERE show_id = NEW.id; UPDATE episodes SET slug = CASE WHEN season_number IS NULL AND episode_number IS NULL THEN NEW.slug - WHEN season_number IS NULL THEN CONCAT(NEW.slug, '-', absolute_number) + WHEN season_number IS NULL THEN CONCAT(NEW.slug, '-', absolute_number) ELSE CONCAT(NEW.slug, '-s', season_number, 'e', episode_number) END WHERE show_id = NEW.id; RETURN NEW; @@ -128,7 +132,7 @@ namespace Kyoo.Postgresql.Migrations BEGIN IF NEW.track_index = 0 THEN NEW.track_index := (SELECT COUNT(*) FROM tracks - WHERE episode_id = NEW.episode_id AND type = NEW.type + WHERE episode_id = NEW.episode_id AND type = NEW.type AND language = NEW.language AND is_forced = NEW.is_forced); END IF; NEW.slug := CONCAT( @@ -149,7 +153,7 @@ namespace Kyoo.Postgresql.Migrations $$;"); // language=PostgreSQL migrationBuilder.Sql(@" - CREATE TRIGGER track_slug_trigger + CREATE TRIGGER track_slug_trigger BEFORE INSERT OR UPDATE OF episode_id, is_forced, language, track_index, type ON tracks FOR EACH ROW EXECUTE PROCEDURE track_slug_update();"); @@ -167,11 +171,12 @@ namespace Kyoo.Postgresql.Migrations INNER JOIN collections AS c ON l.collection_id = c.id WHERE s.id = l.show_id)) UNION ALL - SELECT -c0.id, c0.slug, c0.name AS title, c0.overview, 'unknown'::status AS status, + SELECT -c0.id, c0.slug, c0.name AS title, c0.overview, 'unknown'::status AS status, NULL AS start_air, NULL AS end_air, c0.images, 'collection'::item_type AS type FROM collections AS c0"); } + /// protected override void Down(MigrationBuilder migrationBuilder) { // language=PostgreSQL diff --git a/src/Kyoo.SqLite/Migrations/20210801171534_Initial.cs b/src/Kyoo.SqLite/Migrations/20210801171534_Initial.cs index ee965602..985f0409 100644 --- a/src/Kyoo.SqLite/Migrations/20210801171534_Initial.cs +++ b/src/Kyoo.SqLite/Migrations/20210801171534_Initial.cs @@ -21,8 +21,12 @@ using Microsoft.EntityFrameworkCore.Migrations; namespace Kyoo.SqLite.Migrations { + /// + /// The initial migration that build most of the database. + /// public partial class Initial : Migration { + /// protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( @@ -775,6 +779,7 @@ namespace Kyoo.SqLite.Migrations column: "EpisodeID"); } + /// protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( diff --git a/src/Kyoo.SqLite/Migrations/20210801171544_Triggers.cs b/src/Kyoo.SqLite/Migrations/20210801171544_Triggers.cs index bbb1cc92..10f184c7 100644 --- a/src/Kyoo.SqLite/Migrations/20210801171544_Triggers.cs +++ b/src/Kyoo.SqLite/Migrations/20210801171544_Triggers.cs @@ -20,31 +20,35 @@ using Microsoft.EntityFrameworkCore.Migrations; namespace Kyoo.SqLite.Migrations { + /// + /// A migration that adds sqlite triggers to update slugs. + /// public partial class Triggers : Migration { + /// protected override void Up(MigrationBuilder migrationBuilder) { // language=SQLite migrationBuilder.Sql(@" - CREATE TRIGGER SeasonSlugInsert AFTER INSERT ON Seasons FOR EACH ROW - BEGIN + CREATE TRIGGER SeasonSlugInsert AFTER INSERT ON Seasons FOR EACH ROW + BEGIN UPDATE Seasons SET Slug = (SELECT Slug from Shows WHERE ID = ShowID) || '-s' || SeasonNumber WHERE ID == new.ID; END"); // language=SQLite migrationBuilder.Sql(@" - CREATE TRIGGER SeasonSlugUpdate AFTER UPDATE OF SeasonNumber, ShowID ON Seasons FOR EACH ROW - BEGIN + CREATE TRIGGER SeasonSlugUpdate AFTER UPDATE OF SeasonNumber, ShowID ON Seasons FOR EACH ROW + BEGIN UPDATE Seasons SET Slug = (SELECT Slug from Shows WHERE ID = ShowID) || '-s' || SeasonNumber WHERE ID == new.ID; END"); // language=SQLite migrationBuilder.Sql(@" - CREATE TRIGGER EpisodeSlugInsert AFTER INSERT ON Episodes FOR EACH ROW - BEGIN - UPDATE Episodes - SET Slug = (SELECT Slug from Shows WHERE ID = ShowID) || + CREATE TRIGGER EpisodeSlugInsert AFTER INSERT ON Episodes FOR EACH ROW + BEGIN + UPDATE Episodes + SET Slug = (SELECT Slug from Shows WHERE ID = ShowID) || CASE WHEN SeasonNumber IS NULL AND AbsoluteNumber IS NULL THEN '' WHEN SeasonNumber IS NULL THEN '-' || AbsoluteNumber @@ -54,11 +58,11 @@ namespace Kyoo.SqLite.Migrations END"); // language=SQLite migrationBuilder.Sql(@" - CREATE TRIGGER EpisodeSlugUpdate AFTER UPDATE OF AbsoluteNumber, EpisodeNumber, SeasonNumber, ShowID - ON Episodes FOR EACH ROW - BEGIN - UPDATE Episodes - SET Slug = (SELECT Slug from Shows WHERE ID = ShowID) || + CREATE TRIGGER EpisodeSlugUpdate AFTER UPDATE OF AbsoluteNumber, EpisodeNumber, SeasonNumber, ShowID + ON Episodes FOR EACH ROW + BEGIN + UPDATE Episodes + SET Slug = (SELECT Slug from Shows WHERE ID = ShowID) || CASE WHEN SeasonNumber IS NULL AND AbsoluteNumber IS NULL THEN '' WHEN SeasonNumber IS NULL THEN '-' || AbsoluteNumber @@ -69,7 +73,7 @@ namespace Kyoo.SqLite.Migrations // language=SQLite migrationBuilder.Sql(@" - CREATE TRIGGER TrackSlugInsert + CREATE TRIGGER TrackSlugInsert AFTER INSERT ON Tracks FOR EACH ROW BEGIN @@ -98,7 +102,7 @@ namespace Kyoo.SqLite.Migrations END;"); // language=SQLite migrationBuilder.Sql(@" - CREATE TRIGGER TrackSlugUpdate + CREATE TRIGGER TrackSlugUpdate AFTER UPDATE OF EpisodeID, IsForced, Language, TrackIndex, Type ON Tracks FOR EACH ROW BEGIN @@ -107,7 +111,7 @@ namespace Kyoo.SqLite.Migrations WHERE EpisodeID = new.EpisodeID AND Type = new.Type AND Language = new.Language AND IsForced = new.IsForced ) WHERE ID = new.ID AND TrackIndex = 0; - UPDATE Tracks SET Slug = + UPDATE Tracks SET Slug = (SELECT Slug FROM Episodes WHERE ID = EpisodeID) || '.' || Language || CASE (TrackIndex) @@ -128,7 +132,7 @@ namespace Kyoo.SqLite.Migrations END;"); // language=SQLite migrationBuilder.Sql(@" - CREATE TRIGGER EpisodeUpdateTracksSlug + CREATE TRIGGER EpisodeUpdateTracksSlug AFTER UPDATE OF Slug ON Episodes FOR EACH ROW BEGIN @@ -157,8 +161,8 @@ namespace Kyoo.SqLite.Migrations CREATE TRIGGER ShowSlugUpdate AFTER UPDATE OF Slug ON Shows FOR EACH ROW BEGIN UPDATE Seasons SET Slug = new.Slug || '-s' || SeasonNumber WHERE ShowID = new.ID; - UPDATE Episodes - SET Slug = new.Slug || + UPDATE Episodes + SET Slug = new.Slug || CASE WHEN SeasonNumber IS NULL AND AbsoluteNumber IS NULL THEN '' WHEN SeasonNumber IS NULL THEN '-' || AbsoluteNumber @@ -181,11 +185,12 @@ namespace Kyoo.SqLite.Migrations INNER JOIN Collections AS c ON l.CollectionID = c.ID WHERE s.ID = l.ShowID)) UNION ALL - SELECT -c0.ID, c0.Slug, c0.Name AS Title, c0.Overview, 0 AS Status, + SELECT -c0.ID, c0.Slug, c0.Name AS Title, c0.Overview, 0 AS Status, NULL AS StartAir, NULL AS EndAir, c0.Images, 2 AS Type FROM collections AS c0"); } + /// protected override void Down(MigrationBuilder migrationBuilder) { // language=SQLite