diff --git a/.editorconfig b/.editorconfig index 418a3f49..ad56e82e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,6 +7,7 @@ trim_trailing_whitespace = true insert_final_newline = true indent_style = tab indent_size = tab +smart_tab = true [{*.yaml,*.yml}] indent_style = space diff --git a/Directory.Build.props b/Directory.Build.props index 50209a07..5b03be84 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,12 +6,15 @@ - + + + - + $(MSBuildThisFileDirectory)Kyoo.ruleset + diff --git a/Kyoo.Abstractions/Controllers/ILibraryManager.cs b/Kyoo.Abstractions/Controllers/ILibraryManager.cs index 856becb7..2257af97 100644 --- a/Kyoo.Abstractions/Controllers/ILibraryManager.cs +++ b/Kyoo.Abstractions/Controllers/ILibraryManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Runtime.InteropServices; @@ -26,62 +26,62 @@ namespace Kyoo.Abstractions.Controllers /// The repository that handle libraries. /// ILibraryRepository LibraryRepository { get; } - + /// /// The repository that handle libraries's items (a wrapper arround shows & collections). /// ILibraryItemRepository LibraryItemRepository { get; } - + /// /// The repository that handle collections. /// ICollectionRepository CollectionRepository { get; } - + /// /// The repository that handle shows. /// IShowRepository ShowRepository { get; } - + /// /// The repository that handle seasons. /// ISeasonRepository SeasonRepository { get; } - + /// /// The repository that handle episodes. /// IEpisodeRepository EpisodeRepository { get; } - + /// /// The repository that handle tracks. /// ITrackRepository TrackRepository { get; } - + /// /// The repository that handle people. /// IPeopleRepository PeopleRepository { get; } - + /// /// The repository that handle studios. /// IStudioRepository StudioRepository { get; } - + /// /// The repository that handle genres. /// IGenreRepository GenreRepository { get; } - + /// /// The repository that handle providers. /// IProviderRepository ProviderRepository { get; } - + /// /// The repository that handle users. /// IUserRepository UserRepository { get; } - + /// /// Get the resource by it's ID /// @@ -91,7 +91,7 @@ namespace Kyoo.Abstractions.Controllers /// The resource found [ItemNotNull] Task Get(int id) where T : class, IResource; - + /// /// Get the resource by it's slug /// @@ -101,7 +101,7 @@ namespace Kyoo.Abstractions.Controllers /// The resource found [ItemNotNull] Task Get(string slug) where T : class, IResource; - + /// /// Get the resource by a filter function. /// @@ -121,7 +121,7 @@ namespace Kyoo.Abstractions.Controllers /// The season found [ItemNotNull] Task Get(int showID, int seasonNumber); - + /// /// Get a season from it's show slug and it's seasonNumber /// @@ -131,7 +131,7 @@ namespace Kyoo.Abstractions.Controllers /// The season found [ItemNotNull] Task Get(string showSlug, int seasonNumber); - + /// /// Get a episode from it's showID, it's seasonNumber and it's episode number. /// @@ -142,7 +142,7 @@ namespace Kyoo.Abstractions.Controllers /// The episode found [ItemNotNull] Task Get(int showID, int seasonNumber, int episodeNumber); - + /// /// Get a episode from it's show slug, it's seasonNumber and it's episode number. /// @@ -162,7 +162,7 @@ namespace Kyoo.Abstractions.Controllers /// The resource found [ItemCanBeNull] Task GetOrDefault(int id) where T : class, IResource; - + /// /// Get the resource by it's slug or null if it is not found. /// @@ -171,7 +171,7 @@ namespace Kyoo.Abstractions.Controllers /// The resource found [ItemCanBeNull] Task GetOrDefault(string slug) where T : class, IResource; - + /// /// Get the resource by a filter function or null if it is not found. /// @@ -189,7 +189,7 @@ namespace Kyoo.Abstractions.Controllers /// The season found [ItemCanBeNull] Task GetOrDefault(int showID, int seasonNumber); - + /// /// Get a season from it's show slug and it's seasonNumber or null if it is not found. /// @@ -198,7 +198,7 @@ namespace Kyoo.Abstractions.Controllers /// The season found [ItemCanBeNull] Task GetOrDefault(string showSlug, int seasonNumber); - + /// /// Get a episode from it's showID, it's seasonNumber and it's episode number or null if it is not found. /// @@ -208,7 +208,7 @@ namespace Kyoo.Abstractions.Controllers /// The episode found [ItemCanBeNull] Task GetOrDefault(int showID, int seasonNumber, int episodeNumber); - + /// /// Get a episode from it's show slug, it's seasonNumber and it's episode number or null if it is not found. /// @@ -284,7 +284,7 @@ namespace Kyoo.Abstractions.Controllers /// /// Task Load([NotNull] IResource obj, string memberName, bool force = false); - + /// /// Get items (A wrapper arround shows or collections) from a library. /// @@ -297,7 +297,7 @@ namespace Kyoo.Abstractions.Controllers Expression> where = null, Sort sort = default, Pagination limit = default); - + /// /// Get items (A wrapper arround shows or collections) from a library. /// @@ -311,7 +311,7 @@ namespace Kyoo.Abstractions.Controllers Expression> sort, Pagination limit = default ) => GetItemsFromLibrary(id, where, new Sort(sort), limit); - + /// /// Get items (A wrapper arround shows or collections) from a library. /// @@ -324,7 +324,7 @@ namespace Kyoo.Abstractions.Controllers Expression> where = null, Sort sort = default, Pagination limit = default); - + /// /// Get items (A wrapper arround shows or collections) from a library. /// @@ -338,8 +338,8 @@ namespace Kyoo.Abstractions.Controllers Expression> sort, Pagination limit = default ) => GetItemsFromLibrary(slug, where, new Sort(sort), limit); - - + + /// /// Get people's roles from a show. /// @@ -349,10 +349,10 @@ namespace Kyoo.Abstractions.Controllers /// How many items to return and where to start /// A list of items that match every filters Task> GetPeopleFromShow(int showID, - Expression> where = null, + Expression> where = null, Sort sort = default, Pagination limit = default); - + /// /// Get people's roles from a show. /// @@ -366,7 +366,7 @@ namespace Kyoo.Abstractions.Controllers Expression> sort, Pagination limit = default ) => GetPeopleFromShow(showID, where, new Sort(sort), limit); - + /// /// Get people's roles from a show. /// @@ -376,10 +376,10 @@ namespace Kyoo.Abstractions.Controllers /// How many items to return and where to start /// A list of items that match every filters Task> GetPeopleFromShow(string showSlug, - Expression> where = null, + Expression> where = null, Sort sort = default, Pagination limit = default); - + /// /// Get people's roles from a show. /// @@ -393,8 +393,8 @@ namespace Kyoo.Abstractions.Controllers Expression> sort, Pagination limit = default ) => GetPeopleFromShow(showSlug, where, new Sort(sort), limit); - - + + /// /// Get people's roles from a person. /// @@ -404,10 +404,10 @@ namespace Kyoo.Abstractions.Controllers /// How many items to return and where to start /// A list of items that match every filters Task> GetRolesFromPeople(int id, - Expression> where = null, + Expression> where = null, Sort sort = default, Pagination limit = default); - + /// /// Get people's roles from a person. /// @@ -421,7 +421,7 @@ namespace Kyoo.Abstractions.Controllers Expression> sort, Pagination limit = default ) => GetRolesFromPeople(id, where, new Sort(sort), limit); - + /// /// Get people's roles from a person. /// @@ -431,10 +431,10 @@ namespace Kyoo.Abstractions.Controllers /// How many items to return and where to start /// A list of items that match every filters Task> GetRolesFromPeople(string slug, - Expression> where = null, + Expression> where = null, Sort sort = default, Pagination limit = default); - + /// /// Get people's roles from a person. /// @@ -449,7 +449,7 @@ namespace Kyoo.Abstractions.Controllers Pagination limit = default ) => GetRolesFromPeople(slug, where, new Sort(sort), limit); - + /// /// Setup relations between a show, a library and a collection /// @@ -457,7 +457,7 @@ namespace Kyoo.Abstractions.Controllers /// The library's ID to setup relations with (optional) /// The collection's ID to setup relations with (optional) Task AddShowLink(int showID, int? libraryID, int? collectionID); - + /// /// Setup relations between a show, a library and a collection /// @@ -477,7 +477,7 @@ namespace Kyoo.Abstractions.Controllers Task> GetAll(Expression> where = null, Sort sort = default, Pagination limit = default) where T : class, IResource; - + /// /// Get all resources with filters /// @@ -516,7 +516,7 @@ namespace Kyoo.Abstractions.Controllers /// The type of resource /// The resource registers and completed by database's informations (related items & so on) Task Create([NotNull] T item) where T : class, IResource; - + /// /// Create a new resource if it does not exist already. If it does, the existing value is returned instead. /// @@ -524,7 +524,7 @@ namespace Kyoo.Abstractions.Controllers /// The type of resource /// The newly created item or the existing value if it existed. Task CreateIfNotExists([NotNull] T item) where T : class, IResource; - + /// /// Edit a resource /// @@ -542,7 +542,7 @@ namespace Kyoo.Abstractions.Controllers /// The type of resource to delete /// If the item is not found Task Delete(T item) where T : class, IResource; - + /// /// Delete a resource by it's ID. /// @@ -550,7 +550,7 @@ namespace Kyoo.Abstractions.Controllers /// The type of resource to delete /// If the item is not found Task Delete(int id) where T : class, IResource; - + /// /// Delete a resource by it's slug. /// diff --git a/Kyoo.Abstractions/Controllers/IPlugin.cs b/Kyoo.Abstractions/Controllers/IPlugin.cs index 6fc2cc3c..507fbb39 100644 --- a/Kyoo.Abstractions/Controllers/IPlugin.cs +++ b/Kyoo.Abstractions/Controllers/IPlugin.cs @@ -20,17 +20,17 @@ namespace Kyoo.Abstractions.Controllers /// A slug to identify this plugin in queries. /// string Slug { get; } - + /// /// The name of the plugin /// string Name { get; } - + /// /// The description of this plugin. This will be displayed on the "installed plugins" page. /// string Description { get; } - + /// /// true if the plugin should be enabled, false otherwise. /// If a plugin is not enabled, no configure method will be called. @@ -41,7 +41,7 @@ namespace Kyoo.Abstractions.Controllers /// By default, a plugin is always enabled. This method can be overriden to change this behavior. /// virtual bool Enabled => true; - + /// /// A list of types that will be available via the IOptions interfaces and will be listed inside /// an IConfiguration. @@ -64,7 +64,7 @@ namespace Kyoo.Abstractions.Controllers /// virtual IEnumerable ConfigureSteps => ArraySegment.Empty; - /// + /// /// A configure method that will be run on plugin's startup. /// /// The autofac service container to register services. @@ -72,7 +72,7 @@ namespace Kyoo.Abstractions.Controllers { // Skipped } - + /// /// A configure method that will be run on plugin's startup. /// This is available for libraries that build upon a , for more precise @@ -94,4 +94,4 @@ namespace Kyoo.Abstractions.Controllers // Skipped } } -} \ No newline at end of file +} diff --git a/Kyoo.Abstractions/Controllers/IPluginManager.cs b/Kyoo.Abstractions/Controllers/IPluginManager.cs index 795339a4..c9530e02 100644 --- a/Kyoo.Abstractions/Controllers/IPluginManager.cs +++ b/Kyoo.Abstractions/Controllers/IPluginManager.cs @@ -17,14 +17,14 @@ namespace Kyoo.Abstractions.Controllers /// If no plugins match the query /// A plugin that match the queries public T GetPlugin(string name); - + /// /// Get all plugins of the given type. /// /// The type of plugins to get /// A list of plugins matching the given type or an empty list of none match. public ICollection GetPlugins(); - + /// /// Get all plugins currently running on Kyoo. This also includes deleted plugins if the app as not been restarted. /// @@ -39,7 +39,7 @@ namespace Kyoo.Abstractions.Controllers /// You should not try to put plugins from the plugins directory here as they will get automatically loaded. /// public void LoadPlugins(ICollection plugins); - + /// /// Load plugins and their dependencies from the plugin directory. /// @@ -49,4 +49,4 @@ namespace Kyoo.Abstractions.Controllers /// public void LoadPlugins(params Type[] plugins); } -} \ No newline at end of file +} diff --git a/Kyoo.Abstractions/Controllers/IRepository.cs b/Kyoo.Abstractions/Controllers/IRepository.cs index 45c866ba..5cc6c9a3 100644 --- a/Kyoo.Abstractions/Controllers/IRepository.cs +++ b/Kyoo.Abstractions/Controllers/IRepository.cs @@ -34,7 +34,7 @@ namespace Kyoo.Abstractions.Controllers Count = count; AfterID = afterID; } - + /// /// Implicitly create a new pagination from a limit number. /// @@ -57,7 +57,7 @@ namespace Kyoo.Abstractions.Controllers /// If this is set to true, items will be sorted in descend order else, they will be sorted in ascendant order. /// public bool Descendant { get; } - + /// /// Create a new instance. /// @@ -68,7 +68,7 @@ namespace Kyoo.Abstractions.Controllers { Key = key; Descendant = descendant; - + if (!Utility.IsPropertyExpression(Key)) throw new ArgumentException("The given sort key is not valid."); } @@ -86,7 +86,7 @@ namespace Kyoo.Abstractions.Controllers Descendant = false; return; } - + string key = sortBy.Contains(':') ? sortBy[..sortBy.IndexOf(':')] : sortBy; string order = sortBy.Contains(':') ? sortBy[(sortBy.IndexOf(':') + 1)..] : null; @@ -116,7 +116,7 @@ namespace Kyoo.Abstractions.Controllers /// Type RepositoryType { get; } } - + /// /// A common repository for every resources. /// @@ -147,7 +147,7 @@ namespace Kyoo.Abstractions.Controllers /// The resource found [ItemNotNull] Task Get(Expression> where); - + /// /// Get a resource from it's ID or null if it is not found. /// @@ -169,7 +169,7 @@ namespace Kyoo.Abstractions.Controllers /// The resource found [ItemCanBeNull] Task GetOrDefault(Expression> where); - + /// /// Search for resources. /// @@ -177,7 +177,7 @@ namespace Kyoo.Abstractions.Controllers /// A list of resources found [ItemNotNull] Task> Search(string query); - + /// /// Get every resources that match all filters /// @@ -186,7 +186,7 @@ namespace Kyoo.Abstractions.Controllers /// How pagination should be done (where to start and how many to return) /// A list of resources that match every filters [ItemNotNull] - Task> GetAll(Expression> where = null, + Task> GetAll(Expression> where = null, Sort sort = default, Pagination limit = default); /// @@ -208,8 +208,8 @@ namespace Kyoo.Abstractions.Controllers /// A filter predicate /// How many resources matched that filter Task GetCount(Expression> where = null); - - + + /// /// Create a new resource. /// @@ -217,7 +217,7 @@ namespace Kyoo.Abstractions.Controllers /// The resource registers and completed by database's information (related items & so on) [ItemNotNull] Task Create([NotNull] T obj); - + /// /// Create a new resource if it does not exist already. If it does, the existing value is returned instead. /// @@ -225,7 +225,7 @@ namespace Kyoo.Abstractions.Controllers /// The newly created item or the existing value if it existed. [ItemNotNull] Task CreateIfNotExists([NotNull] T obj); - + /// /// Edit a resource /// @@ -235,7 +235,7 @@ namespace Kyoo.Abstractions.Controllers /// The resource edited and completed by database's information (related items & so on) [ItemNotNull] Task Edit([NotNull] T edited, bool resetOld); - + /// /// Delete a resource by it's ID /// @@ -254,7 +254,7 @@ namespace Kyoo.Abstractions.Controllers /// The resource to delete /// If the item is not found Task Delete([NotNull] T obj); - + /// /// Delete all resources that match the predicate. /// @@ -299,7 +299,7 @@ namespace Kyoo.Abstractions.Controllers /// If the item is not found /// The season found Task Get(int showID, int seasonNumber); - + /// /// Get a season from it's show slug and it's seasonNumber /// @@ -308,7 +308,7 @@ namespace Kyoo.Abstractions.Controllers /// If the item is not found /// The season found Task Get(string showSlug, int seasonNumber); - + /// /// Get a season from it's showID and it's seasonNumber or null if it is not found. /// @@ -316,7 +316,7 @@ namespace Kyoo.Abstractions.Controllers /// The season's number /// The season found Task GetOrDefault(int showID, int seasonNumber); - + /// /// Get a season from it's show slug and it's seasonNumber or null if it is not found. /// @@ -325,7 +325,7 @@ namespace Kyoo.Abstractions.Controllers /// The season found Task GetOrDefault(string showSlug, int seasonNumber); } - + /// /// The repository to handle episodes /// @@ -366,7 +366,7 @@ namespace Kyoo.Abstractions.Controllers /// The episode's number /// The episode found Task GetOrDefault(string showSlug, int seasonNumber, int episodeNumber); - + /// /// Get a episode from it's showID and it's absolute number. /// @@ -389,7 +389,7 @@ namespace Kyoo.Abstractions.Controllers /// A repository to handle tracks /// public interface ITrackRepository : IRepository { } - + /// /// A repository to handle libraries. /// @@ -425,7 +425,7 @@ namespace Kyoo.Abstractions.Controllers Expression> sort, Pagination limit = default ) => GetFromLibrary(id, where, new Sort(sort), limit); - + /// /// Get items (A wrapper around shows or collections) from a library. /// @@ -451,18 +451,18 @@ namespace Kyoo.Abstractions.Controllers Expression> sort, Pagination limit = default ) => GetFromLibrary(slug, where, new Sort(sort), limit); - } - + } + /// /// A repository for collections /// public interface ICollectionRepository : IRepository { } - + /// /// A repository for genres. /// public interface IGenreRepository : IRepository { } - + /// /// A repository for studios. /// @@ -482,7 +482,7 @@ namespace Kyoo.Abstractions.Controllers /// How many items to return and where to start /// A list of items that match every filters Task> GetFromShow(int showID, - Expression> where = null, + Expression> where = null, Sort sort = default, Pagination limit = default); /// @@ -498,7 +498,7 @@ namespace Kyoo.Abstractions.Controllers Expression> sort, Pagination limit = default ) => GetFromShow(showID, where, new Sort(sort), limit); - + /// /// Get people's roles from a show. /// @@ -508,7 +508,7 @@ namespace Kyoo.Abstractions.Controllers /// How many items to return and where to start /// A list of items that match every filters Task> GetFromShow(string showSlug, - Expression> where = null, + Expression> where = null, Sort sort = default, Pagination limit = default); /// @@ -524,7 +524,7 @@ namespace Kyoo.Abstractions.Controllers Expression> sort, Pagination limit = default ) => GetFromShow(showSlug, where, new Sort(sort), limit); - + /// /// Get people's roles from a person. /// @@ -534,7 +534,7 @@ namespace Kyoo.Abstractions.Controllers /// How many items to return and where to start /// A list of items that match every filters Task> GetFromPeople(int id, - Expression> where = null, + Expression> where = null, Sort sort = default, Pagination limit = default); /// @@ -550,7 +550,7 @@ namespace Kyoo.Abstractions.Controllers Expression> sort, Pagination limit = default ) => GetFromPeople(id, where, new Sort(sort), limit); - + /// /// Get people's roles from a person. /// @@ -560,7 +560,7 @@ namespace Kyoo.Abstractions.Controllers /// How many items to return and where to start /// A list of items that match every filters Task> GetFromPeople(string slug, - Expression> where = null, + Expression> where = null, Sort sort = default, Pagination limit = default); /// @@ -591,7 +591,7 @@ namespace Kyoo.Abstractions.Controllers /// Pagination information (where to start and how many to get) /// The type of metadata to retrieve /// A filtered list of external ids. - Task> GetMetadataID(Expression> where = null, + Task> GetMetadataID(Expression> where = null, Sort sort = default, Pagination limit = default) where T : class, IMetadata; @@ -609,9 +609,9 @@ namespace Kyoo.Abstractions.Controllers ) where T : class, IMetadata => GetMetadataID(where, new Sort(sort), limit); } - + /// /// A repository to handle users. /// - public interface IUserRepository : IRepository {} + public interface IUserRepository : IRepository { } } diff --git a/Kyoo.Abstractions/Controllers/ITask.cs b/Kyoo.Abstractions/Controllers/ITask.cs index 79c8c778..69856e21 100644 --- a/Kyoo.Abstractions/Controllers/ITask.cs +++ b/Kyoo.Abstractions/Controllers/ITask.cs @@ -19,22 +19,22 @@ namespace Kyoo.Abstractions.Controllers /// The name of this parameter. /// public string Name { get; init; } - + /// /// The description of this parameter. /// public string Description { get; init; } - + /// /// The type of this parameter. /// public Type Type { get; init; } - + /// /// Is this parameter required or can it be ignored? /// public bool IsRequired { get; init; } - + /// /// The default value of this object. /// @@ -44,7 +44,7 @@ namespace Kyoo.Abstractions.Controllers /// The value of the parameter. /// private object Value { get; init; } - + /// /// Create a new task parameter. /// @@ -61,7 +61,7 @@ namespace Kyoo.Abstractions.Controllers Type = typeof(T) }; } - + /// /// Create a new required task parameter. /// @@ -79,7 +79,7 @@ namespace Kyoo.Abstractions.Controllers IsRequired = true }; } - + /// /// Create a parameter's value to give to a task. /// @@ -104,9 +104,9 @@ namespace Kyoo.Abstractions.Controllers /// A new parameter's value for this current parameter public TaskParameter CreateValue(object value) { - return this with {Value = value}; + return this with { Value = value }; } - + /// /// Get the value of this parameter. If the value is of the wrong type, it will be converted. /// @@ -140,12 +140,12 @@ namespace Kyoo.Abstractions.Controllers /// The name of the task (case sensitive) public TaskParameter this[string name] => this.FirstOrDefault(x => x.Name == name); - + /// /// Create a new, empty, /// - public TaskParameters() {} - + public TaskParameters() { } + /// /// Create a with an initial parameters content /// @@ -155,7 +155,7 @@ namespace Kyoo.Abstractions.Controllers AddRange(parameters); } } - + /// /// A common interface that tasks should implement. /// @@ -168,7 +168,7 @@ namespace Kyoo.Abstractions.Controllers /// All parameters that this task as. Every one of them will be given to the run function with a value. /// public TaskParameters GetParameters(); - + /// /// Start this task. /// @@ -191,4 +191,4 @@ namespace Kyoo.Abstractions.Controllers [NotNull] IProgress progress, CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/Kyoo.Abstractions/Controllers/ITaskManager.cs b/Kyoo.Abstractions/Controllers/ITaskManager.cs index 2a999aad..01eaeb19 100644 --- a/Kyoo.Abstractions/Controllers/ITaskManager.cs +++ b/Kyoo.Abstractions/Controllers/ITaskManager.cs @@ -35,7 +35,7 @@ namespace Kyoo.Abstractions.Controllers /// /// The task could not be found. /// - void StartTask(string taskSlug, + void StartTask(string taskSlug, [NotNull] IProgress progress, Dictionary arguments = null, CancellationToken? cancellationToken = null); @@ -66,13 +66,13 @@ namespace Kyoo.Abstractions.Controllers Dictionary arguments = null, CancellationToken? cancellationToken = null) where T : ITask; - + /// /// Get all currently running tasks /// /// A list of currently running tasks. ICollection<(TaskMetadataAttribute, ITask)> GetRunningTasks(); - + /// /// Get all available tasks /// diff --git a/Kyoo.Abstractions/Controllers/IThumbnailsManager.cs b/Kyoo.Abstractions/Controllers/IThumbnailsManager.cs index 54e5be0a..51fd8a85 100644 --- a/Kyoo.Abstractions/Controllers/IThumbnailsManager.cs +++ b/Kyoo.Abstractions/Controllers/IThumbnailsManager.cs @@ -1,6 +1,6 @@ -using Kyoo.Abstractions.Models; using System.Threading.Tasks; using JetBrains.Annotations; +using Kyoo.Abstractions.Models; namespace Kyoo.Abstractions.Controllers { diff --git a/Kyoo.Abstractions/Controllers/StartupAction.cs b/Kyoo.Abstractions/Controllers/StartupAction.cs index fb2b29a5..b5b9274e 100644 --- a/Kyoo.Abstractions/Controllers/StartupAction.cs +++ b/Kyoo.Abstractions/Controllers/StartupAction.cs @@ -23,9 +23,9 @@ namespace Kyoo.Abstractions.Controllers /// The action to run /// The priority of the new action /// A new - public static StartupAction New(Action action, int priority) + public static StartupAction New(Action action, int priority) => new(action, priority); - + /// /// Create a new . /// @@ -33,9 +33,9 @@ namespace Kyoo.Abstractions.Controllers /// The priority of the new action /// A dependency that this action will use. /// A new - public static StartupAction New(Action action, int priority) + public static StartupAction New(Action action, int priority) => new(action, priority); - + /// /// Create a new . /// @@ -44,9 +44,9 @@ namespace Kyoo.Abstractions.Controllers /// A dependency that this action will use. /// A second dependency that this action will use. /// A new - public static StartupAction New(Action action, int priority) + public static StartupAction New(Action action, int priority) => new(action, priority); - + /// /// Create a new . /// @@ -56,11 +56,11 @@ namespace Kyoo.Abstractions.Controllers /// A second dependency that this action will use. /// A third dependency that this action will use. /// A new - public static StartupAction New(Action action, int priority) + public static StartupAction New(Action action, int priority) => new(action, priority); } - - + + /// /// An action executed on kyoo's startup to initialize the asp-net container. /// @@ -81,7 +81,7 @@ namespace Kyoo.Abstractions.Controllers /// The service provider containing all services can be used. void Run(IServiceProvider provider); } - + /// /// A with no dependencies. /// @@ -94,7 +94,7 @@ namespace Kyoo.Abstractions.Controllers /// public int Priority { get; } - + /// /// Create a new . /// @@ -126,7 +126,7 @@ namespace Kyoo.Abstractions.Controllers /// public int Priority { get; } - + /// /// Create a new . /// @@ -144,7 +144,7 @@ namespace Kyoo.Abstractions.Controllers _action.Invoke(provider.GetRequiredService()); } } - + /// /// A with two dependencies. /// @@ -180,7 +180,7 @@ namespace Kyoo.Abstractions.Controllers ); } } - + /// /// A with three dependencies. /// @@ -196,7 +196,7 @@ namespace Kyoo.Abstractions.Controllers /// public int Priority { get; } - + /// /// Create a new . /// diff --git a/Kyoo.Abstractions/Models/Attributes/FileSystemMetadataAttribute.cs b/Kyoo.Abstractions/Models/Attributes/FileSystemMetadataAttribute.cs index afab7bc1..574b98d6 100644 --- a/Kyoo.Abstractions/Models/Attributes/FileSystemMetadataAttribute.cs +++ b/Kyoo.Abstractions/Models/Attributes/FileSystemMetadataAttribute.cs @@ -20,13 +20,13 @@ namespace Kyoo.Abstractions.Models.Attributes /// If multiples files with the same schemes exists, an exception will be thrown. /// public string[] Scheme { get; } - + /// /// true if the scheme should be removed from the path before calling /// methods of this , false otherwise. /// public bool StripScheme { get; set; } - + /// /// Create a new using the specified schemes. @@ -36,7 +36,7 @@ namespace Kyoo.Abstractions.Models.Attributes { Scheme = schemes; } - + /// /// Create a new using a dictionary of metadata. /// diff --git a/Kyoo.Abstractions/Models/Attributes/PermissionAttribute.cs b/Kyoo.Abstractions/Models/Attributes/PermissionAttribute.cs index b343547a..19bd1961 100644 --- a/Kyoo.Abstractions/Models/Attributes/PermissionAttribute.cs +++ b/Kyoo.Abstractions/Models/Attributes/PermissionAttribute.cs @@ -23,7 +23,7 @@ namespace Kyoo.Abstractions.Models.Permissions Overall, Admin } - + /// /// Specify permissions needed for the API. /// @@ -63,7 +63,7 @@ namespace Kyoo.Abstractions.Models.Permissions Kind = permission; Group = group; } - + /// public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { @@ -97,7 +97,7 @@ namespace Kyoo.Abstractions.Models.Permissions /// The needed permission kind. /// public Kind Kind { get; } - + /// /// Ask a permission to run an action. /// @@ -118,7 +118,7 @@ namespace Kyoo.Abstractions.Models.Permissions type = type[..^3]; Type = type.ToLower(); } - + /// /// Ask a permission to run an action. /// @@ -134,7 +134,7 @@ namespace Kyoo.Abstractions.Models.Permissions { Kind = permission; } - + /// public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { @@ -158,7 +158,7 @@ namespace Kyoo.Abstractions.Models.Permissions /// The permission attribute to validate /// An authorization filter used to validate the permission IFilterMetadata Create(PermissionAttribute attribute); - + /// /// Create an IAuthorizationFilter that will be used to validate permissions. /// This can registered with any lifetime. diff --git a/Kyoo.Abstractions/Models/Attributes/RelationAttributes.cs b/Kyoo.Abstractions/Models/Attributes/RelationAttributes.cs index 943bd1fe..31b5fa87 100644 --- a/Kyoo.Abstractions/Models/Attributes/RelationAttributes.cs +++ b/Kyoo.Abstractions/Models/Attributes/RelationAttributes.cs @@ -19,11 +19,11 @@ namespace Kyoo.Abstractions.Models.Attributes /// The name of the field containing the related resource's ID. /// public string RelationID { get; } - + /// /// Create a new . /// - public LoadableRelationAttribute() {} + public LoadableRelationAttribute() { } /// /// Create a new with a baking relationID field. diff --git a/Kyoo.Abstractions/Models/Attributes/SerializeAttribute.cs b/Kyoo.Abstractions/Models/Attributes/SerializeAttribute.cs index d2f2eb68..ae3f3f16 100644 --- a/Kyoo.Abstractions/Models/Attributes/SerializeAttribute.cs +++ b/Kyoo.Abstractions/Models/Attributes/SerializeAttribute.cs @@ -6,13 +6,13 @@ namespace Kyoo.Abstractions.Models.Attributes /// Remove an property from the serialization pipeline. It will simply be skipped. /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] - public class SerializeIgnoreAttribute : Attribute {} - + public class SerializeIgnoreAttribute : Attribute { } + /// /// Remove a property from the deserialization pipeline. The user can't input value for this property. /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] - public class DeserializeIgnoreAttribute : Attribute {} + public class DeserializeIgnoreAttribute : Attribute { } /// /// Change the way the field is serialized. It allow one to use a string format like formatting instead of the default value. @@ -25,7 +25,7 @@ namespace Kyoo.Abstractions.Models.Attributes /// The format string to use. /// public string Format { get; } - + /// /// Create a new with the selected format. /// diff --git a/Kyoo.Abstractions/Models/Attributes/TaskMetadataAttribute.cs b/Kyoo.Abstractions/Models/Attributes/TaskMetadataAttribute.cs index 18cf7802..697303b7 100644 --- a/Kyoo.Abstractions/Models/Attributes/TaskMetadataAttribute.cs +++ b/Kyoo.Abstractions/Models/Attributes/TaskMetadataAttribute.cs @@ -16,12 +16,12 @@ namespace Kyoo.Abstractions.Models.Attributes /// The slug of the task, used to start it. /// public string Slug { get; } - + /// /// The name of the task that will be displayed to the user. /// public string Name { get; } - + /// /// A quick description of what this task will do. /// @@ -31,18 +31,18 @@ namespace Kyoo.Abstractions.Models.Attributes /// Should this task be automatically run at app startup? /// public bool RunOnStartup { get; set; } - + /// /// The priority of this task. Only used if is true. /// It allow one to specify witch task will be started first as tasked are run on a Priority's descending order. /// public int Priority { get; set; } - + /// /// true if this task should not be displayed to the user, false otherwise. /// public bool IsHidden { get; set; } - + /// /// Create a new with the given slug, name and description. @@ -56,7 +56,7 @@ namespace Kyoo.Abstractions.Models.Attributes Name = name; Description = description; } - + /// /// Create a new using a dictionary of metadata. /// diff --git a/Kyoo.Abstractions/Models/Chapter.cs b/Kyoo.Abstractions/Models/Chapter.cs index 4605e7b4..35b75ec8 100644 --- a/Kyoo.Abstractions/Models/Chapter.cs +++ b/Kyoo.Abstractions/Models/Chapter.cs @@ -9,16 +9,16 @@ namespace Kyoo.Abstractions.Models /// The start time of the chapter (in second from the start of the episode). /// public float StartTime { get; set; } - + /// - /// The end time of the chapter (in second from the start of the episode)&. + /// The end time of the chapter (in second from the start of the episode). /// public float EndTime { get; set; } - + /// /// The name of this chapter. This should be a human-readable name that could be presented to the user. /// There should be well-known chapters name for commonly used chapters. - /// For example, use "Opening" for the introduction-song and "Credits" for the end chapter with credits. + /// For example, use "Opening" for the introduction-song and "Credits" for the end chapter with credits. /// public string Name { get; set; } @@ -35,4 +35,4 @@ namespace Kyoo.Abstractions.Models Name = name; } } -} \ No newline at end of file +} diff --git a/Kyoo.Abstractions/Models/ConfigurationReference.cs b/Kyoo.Abstractions/Models/ConfigurationReference.cs index 676367e3..b0fde22c 100644 --- a/Kyoo.Abstractions/Models/ConfigurationReference.cs +++ b/Kyoo.Abstractions/Models/ConfigurationReference.cs @@ -21,7 +21,7 @@ namespace Kyoo.Abstractions.Models /// public Type Type { get; } - + /// /// Create a new using a given path and type. /// This method does not create sub configuration resources. Please see @@ -75,7 +75,7 @@ namespace Kyoo.Abstractions.Models return ret; } - + /// /// Return the list of configuration reference a type has. /// diff --git a/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs b/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs index 248ea2ba..539fbe27 100644 --- a/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs +++ b/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs @@ -15,7 +15,7 @@ namespace Kyoo.Abstractions.Models.Exceptions public DuplicatedItemException() : base("Already exists in the database.") { } - + /// /// Create a new with a custom message. /// @@ -23,7 +23,7 @@ namespace Kyoo.Abstractions.Models.Exceptions public DuplicatedItemException(string message) : base(message) { } - + /// /// The serialization constructor /// diff --git a/Kyoo.Abstractions/Models/Exceptions/TaskFailedException.cs b/Kyoo.Abstractions/Models/Exceptions/TaskFailedException.cs index eebc784e..7bac57e3 100644 --- a/Kyoo.Abstractions/Models/Exceptions/TaskFailedException.cs +++ b/Kyoo.Abstractions/Models/Exceptions/TaskFailedException.cs @@ -15,24 +15,24 @@ namespace Kyoo.Abstractions.Models.Exceptions /// public TaskFailedException() : base("A task failed.") - {} - + { } + /// /// Create a new with a custom message. /// /// The message to use. public TaskFailedException(string message) : base(message) - {} - + { } + /// /// Create a new wrapping another exception. /// /// The exception to wrap. public TaskFailedException(Exception exception) : base(exception) - {} - + { } + /// /// The serialization constructor /// diff --git a/Kyoo.Abstractions/Models/LibraryItem.cs b/Kyoo.Abstractions/Models/LibraryItem.cs index 802e98a3..d761bbf3 100644 --- a/Kyoo.Abstractions/Models/LibraryItem.cs +++ b/Kyoo.Abstractions/Models/LibraryItem.cs @@ -14,7 +14,7 @@ namespace Kyoo.Abstractions.Models Movie, Collection } - + /// /// A type union between and . /// This is used to list content put inside a library. @@ -23,30 +23,30 @@ namespace Kyoo.Abstractions.Models { /// public int ID { get; set; } - + /// public string Slug { get; set; } - + /// /// The title of the show or collection. /// public string Title { get; set; } - + /// /// The summary of the show or collection. /// public string Overview { get; set; } - + /// /// Is this show airing, not aired yet or finished? This is only applicable for shows. /// public Status? Status { get; set; } - + /// /// The date this show or collection started airing. It can be null if this is unknown. /// public DateTime? StartAir { get; set; } - + /// /// The date this show or collection finished airing. /// It must be after the but can be the same (example: for movies). @@ -64,17 +64,17 @@ namespace Kyoo.Abstractions.Models /// [SerializeAs("{HOST}/api/{Type:l}/{Slug}/poster")] public string Poster => Images?.GetValueOrDefault(Models.Images.Poster); - + /// /// The type of this item (ether a collection, a show or a movie). /// public ItemType Type { get; set; } - - + + /// /// Create a new, empty . /// - public LibraryItem() {} + public LibraryItem() { } /// /// Create a from a show. @@ -92,7 +92,7 @@ namespace Kyoo.Abstractions.Models Images = show.Images; Type = show.IsMovie ? ItemType.Movie : ItemType.Show; } - + /// /// Create a from a collection /// @@ -125,7 +125,7 @@ namespace Kyoo.Abstractions.Models Images = x.Images, Type = x.IsMovie ? ItemType.Movie : ItemType.Show }; - + /// /// An expression to create a representing a collection. /// diff --git a/Kyoo.Abstractions/Models/Page.cs b/Kyoo.Abstractions/Models/Page.cs index 5dc37abb..547e903f 100644 --- a/Kyoo.Abstractions/Models/Page.cs +++ b/Kyoo.Abstractions/Models/Page.cs @@ -15,12 +15,12 @@ namespace Kyoo.Abstractions.Models /// The link of the current page. /// public Uri This { get; } - + /// /// The link of the first page. /// public Uri First { get; } - + /// /// The link of the next page. /// @@ -30,13 +30,13 @@ namespace Kyoo.Abstractions.Models /// The number of items in the current page. /// public int Count => Items.Count; - + /// /// The list of items in the page. /// public ICollection Items { get; } - - + + /// /// Create a new . /// @@ -72,7 +72,7 @@ namespace Kyoo.Abstractions.Models query["afterID"] = items.Last().ID.ToString(); Next = new Uri(url + query.ToQueryString()); } - + query.Remove("afterID"); First = new Uri(url + query.ToQueryString()); } diff --git a/Kyoo.Abstractions/Models/PeopleRole.cs b/Kyoo.Abstractions/Models/PeopleRole.cs index 934daf7d..0baf681e 100644 --- a/Kyoo.Abstractions/Models/PeopleRole.cs +++ b/Kyoo.Abstractions/Models/PeopleRole.cs @@ -12,10 +12,10 @@ namespace Kyoo.Abstractions.Models { /// public int ID { get; set; } - + /// public string Slug => ForPeople ? Show.Slug : People.Slug; - + /// /// Should this role be used as a Show substitute (the value is true) or /// as a People substitute (the value is false). @@ -30,7 +30,7 @@ namespace Kyoo.Abstractions.Models /// The people that played this role. /// public People People { get; set; } - + /// /// The ID of the Show where the People playing in. /// @@ -39,13 +39,13 @@ namespace Kyoo.Abstractions.Models /// The show where the People played in. /// public Show Show { get; set; } - + /// /// The type of work the person has done for the show. /// That can be something like "Actor", "Writer", "Music", "Voice Actor"... /// public string Type { get; set; } - + /// /// The role the People played. /// This is mostly used to inform witch character was played for actor and voice actors. diff --git a/Kyoo.Abstractions/Models/Resources/Collection.cs b/Kyoo.Abstractions/Models/Resources/Collection.cs index faa7ac9f..4472d57e 100644 --- a/Kyoo.Abstractions/Models/Resources/Collection.cs +++ b/Kyoo.Abstractions/Models/Resources/Collection.cs @@ -12,10 +12,10 @@ namespace Kyoo.Abstractions.Models { /// public int ID { get; set; } - + /// public string Slug { get; set; } - + /// /// The name of this collection. /// @@ -23,7 +23,7 @@ namespace Kyoo.Abstractions.Models /// public Dictionary Images { get; set; } - + /// /// The path of this poster. /// By default, the http path for this poster is returned from the public API. @@ -37,17 +37,17 @@ namespace Kyoo.Abstractions.Models /// The description of this collection. /// public string Overview { get; set; } - + /// /// The list of shows contained in this collection. /// [LoadableRelation] public ICollection Shows { get; set; } - + /// /// The list of libraries that contains this collection. /// [LoadableRelation] public ICollection Libraries { get; set; } - + /// [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } } diff --git a/Kyoo.Abstractions/Models/Resources/Episode.cs b/Kyoo.Abstractions/Models/Resources/Episode.cs index 65e1bd29..b2d95a45 100644 --- a/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Text.RegularExpressions; using JetBrains.Annotations; @@ -22,15 +22,15 @@ namespace Kyoo.Abstractions.Models { if (ShowSlug != null || Show != null) return GetSlug(ShowSlug ?? Show.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber); - return ShowID != 0 - ? GetSlug(ShowID.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber) + return ShowID != 0 + ? GetSlug(ShowID.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber) : null; } [UsedImplicitly] [NotNull] private set { if (value == null) throw new ArgumentNullException(nameof(value)); - + Match match = Regex.Match(value, @"(?.+)-s(?\d+)e(?\d+)"); if (match.Success) @@ -59,7 +59,7 @@ namespace Kyoo.Abstractions.Models /// The slug of the Show that contain this episode. If this is not set, this episode is ill-formed. /// [SerializeIgnore] public string ShowSlug { private get; set; } - + /// /// The ID of the Show containing this episode. /// @@ -68,7 +68,7 @@ namespace Kyoo.Abstractions.Models /// The show that contains this episode. This must be explicitly loaded via a call to . /// [LoadableRelation(nameof(ShowID))] public Show Show { get; set; } - + /// /// The ID of the Season containing this episode. /// @@ -87,22 +87,22 @@ namespace Kyoo.Abstractions.Models /// The season in witch this episode is in. /// public int? SeasonNumber { get; set; } - + /// /// The number of this episode in it's season. /// public int? EpisodeNumber { get; set; } - + /// /// The absolute number of this episode. It's an episode number that is not reset to 1 after a new season. /// public int? AbsoluteNumber { get; set; } - + /// /// The path of the video file for this episode. Any format supported by a is allowed. /// [SerializeIgnore] public string Path { get; set; } - + /// public Dictionary Images { get; set; } @@ -114,17 +114,17 @@ namespace Kyoo.Abstractions.Models [SerializeAs("{HOST}/api/episodes/{Slug}/thumbnail")] [Obsolete("Use Images instead of this, this is only kept for the API response.")] public string Thumb => Images?.GetValueOrDefault(Models.Images.Thumbnail); - + /// /// The title of this episode. /// public string Title { get; set; } - + /// /// The overview of this episode. /// public string Overview { get; set; } - + /// /// The release date of this episode. It can be null if unknown. /// @@ -137,7 +137,7 @@ namespace Kyoo.Abstractions.Models /// The list of tracks this episode has. This lists video, audio and subtitles available. /// [EditableRelation] [LoadableRelation] public ICollection Tracks { get; set; } - + /// /// Get the slug of an episode. @@ -157,8 +157,8 @@ namespace Kyoo.Abstractions.Models /// /// The slug corresponding to the given arguments /// The given show slug was null. - public static string GetSlug([NotNull] string showSlug, - int? seasonNumber, + public static string GetSlug([NotNull] string showSlug, + int? seasonNumber, int? episodeNumber, int? absoluteNumber = null) { diff --git a/Kyoo.Abstractions/Models/Resources/Genre.cs b/Kyoo.Abstractions/Models/Resources/Genre.cs index 0cf2d9b3..62aeec6a 100644 --- a/Kyoo.Abstractions/Models/Resources/Genre.cs +++ b/Kyoo.Abstractions/Models/Resources/Genre.cs @@ -11,15 +11,15 @@ namespace Kyoo.Abstractions.Models { /// public int ID { get; set; } - + /// public string Slug { get; set; } - + /// /// The name of this genre. /// public string Name { get; set; } - + /// /// The list of shows that have this genre. /// @@ -28,8 +28,8 @@ namespace Kyoo.Abstractions.Models /// /// Create a new, empty . /// - public Genre() {} - + public Genre() { } + /// /// Create a new and specify it's . /// The is automatically calculated from it's name. diff --git a/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs b/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs index b28d893a..6f8c9b46 100644 --- a/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs +++ b/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs @@ -14,7 +14,8 @@ namespace Kyoo.Abstractions.Models /// /// The link to metadata providers that this show has. See for more information. /// - [EditableRelation] [LoadableRelation] + [EditableRelation] + [LoadableRelation] public ICollection ExternalIDs { get; set; } } diff --git a/Kyoo.Abstractions/Models/Resources/Interfaces/IResource.cs b/Kyoo.Abstractions/Models/Resources/Interfaces/IResource.cs index bde5aef6..c7606d64 100644 --- a/Kyoo.Abstractions/Models/Resources/Interfaces/IResource.cs +++ b/Kyoo.Abstractions/Models/Resources/Interfaces/IResource.cs @@ -15,7 +15,7 @@ namespace Kyoo.Abstractions.Models /// this field is automatically assigned by the . /// public int ID { get; set; } - + /// /// A human-readable identifier that can be used instead of an ID. /// A slug must be unique for a type of resource but it can be changed. @@ -24,6 +24,6 @@ namespace Kyoo.Abstractions.Models /// There is no setter for a slug since it can be computed from other fields. /// For example, a season slug is {ShowSlug}-s{SeasonNumber}. /// - public string Slug { get; } + public string Slug { get; } } } \ No newline at end of file diff --git a/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs b/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs index 670dbb51..b693dea9 100644 --- a/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs +++ b/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs @@ -16,7 +16,7 @@ namespace Kyoo.Abstractions.Models /// An arbitrary index should not be used, instead use indexes from /// public Dictionary Images { get; set; } - + // TODO remove Posters properties add them via the json serializer for every IThumbnails } diff --git a/Kyoo.Abstractions/Models/Resources/Library.cs b/Kyoo.Abstractions/Models/Resources/Library.cs index fad1156e..b3b7037c 100644 --- a/Kyoo.Abstractions/Models/Resources/Library.cs +++ b/Kyoo.Abstractions/Models/Resources/Library.cs @@ -10,15 +10,15 @@ namespace Kyoo.Abstractions.Models { /// public int ID { get; set; } - + /// public string Slug { get; set; } - + /// /// The name of this library. /// public string Name { get; set; } - + /// /// The list of paths that this library is responsible for. This is mainly used by the Scan task. /// @@ -33,7 +33,7 @@ namespace Kyoo.Abstractions.Models /// The list of shows in this library. /// [LoadableRelation] public ICollection Shows { get; set; } - + /// /// The list of collections in this library. /// diff --git a/Kyoo.Abstractions/Models/Resources/People.cs b/Kyoo.Abstractions/Models/Resources/People.cs index ce46c6a7..85ced0c6 100644 --- a/Kyoo.Abstractions/Models/Resources/People.cs +++ b/Kyoo.Abstractions/Models/Resources/People.cs @@ -11,15 +11,15 @@ namespace Kyoo.Abstractions.Models { /// public int ID { get; set; } - + /// public string Slug { get; set; } - + /// /// The name of this person. /// public string Name { get; set; } - + /// public Dictionary Images { get; set; } @@ -31,10 +31,10 @@ namespace Kyoo.Abstractions.Models [SerializeAs("{HOST}/api/people/{Slug}/poster")] [Obsolete("Use Images instead of this, this is only kept for the API response.")] public string Poster => Images?.GetValueOrDefault(Models.Images.Poster); - + /// [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } - + /// /// The list of roles this person has played in. See for more information. /// diff --git a/Kyoo.Abstractions/Models/Resources/Provider.cs b/Kyoo.Abstractions/Models/Resources/Provider.cs index d1a34223..f32fbad0 100644 --- a/Kyoo.Abstractions/Models/Resources/Provider.cs +++ b/Kyoo.Abstractions/Models/Resources/Provider.cs @@ -14,15 +14,15 @@ namespace Kyoo.Abstractions.Models { /// public int ID { get; set; } - + /// public string Slug { get; set; } - + /// /// The name of this provider. /// public string Name { get; set; } - + /// public Dictionary Images { get; set; } diff --git a/Kyoo.Abstractions/Models/Resources/Season.cs b/Kyoo.Abstractions/Models/Resources/Season.cs index d60da6d8..ed9f415c 100644 --- a/Kyoo.Abstractions/Models/Resources/Season.cs +++ b/Kyoo.Abstractions/Models/Resources/Season.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Text.RegularExpressions; using JetBrains.Annotations; @@ -13,7 +13,7 @@ namespace Kyoo.Abstractions.Models public class Season : IResource, IMetadata, IThumbnails { /// - public int ID { get; set; } + public int ID { get; set; } /// [Computed] public string Slug @@ -27,7 +27,7 @@ namespace Kyoo.Abstractions.Models [UsedImplicitly] [NotNull] private set { Match match = Regex.Match(value ?? "", @"(?.+)-s(?\d+)"); - + if (!match.Success) throw new ArgumentException("Invalid season slug. Format: {showSlug}-s{seasonNumber}"); ShowSlug = match.Groups["show"].Value; @@ -39,7 +39,7 @@ namespace Kyoo.Abstractions.Models /// The slug of the Show that contain this episode. If this is not set, this season is ill-formed. /// [SerializeIgnore] public string ShowSlug { private get; set; } - + /// /// The ID of the Show containing this season. /// @@ -59,17 +59,17 @@ namespace Kyoo.Abstractions.Models /// The title of this season. /// public string Title { get; set; } - + /// /// A quick overview of this season. /// public string Overview { get; set; } - + /// /// The starting air date of this season. /// public DateTime? StartDate { get; set; } - + /// /// The ending date of this season. /// @@ -86,7 +86,7 @@ namespace Kyoo.Abstractions.Models [SerializeAs("{HOST}/api/seasons/{Slug}/thumb")] [Obsolete("Use Images instead of this, this is only kept for the API response.")] public string Poster => Images?.GetValueOrDefault(Models.Images.Poster); - + /// [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } diff --git a/Kyoo.Abstractions/Models/Resources/Show.cs b/Kyoo.Abstractions/Models/Resources/Show.cs index 77f18d99..1a4b87c9 100644 --- a/Kyoo.Abstractions/Models/Resources/Show.cs +++ b/Kyoo.Abstractions/Models/Resources/Show.cs @@ -12,31 +12,31 @@ namespace Kyoo.Abstractions.Models { /// public int ID { get; set; } - + /// public string Slug { get; set; } - + /// /// The title of this show. /// public string Title { get; set; } - + /// /// The list of alternative titles of this show. /// [EditableRelation] public string[] Aliases { get; set; } - + /// /// The path of the root directory of this show. /// This can be any kind of path supported by /// [SerializeIgnore] public string Path { get; set; } - + /// /// The summary of this show. /// public string Overview { get; set; } - + /// /// Is this show airing, not aired yet or finished? /// @@ -48,12 +48,12 @@ namespace Kyoo.Abstractions.Models /// TODO for now, this is set to a youtube url. It should be cached and converted to a local file. [Obsolete("Use Images instead of this, this is only kept for the API response.")] public string TrailerUrl => Images?.GetValueOrDefault(Models.Images.Trailer); - + /// /// The date this show started airing. It can be null if this is unknown. /// public DateTime? StartAir { get; set; } - + /// /// The date this show finished airing. /// It must be after the but can be the same (example: for movies). @@ -108,39 +108,39 @@ namespace Kyoo.Abstractions.Models /// This must be explicitly loaded via a call to . /// [LoadableRelation(nameof(StudioID))] [EditableRelation] public Studio Studio { get; set; } - + /// /// The list of genres (themes) this show has. /// [LoadableRelation] [EditableRelation] public ICollection Genres { get; set; } - + /// /// The list of people that made this show. /// [LoadableRelation] [EditableRelation] public ICollection People { get; set; } - + /// /// The different seasons in this show. If this is a movie, this list is always null or empty. /// [LoadableRelation] public ICollection Seasons { get; set; } - + /// /// The list of episodes in this show. /// If this is a movie, there will be a unique episode (with the seasonNumber and episodeNumber set to null). /// Having an episode is necessary to store metadata and tracks. /// [LoadableRelation] public ICollection Episodes { get; set; } - + /// /// The list of libraries that contains this show. /// [LoadableRelation] public ICollection Libraries { get; set; } - + /// /// The list of collections that contains this show. /// [LoadableRelation] public ICollection Collections { get; set; } - + /// public void OnMerge(object merged) { diff --git a/Kyoo.Abstractions/Models/Resources/Studio.cs b/Kyoo.Abstractions/Models/Resources/Studio.cs index fbc4934b..6edb8a28 100644 --- a/Kyoo.Abstractions/Models/Resources/Studio.cs +++ b/Kyoo.Abstractions/Models/Resources/Studio.cs @@ -11,15 +11,15 @@ namespace Kyoo.Abstractions.Models { /// public int ID { get; set; } - + /// public string Slug { get; set; } - + /// /// The name of this studio. /// public string Name { get; set; } - + /// /// The list of shows that are made by this studio. /// @@ -27,7 +27,7 @@ namespace Kyoo.Abstractions.Models /// [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } - + /// /// Create a new, empty, . /// diff --git a/Kyoo.Abstractions/Models/Resources/Track.cs b/Kyoo.Abstractions/Models/Resources/Track.cs index 4f94b7f3..1e5da085 100644 --- a/Kyoo.Abstractions/Models/Resources/Track.cs +++ b/Kyoo.Abstractions/Models/Resources/Track.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; @@ -27,7 +27,7 @@ namespace Kyoo.Abstractions.Models { /// public int ID { get; set; } - + /// [Computed] public string Slug { @@ -42,12 +42,14 @@ namespace Kyoo.Abstractions.Models { if (value == null) throw new ArgumentNullException(nameof(value)); - Match match = Regex.Match(value, + Match match = Regex.Match(value, @"(?[^\.]+)\.(?\w{0,3})(-(?\d+))?(\.(?forced))?\.(?\w+)(\.\w*)?"); if (!match.Success) + { throw new ArgumentException("Invalid track slug. " + "Format: {episodeSlug}.{language}[-{index}][.forced].{type}[.{extension}]"); + } EpisodeSlug = match.Groups["ep"].Value; Language = match.Groups["lang"].Value; @@ -58,53 +60,53 @@ namespace Kyoo.Abstractions.Models Type = Enum.Parse(match.Groups["type"].Value, true); } } - + /// /// The slug of the episode that contain this track. If this is not set, this track is ill-formed. /// [SerializeIgnore] public string EpisodeSlug { private get; set; } - + /// /// The title of the stream. /// public string Title { get; set; } - + /// /// The language of this stream (as a ISO-639-2 language code) /// public string Language { get; set; } - + /// /// The codec of this stream. /// public string Codec { get; set; } - - + + /// /// Is this stream the default one of it's type? /// public bool IsDefault { get; set; } - + /// /// Is this stream tagged as forced? /// public bool IsForced { get; set; } - + /// /// Is this track extern to the episode's file? /// public bool IsExternal { get; set; } - + /// /// The path of this track. /// [SerializeIgnore] public string Path { get; set; } - + /// /// The type of this stream. /// [SerializeIgnore] public StreamType Type { get; set; } - + /// /// The ID of the episode that uses this track. /// @@ -137,7 +139,7 @@ namespace Kyoo.Abstractions.Models name += " Forced"; if (IsExternal) name += " (External)"; - if (Title is {Length: > 1}) + if (Title is { Length: > 1 }) name += " - " + Title; return name; } @@ -164,8 +166,8 @@ namespace Kyoo.Abstractions.Models public static string BuildSlug(string baseSlug, StreamType type) { - return baseSlug.EndsWith($".{type}", StringComparison.InvariantCultureIgnoreCase) - ? baseSlug + return baseSlug.EndsWith($".{type}", StringComparison.InvariantCultureIgnoreCase) + ? baseSlug : $"{baseSlug}.{type.ToString().ToLowerInvariant()}"; } } diff --git a/Kyoo.Abstractions/Models/Resources/User.cs b/Kyoo.Abstractions/Models/Resources/User.cs index 8e61cb64..cad4fd73 100644 --- a/Kyoo.Abstractions/Models/Resources/User.cs +++ b/Kyoo.Abstractions/Models/Resources/User.cs @@ -9,30 +9,30 @@ namespace Kyoo.Abstractions.Models { /// public int ID { get; set; } - + /// public string Slug { get; set; } - + /// /// A username displayed to the user. /// public string Username { get; set; } - + /// /// The user email address. /// public string Email { get; set; } - + /// /// The user password (hashed, it can't be read like that). The hashing format is implementation defined. /// public string Password { get; set; } - + /// /// The list of permissions of the user. The format of this is implementation dependent. /// public string[] Permissions { get; set; } - + /// /// Arbitrary extra data that can be used by specific authentication implementations. /// @@ -45,13 +45,13 @@ namespace Kyoo.Abstractions.Models /// The list of shows the user has finished. /// public ICollection Watched { get; set; } - + /// /// The list of episodes the user is watching (stopped in progress or the next episode of the show) /// public ICollection CurrentlyWatching { get; set; } } - + /// /// Metadata of episode currently watching by an user /// @@ -61,17 +61,17 @@ namespace Kyoo.Abstractions.Models /// 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). /// diff --git a/Kyoo.Abstractions/Models/SearchResult.cs b/Kyoo.Abstractions/Models/SearchResult.cs index 9b1b5006..b9b62040 100644 --- a/Kyoo.Abstractions/Models/SearchResult.cs +++ b/Kyoo.Abstractions/Models/SearchResult.cs @@ -11,32 +11,32 @@ namespace Kyoo.Abstractions.Models /// The query of the search request. /// public string Query { get; init; } - + /// /// The collections that matched the search. /// public ICollection Collections { get; init; } - + /// /// The shows that matched the search. /// public ICollection Shows { get; init; } - + /// /// The episodes that matched the search. /// public ICollection Episodes { get; init; } - + /// /// The people that matched the search. /// public ICollection People { get; init; } - + /// /// The genres that matched the search. /// public ICollection Genres { get; init; } - + /// /// The studios that matched the search. /// diff --git a/Kyoo.Abstractions/Models/WatchItem.cs b/Kyoo.Abstractions/Models/WatchItem.cs index 3f48a636..570ba17b 100644 --- a/Kyoo.Abstractions/Models/WatchItem.cs +++ b/Kyoo.Abstractions/Models/WatchItem.cs @@ -20,7 +20,7 @@ namespace Kyoo.Abstractions.Models /// The ID of the episode associated with this item. /// public int EpisodeID { get; set; } - + /// /// The slug of this episode. /// @@ -30,54 +30,54 @@ namespace Kyoo.Abstractions.Models /// The title of the show containing this episode. /// public string ShowTitle { get; set; } - + /// /// The slug of the show containing this episode /// public string ShowSlug { get; set; } - + /// /// The season in witch this episode is in. /// public int? SeasonNumber { get; set; } - + /// /// The number of this episode is it's season. /// public int? EpisodeNumber { get; set; } - + /// /// The absolute number of this episode. It's an episode number that is not reset to 1 after a new season. /// public int? AbsoluteNumber { get; set; } - + /// /// The title of this episode. /// public string Title { get; set; } - + /// /// The release date of this episode. It can be null if unknown. /// public DateTime? ReleaseDate { get; set; } - + /// /// The path of the video file for this episode. Any format supported by a is allowed. /// [SerializeIgnore] public string Path { get; set; } - + /// /// The episode that come before this one if you follow usual watch orders. /// If this is the first episode or this is a movie, it will be null. /// public Episode PreviousEpisode { get; set; } - + /// /// The episode that come after this one if you follow usual watch orders. /// If this is the last aired episode or this is a movie, it will be null. /// public Episode NextEpisode { get; set; } - + /// /// true if this is a movie, false otherwise. /// @@ -89,14 +89,14 @@ namespace Kyoo.Abstractions.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/show/{ShowSlug}/poster")] public string Poster { get; set; } - + /// /// The path of this item's logo. /// By default, the http path for the logo is returned from the public API. /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/show/{ShowSlug}/logo")] public string Logo { get; set; } - + /// /// The path of this item's backdrop. /// By default, the http path for the backdrop is returned from the public API. @@ -109,27 +109,27 @@ namespace Kyoo.Abstractions.Models /// Common containers are mp4, mkv, avi and so on. /// public string Container { get; set; } - + /// /// The video track. See for more information. /// public Track Video { get; set; } - + /// /// The list of audio tracks. See for more information. /// public ICollection Audios { get; set; } - + /// /// The list of subtitles tracks. See for more information. /// public ICollection Subtitles { get; set; } - + /// /// The list of chapters. See for more information. /// public ICollection Chapters { get; set; } - + /// /// Create a from an . @@ -146,15 +146,15 @@ namespace Kyoo.Abstractions.Models await library.Load(ep, x => x.Show); await library.Load(ep, x => x.Tracks); - + if (!ep.Show.IsMovie && ep.SeasonNumber != null && ep.EpisodeNumber != null) { if (ep.EpisodeNumber > 1) previous = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber.Value, ep.EpisodeNumber.Value - 1); else if (ep.SeasonNumber > 1) { - previous = (await library.GetAll(x => x.ShowID == ep.ShowID - && x.SeasonNumber == ep.SeasonNumber.Value - 1, + previous = (await library.GetAll(x => x.ShowID == ep.ShowID + && x.SeasonNumber == ep.SeasonNumber.Value - 1, limit: 1, sort: new Sort(x => x.EpisodeNumber, true)) ).FirstOrDefault(); @@ -167,12 +167,12 @@ namespace Kyoo.Abstractions.Models } else if (!ep.Show.IsMovie && ep.AbsoluteNumber != null) { - previous = await library.GetOrDefault(x => x.ShowID == ep.ShowID + previous = await library.GetOrDefault(x => x.ShowID == ep.ShowID && x.AbsoluteNumber == ep.EpisodeNumber + 1); - next = await library.GetOrDefault(x => x.ShowID == ep.ShowID + next = await library.GetOrDefault(x => x.ShowID == ep.ShowID && x.AbsoluteNumber == ep.AbsoluteNumber + 1); } - + return new WatchItem { EpisodeID = ep.ID, @@ -188,7 +188,7 @@ namespace Kyoo.Abstractions.Models Container = PathIO.GetExtension(ep.Path)![1..], Video = ep.Tracks.FirstOrDefault(x => x.Type == StreamType.Video), Audios = ep.Tracks.Where(x => x.Type == StreamType.Audio).ToArray(), - Subtitles = ep.Tracks.Where(x => x.Type == StreamType.Subtitle).ToArray(), + Subtitles = ep.Tracks.Where(x => x.Type == StreamType.Subtitle).ToArray(), PreviousEpisode = previous, NextEpisode = next, Chapters = await GetChapters(ep.Path) @@ -200,7 +200,7 @@ namespace Kyoo.Abstractions.Models private static async Task> GetChapters(string episodePath) { string path = PathIO.Combine( - PathIO.GetDirectoryName(episodePath)!, + PathIO.GetDirectoryName(episodePath)!, "Chapters", PathIO.GetFileNameWithoutExtension(episodePath) + ".txt" ); diff --git a/Kyoo.Abstractions/Module.cs b/Kyoo.Abstractions/Module.cs index dcc5bc18..442a1070 100644 --- a/Kyoo.Abstractions/Module.cs +++ b/Kyoo.Abstractions/Module.cs @@ -30,7 +30,7 @@ namespace Kyoo.Abstractions /// The container /// The type of the task /// The registration builder of this new provider. That can be used to edit the registration. - public static IRegistrationBuilder + public static IRegistrationBuilder RegisterProvider(this ContainerBuilder builder) where T : class, IMetadataProvider { @@ -46,7 +46,7 @@ namespace Kyoo.Abstractions /// If your repository implements a special interface, please use /// /// The initial container. - public static IRegistrationBuilder + public static IRegistrationBuilder RegisterRepository(this ContainerBuilder builder) where T : IBaseRepository { diff --git a/Kyoo.Abstractions/Utility/EnumerableExtensions.cs b/Kyoo.Abstractions/Utility/EnumerableExtensions.cs index 9bd81157..7b13bc11 100644 --- a/Kyoo.Abstractions/Utility/EnumerableExtensions.cs +++ b/Kyoo.Abstractions/Utility/EnumerableExtensions.cs @@ -42,7 +42,7 @@ namespace Kyoo.Utils } return Generator(self, mapper); } - + /// /// A map where the mapping function is asynchronous. /// Note: might interest you. @@ -54,7 +54,7 @@ namespace Kyoo.Utils /// The list mapped as an AsyncEnumerable /// The list or the mapper can't be null [LinqTunnel] - public static IAsyncEnumerable MapAsync([NotNull] this IEnumerable self, + public static IAsyncEnumerable MapAsync([NotNull] this IEnumerable self, [NotNull] Func> mapper) { if (self == null) @@ -76,7 +76,7 @@ namespace Kyoo.Utils return Generator(self, mapper); } - + /// /// An asynchronous version of Select. /// @@ -87,7 +87,7 @@ namespace Kyoo.Utils /// The list mapped as an AsyncEnumerable /// The list or the mapper can't be null [LinqTunnel] - public static IAsyncEnumerable SelectAsync([NotNull] this IEnumerable self, + public static IAsyncEnumerable SelectAsync([NotNull] this IEnumerable self, [NotNull] Func> mapper) { if (self == null) @@ -159,7 +159,7 @@ namespace Kyoo.Utils do { yield return enumerator.Current; - } + } while (enumerator.MoveNext()); } @@ -179,7 +179,7 @@ namespace Kyoo.Utils foreach (T i in self) action(i); } - + /// /// A foreach used as a function with a little specificity: the list can be null. /// @@ -192,7 +192,7 @@ namespace Kyoo.Utils foreach (object i in self) action(i); } - + /// /// A foreach used as a function with a little specificity: the list can be null. /// @@ -205,7 +205,7 @@ namespace Kyoo.Utils foreach (object i in self) await action(i); } - + /// /// A foreach used as a function with a little specificity: the list can be null. /// @@ -219,7 +219,7 @@ namespace Kyoo.Utils foreach (T i in self) await action(i); } - + /// /// A foreach used as a function with a little specificity: the list can be null. /// @@ -233,7 +233,7 @@ namespace Kyoo.Utils await foreach (T i in self) action(i); } - + /// /// Split a list in a small chunk of data. /// @@ -247,7 +247,7 @@ namespace Kyoo.Utils for (int i = 0; i < list.Count; i += countPerList) yield return list.GetRange(i, Math.Min(list.Count - i, countPerList)); } - + /// /// Split a list in a small chunk of data. /// @@ -260,7 +260,7 @@ namespace Kyoo.Utils { T[] ret = new T[countPerList]; int i = 0; - + using IEnumerator enumerator = list.GetEnumerator(); while (enumerator.MoveNext()) { diff --git a/Kyoo.Abstractions/Utility/Merger.cs b/Kyoo.Abstractions/Utility/Merger.cs index fe00a924..17b21bd5 100644 --- a/Kyoo.Abstractions/Utility/Merger.cs +++ b/Kyoo.Abstractions/Utility/Merger.cs @@ -24,7 +24,7 @@ namespace Kyoo.Utils /// The two list merged as an array [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] public static T[] MergeLists([CanBeNull] IEnumerable first, - [CanBeNull] IEnumerable second, + [CanBeNull] IEnumerable second, [CanBeNull] Func isEqual = null) { if (first == null) @@ -82,7 +82,7 @@ namespace Kyoo.Utils { bool success = first.TryAdd(key, value); hasChanged |= success; - + if (success || first[key]?.Equals(default) == false || value?.Equals(default) != false) continue; first[key] = value; @@ -150,9 +150,9 @@ namespace Kyoo.Utils { Type type = typeof(T); IEnumerable properties = type.GetProperties() - .Where(x => x.CanRead && x.CanWrite + .Where(x => x.CanRead && x.CanWrite && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); - + foreach (PropertyInfo property in properties) { object value = property.GetValue(second); @@ -163,7 +163,7 @@ namespace Kyoo.Utils merge.OnMerge(second); return first; } - + /// /// Set every non-default values of seconds to the corresponding property of second. /// Dictionaries are handled like anonymous objects with a property per key/pair value @@ -190,15 +190,15 @@ namespace Kyoo.Utils /// Fields of T will be completed /// /// If first is null - public static T Complete([NotNull] T first, - [CanBeNull] T second, + public static T Complete([NotNull] T first, + [CanBeNull] T second, [InstantHandle] Func where = null) { if (first == null) throw new ArgumentNullException(nameof(first)); if (second == null) return first; - + Type type = typeof(T); IEnumerable properties = type.GetProperties() .Where(x => x.CanRead && x.CanWrite @@ -206,7 +206,7 @@ namespace Kyoo.Utils if (where != null) properties = properties.Where(where); - + foreach (PropertyInfo property in properties) { object value = property.GetValue(second); @@ -261,7 +261,7 @@ namespace Kyoo.Utils /// Fields of T will be merged /// [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static T Merge([CanBeNull] T first, + public static T Merge([CanBeNull] T first, [CanBeNull] T second, [InstantHandle] Func where = null) { @@ -269,21 +269,21 @@ namespace Kyoo.Utils return second; if (second == null) return first; - + Type type = typeof(T); IEnumerable properties = type.GetProperties() - .Where(x => x.CanRead && x.CanWrite + .Where(x => x.CanRead && x.CanWrite && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); - + if (where != null) properties = properties.Where(where); - + foreach (PropertyInfo property in properties) { object oldValue = property.GetValue(first); object newValue = property.GetValue(second); object defaultValue = property.PropertyType.GetClrDefault(); - + if (oldValue?.Equals(defaultValue) != false) property.SetValue(first, newValue); else if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>))) @@ -310,7 +310,7 @@ namespace Kyoo.Utils .GenericTypeArguments .First(); Func equalityComparer = enumerableType.IsAssignableTo(typeof(IResource)) - ? (x, y) => x.Slug == y.Slug + ? (x, y) => x.Slug == y.Slug : null; property.SetValue(first, Utility.RunGenericMethod( typeof(Merger), @@ -344,4 +344,4 @@ namespace Kyoo.Utils return obj; } } -} \ No newline at end of file +} diff --git a/Kyoo.Abstractions/Utility/MethodOfUtils.cs b/Kyoo.Abstractions/Utility/MethodOfUtils.cs index 30a1ce4c..21e65919 100644 --- a/Kyoo.Abstractions/Utility/MethodOfUtils.cs +++ b/Kyoo.Abstractions/Utility/MethodOfUtils.cs @@ -17,7 +17,7 @@ namespace Kyoo.Utils { return action.Method; } - + /// /// Get a MethodInfo from a direct method. /// @@ -27,7 +27,7 @@ namespace Kyoo.Utils { return action.Method; } - + /// /// Get a MethodInfo from a direct method. /// @@ -37,7 +37,7 @@ namespace Kyoo.Utils { return action.Method; } - + /// /// Get a MethodInfo from a direct method. /// @@ -47,7 +47,7 @@ namespace Kyoo.Utils { return action.Method; } - + /// /// Get a MethodInfo from a direct method. /// @@ -57,7 +57,7 @@ namespace Kyoo.Utils { return action.Method; } - + /// /// Get a MethodInfo from a direct method. /// @@ -67,7 +67,7 @@ namespace Kyoo.Utils { return action.Method; } - + /// /// Get a MethodInfo from a direct method. /// @@ -77,7 +77,7 @@ namespace Kyoo.Utils { return action.Method; } - + /// /// Get a MethodInfo from a direct method. /// diff --git a/Kyoo.Abstractions/Utility/Utility.cs b/Kyoo.Abstractions/Utility/Utility.cs index d1bb91b1..7b9d1950 100644 --- a/Kyoo.Abstractions/Utility/Utility.cs +++ b/Kyoo.Abstractions/Utility/Utility.cs @@ -28,7 +28,7 @@ namespace Kyoo.Utils return ex.Body is MemberExpression || ex.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)ex.Body).Operand is MemberExpression; } - + /// /// Get the name of a property. Useful for selectors as members ex: Load(x => x.Shows) /// @@ -66,7 +66,7 @@ namespace Kyoo.Utils _ => throw new ArgumentException($"Can't get value of a non property/field (member: {member}).") }; } - + /// /// Slugify a string (Replace spaces by -, Uniformize accents é -> e) /// @@ -78,7 +78,7 @@ namespace Kyoo.Utils return null; str = str.ToLowerInvariant(); - + string normalizedString = str.Normalize(NormalizationForm.FormD); StringBuilder stringBuilder = new(); foreach (char c in normalizedString) @@ -104,7 +104,7 @@ namespace Kyoo.Utils public static object GetClrDefault(this Type type) { return type.IsValueType - ? Activator.CreateInstance(type) + ? Activator.CreateInstance(type) : null; } @@ -135,7 +135,7 @@ namespace Kyoo.Utils throw new ArgumentNullException(nameof(obj)); return IsOfGenericType(obj.GetType(), genericType); } - + /// /// Check if inherit from a generic type . /// @@ -201,15 +201,15 @@ namespace Kyoo.Utils /// The list of generic parameters. /// /// - /// The list of parameters. + /// The list of parameters. /// /// No method match the given constraints. /// The method handle of the matching method. [PublicAPI] [NotNull] - public static MethodInfo GetMethod([NotNull] Type type, + public static MethodInfo GetMethod([NotNull] Type type, BindingFlags flag, - string name, + string name, [NotNull] Type[] generics, [NotNull] object[] args) { @@ -219,7 +219,7 @@ namespace Kyoo.Utils throw new ArgumentNullException(nameof(generics)); if (args == null) throw new ArgumentNullException(nameof(args)); - + MethodInfo[] methods = type.GetMethods(flag | BindingFlags.Public) .Where(x => x.Name == name) .Where(x => x.GetGenericArguments().Length == generics.Length) @@ -234,7 +234,7 @@ namespace Kyoo.Utils // return x.GetGenericArguments().All(y => y.IsAssignableFrom(generics[i++])); // }) // .IfEmpty(() => throw new NullReferenceException($"No method {name} match the generics specified.")) - + // TODO this won't work for Type because T is specified in arguments but not in the parameters type. // .Where(x => // { @@ -249,7 +249,7 @@ namespace Kyoo.Utils return methods[0]; throw new ArgumentException($"Multiple methods named {name} match the generics and parameters constraints."); } - + /// /// Run a generic static method for a runtime . /// @@ -276,14 +276,14 @@ namespace Kyoo.Utils /// /// public static T RunGenericMethod( - [NotNull] Type owner, + [NotNull] Type owner, [NotNull] string methodName, [NotNull] Type type, params object[] args) { - return RunGenericMethod(owner, methodName, new[] {type}, args); + return RunGenericMethod(owner, methodName, new[] { type }, args); } - + /// /// Run a generic static method for a multiple runtime . /// If your generic method only needs one type, see @@ -313,7 +313,7 @@ namespace Kyoo.Utils /// [PublicAPI] public static T RunGenericMethod( - [NotNull] Type owner, + [NotNull] Type owner, [NotNull] string methodName, [NotNull] Type[] types, params object[] args) @@ -361,7 +361,7 @@ namespace Kyoo.Utils [NotNull] Type type, params object[] args) { - return RunGenericMethod(instance, methodName, new[] {type}, args); + return RunGenericMethod(instance, methodName, new[] { type }, args); } /// @@ -392,7 +392,7 @@ namespace Kyoo.Utils /// /// public static T RunGenericMethod( - [NotNull] object instance, + [NotNull] object instance, [NotNull] string methodName, [NotNull] Type[] types, params object[] args) @@ -436,4 +436,4 @@ namespace Kyoo.Utils return $"{type.Name[..type.Name.IndexOf('`')]}<{generics}>"; } } -} \ No newline at end of file +} diff --git a/Kyoo.Authentication/AuthenticationModule.cs b/Kyoo.Authentication/AuthenticationModule.cs index 805b01cd..054b0268 100644 --- a/Kyoo.Authentication/AuthenticationModule.cs +++ b/Kyoo.Authentication/AuthenticationModule.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; @@ -32,10 +33,10 @@ namespace Kyoo.Authentication { /// public string Slug => "auth"; - + /// public string Name => "Authentication"; - + /// public string Description => "Enable OpenID authentication for Kyoo."; @@ -70,8 +71,10 @@ namespace Kyoo.Authentication /// The configuration to use /// The logger used to allow IdentityServer to log things /// The environment information to check if the app runs in debug mode + [SuppressMessage("ReSharper", "ContextualLoggerProblem", + Justification = "The logger is used for a dependency that is not created via the container.")] public AuthenticationModule(IConfiguration configuration, - ILogger logger, + ILogger logger, IWebHostEnvironment environment) { _configuration = configuration; @@ -100,16 +103,16 @@ namespace Kyoo.Authentication IdentityModelEventSource.ShowPII = true; 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(); _configuration.GetSection(CertificateOption.Path).Bind(certificateOptions); - + clients.AddRange(IdentityContext.GetClients()); foreach (Client client in clients) { @@ -131,7 +134,7 @@ namespace Kyoo.Authentication .AddInMemoryClients(clients) .AddProfileService() .AddSigninKeys(certificateOptions); - + services.AddAuthentication() .AddJwtBearer(options => { @@ -181,4 +184,4 @@ namespace Kyoo.Authentication SA.New(app => app.UseAuthorization(), SA.Authorization) }; } -} \ No newline at end of file +} diff --git a/Kyoo.Authentication/Controllers/Certificates.cs b/Kyoo.Authentication/Controllers/Certificates.cs index 906890c4..26edbaf1 100644 --- a/Kyoo.Authentication/Controllers/Certificates.cs +++ b/Kyoo.Authentication/Controllers/Certificates.cs @@ -28,12 +28,12 @@ namespace Kyoo.Authentication /// The identity server that will be modified. /// The certificate options /// - public static IIdentityServerBuilder AddSigninKeys(this IIdentityServerBuilder builder, + public static IIdentityServerBuilder AddSigninKeys(this IIdentityServerBuilder builder, CertificateOption options) { X509Certificate2 certificate = GetCertificate(options); builder.AddSigningCredential(certificate); - + if (certificate.NotAfter.AddDays(-7) <= DateTime.UtcNow) { Console.WriteLine("Signin certificate will expire soon, renewing it."); @@ -54,8 +54,8 @@ namespace Kyoo.Authentication /// A valid certificate private static X509Certificate2 GetCertificate(CertificateOption options) { - return File.Exists(options.File) - ? GetExistingCredential(options.File, options.Password) + return File.Exists(options.File) + ? GetExistingCredential(options.File, options.Password) : GenerateCertificate(options.File, options.Password); } @@ -83,19 +83,19 @@ namespace Kyoo.Authentication private static X509Certificate2 GenerateCertificate(string file, string password) { SecureRandom random = new(); - + X509V3CertificateGenerator certificateGenerator = new(); - certificateGenerator.SetSerialNumber(BigIntegers.CreateRandomInRange(BigInteger.One, + certificateGenerator.SetSerialNumber(BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random)); certificateGenerator.SetIssuerDN(new X509Name($"C=NL, O=SDG, CN=Kyoo")); certificateGenerator.SetSubjectDN(new X509Name($"C=NL, O=SDG, CN=Kyoo")); certificateGenerator.SetNotBefore(DateTime.UtcNow.Date); certificateGenerator.SetNotAfter(DateTime.UtcNow.Date.AddMonths(3)); - + KeyGenerationParameters keyGenerationParameters = new(random, 2048); RsaKeyPairGenerator keyPairGenerator = new(); keyPairGenerator.Init(keyGenerationParameters); - + AsymmetricCipherKeyPair subjectKeyPair = keyPairGenerator.GenerateKeyPair(); certificateGenerator.SetPublicKey(subjectKeyPair.Public); @@ -104,7 +104,7 @@ namespace Kyoo.Authentication X509Certificate bouncyCert = certificateGenerator.Generate(signatureFactory); Pkcs12Store store = new Pkcs12StoreBuilder().Build(); - store.SetKeyEntry("Kyoo_key", new AsymmetricKeyEntry(subjectKeyPair.Private), new [] + store.SetKeyEntry("Kyoo_key", new AsymmetricKeyEntry(subjectKeyPair.Private), new[] { new X509CertificateEntry(bouncyCert) }); diff --git a/Kyoo.Authentication/Controllers/PremissionValidator.cs b/Kyoo.Authentication/Controllers/PremissionValidator.cs index d34f5e57..05ef1a16 100644 --- a/Kyoo.Authentication/Controllers/PremissionValidator.cs +++ b/Kyoo.Authentication/Controllers/PremissionValidator.cs @@ -15,7 +15,7 @@ namespace Kyoo.Authentication { /// /// A permission validator to validate permission with user Permission array - /// or the default array from the configurations if the user is not logged. + /// or the default array from the configurations if the user is not logged. /// public class PermissionValidatorFactory : IPermissionValidator { @@ -38,7 +38,7 @@ namespace Kyoo.Authentication { return new PermissionValidator(attribute.Type, attribute.Kind, attribute.Group, _options); } - + /// public IFilterMetadata Create(PartialPermissionAttribute attribute) { @@ -149,4 +149,4 @@ namespace Kyoo.Authentication } } } -} \ No newline at end of file +} diff --git a/Kyoo.Authentication/Extensions.cs b/Kyoo.Authentication/Extensions.cs index ca37cd09..2a6b734b 100644 --- a/Kyoo.Authentication/Extensions.cs +++ b/Kyoo.Authentication/Extensions.cs @@ -37,7 +37,7 @@ namespace Kyoo.Authentication return new(user.ID.ToString()) { DisplayName = user.Username, - AdditionalClaims = new[] {new Claim("permissions", string.Join(',', user.Permissions))} + AdditionalClaims = new[] { new Claim("permissions", string.Join(',', user.Permissions)) } }; } diff --git a/Kyoo.Authentication/Models/DTO/AccountUpdateRequest.cs b/Kyoo.Authentication/Models/DTO/AccountUpdateRequest.cs index ac135799..0ad32d47 100644 --- a/Kyoo.Authentication/Models/DTO/AccountUpdateRequest.cs +++ b/Kyoo.Authentication/Models/DTO/AccountUpdateRequest.cs @@ -13,13 +13,13 @@ namespace Kyoo.Authentication.Models.DTO /// [EmailAddress(ErrorMessage = "The email is invalid.")] public string Email { get; set; } - + /// /// The new username of the user. /// [MinLength(4, ErrorMessage = "The username must have at least 4 characters")] public string Username { get; set; } - + /// /// The picture icon. /// diff --git a/Kyoo.Authentication/Models/DTO/LoginRequest.cs b/Kyoo.Authentication/Models/DTO/LoginRequest.cs index 9bee4e04..14f1badf 100644 --- a/Kyoo.Authentication/Models/DTO/LoginRequest.cs +++ b/Kyoo.Authentication/Models/DTO/LoginRequest.cs @@ -9,17 +9,17 @@ namespace Kyoo.Authentication.Models.DTO /// The user's username. /// public string Username { get; set; } - + /// /// The user's password. /// public string Password { get; set; } - + /// /// Should the user stay logged in? If true a cookie will be put. /// public bool StayLoggedIn { get; set; } - + /// /// The return url of the login flow. /// diff --git a/Kyoo.Authentication/Models/DTO/OtacRequest.cs b/Kyoo.Authentication/Models/DTO/OtacRequest.cs index 0c007f78..36eb4e56 100644 --- a/Kyoo.Authentication/Models/DTO/OtacRequest.cs +++ b/Kyoo.Authentication/Models/DTO/OtacRequest.cs @@ -9,7 +9,7 @@ namespace Kyoo.Authentication.Models.DTO /// The One Time Access Code /// public string Otac { get; set; } - + /// /// Should the user stay logged /// diff --git a/Kyoo.Authentication/Models/DTO/RegisterRequest.cs b/Kyoo.Authentication/Models/DTO/RegisterRequest.cs index 1e505bb7..5ba89f3b 100644 --- a/Kyoo.Authentication/Models/DTO/RegisterRequest.cs +++ b/Kyoo.Authentication/Models/DTO/RegisterRequest.cs @@ -15,13 +15,13 @@ namespace Kyoo.Authentication.Models.DTO /// [EmailAddress(ErrorMessage = "The email must be a valid email address")] public string Email { get; set; } - + /// /// The user's username. /// [MinLength(4, ErrorMessage = "The username must have at least {1} characters")] public string Username { get; set; } - + /// /// The user's password. /// @@ -44,5 +44,5 @@ namespace Kyoo.Authentication.Models.DTO ExtraData = new Dictionary() }; } - } + } } \ No newline at end of file diff --git a/Kyoo.Authentication/Models/IdentityContext.cs b/Kyoo.Authentication/Models/IdentityContext.cs index e6ca3353..013fa6e5 100644 --- a/Kyoo.Authentication/Models/IdentityContext.cs +++ b/Kyoo.Authentication/Models/IdentityContext.cs @@ -42,11 +42,11 @@ namespace Kyoo.Authentication AllowedGrantTypes = GrantTypes.Code, RequirePkce = true, RequireClientSecret = false, - + AllowAccessTokensViaBrowser = true, AllowOfflineAccess = true, RequireConsent = false, - + AllowedScopes = { "openid", "profile", "kyoo.read", "kyoo.write", "kyoo.play", "kyoo.admin" }, RedirectUris = { "/", "/silent.html" }, PostLogoutRedirectUris = { "/logout" } @@ -84,7 +84,7 @@ namespace Kyoo.Authentication } }; } - + /// /// The list of APIs (this is used to create Audiences) /// diff --git a/Kyoo.Authentication/Models/Options/AuthenticationOption.cs b/Kyoo.Authentication/Models/Options/AuthenticationOption.cs index 23e917aa..df22a323 100644 --- a/Kyoo.Authentication/Models/Options/AuthenticationOption.cs +++ b/Kyoo.Authentication/Models/Options/AuthenticationOption.cs @@ -14,12 +14,12 @@ namespace Kyoo.Authentication.Models /// The options for certificates /// public CertificateOption Certificate { get; set; } - + /// /// Options for permissions /// public PermissionOption Permissions { get; set; } - + /// /// Root path of user's profile pictures. /// diff --git a/Kyoo.Authentication/Models/Options/CertificateOption.cs b/Kyoo.Authentication/Models/Options/CertificateOption.cs index 93d2a878..41eba09a 100644 --- a/Kyoo.Authentication/Models/Options/CertificateOption.cs +++ b/Kyoo.Authentication/Models/Options/CertificateOption.cs @@ -9,7 +9,7 @@ namespace Kyoo.Authentication.Models /// The path to get this option from the root configuration. /// public const string Path = "authentication:certificate"; - + /// /// The path of the certificate file. /// diff --git a/Kyoo.Authentication/Models/Options/PermissionOption.cs b/Kyoo.Authentication/Models/Options/PermissionOption.cs index 8d6c698d..f6098110 100644 --- a/Kyoo.Authentication/Models/Options/PermissionOption.cs +++ b/Kyoo.Authentication/Models/Options/PermissionOption.cs @@ -14,7 +14,7 @@ namespace Kyoo.Authentication.Models /// The default permissions that will be given to a non-connected user. /// public string[] Default { get; set; } - + /// /// Permissions applied to a new user. /// diff --git a/Kyoo.Authentication/Views/AccountApi.cs b/Kyoo.Authentication/Views/AccountApi.cs index 1e369e31..4c496632 100644 --- a/Kyoo.Authentication/Views/AccountApi.cs +++ b/Kyoo.Authentication/Views/AccountApi.cs @@ -57,8 +57,8 @@ namespace Kyoo.Authentication.Views _files = files; _options = options; } - - + + /// /// Register a new user and return a OTAC to connect to it. /// @@ -78,10 +78,10 @@ namespace Kyoo.Authentication.Views } catch (DuplicatedItemException) { - return Conflict(new {Errors = new {Duplicate = new[] {"A user with this name already exists"}}}); + return Conflict(new { Errors = new { Duplicate = new[] { "A user with this name already exists" } } }); } - return Ok(new {Otac = user.ExtraData["otac"]}); + return Ok(new { Otac = user.ExtraData["otac"] }); } /// @@ -99,7 +99,7 @@ namespace Kyoo.Authentication.Views ExpiresUtc = DateTimeOffset.UtcNow.AddMonths(1) }; } - + /// /// Login the user. /// @@ -117,7 +117,7 @@ namespace Kyoo.Authentication.Views await HttpContext.SignInAsync(user.ToIdentityUser(), StayLogged(login.StayLoggedIn)); return Ok(new { RedirectUrl = login.ReturnURL, IsOk = true }); } - + /// /// Use a OTAC to login a user. /// @@ -127,22 +127,23 @@ namespace Kyoo.Authentication.Views { // TODO once hstore (Dictionary accessor) are supported, use them. // We retrieve all users, this is inefficient. - User user = (await _users.GetAll()).FirstOrDefault(x => x.ExtraData.GetValueOrDefault("otac") == otac.Otac); + User user = (await _users.GetAll()).FirstOrDefault(x => x.ExtraData.GetValueOrDefault("otac") == otac.Otac); if (user == null) return Unauthorized(); if (DateTime.ParseExact(user.ExtraData["otac-expire"], "s", CultureInfo.InvariantCulture) <= - DateTime.UtcNow) + DateTime.UtcNow) { return BadRequest(new { - code = "ExpiredOTAC", description = "The OTAC has expired. Try to login with your password." + code = "ExpiredOTAC", + description = "The OTAC has expired. Try to login with your password." }); } - + await HttpContext.SignInAsync(user.ToIdentityUser(), StayLogged(otac.StayLoggedIn)); return Ok(); } - + /// /// Sign out an user /// @@ -170,7 +171,7 @@ namespace Kyoo.Authentication.Views User user = await _users.GetOrDefault(int.Parse(context.Subject.GetSubjectId())); context.IsActive = user != null; } - + /// /// Get the user's profile picture. /// @@ -185,7 +186,7 @@ namespace Kyoo.Authentication.Views string path = Path.Combine(_options.Value.ProfilePicturePath, user.ID.ToString()); return _files.FileResult(path); } - + /// /// Update profile information (email, username, profile picture...) /// diff --git a/Kyoo.Core/Application.cs b/Kyoo.Core/Application.cs index d3a2553f..643aa89c 100644 --- a/Kyoo.Core/Application.cs +++ b/Kyoo.Core/Application.cs @@ -32,7 +32,7 @@ namespace Kyoo.Core /// Should the application restart after a shutdown? /// private bool _shouldRestart; - + /// /// The cancellation token source used to allow the app to be shutdown or restarted. /// @@ -48,7 +48,7 @@ namespace Kyoo.Core /// private ILogger _logger; - + /// /// Create a new that will use the specified environment. /// @@ -80,28 +80,28 @@ namespace Kyoo.Core public async Task Start(string[] args, Action configure) { _dataDir = _SetupDataDir(args); - + LoggerConfiguration config = new(); _ConfigureLogging(config, null, null); Log.Logger = config.CreateBootstrapLogger(); _logger = Log.Logger.ForContext(); AppDomain.CurrentDomain.ProcessExit += (_, _) => Log.CloseAndFlush(); - AppDomain.CurrentDomain.UnhandledException += (_, ex) + AppDomain.CurrentDomain.UnhandledException += (_, ex) => Log.Fatal(ex.ExceptionObject as Exception, "Unhandled exception"); - + do { IHost host = _CreateWebHostBuilder(args) .ConfigureContainer(configure) .Build(); - + _tokenSource = new CancellationTokenSource(); await _StartWithHost(host, _tokenSource.Token); - } + } while (_shouldRestart); } - + /// public void Shutdown() { @@ -155,7 +155,7 @@ namespace Kyoo.Core .AddCommandLine(args) .Build(); - string path = parsed.GetValue("datadir") + string path = parsed.GetValue("datadir") ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Kyoo"); if (!Directory.Exists(path)) @@ -165,7 +165,7 @@ namespace Kyoo.Core if (!File.Exists(GetConfigFile())) File.Copy(Path.Join(AppDomain.CurrentDomain.BaseDirectory, GetConfigFile()), GetConfigFile()); - + return path; } @@ -217,7 +217,7 @@ namespace Kyoo.Core .UseStartup(host => PluginsStartup.FromWebHost(host, new LoggerFactory().AddSerilog())) ); } - + /// /// Register settings.json, environment variables and command lines arguments as configuration. /// diff --git a/Kyoo.Core/Controllers/ConfigurationManager.cs b/Kyoo.Core/Controllers/ConfigurationManager.cs index 7f5432f1..88927d76 100644 --- a/Kyoo.Core/Controllers/ConfigurationManager.cs +++ b/Kyoo.Core/Controllers/ConfigurationManager.cs @@ -17,7 +17,7 @@ namespace Kyoo.Core.Controllers public class ConfigurationManager : IConfigurationManager { /// - /// The configuration to retrieve and edit. + /// The configuration to retrieve and edit. /// private readonly IConfiguration _configuration; @@ -58,7 +58,7 @@ namespace Kyoo.Core.Controllers ConfigurationReference config = ConfigurationReference.CreateUntyped(path); _references.Add(config.Path, config.Type); } - + /// public void Register(string path, Type type) { @@ -98,7 +98,7 @@ namespace Kyoo.Core.Controllers throw new ArgumentException($"The configuration at {path} is not editable or readable."); throw new ItemNotFoundException($"No configuration exists for the name: {path}"); } - + /// public object GetValue(string path) { @@ -124,7 +124,7 @@ namespace Kyoo.Core.Controllers $"a resource of type {type.Name}."); return (T)GetValue(path); } - + /// public async Task EditValue(string path, object value) { @@ -133,7 +133,7 @@ namespace Kyoo.Core.Controllers value = JObject.FromObject(value).ToObject(type); if (value == null) throw new ArgumentException("Invalid value format."); - + ExpandoObject config = _ToObject(_configuration); IDictionary configDic = config; configDic[path] = value; @@ -141,7 +141,7 @@ namespace Kyoo.Core.Controllers await using StreamWriter writer = new(_application.GetConfigFile()); await writer.WriteAsync(obj.ToString()); } - + /// /// Transform a configuration to a strongly typed object (the root configuration is an /// but child elements are using strong types. @@ -169,7 +169,7 @@ namespace Kyoo.Core.Controllers continue; } } - + return obj; } @@ -192,4 +192,4 @@ namespace Kyoo.Core.Controllers return obj; } } -} \ No newline at end of file +} diff --git a/Kyoo.Core/Controllers/FileSystems/FileSystemComposite.cs b/Kyoo.Core/Controllers/FileSystems/FileSystemComposite.cs index 4a715602..2b637051 100644 --- a/Kyoo.Core/Controllers/FileSystems/FileSystemComposite.cs +++ b/Kyoo.Core/Controllers/FileSystems/FileSystemComposite.cs @@ -31,12 +31,12 @@ namespace Kyoo.Core.Controllers /// (only if the option is set to metadata in show) /// private readonly ILibraryManager _libraryManager; - + /// /// Options to check if the metadata should be kept in the show directory or in a kyoo's directory. /// private readonly IOptionsMonitor _options; - + /// /// Create a new from a list of mapped to their /// metadata. @@ -45,7 +45,7 @@ namespace Kyoo.Core.Controllers /// The library manager used to load shows to retrieve their path. /// The options to use. public FileSystemComposite(ICollection, FileSystemMetadataAttribute>> fileSystems, - ILibraryManager libraryManager, + ILibraryManager libraryManager, IOptionsMonitor options) { _fileSystems = fileSystems; @@ -53,7 +53,7 @@ namespace Kyoo.Core.Controllers _options = options; } - + /// /// Retrieve the file system that should be used for a given path. /// @@ -159,7 +159,7 @@ namespace Kyoo.Core.Controllers return _GetFileSystemForPath(path, out string relativePath) .Exists(relativePath); } - + /// public async Task GetExtraDirectory(T resource) { @@ -191,7 +191,7 @@ namespace Kyoo.Core.Controllers Season season => await GetExtraDirectory(season.Show), Episode episode => await GetExtraDirectory(episode.Show), Track track => await GetExtraDirectory(track.Episode), - IResource res => Combine(_options.CurrentValue.MetadataPath, + IResource res => Combine(_options.CurrentValue.MetadataPath, typeof(T).Name.ToLowerInvariant(), res.Slug), _ => Combine(_options.CurrentValue.MetadataPath, typeof(T).Name.ToLowerInvariant()) }; diff --git a/Kyoo.Core/Controllers/FileSystems/HttpFileSystem.cs b/Kyoo.Core/Controllers/FileSystems/HttpFileSystem.cs index 0b5618fa..fb09db8e 100644 --- a/Kyoo.Core/Controllers/FileSystems/HttpFileSystem.cs +++ b/Kyoo.Core/Controllers/FileSystems/HttpFileSystem.cs @@ -14,7 +14,7 @@ namespace Kyoo.Core.Controllers /// /// A for http/https links. /// - [FileSystemMetadata(new [] {"http", "https"})] + [FileSystemMetadata(new[] { "http", "https" })] public class HttpFileSystem : IFileSystem { /// @@ -30,8 +30,8 @@ namespace Kyoo.Core.Controllers { _clientFactory = factory; } - - + + /// public IActionResult FileResult(string path, bool rangeSupport = false, string type = null) { @@ -46,7 +46,7 @@ namespace Kyoo.Core.Controllers HttpClient client = _clientFactory.CreateClient(); return client.GetStreamAsync(path); } - + /// public async Task GetReader(string path, AsyncRef mime) { diff --git a/Kyoo.Core/Controllers/FileSystems/LocalFileSystem.cs b/Kyoo.Core/Controllers/FileSystems/LocalFileSystem.cs index 3c75717f..d70a1f0c 100644 --- a/Kyoo.Core/Controllers/FileSystems/LocalFileSystem.cs +++ b/Kyoo.Core/Controllers/FileSystems/LocalFileSystem.cs @@ -15,7 +15,7 @@ namespace Kyoo.Core.Controllers /// /// A for the local filesystem (using System.IO). /// - [FileSystemMetadata(new [] {"", "file"}, StripScheme = true)] + [FileSystemMetadata(new[] { "", "file" }, StripScheme = true)] public class LocalFileSystem : IFileSystem { /// @@ -51,7 +51,7 @@ namespace Kyoo.Core.Controllers return contentType; throw new NotImplementedException($"Can't get the content type of the file at: {path}"); } - + /// public IActionResult FileResult(string path, bool rangeSupport = false, string type = null) { @@ -72,7 +72,7 @@ namespace Kyoo.Core.Controllers throw new ArgumentNullException(nameof(path)); return Task.FromResult(File.OpenRead(path)); } - + /// public Task GetReader(string path, AsyncRef mime) { @@ -99,19 +99,19 @@ namespace Kyoo.Core.Controllers Directory.CreateDirectory(path); return Task.FromResult(path); } - + /// public string Combine(params string[] paths) { return Path.Combine(paths); } - + /// public Task> ListFiles(string path, SearchOption options = SearchOption.TopDirectoryOnly) { if (path == null) throw new ArgumentNullException(nameof(path)); - string[] ret = Directory.Exists(path) + string[] ret = Directory.Exists(path) ? Directory.GetFiles(path, "*", options) : Array.Empty(); return Task.FromResult>(ret); @@ -122,7 +122,7 @@ namespace Kyoo.Core.Controllers { return Task.FromResult(File.Exists(path) || Directory.Exists(path)); } - + /// public Task GetExtraDirectory(T resource) { diff --git a/Kyoo.Core/Controllers/LibraryManager.cs b/Kyoo.Core/Controllers/LibraryManager.cs index e7759f0a..374dac89 100644 --- a/Kyoo.Core/Controllers/LibraryManager.cs +++ b/Kyoo.Core/Controllers/LibraryManager.cs @@ -16,7 +16,7 @@ namespace Kyoo.Core.Controllers /// The list of repositories /// private readonly IBaseRepository[] _repositories; - + /// public ILibraryRepository LibraryRepository { get; } /// @@ -41,8 +41,8 @@ namespace Kyoo.Core.Controllers public IProviderRepository ProviderRepository { get; } /// public IUserRepository UserRepository { get; } - - + + /// /// Create a new instance with every repository available. /// @@ -82,7 +82,7 @@ namespace Kyoo.Core.Controllers } /// - public Task Get(string slug) + public Task Get(string slug) where T : class, IResource { return GetRepository().Get(slug); @@ -120,19 +120,19 @@ namespace Kyoo.Core.Controllers } /// - public async Task GetOrDefault(int id) + public async Task GetOrDefault(int id) where T : class, IResource { return await GetRepository().GetOrDefault(id); } - + /// - public async Task GetOrDefault(string slug) + public async Task GetOrDefault(string slug) where T : class, IResource { return await GetRepository().GetOrDefault(slug); } - + /// public async Task GetOrDefault(Expression> where) where T : class, IResource @@ -145,19 +145,19 @@ namespace Kyoo.Core.Controllers { return await SeasonRepository.GetOrDefault(showID, seasonNumber); } - + /// public async Task GetOrDefault(string showSlug, int seasonNumber) { return await SeasonRepository.GetOrDefault(showSlug, seasonNumber); } - + /// public async Task GetOrDefault(int showID, int seasonNumber, int episodeNumber) { return await EpisodeRepository.GetOrDefault(showID, seasonNumber, episodeNumber); } - + /// public async Task GetOrDefault(string showSlug, int seasonNumber, int episodeNumber) { @@ -173,9 +173,9 @@ namespace Kyoo.Core.Controllers /// A setter function to store the owner of a releated object loaded /// The type of the owner object /// The type of the related object - private static async Task SetRelation(T1 obj, - Task> loader, - Action> setter, + private static async Task SetRelation(T1 obj, + Task> loader, + Action> setter, Action inverse) { ICollection loaded = await loader; @@ -230,61 +230,61 @@ namespace Kyoo.Core.Controllers (Library l, nameof(Library.Providers)) => ProviderRepository .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID)) .Then(x => l.Providers = x), - + (Library l, nameof(Library.Shows)) => ShowRepository .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID)) - .Then(x => l.Shows = x), - + .Then(x => l.Shows = x), + (Library l, nameof(Library.Collections)) => CollectionRepository .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID)) - .Then(x => l.Collections = x), - - - (Collection c, nameof(Collection.ExternalIDs)) => SetRelation(c, + .Then(x => l.Collections = x), + + + (Collection c, nameof(Collection.ExternalIDs)) => SetRelation(c, ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, (x, y) => { x.ResourceID = y.ID; }), - + (Collection c, nameof(Collection.Shows)) => ShowRepository .GetAll(x => x.Collections.Any(y => y.ID == obj.ID)) - .Then(x => c.Shows = x), - + .Then(x => c.Shows = x), + (Collection c, nameof(Collection.Libraries)) => LibraryRepository .GetAll(x => x.Collections.Any(y => y.ID == obj.ID)) - .Then(x => c.Libraries = x), - - - (Show s, nameof(Show.ExternalIDs)) => SetRelation(s, + .Then(x => c.Libraries = x), + + + (Show s, nameof(Show.ExternalIDs)) => SetRelation(s, ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, (x, y) => { x.ResourceID = y.ID; }), - + (Show s, nameof(Show.Genres)) => GenreRepository .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) .Then(x => s.Genres = x), - + (Show s, nameof(Show.People)) => PeopleRepository .GetFromShow(obj.ID) .Then(x => s.People = x), - - (Show s, nameof(Show.Seasons)) => SetRelation(s, + + (Show s, nameof(Show.Seasons)) => SetRelation(s, SeasonRepository.GetAll(x => x.Show.ID == obj.ID), (x, y) => x.Seasons = y, (x, y) => { x.Show = y; x.ShowID = y.ID; }), - - (Show s, nameof(Show.Episodes)) => SetRelation(s, + + (Show s, nameof(Show.Episodes)) => SetRelation(s, EpisodeRepository.GetAll(x => x.Show.ID == obj.ID), (x, y) => x.Episodes = y, (x, y) => { x.Show = y; x.ShowID = y.ID; }), - + (Show s, nameof(Show.Libraries)) => LibraryRepository .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) .Then(x => s.Libraries = x), - + (Show s, nameof(Show.Collections)) => CollectionRepository .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) .Then(x => s.Collections = x), - + (Show s, nameof(Show.Studio)) => StudioRepository .GetOrDefault(x => x.Shows.Any(y => y.ID == obj.ID)) .Then(x => @@ -292,18 +292,18 @@ namespace Kyoo.Core.Controllers s.Studio = x; s.StudioID = x?.ID ?? 0; }), - - - (Season s, nameof(Season.ExternalIDs)) => SetRelation(s, + + + (Season s, nameof(Season.ExternalIDs)) => SetRelation(s, ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, (x, y) => { x.ResourceID = y.ID; }), - - (Season s, nameof(Season.Episodes)) => SetRelation(s, + + (Season s, nameof(Season.Episodes)) => SetRelation(s, EpisodeRepository.GetAll(x => x.Season.ID == obj.ID), (x, y) => x.Episodes = y, (x, y) => { x.Season = y; x.SeasonID = y.ID; }), - + (Season s, nameof(Season.Show)) => ShowRepository .GetOrDefault(x => x.Seasons.Any(y => y.ID == obj.ID)) .Then(x => @@ -311,18 +311,18 @@ namespace Kyoo.Core.Controllers s.Show = x; s.ShowID = x?.ID ?? 0; }), - - - (Episode e, nameof(Episode.ExternalIDs)) => SetRelation(e, - ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), + + + (Episode e, nameof(Episode.ExternalIDs)) => SetRelation(e, + ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, (x, y) => { x.ResourceID = y.ID; }), - - (Episode e, nameof(Episode.Tracks)) => SetRelation(e, + + (Episode e, nameof(Episode.Tracks)) => SetRelation(e, TrackRepository.GetAll(x => x.Episode.ID == obj.ID), (x, y) => x.Tracks = y, (x, y) => { x.Episode = y; x.EpisodeID = y.ID; }), - + (Episode e, nameof(Episode.Show)) => ShowRepository .GetOrDefault(x => x.Episodes.Any(y => y.ID == obj.ID)) .Then(x => @@ -330,7 +330,7 @@ namespace Kyoo.Core.Controllers e.Show = x; e.ShowID = x?.ID ?? 0; }), - + (Episode e, nameof(Episode.Season)) => SeasonRepository .GetOrDefault(x => x.Episodes.Any(y => y.ID == e.ID)) .Then(x => @@ -338,8 +338,8 @@ namespace Kyoo.Core.Controllers e.Season = x; e.SeasonID = x?.ID ?? 0; }), - - + + (Track t, nameof(Track.Episode)) => EpisodeRepository .GetOrDefault(x => x.Tracks.Any(y => y.ID == obj.ID)) .Then(x => @@ -347,62 +347,62 @@ namespace Kyoo.Core.Controllers t.Episode = x; t.EpisodeID = x?.ID ?? 0; }), - - + + (Genre g, nameof(Genre.Shows)) => ShowRepository .GetAll(x => x.Genres.Any(y => y.ID == obj.ID)) .Then(x => g.Shows = x), - - + + (Studio s, nameof(Studio.Shows)) => ShowRepository .GetAll(x => x.Studio.ID == obj.ID) .Then(x => s.Shows = x), - - (Studio s, nameof(Studio.ExternalIDs)) => SetRelation(s, + + (Studio s, nameof(Studio.ExternalIDs)) => SetRelation(s, ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, (x, y) => { x.ResourceID = y.ID; }), - - - (People p, nameof(People.ExternalIDs)) => SetRelation(p, + + + (People p, nameof(People.ExternalIDs)) => SetRelation(p, ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, (x, y) => { x.ResourceID = y.ID; }), - + (People p, nameof(People.Roles)) => PeopleRepository .GetFromPeople(obj.ID) .Then(x => p.Roles = x), - - + + (Provider p, nameof(Provider.Libraries)) => LibraryRepository .GetAll(x => x.Providers.Any(y => y.ID == obj.ID)) .Then(x => p.Libraries = x), - + _ => throw new ArgumentException($"Couldn't find a way to load {memberName} of {obj.Slug}.") }; } /// - public Task> GetItemsFromLibrary(int id, - Expression> where = null, - Sort sort = default, + public Task> GetItemsFromLibrary(int id, + Expression> where = null, + Sort sort = default, Pagination limit = default) { return LibraryItemRepository.GetFromLibrary(id, where, sort, limit); } /// - public Task> GetItemsFromLibrary(string slug, - Expression> where = null, - Sort sort = default, + public Task> GetItemsFromLibrary(string slug, + Expression> where = null, + Sort sort = default, Pagination limit = default) { return LibraryItemRepository.GetFromLibrary(slug, where, sort, limit); } /// - public Task> GetPeopleFromShow(int showID, + public Task> GetPeopleFromShow(int showID, Expression> where = null, Sort sort = default, Pagination limit = default) @@ -411,16 +411,16 @@ namespace Kyoo.Core.Controllers } /// - public Task> GetPeopleFromShow(string showSlug, + public Task> GetPeopleFromShow(string showSlug, Expression> where = null, - Sort sort = default, + Sort sort = default, Pagination limit = default) { return PeopleRepository.GetFromShow(showSlug, where, sort, limit); } /// - public Task> GetRolesFromPeople(int id, + public Task> GetRolesFromPeople(int id, Expression> where = null, Sort sort = default, Pagination limit = default) @@ -454,7 +454,7 @@ namespace Kyoo.Core.Controllers /// public Task> GetAll(Expression> where = null, Sort sort = default, - Pagination limit = default) + Pagination limit = default) where T : class, IResource { return GetRepository().GetAll(where, sort, limit); @@ -468,19 +468,19 @@ namespace Kyoo.Core.Controllers } /// - public Task> Search(string query) + public Task> Search(string query) where T : class, IResource { return GetRepository().Search(query); } /// - public Task Create(T item) + public Task Create(T item) where T : class, IResource { return GetRepository().Create(item); } - + /// public Task CreateIfNotExists(T item) where T : class, IResource @@ -496,21 +496,21 @@ namespace Kyoo.Core.Controllers } /// - public Task Delete(T item) + public Task Delete(T item) where T : class, IResource { return GetRepository().Delete(item); } /// - public Task Delete(int id) + public Task Delete(int id) where T : class, IResource { return GetRepository().Delete(id); } /// - public Task Delete(string slug) + public Task Delete(string slug) where T : class, IResource { return GetRepository().Delete(slug); diff --git a/Kyoo.Core/Controllers/PassthroughPermissionValidator.cs b/Kyoo.Core/Controllers/PassthroughPermissionValidator.cs index 914835bf..ff9b07aa 100644 --- a/Kyoo.Core/Controllers/PassthroughPermissionValidator.cs +++ b/Kyoo.Core/Controllers/PassthroughPermissionValidator.cs @@ -14,7 +14,7 @@ namespace Kyoo.Core.Controllers { logger.LogWarning("No permission validator has been enabled, all users will have all permissions"); } - + /// public IFilterMetadata Create(PermissionAttribute attribute) { diff --git a/Kyoo.Core/Controllers/PluginManager.cs b/Kyoo.Core/Controllers/PluginManager.cs index 7fc2c6cf..77c00924 100644 --- a/Kyoo.Core/Controllers/PluginManager.cs +++ b/Kyoo.Core/Controllers/PluginManager.cs @@ -30,7 +30,7 @@ namespace Kyoo.Core.Controllers /// The logger used by this class. /// private readonly ILogger _logger; - + /// /// The list of plugins that are currently loaded. /// @@ -93,7 +93,7 @@ namespace Kyoo.Core.Controllers return Array.Empty(); } } - + /// public void LoadPlugins(ICollection plugins) { diff --git a/Kyoo.Core/Controllers/ProviderComposite.cs b/Kyoo.Core/Controllers/ProviderComposite.cs index 1032b022..d804860f 100644 --- a/Kyoo.Core/Controllers/ProviderComposite.cs +++ b/Kyoo.Core/Controllers/ProviderComposite.cs @@ -40,7 +40,7 @@ namespace Kyoo.Core.Controllers _providers = providers.ToArray(); _logger = logger; } - + /// public override void UseProviders(IEnumerable providers) @@ -71,9 +71,9 @@ namespace Kyoo.Core.Controllers { ret = Merger.Merge(ret, await provider.Get(ret)); } - catch (Exception ex) + catch (Exception ex) { - _logger.LogError(ex, "The provider {Provider} could not get a {Type}", + _logger.LogError(ex, "The provider {Provider} could not get a {Type}", provider.Provider.Name, typeof(T).Name); } } @@ -85,16 +85,16 @@ namespace Kyoo.Core.Controllers public override async Task> Search(string query) { List ret = new(); - + foreach (IMetadataProvider provider in _GetProviders()) { try { ret.AddRange(await provider.Search(query)); } - catch (Exception ex) + catch (Exception ex) { - _logger.LogError(ex, "The provider {Provider} could not search for {Type}", + _logger.LogError(ex, "The provider {Provider} could not search for {Type}", provider.Provider.Name, typeof(T).Name); } } diff --git a/Kyoo.Core/Controllers/RegexIdentifier.cs b/Kyoo.Core/Controllers/RegexIdentifier.cs index 1032d4bb..c3da511a 100644 --- a/Kyoo.Core/Controllers/RegexIdentifier.cs +++ b/Kyoo.Core/Controllers/RegexIdentifier.cs @@ -53,7 +53,7 @@ namespace Kyoo.Core.Controllers .FirstOrDefault(); return path[(libraryPath?.Length ?? 0)..]; } - + /// public async Task<(Collection, Show, Season, Episode)> Identify(string path) { @@ -77,21 +77,21 @@ namespace Kyoo.Core.Controllers Slug = Utility.ToSlug(match.Groups["Show"].Value), Title = match.Groups["Show"].Value, Path = Path.GetDirectoryName(path), - StartAir = match.Groups["StartYear"].Success - ? new DateTime(int.Parse(match.Groups["StartYear"].Value), 1, 1) + StartAir = match.Groups["StartYear"].Success + ? new DateTime(int.Parse(match.Groups["StartYear"].Value), 1, 1) : null }, season: null, episode: new Episode { - SeasonNumber = match.Groups["Season"].Success - ? int.Parse(match.Groups["Season"].Value) + SeasonNumber = match.Groups["Season"].Success + ? int.Parse(match.Groups["Season"].Value) : null, - EpisodeNumber = match.Groups["Episode"].Success - ? int.Parse(match.Groups["Episode"].Value) + EpisodeNumber = match.Groups["Episode"].Success + ? int.Parse(match.Groups["Episode"].Value) : null, - AbsoluteNumber = match.Groups["Absolute"].Success - ? int.Parse(match.Groups["Absolute"].Value) + AbsoluteNumber = match.Groups["Absolute"].Success + ? int.Parse(match.Groups["Absolute"].Value) : null, Path = path } diff --git a/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs b/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs index f6d1e220..d10b862c 100644 --- a/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs @@ -19,12 +19,12 @@ namespace Kyoo.Core.Controllers /// The database handle /// private readonly DatabaseContext _database; - + /// /// A provider repository to handle externalID creation and deletion /// private readonly IProviderRepository _providers; - + /// protected override Expression> DefaultSort => x => x.Name; @@ -58,12 +58,12 @@ namespace Kyoo.Core.Controllers await _database.SaveChangesAsync($"Trying to insert a duplicated collection (slug {obj.Slug} already exists)."); return obj; } - + /// protected override async Task Validate(Collection resource) { await base.Validate(resource); - + if (string.IsNullOrEmpty(resource.Slug)) throw new ArgumentException("The collection's slug must be set and not empty"); if (string.IsNullOrEmpty(resource.Name)) @@ -72,7 +72,7 @@ namespace Kyoo.Core.Controllers if (resource.ExternalIDs != null) { foreach (MetadataID id in resource.ExternalIDs) - { + { id.Provider = _database.LocalEntity(id.Provider.Slug) ?? await _providers.CreateIfNotExists(id.Provider); id.ProviderID = id.Provider.ID; @@ -80,12 +80,12 @@ namespace Kyoo.Core.Controllers _database.MetadataIds().AttachRange(resource.ExternalIDs); } } - + /// protected override async Task EditRelations(Collection resource, Collection changed, bool resetOld) { await Validate(changed); - + if (changed.ExternalIDs != null || resetOld) { await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); diff --git a/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs b/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs index f19acdb2..05f6b86d 100644 --- a/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs @@ -29,7 +29,7 @@ namespace Kyoo.Core.Controllers /// A track repository to handle creation and deletion of tracks related to the current episode. /// private readonly ITrackRepository _tracks; - + /// protected override Expression> DefaultSort => x => x.EpisodeNumber; @@ -42,27 +42,27 @@ namespace Kyoo.Core.Controllers /// A track repository public EpisodeRepository(DatabaseContext database, IProviderRepository providers, - ITrackRepository tracks) + ITrackRepository tracks) : base(database) { _database = database; _providers = providers; _tracks = tracks; } - + /// public Task GetOrDefault(int showID, int seasonNumber, int episodeNumber) { - return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID - && x.SeasonNumber == seasonNumber + return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID + && x.SeasonNumber == seasonNumber && x.EpisodeNumber == episodeNumber); } /// public Task GetOrDefault(string showSlug, int seasonNumber, int episodeNumber) { - return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug - && x.SeasonNumber == seasonNumber + return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug + && x.SeasonNumber == seasonNumber && x.EpisodeNumber == episodeNumber); } @@ -87,14 +87,14 @@ namespace Kyoo.Core.Controllers /// public Task GetAbsolute(int showID, int absoluteNumber) { - return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID + return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID && x.AbsoluteNumber == absoluteNumber); } /// public Task GetAbsolute(string showSlug, int absoluteNumber) { - return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug + return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug && x.AbsoluteNumber == absoluteNumber); } @@ -108,7 +108,7 @@ namespace Kyoo.Core.Controllers .Take(20) .ToListAsync(); } - + /// public override async Task Create(Episode obj) { @@ -146,7 +146,7 @@ namespace Kyoo.Core.Controllers { if (resource.Tracks == null) return resource; - + resource.Tracks = await resource.Tracks.SelectAsync(x => { x.Episode = resource; @@ -156,7 +156,7 @@ namespace Kyoo.Core.Controllers _database.Tracks.AttachRange(resource.Tracks); return resource; } - + /// protected override async Task Validate(Episode resource) { @@ -180,17 +180,17 @@ namespace Kyoo.Core.Controllers _database.MetadataIds().AttachRange(resource.ExternalIDs); } } - + /// public override async Task Delete(Episode obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); - + _database.Entry(obj).State = EntityState.Deleted; await obj.Tracks.ForEachAsync(x => _tracks.Delete(x)); obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Deleted); await _database.SaveChangesAsync(); } } -} \ No newline at end of file +} diff --git a/Kyoo.Core/Controllers/Repositories/GenreRepository.cs b/Kyoo.Core/Controllers/Repositories/GenreRepository.cs index f72b13e7..cb58406f 100644 --- a/Kyoo.Core/Controllers/Repositories/GenreRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/GenreRepository.cs @@ -19,11 +19,11 @@ namespace Kyoo.Core.Controllers /// The database handle /// private readonly DatabaseContext _database; - + /// protected override Expression> DefaultSort => x => x.Slug; - - + + /// /// Create a new . /// diff --git a/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs b/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs index ba075c09..ad6d3958 100644 --- a/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs @@ -34,7 +34,7 @@ namespace Kyoo.Core.Controllers /// /// The database instance /// A lazy loaded library repository - public LibraryItemRepository(DatabaseContext database, + public LibraryItemRepository(DatabaseContext database, Lazy libraries) : base(database) { @@ -42,13 +42,13 @@ namespace Kyoo.Core.Controllers _libraries = libraries; } - + /// public override Task GetOrDefault(int id) { return _database.LibraryItems.FirstOrDefaultAsync(x => x.ID == id); } - + /// public override Task GetOrDefault(string slug) { @@ -83,27 +83,27 @@ namespace Kyoo.Core.Controllers } /// - public override Task Create(LibraryItem obj) + public override Task Create(LibraryItem obj) => throw new InvalidOperationException(); - + /// - public override Task CreateIfNotExists(LibraryItem obj) + public override Task CreateIfNotExists(LibraryItem obj) => throw new InvalidOperationException(); - + /// - public override Task Edit(LibraryItem obj, bool resetOld) + public override Task Edit(LibraryItem obj, bool resetOld) => throw new InvalidOperationException(); - + /// - public override Task Delete(int id) + public override Task Delete(int id) => throw new InvalidOperationException(); - + /// - public override Task Delete(string slug) + public override Task Delete(string slug) => throw new InvalidOperationException(); - + /// - public override Task Delete(LibraryItem obj) + public override Task Delete(LibraryItem obj) => throw new InvalidOperationException(); /// @@ -125,8 +125,8 @@ namespace Kyoo.Core.Controllers /// public async Task> GetFromLibrary(int id, - Expression> where = null, - Sort sort = default, + Expression> where = null, + Sort sort = default, Pagination limit = default) { ICollection items = await ApplyFilters(LibraryRelatedQuery(x => x.ID == id), @@ -137,11 +137,11 @@ namespace Kyoo.Core.Controllers throw new ItemNotFoundException(); return items; } - + /// public async Task> GetFromLibrary(string slug, - Expression> where = null, - Sort sort = default, + Expression> where = null, + Sort sort = default, Pagination limit = default) { ICollection items = await ApplyFilters(LibraryRelatedQuery(x => x.Slug == slug), diff --git a/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs b/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs index 1e835114..933a2c07 100644 --- a/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs @@ -24,7 +24,7 @@ namespace Kyoo.Core.Controllers /// A provider repository to handle externalID creation and deletion /// private readonly IProviderRepository _providers; - + /// protected override Expression> DefaultSort => x => x.ID; @@ -41,7 +41,7 @@ namespace Kyoo.Core.Controllers _providers = providers; } - + /// public override async Task> Search(string query) { @@ -65,14 +65,14 @@ namespace Kyoo.Core.Controllers protected override async Task Validate(Library resource) { await base.Validate(resource); - + if (string.IsNullOrEmpty(resource.Slug)) throw new ArgumentException("The library's slug must be set and not empty"); if (string.IsNullOrEmpty(resource.Name)) throw new ArgumentException("The library's name must be set and not empty"); if (resource.Paths == null || !resource.Paths.Any()) throw new ArgumentException("The library should have a least one path."); - + if (resource.Providers != null) { resource.Providers = await resource.Providers @@ -99,7 +99,7 @@ namespace Kyoo.Core.Controllers { if (obj == null) throw new ArgumentNullException(nameof(obj)); - + _database.Entry(obj).State = EntityState.Deleted; await _database.SaveChangesAsync(); } diff --git a/Kyoo.Core/Controllers/Repositories/LocalRepository.cs b/Kyoo.Core/Controllers/Repositories/LocalRepository.cs index 55c6e259..0a729adb 100644 --- a/Kyoo.Core/Controllers/Repositories/LocalRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/LocalRepository.cs @@ -30,8 +30,8 @@ namespace Kyoo.Core.Controllers /// The default sort order that will be used for this resource's type. /// protected abstract Expression> DefaultSort { get; } - - + + /// /// Create a new base with the given database handle. /// @@ -57,7 +57,7 @@ namespace Kyoo.Core.Controllers throw new ItemNotFoundException($"No {typeof(T).Name} found with the id {id}"); return ret; } - + /// public virtual async Task Get(int id) { @@ -84,19 +84,19 @@ namespace Kyoo.Core.Controllers throw new ItemNotFoundException($"No {typeof(T).Name} found with the given predicate."); return ret; } - + /// public virtual Task GetOrDefault(int id) { return Database.Set().FirstOrDefaultAsync(x => x.ID == id); } - + /// public virtual Task GetOrDefault(string slug) { return Database.Set().FirstOrDefaultAsync(x => x.Slug == slug); } - + /// public virtual Task GetOrDefault(Expression> where) { @@ -105,7 +105,7 @@ namespace Kyoo.Core.Controllers /// public abstract Task> Search(string query); - + /// public virtual Task> GetAll(Expression> where = null, Sort sort = default, @@ -113,7 +113,7 @@ namespace Kyoo.Core.Controllers { return ApplyFilters(Database.Set(), where, sort, limit); } - + /// /// Apply filters to a query to ease sort, pagination & where queries for resources of this repository /// @@ -124,12 +124,12 @@ namespace Kyoo.Core.Controllers /// The filtered query protected Task> ApplyFilters(IQueryable query, Expression> where = null, - Sort sort = default, + Sort sort = default, Pagination limit = default) { return ApplyFilters(query, GetOrDefault, DefaultSort, where, sort, limit); } - + /// /// Apply filters to a query to ease sort, pagination & where queries for any resources types. /// For resources of type , see @@ -145,17 +145,17 @@ namespace Kyoo.Core.Controllers Func> get, Expression> defaultSort, Expression> where = null, - Sort sort = default, + Sort sort = default, Pagination limit = default) { if (where != null) query = query.Where(where); - + Expression> sortKey = sort.Key ?? defaultSort; Expression sortExpression = sortKey.Body.NodeType == ExpressionType.Convert ? ((UnaryExpression)sortKey.Body).Operand : sortKey.Body; - + if (typeof(Enum).IsAssignableFrom(sortExpression.Type)) throw new ArgumentException("Invalid sort key."); @@ -205,7 +205,7 @@ namespace Kyoo.Core.Controllers T old = await GetOrDefault(obj.Slug); if (old != null) return old; - + return await Create(obj); } catch (DuplicatedItemException) @@ -225,7 +225,7 @@ namespace Kyoo.Core.Controllers try { T old = await GetWithTracking(edited.ID); - + if (resetOld) old = Merger.Nullify(old); Merger.Complete(old, edited, x => x.GetCustomAttribute() == null); @@ -239,7 +239,7 @@ namespace Kyoo.Core.Controllers Database.ChangeTracker.Clear(); } } - + /// /// An overridable method to edit relation of a resource. /// @@ -257,7 +257,7 @@ namespace Kyoo.Core.Controllers { return Validate(resource); } - + /// /// A method called just before saving a new resource to the database. /// It is also called on the default implementation of @@ -278,7 +278,7 @@ namespace Kyoo.Core.Controllers { MethodInfo setter = typeof(T).GetProperty(nameof(resource.Slug))!.GetSetMethod(); if (setter != null) - setter.Invoke(resource, new object[] {resource.Slug + '!'}); + setter.Invoke(resource, new object[] { resource.Slug + '!' }); else throw new ArgumentException("Resources slug can't be number only."); } @@ -306,7 +306,7 @@ namespace Kyoo.Core.Controllers /// public abstract Task Delete(T obj); - + /// public async Task DeleteAll(Expression> where) { diff --git a/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs b/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs index 9b8eb38f..ca615782 100644 --- a/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs @@ -29,7 +29,7 @@ namespace Kyoo.Core.Controllers /// A lazy loaded show repository to validate requests from shows. /// private readonly Lazy _shows; - + /// protected override Expression> DefaultSort => x => x.Name; @@ -41,14 +41,14 @@ namespace Kyoo.Core.Controllers /// A lazy loaded show repository public PeopleRepository(DatabaseContext database, IProviderRepository providers, - Lazy shows) + Lazy shows) : base(database) { _database = database; _providers = providers; _shows = shows; } - + /// public override async Task> Search(string query) @@ -89,7 +89,7 @@ namespace Kyoo.Core.Controllers { foreach (PeopleRole role in resource.Roles) { - role.Show = _database.LocalEntity(role.Show.Slug) + role.Show = _database.LocalEntity(role.Show.Slug) ?? await _shows.Value.CreateIfNotExists(role.Show); role.ShowID = role.Show.ID; _database.Entry(role).State = EntityState.Added; @@ -101,7 +101,7 @@ namespace Kyoo.Core.Controllers protected override async Task EditRelations(People resource, People changed, bool resetOld) { await Validate(changed); - + if (changed.Roles != null || resetOld) { await Database.Entry(resource).Collection(x => x.Roles).LoadAsync(); @@ -120,7 +120,7 @@ namespace Kyoo.Core.Controllers { if (obj == null) throw new ArgumentNullException(nameof(obj)); - + _database.Entry(obj).State = EntityState.Deleted; obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Deleted); obj.Roles.ForEach(x => _database.Entry(x).State = EntityState.Deleted); @@ -128,9 +128,9 @@ namespace Kyoo.Core.Controllers } /// - public async Task> GetFromShow(int showID, - Expression> where = null, - Sort sort = default, + public async Task> GetFromShow(int showID, + Expression> where = null, + Sort sort = default, Pagination limit = default) { ICollection people = await ApplyFilters(_database.PeopleRoles @@ -151,7 +151,7 @@ namespace Kyoo.Core.Controllers /// public async Task> GetFromShow(string showSlug, Expression> where = null, - Sort sort = default, + Sort sort = default, Pagination limit = default) { ICollection people = await ApplyFilters(_database.PeopleRoles @@ -169,11 +169,11 @@ namespace Kyoo.Core.Controllers role.ForPeople = true; return people; } - + /// public async Task> GetFromPeople(int id, Expression> where = null, - Sort sort = default, + Sort sort = default, Pagination limit = default) { ICollection roles = await ApplyFilters(_database.PeopleRoles @@ -188,11 +188,11 @@ namespace Kyoo.Core.Controllers throw new ItemNotFoundException(); return roles; } - + /// public async Task> GetFromPeople(string slug, Expression> where = null, - Sort sort = default, + Sort sort = default, Pagination limit = default) { ICollection roles = await ApplyFilters(_database.PeopleRoles diff --git a/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs b/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs index 1a22fe15..7075e1ad 100644 --- a/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs @@ -50,7 +50,7 @@ namespace Kyoo.Core.Controllers throw new ItemNotFoundException($"No season {seasonNumber} found for the show {showID}"); return ret; } - + /// public async Task Get(string showSlug, int seasonNumber) { @@ -63,14 +63,14 @@ namespace Kyoo.Core.Controllers /// public Task GetOrDefault(int showID, int seasonNumber) { - return _database.Seasons.FirstOrDefaultAsync(x => x.ShowID == showID + return _database.Seasons.FirstOrDefaultAsync(x => x.ShowID == showID && x.SeasonNumber == seasonNumber); } /// public Task GetOrDefault(string showSlug, int seasonNumber) { - return _database.Seasons.FirstOrDefaultAsync(x => x.Show.Slug == showSlug + return _database.Seasons.FirstOrDefaultAsync(x => x.Show.Slug == showSlug && x.SeasonNumber == seasonNumber); } @@ -121,14 +121,14 @@ namespace Kyoo.Core.Controllers protected override async Task EditRelations(Season resource, Season changed, bool resetOld) { await Validate(changed); - + if (changed.ExternalIDs != null || resetOld) { await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); resource.ExternalIDs = changed.ExternalIDs; } } - + /// public override async Task Delete(Season obj) { @@ -139,4 +139,4 @@ namespace Kyoo.Core.Controllers await _database.SaveChangesAsync(); } } -} \ No newline at end of file +} diff --git a/Kyoo.Core/Controllers/Repositories/ShowRepository.cs b/Kyoo.Core/Controllers/Repositories/ShowRepository.cs index dca498c7..740ea06f 100644 --- a/Kyoo.Core/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/ShowRepository.cs @@ -50,8 +50,8 @@ namespace Kyoo.Core.Controllers /// A provider repository public ShowRepository(DatabaseContext database, IStudioRepository studios, - IPeopleRepository people, - IGenreRepository genres, + IPeopleRepository people, + IGenreRepository genres, IProviderRepository providers) : base(database) { @@ -61,7 +61,7 @@ namespace Kyoo.Core.Controllers _genres = genres; _providers = providers; } - + /// public override async Task> Search(string query) @@ -82,7 +82,7 @@ namespace Kyoo.Core.Controllers await _database.SaveChangesAsync($"Trying to insert a duplicated show (slug {obj.Slug} already exists)."); return obj; } - + /// protected override async Task Validate(Show resource) { @@ -128,7 +128,7 @@ namespace Kyoo.Core.Controllers protected override async Task EditRelations(Show resource, Show changed, bool resetOld) { await Validate(changed); - + if (changed.Aliases != null || resetOld) resource.Aliases = changed.Aliases; @@ -137,7 +137,7 @@ namespace Kyoo.Core.Controllers await Database.Entry(resource).Reference(x => x.Studio).LoadAsync(); resource.Studio = changed.Studio; } - + if (changed.Genres != null || resetOld) { await Database.Entry(resource).Collection(x => x.Genres).LoadAsync(); @@ -177,7 +177,7 @@ namespace Kyoo.Core.Controllers await _database.SaveIfNoDuplicates(); } } - + /// public Task GetSlug(int showID) { @@ -185,7 +185,7 @@ namespace Kyoo.Core.Controllers .Select(x => x.Slug) .FirstOrDefaultAsync(); } - + /// public override async Task Delete(Show obj) { diff --git a/Kyoo.Core/Controllers/Repositories/StudioRepository.cs b/Kyoo.Core/Controllers/Repositories/StudioRepository.cs index 185e4420..24bf8122 100644 --- a/Kyoo.Core/Controllers/Repositories/StudioRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/StudioRepository.cs @@ -19,12 +19,12 @@ namespace Kyoo.Core.Controllers /// The database handle /// private readonly DatabaseContext _database; - + /// /// A provider repository to handle externalID creation and deletion /// private readonly IProviderRepository _providers; - + /// protected override Expression> DefaultSort => x => x.Name; @@ -40,7 +40,7 @@ namespace Kyoo.Core.Controllers _database = database; _providers = providers; } - + /// public override async Task> Search(string query) { @@ -59,7 +59,7 @@ namespace Kyoo.Core.Controllers await _database.SaveChangesAsync($"Trying to insert a duplicated studio (slug {obj.Slug} already exists)."); return obj; } - + /// protected override async Task Validate(Studio resource) { @@ -75,7 +75,7 @@ namespace Kyoo.Core.Controllers _database.MetadataIds().AttachRange(resource.ExternalIDs); } } - + /// protected override async Task EditRelations(Studio resource, Studio changed, bool resetOld) { @@ -93,7 +93,7 @@ namespace Kyoo.Core.Controllers { if (obj == null) throw new ArgumentNullException(nameof(obj)); - + _database.Entry(obj).State = EntityState.Deleted; await _database.SaveChangesAsync(); } diff --git a/Kyoo.Core/Controllers/Repositories/TrackRepository.cs b/Kyoo.Core/Controllers/Repositories/TrackRepository.cs index c6b7889c..ce582bb8 100644 --- a/Kyoo.Core/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/TrackRepository.cs @@ -18,7 +18,7 @@ namespace Kyoo.Core.Controllers /// The database handle /// private readonly DatabaseContext _database; - + /// protected override Expression> DefaultSort => x => x.TrackIndex; @@ -27,7 +27,7 @@ namespace Kyoo.Core.Controllers /// Create a new . /// /// The database handle - public TrackRepository(DatabaseContext database) + public TrackRepository(DatabaseContext database) : base(database) { _database = database; @@ -69,7 +69,7 @@ namespace Kyoo.Core.Controllers { if (obj == null) throw new ArgumentNullException(nameof(obj)); - + _database.Entry(obj).State = EntityState.Deleted; await _database.SaveChangesAsync(); } diff --git a/Kyoo.Core/Controllers/Repositories/UserRepository.cs b/Kyoo.Core/Controllers/Repositories/UserRepository.cs index 1b267ab6..d632a473 100644 --- a/Kyoo.Core/Controllers/Repositories/UserRepository.cs +++ b/Kyoo.Core/Controllers/Repositories/UserRepository.cs @@ -19,11 +19,11 @@ namespace Kyoo.Core.Controllers /// The database handle /// private readonly DatabaseContext _database; - + /// protected override Expression> DefaultSort => x => x.Username; - - + + /// /// Create a new /// @@ -33,7 +33,7 @@ namespace Kyoo.Core.Controllers { _database = database; } - + /// public override async Task> Search(string query) { @@ -43,7 +43,7 @@ namespace Kyoo.Core.Controllers .Take(20) .ToListAsync(); } - + /// public override async Task Create(User obj) { @@ -58,7 +58,7 @@ namespace Kyoo.Core.Controllers { if (obj == null) throw new ArgumentNullException(nameof(obj)); - + _database.Entry(obj).State = EntityState.Deleted; await _database.SaveChangesAsync(); } diff --git a/Kyoo.Core/Controllers/TaskManager.cs b/Kyoo.Core/Controllers/TaskManager.cs index af1d24e6..ba713e81 100644 --- a/Kyoo.Core/Controllers/TaskManager.cs +++ b/Kyoo.Core/Controllers/TaskManager.cs @@ -32,7 +32,7 @@ namespace Kyoo.Core.Controllers /// The metadata for this task (the slug, and other useful information). /// public TaskMetadataAttribute Metadata { get; set; } - + /// /// The function used to create the task object. /// @@ -53,23 +53,23 @@ namespace Kyoo.Core.Controllers /// The task currently queued. /// public ManagedTask Task { get; init; } - + /// /// The progress reporter that this task should use. /// public IProgress ProgressReporter { get; init; } - + /// /// The arguments to give to run the task with. /// public Dictionary Arguments { get; init; } - + /// /// A token informing the task that it should be cancelled or not. /// public CancellationToken? CancellationToken { get; init; } } - + /// /// The configuration instance used to get schedule information /// @@ -115,14 +115,14 @@ namespace Kyoo.Core.Controllers Metadata = x.Metadata, ScheduledDate = GetNextTaskDate(x.Metadata.Slug) }).ToList(); - + if (_tasks.Any()) _logger.LogTrace("Task manager initiated with: {Tasks}", _tasks.Select(x => x.Metadata.Name)); else _logger.LogInformation("Task manager initiated without any tasks"); } - - + + /// /// Triggered when the application host is ready to start the service. /// @@ -133,7 +133,7 @@ namespace Kyoo.Core.Controllers Task.Run(() => base.StartAsync(cancellationToken), CancellationToken.None); return Task.CompletedTask; } - + /// public override Task StopAsync(CancellationToken cancellationToken) { @@ -148,7 +148,7 @@ namespace Kyoo.Core.Controllers protected override async Task ExecuteAsync(CancellationToken cancellationToken) { _EnqueueStartupTasks(); - + while (!cancellationToken.IsCancellationRequested) { if (_queuedTasks.Any()) @@ -160,12 +160,12 @@ namespace Kyoo.Core.Controllers } catch (TaskFailedException ex) { - _logger.LogWarning("The task \"{Task}\" failed: {Message}", + _logger.LogWarning("The task \"{Task}\" failed: {Message}", task.Task.Metadata.Name, ex.Message); } catch (Exception e) { - _logger.LogError(e, "An unhandled exception occured while running the task {Task}", + _logger.LogError(e, "An unhandled exception occured while running the task {Task}", task.Task.Metadata.Name); } } @@ -188,7 +188,7 @@ namespace Kyoo.Core.Controllers /// If the number of arguments is invalid, if an argument can't be converted or if the task finds the argument /// invalid. /// - private async Task _RunTask(ManagedTask task, + private async Task _RunTask(ManagedTask task, [NotNull] IProgress progress, Dictionary arguments, CancellationToken? cancellationToken = null) @@ -220,14 +220,14 @@ namespace Kyoo.Core.Controllers return x.CreateValue(value ?? x.DefaultValue); })); - _logger.LogInformation("Task starting: {Task} ({Parameters})", + _logger.LogInformation("Task starting: {Task} ({Parameters})", task.Metadata.Name, args.ToDictionary(x => x.Name, x => x.As())); - + CancellationToken token = cancellationToken != null ? CancellationTokenSource.CreateLinkedTokenSource(_taskToken.Token, cancellationToken.Value).Token : _taskToken.Token; await taskObj.Value.Run(args, progress, token); - + _logger.LogInformation("Task finished: {Task}", task.Metadata.Name); _runningTask = null; } @@ -261,13 +261,13 @@ namespace Kyoo.Core.Controllers } /// - public void StartTask(string taskSlug, + public void StartTask(string taskSlug, IProgress progress, Dictionary arguments = null, CancellationToken? cancellationToken = null) { arguments ??= new Dictionary(); - + int index = _tasks.FindIndex(x => x.Metadata.Slug == taskSlug); if (index == -1) throw new ItemNotFoundException($"No task found with the slug {taskSlug}"); @@ -276,13 +276,13 @@ namespace Kyoo.Core.Controllers Task = _tasks[index], ProgressReporter = progress, Arguments = arguments, - CancellationToken = cancellationToken + CancellationToken = cancellationToken }); _tasks[index].ScheduledDate = GetNextTaskDate(taskSlug); } /// - public void StartTask(IProgress progress, + public void StartTask(IProgress progress, Dictionary arguments = null, CancellationToken? cancellationToken = null) where T : ITask @@ -304,12 +304,12 @@ namespace Kyoo.Core.Controllers return DateTime.Now + delay; return DateTime.MaxValue; } - + /// public ICollection<(TaskMetadataAttribute, ITask)> GetRunningTasks() { - return _runningTask == null - ? ArraySegment<(TaskMetadataAttribute, ITask)>.Empty + return _runningTask == null + ? ArraySegment<(TaskMetadataAttribute, ITask)>.Empty : new[] { _runningTask.Value }; } diff --git a/Kyoo.Core/Controllers/ThumbnailsManager.cs b/Kyoo.Core/Controllers/ThumbnailsManager.cs index d82db69f..0f414d62 100644 --- a/Kyoo.Core/Controllers/ThumbnailsManager.cs +++ b/Kyoo.Core/Controllers/ThumbnailsManager.cs @@ -28,7 +28,7 @@ namespace Kyoo.Core.Controllers /// /// The file manager to use. /// A logger to report errors - public ThumbnailsManager(IFileSystem files, + public ThumbnailsManager(IFileSystem files, ILogger logger) { _files = files; @@ -66,7 +66,7 @@ namespace Kyoo.Core.Controllers } /// - public async Task DownloadImages(T item, bool alwaysDownload = false) + public async Task DownloadImages(T item, bool alwaysDownload = false) where T : IThumbnails { if (item == null) @@ -84,7 +84,7 @@ namespace Kyoo.Core.Controllers if (alwaysDownload || !await _files.Exists(localPath)) ret |= await _DownloadImage(image, localPath, $"The image n°{id} of {name}"); } - + return ret; } @@ -99,7 +99,7 @@ namespace Kyoo.Core.Controllers { if (item == null) throw new ArgumentNullException(nameof(item)); - + string directory = await _files.GetExtraDirectory(item); string imageName = imageID switch { @@ -109,7 +109,7 @@ namespace Kyoo.Core.Controllers Images.Trailer => "trailer", _ => $"{imageID}" }; - + switch (item) { case Season season: @@ -123,7 +123,7 @@ namespace Kyoo.Core.Controllers return _files.Combine(directory, imageName); } - + /// public async Task GetImagePath(T item, int imageID) where T : IThumbnails diff --git a/Kyoo.Core/Controllers/Transcoder.cs b/Kyoo.Core/Controllers/Transcoder.cs index 1f2f8d08..fdbd28e1 100644 --- a/Kyoo.Core/Controllers/Transcoder.cs +++ b/Kyoo.Core/Controllers/Transcoder.cs @@ -13,8 +13,8 @@ using Stream = Kyoo.Core.Models.Watch.Stream; namespace Kyoo.Core.Controllers { - public class BadTranscoderException : Exception {} - + public class BadTranscoderException : Exception { } + public class Transcoder : ITranscoder { private static class TranscoderAPI @@ -26,7 +26,7 @@ namespace Kyoo.Core.Controllers public static int Init() => init(); - [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl, + [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] private static extern int transmux(string path, string outpath, out float playableDuration); @@ -37,9 +37,9 @@ namespace Kyoo.Core.Controllers return transmux(path, outPath, out playableDuration); } - [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl, + [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] - private static extern IntPtr extract_infos(string path, + private static extern IntPtr extract_infos(string path, string outpath, out uint length, out uint trackCount, @@ -53,12 +53,12 @@ namespace Kyoo.Core.Controllers { path = path.Replace('\\', '/'); outPath = outPath.Replace('\\', '/'); - + int size = Marshal.SizeOf(); IntPtr ptr = extract_infos(path, outPath, out uint arrayLength, out uint trackCount, reextract); IntPtr streamsPtr = ptr; Track[] tracks; - + if (trackCount > 0 && ptr != IntPtr.Zero) { tracks = new Track[trackCount]; @@ -113,7 +113,7 @@ namespace Kyoo.Core.Controllers { if (!File.Exists(episode.Path)) throw new ArgumentException("Path does not exists. Can't transcode."); - + string folder = Path.Combine(_options.Value.TransmuxPath, episode.Slug); string manifest = Path.Combine(folder, episode.Slug + ".m3u8"); float playableDuration = 0; @@ -130,7 +130,7 @@ namespace Kyoo.Core.Controllers await Console.Error.WriteLineAsync($"Access to the path {manifest} is denied. Please change your transmux path in the config."); return null; } - + Task.Factory.StartNew(() => { transmuxFailed = TranscoderAPI.Transmux(episode.Path, manifest, out playableDuration) != 0; diff --git a/Kyoo.Core/CoreModule.cs b/Kyoo.Core/CoreModule.cs index ef0961cc..0b04223f 100644 --- a/Kyoo.Core/CoreModule.cs +++ b/Kyoo.Core/CoreModule.cs @@ -29,10 +29,10 @@ namespace Kyoo.Core { /// public string Slug => "core"; - + /// public string Name => "Core"; - + /// public string Description => "The core module containing default implementations."; @@ -66,11 +66,11 @@ namespace Kyoo.Core public void Configure(ContainerBuilder builder) { builder.RegisterModule(); - + builder.RegisterComposite().InstancePerLifetimeScope(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); - + builder.RegisterType().As().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); @@ -78,7 +78,7 @@ namespace Kyoo.Core builder.RegisterType().As().InstancePerLifetimeScope(); builder.RegisterType().As().InstancePerLifetimeScope(); builder.RegisterType().As().SingleInstance(); - + builder.RegisterComposite(); builder.Register(x => (AProviderComposite)x.Resolve()); @@ -106,7 +106,7 @@ namespace Kyoo.Core builder.RegisterType().As() .IfNotRegistered(typeof(IPermissionValidator)); - + builder.RegisterType().As().SingleInstance() .OnActivating(x => { @@ -117,7 +117,7 @@ namespace Kyoo.Core x.Instance.Mappings[".m3u8"] = "application/x-mpegurl"; }); } - + /// public void Configure(IServiceCollection services) { @@ -130,12 +130,12 @@ namespace Kyoo.Core x.SerializerSettings.ContractResolver = new JsonPropertyIgnorer(publicUrl); x.SerializerSettings.Converters.Add(new PeopleRoleConverter()); }); - + services.AddResponseCompression(x => { x.EnableForHttps = true; }); - + services.AddHttpClient(); } diff --git a/Kyoo.Core/Models/FileExtensions.cs b/Kyoo.Core/Models/FileExtensions.cs index 26e520d1..b8d9f0ee 100644 --- a/Kyoo.Core/Models/FileExtensions.cs +++ b/Kyoo.Core/Models/FileExtensions.cs @@ -17,7 +17,7 @@ namespace Kyoo.Core.Models.Watch ".mkv", ".flv", ".vob", - ".ogg", + ".ogg", ".ogv", ".avi", ".mts", @@ -25,7 +25,7 @@ namespace Kyoo.Core.Models.Watch ".ts", ".mov", ".qt", - ".asf", + ".asf", ".mp4", ".m4p", ".m4v", @@ -48,11 +48,11 @@ namespace Kyoo.Core.Models.Watch { return VideoExtensions.Contains(Path.GetExtension(filePath)); } - + /// /// The dictionary of known subtitles extensions and the name of the subtitle codec. /// - public static readonly ImmutableDictionary SubtitleExtensions = new Dictionary + public static readonly ImmutableDictionary SubtitleExtensions = new Dictionary { {".ass", "ass"}, {".str", "subrip"} diff --git a/Kyoo.Core/Models/Options/BasicOptions.cs b/Kyoo.Core/Models/Options/BasicOptions.cs index 5bb108f8..81dd50df 100644 --- a/Kyoo.Core/Models/Options/BasicOptions.cs +++ b/Kyoo.Core/Models/Options/BasicOptions.cs @@ -32,7 +32,7 @@ namespace Kyoo.Core.Models.Options /// The temporary folder to cache transmuxed file. /// public string TransmuxPath { get; set; } = "cached/transmux"; - + /// /// The temporary folder to cache transcoded file. /// diff --git a/Kyoo.Core/Models/Options/MediaOptions.cs b/Kyoo.Core/Models/Options/MediaOptions.cs index 3133e8d2..9f2aba26 100644 --- a/Kyoo.Core/Models/Options/MediaOptions.cs +++ b/Kyoo.Core/Models/Options/MediaOptions.cs @@ -9,12 +9,12 @@ namespace Kyoo.Core.Models.Options /// The path of this options /// public const string Path = "Media"; - + /// /// A regex for episodes /// public string[] Regex { get; set; } - + /// /// A regex for subtitles /// diff --git a/Kyoo.Core/Models/Options/TaskOptions.cs b/Kyoo.Core/Models/Options/TaskOptions.cs index 89b77687..abd49a1a 100644 --- a/Kyoo.Core/Models/Options/TaskOptions.cs +++ b/Kyoo.Core/Models/Options/TaskOptions.cs @@ -13,7 +13,7 @@ namespace Kyoo.Core.Models.Options /// The path of this options /// public const string Path = "Tasks"; - + /// /// The number of tasks that can be run concurrently. /// diff --git a/Kyoo.Core/Models/Stream.cs b/Kyoo.Core/Models/Stream.cs index 2980adae..0161b16b 100644 --- a/Kyoo.Core/Models/Stream.cs +++ b/Kyoo.Core/Models/Stream.cs @@ -14,17 +14,17 @@ namespace Kyoo.Core.Models.Watch /// The title of the stream. /// public string Title { get; set; } - + /// /// The language of this stream (as a ISO-639-2 language code) /// public string Language { get; set; } - + /// /// The codec of this stream. /// public string Codec { get; set; } - + /// /// Is this stream the default one of it's type? /// @@ -34,12 +34,12 @@ namespace Kyoo.Core.Models.Watch /// Is this stream tagged as forced? /// [MarshalAs(UnmanagedType.I1)] public bool IsForced; - + /// /// The path of this track. /// [SerializeIgnore] public string Path { get; set; } - + /// /// The type of this stream. /// diff --git a/Kyoo.Core/PluginsStartup.cs b/Kyoo.Core/PluginsStartup.cs index 470c9a4f..74c800f9 100644 --- a/Kyoo.Core/PluginsStartup.cs +++ b/Kyoo.Core/PluginsStartup.cs @@ -69,7 +69,7 @@ namespace Kyoo.Core { foreach (IPlugin plugin in _plugins.GetAllPlugins()) plugin.Configure(services); - + IEnumerable> configTypes = _plugins.GetAllPlugins() .SelectMany(x => x.Configuration) .Where(x => x.Value != null); @@ -92,7 +92,7 @@ namespace Kyoo.Core { builder.RegisterInstance(_plugins).As().ExternallyOwned(); builder.RegisterTask(); - + foreach (IPlugin plugin in _plugins.GetAllPlugins()) plugin.Configure(builder); } @@ -125,8 +125,8 @@ namespace Kyoo.Core foreach ((string path, Type type) in pluginConfig) config.Register(path, type); } - - + + /// /// Create a new from a webhost. /// This is meant to be used from . @@ -136,7 +136,7 @@ namespace Kyoo.Core /// The logger factory used to log while the application is setting itself up. /// /// A new . - public static PluginsStartup FromWebHost(WebHostBuilderContext host, + public static PluginsStartup FromWebHost(WebHostBuilderContext host, ILoggerFactory logger) { HostServiceProvider hostProvider = new(host.HostingEnvironment, host.Configuration, logger); @@ -147,7 +147,7 @@ namespace Kyoo.Core ); 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. @@ -158,18 +158,18 @@ namespace Kyoo.Core /// The host environment that could be used by plugins to configure themself. /// private readonly IWebHostEnvironment _hostEnvironment; - + /// /// The configuration context. /// private readonly IConfiguration _configuration; - + /// /// A logger factory used to create a logger for the plugin manager. /// private readonly ILoggerFactory _loggerFactory; - + /// /// Create a new that will return given services when asked. /// diff --git a/Kyoo.Core/Tasks/Crawler.cs b/Kyoo.Core/Tasks/Crawler.cs index 3633fbb3..186f2c7e 100644 --- a/Kyoo.Core/Tasks/Crawler.cs +++ b/Kyoo.Core/Tasks/Crawler.cs @@ -52,8 +52,8 @@ namespace Kyoo.Core.Tasks _taskManager = taskManager; _logger = logger; } - - + + /// public TaskParameters GetParameters() { @@ -67,9 +67,9 @@ namespace Kyoo.Core.Tasks public async Task Run(TaskParameters arguments, IProgress progress, CancellationToken cancellationToken) { string argument = arguments["slug"].As(); - ICollection libraries = argument == null + ICollection libraries = argument == null ? await _libraryManager.GetAll() - : new [] { await _libraryManager.GetOrDefault(argument)}; + : new[] { await _libraryManager.GetOrDefault(argument) }; if (argument != null && libraries.First() == null) throw new ArgumentException($"No library found with the name {argument}"); @@ -79,7 +79,7 @@ namespace Kyoo.Core.Tasks progress.Report(0); float percent = 0; - + ICollection episodes = await _libraryManager.GetAll(); ICollection tracks = await _libraryManager.GetAll(); foreach (Library library in libraries) @@ -91,15 +91,15 @@ namespace Kyoo.Core.Tasks }); await Scan(library, episodes, tracks, reporter, cancellationToken); percent += 100f / libraries.Count; - + if (cancellationToken.IsCancellationRequested) return; } - + progress.Report(100); } - private async Task Scan(Library library, + private async Task Scan(Library library, IEnumerable episodes, IEnumerable tracks, IProgress progress, @@ -109,7 +109,7 @@ namespace Kyoo.Core.Tasks foreach (string path in library.Paths) { ICollection files = await _fileSystem.ListFiles(path, SearchOption.AllDirectories); - + if (cancellationToken.IsCancellationRequested) return; @@ -121,8 +121,8 @@ namespace Kyoo.Core.Tasks .Where(x => episodes.All(y => y.Path != x)) .GroupBy(Path.GetDirectoryName) .ToList(); - - + + string[] paths = shows.Select(x => x.First()) .Concat(shows.SelectMany(x => x.Skip(1))) .ToArray(); @@ -132,7 +132,7 @@ namespace Kyoo.Core.Tasks // ReSharper disable once AccessToModifiedClosure progress.Report((percent + x / paths.Length - 10) / library.Paths.Length); }); - + foreach (string episodePath in paths) { _taskManager.StartTask(reporter, new Dictionary @@ -143,7 +143,7 @@ namespace Kyoo.Core.Tasks percent += 100f / paths.Length; } - + string[] subtitles = files .Where(FileExtensions.IsSubtitle) .Where(x => !x.Contains("Extra")) @@ -155,7 +155,7 @@ namespace Kyoo.Core.Tasks // ReSharper disable once AccessToModifiedClosure progress.Report((90 + (percent + x / subtitles.Length)) / library.Paths.Length); }); - + foreach (string trackPath in subtitles) { _taskManager.StartTask(reporter, new Dictionary diff --git a/Kyoo.Core/Tasks/Housekeeping.cs b/Kyoo.Core/Tasks/Housekeeping.cs index 38d1c9f4..f27a05c4 100644 --- a/Kyoo.Core/Tasks/Housekeeping.cs +++ b/Kyoo.Core/Tasks/Housekeeping.cs @@ -57,10 +57,10 @@ namespace Kyoo.Core.Tasks { progress.Report(count / delCount * 100); count++; - + if (await _fileSystem.Exists(show.Path)) continue; - _logger.LogWarning("Show {Name}'s folder has been deleted (was {Path}), removing it from kyoo", + _logger.LogWarning("Show {Name}'s folder has been deleted (was {Path}), removing it from kyoo", show.Title, show.Path); await _libraryManager.Delete(show); } @@ -69,14 +69,14 @@ namespace Kyoo.Core.Tasks { progress.Report(count / delCount * 100); count++; - + if (await _fileSystem.Exists(episode.Path)) continue; - _logger.LogWarning("Episode {Slug}'s file has been deleted (was {Path}), removing it from kyoo", + _logger.LogWarning("Episode {Slug}'s file has been deleted (was {Path}), removing it from kyoo", episode.Slug, episode.Path); await _libraryManager.Delete(episode); } - + progress.Report(100); } } diff --git a/Kyoo.Core/Tasks/MetadataProviderLoader.cs b/Kyoo.Core/Tasks/MetadataProviderLoader.cs index cfe398ae..6a3270f3 100644 --- a/Kyoo.Core/Tasks/MetadataProviderLoader.cs +++ b/Kyoo.Core/Tasks/MetadataProviderLoader.cs @@ -40,7 +40,7 @@ namespace Kyoo.Core.Tasks /// /// The list of metadata providers to register. /// - public MetadataProviderLoader(IProviderRepository providers, + public MetadataProviderLoader(IProviderRepository providers, IThumbnailsManager thumbnails, ICollection metadataProviders) { @@ -61,7 +61,7 @@ namespace Kyoo.Core.Tasks { float percent = 0; progress.Report(0); - + foreach (IMetadataProvider provider in _metadataProviders) { if (string.IsNullOrEmpty(provider.Provider.Slug)) diff --git a/Kyoo.Core/Tasks/PluginInitializer.cs b/Kyoo.Core/Tasks/PluginInitializer.cs index a5b36b6d..ece55603 100644 --- a/Kyoo.Core/Tasks/PluginInitializer.cs +++ b/Kyoo.Core/Tasks/PluginInitializer.cs @@ -10,7 +10,7 @@ namespace Kyoo.Core.Tasks /// /// A task run on Kyoo's startup to initialize plugins /// - [TaskMetadata("plugin-init", "Plugin Initializer", "A task to initialize plugins.", + [TaskMetadata("plugin-init", "Plugin Initializer", "A task to initialize plugins.", RunOnStartup = true, Priority = int.MaxValue, IsHidden = true)] public class PluginInitializer : ITask { @@ -33,14 +33,14 @@ namespace Kyoo.Core.Tasks _pluginManager = pluginManager; _provider = provider; } - + /// public TaskParameters GetParameters() { return new(); } - + /// public Task Run(TaskParameters arguments, IProgress progress, CancellationToken cancellationToken) { @@ -51,11 +51,11 @@ namespace Kyoo.Core.Tasks foreach (IPlugin plugin in plugins) { plugin.Initialize(_provider); - + progress.Report(count / plugins.Count * 100); count++; } - + progress.Report(100); return Task.CompletedTask; } diff --git a/Kyoo.Core/Tasks/RegisterEpisode.cs b/Kyoo.Core/Tasks/RegisterEpisode.cs index d713a9d1..88913b3d 100644 --- a/Kyoo.Core/Tasks/RegisterEpisode.cs +++ b/Kyoo.Core/Tasks/RegisterEpisode.cs @@ -76,7 +76,7 @@ namespace Kyoo.Core.Tasks TaskParameter.CreateRequired("library", "The library in witch the episode is") }; } - + /// public async Task Run(TaskParameters arguments, IProgress progress, CancellationToken cancellationToken) { @@ -148,7 +148,7 @@ namespace Kyoo.Core.Tasks throw new TaskFailedException(ex); } } - + /// /// Retrieve the equivalent item if it already exists in the database, /// if it does not, fill metadata using the metadata provider, download images and register the item to the @@ -172,7 +172,7 @@ namespace Kyoo.Core.Tasks item = await _metadataProvider.Get(item); await _thumbnailsManager.DownloadImages(item); - + switch (item) { case Show show when show.People != null: diff --git a/Kyoo.Core/Tasks/RegisterSubtitle.cs b/Kyoo.Core/Tasks/RegisterSubtitle.cs index 3b931ab7..dffd5cdc 100644 --- a/Kyoo.Core/Tasks/RegisterSubtitle.cs +++ b/Kyoo.Core/Tasks/RegisterSubtitle.cs @@ -42,7 +42,7 @@ namespace Kyoo.Core.Tasks TaskParameter.CreateRequired("path", "The path of the subtitle file") }; } - + /// public async Task Run(TaskParameters arguments, IProgress progress, CancellationToken cancellationToken) { diff --git a/Kyoo.Core/Views/CollectionApi.cs b/Kyoo.Core/Views/CollectionApi.cs index 56af8b0a..3ad2ab08 100644 --- a/Kyoo.Core/Views/CollectionApi.cs +++ b/Kyoo.Core/Views/CollectionApi.cs @@ -22,17 +22,17 @@ namespace Kyoo.Core.Api private readonly IFileSystem _files; private readonly IThumbnailsManager _thumbs; - public CollectionApi(ILibraryManager libraryManager, - IFileSystem files, + public CollectionApi(ILibraryManager libraryManager, + IFileSystem files, IThumbnailsManager thumbs, - IOptions options) + IOptions options) : base(libraryManager.CollectionRepository, options.Value.PublicUrl) { _libraryManager = libraryManager; _files = files; _thumbs = thumbs; } - + [HttpGet("{id:int}/show")] [HttpGet("{id:int}/shows")] [PartialPermission(Kind.Read)] @@ -55,10 +55,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{slug}/show")] [HttpGet("{slug}/shows")] [PartialPermission(Kind.Read)] @@ -81,10 +81,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{id:int}/library")] [HttpGet("{id:int}/libraries")] [PartialPermission(Kind.Read)] @@ -107,10 +107,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{slug}/library")] [HttpGet("{slug}/libraries")] [PartialPermission(Kind.Read)] @@ -133,10 +133,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{slug}/poster")] public async Task GetPoster(string slug) { @@ -150,7 +150,7 @@ namespace Kyoo.Core.Api return NotFound(); } } - + [HttpGet("{slug}/logo")] public async Task GetLogo(string slug) { @@ -164,7 +164,7 @@ namespace Kyoo.Core.Api return NotFound(); } } - + [HttpGet("{slug}/backdrop")] [HttpGet("{slug}/thumbnail")] public async Task GetBackdrop(string slug) diff --git a/Kyoo.Core/Views/EpisodeApi.cs b/Kyoo.Core/Views/EpisodeApi.cs index c4fdf566..086e21e4 100644 --- a/Kyoo.Core/Views/EpisodeApi.cs +++ b/Kyoo.Core/Views/EpisodeApi.cs @@ -25,7 +25,7 @@ namespace Kyoo.Core.Api public EpisodeApi(ILibraryManager libraryManager, IOptions options, IFileSystem files, - IThumbnailsManager thumbnails) + IThumbnailsManager thumbnails) : base(libraryManager.EpisodeRepository, options.Value.PublicUrl) { _libraryManager = libraryManager; @@ -37,12 +37,12 @@ namespace Kyoo.Core.Api [PartialPermission(Kind.Read)] public async Task> GetShow(int episodeID) { - Show ret = await _libraryManager.GetOrDefault(x => x.Episodes.Any(y => y.ID == episodeID)); + Show ret = await _libraryManager.GetOrDefault(x => x.Episodes.Any(y => y.ID == episodeID)); if (ret == null) return NotFound(); return ret; } - + [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/show")] [PartialPermission(Kind.Read)] public async Task> GetShow(string showSlug, int seasonNumber, int episodeNumber) @@ -52,7 +52,7 @@ namespace Kyoo.Core.Api return NotFound(); return ret; } - + [HttpGet("{showID:int}-{seasonNumber:int}e{episodeNumber:int}/show")] [PartialPermission(Kind.Read)] public async Task> GetShow(int showID, int seasonNumber, int episodeNumber) @@ -62,7 +62,7 @@ namespace Kyoo.Core.Api return NotFound(); return ret; } - + [HttpGet("{episodeID:int}/season")] [PartialPermission(Kind.Read)] public async Task> GetSeason(int episodeID) @@ -72,7 +72,7 @@ namespace Kyoo.Core.Api return NotFound(); return ret; } - + [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/season")] [PartialPermission(Kind.Read)] public async Task> GetSeason(string showSlug, int seasonNumber, int episodeNumber) @@ -86,7 +86,7 @@ namespace Kyoo.Core.Api return NotFound(); } } - + [HttpGet("{showID:int}-{seasonNumber:int}e{episodeNumber:int}/season")] [PartialPermission(Kind.Read)] public async Task> GetSeason(int showID, int seasonNumber, int episodeNumber) @@ -100,7 +100,7 @@ namespace Kyoo.Core.Api return NotFound(); } } - + [HttpGet("{episodeID:int}/track")] [HttpGet("{episodeID:int}/tracks")] [PartialPermission(Kind.Read)] @@ -123,10 +123,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{showID:int}-s{seasonNumber:int}e{episodeNumber:int}/track")] [HttpGet("{showID:int}-s{seasonNumber:int}e{episodeNumber:int}/tracks")] [PartialPermission(Kind.Read)] @@ -141,7 +141,7 @@ namespace Kyoo.Core.Api try { ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, x => x.Episode.ShowID == showID + ApiHelper.ParseWhere(where, x => x.Episode.ShowID == showID && x.Episode.SeasonNumber == seasonNumber && x.Episode.EpisodeNumber == episodeNumber), new Sort(sortBy), @@ -153,10 +153,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{slug}-s{seasonNumber:int}e{episodeNumber:int}/track")] [HttpGet("{slug}-s{seasonNumber:int}e{episodeNumber:int}/tracks")] [PartialPermission(Kind.Read)] @@ -171,7 +171,7 @@ namespace Kyoo.Core.Api try { ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, x => x.Episode.Show.Slug == slug + ApiHelper.ParseWhere(where, x => x.Episode.Show.Slug == slug && x.Episode.SeasonNumber == seasonNumber && x.Episode.EpisodeNumber == episodeNumber), new Sort(sortBy), @@ -183,10 +183,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{id:int}/thumbnail")] [HttpGet("{id:int}/backdrop")] public async Task GetThumb(int id) @@ -201,7 +201,7 @@ namespace Kyoo.Core.Api return NotFound(); } } - + [HttpGet("{slug}/thumbnail")] [HttpGet("{slug}/backdrop")] public async Task GetThumb(string slug) @@ -217,4 +217,4 @@ namespace Kyoo.Core.Api } } } -} \ No newline at end of file +} diff --git a/Kyoo.Core/Views/GenreApi.cs b/Kyoo.Core/Views/GenreApi.cs index d6e6a678..aa74aded 100644 --- a/Kyoo.Core/Views/GenreApi.cs +++ b/Kyoo.Core/Views/GenreApi.cs @@ -24,7 +24,7 @@ namespace Kyoo.Core.Api { _libraryManager = libraryManager; } - + [HttpGet("{id:int}/show")] [HttpGet("{id:int}/shows")] [PartialPermission(Kind.Read)] @@ -47,10 +47,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{slug}/show")] [HttpGet("{slug}/shows")] [PartialPermission(Kind.Read)] @@ -73,7 +73,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } } diff --git a/Kyoo.Core/Views/Helper/ApiHelper.cs b/Kyoo.Core/Views/Helper/ApiHelper.cs index 31e545c6..35d9cd88 100644 --- a/Kyoo.Core/Views/Helper/ApiHelper.cs +++ b/Kyoo.Core/Views/Helper/ApiHelper.cs @@ -21,8 +21,8 @@ namespace Kyoo.Core.Api } return operand(left, right); } - - public static Expression> ParseWhere(Dictionary where, + + public static Expression> ParseWhere(Dictionary where, Expression> defaultWhere = null) { if (where == null || where.Count == 0) @@ -35,7 +35,7 @@ namespace Kyoo.Core.Api { if (key == null || desired == null) throw new ArgumentException("Invalid key/value pair. Can't be null."); - + string value = desired; string operand = "eq"; if (desired.Contains(':')) @@ -68,15 +68,15 @@ namespace Kyoo.Core.Api valueExpr = Expression.Constant(val, property.PropertyType); } - + Expression condition = operand switch { "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" => Expression.Equal(propertyExpr, valueExpr), "not" => Expression.NotEqual(propertyExpr, valueExpr!), "lt" => StringCompatibleExpression(Expression.LessThan, propertyExpr, valueExpr), @@ -110,11 +110,11 @@ namespace Kyoo.Core.Api valueConst = Expression.Constant(value); } - return notEqual - ? Expression.NotEqual(field, valueConst) + return notEqual + ? Expression.NotEqual(field, valueConst) : Expression.Equal(field, valueConst); } - + private static Expression ContainsResourceExpression(MemberExpression xProperty, string value) { // x => x.PROPERTY.Any(y => y.Slug == value) diff --git a/Kyoo.Core/Views/Helper/CrudApi.cs b/Kyoo.Core/Views/Helper/CrudApi.cs index ab1eef72..e8010d58 100644 --- a/Kyoo.Core/Views/Helper/CrudApi.cs +++ b/Kyoo.Core/Views/Helper/CrudApi.cs @@ -54,10 +54,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet] [PartialPermission(Kind.Read)] public virtual async Task>> GetAll([FromQuery] string sortBy, @@ -75,7 +75,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } @@ -98,7 +98,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } catch (DuplicatedItemException) { @@ -106,7 +106,7 @@ namespace Kyoo.Core.Api return Conflict(existing); } } - + [HttpPut] [PartialPermission(Kind.Write)] public virtual async Task> Edit([FromQuery] bool resetOld, [FromBody] T resource) @@ -140,7 +140,7 @@ namespace Kyoo.Core.Api return NotFound(); } } - + [HttpPut("{slug}")] [PartialPermission(Kind.Write)] public virtual async Task> Edit(string slug, [FromQuery] bool resetOld, [FromBody] T resource) @@ -172,7 +172,7 @@ namespace Kyoo.Core.Api return Ok(); } - + [HttpDelete("{slug}")] [PartialPermission(Kind.Delete)] public virtual async Task Delete(string slug) @@ -188,7 +188,7 @@ namespace Kyoo.Core.Api return Ok(); } - + [PartialPermission(Kind.Delete)] public virtual async Task Delete(Dictionary where) { diff --git a/Kyoo.Core/Views/Helper/JsonSerializer.cs b/Kyoo.Core/Views/Helper/JsonSerializer.cs index 96d6d6ea..1cd78e05 100644 --- a/Kyoo.Core/Views/Helper/JsonSerializer.cs +++ b/Kyoo.Core/Views/Helper/JsonSerializer.cs @@ -81,7 +81,7 @@ namespace Kyoo.Core.Api 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); @@ -93,7 +93,7 @@ namespace Kyoo.Core.Api value.People.Roles = oldRoles; } - public override PeopleRole ReadJson(JsonReader reader, + public override PeopleRole ReadJson(JsonReader reader, Type objectType, PeopleRole existingValue, bool hasExistingValue, @@ -113,7 +113,7 @@ namespace Kyoo.Core.Api _format = format; _host = host.TrimEnd('/'); } - + public object GetValue(object target) { return Regex.Replace(_format, @"(? @@ -123,7 +123,7 @@ namespace Kyoo.Core.Api if (value == "HOST") return _host; - + PropertyInfo properties = target.GetType() .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .FirstOrDefault(y => y.Name == value); @@ -147,7 +147,7 @@ namespace Kyoo.Core.Api 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/ResourceViewAttribute.cs b/Kyoo.Core/Views/Helper/ResourceViewAttribute.cs index ad4d9055..b3d3a2a8 100644 --- a/Kyoo.Core/Views/Helper/ResourceViewAttribute.cs +++ b/Kyoo.Core/Views/Helper/ResourceViewAttribute.cs @@ -40,7 +40,7 @@ namespace Kyoo.Core.Api type = Utility.GetGenericDefinition(type, typeof(Task<>))?.GetGenericArguments()[0] ?? type; type = Utility.GetGenericDefinition(type, typeof(ActionResult<>))?.GetGenericArguments()[0] ?? type; type = Utility.GetGenericDefinition(type, typeof(Page<>))?.GetGenericArguments()[0] ?? type; - + PropertyInfo[] properties = type.GetProperties() .Where(x => x.GetCustomAttribute() != null) .ToArray(); @@ -90,7 +90,7 @@ namespace Kyoo.Core.Api ICollection fields = (ICollection)context.HttpContext.Items["fields"]; Type pageType = Utility.GetGenericDefinition(result.DeclaredType, typeof(Page<>)); - + if (pageType != null) { foreach (IResource resource in ((dynamic)result.Value).Items) diff --git a/Kyoo.Core/Views/LibraryApi.cs b/Kyoo.Core/Views/LibraryApi.cs index 6dd4faba..454b8836 100644 --- a/Kyoo.Core/Views/LibraryApi.cs +++ b/Kyoo.Core/Views/LibraryApi.cs @@ -34,7 +34,7 @@ namespace Kyoo.Core.Api if (result.Value != null) _taskManager.StartTask("scan", new Progress(), - new Dictionary {{"slug", result.Value.Slug}}); + new Dictionary { { "slug", result.Value.Slug } }); return result; } @@ -60,7 +60,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } @@ -86,7 +86,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } @@ -112,7 +112,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } @@ -138,7 +138,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } @@ -164,7 +164,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } @@ -190,7 +190,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } } diff --git a/Kyoo.Core/Views/LibraryItemApi.cs b/Kyoo.Core/Views/LibraryItemApi.cs index 207eae9c..549f9b11 100644 --- a/Kyoo.Core/Views/LibraryItemApi.cs +++ b/Kyoo.Core/Views/LibraryItemApi.cs @@ -42,7 +42,7 @@ namespace Kyoo.Core.Api new Sort(sortBy), new Pagination(limit, afterID)); - return new Page(resources, + return new Page(resources, new Uri(_baseURL, Request.Path), Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase), limit); @@ -53,7 +53,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } } diff --git a/Kyoo.Core/Views/PeopleApi.cs b/Kyoo.Core/Views/PeopleApi.cs index 5135aa86..3fcd5932 100644 --- a/Kyoo.Core/Views/PeopleApi.cs +++ b/Kyoo.Core/Views/PeopleApi.cs @@ -23,7 +23,7 @@ namespace Kyoo.Core.Api public PeopleApi(ILibraryManager libraryManager, IOptions options, IFileSystem files, - IThumbnailsManager thumbs) + IThumbnailsManager thumbs) : base(libraryManager.PeopleRepository, options.Value.PublicUrl) { _libraryManager = libraryManager; @@ -55,7 +55,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } @@ -83,10 +83,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{id:int}/poster")] public async Task GetPeopleIcon(int id) { @@ -95,7 +95,7 @@ namespace Kyoo.Core.Api return NotFound(); return _files.FileResult(await _thumbs.GetImagePath(people, Images.Poster)); } - + [HttpGet("{slug}/poster")] public async Task GetPeopleIcon(string slug) { diff --git a/Kyoo.Core/Views/ProviderApi.cs b/Kyoo.Core/Views/ProviderApi.cs index 431fefed..1e6a297c 100644 --- a/Kyoo.Core/Views/ProviderApi.cs +++ b/Kyoo.Core/Views/ProviderApi.cs @@ -17,7 +17,7 @@ namespace Kyoo.Core.Api private readonly IThumbnailsManager _thumbnails; private readonly ILibraryManager _libraryManager; private readonly IFileSystem _files; - + public ProviderApi(ILibraryManager libraryManager, IOptions options, IFileSystem files, @@ -28,7 +28,7 @@ namespace Kyoo.Core.Api _files = files; _thumbnails = thumbnails; } - + [HttpGet("{id:int}/logo")] public async Task GetLogo(int id) { @@ -37,7 +37,7 @@ namespace Kyoo.Core.Api return NotFound(); return _files.FileResult(await _thumbnails.GetImagePath(provider, Images.Logo)); } - + [HttpGet("{slug}/logo")] public async Task GetLogo(string slug) { diff --git a/Kyoo.Core/Views/SearchApi.cs b/Kyoo.Core/Views/SearchApi.cs index a6e17c91..5138f3a1 100644 --- a/Kyoo.Core/Views/SearchApi.cs +++ b/Kyoo.Core/Views/SearchApi.cs @@ -38,7 +38,7 @@ namespace Kyoo.Core.Api Studios = await _libraryManager.Search(query) }; } - + [HttpGet("collection")] [HttpGet("collections")] [Permission(nameof(Collection), Kind.Read)] @@ -46,7 +46,7 @@ namespace Kyoo.Core.Api { return _libraryManager.Search(query); } - + [HttpGet("show")] [HttpGet("shows")] [Permission(nameof(Show), Kind.Read)] @@ -54,7 +54,7 @@ namespace Kyoo.Core.Api { return _libraryManager.Search(query); } - + [HttpGet("episode")] [HttpGet("episodes")] [Permission(nameof(Episode), Kind.Read)] @@ -62,14 +62,14 @@ namespace Kyoo.Core.Api { return _libraryManager.Search(query); } - + [HttpGet("people")] [Permission(nameof(People), Kind.Read)] public Task> SearchPeople(string query) { return _libraryManager.Search(query); } - + [HttpGet("genre")] [HttpGet("genres")] [Permission(nameof(Genre), Kind.Read)] @@ -77,7 +77,7 @@ namespace Kyoo.Core.Api { return _libraryManager.Search(query); } - + [HttpGet("studio")] [HttpGet("studios")] [Permission(nameof(Studio), Kind.Read)] diff --git a/Kyoo.Core/Views/SeasonApi.cs b/Kyoo.Core/Views/SeasonApi.cs index f7be874a..d0ab4f16 100644 --- a/Kyoo.Core/Views/SeasonApi.cs +++ b/Kyoo.Core/Views/SeasonApi.cs @@ -31,7 +31,7 @@ namespace Kyoo.Core.Api _thumbs = thumbs; _files = files; } - + [HttpGet("{seasonID:int}/episode")] [HttpGet("{seasonID:int}/episodes")] [PartialPermission(Kind.Read)] @@ -54,10 +54,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{showSlug}-s{seasonNumber:int}/episode")] [HttpGet("{showSlug}-s{seasonNumber:int}/episodes")] [PartialPermission(Kind.Read)] @@ -71,7 +71,7 @@ namespace Kyoo.Core.Api try { ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, x => x.Show.Slug == showSlug + ApiHelper.ParseWhere(where, x => x.Show.Slug == showSlug && x.SeasonNumber == seasonNumber), new Sort(sortBy), new Pagination(limit, afterID)); @@ -82,10 +82,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{showID:int}-s{seasonNumber:int}/episode")] [HttpGet("{showID:int}-s{seasonNumber:int}/episodes")] [PartialPermission(Kind.Read)] @@ -109,10 +109,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{seasonID:int}/show")] [PartialPermission(Kind.Read)] public async Task> GetShow(int seasonID) @@ -122,7 +122,7 @@ namespace Kyoo.Core.Api return NotFound(); return ret; } - + [HttpGet("{showSlug}-s{seasonNumber:int}/show")] [PartialPermission(Kind.Read)] public async Task> GetShow(string showSlug, int seasonNumber) @@ -132,7 +132,7 @@ namespace Kyoo.Core.Api return NotFound(); return ret; } - + [HttpGet("{showID:int}-s{seasonNumber:int}/show")] [PartialPermission(Kind.Read)] public async Task> GetShow(int showID, int seasonNumber) @@ -142,7 +142,7 @@ namespace Kyoo.Core.Api return NotFound(); return ret; } - + [HttpGet("{id:int}/poster")] public async Task GetPoster(int id) { @@ -152,7 +152,7 @@ namespace Kyoo.Core.Api await _libraryManager.Load(season, x => x.Show); return _files.FileResult(await _thumbs.GetImagePath(season, Images.Poster)); } - + [HttpGet("{slug}/poster")] public async Task GetPoster(string slug) { @@ -163,4 +163,4 @@ namespace Kyoo.Core.Api return _files.FileResult(await _thumbs.GetImagePath(season, Images.Poster)); } } -} \ No newline at end of file +} diff --git a/Kyoo.Core/Views/ShowApi.cs b/Kyoo.Core/Views/ShowApi.cs index fe0aaeee..7db85962 100644 --- a/Kyoo.Core/Views/ShowApi.cs +++ b/Kyoo.Core/Views/ShowApi.cs @@ -26,7 +26,7 @@ namespace Kyoo.Core.Api private readonly IThumbnailsManager _thumbs; public ShowApi(ILibraryManager libraryManager, - IFileSystem files, + IFileSystem files, IThumbnailsManager thumbs, IOptions options) : base(libraryManager.ShowRepository, options.Value.PublicUrl) @@ -58,7 +58,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } @@ -84,10 +84,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{showID:int}/episode")] [HttpGet("{showID:int}/episodes")] [PartialPermission(Kind.Read)] @@ -110,7 +110,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } @@ -136,10 +136,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{showID:int}/people")] [PartialPermission(Kind.Read)] public async Task>> GetPeople(int showID, @@ -161,7 +161,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } @@ -186,10 +186,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{showID:int}/genre")] [HttpGet("{showID:int}/genres")] [PartialPermission(Kind.Read)] @@ -212,7 +212,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } @@ -238,10 +238,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{showID:int}/studio")] [PartialPermission(Kind.Read)] public async Task> GetStudio(int showID) @@ -269,7 +269,7 @@ namespace Kyoo.Core.Api return NotFound(); } } - + [HttpGet("{showID:int}/library")] [HttpGet("{showID:int}/libraries")] [PartialPermission(Kind.Read)] @@ -292,7 +292,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } @@ -318,10 +318,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{showID:int}/collection")] [HttpGet("{showID:int}/collections")] [PartialPermission(Kind.Read)] @@ -344,7 +344,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } @@ -370,7 +370,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } @@ -392,7 +392,7 @@ namespace Kyoo.Core.Api return NotFound(); } } - + [HttpGet("{showSlug}/font/{slug}")] [HttpGet("{showSlug}/fonts/{slug}")] [PartialPermission(Kind.Read)] @@ -423,7 +423,7 @@ namespace Kyoo.Core.Api return NotFound(); } } - + [HttpGet("{slug}/logo")] public async Task GetLogo(string slug) { @@ -437,7 +437,7 @@ namespace Kyoo.Core.Api return NotFound(); } } - + [HttpGet("{slug}/backdrop")] [HttpGet("{slug}/thumbnail")] public async Task GetBackdrop(string slug) diff --git a/Kyoo.Core/Views/StudioApi.cs b/Kyoo.Core/Views/StudioApi.cs index 87a06a11..453c8ba2 100644 --- a/Kyoo.Core/Views/StudioApi.cs +++ b/Kyoo.Core/Views/StudioApi.cs @@ -24,7 +24,7 @@ namespace Kyoo.Core.Api { _libraryManager = libraryManager; } - + [HttpGet("{id:int}/show")] [HttpGet("{id:int}/shows")] [PartialPermission(Kind.Read)] @@ -47,10 +47,10 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } - + [HttpGet("{slug}/show")] [HttpGet("{slug}/shows")] [PartialPermission(Kind.Read)] @@ -66,14 +66,14 @@ namespace Kyoo.Core.Api ApiHelper.ParseWhere(where, x => x.Studio.Slug == slug), new Sort(sortBy), new Pagination(limit, afterID)); - + if (!resources.Any() && await _libraryManager.GetOrDefault(slug) == null) return NotFound(); return Page(resources, limit); } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } } diff --git a/Kyoo.Core/Views/SubtitleApi.cs b/Kyoo.Core/Views/SubtitleApi.cs index 04ddf1d5..3d6c0231 100644 --- a/Kyoo.Core/Views/SubtitleApi.cs +++ b/Kyoo.Core/Views/SubtitleApi.cs @@ -21,7 +21,7 @@ namespace Kyoo.Core.Api _libraryManager = libraryManager; _files = files; } - + [HttpGet("{id:int}")] [Permission(nameof(SubtitleApi), Kind.Read)] public async Task GetSubtitle(int id) @@ -31,7 +31,7 @@ namespace Kyoo.Core.Api ? _files.FileResult(subtitle.Path) : NotFound(); } - + [HttpGet("{id:int}.{extension}")] [Permission(nameof(SubtitleApi), Kind.Read)] public async Task GetSubtitle(int id, string extension) @@ -43,14 +43,14 @@ namespace Kyoo.Core.Api return new ConvertSubripToVtt(subtitle.Path, _files); return _files.FileResult(subtitle.Path); } - - + + [HttpGet("{slug}")] [Permission(nameof(SubtitleApi), Kind.Read)] public async Task GetSubtitle(string slug) { string extension = null; - + if (slug.Count(x => x == '.') == 3) { int idx = slug.LastIndexOf('.'); @@ -115,7 +115,7 @@ namespace Kyoo.Core.Api private static IEnumerable ConvertBlock(IList lines) { if (lines.Count < 3) - return lines; + return lines; lines[1] = lines[1].Replace(',', '.'); if (lines[2].Length > 5) { diff --git a/Kyoo.Core/Views/TaskApi.cs b/Kyoo.Core/Views/TaskApi.cs index 4bb5b534..8ccc8544 100644 --- a/Kyoo.Core/Views/TaskApi.cs +++ b/Kyoo.Core/Views/TaskApi.cs @@ -26,7 +26,7 @@ namespace Kyoo.Core.Api { return Ok(_taskManager.GetAllTasks()); } - + [HttpGet("{taskSlug}")] [HttpPut("{taskSlug}")] [Permission(nameof(TaskApi), Kind.Create)] @@ -43,7 +43,7 @@ namespace Kyoo.Core.Api } catch (ArgumentException ex) { - return BadRequest(new {Error = ex.Message}); + return BadRequest(new { Error = ex.Message }); } } } diff --git a/Kyoo.Core/Views/TrackApi.cs b/Kyoo.Core/Views/TrackApi.cs index 6d16f632..4127f3ba 100644 --- a/Kyoo.Core/Views/TrackApi.cs +++ b/Kyoo.Core/Views/TrackApi.cs @@ -37,7 +37,7 @@ namespace Kyoo.Core.Api return NotFound(); } } - + [HttpGet("{slug}/episode")] [PartialPermission(Kind.Read)] public async Task> GetEpisode(string slug) diff --git a/Kyoo.Core/Views/VideoApi.cs b/Kyoo.Core/Views/VideoApi.cs index b2fa82df..8a21a560 100644 --- a/Kyoo.Core/Views/VideoApi.cs +++ b/Kyoo.Core/Views/VideoApi.cs @@ -20,8 +20,8 @@ namespace Kyoo.Core.Api private readonly IOptions _options; private readonly IFileSystem _files; - public VideoApi(ILibraryManager libraryManager, - ITranscoder transcoder, + public VideoApi(ILibraryManager libraryManager, + ITranscoder transcoder, IOptions options, IFileSystem files) { @@ -40,7 +40,7 @@ namespace Kyoo.Core.Api ctx.HttpContext.Response.Headers.Add("Expires", "0"); } - + [HttpGet("{slug}")] [HttpGet("direct/{slug}")] // TODO enable the following line, this is disabled since the web app can't use bearers. [Permission("video", Kind.Read)] @@ -94,8 +94,8 @@ namespace Kyoo.Core.Api return NotFound(); } } - - + + [HttpGet("transmux/{episodeLink}/segments/{chunk}")] [Permission("video", Kind.Read)] public IActionResult GetTransmuxedChunk(string episodeLink, string chunk) @@ -104,7 +104,7 @@ namespace Kyoo.Core.Api path = Path.Combine(path, "segments", chunk); return PhysicalFile(path, "video/MP2T"); } - + [HttpGet("transcode/{episodeLink}/segments/{chunk}")] [Permission("video", Kind.Read)] public IActionResult GetTranscodedChunk(string episodeLink, string chunk) diff --git a/Kyoo.Database/DatabaseContext.cs b/Kyoo.Database/DatabaseContext.cs index 282cb93c..50d5ebeb 100644 --- a/Kyoo.Database/DatabaseContext.cs +++ b/Kyoo.Database/DatabaseContext.cs @@ -67,17 +67,17 @@ namespace Kyoo.Database /// The list of registered users. /// public DbSet Users { get; set; } - + /// /// All people's role. See . /// public DbSet PeopleRoles { get; set; } - + /// /// Episodes with a watch percentage. See /// public DbSet WatchedEpisodes { get; set; } - + /// /// The list of library items (shows and collections that are part of a library - or the global one) /// @@ -130,7 +130,7 @@ namespace Kyoo.Database protected DatabaseContext(DbContextOptions options) : base(options) { } - + /// /// Get the name of the metadata table of the given type. /// @@ -177,7 +177,7 @@ namespace Kyoo.Database { modelBuilder.SharedTypeEntity(MetadataName()) .HasKey(MetadataID.PrimaryKey); - + modelBuilder.SharedTypeEntity(MetadataName()) .HasOne() .WithMany(x => x.ExternalIDs) @@ -194,7 +194,7 @@ namespace Kyoo.Database /// The second navigation expression from T2 to T /// The owning type of the relationship /// The owned type of the relationship - private void _HasManyToMany(ModelBuilder modelBuilder, + private void _HasManyToMany(ModelBuilder modelBuilder, Expression>> firstNavigation, Expression>> secondNavigation) where T : class, IResource @@ -209,7 +209,7 @@ namespace Kyoo.Database .HasOne() .WithMany() .HasForeignKey(LinkNameFk()) - .OnDelete(DeleteBehavior.Cascade), + .OnDelete(DeleteBehavior.Cascade), x => x .HasOne() .WithMany() @@ -217,8 +217,8 @@ namespace Kyoo.Database .OnDelete(DeleteBehavior.Cascade) ); } - - + + /// /// Set database parameters to support every types of Kyoo. /// @@ -257,7 +257,7 @@ namespace Kyoo.Database _HasManyToMany(modelBuilder, x => x.Shows, x => x.Libraries); _HasManyToMany(modelBuilder, x => x.Shows, x => x.Collections); _HasManyToMany(modelBuilder, x => x.Genres, x => x.Shows); - + modelBuilder.Entity() .HasMany(x => x.Watched) .WithMany("Users") @@ -269,7 +269,7 @@ namespace Kyoo.Database _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); - + modelBuilder.Entity() .HasKey(x => new { User = x.UserID, Episode = x.EpisodeID }); @@ -281,7 +281,7 @@ namespace Kyoo.Database modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); - + modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); @@ -304,19 +304,19 @@ namespace Kyoo.Database .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() - .HasIndex(x => new {x.ShowID, x.SeasonNumber}) + .HasIndex(x => new { x.ShowID, x.SeasonNumber }) .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() - .HasIndex(x => new {x.ShowID, x.SeasonNumber, x.EpisodeNumber, x.AbsoluteNumber}) + .HasIndex(x => new { x.ShowID, x.SeasonNumber, x.EpisodeNumber, x.AbsoluteNumber }) .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() - .HasIndex(x => new {x.EpisodeID, x.Type, x.Language, x.TrackIndex, x.IsForced}) + .HasIndex(x => new { x.EpisodeID, x.Type, x.Language, x.TrackIndex, x.IsForced }) .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) @@ -394,7 +394,7 @@ namespace Kyoo.Database throw; } } - + /// /// Save changes that are applied to this context. /// @@ -425,7 +425,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(bool acceptAllChangesOnSuccess, + public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new()) { try @@ -461,7 +461,7 @@ namespace Kyoo.Database throw; } } - + /// /// Save changes that are applied to this context. /// @@ -538,7 +538,7 @@ namespace Kyoo.Database entry.State = EntityState.Detached; } } - + /// /// Perform a case insensitive like operation. /// diff --git a/Kyoo.Database/Extensions.cs b/Kyoo.Database/Extensions.cs index 57c72417..b8396294 100644 --- a/Kyoo.Database/Extensions.cs +++ b/Kyoo.Database/Extensions.cs @@ -22,7 +22,7 @@ namespace Kyoo.Database builder[child.Key] = child.Value; return builder.ConnectionString; } - + /// /// Get the name of the selected database. /// diff --git a/Kyoo.Database/Kyoo.Database.csproj b/Kyoo.Database/Kyoo.Database.csproj index 9a64194a..a8fd3c51 100644 --- a/Kyoo.Database/Kyoo.Database.csproj +++ b/Kyoo.Database/Kyoo.Database.csproj @@ -1,4 +1,4 @@ - + net5.0 @@ -6,7 +6,6 @@ Kyoo.Database Zoe Roux https://github.com/AnonymusRaccoon/Kyoo - Library default diff --git a/Kyoo.Postgresql/Migrations/20210801171613_Initial.cs b/Kyoo.Postgresql/Migrations/20210801171613_Initial.cs index 1f336f4e..377bbe91 100644 --- a/Kyoo.Postgresql/Migrations/20210801171613_Initial.cs +++ b/Kyoo.Postgresql/Migrations/20210801171613_Initial.cs @@ -6,841 +6,841 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Postgresql.Migrations { - public partial class Initial : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterDatabase() - .Annotation("Npgsql:Enum:item_type", "show,movie,collection") - .Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned") - .Annotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle,attachment"); - - migrationBuilder.CreateTable( - name: "collections", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: false), - name = table.Column(type: "text", nullable: true), - images = table.Column>(type: "jsonb", nullable: true), - overview = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_collections", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "genres", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: false), - name = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_genres", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "libraries", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: false), - name = table.Column(type: "text", nullable: true), - paths = table.Column(type: "text[]", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_libraries", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "people", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: false), - name = table.Column(type: "text", nullable: true), - images = table.Column>(type: "jsonb", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_people", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "providers", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: false), - name = table.Column(type: "text", nullable: true), - images = table.Column>(type: "jsonb", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_providers", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "studios", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: false), - name = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_studios", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "users", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: false), - username = table.Column(type: "text", nullable: true), - email = table.Column(type: "text", nullable: true), - password = table.Column(type: "text", nullable: true), - permissions = table.Column(type: "text[]", nullable: true), - extra_data = table.Column>(type: "jsonb", nullable: true), - images = table.Column>(type: "jsonb", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_users", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "link_library_collection", - columns: table => new - { - collection_id = table.Column(type: "integer", nullable: false), - library_id = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_link_library_collection", x => new { x.collection_id, x.library_id }); - table.ForeignKey( - name: "fk_link_library_collection_collections_collection_id", - column: x => x.collection_id, - principalTable: "collections", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_link_library_collection_libraries_library_id", - column: x => x.library_id, - principalTable: "libraries", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "collection_metadata_id", - columns: table => new - { - resource_id = table.Column(type: "integer", nullable: false), - provider_id = table.Column(type: "integer", nullable: false), - data_id = table.Column(type: "text", nullable: true), - link = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_collection_metadata_id", x => new { x.resource_id, x.provider_id }); - table.ForeignKey( - name: "fk_collection_metadata_id_collections_collection_id", - column: x => x.resource_id, - principalTable: "collections", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_collection_metadata_id_providers_provider_id", - column: x => x.provider_id, - principalTable: "providers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "link_library_provider", - columns: table => new - { - library_id = table.Column(type: "integer", nullable: false), - provider_id = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_link_library_provider", x => new { x.library_id, x.provider_id }); - table.ForeignKey( - name: "fk_link_library_provider_libraries_library_id", - column: x => x.library_id, - principalTable: "libraries", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_link_library_provider_providers_provider_id", - column: x => x.provider_id, - principalTable: "providers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "people_metadata_id", - columns: table => new - { - resource_id = table.Column(type: "integer", nullable: false), - provider_id = table.Column(type: "integer", nullable: false), - data_id = table.Column(type: "text", nullable: true), - link = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_people_metadata_id", x => new { x.resource_id, x.provider_id }); - table.ForeignKey( - name: "fk_people_metadata_id_people_people_id", - column: x => x.resource_id, - principalTable: "people", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_people_metadata_id_providers_provider_id", - column: x => x.provider_id, - principalTable: "providers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "shows", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: false), - title = table.Column(type: "text", nullable: true), - aliases = table.Column(type: "text[]", nullable: true), - path = table.Column(type: "text", nullable: true), - overview = table.Column(type: "text", nullable: true), - status = table.Column(type: "status", nullable: false), - start_air = table.Column(type: "timestamp without time zone", nullable: true), - end_air = table.Column(type: "timestamp without time zone", nullable: true), - images = table.Column>(type: "jsonb", nullable: true), - is_movie = table.Column(type: "boolean", nullable: false), - studio_id = table.Column(type: "integer", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_shows", x => x.id); - table.ForeignKey( - name: "fk_shows_studios_studio_id", - column: x => x.studio_id, - principalTable: "studios", - principalColumn: "id", - onDelete: ReferentialAction.SetNull); - }); - - migrationBuilder.CreateTable( - name: "studio_metadata_id", - columns: table => new - { - resource_id = table.Column(type: "integer", nullable: false), - provider_id = table.Column(type: "integer", nullable: false), - data_id = table.Column(type: "text", nullable: true), - link = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_studio_metadata_id", x => new { x.resource_id, x.provider_id }); - table.ForeignKey( - name: "fk_studio_metadata_id_providers_provider_id", - column: x => x.provider_id, - principalTable: "providers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_studio_metadata_id_studios_studio_id", - column: x => x.resource_id, - principalTable: "studios", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "link_collection_show", - columns: table => new - { - collection_id = table.Column(type: "integer", nullable: false), - show_id = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_link_collection_show", x => new { x.collection_id, x.show_id }); - table.ForeignKey( - name: "fk_link_collection_show_collections_collection_id", - column: x => x.collection_id, - principalTable: "collections", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_link_collection_show_shows_show_id", - column: x => x.show_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "link_library_show", - columns: table => new - { - library_id = table.Column(type: "integer", nullable: false), - show_id = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_link_library_show", x => new { x.library_id, x.show_id }); - table.ForeignKey( - name: "fk_link_library_show_libraries_library_id", - column: x => x.library_id, - principalTable: "libraries", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_link_library_show_shows_show_id", - column: x => x.show_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "link_show_genre", - columns: table => new - { - genre_id = table.Column(type: "integer", nullable: false), - show_id = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_link_show_genre", x => new { x.genre_id, x.show_id }); - table.ForeignKey( - name: "fk_link_show_genre_genres_genre_id", - column: x => x.genre_id, - principalTable: "genres", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_link_show_genre_shows_show_id", - column: x => x.show_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "link_user_show", - columns: table => new - { - users_id = table.Column(type: "integer", nullable: false), - watched_id = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_link_user_show", x => new { x.users_id, x.watched_id }); - table.ForeignKey( - name: "fk_link_user_show_shows_watched_id", - column: x => x.watched_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_link_user_show_users_users_id", - column: x => x.users_id, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "people_roles", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - people_id = table.Column(type: "integer", nullable: false), - show_id = table.Column(type: "integer", nullable: false), - type = table.Column(type: "text", nullable: true), - role = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_people_roles", x => x.id); - table.ForeignKey( - name: "fk_people_roles_people_people_id", - column: x => x.people_id, - principalTable: "people", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_people_roles_shows_show_id", - column: x => x.show_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "seasons", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: true), - show_id = table.Column(type: "integer", nullable: false), - season_number = table.Column(type: "integer", nullable: false), - title = table.Column(type: "text", nullable: true), - overview = table.Column(type: "text", nullable: true), - start_date = table.Column(type: "timestamp without time zone", nullable: true), - end_date = table.Column(type: "timestamp without time zone", nullable: true), - images = table.Column>(type: "jsonb", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_seasons", x => x.id); - table.ForeignKey( - name: "fk_seasons_shows_show_id", - column: x => x.show_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "show_metadata_id", - columns: table => new - { - resource_id = table.Column(type: "integer", nullable: false), - provider_id = table.Column(type: "integer", nullable: false), - data_id = table.Column(type: "text", nullable: true), - link = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_show_metadata_id", x => new { x.resource_id, x.provider_id }); - table.ForeignKey( - name: "fk_show_metadata_id_providers_provider_id", - column: x => x.provider_id, - principalTable: "providers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_show_metadata_id_shows_show_id", - column: x => x.resource_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "episodes", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: true), - show_id = table.Column(type: "integer", nullable: false), - season_id = table.Column(type: "integer", nullable: true), - season_number = table.Column(type: "integer", nullable: true), - episode_number = table.Column(type: "integer", nullable: true), - absolute_number = table.Column(type: "integer", nullable: true), - path = table.Column(type: "text", nullable: true), - images = table.Column>(type: "jsonb", nullable: true), - title = table.Column(type: "text", nullable: true), - overview = table.Column(type: "text", nullable: true), - release_date = table.Column(type: "timestamp without time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_episodes", x => x.id); - table.ForeignKey( - name: "fk_episodes_seasons_season_id", - column: x => x.season_id, - principalTable: "seasons", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_episodes_shows_show_id", - column: x => x.show_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "season_metadata_id", - columns: table => new - { - resource_id = table.Column(type: "integer", nullable: false), - provider_id = table.Column(type: "integer", nullable: false), - data_id = table.Column(type: "text", nullable: true), - link = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_season_metadata_id", x => new { x.resource_id, x.provider_id }); - table.ForeignKey( - name: "fk_season_metadata_id_providers_provider_id", - column: x => x.provider_id, - principalTable: "providers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_season_metadata_id_seasons_season_id", - column: x => x.resource_id, - principalTable: "seasons", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "episode_metadata_id", - columns: table => new - { - resource_id = table.Column(type: "integer", nullable: false), - provider_id = table.Column(type: "integer", nullable: false), - data_id = table.Column(type: "text", nullable: true), - link = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_episode_metadata_id", x => new { x.resource_id, x.provider_id }); - table.ForeignKey( - name: "fk_episode_metadata_id_episodes_episode_id", - column: x => x.resource_id, - principalTable: "episodes", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_episode_metadata_id_providers_provider_id", - column: x => x.provider_id, - principalTable: "providers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "tracks", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: true), - title = table.Column(type: "text", nullable: true), - language = table.Column(type: "text", nullable: true), - codec = table.Column(type: "text", nullable: true), - is_default = table.Column(type: "boolean", nullable: false), - is_forced = table.Column(type: "boolean", nullable: false), - is_external = table.Column(type: "boolean", nullable: false), - path = table.Column(type: "text", nullable: true), - type = table.Column(type: "stream_type", nullable: false), - episode_id = table.Column(type: "integer", nullable: false), - track_index = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_tracks", x => x.id); - table.ForeignKey( - name: "fk_tracks_episodes_episode_id", - column: x => x.episode_id, - principalTable: "episodes", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "watched_episodes", - columns: table => new - { - user_id = table.Column(type: "integer", nullable: false), - episode_id = table.Column(type: "integer", nullable: false), - watched_percentage = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_watched_episodes", x => new { x.user_id, x.episode_id }); - table.ForeignKey( - name: "fk_watched_episodes_episodes_episode_id", - column: x => x.episode_id, - principalTable: "episodes", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_watched_episodes_users_user_id", - column: x => x.user_id, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_collection_metadata_id_provider_id", - table: "collection_metadata_id", - column: "provider_id"); - - migrationBuilder.CreateIndex( - name: "ix_collections_slug", - table: "collections", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_episode_metadata_id_provider_id", - table: "episode_metadata_id", - column: "provider_id"); - - migrationBuilder.CreateIndex( - name: "ix_episodes_season_id", - table: "episodes", - column: "season_id"); - - migrationBuilder.CreateIndex( - name: "ix_episodes_show_id_season_number_episode_number_absolute_numb", - table: "episodes", - columns: new[] { "show_id", "season_number", "episode_number", "absolute_number" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_episodes_slug", - table: "episodes", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_genres_slug", - table: "genres", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_libraries_slug", - table: "libraries", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_link_collection_show_show_id", - table: "link_collection_show", - column: "show_id"); - - migrationBuilder.CreateIndex( - name: "ix_link_library_collection_library_id", - table: "link_library_collection", - column: "library_id"); - - migrationBuilder.CreateIndex( - name: "ix_link_library_provider_provider_id", - table: "link_library_provider", - column: "provider_id"); - - migrationBuilder.CreateIndex( - name: "ix_link_library_show_show_id", - table: "link_library_show", - column: "show_id"); - - migrationBuilder.CreateIndex( - name: "ix_link_show_genre_show_id", - table: "link_show_genre", - column: "show_id"); - - migrationBuilder.CreateIndex( - name: "ix_link_user_show_watched_id", - table: "link_user_show", - column: "watched_id"); - - migrationBuilder.CreateIndex( - name: "ix_people_slug", - table: "people", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_people_metadata_id_provider_id", - table: "people_metadata_id", - column: "provider_id"); - - migrationBuilder.CreateIndex( - name: "ix_people_roles_people_id", - table: "people_roles", - column: "people_id"); - - migrationBuilder.CreateIndex( - name: "ix_people_roles_show_id", - table: "people_roles", - column: "show_id"); - - migrationBuilder.CreateIndex( - name: "ix_providers_slug", - table: "providers", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_season_metadata_id_provider_id", - table: "season_metadata_id", - column: "provider_id"); - - migrationBuilder.CreateIndex( - name: "ix_seasons_show_id_season_number", - table: "seasons", - columns: new[] { "show_id", "season_number" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_seasons_slug", - table: "seasons", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_show_metadata_id_provider_id", - table: "show_metadata_id", - column: "provider_id"); - - migrationBuilder.CreateIndex( - name: "ix_shows_slug", - table: "shows", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_shows_studio_id", - table: "shows", - column: "studio_id"); - - migrationBuilder.CreateIndex( - name: "ix_studio_metadata_id_provider_id", - table: "studio_metadata_id", - column: "provider_id"); - - migrationBuilder.CreateIndex( - name: "ix_studios_slug", - table: "studios", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_tracks_episode_id_type_language_track_index_is_forced", - table: "tracks", - columns: new[] { "episode_id", "type", "language", "track_index", "is_forced" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_tracks_slug", - table: "tracks", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_users_slug", - table: "users", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_watched_episodes_episode_id", - table: "watched_episodes", - column: "episode_id"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "collection_metadata_id"); - - migrationBuilder.DropTable( - name: "episode_metadata_id"); - - migrationBuilder.DropTable( - name: "link_collection_show"); - - migrationBuilder.DropTable( - name: "link_library_collection"); - - migrationBuilder.DropTable( - name: "link_library_provider"); - - migrationBuilder.DropTable( - name: "link_library_show"); + public partial class Initial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("Npgsql:Enum:item_type", "show,movie,collection") + .Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned") + .Annotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle,attachment"); + + migrationBuilder.CreateTable( + name: "collections", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "text", nullable: false), + name = table.Column(type: "text", nullable: true), + images = table.Column>(type: "jsonb", nullable: true), + overview = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_collections", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "genres", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "text", nullable: false), + name = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_genres", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "libraries", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "text", nullable: false), + name = table.Column(type: "text", nullable: true), + paths = table.Column(type: "text[]", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_libraries", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "people", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "text", nullable: false), + name = table.Column(type: "text", nullable: true), + images = table.Column>(type: "jsonb", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_people", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "providers", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "text", nullable: false), + name = table.Column(type: "text", nullable: true), + images = table.Column>(type: "jsonb", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_providers", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "studios", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "text", nullable: false), + name = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_studios", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "users", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "text", nullable: false), + username = table.Column(type: "text", nullable: true), + email = table.Column(type: "text", nullable: true), + password = table.Column(type: "text", nullable: true), + permissions = table.Column(type: "text[]", nullable: true), + extra_data = table.Column>(type: "jsonb", nullable: true), + images = table.Column>(type: "jsonb", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_users", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "link_library_collection", + columns: table => new + { + collection_id = table.Column(type: "integer", nullable: false), + library_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_link_library_collection", x => new { x.collection_id, x.library_id }); + table.ForeignKey( + name: "fk_link_library_collection_collections_collection_id", + column: x => x.collection_id, + principalTable: "collections", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_link_library_collection_libraries_library_id", + column: x => x.library_id, + principalTable: "libraries", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "collection_metadata_id", + columns: table => new + { + resource_id = table.Column(type: "integer", nullable: false), + provider_id = table.Column(type: "integer", nullable: false), + data_id = table.Column(type: "text", nullable: true), + link = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_collection_metadata_id", x => new { x.resource_id, x.provider_id }); + table.ForeignKey( + name: "fk_collection_metadata_id_collections_collection_id", + column: x => x.resource_id, + principalTable: "collections", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_collection_metadata_id_providers_provider_id", + column: x => x.provider_id, + principalTable: "providers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "link_library_provider", + columns: table => new + { + library_id = table.Column(type: "integer", nullable: false), + provider_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_link_library_provider", x => new { x.library_id, x.provider_id }); + table.ForeignKey( + name: "fk_link_library_provider_libraries_library_id", + column: x => x.library_id, + principalTable: "libraries", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_link_library_provider_providers_provider_id", + column: x => x.provider_id, + principalTable: "providers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "people_metadata_id", + columns: table => new + { + resource_id = table.Column(type: "integer", nullable: false), + provider_id = table.Column(type: "integer", nullable: false), + data_id = table.Column(type: "text", nullable: true), + link = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_people_metadata_id", x => new { x.resource_id, x.provider_id }); + table.ForeignKey( + name: "fk_people_metadata_id_people_people_id", + column: x => x.resource_id, + principalTable: "people", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_people_metadata_id_providers_provider_id", + column: x => x.provider_id, + principalTable: "providers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "shows", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "text", nullable: false), + title = table.Column(type: "text", nullable: true), + aliases = table.Column(type: "text[]", nullable: true), + path = table.Column(type: "text", nullable: true), + overview = table.Column(type: "text", nullable: true), + status = table.Column(type: "status", nullable: false), + start_air = table.Column(type: "timestamp without time zone", nullable: true), + end_air = table.Column(type: "timestamp without time zone", nullable: true), + images = table.Column>(type: "jsonb", nullable: true), + is_movie = table.Column(type: "boolean", nullable: false), + studio_id = table.Column(type: "integer", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_shows", x => x.id); + table.ForeignKey( + name: "fk_shows_studios_studio_id", + column: x => x.studio_id, + principalTable: "studios", + principalColumn: "id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "studio_metadata_id", + columns: table => new + { + resource_id = table.Column(type: "integer", nullable: false), + provider_id = table.Column(type: "integer", nullable: false), + data_id = table.Column(type: "text", nullable: true), + link = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_studio_metadata_id", x => new { x.resource_id, x.provider_id }); + table.ForeignKey( + name: "fk_studio_metadata_id_providers_provider_id", + column: x => x.provider_id, + principalTable: "providers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_studio_metadata_id_studios_studio_id", + column: x => x.resource_id, + principalTable: "studios", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "link_collection_show", + columns: table => new + { + collection_id = table.Column(type: "integer", nullable: false), + show_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_link_collection_show", x => new { x.collection_id, x.show_id }); + table.ForeignKey( + name: "fk_link_collection_show_collections_collection_id", + column: x => x.collection_id, + principalTable: "collections", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_link_collection_show_shows_show_id", + column: x => x.show_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "link_library_show", + columns: table => new + { + library_id = table.Column(type: "integer", nullable: false), + show_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_link_library_show", x => new { x.library_id, x.show_id }); + table.ForeignKey( + name: "fk_link_library_show_libraries_library_id", + column: x => x.library_id, + principalTable: "libraries", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_link_library_show_shows_show_id", + column: x => x.show_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "link_show_genre", + columns: table => new + { + genre_id = table.Column(type: "integer", nullable: false), + show_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_link_show_genre", x => new { x.genre_id, x.show_id }); + table.ForeignKey( + name: "fk_link_show_genre_genres_genre_id", + column: x => x.genre_id, + principalTable: "genres", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_link_show_genre_shows_show_id", + column: x => x.show_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "link_user_show", + columns: table => new + { + users_id = table.Column(type: "integer", nullable: false), + watched_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_link_user_show", x => new { x.users_id, x.watched_id }); + table.ForeignKey( + name: "fk_link_user_show_shows_watched_id", + column: x => x.watched_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_link_user_show_users_users_id", + column: x => x.users_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "people_roles", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + people_id = table.Column(type: "integer", nullable: false), + show_id = table.Column(type: "integer", nullable: false), + type = table.Column(type: "text", nullable: true), + role = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_people_roles", x => x.id); + table.ForeignKey( + name: "fk_people_roles_people_people_id", + column: x => x.people_id, + principalTable: "people", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_people_roles_shows_show_id", + column: x => x.show_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "seasons", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "text", nullable: true), + show_id = table.Column(type: "integer", nullable: false), + season_number = table.Column(type: "integer", nullable: false), + title = table.Column(type: "text", nullable: true), + overview = table.Column(type: "text", nullable: true), + start_date = table.Column(type: "timestamp without time zone", nullable: true), + end_date = table.Column(type: "timestamp without time zone", nullable: true), + images = table.Column>(type: "jsonb", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_seasons", x => x.id); + table.ForeignKey( + name: "fk_seasons_shows_show_id", + column: x => x.show_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "show_metadata_id", + columns: table => new + { + resource_id = table.Column(type: "integer", nullable: false), + provider_id = table.Column(type: "integer", nullable: false), + data_id = table.Column(type: "text", nullable: true), + link = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_show_metadata_id", x => new { x.resource_id, x.provider_id }); + table.ForeignKey( + name: "fk_show_metadata_id_providers_provider_id", + column: x => x.provider_id, + principalTable: "providers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_show_metadata_id_shows_show_id", + column: x => x.resource_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "episodes", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "text", nullable: true), + show_id = table.Column(type: "integer", nullable: false), + season_id = table.Column(type: "integer", nullable: true), + season_number = table.Column(type: "integer", nullable: true), + episode_number = table.Column(type: "integer", nullable: true), + absolute_number = table.Column(type: "integer", nullable: true), + path = table.Column(type: "text", nullable: true), + images = table.Column>(type: "jsonb", nullable: true), + title = table.Column(type: "text", nullable: true), + overview = table.Column(type: "text", nullable: true), + release_date = table.Column(type: "timestamp without time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_episodes", x => x.id); + table.ForeignKey( + name: "fk_episodes_seasons_season_id", + column: x => x.season_id, + principalTable: "seasons", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_episodes_shows_show_id", + column: x => x.show_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "season_metadata_id", + columns: table => new + { + resource_id = table.Column(type: "integer", nullable: false), + provider_id = table.Column(type: "integer", nullable: false), + data_id = table.Column(type: "text", nullable: true), + link = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_season_metadata_id", x => new { x.resource_id, x.provider_id }); + table.ForeignKey( + name: "fk_season_metadata_id_providers_provider_id", + column: x => x.provider_id, + principalTable: "providers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_season_metadata_id_seasons_season_id", + column: x => x.resource_id, + principalTable: "seasons", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "episode_metadata_id", + columns: table => new + { + resource_id = table.Column(type: "integer", nullable: false), + provider_id = table.Column(type: "integer", nullable: false), + data_id = table.Column(type: "text", nullable: true), + link = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_episode_metadata_id", x => new { x.resource_id, x.provider_id }); + table.ForeignKey( + name: "fk_episode_metadata_id_episodes_episode_id", + column: x => x.resource_id, + principalTable: "episodes", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_episode_metadata_id_providers_provider_id", + column: x => x.provider_id, + principalTable: "providers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "tracks", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "text", nullable: true), + title = table.Column(type: "text", nullable: true), + language = table.Column(type: "text", nullable: true), + codec = table.Column(type: "text", nullable: true), + is_default = table.Column(type: "boolean", nullable: false), + is_forced = table.Column(type: "boolean", nullable: false), + is_external = table.Column(type: "boolean", nullable: false), + path = table.Column(type: "text", nullable: true), + type = table.Column(type: "stream_type", nullable: false), + episode_id = table.Column(type: "integer", nullable: false), + track_index = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_tracks", x => x.id); + table.ForeignKey( + name: "fk_tracks_episodes_episode_id", + column: x => x.episode_id, + principalTable: "episodes", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "watched_episodes", + columns: table => new + { + user_id = table.Column(type: "integer", nullable: false), + episode_id = table.Column(type: "integer", nullable: false), + watched_percentage = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_watched_episodes", x => new { x.user_id, x.episode_id }); + table.ForeignKey( + name: "fk_watched_episodes_episodes_episode_id", + column: x => x.episode_id, + principalTable: "episodes", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_watched_episodes_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "ix_collection_metadata_id_provider_id", + table: "collection_metadata_id", + column: "provider_id"); + + migrationBuilder.CreateIndex( + name: "ix_collections_slug", + table: "collections", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_episode_metadata_id_provider_id", + table: "episode_metadata_id", + column: "provider_id"); + + migrationBuilder.CreateIndex( + name: "ix_episodes_season_id", + table: "episodes", + column: "season_id"); + + migrationBuilder.CreateIndex( + name: "ix_episodes_show_id_season_number_episode_number_absolute_numb", + table: "episodes", + columns: new[] { "show_id", "season_number", "episode_number", "absolute_number" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_episodes_slug", + table: "episodes", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_genres_slug", + table: "genres", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_libraries_slug", + table: "libraries", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_link_collection_show_show_id", + table: "link_collection_show", + column: "show_id"); + + migrationBuilder.CreateIndex( + name: "ix_link_library_collection_library_id", + table: "link_library_collection", + column: "library_id"); + + migrationBuilder.CreateIndex( + name: "ix_link_library_provider_provider_id", + table: "link_library_provider", + column: "provider_id"); + + migrationBuilder.CreateIndex( + name: "ix_link_library_show_show_id", + table: "link_library_show", + column: "show_id"); + + migrationBuilder.CreateIndex( + name: "ix_link_show_genre_show_id", + table: "link_show_genre", + column: "show_id"); + + migrationBuilder.CreateIndex( + name: "ix_link_user_show_watched_id", + table: "link_user_show", + column: "watched_id"); + + migrationBuilder.CreateIndex( + name: "ix_people_slug", + table: "people", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_people_metadata_id_provider_id", + table: "people_metadata_id", + column: "provider_id"); + + migrationBuilder.CreateIndex( + name: "ix_people_roles_people_id", + table: "people_roles", + column: "people_id"); + + migrationBuilder.CreateIndex( + name: "ix_people_roles_show_id", + table: "people_roles", + column: "show_id"); + + migrationBuilder.CreateIndex( + name: "ix_providers_slug", + table: "providers", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_season_metadata_id_provider_id", + table: "season_metadata_id", + column: "provider_id"); + + migrationBuilder.CreateIndex( + name: "ix_seasons_show_id_season_number", + table: "seasons", + columns: new[] { "show_id", "season_number" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_seasons_slug", + table: "seasons", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_show_metadata_id_provider_id", + table: "show_metadata_id", + column: "provider_id"); + + migrationBuilder.CreateIndex( + name: "ix_shows_slug", + table: "shows", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_shows_studio_id", + table: "shows", + column: "studio_id"); + + migrationBuilder.CreateIndex( + name: "ix_studio_metadata_id_provider_id", + table: "studio_metadata_id", + column: "provider_id"); + + migrationBuilder.CreateIndex( + name: "ix_studios_slug", + table: "studios", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_tracks_episode_id_type_language_track_index_is_forced", + table: "tracks", + columns: new[] { "episode_id", "type", "language", "track_index", "is_forced" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_tracks_slug", + table: "tracks", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_users_slug", + table: "users", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_watched_episodes_episode_id", + table: "watched_episodes", + column: "episode_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "collection_metadata_id"); + + migrationBuilder.DropTable( + name: "episode_metadata_id"); + + migrationBuilder.DropTable( + name: "link_collection_show"); + + migrationBuilder.DropTable( + name: "link_library_collection"); + + migrationBuilder.DropTable( + name: "link_library_provider"); + + migrationBuilder.DropTable( + name: "link_library_show"); - migrationBuilder.DropTable( - name: "link_show_genre"); - - migrationBuilder.DropTable( - name: "link_user_show"); + migrationBuilder.DropTable( + name: "link_show_genre"); + + migrationBuilder.DropTable( + name: "link_user_show"); - migrationBuilder.DropTable( - name: "people_metadata_id"); + migrationBuilder.DropTable( + name: "people_metadata_id"); - migrationBuilder.DropTable( - name: "people_roles"); + migrationBuilder.DropTable( + name: "people_roles"); - migrationBuilder.DropTable( - name: "season_metadata_id"); + migrationBuilder.DropTable( + name: "season_metadata_id"); - migrationBuilder.DropTable( - name: "show_metadata_id"); + migrationBuilder.DropTable( + name: "show_metadata_id"); - migrationBuilder.DropTable( - name: "studio_metadata_id"); + migrationBuilder.DropTable( + name: "studio_metadata_id"); - migrationBuilder.DropTable( - name: "tracks"); + migrationBuilder.DropTable( + name: "tracks"); - migrationBuilder.DropTable( - name: "watched_episodes"); + migrationBuilder.DropTable( + name: "watched_episodes"); - migrationBuilder.DropTable( - name: "collections"); + migrationBuilder.DropTable( + name: "collections"); - migrationBuilder.DropTable( - name: "libraries"); + migrationBuilder.DropTable( + name: "libraries"); - migrationBuilder.DropTable( - name: "genres"); + migrationBuilder.DropTable( + name: "genres"); - migrationBuilder.DropTable( - name: "people"); + migrationBuilder.DropTable( + name: "people"); - migrationBuilder.DropTable( - name: "providers"); + migrationBuilder.DropTable( + name: "providers"); - migrationBuilder.DropTable( - name: "episodes"); + migrationBuilder.DropTable( + name: "episodes"); - migrationBuilder.DropTable( - name: "users"); + migrationBuilder.DropTable( + name: "users"); - migrationBuilder.DropTable( - name: "seasons"); + migrationBuilder.DropTable( + name: "seasons"); - migrationBuilder.DropTable( - name: "shows"); + migrationBuilder.DropTable( + name: "shows"); - migrationBuilder.DropTable( - name: "studios"); - } - } + migrationBuilder.DropTable( + name: "studios"); + } + } } diff --git a/Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs b/Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs index 6ee2961c..6ea630e5 100644 --- a/Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs +++ b/Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs @@ -21,13 +21,13 @@ namespace Kyoo.Postgresql.Migrations RETURN NEW; END $$;"); - + // language=PostgreSQL migrationBuilder.Sql(@" 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 migrationBuilder.Sql(@" CREATE FUNCTION episode_slug_update() @@ -46,7 +46,7 @@ namespace Kyoo.Postgresql.Migrations RETURN NEW; END $$;"); - + // language=PostgreSQL migrationBuilder.Sql(@" CREATE TRIGGER episode_slug_trigger @@ -74,7 +74,7 @@ namespace Kyoo.Postgresql.Migrations migrationBuilder.Sql(@" CREATE TRIGGER show_slug_trigger AFTER UPDATE OF slug ON shows FOR EACH ROW EXECUTE PROCEDURE show_slug_update();"); - + // language=PostgreSQL migrationBuilder.Sql(@" CREATE FUNCTION episode_update_tracks_slug() @@ -102,7 +102,7 @@ namespace Kyoo.Postgresql.Migrations migrationBuilder.Sql(@" CREATE TRIGGER episode_track_slug_trigger AFTER UPDATE OF slug ON episodes FOR EACH ROW EXECUTE PROCEDURE episode_update_tracks_slug();"); - + // language=PostgreSQL migrationBuilder.Sql(@" CREATE FUNCTION track_slug_update() diff --git a/Kyoo.Postgresql/PostgresContext.cs b/Kyoo.Postgresql/PostgresContext.cs index c8477169..1e8a7cf7 100644 --- a/Kyoo.Postgresql/PostgresContext.cs +++ b/Kyoo.Postgresql/PostgresContext.cs @@ -38,7 +38,7 @@ namespace Kyoo.Postgresql NpgsqlConnection.GlobalTypeMapper.MapEnum(); NpgsqlConnection.GlobalTypeMapper.MapEnum(); } - + /// /// A basic constructor that set default values (query tracker behaviors, mapping enums...) /// @@ -102,7 +102,7 @@ namespace Kyoo.Postgresql modelBuilder.Entity() .Property(x => x.ExtraData) .HasColumnType("jsonb"); - + modelBuilder.Entity() .Property(x => x.Images) .HasColumnType("jsonb"); @@ -127,7 +127,7 @@ namespace Kyoo.Postgresql modelBuilder.Entity() .Property(x => x.Images) .HasColumnType("jsonb"); - + base.OnModelCreating(modelBuilder); } @@ -137,14 +137,14 @@ namespace Kyoo.Postgresql SnakeCaseNameRewriter rewriter = new(CultureInfo.InvariantCulture); return rewriter.RewriteName(typeof(T).Name + nameof(MetadataID)); } - + /// protected override string LinkName() { SnakeCaseNameRewriter rewriter = new(CultureInfo.InvariantCulture); return rewriter.RewriteName("Link" + typeof(T).Name + typeof(T2).Name); } - + /// protected override string LinkNameFk() { @@ -155,7 +155,7 @@ namespace Kyoo.Postgresql /// protected override bool IsDuplicateException(Exception ex) { - return ex.InnerException is PostgresException {SqlState: PostgresErrorCodes.UniqueViolation}; + return ex.InnerException is PostgresException { SqlState: PostgresErrorCodes.UniqueViolation }; } /// diff --git a/Kyoo.Postgresql/PostgresModule.cs b/Kyoo.Postgresql/PostgresModule.cs index 295faf96..aae459c0 100644 --- a/Kyoo.Postgresql/PostgresModule.cs +++ b/Kyoo.Postgresql/PostgresModule.cs @@ -24,7 +24,7 @@ namespace Kyoo.Postgresql /// public string Description => "A database context for postgresql."; - + /// public Dictionary Configuration => new(); @@ -51,7 +51,7 @@ namespace Kyoo.Postgresql _configuration = configuration; _environment = env; } - + /// public void Configure(IServiceCollection services) { diff --git a/Kyoo.SqLite/Migrations/20210801171544_Triggers.cs b/Kyoo.SqLite/Migrations/20210801171544_Triggers.cs index 789bc182..4c57d4d2 100644 --- a/Kyoo.SqLite/Migrations/20210801171544_Triggers.cs +++ b/Kyoo.SqLite/Migrations/20210801171544_Triggers.cs @@ -20,7 +20,7 @@ namespace Kyoo.SqLite.Migrations 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 @@ -149,8 +149,8 @@ namespace Kyoo.SqLite.Migrations END WHERE ShowID = new.ID; END;"); - - + + // language=SQLite migrationBuilder.Sql(@" CREATE VIEW LibraryItems AS diff --git a/Kyoo.SqLite/SqLiteContext.cs b/Kyoo.SqLite/SqLiteContext.cs index 87aa5637..709e239f 100644 --- a/Kyoo.SqLite/SqLiteContext.cs +++ b/Kyoo.SqLite/SqLiteContext.cs @@ -96,7 +96,7 @@ namespace Kyoo.SqLite modelBuilder.Entity() .Property(x => x.Permissions) .HasConversion(arrayConvertor); - + modelBuilder.Entity() .Property(x => x.Status) .HasConversion(); @@ -110,7 +110,7 @@ namespace Kyoo.SqLite modelBuilder.Entity() .Property(x => x.ExtraData) .HasConversion(extraDataConvertor); - + ValueConverter, string> jsonConvertor = new( x => JsonConvert.SerializeObject(x), x => JsonConvert.DeserializeObject>(x)); @@ -138,8 +138,8 @@ namespace Kyoo.SqLite modelBuilder.Entity() .Property(x => x.Images) .HasConversion(jsonConvertor); - - + + modelBuilder.Entity() .ToView("LibraryItems") .HasKey(x => x.ID); @@ -151,13 +151,13 @@ namespace Kyoo.SqLite { return typeof(T).Name + nameof(MetadataID); } - + /// protected override string LinkName() { return "Link" + typeof(T).Name + typeof(T2).Name; } - + /// protected override string LinkNameFk() { @@ -180,4 +180,4 @@ namespace Kyoo.SqLite return Expression.Lambda>(call, query.Parameters); } } -} \ No newline at end of file +} diff --git a/Kyoo.SqLite/SqLiteModule.cs b/Kyoo.SqLite/SqLiteModule.cs index 9a1dc76b..0cf4b493 100644 --- a/Kyoo.SqLite/SqLiteModule.cs +++ b/Kyoo.SqLite/SqLiteModule.cs @@ -23,10 +23,10 @@ namespace Kyoo.SqLite /// public string Description => "A database context for sqlite."; - + /// public Dictionary Configuration => new(); - + /// public bool Enabled => _configuration.GetSelectedDatabase() == "sqlite"; diff --git a/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs b/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs index 47c88f41..d5854163 100644 --- a/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs @@ -32,7 +32,7 @@ namespace Kyoo.TheMovieDb ? $"https://image.tmdb.org/t/p/original{collection.BackdropPath}" : null }, - ExternalIDs = new [] + ExternalIDs = new[] { new MetadataID { @@ -43,7 +43,7 @@ namespace Kyoo.TheMovieDb } }; } - + /// /// Convert a into a . /// @@ -65,7 +65,7 @@ namespace Kyoo.TheMovieDb ? $"https://image.tmdb.org/t/p/original{collection.BackdropPath}" : null }, - ExternalIDs = new [] + ExternalIDs = new[] { new MetadataID { diff --git a/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs b/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs index efee43df..ddc43eff 100644 --- a/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs @@ -27,11 +27,11 @@ namespace Kyoo.TheMovieDb ReleaseDate = episode.AirDate, Images = new Dictionary { - [Images.Thumbnail] = episode.StillPath != null - ? $"https://image.tmdb.org/t/p/original{episode.StillPath}" + [Images.Thumbnail] = episode.StillPath != null + ? $"https://image.tmdb.org/t/p/original{episode.StillPath}" : null }, - ExternalIDs = new [] + ExternalIDs = new[] { new MetadataID { diff --git a/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs b/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs index d123ca28..9e29f655 100644 --- a/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs @@ -50,7 +50,7 @@ namespace Kyoo.TheMovieDb .Select(x => x.ToPeople(provider)) .Concat(movie.Credits.Crew.Select(x => x.ToPeople(provider))) .ToArray(), - ExternalIDs = new [] + ExternalIDs = new[] { new MetadataID { @@ -61,7 +61,7 @@ namespace Kyoo.TheMovieDb } }; } - + /// /// Convert a into a . /// @@ -87,7 +87,7 @@ namespace Kyoo.TheMovieDb : null, }, IsMovie = true, - ExternalIDs = new [] + ExternalIDs = new[] { new MetadataID { diff --git a/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs b/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs index 8c5dc7a1..cdaf5bc6 100644 --- a/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs @@ -26,11 +26,11 @@ namespace Kyoo.TheMovieDb StartDate = season.AirDate, Images = new Dictionary { - [Images.Poster] = season.PosterPath != null + [Images.Poster] = season.PosterPath != null ? $"https://image.tmdb.org/t/p/original{season.PosterPath}" : null }, - ExternalIDs = new [] + ExternalIDs = new[] { new MetadataID { diff --git a/Kyoo.TheMovieDb/ProviderTmdb.cs b/Kyoo.TheMovieDb/ProviderTmdb.cs index 7d8c4057..826e348b 100644 --- a/Kyoo.TheMovieDb/ProviderTmdb.cs +++ b/Kyoo.TheMovieDb/ProviderTmdb.cs @@ -50,10 +50,10 @@ namespace Kyoo.TheMovieDb _apiKey = apiKey; _logger = logger; } - + /// - public Task Get(T item) + public Task Get(T item) where T : class, IResource { return item switch @@ -100,21 +100,21 @@ namespace Kyoo.TheMovieDb if (found?.TryGetID(Provider.Slug, out id) != true) return found; } - + TMDbClient client = new(_apiKey.Value.ApiKey); - + if (show.IsMovie) { return (await client .GetMovieAsync(id, MovieMethods.AlternativeTitles | MovieMethods.Videos | MovieMethods.Credits)) ?.ToShow(Provider); } - + return (await client .GetTvShowAsync(id, TvShowMethods.AlternativeTitles | TvShowMethods.Videos | TvShowMethods.Credits)) ?.ToShow(Provider); } - + /// /// Get a season using it's show and it's season number. /// @@ -131,7 +131,7 @@ namespace Kyoo.TheMovieDb if (!season.Show.TryGetID(Provider.Slug, out int id)) return null; - + TMDbClient client = new(_apiKey.Value.ApiKey); return (await client.GetTvSeasonAsync(id, season.SeasonNumber)) .ToSeason(id, Provider); @@ -151,15 +151,15 @@ namespace Kyoo.TheMovieDb "This is unsupported"); return null; } - if (!episode.Show.TryGetID(Provider.Slug, out int id) + if (!episode.Show.TryGetID(Provider.Slug, out int id) || episode.SeasonNumber == null || episode.EpisodeNumber == null) return null; - + TMDbClient client = new(_apiKey.Value.ApiKey); return (await client.GetTvEpisodeAsync(id, episode.SeasonNumber.Value, episode.EpisodeNumber.Value)) ?.ToEpisode(id, Provider); } - + /// /// Get a person using it's id, if the id is not present in the person, fallback to a name search. /// @@ -177,7 +177,7 @@ namespace Kyoo.TheMovieDb TMDbClient client = new(_apiKey.Value.ApiKey); return (await client.GetPersonAsync(id)).ToPeople(Provider); } - + /// /// Get a studio using it's id, if the id is not present in the studio, fallback to a name search. /// @@ -195,9 +195,9 @@ namespace Kyoo.TheMovieDb TMDbClient client = new(_apiKey.Value.ApiKey); return (await client.GetCompanyAsync(id)).ToStudio(Provider); } - + /// - public async Task> Search(string query) + public async Task> Search(string query) where T : class, IResource { if (typeof(T) == typeof(Collection)) @@ -248,7 +248,7 @@ namespace Kyoo.TheMovieDb .Where(x => x != null) .ToArray(); } - + /// /// Search for people using there name as a query. /// @@ -262,7 +262,7 @@ namespace Kyoo.TheMovieDb .Select(x => x.ToPeople(Provider)) .ToArray(); } - + /// /// Search for studios using there name as a query. /// diff --git a/Kyoo.TheTvdb/ProviderTvdb.cs b/Kyoo.TheTvdb/ProviderTvdb.cs index ae983720..143c080c 100644 --- a/Kyoo.TheTvdb/ProviderTvdb.cs +++ b/Kyoo.TheTvdb/ProviderTvdb.cs @@ -34,11 +34,11 @@ namespace Kyoo.TheTvdb Name = "TheTVDB", Images = new Dictionary { - [Images.Logo] = "https://www.thetvdb.com/images/logo.png" + [Images.Logo] = "https://www.thetvdb.com/images/logo.png" } }; - - + + /// /// Create a new using a tvdb client and an api key. /// @@ -59,9 +59,9 @@ namespace Kyoo.TheTvdb return _client.Authentication.AuthenticateAsync(_apiKey.Value.ApiKey); return _client.Authentication.RefreshTokenAsync(); } - + /// - public async Task Get(T item) + public async Task Get(T item) where T : class, IResource { await _Authenticate(); @@ -72,7 +72,7 @@ namespace Kyoo.TheTvdb _ => null }; } - + /// /// Retrieve metadata about a show. /// @@ -83,7 +83,7 @@ namespace Kyoo.TheTvdb { if (show.IsMovie) return null; - + if (!int.TryParse(show.GetID(Provider.Slug), out int id)) { Show found = (await _SearchShow(show.Title)).FirstOrDefault(); @@ -93,7 +93,7 @@ namespace Kyoo.TheTvdb } TvDbResponse series = await _client.Series.GetAsync(id); Show ret = series.Data.ToShow(Provider); - + TvDbResponse people = await _client.Series.GetActorsAsync(id); ret.People = people.Data.Select(x => x.ToPeopleRole()).ToArray(); return ret; @@ -110,14 +110,14 @@ namespace Kyoo.TheTvdb if (!int.TryParse(episode.Show?.GetID(Provider.Slug), out int id)) return null; EpisodeQuery query = episode.AbsoluteNumber != null - ? new EpisodeQuery {AbsoluteNumber = episode.AbsoluteNumber} - : new EpisodeQuery {AiredSeason = episode.SeasonNumber, AiredEpisode = episode.EpisodeNumber}; + ? new EpisodeQuery { AbsoluteNumber = episode.AbsoluteNumber } + : new EpisodeQuery { AiredSeason = episode.SeasonNumber, AiredEpisode = episode.EpisodeNumber }; TvDbResponse episodes = await _client.Series.GetEpisodesAsync(id, 0, query); return episodes.Data.FirstOrDefault()?.ToEpisode(Provider); } /// - public async Task> Search(string query) + public async Task> Search(string query) where T : class, IResource { await _Authenticate(); @@ -125,7 +125,7 @@ namespace Kyoo.TheTvdb return (await _SearchShow(query) as ICollection)!; return ArraySegment.Empty; } - + /// /// Search for shows in the tvdb. /// diff --git a/Kyoo.WebApp/WebAppModule.cs b/Kyoo.WebApp/WebAppModule.cs index a83910cb..627b88b2 100644 --- a/Kyoo.WebApp/WebAppModule.cs +++ b/Kyoo.WebApp/WebAppModule.cs @@ -21,7 +21,7 @@ namespace Kyoo.WebApp { /// public string Slug => "webapp"; - + /// public string Name => "WebApp"; @@ -41,8 +41,10 @@ namespace Kyoo.WebApp public WebAppModule(ILogger logger) { if (!Enabled) - logger.LogError("The web app files could not be found, it will be disabled. " + + { + logger.LogError("The web app files could not be found, it will be disabled. " + "If you cloned the project, you probably forgot to use the --recurse flag"); + } } /// @@ -53,7 +55,7 @@ namespace Kyoo.WebApp x.RootPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "wwwroot"); }); } - + /// public IEnumerable ConfigureSteps => new IStartupAction[] { @@ -72,7 +74,7 @@ namespace Kyoo.WebApp }, SA.StaticFiles), SA.New(app => { - app.Use((ctx, next) => + app.Use((ctx, next) => { ctx.Response.Headers.Remove("X-Powered-By"); ctx.Response.Headers.Remove("Server"); @@ -90,7 +92,7 @@ namespace Kyoo.WebApp app.UseSpa(spa => { spa.Options.SourcePath = _GetSpaSourcePath(); - + if (env.IsDevelopment()) spa.UseAngularCliServer("start"); }); @@ -112,4 +114,4 @@ namespace Kyoo.WebApp return Directory.Exists(path) ? path : null; } } -} \ No newline at end of file +} diff --git a/Kyoo.ruleset b/Kyoo.ruleset index 1942824c..0ef4692c 100644 --- a/Kyoo.ruleset +++ b/Kyoo.ruleset @@ -10,9 +10,14 @@ + - + + + + + diff --git a/tests/Kyoo.Tests/Database/RepositoryActivator.cs b/tests/Kyoo.Tests/Database/RepositoryActivator.cs index 267bafb7..902d7c68 100644 --- a/tests/Kyoo.Tests/Database/RepositoryActivator.cs +++ b/tests/Kyoo.Tests/Database/RepositoryActivator.cs @@ -15,11 +15,11 @@ namespace Kyoo.Tests private readonly List _databases = new(); - + public RepositoryActivator(ITestOutputHelper output, PostgresFixture postgres = null) { - Context = postgres == null - ? new SqLiteTestContext(output) + Context = postgres == null + ? new SqLiteTestContext(output) : new PostgresTestContext(postgres, output); ProviderRepository provider = new(_NewContext()); @@ -27,11 +27,11 @@ namespace Kyoo.Tests CollectionRepository collection = new(_NewContext(), provider); GenreRepository genre = new(_NewContext()); StudioRepository studio = new(_NewContext(), provider); - PeopleRepository people = new(_NewContext(), provider, + PeopleRepository people = new(_NewContext(), provider, new Lazy(() => LibraryManager.ShowRepository)); ShowRepository show = new(_NewContext(), studio, people, genre, provider); SeasonRepository season = new(_NewContext(), provider); - LibraryItemRepository libraryItem = new(_NewContext(), + LibraryItemRepository libraryItem = new(_NewContext(), new Lazy(() => LibraryManager.LibraryRepository)); TrackRepository track = new(_NewContext()); EpisodeRepository episode = new(_NewContext(), provider, track); diff --git a/tests/Kyoo.Tests/Database/RepositoryTests.cs b/tests/Kyoo.Tests/Database/RepositoryTests.cs index 536673b3..3d6b1b39 100644 --- a/tests/Kyoo.Tests/Database/RepositoryTests.cs +++ b/tests/Kyoo.Tests/Database/RepositoryTests.cs @@ -48,20 +48,20 @@ namespace Kyoo.Tests T value = await _repository.Get(TestSample.Get().ID); KAssert.DeepEqual(TestSample.Get(), value); } - + [Fact] public async Task GetBySlugTest() { T value = await _repository.Get(TestSample.Get().Slug); KAssert.DeepEqual(TestSample.Get(), value); } - + [Fact] public async Task GetByFakeIdTest() { await Assert.ThrowsAsync(() => _repository.Get(2)); } - + [Fact] public async Task GetByFakeSlugTest() { @@ -74,21 +74,21 @@ namespace Kyoo.Tests await _repository.Delete(TestSample.Get().ID); Assert.Equal(0, await _repository.GetCount()); } - + [Fact] public async Task DeleteBySlugTest() { await _repository.Delete(TestSample.Get().Slug); Assert.Equal(0, await _repository.GetCount()); } - + [Fact] public async Task DeleteByValueTest() { await _repository.Delete(TestSample.Get()); Assert.Equal(0, await _repository.GetCount()); } - + [Fact] public async Task CreateTest() { @@ -100,19 +100,19 @@ namespace Kyoo.Tests await _repository.Create(expected); KAssert.DeepEqual(expected, await _repository.Get(expected.Slug)); } - + [Fact] public async Task CreateNullTest() { await Assert.ThrowsAsync(() => _repository.Create(null!)); } - + [Fact] public async Task CreateIfNotExistNullTest() { await Assert.ThrowsAsync(() => _repository.CreateIfNotExists(null!)); } - + [Fact] public async Task CreateIfNotExistTest() { @@ -121,17 +121,17 @@ namespace Kyoo.Tests await _repository.Delete(TestSample.Get()); KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(TestSample.Get())); } - + [Fact] public async Task EditNullTest() { await Assert.ThrowsAsync(() => _repository.Edit(null!, false)); } - + [Fact] public async Task EditNonExistingTest() { - await Assert.ThrowsAsync(() => _repository.Edit(new T {ID = 56}, false)); + await Assert.ThrowsAsync(() => _repository.Edit(new T { ID = 56 }, false)); } [Fact] @@ -145,19 +145,19 @@ namespace Kyoo.Tests { KAssert.DeepEqual(TestSample.Get(), await _repository.Get(x => x.Slug == TestSample.Get().Slug)); } - + [Fact] public async Task GetExpressionNotFoundTest() { await Assert.ThrowsAsync(() => _repository.Get(x => x.Slug == "non-existing")); } - + [Fact] public async Task GetExpressionNullTest() { await Assert.ThrowsAsync(() => _repository.Get((Expression>)null!)); } - + [Fact] public async Task GetOrDefaultTest() { @@ -172,7 +172,7 @@ namespace Kyoo.Tests string slug = TestSample.Get().Slug[2..4]; Assert.Equal(1, await _repository.GetCount(x => x.Slug.Contains(slug))); } - + [Fact] public async Task GetAllTest() { @@ -181,7 +181,7 @@ namespace Kyoo.Tests Assert.Equal(1, ret.Count); KAssert.DeepEqual(TestSample.Get(), ret.First()); } - + [Fact] public async Task DeleteAllTest() { diff --git a/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs index f28cef94..9a9f0597 100644 --- a/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs @@ -39,7 +39,7 @@ namespace Kyoo.Tests.Database { _repository = Repositories.LibraryManager.CollectionRepository; } - + [Fact] public async Task CreateWithEmptySlugTest() { @@ -47,7 +47,7 @@ namespace Kyoo.Tests.Database collection.Slug = ""; await Assert.ThrowsAsync(() => _repository.Create(collection)); } - + [Fact] public async Task CreateWithNumberSlugTest() { @@ -56,7 +56,7 @@ namespace Kyoo.Tests.Database Collection ret = await _repository.Create(collection); Assert.Equal("2!", ret.Slug); } - + [Fact] public async Task CreateWithoutNameTest() { @@ -64,7 +64,7 @@ namespace Kyoo.Tests.Database collection.Name = null; await Assert.ThrowsAsync(() => _repository.Create(collection)); } - + [Fact] public async Task CreateWithExternalIdTest() { @@ -85,14 +85,14 @@ namespace Kyoo.Tests.Database } }; await _repository.Create(collection); - + Collection retrieved = await _repository.Get(2); await Repositories.LibraryManager.Load(retrieved, x => x.ExternalIDs); Assert.Equal(2, retrieved.ExternalIDs.Count); KAssert.DeepEqual(collection.ExternalIDs.First(), retrieved.ExternalIDs.First()); KAssert.DeepEqual(collection.ExternalIDs.Last(), retrieved.ExternalIDs.Last()); } - + [Fact] public async Task EditTest() { @@ -103,13 +103,13 @@ namespace Kyoo.Tests.Database [Images.Poster] = "new-poster" }; await _repository.Edit(value, false); - + await using DatabaseContext database = Repositories.Context.New(); Collection retrieved = await database.Collections.FirstAsync(); - + KAssert.DeepEqual(value, retrieved); } - + [Fact] public async Task EditMetadataTest() { @@ -124,13 +124,13 @@ namespace Kyoo.Tests.Database }, }; await _repository.Edit(value, false); - + await using DatabaseContext database = Repositories.Context.New(); Collection retrieved = await database.Collections .Include(x => x.ExternalIDs) .ThenInclude(x => x.Provider) .FirstAsync(); - + KAssert.DeepEqual(value, retrieved); } @@ -166,7 +166,7 @@ namespace Kyoo.Tests.Database DataID = "id" }); await _repository.Edit(value, false); - + { await using DatabaseContext database = Repositories.Context.New(); Collection retrieved = await database.Collections @@ -177,7 +177,7 @@ namespace Kyoo.Tests.Database KAssert.DeepEqual(value, retrieved); } } - + [Theory] [InlineData("test")] [InlineData("super")] diff --git a/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs index 0aff5ec7..b8fcdbd0 100644 --- a/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs @@ -39,7 +39,7 @@ namespace Kyoo.Tests.Database { _repository = repositories.LibraryManager.EpisodeRepository; } - + [Fact] public async Task SlugEditTest() { @@ -54,7 +54,7 @@ namespace Kyoo.Tests.Database episode = await _repository.Get(1); Assert.Equal("new-slug-s1e1", episode.Slug); } - + [Fact] public async Task SeasonNumberEditTest() { @@ -70,7 +70,7 @@ namespace Kyoo.Tests.Database episode = await _repository.Get(1); Assert.Equal($"{TestSample.Get().Slug}-s2e1", episode.Slug); } - + [Fact] public async Task EpisodeNumberEditTest() { @@ -86,7 +86,7 @@ namespace Kyoo.Tests.Database episode = await _repository.Get(1); Assert.Equal($"{TestSample.Get().Slug}-s1e2", episode.Slug); } - + [Fact] public async Task EpisodeCreationSlugTest() { @@ -102,17 +102,17 @@ namespace Kyoo.Tests.Database [Fact] public void AbsoluteSlugTest() { - Assert.Equal($"{TestSample.Get().Slug}-{TestSample.GetAbsoluteEpisode().AbsoluteNumber}", + Assert.Equal($"{TestSample.Get().Slug}-{TestSample.GetAbsoluteEpisode().AbsoluteNumber}", TestSample.GetAbsoluteEpisode().Slug); } - + [Fact] public async Task EpisodeCreationAbsoluteSlugTest() { Episode episode = await _repository.Create(TestSample.GetAbsoluteEpisode()); Assert.Equal($"{TestSample.Get().Slug}-{TestSample.GetAbsoluteEpisode().AbsoluteNumber}", episode.Slug); } - + [Fact] public async Task SlugEditAbsoluteTest() { @@ -126,8 +126,8 @@ namespace Kyoo.Tests.Database episode = await _repository.Get(2); Assert.Equal($"new-slug-3", episode.Slug); } - - + + [Fact] public async Task AbsoluteNumberEditTest() { @@ -142,7 +142,7 @@ namespace Kyoo.Tests.Database episode = await _repository.Get(2); Assert.Equal($"{TestSample.Get().Slug}-56", episode.Slug); } - + [Fact] public async Task AbsoluteToNormalEditTest() { @@ -158,7 +158,7 @@ namespace Kyoo.Tests.Database episode = await _repository.Get(2); Assert.Equal($"{TestSample.Get().Slug}-s1e2", episode.Slug); } - + [Fact] public async Task NormalToAbsoluteEditTest() { @@ -170,7 +170,7 @@ namespace Kyoo.Tests.Database episode = await _repository.Get(1); Assert.Equal($"{TestSample.Get().Slug}-12", episode.Slug); } - + [Fact] public async Task MovieEpisodeTest() { @@ -179,7 +179,7 @@ namespace Kyoo.Tests.Database episode = await _repository.Get(3); Assert.Equal(TestSample.Get().Slug, episode.Slug); } - + [Fact] public async Task MovieEpisodeEditTest() { @@ -192,7 +192,7 @@ namespace Kyoo.Tests.Database Episode episode = await _repository.Get(3); Assert.Equal("john-wick", episode.Slug); } - + [Fact] public async Task CreateWithExternalIdTest() { @@ -213,14 +213,14 @@ namespace Kyoo.Tests.Database } }; await _repository.Create(value); - + Episode retrieved = await _repository.Get(2); await Repositories.LibraryManager.Load(retrieved, x => x.ExternalIDs); Assert.Equal(2, retrieved.ExternalIDs.Count); KAssert.DeepEqual(value.ExternalIDs.First(), retrieved.ExternalIDs.First()); KAssert.DeepEqual(value.ExternalIDs.Last(), retrieved.ExternalIDs.Last()); } - + [Fact] public async Task EditTest() { @@ -231,13 +231,13 @@ namespace Kyoo.Tests.Database [Images.Poster] = "new-poster" }; await _repository.Edit(value, false); - + await using DatabaseContext database = Repositories.Context.New(); Episode retrieved = await database.Episodes.FirstAsync(); - + KAssert.DeepEqual(value, retrieved); } - + [Fact] public async Task EditMetadataTest() { @@ -252,13 +252,13 @@ namespace Kyoo.Tests.Database }, }; await _repository.Edit(value, false); - + await using DatabaseContext database = Repositories.Context.New(); Episode retrieved = await database.Episodes .Include(x => x.ExternalIDs) .ThenInclude(x => x.Provider) .FirstAsync(); - + KAssert.DeepEqual(value, retrieved); } @@ -294,7 +294,7 @@ namespace Kyoo.Tests.Database DataID = "id" }); await _repository.Edit(value, false); - + { await using DatabaseContext database = Repositories.Context.New(); Episode retrieved = await database.Episodes @@ -305,7 +305,7 @@ namespace Kyoo.Tests.Database KAssert.DeepEqual(value, retrieved); } } - + [Theory] [InlineData("test")] [InlineData("super")] diff --git a/tests/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs b/tests/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs index d370996a..2d6fdef3 100644 --- a/tests/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs @@ -26,12 +26,12 @@ namespace Kyoo.Tests.Database : base(new RepositoryActivator(output, postgres)) { } } } - + public abstract class ALibraryItemTest { private readonly ILibraryItemRepository _repository; private readonly RepositoryActivator _repositories; - + protected ALibraryItemTest(RepositoryActivator repositories) { _repositories = repositories; @@ -43,7 +43,7 @@ namespace Kyoo.Tests.Database { Assert.Equal(2, await _repository.GetCount()); } - + [Fact] public async Task GetShowTests() { @@ -51,7 +51,7 @@ namespace Kyoo.Tests.Database LibraryItem actual = await _repository.Get(1); KAssert.DeepEqual(expected, actual); } - + [Fact] public async Task GetCollectionTests() { @@ -59,7 +59,7 @@ namespace Kyoo.Tests.Database LibraryItem actual = await _repository.Get(-1); KAssert.DeepEqual(expected, actual); } - + [Fact] public async Task GetShowSlugTests() { @@ -67,7 +67,7 @@ namespace Kyoo.Tests.Database LibraryItem actual = await _repository.Get(TestSample.Get().Slug); KAssert.DeepEqual(expected, actual); } - + [Fact] public async Task GetCollectionSlugTests() { @@ -75,7 +75,7 @@ namespace Kyoo.Tests.Database LibraryItem actual = await _repository.Get(TestSample.Get().Slug); KAssert.DeepEqual(expected, actual); } - + [Fact] public async Task GetDuplicatedSlugTests() { diff --git a/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs index a1a3d131..d6a4ea37 100644 --- a/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs @@ -48,7 +48,7 @@ namespace Kyoo.Tests.Database library.Paths = null; await Assert.ThrowsAsync(() => _repository.Create(library)); } - + [Fact] public async Task CreateWithEmptySlugTest() { @@ -56,7 +56,7 @@ namespace Kyoo.Tests.Database library.Slug = ""; await Assert.ThrowsAsync(() => _repository.Create(library)); } - + [Fact] public async Task CreateWithNumberSlugTest() { @@ -65,7 +65,7 @@ namespace Kyoo.Tests.Database Library ret = await _repository.Create(library); Assert.Equal("2!", ret.Slug); } - + [Fact] public async Task CreateWithoutNameTest() { @@ -73,7 +73,7 @@ namespace Kyoo.Tests.Database library.Name = null; await Assert.ThrowsAsync(() => _repository.Create(library)); } - + [Fact] public async Task CreateWithProvider() { @@ -90,16 +90,16 @@ namespace Kyoo.Tests.Database public async Task EditTest() { Library value = await _repository.Get(TestSample.Get().Slug); - value.Paths = new [] {"/super", "/test"}; + value.Paths = new[] { "/super", "/test" }; value.Name = "New Title"; Library edited = await _repository.Edit(value, false); - + await using DatabaseContext database = Repositories.Context.New(); Library show = await database.Libraries.FirstAsync(); - + KAssert.DeepEqual(show, edited); } - + [Fact] public async Task EditProvidersTest() { @@ -109,17 +109,17 @@ namespace Kyoo.Tests.Database TestSample.GetNew() }; Library edited = await _repository.Edit(value, false); - + await using DatabaseContext database = Repositories.Context.New(); Library show = await database.Libraries .Include(x => x.Providers) .FirstAsync(); - + show.Providers.ForEach(x => x.Libraries = null); edited.Providers.ForEach(x => x.Libraries = null); KAssert.DeepEqual(show, edited); } - + [Fact] public async Task AddProvidersTest() { @@ -127,17 +127,17 @@ namespace Kyoo.Tests.Database await Repositories.LibraryManager.Load(value, x => x.Providers); value.Providers.Add(TestSample.GetNew()); Library edited = await _repository.Edit(value, false); - + await using DatabaseContext database = Repositories.Context.New(); Library show = await database.Libraries .Include(x => x.Providers) .FirstAsync(); - + show.Providers.ForEach(x => x.Libraries = null); edited.Providers.ForEach(x => x.Libraries = null); KAssert.DeepEqual(show, edited); } - + [Theory] [InlineData("test")] [InlineData("super")] @@ -150,7 +150,7 @@ namespace Kyoo.Tests.Database { Slug = "super-test", Name = "This is a test title", - Paths = new [] {"path"} + Paths = new[] { "path" } }; await _repository.Create(value); ICollection ret = await _repository.Search(query); diff --git a/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs index 669f6f91..cb020c38 100644 --- a/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs @@ -38,7 +38,7 @@ namespace Kyoo.Tests.Database { _repository = Repositories.LibraryManager.PeopleRepository; } - + [Fact] public async Task CreateWithExternalIdTest() { @@ -59,14 +59,14 @@ namespace Kyoo.Tests.Database } }; await _repository.Create(value); - + People retrieved = await _repository.Get(2); await Repositories.LibraryManager.Load(retrieved, x => x.ExternalIDs); Assert.Equal(2, retrieved.ExternalIDs.Count); KAssert.DeepEqual(value.ExternalIDs.First(), retrieved.ExternalIDs.First()); KAssert.DeepEqual(value.ExternalIDs.Last(), retrieved.ExternalIDs.Last()); } - + [Fact] public async Task EditTest() { @@ -77,13 +77,13 @@ namespace Kyoo.Tests.Database [Images.Poster] = "new-poster" }; await _repository.Edit(value, false); - + await using DatabaseContext database = Repositories.Context.New(); People retrieved = await database.People.FirstAsync(); - + KAssert.DeepEqual(value, retrieved); } - + [Fact] public async Task EditMetadataTest() { @@ -98,13 +98,13 @@ namespace Kyoo.Tests.Database }, }; await _repository.Edit(value, false); - + await using DatabaseContext database = Repositories.Context.New(); People retrieved = await database.People .Include(x => x.ExternalIDs) .ThenInclude(x => x.Provider) .FirstAsync(); - + KAssert.DeepEqual(value, retrieved); } @@ -140,7 +140,7 @@ namespace Kyoo.Tests.Database DataID = "id" }); await _repository.Edit(value, false); - + { await using DatabaseContext database = Repositories.Context.New(); People retrieved = await database.People @@ -151,7 +151,7 @@ namespace Kyoo.Tests.Database KAssert.DeepEqual(value, retrieved); } } - + [Theory] [InlineData("Me")] [InlineData("me")] diff --git a/tests/Kyoo.Tests/Database/SpecificTests/SanityTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/SanityTests.cs index efbdbd26..c172fb75 100644 --- a/tests/Kyoo.Tests/Database/SpecificTests/SanityTests.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/SanityTests.cs @@ -10,10 +10,10 @@ namespace Kyoo.Tests.Database public class GlobalTests : IDisposable, IAsyncDisposable { private readonly RepositoryActivator _repositories; - + public GlobalTests(ITestOutputHelper output) { - _repositories = new RepositoryActivator(output); + _repositories = new RepositoryActivator(output); } public void Dispose() @@ -26,7 +26,7 @@ namespace Kyoo.Tests.Database { return _repositories.DisposeAsync(); } - + [Fact] [SuppressMessage("ReSharper", "EqualExpressionComparison")] public void SampleTest() diff --git a/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs index 54d164a9..fb2aed76 100644 --- a/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs @@ -54,7 +54,7 @@ namespace Kyoo.Tests.Database season = await _repository.Get(1); Assert.Equal("new-slug-s1", season.Slug); } - + [Fact] public async Task SeasonNumberEditTest() { @@ -69,7 +69,7 @@ namespace Kyoo.Tests.Database season = await _repository.Get(1); Assert.Equal("anohana-s2", season.Slug); } - + [Fact] public async Task SeasonCreationSlugTest() { @@ -80,7 +80,7 @@ namespace Kyoo.Tests.Database }); Assert.Equal($"{TestSample.Get().Slug}-s2", season.Slug); } - + [Fact] public async Task CreateWithExternalIdTest() { @@ -101,14 +101,14 @@ namespace Kyoo.Tests.Database } }; await _repository.Create(season); - + Season retrieved = await _repository.Get(2); await Repositories.LibraryManager.Load(retrieved, x => x.ExternalIDs); Assert.Equal(2, retrieved.ExternalIDs.Count); KAssert.DeepEqual(season.ExternalIDs.First(), retrieved.ExternalIDs.First()); KAssert.DeepEqual(season.ExternalIDs.Last(), retrieved.ExternalIDs.Last()); } - + [Fact] public async Task EditTest() { @@ -119,13 +119,13 @@ namespace Kyoo.Tests.Database [Images.Poster] = "new-poster" }; await _repository.Edit(value, false); - + await using DatabaseContext database = Repositories.Context.New(); Season retrieved = await database.Seasons.FirstAsync(); - + KAssert.DeepEqual(value, retrieved); } - + [Fact] public async Task EditMetadataTest() { @@ -140,13 +140,13 @@ namespace Kyoo.Tests.Database }, }; await _repository.Edit(value, false); - + await using DatabaseContext database = Repositories.Context.New(); Season retrieved = await database.Seasons .Include(x => x.ExternalIDs) .ThenInclude(x => x.Provider) .FirstAsync(); - + KAssert.DeepEqual(value, retrieved); } @@ -182,7 +182,7 @@ namespace Kyoo.Tests.Database DataID = "id" }); await _repository.Edit(value, false); - + { await using DatabaseContext database = Repositories.Context.New(); Season retrieved = await database.Seasons @@ -193,7 +193,7 @@ namespace Kyoo.Tests.Database KAssert.DeepEqual(value, retrieved); } } - + [Theory] [InlineData("test")] [InlineData("super")] diff --git a/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs index b502e125..0faab54d 100644 --- a/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs @@ -41,7 +41,7 @@ namespace Kyoo.Tests.Database { _repository = Repositories.LibraryManager.ShowRepository; } - + [Fact] public async Task EditTest() { @@ -50,32 +50,32 @@ namespace Kyoo.Tests.Database value.Title = "New Title"; Show edited = await _repository.Edit(value, false); KAssert.DeepEqual(value, edited); - + await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows.FirstAsync(); - + KAssert.DeepEqual(show, value); } - + [Fact] public async Task EditGenreTest() { Show value = await _repository.Get(TestSample.Get().Slug); - value.Genres = new[] {new Genre("test")}; + value.Genres = new[] { new Genre("test") }; Show edited = await _repository.Edit(value, false); - + Assert.Equal(value.Slug, edited.Slug); - Assert.Equal(value.Genres.Select(x => new{x.Slug, x.Name}), edited.Genres.Select(x => new{x.Slug, x.Name})); - + Assert.Equal(value.Genres.Select(x => new { x.Slug, x.Name }), edited.Genres.Select(x => new { x.Slug, x.Name })); + await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows .Include(x => x.Genres) .FirstAsync(); - + Assert.Equal(value.Slug, show.Slug); - Assert.Equal(value.Genres.Select(x => new{x.Slug, x.Name}), show.Genres.Select(x => new{x.Slug, x.Name})); + Assert.Equal(value.Genres.Select(x => new { x.Slug, x.Name }), show.Genres.Select(x => new { x.Slug, x.Name })); } - + [Fact] public async Task AddGenreTest() { @@ -83,55 +83,55 @@ namespace Kyoo.Tests.Database await Repositories.LibraryManager.Load(value, x => x.Genres); value.Genres.Add(new Genre("test")); Show edited = await _repository.Edit(value, false); - + Assert.Equal(value.Slug, edited.Slug); - Assert.Equal(value.Genres.Select(x => new{x.Slug, x.Name}), edited.Genres.Select(x => new{x.Slug, x.Name})); - + Assert.Equal(value.Genres.Select(x => new { x.Slug, x.Name }), edited.Genres.Select(x => new { x.Slug, x.Name })); + await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows .Include(x => x.Genres) .FirstAsync(); - + Assert.Equal(value.Slug, show.Slug); - Assert.Equal(value.Genres.Select(x => new{x.Slug, x.Name}), show.Genres.Select(x => new{x.Slug, x.Name})); + Assert.Equal(value.Genres.Select(x => new { x.Slug, x.Name }), show.Genres.Select(x => new { x.Slug, x.Name })); } - + [Fact] public async Task EditStudioTest() { Show value = await _repository.Get(TestSample.Get().Slug); value.Studio = new Studio("studio"); Show edited = await _repository.Edit(value, false); - + Assert.Equal(value.Slug, edited.Slug); Assert.Equal("studio", edited.Studio.Slug); - + await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows .Include(x => x.Studio) .FirstAsync(); - + Assert.Equal(value.Slug, show.Slug); Assert.Equal("studio", show.Studio.Slug); } - + [Fact] public async Task EditAliasesTest() { Show value = await _repository.Get(TestSample.Get().Slug); - value.Aliases = new[] {"NiceNewAlias", "SecondAlias"}; + value.Aliases = new[] { "NiceNewAlias", "SecondAlias" }; Show edited = await _repository.Edit(value, false); - + Assert.Equal(value.Slug, edited.Slug); Assert.Equal(value.Aliases, edited.Aliases); - + await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows.FirstAsync(); - + Assert.Equal(value.Slug, show.Slug); Assert.Equal(value.Aliases, show.Aliases); } - + [Fact] public async Task EditPeopleTest() { @@ -148,25 +148,25 @@ namespace Kyoo.Tests.Database } }; Show edited = await _repository.Edit(value, false); - + Assert.Equal(value.Slug, edited.Slug); Assert.Equal(edited.People.First().ShowID, value.ID); Assert.Equal( - value.People.Select(x => new{x.Role, x.Slug, x.People.Name}), - edited.People.Select(x => new{x.Role, x.Slug, x.People.Name})); - + value.People.Select(x => new { x.Role, x.Slug, x.People.Name }), + edited.People.Select(x => new { x.Role, x.Slug, x.People.Name })); + await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows .Include(x => x.People) .ThenInclude(x => x.People) .FirstAsync(); - + Assert.Equal(value.Slug, show.Slug); Assert.Equal( - value.People.Select(x => new{x.Role, x.Slug, x.People.Name}), - show.People.Select(x => new{x.Role, x.Slug, x.People.Name})); + value.People.Select(x => new { x.Role, x.Slug, x.People.Name }), + show.People.Select(x => new { x.Role, x.Slug, x.People.Name })); } - + [Fact] public async Task EditExternalIDsTest() { @@ -180,24 +180,24 @@ namespace Kyoo.Tests.Database } }; Show edited = await _repository.Edit(value, false); - + Assert.Equal(value.Slug, edited.Slug); Assert.Equal( - value.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug}), - edited.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug})); - + value.ExternalIDs.Select(x => new { x.DataID, x.Provider.Slug }), + edited.ExternalIDs.Select(x => new { x.DataID, x.Provider.Slug })); + await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows .Include(x => x.ExternalIDs) .ThenInclude(x => x.Provider) .FirstAsync(); - + Assert.Equal(value.Slug, show.Slug); Assert.Equal( - value.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug}), - show.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug})); + value.ExternalIDs.Select(x => new { x.DataID, x.Provider.Slug }), + show.ExternalIDs.Select(x => new { x.DataID, x.Provider.Slug })); } - + [Fact] public async Task EditResetOldTest() { @@ -207,12 +207,12 @@ namespace Kyoo.Tests.Database ID = value.ID, Title = "Reset" }; - + await Assert.ThrowsAsync(() => _repository.Edit(newValue, true)); - + newValue.Slug = "reset"; Show edited = await _repository.Edit(newValue, true); - + Assert.Equal(value.ID, edited.ID); Assert.Null(edited.Overview); Assert.Equal("reset", edited.Slug); @@ -223,7 +223,7 @@ namespace Kyoo.Tests.Database Assert.Null(edited.Genres); Assert.Null(edited.Studio); } - + [Fact] public async Task CreateWithRelationsTest() { @@ -259,7 +259,7 @@ namespace Kyoo.Tests.Database expected.Studio = new Studio("studio"); Show created = await _repository.Create(expected); KAssert.DeepEqual(expected, created); - + await using DatabaseContext context = Repositories.Context.New(); Show retrieved = await context.Shows .Include(x => x.ExternalIDs) @@ -269,14 +269,14 @@ namespace Kyoo.Tests.Database .ThenInclude(x => x.People) .Include(x => x.Studio) .FirstAsync(x => x.ID == created.ID); - retrieved.People.ForEach(x => + retrieved.People.ForEach(x => { x.Show = null; x.People.Roles = null; }); retrieved.Studio.Shows = null; retrieved.Genres.ForEach(x => x.Shows = null); - + expected.Genres.ForEach(x => x.Shows = null); expected.People.ForEach(x => { @@ -286,7 +286,7 @@ namespace Kyoo.Tests.Database retrieved.Should().BeEquivalentTo(expected); } - + [Fact] public async Task CreateWithExternalID() { @@ -322,14 +322,14 @@ namespace Kyoo.Tests.Database Show created = await _repository.Create(test); Assert.Equal("300!", created.Slug); } - + [Fact] public async Task GetSlugTest() { Show reference = TestSample.Get(); Assert.Equal(reference.Slug, await _repository.GetSlug(reference.ID)); } - + [Theory] [InlineData("test")] [InlineData("super")] @@ -347,7 +347,7 @@ namespace Kyoo.Tests.Database ICollection ret = await _repository.Search(query); KAssert.DeepEqual(value, ret.First()); } - + [Fact] public async Task DeleteShowWithEpisodeAndSeason() { @@ -368,7 +368,7 @@ namespace Kyoo.Tests.Database { await Repositories.LibraryManager.Create(TestSample.GetNew()); await _repository.AddShowLink(1, 2, null); - + await using DatabaseContext context = Repositories.Context.New(); Show show = context.Shows .Include(x => x.Libraries) diff --git a/tests/Kyoo.Tests/Database/SpecificTests/TrackTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/TrackTests.cs index 2bf7cc17..d451c6df 100644 --- a/tests/Kyoo.Tests/Database/SpecificTests/TrackTests.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/TrackTests.cs @@ -35,7 +35,7 @@ namespace Kyoo.Tests.Database { _repository = repositories.LibraryManager.TrackRepository; } - + [Fact] public async Task SlugEditTest() { @@ -47,7 +47,7 @@ namespace Kyoo.Tests.Database Track track = await _repository.Get(1); Assert.Equal("new-slug-s1e1.eng-1.subtitle", track.Slug); } - + [Fact] public async Task UndefinedLanguageSlugTest() { diff --git a/tests/Kyoo.Tests/Database/TestContext.cs b/tests/Kyoo.Tests/Database/TestContext.cs index 79d873c4..aa6cc396 100644 --- a/tests/Kyoo.Tests/Database/TestContext.cs +++ b/tests/Kyoo.Tests/Database/TestContext.cs @@ -18,7 +18,7 @@ namespace Kyoo.Tests /// The internal sqlite connection used by all context returned by this class. /// private readonly SqliteConnection _connection; - + /// /// The context's options that specify to use an in memory Sqlite database. /// @@ -28,7 +28,7 @@ namespace Kyoo.Tests { _connection = new SqliteConnection("DataSource=:memory:"); _connection.Open(); - + _context = new DbContextOptionsBuilder() .UseSqlite(_connection) .UseLoggerFactory(LoggerFactory.Create(x => @@ -39,12 +39,12 @@ namespace Kyoo.Tests .EnableSensitiveDataLogging() .EnableDetailedErrors() .Options; - + using DatabaseContext context = New(); context.Database.Migrate(); TestSample.FillDatabase(context); } - + public override void Dispose() { _connection.Close(); @@ -60,30 +60,30 @@ namespace Kyoo.Tests return new SqLiteContext(_context); } } - + [CollectionDefinition(nameof(Postgresql))] public class PostgresCollection : ICollectionFixture - {} + { } public sealed class PostgresFixture : IDisposable { private readonly DbContextOptions _options; - + public string Template { get; } public string Connection => PostgresTestContext.GetConnectionString(Template); - + public PostgresFixture() { // TODO Assert.Skip when postgres is not available. (this needs xunit v3) - + string id = Guid.NewGuid().ToString().Replace('-', '_'); Template = $"kyoo_template_{id}"; - + _options = new DbContextOptionsBuilder() .UseNpgsql(Connection) .Options; - + using PostgresContext context = new(_options); context.Database.Migrate(); @@ -94,19 +94,19 @@ namespace Kyoo.Tests TestSample.FillDatabase(context); conn.Close(); } - + public void Dispose() { using PostgresContext context = new(_options); context.Database.EnsureDeleted(); } } - + public sealed class PostgresTestContext : TestContext { private readonly NpgsqlConnection _connection; private readonly DbContextOptions _context; - + public PostgresTestContext(PostgresFixture template, ITestOutputHelper output) { string id = Guid.NewGuid().ToString().Replace('-', '_'); @@ -133,7 +133,7 @@ namespace Kyoo.Tests .EnableDetailedErrors() .Options; } - + public static string GetConnectionString(string database) { string server = Environment.GetEnvironmentVariable("POSTGRES_HOST") ?? "127.0.0.1"; @@ -142,7 +142,7 @@ namespace Kyoo.Tests string password = Environment.GetEnvironmentVariable("POSTGRES_PASSWORD") ?? "kyooPassword"; return $"Server={server};Port={port};Database={database};User ID={username};Password={password};Include Error Detail=true"; } - + public override void Dispose() { using DatabaseContext db = New(); @@ -162,8 +162,8 @@ namespace Kyoo.Tests return new PostgresContext(_context); } } - - + + /// /// Class responsible to fill and create in memory databases for unit tests. /// @@ -172,18 +172,18 @@ namespace Kyoo.Tests /// /// Add an arbitrary data to the test context. /// - public void Add(T obj) + public void Add(T obj) where T : class { using DatabaseContext context = New(); context.Set().Add(obj); context.SaveChanges(); } - + /// /// Add an arbitrary data to the test context. /// - public async Task AddAsync(T obj) + public async Task AddAsync(T obj) where T : class { await using DatabaseContext context = New(); diff --git a/tests/Kyoo.Tests/Database/TestSample.cs b/tests/Kyoo.Tests/Database/TestSample.cs index 2cf72abd..10994708 100644 --- a/tests/Kyoo.Tests/Database/TestSample.cs +++ b/tests/Kyoo.Tests/Database/TestSample.cs @@ -16,7 +16,7 @@ namespace Kyoo.Tests ID = 2, Slug = "new-library", Name = "New Library", - Paths = new [] {"/a/random/path"} + Paths = new[] { "/a/random/path" } } }, { @@ -121,8 +121,8 @@ namespace Kyoo.Tests } } }; - - + + private static readonly Dictionary> Samples = new() { { @@ -132,7 +132,7 @@ namespace Kyoo.Tests ID = 1, Slug = "deck", Name = "Deck", - Paths = new[] {"/path/to/deck"} + Paths = new[] { "/path/to/deck" } } }, { @@ -190,7 +190,7 @@ namespace Kyoo.Tests Title = "Season 1", Overview = "The first season", StartDate = new DateTime(2020, 06, 05), - EndDate = new DateTime(2020, 07, 05), + EndDate = new DateTime(2020, 07, 05), Images = new Dictionary { [Images.Poster] = "Poster", @@ -297,7 +297,7 @@ namespace Kyoo.Tests Username = "User", Email = "user@im-a-user.com", Password = "MD5-encoded", - Permissions = new [] {"overall.read"} + Permissions = new[] { "overall.read" } } } }; @@ -306,7 +306,7 @@ namespace Kyoo.Tests { return (T)Samples[typeof(T)](); } - + public static T GetNew() { return (T)NewSamples[typeof(T)](); @@ -317,7 +317,7 @@ namespace Kyoo.Tests Collection collection = Get(); collection.ID = 0; context.Collections.Add(collection); - + Show show = Get(); show.ID = 0; show.StudioID = 0; @@ -345,26 +345,26 @@ namespace Kyoo.Tests Studio studio = Get(); studio.ID = 0; - studio.Shows = new List {show}; + studio.Shows = new List { show }; context.Studios.Add(studio); Genre genre = Get(); genre.ID = 0; - genre.Shows = new List {show}; + genre.Shows = new List { show }; context.Genres.Add(genre); People people = Get(); people.ID = 0; context.People.Add(people); - + Provider provider = Get(); provider.ID = 0; context.Providers.Add(provider); - + Library library = Get(); library.ID = 0; - library.Collections = new List {collection}; - library.Providers = new List {provider}; + library.Collections = new List { collection }; + library.Providers = new List { provider }; context.Libraries.Add(library); User user = Get(); @@ -417,4 +417,4 @@ namespace Kyoo.Tests }; } } -} \ No newline at end of file +} diff --git a/tests/Kyoo.Tests/Identifier/IdentifierTests.cs b/tests/Kyoo.Tests/Identifier/IdentifierTests.cs index 3ab526d6..337cb924 100644 --- a/tests/Kyoo.Tests/Identifier/IdentifierTests.cs +++ b/tests/Kyoo.Tests/Identifier/IdentifierTests.cs @@ -14,13 +14,13 @@ namespace Kyoo.Tests.Identifier { private readonly Mock _manager; private readonly IIdentifier _identifier; - + public Identifier() { Mock> options = new(); options.Setup(x => x.CurrentValue).Returns(new MediaOptions { - Regex = new [] + Regex = new[] { "^\\/?(?.+)?\\/(?.+?)(?: \\((?\\d+)\\))?\\/\\k(?: \\(\\d+\\))? S(?\\d+)E(?\\d+)\\..*$", "^\\/?(?.+)?\\/(?.+?)(?: \\((?\\d+)\\))?\\/\\k(?: \\(\\d+\\))? (?\\d+)\\..*$", @@ -31,12 +31,12 @@ namespace Kyoo.Tests.Identifier "^(?.+)\\.(?\\w{1,3})\\.(?default\\.)?(?forced\\.)?.*$" } }); - + _manager = new Mock(); _identifier = new RegexIdentifier(options.Object, _manager.Object); } - - + + [Fact] public async Task EpisodeIdentification() { @@ -56,7 +56,7 @@ namespace Kyoo.Tests.Identifier Assert.Equal(1, episode.EpisodeNumber); Assert.Null(episode.AbsoluteNumber); } - + [Fact] public async Task EpisodeIdentificationWithoutLibraryTrailingSlash() { @@ -76,7 +76,7 @@ namespace Kyoo.Tests.Identifier Assert.Equal(1, episode.EpisodeNumber); Assert.Null(episode.AbsoluteNumber); } - + [Fact] public async Task EpisodeIdentificationMultiplePaths() { @@ -96,7 +96,7 @@ namespace Kyoo.Tests.Identifier Assert.Equal(1, episode.EpisodeNumber); Assert.Null(episode.AbsoluteNumber); } - + [Fact] public async Task AbsoluteEpisodeIdentification() { @@ -116,7 +116,7 @@ namespace Kyoo.Tests.Identifier Assert.Null(episode.EpisodeNumber); Assert.Equal(100, episode.AbsoluteNumber); } - + [Fact] public async Task MovieEpisodeIdentification() { @@ -137,7 +137,7 @@ namespace Kyoo.Tests.Identifier Assert.Null(episode.EpisodeNumber); Assert.Null(episode.AbsoluteNumber); } - + [Fact] public async Task InvalidEpisodeIdentification() { @@ -147,7 +147,7 @@ namespace Kyoo.Tests.Identifier }); await Assert.ThrowsAsync(() => _identifier.Identify("/invalid/path")); } - + [Fact] public async Task SubtitleIdentification() { @@ -163,7 +163,7 @@ namespace Kyoo.Tests.Identifier Assert.False(track.IsForced); Assert.StartsWith("/kyoo/Library/Collection/Show (2000)/Show", track.Episode.Path); } - + [Fact] public async Task SubtitleIdentificationUnknownCodec() { @@ -179,7 +179,7 @@ namespace Kyoo.Tests.Identifier Assert.False(track.IsForced); Assert.StartsWith("/kyoo/Library/Collection/Show (2000)/Show", track.Episode.Path); } - + [Fact] public async Task InvalidSubtitleIdentification() { diff --git a/tests/Kyoo.Tests/Identifier/ProviderTests.cs b/tests/Kyoo.Tests/Identifier/ProviderTests.cs index 384e6b79..e7a6bac9 100644 --- a/tests/Kyoo.Tests/Identifier/ProviderTests.cs +++ b/tests/Kyoo.Tests/Identifier/ProviderTests.cs @@ -15,7 +15,7 @@ namespace Kyoo.Tests.Identifier public class ProviderTests { private readonly ILoggerFactory _factory; - + public ProviderTests(ITestOutputHelper output) { _factory = LoggerFactory.Create(x => @@ -24,7 +24,7 @@ namespace Kyoo.Tests.Identifier x.AddXunit(output); }); } - + [Fact] public async Task NoProviderGetTest() { @@ -38,7 +38,7 @@ namespace Kyoo.Tests.Identifier Show ret = await provider.Get(show); KAssert.DeepEqual(show, ret); } - + [Fact] public async Task NoProviderSearchTest() { @@ -47,7 +47,7 @@ namespace Kyoo.Tests.Identifier ICollection ret = await provider.Search("show"); Assert.Empty(ret); } - + [Fact] public async Task OneProviderGetTest() { @@ -60,14 +60,14 @@ namespace Kyoo.Tests.Identifier mock.Setup(x => x.Get(show)).ReturnsAsync(new Show { Title = "title", - Genres = new[] { new Genre("ToMerge")} + Genres = new[] { new Genre("ToMerge") } }); - AProviderComposite provider = new ProviderComposite(new [] + AProviderComposite provider = new ProviderComposite(new[] { mock.Object }, _factory.CreateLogger()); - + Show ret = await provider.Get(show); Assert.Equal(4, ret.ID); Assert.Equal("title", ret.Title); @@ -75,7 +75,7 @@ namespace Kyoo.Tests.Identifier Assert.Contains("genre", ret.Genres.Select(x => x.Slug)); Assert.Contains("tomerge", ret.Genres.Select(x => x.Slug)); } - + [Fact] public async Task FailingProviderGetTest() { @@ -89,30 +89,30 @@ namespace Kyoo.Tests.Identifier mock.Setup(x => x.Get(show)).ReturnsAsync(new Show { Title = "title", - Genres = new[] { new Genre("ToMerge")} + Genres = new[] { new Genre("ToMerge") } }); - + Mock mockTwo = new(); mockTwo.Setup(x => x.Provider).Returns(new Provider("mockTwo", "")); mockTwo.Setup(x => x.Get(show)).ReturnsAsync(new Show { Title = "title2", Status = Status.Finished, - Genres = new[] { new Genre("ToMerge")} + Genres = new[] { new Genre("ToMerge") } }); - + Mock mockFailing = new(); mockFailing.Setup(x => x.Provider).Returns(new Provider("mockFail", "")); mockFailing.Setup(x => x.Get(show)).Throws(); - - AProviderComposite provider = new ProviderComposite(new [] + + AProviderComposite provider = new ProviderComposite(new[] { mock.Object, mockTwo.Object, mockFailing.Object }, _factory.CreateLogger()); - + Show ret = await provider.Get(show); Assert.Equal(4, ret.ID); Assert.Equal("title", ret.Title); diff --git a/tests/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs b/tests/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs index 48e6cf56..49dc7338 100644 --- a/tests/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs +++ b/tests/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs @@ -25,7 +25,7 @@ namespace Kyoo.Tests.Identifier.Tvdb }; Provider provider = TestSample.Get(); Show show = result.ToShow(provider); - + Assert.Equal("slug", show.Slug); Assert.Equal("name", show.Title); Assert.Single(show.Aliases); @@ -85,7 +85,7 @@ namespace Kyoo.Tests.Identifier.Tvdb Poster = "poster", FanArt = "fanart", Id = 5, - Genre = new [] + Genre = new[] { "Action", "Test With Spéàacial characters" @@ -93,7 +93,7 @@ namespace Kyoo.Tests.Identifier.Tvdb }; Provider provider = TestSample.Get(); Show show = result.ToShow(provider); - + Assert.Equal("slug", show.Slug); Assert.Equal("name", show.Title); Assert.Single(show.Aliases); @@ -125,13 +125,13 @@ namespace Kyoo.Tests.Identifier.Tvdb Role = "role" }; PeopleRole people = actor.ToPeopleRole(); - + Assert.Equal("name", people.Slug); Assert.Equal("Name", people.People.Name); Assert.Equal("role", people.Role); Assert.Equal("https://www.thetvdb.com/banners/image", people.People.Images[Images.Poster]); } - + [Fact] public void EpisodeRecordToEpisode() { @@ -147,7 +147,7 @@ namespace Kyoo.Tests.Identifier.Tvdb }; Provider provider = TestSample.Get(); Episode episode = record.ToEpisode(provider); - + Assert.Equal("title", episode.Title); Assert.Equal(2, episode.SeasonNumber); Assert.Equal(3, episode.EpisodeNumber); diff --git a/tests/Kyoo.Tests/KAssert.cs b/tests/Kyoo.Tests/KAssert.cs index e4c2e28f..594f2561 100644 --- a/tests/Kyoo.Tests/KAssert.cs +++ b/tests/Kyoo.Tests/KAssert.cs @@ -29,7 +29,7 @@ namespace Kyoo.Tests { throw new XunitException(); } - + /// /// Explicitly fail a test. /// diff --git a/tests/Kyoo.Tests/Utility/EnumerableTests.cs b/tests/Kyoo.Tests/Utility/EnumerableTests.cs index 627de26b..1a4c8a31 100644 --- a/tests/Kyoo.Tests/Utility/EnumerableTests.cs +++ b/tests/Kyoo.Tests/Utility/EnumerableTests.cs @@ -12,18 +12,18 @@ namespace Kyoo.Tests.Utility [Fact] public void MapTest() { - int[] list = {1, 2, 3, 4}; + int[] list = { 1, 2, 3, 4 }; Assert.All(list.Map((x, i) => (x, i)), x => Assert.Equal(x.x - 1, x.i)); Assert.Throws(() => list.Map(((Func)null)!)); list = null; Assert.Throws(() => list!.Map((x, _) => x + 1)); } - + [Fact] public async Task MapAsyncTest() { - int[] list = {1, 2, 3, 4}; - await foreach((int x, int i) in list.MapAsync((x, i) => Task.FromResult((x, i)))) + int[] list = { 1, 2, 3, 4 }; + await foreach ((int x, int i) in list.MapAsync((x, i) => Task.FromResult((x, i)))) { Assert.Equal(x - 1, i); } @@ -31,13 +31,13 @@ namespace Kyoo.Tests.Utility list = null; Assert.Throws(() => list!.MapAsync((x, _) => Task.FromResult(x + 1))); } - + [Fact] public async Task SelectAsyncTest() { - int[] list = {1, 2, 3, 4}; + int[] list = { 1, 2, 3, 4 }; int i = 2; - await foreach(int x in list.SelectAsync(x => Task.FromResult(x + 1))) + await foreach (int x in list.SelectAsync(x => Task.FromResult(x + 1))) { Assert.Equal(i++, x); } @@ -45,28 +45,28 @@ namespace Kyoo.Tests.Utility list = null; Assert.Throws(() => list!.SelectAsync(x => Task.FromResult(x + 1))); } - + [Fact] public async Task ToListAsyncTest() { - int[] expected = {1, 2, 3, 4}; + int[] expected = { 1, 2, 3, 4 }; IAsyncEnumerable list = expected.SelectAsync(Task.FromResult); Assert.Equal(expected, await list.ToListAsync()); list = null; await Assert.ThrowsAsync(() => list!.ToListAsync()); } - + [Fact] public void IfEmptyTest() { - int[] list = {1, 2, 3, 4}; + int[] list = { 1, 2, 3, 4 }; list = list.IfEmpty(() => KAssert.Fail("Empty action should not be triggered.")).ToArray(); Assert.Throws(() => list.IfEmpty(null!).ToList()); list = null; - Assert.Throws(() => list!.IfEmpty(() => {}).ToList()); + Assert.Throws(() => list!.IfEmpty(() => { }).ToList()); list = Array.Empty(); Assert.Throws(() => list.IfEmpty(() => throw new ArgumentException()).ToList()); - Assert.Empty(list.IfEmpty(() => {})); + Assert.Empty(list.IfEmpty(() => { })); } } } \ No newline at end of file diff --git a/tests/Kyoo.Tests/Utility/MergerTests.cs b/tests/Kyoo.Tests/Utility/MergerTests.cs index 2765c7e2..598b2593 100644 --- a/tests/Kyoo.Tests/Utility/MergerTests.cs +++ b/tests/Kyoo.Tests/Utility/MergerTests.cs @@ -24,7 +24,7 @@ namespace Kyoo.Tests.Utility Assert.Null(genre.Name); Assert.Null(genre.Slug); } - + [Fact] public void MergeTest() { @@ -42,7 +42,7 @@ namespace Kyoo.Tests.Utility Assert.Equal("test", genre.Name); Assert.Null(genre.Slug); } - + [Fact] [SuppressMessage("ReSharper", "ExpressionIsAlwaysNull")] public void MergeNullTests() @@ -65,7 +65,7 @@ namespace Kyoo.Tests.Utility throw exception; } } - + [Fact] public void OnMergeTest() { @@ -81,25 +81,25 @@ namespace Kyoo.Tests.Utility Assert.True(ReferenceEquals(test2, ex.Data[0])); } } - + private class Test { public int ID { get; set; } public int[] Numbers { get; set; } } - + [Fact] public void GlobalMergeListTest() { Test test = new() { ID = 5, - Numbers = new [] { 1 } + Numbers = new[] { 1 } }; Test test2 = new() { - Numbers = new [] { 3 } + Numbers = new[] { 3 } }; Test ret = Merger.Merge(test, test2); Assert.True(ReferenceEquals(test, ret)); @@ -109,18 +109,18 @@ namespace Kyoo.Tests.Utility Assert.Equal(1, ret.Numbers[0]); Assert.Equal(3, ret.Numbers[1]); } - + [Fact] public void GlobalMergeListDuplicatesTest() { Test test = new() { ID = 5, - Numbers = new [] { 1 } + Numbers = new[] { 1 } }; Test test2 = new() { - Numbers = new [] + Numbers = new[] { 1, 3, @@ -137,14 +137,14 @@ namespace Kyoo.Tests.Utility Assert.Equal(3, ret.Numbers[2]); Assert.Equal(3, ret.Numbers[3]); } - + private class MergeDictionaryTest { public int ID { get; set; } public Dictionary Dictionary { get; set; } } - + [Fact] public void GlobalMergeDictionariesTest() { @@ -171,7 +171,7 @@ namespace Kyoo.Tests.Utility Assert.Equal("two", ret.Dictionary[2]); Assert.Equal("third", ret.Dictionary[3]); } - + [Fact] public void GlobalMergeDictionariesDuplicatesTest() { @@ -199,21 +199,21 @@ namespace Kyoo.Tests.Utility Assert.Equal("two", ret.Dictionary[2]); Assert.Equal("third", ret.Dictionary[3]); } - + [Fact] public void GlobalMergeListDuplicatesResourcesTest() { Show test = new() { ID = 5, - Genres = new [] { new Genre("test") } + Genres = new[] { new Genre("test") } }; Show test2 = new() { - Genres = new [] + Genres = new[] { new Genre("test"), - new Genre("test2") + new Genre("test2") } }; Show ret = Merger.Merge(test, test2); @@ -234,13 +234,13 @@ namespace Kyoo.Tests.Utility 3 }; int[] ret = Merger.MergeLists(first, second); - + Assert.Equal(3, ret.Length); Assert.Equal(1, ret[0]); Assert.Equal(3, ret[1]); Assert.Equal(3, ret[2]); } - + [Fact] public void MergeListDuplicateTest() { @@ -251,14 +251,14 @@ namespace Kyoo.Tests.Utility 3 }; int[] ret = Merger.MergeLists(first, second); - + Assert.Equal(4, ret.Length); Assert.Equal(1, ret[0]); Assert.Equal(1, ret[1]); Assert.Equal(3, ret[2]); Assert.Equal(3, ret[3]); } - + [Fact] public void MergeListDuplicateCustomEqualityTest() { @@ -268,12 +268,12 @@ namespace Kyoo.Tests.Utility 2 }; int[] ret = Merger.MergeLists(first, second, (x, y) => x % 2 == y % 2); - + Assert.Equal(2, ret.Length); Assert.Equal(1, ret[0]); Assert.Equal(2, ret[1]); } - + [Fact] public void MergeDictionariesTest() { @@ -287,13 +287,13 @@ namespace Kyoo.Tests.Utility [3] = "third", }; IDictionary ret = Merger.MergeDictionaries(first, second); - + Assert.Equal(3, ret.Count); Assert.Equal("test", ret[1]); Assert.Equal("value", ret[5]); Assert.Equal("third", ret[3]); } - + [Fact] public void MergeDictionariesDuplicateTest() { @@ -308,13 +308,13 @@ namespace Kyoo.Tests.Utility [5] = "new-value", }; IDictionary ret = Merger.MergeDictionaries(first, second); - + Assert.Equal(3, ret.Count); Assert.Equal("test", ret[1]); Assert.Equal("value", ret[5]); Assert.Equal("third", ret[3]); } - + [Fact] public void CompleteTest() { @@ -333,7 +333,7 @@ namespace Kyoo.Tests.Utility Assert.Equal("test", genre.Name); Assert.Null(genre.Slug); } - + [Fact] public void CompleteDictionaryTest() { @@ -346,7 +346,7 @@ namespace Kyoo.Tests.Utility [Images.Logo] = "logo", [Images.Poster] = "poster" } - + }; Collection collection2 = new() { @@ -388,7 +388,7 @@ namespace Kyoo.Tests.Utility Assert.Equal("thumbnails", ret[Images.Thumbnail]); Assert.Equal("logo", ret[Images.Logo]); } - + [Fact] public void CompleteDictionaryEqualTest() { @@ -409,8 +409,9 @@ namespace Kyoo.Tests.Utility private class TestMergeSetter { public Dictionary Backing; - - [UsedImplicitly] public Dictionary Dictionary + + [UsedImplicitly] + public Dictionary Dictionary { get => Backing; set @@ -438,7 +439,7 @@ namespace Kyoo.Tests.Utility Merger.Complete(first, second); // This should no call the setter of first so the test should pass. } - + [Fact] public void MergeDictionaryNoChangeNoSetTest() { @@ -456,7 +457,7 @@ namespace Kyoo.Tests.Utility Merger.Merge(first, second); // This should no call the setter of first so the test should pass. } - + [Fact] public void MergeDictionaryNullValue() { @@ -477,7 +478,7 @@ namespace Kyoo.Tests.Utility Assert.Equal("thumbnails", ret[Images.Thumbnail]); Assert.Equal("logo", ret[Images.Logo]); } - + [Fact] public void MergeDictionaryNullValueNoChange() { @@ -496,7 +497,7 @@ namespace Kyoo.Tests.Utility Assert.Null(ret[Images.Poster]); Assert.Equal("logo", ret[Images.Logo]); } - + [Fact] public void CompleteDictionaryNullValue() { @@ -517,7 +518,7 @@ namespace Kyoo.Tests.Utility Assert.Equal("thumbnails", ret[Images.Thumbnail]); Assert.Equal("logo", ret[Images.Logo]); } - + [Fact] public void CompleteDictionaryNullValueNoChange() { diff --git a/tests/Kyoo.Tests/Utility/TaskTests.cs b/tests/Kyoo.Tests/Utility/TaskTests.cs index 47cbbf69..4596d102 100644 --- a/tests/Kyoo.Tests/Utility/TaskTests.cs +++ b/tests/Kyoo.Tests/Utility/TaskTests.cs @@ -21,7 +21,7 @@ namespace Kyoo.Tests.Utility await Assert.ThrowsAsync(() => Task.FromResult(1) .Then(_ => throw new ArgumentException())); Assert.Equal(1, await Task.FromResult(1) - .Then(_ => {})); + .Then(_ => { })); static async Task Faulted() { @@ -39,9 +39,9 @@ namespace Kyoo.Tests.Utility CancellationTokenSource token = new(); token.Cancel(); await Assert.ThrowsAsync(() => Task.Run(Infinite, token.Token) - .Then(_ => {})); + .Then(_ => { })); } - + [Fact] public async Task MapTest() { diff --git a/tests/Kyoo.Tests/Utility/UtilityTests.cs b/tests/Kyoo.Tests/Utility/UtilityTests.cs index 262337d0..f8f7553c 100644 --- a/tests/Kyoo.Tests/Utility/UtilityTests.cs +++ b/tests/Kyoo.Tests/Utility/UtilityTests.cs @@ -24,7 +24,7 @@ namespace Kyoo.Tests.Utility Expression> call = x => x.GetID("test"); Assert.False(KUtility.IsPropertyExpression(call)); } - + [Fact] public void GetPropertyName_Test() { @@ -35,45 +35,45 @@ namespace Kyoo.Tests.Utility Assert.Equal("ID", KUtility.GetPropertyName(memberCast)); Assert.Throws(() => KUtility.GetPropertyName(null)); } - + [Fact] public void GetMethodTest() { MethodInfo method = KUtility.GetMethod(typeof(UtilityTests), - BindingFlags.Instance | BindingFlags.Public, + BindingFlags.Instance | BindingFlags.Public, nameof(GetMethodTest), Array.Empty(), Array.Empty()); Assert.Equal(MethodBase.GetCurrentMethod(), method); } - + [Fact] public void GetMethodInvalidGenericsTest() { Assert.Throws(() => KUtility.GetMethod(typeof(UtilityTests), - BindingFlags.Instance | BindingFlags.Public, + BindingFlags.Instance | BindingFlags.Public, nameof(GetMethodTest), - new [] { typeof(KUtility) }, + new[] { typeof(KUtility) }, Array.Empty())); } - + [Fact] public void GetMethodInvalidParamsTest() { Assert.Throws(() => KUtility.GetMethod(typeof(UtilityTests), - BindingFlags.Instance | BindingFlags.Public, + BindingFlags.Instance | BindingFlags.Public, nameof(GetMethodTest), Array.Empty(), new object[] { this })); } - + [Fact] public void GetMethodTest2() { MethodInfo method = KUtility.GetMethod(typeof(Merger), - BindingFlags.Static | BindingFlags.Public, + BindingFlags.Static | BindingFlags.Public, nameof(Merger.MergeLists), - new [] { typeof(string) }, + new[] { typeof(string) }, new object[] { "string", "string2", null }); Assert.Equal(nameof(Merger.MergeLists), method.Name); }