using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Runtime.InteropServices; using System.Threading.Tasks; using JetBrains.Annotations; using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Utils; namespace Kyoo.Abstractions.Controllers { /// /// Information about the pagination. How many items should be displayed and where to start. /// public readonly struct Pagination { /// /// The count of items to return. /// public int Count { get; } /// /// Where to start? Using the given sort. /// public int AfterID { get; } /// /// Create a new instance. /// /// Set the value /// Set the value. If not specified, it will start from the start public Pagination(int count, int afterID = 0) { Count = count; AfterID = afterID; } /// /// Implicitly create a new pagination from a limit number. /// /// Set the value /// A new instance public static implicit operator Pagination(int limit) => new(limit); } /// /// Information about how a query should be sorted. What factor should decide the sort and in which order. /// /// For witch type this sort applies public readonly struct Sort { /// /// The sort key. This member will be used to sort the results. /// public Expression> Key { get; } /// /// 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. /// /// The sort key given. It is assigned to . /// Should this be in descendant order? The default is false. /// If the given key is not a member. public Sort(Expression> key, bool descendant = false) { Key = key; Descendant = descendant; if (!Utility.IsPropertyExpression(Key)) throw new ArgumentException("The given sort key is not valid."); } /// /// Create a new instance from a key's name (case insensitive). /// /// A key name with an optional order specifier. Format: "key:asc", "key:desc" or "key". /// An invalid key or sort specifier as been given. public Sort(string sortBy) { if (string.IsNullOrEmpty(sortBy)) { Key = null; Descendant = false; return; } string key = sortBy.Contains(':') ? sortBy[..sortBy.IndexOf(':')] : sortBy; string order = sortBy.Contains(':') ? sortBy[(sortBy.IndexOf(':') + 1)..] : null; ParameterExpression param = Expression.Parameter(typeof(T), "x"); MemberExpression property = Expression.Property(param, key); Key = property.Type.IsValueType ? Expression.Lambda>(Expression.Convert(property, typeof(object)), param) : Expression.Lambda>(property, param); Descendant = order switch { "desc" => true, "asc" => false, null => false, _ => throw new ArgumentException($"The sort order, if set, should be :asc or :desc but it was :{order}.") }; } } /// /// A base class for repositories. Every service implementing this will be handled by the . /// public interface IBaseRepository { /// /// The type for witch this repository is responsible or null if non applicable. /// Type RepositoryType { get; } } /// /// A common repository for every resources. /// /// The resource's type that this repository manage. public interface IRepository : IBaseRepository where T : class, IResource { /// /// Get a resource from it's ID. /// /// The id of the resource /// If the item could not be found. /// The resource found [ItemNotNull] Task Get(int id); /// /// Get a resource from it's slug. /// /// The slug of the resource /// If the item could not be found. /// The resource found [ItemNotNull] Task Get(string slug); /// /// Get the first resource that match the predicate. /// /// A predicate to filter the resource. /// If the item could not be found. /// The resource found [ItemNotNull] Task Get(Expression> where); /// /// Get a resource from it's ID or null if it is not found. /// /// The id of the resource /// The resource found [ItemCanBeNull] Task GetOrDefault(int id); /// /// Get a resource from it's slug or null if it is not found. /// /// The slug of the resource /// The resource found [ItemCanBeNull] Task GetOrDefault(string slug); /// /// Get the first resource that match the predicate or null if it is not found. /// /// A predicate to filter the resource. /// The resource found [ItemCanBeNull] Task GetOrDefault(Expression> where); /// /// Search for resources. /// /// The query string. /// A list of resources found [ItemNotNull] Task> Search(string query); /// /// Get every resources that match all filters /// /// A filter predicate /// Sort information about the query (sort by, sort order) /// 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, Sort sort = default, Pagination limit = default); /// /// Get every resources that match all filters /// /// A filter predicate /// A sort by predicate. The order is ascending. /// How pagination should be done (where to start and how many to return) /// A list of resources that match every filters [ItemNotNull] Task> GetAll([Optional] Expression> where, Expression> sort, Pagination limit = default ) => GetAll(where, new Sort(sort), limit); /// /// Get the number of resources that match the filter's predicate. /// /// A filter predicate /// How many resources matched that filter Task GetCount(Expression> where = null); /// /// Create a new resource. /// /// The item to register /// 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. /// /// The object to create /// The newly created item or the existing value if it existed. [ItemNotNull] Task CreateIfNotExists([NotNull] T obj); /// /// Edit a resource /// /// The resource to edit, it's ID can't change. /// Should old properties of the resource be discarded or should null values considered as not changed? /// If the item is not found /// The resource edited and completed by database's information (related items & so on) [ItemNotNull] Task Edit([NotNull] T edited, bool resetOld); /// /// Delete a resource by it's ID /// /// The ID of the resource /// If the item is not found Task Delete(int id); /// /// Delete a resource by it's slug /// /// The slug of the resource /// If the item is not found Task Delete(string slug); /// /// Delete a resource /// /// The resource to delete /// If the item is not found Task Delete([NotNull] T obj); /// /// Delete all resources that match the predicate. /// /// A predicate to filter resources to delete. Every resource that match this will be deleted. /// If the item is not found Task DeleteAll([NotNull] Expression> where); } /// /// A repository to handle shows. /// public interface IShowRepository : IRepository { /// /// Link a show to a collection and/or a library. The given show is now part of those containers. /// If both a library and a collection are given, the collection is added to the library too. /// /// The ID of the show /// The ID of the library (optional) /// The ID of the collection (optional) Task AddShowLink(int showID, int? libraryID, int? collectionID); /// /// Get a show's slug from it's ID. /// /// The ID of the show /// If a show with the given ID is not found. /// The show's slug Task GetSlug(int showID); } /// /// A repository to handle seasons. /// public interface ISeasonRepository : IRepository { /// /// Get a season from it's showID and it's seasonNumber /// /// The id of the show /// The season's number /// 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 /// /// The slug of the show /// The season's number /// 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. /// /// The id of the show /// 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. /// /// The slug of the show /// The season's number /// The season found Task GetOrDefault(string showSlug, int seasonNumber); } /// /// The repository to handle episodes /// public interface IEpisodeRepository : IRepository { /// /// Get a episode from it's showID, it's seasonNumber and it's episode number. /// /// The id of the show /// The season's number /// The episode's number /// If the item is not found /// The episode found Task Get(int showID, int seasonNumber, int episodeNumber); /// /// Get a episode from it's show slug, it's seasonNumber and it's episode number. /// /// The slug of the show /// The season's number /// The episode's number /// If the item is not found /// The episode found Task Get(string showSlug, int seasonNumber, int episodeNumber); /// /// Get a episode from it's showID, it's seasonNumber and it's episode number or null if it is not found. /// /// The id of the show /// The season's number /// The episode's number /// The episode found 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. /// /// The slug of the show /// The season's number /// 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. /// /// The id of the show /// The episode's absolute number (The episode number does not reset to 1 after the end of a season. /// If the item is not found /// The episode found Task GetAbsolute(int showID, int absoluteNumber); /// /// Get a episode from it's showID and it's absolute number. /// /// The slug of the show /// The episode's absolute number (The episode number does not reset to 1 after the end of a season. /// If the item is not found /// The episode found Task GetAbsolute(string showSlug, int absoluteNumber); } /// /// A repository to handle tracks /// public interface ITrackRepository : IRepository { } /// /// A repository to handle libraries. /// public interface ILibraryRepository : IRepository { } /// /// A repository to handle library items (A wrapper around shows and collections). /// public interface ILibraryItemRepository : IRepository { /// /// Get items (A wrapper around shows or collections) from a library. /// /// The ID of the library /// A filter function /// Sort information (sort order & sort by) /// How many items to return and where to start /// A list of items that match every filters public Task> GetFromLibrary(int id, Expression> where = null, Sort sort = default, Pagination limit = default); /// /// Get items (A wrapper around shows or collections) from a library. /// /// The ID of the library /// A filter function /// A sort by method /// How many items to return and where to start /// A list of items that match every filters public Task> GetFromLibrary(int id, [Optional] Expression> where, Expression> sort, Pagination limit = default ) => GetFromLibrary(id, where, new Sort(sort), limit); /// /// Get items (A wrapper around shows or collections) from a library. /// /// The slug of the library /// A filter function /// Sort information (sort order & sort by) /// How many items to return and where to start /// A list of items that match every filters public Task> GetFromLibrary(string slug, Expression> where = null, Sort sort = default, Pagination limit = default); /// /// Get items (A wrapper around shows or collections) from a library. /// /// The slug of the library /// A filter function /// A sort by method /// How many items to return and where to start /// A list of items that match every filters public Task> GetFromLibrary(string slug, [Optional] Expression> where, 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. /// public interface IStudioRepository : IRepository { } /// /// A repository for people. /// public interface IPeopleRepository : IRepository { /// /// Get people's roles from a show. /// /// The ID of the show /// A filter function /// Sort information (sort order & sort by) /// How many items to return and where to start /// A list of items that match every filters Task> GetFromShow(int showID, Expression> where = null, Sort sort = default, Pagination limit = default); /// /// Get people's roles from a show. /// /// The ID of the show /// A filter function /// A sort by method /// How many items to return and where to start /// A list of items that match every filters Task> GetFromShow(int showID, [Optional] Expression> where, Expression> sort, Pagination limit = default ) => GetFromShow(showID, where, new Sort(sort), limit); /// /// Get people's roles from a show. /// /// The slug of the show /// A filter function /// Sort information (sort order & sort by) /// How many items to return and where to start /// A list of items that match every filters Task> GetFromShow(string showSlug, Expression> where = null, Sort sort = default, Pagination limit = default); /// /// Get people's roles from a show. /// /// The slug of the show /// A filter function /// A sort by method /// How many items to return and where to start /// A list of items that match every filters Task> GetFromShow(string showSlug, [Optional] Expression> where, Expression> sort, Pagination limit = default ) => GetFromShow(showSlug, where, new Sort(sort), limit); /// /// Get people's roles from a person. /// /// The id of the person /// A filter function /// Sort information (sort order & sort by) /// How many items to return and where to start /// A list of items that match every filters Task> GetFromPeople(int id, Expression> where = null, Sort sort = default, Pagination limit = default); /// /// Get people's roles from a person. /// /// The id of the person /// A filter function /// A sort by method /// How many items to return and where to start /// A list of items that match every filters Task> GetFromPeople(int id, [Optional] Expression> where, Expression> sort, Pagination limit = default ) => GetFromPeople(id, where, new Sort(sort), limit); /// /// Get people's roles from a person. /// /// The slug of the person /// A filter function /// Sort information (sort order & sort by) /// How many items to return and where to start /// A list of items that match every filters Task> GetFromPeople(string slug, Expression> where = null, Sort sort = default, Pagination limit = default); /// /// Get people's roles from a person. /// /// The slug of the person /// A filter function /// A sort by method /// How many items to return and where to start /// A list of items that match every filters Task> GetFromPeople(string slug, [Optional] Expression> where, Expression> sort, Pagination limit = default ) => GetFromPeople(slug, where, new Sort(sort), limit); } /// /// A repository to handle providers. /// public interface IProviderRepository : IRepository { /// /// Get a list of external ids that match all filters /// /// A predicate to add arbitrary filter /// Sort information (sort order & sort by) /// 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, Sort sort = default, Pagination limit = default) where T : class, IMetadata; /// /// Get a list of external ids that match all filters /// /// A predicate to add arbitrary filter /// A sort by expression /// Pagination information (where to start and how many to get) /// A filtered list of external ids. Task> GetMetadataID([Optional] Expression> where, Expression> sort, Pagination limit = default ) where T : class, IMetadata => GetMetadataID(where, new Sort(sort), limit); } /// /// A repository to handle users. /// public interface IUserRepository : IRepository { } }