From 17103548c5f71acffa72f0297719379d99340104 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 25 Jul 2021 23:55:55 +0200 Subject: [PATCH 01/34] Implenting a the movie db provider --- Kyoo.Common/Kyoo.Common.csproj | 2 +- Kyoo.Tests/Kyoo.Tests.csproj | 4 +- Kyoo.TheMovieDb/Convertors.cs | 264 +++++++++++++++++++++++++ Kyoo.TheMovieDb/Kyoo.TheMovieDb.csproj | 34 ++++ Kyoo.TheMovieDb/PluginTmdb.cs | 79 ++++++++ Kyoo.TheMovieDb/ProviderTmdb.cs | 154 +++++++++++++++ Kyoo.TheMovieDb/TheMovieDbOptions.cs | 18 ++ Kyoo.sln | 6 + Kyoo/settings.json | 3 + 9 files changed, 561 insertions(+), 3 deletions(-) create mode 100644 Kyoo.TheMovieDb/Convertors.cs create mode 100644 Kyoo.TheMovieDb/Kyoo.TheMovieDb.csproj create mode 100644 Kyoo.TheMovieDb/PluginTmdb.cs create mode 100644 Kyoo.TheMovieDb/ProviderTmdb.cs create mode 100644 Kyoo.TheMovieDb/TheMovieDbOptions.cs diff --git a/Kyoo.Common/Kyoo.Common.csproj b/Kyoo.Common/Kyoo.Common.csproj index 844be997..fcf33415 100644 --- a/Kyoo.Common/Kyoo.Common.csproj +++ b/Kyoo.Common/Kyoo.Common.csproj @@ -22,7 +22,7 @@ - + diff --git a/Kyoo.Tests/Kyoo.Tests.csproj b/Kyoo.Tests/Kyoo.Tests.csproj index 120f8ba7..eeaf81f9 100644 --- a/Kyoo.Tests/Kyoo.Tests.csproj +++ b/Kyoo.Tests/Kyoo.Tests.csproj @@ -10,7 +10,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -24,7 +24,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Kyoo.TheMovieDb/Convertors.cs b/Kyoo.TheMovieDb/Convertors.cs new file mode 100644 index 00000000..02497549 --- /dev/null +++ b/Kyoo.TheMovieDb/Convertors.cs @@ -0,0 +1,264 @@ +using System.Linq; +using Kyoo.Models; +using TMDbLib.Objects.General; +using TMDbLib.Objects.Movies; +using TMDbLib.Objects.Search; +using TMDbLib.Objects.TvShows; +using Genre = Kyoo.Models.Genre; +using TvCast = TMDbLib.Objects.TvShows.Cast; +using MovieCast = TMDbLib.Objects.Movies.Cast; + +namespace Kyoo.TheMovieDb +{ + public static class Convertors + { + /// + /// Convert a into a . + /// + /// The movie to convert. + /// The provider representing TheMovieDb. + /// The converted movie as a . + public static Show ToShow(this Movie movie, Provider provider) + { + return new() + { + Slug = Utility.ToSlug(movie.Title), + Title = movie.Title, + Aliases = movie.AlternativeTitles.Titles.Select(x => x.Title).ToArray(), + Overview = movie.Overview, + TrailerUrl = movie.Videos?.Results.Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube") + .Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault(), + Status = movie.Status == "Released" ? Status.Finished : Status.Planned, + StartAir = movie.ReleaseDate, + EndAir = movie.ReleaseDate, + Poster = movie.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" + : null, + Backdrop = movie.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}" + : null, + Genres = movie.Genres.Select(x => new Genre(x.Name)).ToArray(), + Studio = !string.IsNullOrEmpty(movie.ProductionCompanies.FirstOrDefault()?.Name) + ? new Studio(movie.ProductionCompanies.First().Name) + : null, + IsMovie = true, + People = movie.Credits.Cast + .Select(x => x.ToPeople(provider)) + .Concat(movie.Credits.Crew.Select(x => x.ToPeople(provider))) + .ToArray(), + ExternalIDs = new [] + { + new MetadataID + { + Second = provider, + Link = $"https://www.themoviedb.org/movie/{movie.Id}", + DataID = movie.Id.ToString() + } + } + }; + } + + /// + /// Convert a to a . + /// + /// The show to convert. + /// The provider representing TheMovieDb. + /// A converted as a . + public static Show ToShow(this TvShow tv, Provider provider) + { + return new() + { + Slug = Utility.ToSlug(tv.Name), + Title = tv.Name, + Aliases = tv.AlternativeTitles.Results.Select(x => x.Title).ToArray(), + Overview = tv.Overview, + TrailerUrl = tv.Videos?.Results.Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube") + .Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault(), + Status = tv.Status == "Ended" ? Status.Finished : Status.Planned, + StartAir = tv.FirstAirDate, + EndAir = tv.LastAirDate, + Poster = tv.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" + : null, + Backdrop = tv.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" + : null, + Genres = tv.Genres.Select(x => new Genre(x.Name)).ToArray(), + Studio = !string.IsNullOrEmpty(tv.ProductionCompanies.FirstOrDefault()?.Name) + ? new Studio(tv.ProductionCompanies.First().Name) + : null, + IsMovie = true, + People = tv.Credits.Cast + .Select(x => x.ToPeople(provider)) + .Concat(tv.Credits.Crew.Select(x => x.ToPeople(provider))) + .ToArray(), + ExternalIDs = new [] + { + new MetadataID + { + Second = provider, + Link = $"https://www.themoviedb.org/movie/{tv.Id}", + DataID = tv.Id.ToString() + } + } + }; + } + + /// + /// Convert a into a . + /// + /// The movie to convert. + /// The provider representing TheMovieDb. + /// The converted movie as a . + public static Show ToShow(this SearchMovie movie, Provider provider) + { + return new() + { + Slug = Utility.ToSlug(movie.Title), + Title = movie.Title, + Overview = movie.Overview, + StartAir = movie.ReleaseDate, + EndAir = movie.ReleaseDate, + Poster = movie.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" + : null, + Backdrop = movie.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}" + : null, + IsMovie = true, + ExternalIDs = new [] + { + new MetadataID + { + Second = provider, + Link = $"https://www.themoviedb.org/movie/{movie.Id}", + DataID = movie.Id.ToString() + } + } + }; + } + + /// + /// Convert a to a . + /// + /// The show to convert. + /// The provider representing TheMovieDb. + /// A converted as a . + public static Show ToShow(this SearchTv tv, Provider provider) + { + return new() + { + Slug = Utility.ToSlug(tv.Name), + Title = tv.Name, + Overview = tv.Overview, + StartAir = tv.FirstAirDate, + Poster = tv.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" + : null, + Backdrop = tv.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" + : null, + IsMovie = true, + ExternalIDs = new [] + { + new MetadataID + { + Second = provider, + Link = $"https://www.themoviedb.org/movie/{tv.Id}", + DataID = tv.Id.ToString() + } + } + }; + } + + /// + /// Convert a to a . + /// + /// An internal TheMovieDB cast. + /// The provider that represent TheMovieDB inside Kyoo. + /// A representing the movie cast. + public static PeopleRole ToPeople(this MovieCast cast, Provider provider) + { + return new() + { + People = new People + { + Slug = Utility.ToSlug(cast.Name), + Name = cast.Name, + Poster = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" : null, + ExternalIDs = new[] + { + new MetadataID + { + Second = provider, + DataID = cast.Id.ToString(), + Link = $"https://www.themoviedb.org/person/{cast.Id}" + } + } + }, + Type = "Actor", + Role = cast.Character + }; + } + + /// + /// Convert a to a . + /// + /// An internal TheMovieDB cast. + /// The provider that represent TheMovieDB inside Kyoo. + /// A representing the movie cast. + public static PeopleRole ToPeople(this TvCast cast, Provider provider) + { + return new() + { + People = new People + { + Slug = Utility.ToSlug(cast.Name), + Name = cast.Name, + Poster = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" : null, + ExternalIDs = new[] + { + new MetadataID + { + Second = provider, + DataID = cast.Id.ToString(), + Link = $"https://www.themoviedb.org/person/{cast.Id}" + } + } + }, + Type = "Actor", + Role = cast.Character + }; + } + + /// + /// Convert a to a . + /// + /// An internal TheMovieDB crew member. + /// The provider that represent TheMovieDB inside Kyoo. + /// A representing the movie crew. + public static PeopleRole ToPeople(this Crew crew, Provider provider) + { + return new() + { + People = new People + { + Slug = Utility.ToSlug(crew.Name), + Name = crew.Name, + Poster = crew.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{crew.ProfilePath}" : null, + ExternalIDs = new[] + { + new MetadataID + { + Second = provider, + DataID = crew.Id.ToString(), + Link = $"https://www.themoviedb.org/person/{crew.Id}" + } + } + }, + Type = crew.Department, + Role = crew.Job + }; + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/Kyoo.TheMovieDb.csproj b/Kyoo.TheMovieDb/Kyoo.TheMovieDb.csproj new file mode 100644 index 00000000..3f50e6b3 --- /dev/null +++ b/Kyoo.TheMovieDb/Kyoo.TheMovieDb.csproj @@ -0,0 +1,34 @@ + + + net5.0 + + SDG + Zoe Roux + https://github.com/AnonymusRaccoon/Kyoo + default + Kyoo.TheMovieDb + + + + ../Kyoo/bin/$(Configuration)/$(TargetFramework)/plugins/the-moviedb + false + false + false + false + true + + + + + + + + + + + all + false + runtime + + + diff --git a/Kyoo.TheMovieDb/PluginTmdb.cs b/Kyoo.TheMovieDb/PluginTmdb.cs new file mode 100644 index 00000000..aaf5aa9c --- /dev/null +++ b/Kyoo.TheMovieDb/PluginTmdb.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using Autofac; +using Kyoo.Controllers; +using Kyoo.Models.Attributes; +using Kyoo.TheMovieDb.Models; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Kyoo.TheMovieDb +{ + /// + /// A plugin that add a for TheMovieDB. + /// + public class PluginTmdb : IPlugin + { + /// + public string Slug => "the-moviedb"; + + /// + public string Name => "TheMovieDb Provider"; + + /// + public string Description => "A metadata provider for TheMovieDB."; + + /// + public ICollection Provides => new [] + { + typeof(IMetadataProvider) + }; + + /// + public ICollection ConditionalProvides => ArraySegment.Empty; + + /// + public ICollection Requires => ArraySegment.Empty; + + + /// + /// The configuration to use. + /// + private readonly IConfiguration _configuration; + + /// + /// The configuration manager used to register typed/untyped implementations. + /// + [Injected] public IConfigurationManager ConfigurationManager { private get; set; } + + + /// + /// Create a new tmdb module instance and use the given configuration. + /// + /// The configuration to use + public PluginTmdb(IConfiguration configuration) + { + _configuration = configuration; + } + + + /// + public void Configure(ContainerBuilder builder) + { + builder.RegisterProvider(); + } + + /// + public void Configure(IServiceCollection services, ICollection availableTypes) + { + services.Configure(_configuration.GetSection(TheMovieDbOptions.Path)); + } + + /// + public void ConfigureAspNet(IApplicationBuilder app) + { + ConfigurationManager.AddTyped(TheMovieDbOptions.Path); + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/ProviderTmdb.cs b/Kyoo.TheMovieDb/ProviderTmdb.cs new file mode 100644 index 00000000..aedfeea6 --- /dev/null +++ b/Kyoo.TheMovieDb/ProviderTmdb.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Kyoo.Controllers; +using Kyoo.Models; +using Kyoo.TheMovieDb.Models; +using Microsoft.Extensions.Options; +using TMDbLib.Client; +using TMDbLib.Objects.Movies; +using TMDbLib.Objects.Search; +using TMDbLib.Objects.TvShows; + +namespace Kyoo.TheMovieDb +{ + /// + /// A metadata provider for TheMovieDb. + /// + public class TheMovieDbProvider : IMetadataProvider + { + /// + /// The API key used to authenticate with TheMovieDb API. + /// + private readonly IOptions _apiKey; + + /// + public Provider Provider => new() + { + Slug = "the-moviedb", + Name = "TheMovieDB", + LogoExtension = "svg", + Logo = "https://www.themoviedb.org/assets/2/v4/logos/v2/blue_short-8e7b30f73a4020692ccca9c88bafe5dcb6f8a62a4c6bc55cd9ba82bb2cd95f6c.svg" + }; + + /// + /// Create a new using the given api key. + /// + /// The api key + public TheMovieDbProvider(IOptions apiKey) + { + _apiKey = apiKey; + } + + + /// + public Task Get(T item) + where T : class, IResource + { + return item switch + { + Show show => _GetShow(show) as Task, + _ => null + }; + } + + /// + /// Get a show using it's id, if the id is not present in the show, fallback to a title search. + /// + /// The show to search for + /// A show containing metadata from TheMovieDb + private async Task _GetShow(Show show) + { + if (!int.TryParse(show.GetID(Provider.Name), out int id)) + return (await _SearchShows(show.Title ?? show.Slug)).FirstOrDefault(); + 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); + } + + + /// + public async Task> Search(string query) + where T : class, IResource + { + if (typeof(T) == typeof(Show)) + return (await _SearchShows(query) as ICollection)!; + return ArraySegment.Empty; + } + + /// + /// Search for a show using it's name as a query. + /// + /// The query to search for + /// A show containing metadata from TheMovieDb + private async Task> _SearchShows(string query) + { + TMDbClient client = new(_apiKey.Value.ApiKey); + return (await client.SearchMultiAsync(query)) + .Results + .Select(x => + { + return x switch + { + SearchTv tv => tv.ToShow(Provider), + SearchMovie movie => movie.ToShow(Provider), + _ => null + }; + }) + .Where(x => x != null) + .ToArray(); + } + + // public async Task GetSeason(Show show, int seasonNumber) + // { + // string id = show?.GetID(Provider.Name); + // if (id == null) + // return await Task.FromResult(null); + // TMDbClient client = new TMDbClient(APIKey); + // TvSeason season = await client.GetTvSeasonAsync(int.Parse(id), seasonNumber); + // if (season == null) + // return null; + // return new Season(show.ID, + // seasonNumber, + // season.Name, + // season.Overview, + // season.AirDate?.Year, + // season.PosterPath != null ? "https://image.tmdb.org/t/p/original" + season.PosterPath : null, + // new[] {new MetadataID(Provider, $"{season.Id}", $"https://www.themoviedb.org/tv/{id}/season/{season.SeasonNumber}")}); + // } + // + // public async Task GetEpisode(Show show, int seasonNumber, int episodeNumber, int absoluteNumber) + // { + // if (seasonNumber == -1 || episodeNumber == -1) + // return await Task.FromResult(null); + // + // string id = show?.GetID(Provider.Name); + // if (id == null) + // return await Task.FromResult(null); + // TMDbClient client = new(APIKey); + // TvEpisode episode = await client.GetTvEpisodeAsync(int.Parse(id), seasonNumber, episodeNumber); + // if (episode == null) + // return null; + // return new Episode(seasonNumber, episodeNumber, absoluteNumber, + // episode.Name, + // episode.Overview, + // episode.AirDate, + // 0, + // episode.StillPath != null ? "https://image.tmdb.org/t/p/original" + episode.StillPath : null, + // new [] + // { + // new MetadataID(Provider, $"{episode.Id}", $"https://www.themoviedb.org/tv/{id}/season/{episode.SeasonNumber}/episode/{episode.EpisodeNumber}") + // }); + // } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/TheMovieDbOptions.cs b/Kyoo.TheMovieDb/TheMovieDbOptions.cs new file mode 100644 index 00000000..2387f553 --- /dev/null +++ b/Kyoo.TheMovieDb/TheMovieDbOptions.cs @@ -0,0 +1,18 @@ +namespace Kyoo.TheMovieDb.Models +{ + /// + /// The option containing the api key for TheMovieDb. + /// + public class TheMovieDbOptions + { + /// + /// The path to get this option from the root configuration. + /// + public const string Path = "the-moviedb"; + + /// + /// The api key of TheMovieDb. + /// + public string ApiKey { get; set; } + } +} \ No newline at end of file diff --git a/Kyoo.sln b/Kyoo.sln index 55aeb44c..fbec126b 100644 --- a/Kyoo.sln +++ b/Kyoo.sln @@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.SqLite", "Kyoo.SqLite\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.TheTvdb", "Kyoo.TheTvdb\Kyoo.TheTvdb.csproj", "{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.TheMovieDb", "Kyoo.TheMovieDb\Kyoo.TheMovieDb.csproj", "{BAB270D4-E0EA-4329-BA65-512FDAB01001}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -53,5 +55,9 @@ Global {D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Debug|Any CPU.Build.0 = Debug|Any CPU {D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Release|Any CPU.ActiveCfg = Release|Any CPU {D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Release|Any CPU.Build.0 = Release|Any CPU + {BAB270D4-E0EA-4329-BA65-512FDAB01001}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BAB270D4-E0EA-4329-BA65-512FDAB01001}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BAB270D4-E0EA-4329-BA65-512FDAB01001}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BAB270D4-E0EA-4329-BA65-512FDAB01001}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Kyoo/settings.json b/Kyoo/settings.json index 66ff8566..8529a35a 100644 --- a/Kyoo/settings.json +++ b/Kyoo/settings.json @@ -70,5 +70,8 @@ "tvdb": { "apiKey": "REDACTED" + }, + "the-moviedb": { + "apiKey": "REDACTED" } } From 3262343c4af97463712a38958c41abdccb2681cc Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 26 Jul 2021 00:06:19 +0200 Subject: [PATCH 02/34] Implementing collection search --- Kyoo.TheMovieDb/Convertors.cs | 17 +++++++++++++++++ Kyoo.TheMovieDb/ProviderTmdb.cs | 16 ++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/Kyoo.TheMovieDb/Convertors.cs b/Kyoo.TheMovieDb/Convertors.cs index 02497549..cb5b4412 100644 --- a/Kyoo.TheMovieDb/Convertors.cs +++ b/Kyoo.TheMovieDb/Convertors.cs @@ -103,6 +103,23 @@ namespace Kyoo.TheMovieDb } }; } + + + /// + /// Convert a into a . + /// + /// The collection to convert. + /// The provider representing TheMovieDb. + /// The converted collection as a . + public static Collection ToCollection(this SearchCollection collection, Provider provider) + { + return new() + { + Slug = Utility.ToSlug(collection.Name), + Name = collection.Name, + Poster = $"https://image.tmdb.org/t/p/original{collection.PosterPath}" + }; + } /// /// Convert a into a . diff --git a/Kyoo.TheMovieDb/ProviderTmdb.cs b/Kyoo.TheMovieDb/ProviderTmdb.cs index aedfeea6..b613e5f3 100644 --- a/Kyoo.TheMovieDb/ProviderTmdb.cs +++ b/Kyoo.TheMovieDb/ProviderTmdb.cs @@ -81,11 +81,27 @@ namespace Kyoo.TheMovieDb public async Task> Search(string query) where T : class, IResource { + if (typeof(T) == typeof(Collection)) + return (await _SearchCollections(query) as ICollection)!; if (typeof(T) == typeof(Show)) return (await _SearchShows(query) as ICollection)!; return ArraySegment.Empty; } + /// + /// Search for a collection using it's name as a query. + /// + /// The query to search for + /// A collection containing metadata from TheMovieDb + private async Task> _SearchCollections(string query) + { + TMDbClient client = new(_apiKey.Value.ApiKey); + return (await client.SearchCollectionAsync(query)) + .Results + .Select(x => x.ToCollection(Provider)) + .ToArray(); + } + /// /// Search for a show using it's name as a query. /// From 2812c9cacf8a2763d7fcff13fb358829b2b1afcd Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 26 Jul 2021 17:00:23 +0200 Subject: [PATCH 03/34] Reworking metadata handling and adding it to Collections and Studios --- Kyoo.Common/Controllers/IRepository.cs | 14 ++-- .../Implementations/LibraryManager.cs | 28 ++++--- Kyoo.Common/Models/Link.cs | 4 +- Kyoo.Common/Models/MetadataID.cs | 35 +++++--- Kyoo.Common/Models/Resources/Collection.cs | 5 +- Kyoo.Common/Models/Resources/Episode.cs | 8 +- .../Models/Resources/Interfaces/IMetadata.cs | 16 ++++ .../Resources/{ => Interfaces}/IResource.cs | 0 Kyoo.Common/Models/Resources/People.cs | 8 +- Kyoo.Common/Models/Resources/Season.cs | 8 +- Kyoo.Common/Models/Resources/Show.cs | 17 ++-- Kyoo.Common/Models/Resources/Studio.cs | 5 +- Kyoo.CommonAPI/DatabaseContext.cs | 81 ++++++++----------- Kyoo.Tests/Database/RepositoryActivator.cs | 4 +- .../Database/SpecificTests/ShowTests.cs | 20 +++-- Kyoo.TheMovieDb/Convertors.cs | 28 +++---- Kyoo.TheTvdb/Convertors.cs | 16 ++-- .../Repositories/CollectionRepository.cs | 35 +++++++- .../Repositories/EpisodeRepository.cs | 7 +- .../Repositories/PeopleRepository.cs | 7 +- .../Repositories/ProviderRepository.cs | 33 ++++---- .../Repositories/SeasonRepository.cs | 7 +- .../Repositories/ShowRepository.cs | 7 +- .../Repositories/StudioRepository.cs | 34 +++++++- 24 files changed, 260 insertions(+), 167 deletions(-) create mode 100644 Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs rename Kyoo.Common/Models/Resources/{ => Interfaces}/IResource.cs (100%) diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index a83d17fa..c5fe7a7c 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -590,10 +590,10 @@ namespace Kyoo.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, bool>> where = null, - Sort> sort = default, + Task> GetMetadataID(Expression> where = null, + Sort sort = default, Pagination limit = default) - where T : class, IResource; + where T : class, IMetadata; /// /// Get a list of external ids that match all filters @@ -602,11 +602,11 @@ namespace Kyoo.Controllers /// A sort by expression /// Pagination information (where to start and how many to get) /// A filtered list of external ids. - Task>> GetMetadataID([Optional] Expression, bool>> where, - Expression, object>> sort, + Task> GetMetadataID([Optional] Expression> where, + Expression> sort, Pagination limit = default - ) where T : class, IResource - => GetMetadataID(where, new Sort>(sort), limit); + ) where T : class, IMetadata + => GetMetadataID(where, new Sort(sort), limit); } /// diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 12ea0b2a..5b051ed8 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -231,7 +231,12 @@ namespace Kyoo.Controllers .Then(x => l.Collections = x), - (Collection c, nameof(Library.Shows)) => ShowRepository + (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), @@ -241,9 +246,9 @@ namespace Kyoo.Controllers (Show s, nameof(Show.ExternalIDs)) => SetRelation(s, - ProviderRepository.GetMetadataID(x => x.FirstID == obj.ID), + ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, - (x, y) => { x.First = y; x.FirstID = y.ID; }), + (x, y) => { x.ResourceID = y.ID; }), (Show s, nameof(Show.Genres)) => GenreRepository .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) @@ -281,9 +286,9 @@ namespace Kyoo.Controllers (Season s, nameof(Season.ExternalIDs)) => SetRelation(s, - ProviderRepository.GetMetadataID(x => x.FirstID == obj.ID), + ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, - (x, y) => { x.First = y; x.FirstID = y.ID; }), + (x, y) => { x.ResourceID = y.ID; }), (Season s, nameof(Season.Episodes)) => SetRelation(s, EpisodeRepository.GetAll(x => x.Season.ID == obj.ID), @@ -300,9 +305,9 @@ namespace Kyoo.Controllers (Episode e, nameof(Episode.ExternalIDs)) => SetRelation(e, - ProviderRepository.GetMetadataID(x => x.FirstID == obj.ID), + ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, - (x, y) => { x.First = y; x.FirstID = y.ID; }), + (x, y) => { x.ResourceID = y.ID; }), (Episode e, nameof(Episode.Tracks)) => SetRelation(e, TrackRepository.GetAll(x => x.Episode.ID == obj.ID), @@ -344,11 +349,16 @@ namespace Kyoo.Controllers .GetAll(x => x.Studio.ID == obj.ID) .Then(x => s.Shows = x), + (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, - ProviderRepository.GetMetadataID(x => x.FirstID == obj.ID), + ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, - (x, y) => { x.First = y; x.FirstID = y.ID; }), + (x, y) => { x.ResourceID = y.ID; }), (People p, nameof(People.Roles)) => PeopleRepository .GetFromPeople(obj.ID) diff --git a/Kyoo.Common/Models/Link.cs b/Kyoo.Common/Models/Link.cs index 09c519da..1f4cf4ac 100644 --- a/Kyoo.Common/Models/Link.cs +++ b/Kyoo.Common/Models/Link.cs @@ -92,7 +92,7 @@ namespace Kyoo.Models get { return x => new {First = x.FirstID, Second = x.SecondID}; - } + } } } @@ -157,7 +157,7 @@ namespace Kyoo.Models get { return x => new {First = x.FirstID, Second = x.SecondID}; - } + } } } } \ No newline at end of file diff --git a/Kyoo.Common/Models/MetadataID.cs b/Kyoo.Common/Models/MetadataID.cs index de6ac817..e3bb42a6 100644 --- a/Kyoo.Common/Models/MetadataID.cs +++ b/Kyoo.Common/Models/MetadataID.cs @@ -1,15 +1,34 @@ using System; using System.Linq.Expressions; +using Kyoo.Models.Attributes; namespace Kyoo.Models { /// /// ID and link of an item on an external provider. /// - /// - public class MetadataID : Link - where T : class, IResource + public class MetadataID { + /// + /// The ID of the resource which possess the metadata. + /// + [SerializeIgnore] public int ResourceID { get; set; } + + /// + /// The name of the resource type. This is only used internally to discriminate types. + /// + [SerializeIgnore] public string ResourceType { get; set; } + + /// + /// The ID of the provider. + /// + [SerializeIgnore] public int ProviderID { get; set; } + + /// + /// The provider that can do something with this ID. + /// + public Provider Provider { get; set; } + /// /// The ID of the resource on the external provider. /// @@ -20,20 +39,14 @@ namespace Kyoo.Models /// public string Link { get; set; } - /// - /// A shortcut to access the provider of this metadata. - /// Unlike the property, this is serializable. - /// - public Provider Provider => Second; - /// /// The expression to retrieve the unique ID of a MetadataID. This is an aggregate of the two resources IDs. /// - public new static Expression, object>> PrimaryKey + public static Expression> PrimaryKey { get { - return x => new {First = x.FirstID, Second = x.SecondID}; + return x => new {First = x.ResourceID, Second = x.ProviderID, Type = x.ResourceType}; } } } diff --git a/Kyoo.Common/Models/Resources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs index 8162ff16..f7c5db3f 100644 --- a/Kyoo.Common/Models/Resources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -8,7 +8,7 @@ namespace Kyoo.Models /// A class representing collections of . /// A collection can also be stored in a . /// - public class Collection : IResource + public class Collection : IResource, IMetadata { /// public int ID { get; set; } @@ -42,6 +42,9 @@ namespace Kyoo.Models /// The list of libraries that contains this collection. /// [LoadableRelation] public ICollection Libraries { get; set; } + + /// + public ICollection ExternalIDs { get; set; } #if ENABLE_INTERNAL_LINKS diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index 7f76a8a4..687e88fb 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -10,7 +10,7 @@ namespace Kyoo.Models /// /// A class to represent a single show's episode. /// - public class Episode : IResource + public class Episode : IResource, IMetadata { /// public int ID { get; set; } @@ -121,10 +121,8 @@ namespace Kyoo.Models /// public DateTime? ReleaseDate { get; set; } - /// - /// The link to metadata providers that this episode has. See for more information. - /// - [EditableRelation] [LoadableRelation] public ICollection> ExternalIDs { get; set; } + /// + public ICollection ExternalIDs { get; set; } /// /// The list of tracks this episode has. This lists video, audio and subtitles available. diff --git a/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs b/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs new file mode 100644 index 00000000..469d3dbb --- /dev/null +++ b/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Kyoo.Models.Attributes; + +namespace Kyoo.Models +{ + /// + /// An interface applied to resources containing external metadata. + /// + public interface IMetadata + { + /// + /// The link to metadata providers that this show has. See for more information. + /// + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } + } +} \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/IResource.cs b/Kyoo.Common/Models/Resources/Interfaces/IResource.cs similarity index 100% rename from Kyoo.Common/Models/Resources/IResource.cs rename to Kyoo.Common/Models/Resources/Interfaces/IResource.cs diff --git a/Kyoo.Common/Models/Resources/People.cs b/Kyoo.Common/Models/Resources/People.cs index 7ae04613..ff2b8ed1 100644 --- a/Kyoo.Common/Models/Resources/People.cs +++ b/Kyoo.Common/Models/Resources/People.cs @@ -6,7 +6,7 @@ namespace Kyoo.Models /// /// An actor, voice actor, writer, animator, somebody who worked on a . /// - public class People : IResource + public class People : IResource, IMetadata { /// public int ID { get; set; } @@ -26,10 +26,8 @@ namespace Kyoo.Models /// [SerializeAs("{HOST}/api/people/{Slug}/poster")] public string Poster { get; set; } - /// - /// The link to metadata providers that this person has. See for more information. - /// - [EditableRelation] [LoadableRelation] public ICollection> ExternalIDs { get; set; } + /// + public ICollection ExternalIDs { get; set; } /// /// The list of roles this person has played in. See for more information. diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index 9b292020..b390f712 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -10,7 +10,7 @@ namespace Kyoo.Models /// /// A season of a . /// - public class Season : IResource + public class Season : IResource, IMetadata { /// public int ID { get; set; } @@ -81,10 +81,8 @@ namespace Kyoo.Models /// [SerializeAs("{HOST}/api/seasons/{Slug}/thumb")] public string Poster { get; set; } - /// - /// The link to metadata providers that this episode has. See for more information. - /// - [EditableRelation] [LoadableRelation] public ICollection> ExternalIDs { get; set; } + /// + public ICollection ExternalIDs { get; set; } /// /// The list of episodes that this season contains. diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index 57c9fcef..85c5b959 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -11,7 +11,7 @@ namespace Kyoo.Models /// /// A series or a movie. /// - public class Show : IResource, IOnMerge + public class Show : IResource, IMetadata, IOnMerge { /// public int ID { get; set; } @@ -89,11 +89,9 @@ namespace Kyoo.Models /// public bool IsMovie { get; set; } - /// - /// The link to metadata providers that this show has. See for more information. - /// - [EditableRelation] [LoadableRelation] public ICollection> ExternalIDs { get; set; } - + /// + public ICollection ExternalIDs { get; set; } + /// /// The ID of the Studio that made this show. /// @@ -157,19 +155,16 @@ namespace Kyoo.Models /// /// This method will never return anything if the are not loaded. /// The slug of the provider - /// The field of the asked provider. + /// The field of the asked provider. [CanBeNull] public string GetID(string provider) { - return ExternalIDs?.FirstOrDefault(x => x.Second.Slug == provider)?.DataID; + return ExternalIDs?.FirstOrDefault(x => x.Provider.Slug == provider)?.DataID; } /// public void OnMerge(object merged) { - if (ExternalIDs != null) - foreach (MetadataID id in ExternalIDs) - id.First = this; if (People != null) foreach (PeopleRole link in People) link.Show = this; diff --git a/Kyoo.Common/Models/Resources/Studio.cs b/Kyoo.Common/Models/Resources/Studio.cs index ebc3c4c1..80195317 100644 --- a/Kyoo.Common/Models/Resources/Studio.cs +++ b/Kyoo.Common/Models/Resources/Studio.cs @@ -6,7 +6,7 @@ namespace Kyoo.Models /// /// A studio that make shows. /// - public class Studio : IResource + public class Studio : IResource, IMetadata { /// public int ID { get; set; } @@ -24,6 +24,9 @@ namespace Kyoo.Models /// [LoadableRelation] public ICollection Shows { get; set; } + /// + public ICollection ExternalIDs { get; set; } + /// /// Create a new, empty, . /// diff --git a/Kyoo.CommonAPI/DatabaseContext.cs b/Kyoo.CommonAPI/DatabaseContext.cs index 36cdc47b..d0600da1 100644 --- a/Kyoo.CommonAPI/DatabaseContext.cs +++ b/Kyoo.CommonAPI/DatabaseContext.cs @@ -61,6 +61,10 @@ namespace Kyoo /// public DbSet Providers { get; set; } /// + /// All metadata ids, not discriminated by type. See . + /// + public DbSet MetadataIDs { get; set; } + /// /// The list of registered users. /// public DbSet Users { get; set; } @@ -83,17 +87,6 @@ namespace Kyoo /// public DbSet LibraryItems { get; set; } - /// - /// Get all metadataIDs (ExternalIDs) of a given resource. See . - /// - /// The metadata of this type will be returned. - /// A queryable of metadata ids for a type. - public DbSet> MetadataIds() - where T : class, IResource - { - return Set>(); - } - /// /// Get a generic link between two resource types. /// @@ -132,6 +125,22 @@ namespace Kyoo optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); } + /// + /// Build the metadata model for the given type. + /// + /// The database model builder + /// The type to add metadata to. + private void _HasMetadata(ModelBuilder modelBuilder) + where T : class, IMetadata + { + modelBuilder.Entity() + .HasMany(x => x.ExternalIDs) + .WithOne() + .HasForeignKey(x => x.ResourceID) + .OnDelete(DeleteBehavior.Cascade); + } + + /// /// Set database parameters to support every types of Kyoo. /// @@ -234,43 +243,21 @@ namespace Kyoo .WithMany(x => x.ShowLinks), y => y.HasKey(Link.PrimaryKey)); - modelBuilder.Entity>() - .HasKey(MetadataID.PrimaryKey); - modelBuilder.Entity>() - .HasOne(x => x.First) - .WithMany(x => x.ExternalIDs) - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity>() - .HasKey(MetadataID.PrimaryKey); - modelBuilder.Entity>() - .HasOne(x => x.First) - .WithMany(x => x.ExternalIDs) - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity>() - .HasKey(MetadataID.PrimaryKey); - modelBuilder.Entity>() - .HasOne(x => x.First) - .WithMany(x => x.ExternalIDs) - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity>() - .HasKey(MetadataID.PrimaryKey); - modelBuilder.Entity>() - .HasOne(x => x.First) - .WithMany(x => x.ExternalIDs) - .OnDelete(DeleteBehavior.Cascade); - - - modelBuilder.Entity>().HasOne(x => x.Second).WithMany() - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity>().HasOne(x => x.Second).WithMany() - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity>().HasOne(x => x.Second).WithMany() - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity>().HasOne(x => x.Second).WithMany() - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity>().HasOne(x => x.Second).WithMany() - .OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity() + .HasKey(MetadataID.PrimaryKey); + modelBuilder.Entity() + .Property(x => x.ResourceType) + .IsRequired(); + modelBuilder.Entity() + .HasDiscriminator(x => x.ResourceType); + _HasMetadata(modelBuilder); + _HasMetadata(modelBuilder); + _HasMetadata(modelBuilder); + _HasMetadata(modelBuilder); + _HasMetadata(modelBuilder); + _HasMetadata(modelBuilder); + modelBuilder.Entity() .HasKey(x => new {First = x.FirstID, Second = x.SecondID}); diff --git a/Kyoo.Tests/Database/RepositoryActivator.cs b/Kyoo.Tests/Database/RepositoryActivator.cs index ee6aa4df..25530f78 100644 --- a/Kyoo.Tests/Database/RepositoryActivator.cs +++ b/Kyoo.Tests/Database/RepositoryActivator.cs @@ -22,9 +22,9 @@ namespace Kyoo.Tests ProviderRepository provider = new(_database); LibraryRepository library = new(_database, provider); - CollectionRepository collection = new(_database); + CollectionRepository collection = new(_database, provider); GenreRepository genre = new(_database); - StudioRepository studio = new(_database); + StudioRepository studio = new(_database, provider); PeopleRepository people = new(_database, provider, new Lazy(() => LibraryManager.ShowRepository)); ShowRepository show = new(_database, studio, people, genre, provider); diff --git a/Kyoo.Tests/Database/SpecificTests/ShowTests.cs b/Kyoo.Tests/Database/SpecificTests/ShowTests.cs index 63207710..42ab693c 100644 --- a/Kyoo.Tests/Database/SpecificTests/ShowTests.cs +++ b/Kyoo.Tests/Database/SpecificTests/ShowTests.cs @@ -149,10 +149,9 @@ namespace Kyoo.Tests.Database Show value = await _repository.Get(TestSample.Get().Slug); value.ExternalIDs = new[] { - new MetadataID() + new MetadataID { - First = value, - Second = new Provider("test", "test.png"), + Provider = new Provider("test", "test.png"), DataID = "1234" } }; @@ -160,19 +159,19 @@ namespace Kyoo.Tests.Database Assert.Equal(value.Slug, edited.Slug); Assert.Equal( - value.ExternalIDs.Select(x => new {x.DataID, x.Second.Slug}), - edited.ExternalIDs.Select(x => new {x.DataID, x.Second.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.Second) + .ThenInclude(x => x.Provider) .FirstAsync(); Assert.Equal(value.Slug, show.Slug); Assert.Equal( - value.ExternalIDs.Select(x => new {x.DataID, x.Second.Slug}), - show.ExternalIDs.Select(x => new {x.DataID, x.Second.Slug})); + value.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug}), + show.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug})); } [Fact] @@ -209,10 +208,9 @@ namespace Kyoo.Tests.Database expected.Slug = "created-relation-test"; expected.ExternalIDs = new[] { - new MetadataID + new MetadataID { - First = expected, - Second = new Provider("provider", "provider.png"), + Provider = new Provider("provider", "provider.png"), DataID = "ID" } }; diff --git a/Kyoo.TheMovieDb/Convertors.cs b/Kyoo.TheMovieDb/Convertors.cs index cb5b4412..0cf5fe98 100644 --- a/Kyoo.TheMovieDb/Convertors.cs +++ b/Kyoo.TheMovieDb/Convertors.cs @@ -48,9 +48,9 @@ namespace Kyoo.TheMovieDb .ToArray(), ExternalIDs = new [] { - new MetadataID + new MetadataID { - Second = provider, + Provider = provider, Link = $"https://www.themoviedb.org/movie/{movie.Id}", DataID = movie.Id.ToString() } @@ -94,9 +94,9 @@ namespace Kyoo.TheMovieDb .ToArray(), ExternalIDs = new [] { - new MetadataID + new MetadataID { - Second = provider, + Provider = provider, Link = $"https://www.themoviedb.org/movie/{tv.Id}", DataID = tv.Id.ToString() } @@ -145,9 +145,9 @@ namespace Kyoo.TheMovieDb IsMovie = true, ExternalIDs = new [] { - new MetadataID + new MetadataID { - Second = provider, + Provider = provider, Link = $"https://www.themoviedb.org/movie/{movie.Id}", DataID = movie.Id.ToString() } @@ -178,9 +178,9 @@ namespace Kyoo.TheMovieDb IsMovie = true, ExternalIDs = new [] { - new MetadataID + new MetadataID { - Second = provider, + Provider = provider, Link = $"https://www.themoviedb.org/movie/{tv.Id}", DataID = tv.Id.ToString() } @@ -205,9 +205,9 @@ namespace Kyoo.TheMovieDb Poster = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" : null, ExternalIDs = new[] { - new MetadataID + new MetadataID { - Second = provider, + Provider = provider, DataID = cast.Id.ToString(), Link = $"https://www.themoviedb.org/person/{cast.Id}" } @@ -235,9 +235,9 @@ namespace Kyoo.TheMovieDb Poster = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" : null, ExternalIDs = new[] { - new MetadataID + new MetadataID { - Second = provider, + Provider = provider, DataID = cast.Id.ToString(), Link = $"https://www.themoviedb.org/person/{cast.Id}" } @@ -265,9 +265,9 @@ namespace Kyoo.TheMovieDb Poster = crew.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{crew.ProfilePath}" : null, ExternalIDs = new[] { - new MetadataID + new MetadataID { - Second = provider, + Provider = provider, DataID = crew.Id.ToString(), Link = $"https://www.themoviedb.org/person/{crew.Id}" } diff --git a/Kyoo.TheTvdb/Convertors.cs b/Kyoo.TheTvdb/Convertors.cs index bc31b5fd..6aad939b 100644 --- a/Kyoo.TheTvdb/Convertors.cs +++ b/Kyoo.TheTvdb/Convertors.cs @@ -58,11 +58,11 @@ namespace Kyoo.TheTvdb Poster = result.Poster != null ? $"https://www.thetvdb.com{result.Poster}" : null, ExternalIDs = new[] { - new MetadataID + new MetadataID { DataID = result.Id.ToString(), Link = $"https://www.thetvdb.com/series/{result.Slug}", - Second = provider + Provider = provider } } }; @@ -89,11 +89,11 @@ namespace Kyoo.TheTvdb Genres = series.Genre.Select(y => new Genre(y)).ToList(), ExternalIDs = new[] { - new MetadataID + new MetadataID { DataID = series.Id.ToString(), Link = $"https://www.thetvdb.com/series/{series.Slug}", - Second = provider + Provider = provider } } }; @@ -116,11 +116,11 @@ namespace Kyoo.TheTvdb Poster = actor.Image != null ? $"https://www.thetvdb.com/banners/{actor.Image}" : null, ExternalIDs = new [] { - new MetadataID() + new MetadataID { DataID = actor.Id.ToString(), Link = $"https://www.thetvdb.com/people/{actor.Id}", - Second = provider + Provider = provider } } }, @@ -147,11 +147,11 @@ namespace Kyoo.TheTvdb Thumb = episode.Filename != null ? $"https://www.thetvdb.com/banners/{episode.Filename}" : null, ExternalIDs = new[] { - new MetadataID + new MetadataID { DataID = episode.Id.ToString(), Link = $"https://www.thetvdb.com/series/{episode.SeriesId}/episodes/{episode.Id}", - Second = provider + Provider = provider } } }; diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index e0bc7843..6b546f07 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -18,6 +18,11 @@ namespace Kyoo.Controllers /// private readonly DatabaseContext _database; + /// + /// A provider repository to handle externalID creation and deletion + /// + private readonly IProviderRepository _providers; + /// protected override Expression> DefaultSort => x => x.Name; @@ -25,10 +30,12 @@ namespace Kyoo.Controllers /// Create a new . /// /// The database handle to use - public CollectionRepository(DatabaseContext database) + /// /// A provider repository + public CollectionRepository(DatabaseContext database, IProviderRepository providers) : base(database) { _database = database; + _providers = providers; } /// @@ -46,9 +53,35 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; + obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); 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); + await resource.ExternalIDs.ForEachAsync(async x => + { + x.Provider = await _providers.CreateIfNotExists(x.Provider); + x.ProviderID = x.Provider.ID; + x.ResourceType = nameof(Collection); + _database.Entry(x.Provider).State = EntityState.Detached; + }); + } + + /// + protected override async Task EditRelations(Collection resource, Collection changed, bool resetOld) + { + if (changed.ExternalIDs != null || resetOld) + { + await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); + resource.ExternalIDs = changed.ExternalIDs; + } + + await base.EditRelations(resource, changed, resetOld); + } /// public override async Task Delete(Collection obj) diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 3c1393eb..7a2b0479 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -160,9 +160,10 @@ namespace Kyoo.Controllers await base.Validate(resource); await resource.ExternalIDs.ForEachAsync(async x => { - x.Second = await _providers.CreateIfNotExists(x.Second); - x.SecondID = x.Second.ID; - _database.Entry(x.Second).State = EntityState.Detached; + x.Provider = await _providers.CreateIfNotExists(x.Provider); + x.ProviderID = x.Provider.ID; + x.ResourceType = nameof(Episode); + _database.Entry(x.Provider).State = EntityState.Detached; }); } diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index 9adb37ee..6267f52f 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -73,9 +73,10 @@ namespace Kyoo.Controllers await base.Validate(resource); await resource.ExternalIDs.ForEachAsync(async id => { - id.Second = await _providers.CreateIfNotExists(id.Second); - id.SecondID = id.Second.ID; - _database.Entry(id.Second).State = EntityState.Detached; + id.Provider = await _providers.CreateIfNotExists(id.Provider); + id.ProviderID = id.Provider.ID; + id.ResourceType = nameof(People); + _database.Entry(id.Provider).State = EntityState.Detached; }); await resource.Roles.ForEachAsync(async role => { diff --git a/Kyoo/Controllers/Repositories/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index e48d4f50..13525c9c 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -9,21 +9,18 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { /// - /// A local repository to handle providers. + /// A local repository to handle providers. /// public class ProviderRepository : LocalRepository, IProviderRepository { /// - /// The database handle + /// The database handle /// private readonly DatabaseContext _database; - - /// - protected override Expression> DefaultSort => x => x.Slug; /// - /// Create a new . + /// Create a new . /// /// The database handle public ProviderRepository(DatabaseContext database) @@ -32,6 +29,9 @@ namespace Kyoo.Controllers _database = database; } + /// + protected override Expression> DefaultSort => x => x.Slug; + /// public override async Task> Search(string query) { @@ -47,7 +47,8 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync($"Trying to insert a duplicated provider (slug {obj.Slug} already exists)."); + await _database.SaveChangesAsync("Trying to insert a duplicated provider " + + $"(slug {obj.Slug} already exists)."); return obj; } @@ -56,20 +57,24 @@ namespace Kyoo.Controllers { if (obj == null) throw new ArgumentNullException(nameof(obj)); - + _database.Entry(obj).State = EntityState.Deleted; await _database.SaveChangesAsync(); } /// - public Task>> GetMetadataID(Expression, bool>> where = null, - Sort> sort = default, + public Task> GetMetadataID(Expression> where = null, + Sort sort = default, Pagination limit = default) - where T : class, IResource + where T : class, IMetadata { - return ApplyFilters(_database.MetadataIds().Include(y => y.Second), - x => _database.MetadataIds().FirstOrDefaultAsync(y => y.FirstID == x), - x => x.FirstID, + string discriminator = typeof(T).Name; + return ApplyFilters(_database.MetadataIDs + .Include(y => y.Provider) + .Where(x => x.ResourceType == discriminator), + x => _database.MetadataIDs.FirstOrDefaultAsync(y => y.ResourceID == x + && y.ResourceType == discriminator), + x => x.ResourceID, where, sort, limit); diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index e0036982..45aa4554 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -106,9 +106,10 @@ namespace Kyoo.Controllers await base.Validate(resource); await resource.ExternalIDs.ForEachAsync(async id => { - id.Second = await _providers.CreateIfNotExists(id.Second); - id.SecondID = id.Second.ID; - _database.Entry(id.Second).State = EntityState.Detached; + id.Provider = await _providers.CreateIfNotExists(id.Provider); + id.ProviderID = id.Provider.ID; + id.ResourceType = nameof(Season); + _database.Entry(id.Provider).State = EntityState.Detached; }); } diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 769cb232..37921c4d 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -101,9 +101,10 @@ namespace Kyoo.Controllers }); await resource.ExternalIDs.ForEachAsync(async id => { - id.Second = await _providers.CreateIfNotExists(id.Second); - id.SecondID = id.Second.ID; - _database.Entry(id.Second).State = EntityState.Detached; + id.Provider = await _providers.CreateIfNotExists(id.Provider); + id.ProviderID = id.Provider.ID; + id.ResourceType = nameof(Show); + _database.Entry(id.Provider).State = EntityState.Detached; }); await resource.People.ForEachAsync(async role => { diff --git a/Kyoo/Controllers/Repositories/StudioRepository.cs b/Kyoo/Controllers/Repositories/StudioRepository.cs index 516b7c08..0422c382 100644 --- a/Kyoo/Controllers/Repositories/StudioRepository.cs +++ b/Kyoo/Controllers/Repositories/StudioRepository.cs @@ -18,6 +18,11 @@ namespace Kyoo.Controllers /// private readonly DatabaseContext _database; + /// + /// A provider repository to handle externalID creation and deletion + /// + private readonly IProviderRepository _providers; + /// protected override Expression> DefaultSort => x => x.Name; @@ -26,10 +31,12 @@ namespace Kyoo.Controllers /// Create a new . /// /// The database handle - public StudioRepository(DatabaseContext database) + /// A provider repository + public StudioRepository(DatabaseContext database, IProviderRepository providers) : base(database) { _database = database; + _providers = providers; } /// @@ -50,6 +57,31 @@ namespace Kyoo.Controllers await _database.SaveChangesAsync($"Trying to insert a duplicated studio (slug {obj.Slug} already exists)."); return obj; } + + /// + protected override async Task Validate(Studio resource) + { + await base.Validate(resource); + await resource.ExternalIDs.ForEachAsync(async x => + { + x.Provider = await _providers.CreateIfNotExists(x.Provider); + x.ProviderID = x.Provider.ID; + x.ResourceType = nameof(Studio); + _database.Entry(x.Provider).State = EntityState.Detached; + }); + } + + /// + protected override async Task EditRelations(Studio resource, Studio changed, bool resetOld) + { + if (changed.ExternalIDs != null || resetOld) + { + await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); + resource.ExternalIDs = changed.ExternalIDs; + } + + await base.EditRelations(resource, changed, resetOld); + } /// public override async Task Delete(Studio obj) From 9777551b78ab6977161e08894088f22a25ac2a19 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 27 Jul 2021 00:11:06 +0200 Subject: [PATCH 04/34] Reworking images --- Kyoo.Common/Controllers/IThumbnailsManager.cs | 29 +- Kyoo.Common/Models/Resources/Collection.cs | 12 +- Kyoo.Common/Models/Resources/Episode.cs | 10 +- .../Models/Resources/Interfaces/IMetadata.cs | 3 +- .../Resources/Interfaces/IThumbnails.cs | 49 ++++ Kyoo.Common/Models/Resources/People.cs | 10 +- Kyoo.Common/Models/Resources/Provider.cs | 10 +- Kyoo.Common/Models/Resources/Season.cs | 10 +- Kyoo.Common/Models/Resources/Show.cs | 24 +- Kyoo.Common/Models/Resources/Studio.cs | 2 +- Kyoo.Tests/Database/TestSample.cs | 59 ++++- Kyoo.TheMovieDb/Convertors.cs | 101 ++++--- Kyoo.TheMovieDb/ProviderTmdb.cs | 6 +- Kyoo.TheTvdb/Convertors.cs | 32 ++- Kyoo.TheTvdb/ProviderTvdb.cs | 5 +- Kyoo/Controllers/ThumbnailsManager.cs | 250 ++++-------------- Kyoo/Tasks/RegisterEpisode.cs | 2 +- Kyoo/Views/EpisodeApi.cs | 4 +- Kyoo/Views/PeopleApi.cs | 4 +- Kyoo/Views/ProviderApi.cs | 4 +- Kyoo/Views/SeasonApi.cs | 4 +- Kyoo/Views/ShowApi.cs | 6 +- 22 files changed, 326 insertions(+), 310 deletions(-) create mode 100644 Kyoo.Common/Models/Resources/Interfaces/IThumbnails.cs diff --git a/Kyoo.Common/Controllers/IThumbnailsManager.cs b/Kyoo.Common/Controllers/IThumbnailsManager.cs index 465d4c62..023e566c 100644 --- a/Kyoo.Common/Controllers/IThumbnailsManager.cs +++ b/Kyoo.Common/Controllers/IThumbnailsManager.cs @@ -23,37 +23,18 @@ namespace Kyoo.Controllers /// The type of the item /// true if an image has been downloaded, false otherwise. Task DownloadImages([NotNull] T item, bool alwaysDownload = false) - where T : IResource; - + where T : IThumbnails; + /// /// Retrieve the local path of the poster of the given item. /// /// The item to retrieve the poster from. + /// The ID of the image. See for values. /// The type of the item /// If the type does not have a poster /// The path of the poster for the given resource (it might or might not exists). - Task GetPoster([NotNull] T item) - where T : IResource; - - /// - /// Retrieve the local path of the logo of the given item. - /// - /// The item to retrieve the logo from. - /// The type of the item - /// If the type does not have a logo - /// The path of the logo for the given resource (it might or might not exists). - Task GetLogo([NotNull] T item) - where T : IResource; - - /// - /// Retrieve the local path of the thumbnail of the given item. - /// - /// The item to retrieve the thumbnail from. - /// The type of the item - /// If the type does not have a thumbnail - /// The path of the thumbnail for the given resource (it might or might not exists). - Task GetThumbnail([NotNull] T item) - where T : IResource; + Task GetImagePath([NotNull] T item, int imageID) + where T : IThumbnails; } } diff --git a/Kyoo.Common/Models/Resources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs index f7c5db3f..a8b796f2 100644 --- a/Kyoo.Common/Models/Resources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -8,7 +8,7 @@ namespace Kyoo.Models /// A class representing collections of . /// A collection can also be stored in a . /// - public class Collection : IResource, IMetadata + public class Collection : IResource, IMetadata, IThumbnails { /// public int ID { get; set; } @@ -20,14 +20,18 @@ namespace Kyoo.Models /// The name of this collection. /// public string Name { get; set; } + + /// + public Dictionary Images { get; set; } /// /// The path of this poster. /// By default, the http path for this poster is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/collection/{Slug}/poster")] public string Poster { get; set; } - + [SerializeAs("{HOST}/api/collection/{Slug}/poster")] + public string Poster => Images[Thumbnails.Poster]; + /// /// The description of this collection. /// @@ -44,7 +48,7 @@ namespace Kyoo.Models [LoadableRelation] public ICollection Libraries { get; set; } /// - public ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } #if ENABLE_INTERNAL_LINKS diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index 687e88fb..ad2b79bb 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -10,7 +10,7 @@ namespace Kyoo.Models /// /// A class to represent a single show's episode. /// - public class Episode : IResource, IMetadata + public class Episode : IResource, IMetadata, IThumbnails { /// public int ID { get; set; } @@ -98,13 +98,17 @@ namespace Kyoo.Models /// 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; } /// /// The path of this episode's thumbnail. /// By default, the http path for the thumbnail is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/episodes/{Slug}/thumb")] public string Thumb { get; set; } + [SerializeAs("{HOST}/api/episodes/{Slug}/thumb")] + public string Thumb => Images[Thumbnails.Thumbnail]; /// /// The title of this episode. @@ -122,7 +126,7 @@ namespace Kyoo.Models public DateTime? ReleaseDate { get; set; } /// - public ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } /// /// The list of tracks this episode has. This lists video, audio and subtitles available. diff --git a/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs b/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs index 469d3dbb..1c937715 100644 --- a/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs +++ b/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs @@ -11,6 +11,7 @@ namespace Kyoo.Models /// /// The link to metadata providers that this show has. See for more information. /// - [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] + public ICollection ExternalIDs { get; set; } } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Interfaces/IThumbnails.cs b/Kyoo.Common/Models/Resources/Interfaces/IThumbnails.cs new file mode 100644 index 00000000..074751c4 --- /dev/null +++ b/Kyoo.Common/Models/Resources/Interfaces/IThumbnails.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using Kyoo.Controllers; + +namespace Kyoo.Models +{ + /// + /// An interface representing items that contains images (like posters, thumbnails, logo, banners...) + /// + public interface IThumbnails + { + /// + /// The list of images mapped to a certain index. + /// The string value should be a path supported by the . + /// + /// + /// 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 + } + + /// + /// A class containing constant values for images. To be used as index of a . + /// + public static class Thumbnails + { + /// + /// A poster is a 9/16 format image with the cover of the resource. + /// + public const int Poster = 0; + + /// + /// A thumbnail is a 16/9 format image, it could ether be used as a background or as a preview but it usually + /// is not an official image. + /// + public const int Thumbnail = 1; + + /// + /// A logo is a small image representing the resource. + /// + public const int Logo = 2; + + /// + /// A video of a few minutes that tease the content. + /// + public const int Trailer = 3; + } +} \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/People.cs b/Kyoo.Common/Models/Resources/People.cs index ff2b8ed1..5b7f6864 100644 --- a/Kyoo.Common/Models/Resources/People.cs +++ b/Kyoo.Common/Models/Resources/People.cs @@ -6,7 +6,7 @@ namespace Kyoo.Models /// /// An actor, voice actor, writer, animator, somebody who worked on a . /// - public class People : IResource, IMetadata + public class People : IResource, IMetadata, IThumbnails { /// public int ID { get; set; } @@ -19,15 +19,19 @@ namespace Kyoo.Models /// public string Name { get; set; } + /// + public Dictionary Images { get; set; } + /// /// The path of this poster. /// By default, the http path for this poster is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/people/{Slug}/poster")] public string Poster { get; set; } + [SerializeAs("{HOST}/api/people/{Slug}/poster")] + public string Poster => Images[Thumbnails.Poster]; /// - public ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } /// /// The list of roles this person has played in. See for more information. diff --git a/Kyoo.Common/Models/Resources/Provider.cs b/Kyoo.Common/Models/Resources/Provider.cs index e13a9be4..e931479a 100644 --- a/Kyoo.Common/Models/Resources/Provider.cs +++ b/Kyoo.Common/Models/Resources/Provider.cs @@ -9,7 +9,7 @@ namespace Kyoo.Models /// This class contains metadata about . /// You can have providers even if you don't have the corresponding . /// - public class Provider : IResource + public class Provider : IResource, IThumbnails { /// public int ID { get; set; } @@ -22,12 +22,16 @@ namespace Kyoo.Models /// public string Name { get; set; } + /// + public Dictionary Images { get; set; } + /// /// The path of this provider's logo. /// By default, the http path for this logo is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/providers/{Slug}/logo")] public string Logo { get; set; } + [SerializeAs("{HOST}/api/providers/{Slug}/logo")] + public string Logo => Images[Thumbnails.Logo]; /// /// The extension of the logo. This is used for http responses. @@ -61,7 +65,7 @@ namespace Kyoo.Models { Slug = Utility.ToSlug(name); Name = name; - Logo = logo; + Images[Thumbnails.Logo] = logo; } } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index b390f712..fddb7eb4 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -10,7 +10,7 @@ namespace Kyoo.Models /// /// A season of a . /// - public class Season : IResource, IMetadata + public class Season : IResource, IMetadata, IThumbnails { /// public int ID { get; set; } @@ -74,15 +74,19 @@ namespace Kyoo.Models /// public DateTime? EndDate { get; set; } + /// + public Dictionary Images { get; set; } + /// /// The path of this poster. /// By default, the http path for this poster is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/seasons/{Slug}/thumb")] public string Poster { get; set; } + [SerializeAs("{HOST}/api/seasons/{Slug}/thumb")] + public string Poster => Images[Thumbnails.Poster]; /// - public ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } /// /// The list of episodes that this season contains. diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index 85c5b959..a32a0169 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -11,7 +11,7 @@ namespace Kyoo.Models /// /// A series or a movie. /// - public class Show : IResource, IMetadata, IOnMerge + public class Show : IResource, IMetadata, IOnMerge, IThumbnails { /// public int ID { get; set; } @@ -44,12 +44,12 @@ namespace Kyoo.Models /// Is this show airing, not aired yet or finished? /// public Status Status { get; set; } - + /// /// An URL to a trailer. This could be any path supported by the . /// /// TODO for now, this is set to a youtube url. It should be cached and converted to a local file. - public string TrailerUrl { get; set; } + public string TrailerUrl => Images[Thumbnails.Trailer]; /// /// The date this show started airing. It can be null if this is unknown. @@ -63,26 +63,32 @@ namespace Kyoo.Models /// public DateTime? EndAir { get; set; } + /// + public Dictionary Images { get; set; } + /// /// The path of this show's poster. /// By default, the http path for this poster is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/shows/{Slug}/poster")] public string Poster { get; set; } - + [SerializeAs("{HOST}/api/shows/{Slug}/poster")] + public string Poster => Images[Thumbnails.Poster]; + /// /// The path of this show's logo. /// By default, the http path for this logo is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/shows/{Slug}/logo")] public string Logo { get; set; } - + [SerializeAs("{HOST}/api/shows/{Slug}/logo")] + public string Logo => Images[Thumbnails.Logo]; + /// /// The path of this show's backdrop. /// By default, the http path for this backdrop is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] public string Backdrop { get; set; } + [SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] + public string Backdrop => Images[Thumbnails.Thumbnail]; /// /// True if this show represent a movie, false otherwise. @@ -90,7 +96,7 @@ namespace Kyoo.Models public bool IsMovie { get; set; } /// - public ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } /// /// The ID of the Studio that made this show. diff --git a/Kyoo.Common/Models/Resources/Studio.cs b/Kyoo.Common/Models/Resources/Studio.cs index 80195317..03ec04df 100644 --- a/Kyoo.Common/Models/Resources/Studio.cs +++ b/Kyoo.Common/Models/Resources/Studio.cs @@ -25,7 +25,7 @@ namespace Kyoo.Models [LoadableRelation] public ICollection Shows { get; set; } /// - public ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } /// /// Create a new, empty, . diff --git a/Kyoo.Tests/Database/TestSample.cs b/Kyoo.Tests/Database/TestSample.cs index e0ff955f..d81612ad 100644 --- a/Kyoo.Tests/Database/TestSample.cs +++ b/Kyoo.Tests/Database/TestSample.cs @@ -41,7 +41,10 @@ namespace Kyoo.Tests Slug = "collection", Name = "Collection", Overview = "A nice collection for tests", - Poster = "Poster" + Images = + { + [Thumbnails.Poster] = "Poster" + } } }, { @@ -61,12 +64,14 @@ namespace Kyoo.Tests "In time, however, these childhood friends drifted apart, and when they became high " + "school students, they had long ceased to think of each other as friends.", Status = Status.Finished, - TrailerUrl = null, StartAir = new DateTime(2011, 1, 1), EndAir = new DateTime(2011, 1, 1), - Poster = "poster", - Logo = "logo", - Backdrop = "backdrop", + Images = + { + [Thumbnails.Poster] = "Poster", + [Thumbnails.Logo] = "Logo", + [Thumbnails.Thumbnail] = "Thumbnail" + }, IsMovie = false, Studio = null } @@ -83,7 +88,12 @@ namespace Kyoo.Tests Overview = "The first season", StartDate = new DateTime(2020, 06, 05), EndDate = new DateTime(2020, 07, 05), - Poster = "poster" + Images = + { + [Thumbnails.Poster] = "Poster", + [Thumbnails.Logo] = "Logo", + [Thumbnails.Thumbnail] = "Thumbnail" + }, } }, { @@ -98,7 +108,12 @@ namespace Kyoo.Tests EpisodeNumber = 1, AbsoluteNumber = 1, Path = "/home/kyoo/anohana-s1e1", - Thumb = "thumbnail", + Images = + { + [Thumbnails.Poster] = "Poster", + [Thumbnails.Logo] = "Logo", + [Thumbnails.Thumbnail] = "Thumbnail" + }, Title = "Episode 1", Overview = "Summary of the first episode", ReleaseDate = new DateTime(2020, 06, 05) @@ -129,7 +144,12 @@ namespace Kyoo.Tests ID = 1, Slug = "the-actor", Name = "The Actor", - Poster = "NicePoster" + Images = + { + [Thumbnails.Poster] = "Poster", + [Thumbnails.Logo] = "Logo", + [Thumbnails.Thumbnail] = "Thumbnail" + }, } }, { @@ -138,7 +158,7 @@ namespace Kyoo.Tests { ID = 1, Slug = "hyper-studio", - Name = "Hyper studio" + Name = "Hyper studio", } }, { @@ -157,7 +177,12 @@ namespace Kyoo.Tests ID = 1, Slug = "tvdb", Name = "The TVDB", - Logo = "path/tvdb.svg", + Images = + { + [Thumbnails.Poster] = "Poster", + [Thumbnails.Logo] = "path/tvdb.svg", + [Thumbnails.Thumbnail] = "Thumbnail" + }, LogoExtension = "svg" } }, @@ -257,7 +282,12 @@ namespace Kyoo.Tests EpisodeNumber = null, AbsoluteNumber = 3, Path = "/home/kyoo/anohana-3", - Thumb = "thumbnail", + Images = + { + [Thumbnails.Poster] = "Poster", + [Thumbnails.Logo] = "Logo", + [Thumbnails.Thumbnail] = "Thumbnail" + }, Title = "Episode 3", Overview = "Summary of the third absolute episode", ReleaseDate = new DateTime(2020, 06, 05) @@ -272,7 +302,12 @@ namespace Kyoo.Tests ShowSlug = "anohana", ShowID = 1, Path = "/home/kyoo/john-wick", - Thumb = "thumb", + Images = + { + [Thumbnails.Poster] = "Poster", + [Thumbnails.Logo] = "Logo", + [Thumbnails.Thumbnail] = "Thumbnail" + }, Title = "John wick", Overview = "A movie episode test", ReleaseDate = new DateTime(1595, 05, 12) diff --git a/Kyoo.TheMovieDb/Convertors.cs b/Kyoo.TheMovieDb/Convertors.cs index 0cf5fe98..b6fd4ad1 100644 --- a/Kyoo.TheMovieDb/Convertors.cs +++ b/Kyoo.TheMovieDb/Convertors.cs @@ -26,17 +26,21 @@ namespace Kyoo.TheMovieDb Title = movie.Title, Aliases = movie.AlternativeTitles.Titles.Select(x => x.Title).ToArray(), Overview = movie.Overview, - TrailerUrl = movie.Videos?.Results.Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube") - .Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault(), Status = movie.Status == "Released" ? Status.Finished : Status.Planned, StartAir = movie.ReleaseDate, EndAir = movie.ReleaseDate, - Poster = movie.PosterPath != null - ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" - : null, - Backdrop = movie.BackdropPath != null - ? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}" - : null, + Images = + { + [Thumbnails.Poster] = movie.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" + : null, + [Thumbnails.Thumbnail] = movie.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}" + : null, + [Thumbnails.Trailer] = movie.Videos?.Results + .Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube") + .Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault(), + }, Genres = movie.Genres.Select(x => new Genre(x.Name)).ToArray(), Studio = !string.IsNullOrEmpty(movie.ProductionCompanies.FirstOrDefault()?.Name) ? new Studio(movie.ProductionCompanies.First().Name) @@ -72,17 +76,21 @@ namespace Kyoo.TheMovieDb Title = tv.Name, Aliases = tv.AlternativeTitles.Results.Select(x => x.Title).ToArray(), Overview = tv.Overview, - TrailerUrl = tv.Videos?.Results.Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube") - .Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault(), Status = tv.Status == "Ended" ? Status.Finished : Status.Planned, StartAir = tv.FirstAirDate, EndAir = tv.LastAirDate, - Poster = tv.PosterPath != null - ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" - : null, - Backdrop = tv.BackdropPath != null - ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" - : null, + Images = + { + [Thumbnails.Poster] = tv.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" + : null, + [Thumbnails.Thumbnail] = tv.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" + : null, + [Thumbnails.Trailer] = tv.Videos?.Results + .Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube") + .Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault() + }, Genres = tv.Genres.Select(x => new Genre(x.Name)).ToArray(), Studio = !string.IsNullOrEmpty(tv.ProductionCompanies.FirstOrDefault()?.Name) ? new Studio(tv.ProductionCompanies.First().Name) @@ -117,7 +125,15 @@ namespace Kyoo.TheMovieDb { Slug = Utility.ToSlug(collection.Name), Name = collection.Name, - Poster = $"https://image.tmdb.org/t/p/original{collection.PosterPath}" + Images = + { + [Thumbnails.Poster] = collection.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{collection.PosterPath}" + : null, + [Thumbnails.Thumbnail] = collection.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{collection.BackdropPath}" + : null + } }; } @@ -136,12 +152,15 @@ namespace Kyoo.TheMovieDb Overview = movie.Overview, StartAir = movie.ReleaseDate, EndAir = movie.ReleaseDate, - Poster = movie.PosterPath != null - ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" - : null, - Backdrop = movie.BackdropPath != null - ? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}" - : null, + Images = + { + [Thumbnails.Poster] = movie.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" + : null, + [Thumbnails.Thumbnail] = movie.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}" + : null, + }, IsMovie = true, ExternalIDs = new [] { @@ -169,12 +188,15 @@ namespace Kyoo.TheMovieDb Title = tv.Name, Overview = tv.Overview, StartAir = tv.FirstAirDate, - Poster = tv.PosterPath != null - ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" - : null, - Backdrop = tv.BackdropPath != null - ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" - : null, + Images = + { + [Thumbnails.Poster] = tv.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" + : null, + [Thumbnails.Thumbnail] = tv.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" + : null, + }, IsMovie = true, ExternalIDs = new [] { @@ -202,7 +224,12 @@ namespace Kyoo.TheMovieDb { Slug = Utility.ToSlug(cast.Name), Name = cast.Name, - Poster = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" : null, + Images = + { + [Thumbnails.Poster] = cast.ProfilePath != null + ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" + : null + }, ExternalIDs = new[] { new MetadataID @@ -232,7 +259,12 @@ namespace Kyoo.TheMovieDb { Slug = Utility.ToSlug(cast.Name), Name = cast.Name, - Poster = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" : null, + Images = + { + [Thumbnails.Poster] = cast.ProfilePath != null + ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" + : null + }, ExternalIDs = new[] { new MetadataID @@ -262,7 +294,12 @@ namespace Kyoo.TheMovieDb { Slug = Utility.ToSlug(crew.Name), Name = crew.Name, - Poster = crew.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{crew.ProfilePath}" : null, + Images = + { + [Thumbnails.Poster] = crew.ProfilePath != null + ? $"https://image.tmdb.org/t/p/original{crew.ProfilePath}" + : null + }, ExternalIDs = new[] { new MetadataID diff --git a/Kyoo.TheMovieDb/ProviderTmdb.cs b/Kyoo.TheMovieDb/ProviderTmdb.cs index b613e5f3..bde0a748 100644 --- a/Kyoo.TheMovieDb/ProviderTmdb.cs +++ b/Kyoo.TheMovieDb/ProviderTmdb.cs @@ -29,7 +29,11 @@ namespace Kyoo.TheMovieDb Slug = "the-moviedb", Name = "TheMovieDB", LogoExtension = "svg", - Logo = "https://www.themoviedb.org/assets/2/v4/logos/v2/blue_short-8e7b30f73a4020692ccca9c88bafe5dcb6f8a62a4c6bc55cd9ba82bb2cd95f6c.svg" + Images = + { + [Thumbnails.Logo] = "https://www.themoviedb.org/assets/2/v4/logos/v2/" + + "blue_short-8e7b30f73a4020692ccca9c88bafe5dcb6f8a62a4c6bc55cd9ba82bb2cd95f6c.svg" + } }; /// diff --git a/Kyoo.TheTvdb/Convertors.cs b/Kyoo.TheTvdb/Convertors.cs index 6aad939b..b992481d 100644 --- a/Kyoo.TheTvdb/Convertors.cs +++ b/Kyoo.TheTvdb/Convertors.cs @@ -55,7 +55,12 @@ namespace Kyoo.TheTvdb Overview = result.Overview, Status = _GetStatus(result.Status), StartAir = _ParseDate(result.FirstAired), - Poster = result.Poster != null ? $"https://www.thetvdb.com{result.Poster}" : null, + Images = + { + [Thumbnails.Poster] = result.Poster != null + ? $"https://www.thetvdb.com{result.Poster}" + : null, + }, ExternalIDs = new[] { new MetadataID @@ -84,8 +89,15 @@ namespace Kyoo.TheTvdb Overview = series.Overview, Status = _GetStatus(series.Status), StartAir = _ParseDate(series.FirstAired), - Poster = series.Poster != null ? $"https://www.thetvdb.com/banners/{series.Poster}" : null, - Backdrop = series.FanArt != null ? $"https://www.thetvdb.com/banners/{series.FanArt}" : null, + Images= + { + [Thumbnails.Poster] = series.Poster != null + ? $"https://www.thetvdb.com/banners/{series.Poster}" + : null, + [Thumbnails.Thumbnail] = series.FanArt != null + ? $"https://www.thetvdb.com/banners/{series.FanArt}" + : null + }, Genres = series.Genre.Select(y => new Genre(y)).ToList(), ExternalIDs = new[] { @@ -113,7 +125,12 @@ namespace Kyoo.TheTvdb { Slug = Utility.ToSlug(actor.Name), Name = actor.Name, - Poster = actor.Image != null ? $"https://www.thetvdb.com/banners/{actor.Image}" : null, + Images = + { + [Thumbnails.Poster] = actor.Image != null + ? $"https://www.thetvdb.com/banners/{actor.Image}" + : null + }, ExternalIDs = new [] { new MetadataID @@ -144,7 +161,12 @@ namespace Kyoo.TheTvdb AbsoluteNumber = episode.AbsoluteNumber, Title = episode.EpisodeName, Overview = episode.Overview, - Thumb = episode.Filename != null ? $"https://www.thetvdb.com/banners/{episode.Filename}" : null, + Images = + { + [Thumbnails.Thumbnail] = episode.Filename != null + ? $"https://www.thetvdb.com/banners/{episode.Filename}" + : null + }, ExternalIDs = new[] { new MetadataID diff --git a/Kyoo.TheTvdb/ProviderTvdb.cs b/Kyoo.TheTvdb/ProviderTvdb.cs index 47183fb7..b2ba5683 100644 --- a/Kyoo.TheTvdb/ProviderTvdb.cs +++ b/Kyoo.TheTvdb/ProviderTvdb.cs @@ -33,7 +33,10 @@ namespace Kyoo.TheTvdb Slug = "the-tvdb", Name = "TheTVDB", LogoExtension = "png", - Logo = "https://www.thetvdb.com/images/logo.png" + Images = + { + [Thumbnails.Logo] = "https://www.thetvdb.com/images/logo.png" + } }; diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index 389891b3..61d14613 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -2,7 +2,6 @@ using System; using System.IO; using System.Threading.Tasks; -using JetBrains.Annotations; using Kyoo.Models.Options; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -55,23 +54,6 @@ namespace Kyoo.Controllers }); } - /// - public Task DownloadImages(T item, bool alwaysDownload = false) - where T : IResource - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - return item switch - { - Show show => _Validate(show, alwaysDownload), - Season season => _Validate(season, alwaysDownload), - Episode episode => _Validate(episode, alwaysDownload), - People people => _Validate(people, alwaysDownload), - Provider provider => _Validate(provider, alwaysDownload), - _ => Task.FromResult(false) - }; - } - /// /// An helper function to download an image using a . /// @@ -97,196 +79,72 @@ namespace Kyoo.Controllers return false; } } - - /// - /// Download images of a specified show. - /// - /// - /// The item to cache images. - /// - /// - /// true if images should be downloaded even if they already exists locally, false otherwise. - /// - /// true if an image has been downloaded, false otherwise. - private async Task _Validate([NotNull] Show show, bool alwaysDownload) + + /// + public async Task DownloadImages(T item, bool alwaysDownload = false) + where T : IThumbnails { - bool ret = false; - - if (show.Poster != null) - { - string posterPath = await GetPoster(show); - if (alwaysDownload || !await _files.Exists(posterPath)) - ret |= await _DownloadImage(show.Poster, posterPath, $"The poster of {show.Title}"); - } - if (show.Logo != null) - { - string logoPath = await GetLogo(show); - if (alwaysDownload || !await _files.Exists(logoPath)) - ret |= await _DownloadImage(show.Logo, logoPath, $"The logo of {show.Title}"); - } - if (show.Backdrop != null) - { - string backdropPath = await GetThumbnail(show); - if (alwaysDownload || !await _files.Exists(backdropPath)) - ret |= await _DownloadImage(show.Backdrop, backdropPath, $"The backdrop of {show.Title}"); - } + if (item == null) + throw new ArgumentNullException(nameof(item)); + string name = item is IResource res ? res.Slug : "???"; + bool ret = false; + + foreach ((int id, string image) in item.Images) + { + string localPath = await GetImagePath(item, id); + if (alwaysDownload || !await _files.Exists(localPath)) + ret |= await _DownloadImage(image, localPath, $"The image n°{id} of {name}"); + } + return ret; } - - /// - /// Download images of a specified person. - /// - /// - /// The item to cache images. - /// - /// - /// true if images should be downloaded even if they already exists locally, false otherwise. - /// - /// true if an image has been downloaded, false otherwise. - private async Task _Validate([NotNull] People people, bool alwaysDownload) - { - if (people == null) - throw new ArgumentNullException(nameof(people)); - if (people.Poster == null) - return false; - string localPath = await GetPoster(people); - if (alwaysDownload || !await _files.Exists(localPath)) - return await _DownloadImage(people.Poster, localPath, $"The profile picture of {people.Name}"); - return false; - } - - /// - /// Download images of a specified season. - /// - /// - /// The item to cache images. - /// - /// - /// true if images should be downloaded even if they already exists locally, false otherwise. - /// - /// true if an image has been downloaded, false otherwise. - private async Task _Validate([NotNull] Season season, bool alwaysDownload) - { - if (season.Poster == null) - return false; - - string localPath = await GetPoster(season); - if (alwaysDownload || !await _files.Exists(localPath)) - return await _DownloadImage(season.Poster, localPath, $"The poster of {season.Slug}"); - return false; - } - /// - /// Download images of a specified episode. - /// - /// - /// The item to cache images. - /// - /// - /// true if images should be downloaded even if they already exists locally, false otherwise. - /// - /// true if an image has been downloaded, false otherwise. - private async Task _Validate([NotNull] Episode episode, bool alwaysDownload) - { - if (episode.Thumb == null) - return false; - - string localPath = await _GetEpisodeThumb(episode); - if (alwaysDownload || !await _files.Exists(localPath)) - return await _DownloadImage(episode.Thumb, localPath, $"The thumbnail of {episode.Slug}"); - return false; - } - - /// - /// Download images of a specified provider. - /// - /// - /// The item to cache images. - /// - /// - /// true if images should be downloaded even if they already exists locally, false otherwise. - /// - /// true if an image has been downloaded, false otherwise. - private async Task _Validate([NotNull] Provider provider, bool alwaysDownload) - { - if (provider.Logo == null) - return false; - - string localPath = await GetLogo(provider); - if (alwaysDownload || !await _files.Exists(localPath)) - return await _DownloadImage(provider.Logo, localPath, $"The logo of {provider.Slug}"); - return false; - } - /// - public Task GetPoster(T item) - where T : IResource + public async Task GetImagePath(T item, int imageID) + where T : IThumbnails { if (item == null) throw new ArgumentNullException(nameof(item)); - return item switch + string imageName = imageID switch { - Show show => Task.FromResult(_files.Combine(_files.GetExtraDirectory(show), "poster.jpg")), - Season season => _GetSeasonPoster(season), - People actor => Task.FromResult(_files.Combine(_options.CurrentValue.PeoplePath, $"{actor.Slug}.jpg")), - _ => throw new NotSupportedException($"The type {typeof(T).Name} does not have a poster.") + Thumbnails.Poster => "poster.jpg", + Thumbnails.Logo => "logo.jpg", + Thumbnails.Thumbnail => "thumbnail.jpg", + _ => $"{imageID}.jpg" }; - } - - /// - /// Retrieve the path of a season's poster. - /// - /// The season to retrieve the poster from. - /// The path of the season's poster. - private async Task _GetSeasonPoster(Season season) - { - if (season.Show == null) - await _library.Value.Load(season, x => x.Show); - return _files.Combine(_files.GetExtraDirectory(season.Show), $"season-{season.SeasonNumber}.jpg"); - } - - /// - public Task GetThumbnail(T item) - where T : IResource - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - return item switch + + // TODO implement a generic way, probably need to rework IFileManager.GetExtraDirectory too. + switch (item) { - Show show => Task.FromResult(_files.Combine(_files.GetExtraDirectory(show), "backdrop.jpg")), - Episode episode => _GetEpisodeThumb(episode), - _ => throw new NotSupportedException($"The type {typeof(T).Name} does not have a thumbnail.") - }; - } - - /// - /// Get the path for an episode's thumbnail. - /// - /// The episode to retrieve the thumbnail from - /// The path of the given episode's thumbnail. - private async Task _GetEpisodeThumb(Episode episode) - { - if (episode.Show == null) - await _library.Value.Load(episode, x => x.Show); - string dir = _files.Combine(_files.GetExtraDirectory(episode.Show), "Thumbnails"); - await _files.CreateDirectory(dir); - return _files.Combine(dir, $"{Path.GetFileNameWithoutExtension(episode.Path)}.jpg"); - } - - /// - public Task GetLogo(T item) - where T : IResource - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - return Task.FromResult(item switch - { - Show show => _files.Combine(_files.GetExtraDirectory(show), "logo.png"), - Provider provider => _files.Combine(_options.CurrentValue.ProviderPath, - $"{provider.Slug}.{provider.LogoExtension}"), - _ => throw new NotSupportedException($"The type {typeof(T).Name} does not have a thumbnail.") - }); + case Show show: + return _files.Combine(_files.GetExtraDirectory(show), imageName); + + case Season season: + if (season.Show == null) + await _library.Value.Load(season, x => x.Show); + return _files.Combine( + _files.GetExtraDirectory(season.Show!), + $"season-{season.SeasonNumber}-{imageName}"); + + case Episode episode: + if (episode.Show == null) + await _library.Value.Load(episode, x => x.Show); + string dir = _files.Combine(_files.GetExtraDirectory(episode.Show!), "Thumbnails"); + await _files.CreateDirectory(dir); + return _files.Combine(dir, $"{Path.GetFileNameWithoutExtension(episode.Path)}-{imageName}"); + + case People actor: + return _files.Combine(_options.CurrentValue.PeoplePath, $"{actor.Slug}-{imageName}"); + + case Provider provider: + return _files.Combine( + _options.CurrentValue.ProviderPath, + $"{provider.Slug}-{imageName[..^4]}{provider.LogoExtension}"); + + default: + throw new NotSupportedException($"The type {typeof(T).Name} is not supported."); + } } } } diff --git a/Kyoo/Tasks/RegisterEpisode.cs b/Kyoo/Tasks/RegisterEpisode.cs index 9df8b1f7..033bfceb 100644 --- a/Kyoo/Tasks/RegisterEpisode.cs +++ b/Kyoo/Tasks/RegisterEpisode.cs @@ -163,7 +163,7 @@ namespace Kyoo.Tasks /// The type of the item /// The existing or filled item. private async Task _RegisterAndFill(T item) - where T : class, IResource + where T : class, IResource, IThumbnails { if (item == null || string.IsNullOrEmpty(item.Slug)) return null; diff --git a/Kyoo/Views/EpisodeApi.cs b/Kyoo/Views/EpisodeApi.cs index b7d83e27..beaf0273 100644 --- a/Kyoo/Views/EpisodeApi.cs +++ b/Kyoo/Views/EpisodeApi.cs @@ -195,7 +195,7 @@ namespace Kyoo.Api try { Episode episode = await _libraryManager.Get(id); - return _files.FileResult(await _thumbnails.GetThumbnail(episode)); + return _files.FileResult(await _thumbnails.GetImagePath(episode, Thumbnails.Thumbnail)); } catch (ItemNotFoundException) { @@ -210,7 +210,7 @@ namespace Kyoo.Api try { Episode episode = await _libraryManager.Get(slug); - return _files.FileResult(await _thumbnails.GetThumbnail(episode)); + return _files.FileResult(await _thumbnails.GetImagePath(episode, Thumbnails.Thumbnail)); } catch (ItemNotFoundException) { diff --git a/Kyoo/Views/PeopleApi.cs b/Kyoo/Views/PeopleApi.cs index bb757a60..194504ff 100644 --- a/Kyoo/Views/PeopleApi.cs +++ b/Kyoo/Views/PeopleApi.cs @@ -94,7 +94,7 @@ namespace Kyoo.Api People people = await _libraryManager.GetOrDefault(id); if (people == null) return NotFound(); - return _files.FileResult(await _thumbs.GetPoster(people)); + return _files.FileResult(await _thumbs.GetImagePath(people, Thumbnails.Poster)); } [HttpGet("{slug}/poster")] @@ -103,7 +103,7 @@ namespace Kyoo.Api People people = await _libraryManager.GetOrDefault(slug); if (people == null) return NotFound(); - return _files.FileResult(await _thumbs.GetPoster(people)); + return _files.FileResult(await _thumbs.GetImagePath(people, Thumbnails.Poster)); } } } \ No newline at end of file diff --git a/Kyoo/Views/ProviderApi.cs b/Kyoo/Views/ProviderApi.cs index 026b79ef..06c06eb8 100644 --- a/Kyoo/Views/ProviderApi.cs +++ b/Kyoo/Views/ProviderApi.cs @@ -36,7 +36,7 @@ namespace Kyoo.Api Provider provider = await _libraryManager.GetOrDefault(id); if (provider == null) return NotFound(); - return _files.FileResult(await _thumbnails.GetLogo(provider)); + return _files.FileResult(await _thumbnails.GetImagePath(provider, Thumbnails.Logo)); } [HttpGet("{slug}/logo")] @@ -45,7 +45,7 @@ namespace Kyoo.Api Provider provider = await _libraryManager.GetOrDefault(slug); if (provider == null) return NotFound(); - return _files.FileResult(await _thumbnails.GetLogo(provider)); + return _files.FileResult(await _thumbnails.GetImagePath(provider, Thumbnails.Logo)); } } } \ No newline at end of file diff --git a/Kyoo/Views/SeasonApi.cs b/Kyoo/Views/SeasonApi.cs index 8d08dd0c..0241de55 100644 --- a/Kyoo/Views/SeasonApi.cs +++ b/Kyoo/Views/SeasonApi.cs @@ -151,7 +151,7 @@ namespace Kyoo.Api if (season == null) return NotFound(); await _libraryManager.Load(season, x => x.Show); - return _files.FileResult(await _thumbs.GetPoster(season)); + return _files.FileResult(await _thumbs.GetImagePath(season, Thumbnails.Poster)); } [HttpGet("{slug}/poster")] @@ -161,7 +161,7 @@ namespace Kyoo.Api if (season == null) return NotFound(); await _libraryManager.Load(season, x => x.Show); - return _files.FileResult(await _thumbs.GetPoster(season)); + return _files.FileResult(await _thumbs.GetImagePath(season, Thumbnails.Poster)); } } } \ No newline at end of file diff --git a/Kyoo/Views/ShowApi.cs b/Kyoo/Views/ShowApi.cs index 04854849..d7162792 100644 --- a/Kyoo/Views/ShowApi.cs +++ b/Kyoo/Views/ShowApi.cs @@ -417,7 +417,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(slug); - return _files.FileResult(await _thumbs.GetPoster(show)); + return _files.FileResult(await _thumbs.GetImagePath(show, Thumbnails.Poster)); } catch (ItemNotFoundException) { @@ -431,7 +431,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(slug); - return _files.FileResult(await _thumbs.GetLogo(show)); + return _files.FileResult(await _thumbs.GetImagePath(show, Thumbnails.Logo)); } catch (ItemNotFoundException) { @@ -446,7 +446,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(slug); - return _files.FileResult(await _thumbs.GetThumbnail(show)); + return _files.FileResult(await _thumbs.GetImagePath(show, Thumbnails.Thumbnail)); } catch (ItemNotFoundException) { From 5f49986d9d6f781a8a8a2b1e3864f0387d0baaf3 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 28 Jul 2021 16:24:03 +0200 Subject: [PATCH 05/34] Updating migrations --- Kyoo.Common/Models/LibraryItem.cs | 19 +- Kyoo.Common/Models/MetadataID.cs | 18 +- Kyoo.Common/Models/Resources/Provider.cs | 5 +- Kyoo.CommonAPI/DatabaseContext.cs | 46 +- ....cs => 20210728141410_Initial.Designer.cs} | 544 ++++++++++-------- ...6_Initial.cs => 20210728141410_Initial.cs} | 248 +++++--- ...cs => 20210728142213_Triggers.Designer.cs} | 544 ++++++++++-------- ...Triggers.cs => 20210728142213_Triggers.cs} | 4 +- .../PostgresContextModelSnapshot.cs | 542 +++++++++-------- Kyoo.Postgresql/PostgresContext.cs | 31 + ....cs => 20210728134952_Initial.Designer.cs} | 436 ++++++++------ ...2_Initial.cs => 20210728134952_Initial.cs} | 248 +++++--- ...cs => 20210728135127_Triggers.Designer.cs} | 436 ++++++++------ ...Triggers.cs => 20210728135127_Triggers.cs} | 4 +- .../Migrations/SqLiteContextModelSnapshot.cs | 434 ++++++++------ Kyoo.SqLite/SqLiteContext.cs | 34 +- Kyoo.Tests/Database/TestSample.cs | 16 +- Kyoo.TheMovieDb/Convertors.cs | 17 +- Kyoo.TheMovieDb/ProviderTmdb.cs | 2 +- Kyoo.TheTvdb/Convertors.cs | 9 +- Kyoo.TheTvdb/ProviderTvdb.cs | 2 +- .../Repositories/CollectionRepository.cs | 2 - .../Repositories/EpisodeRepository.cs | 2 - .../Repositories/PeopleRepository.cs | 2 - .../Repositories/ProviderRepository.cs | 8 +- .../Repositories/SeasonRepository.cs | 2 - .../Repositories/ShowRepository.cs | 2 - .../Repositories/StudioRepository.cs | 1 - 28 files changed, 2124 insertions(+), 1534 deletions(-) rename Kyoo.Postgresql/Migrations/{20210723224326_Initial.Designer.cs => 20210728141410_Initial.Designer.cs} (82%) rename Kyoo.Postgresql/Migrations/{20210723224326_Initial.cs => 20210728141410_Initial.cs} (82%) rename Kyoo.Postgresql/Migrations/{20210723224335_Triggers.Designer.cs => 20210728142213_Triggers.Designer.cs} (82%) rename Kyoo.Postgresql/Migrations/{20210723224335_Triggers.cs => 20210728142213_Triggers.cs} (98%) rename Kyoo.SqLite/Migrations/{20210723224542_Initial.Designer.cs => 20210728134952_Initial.Designer.cs} (84%) rename Kyoo.SqLite/Migrations/{20210723224542_Initial.cs => 20210728134952_Initial.cs} (82%) rename Kyoo.SqLite/Migrations/{20210723224550_Triggers.Designer.cs => 20210728135127_Triggers.Designer.cs} (84%) rename Kyoo.SqLite/Migrations/{20210723224550_Triggers.cs => 20210728135127_Triggers.cs} (98%) diff --git a/Kyoo.Common/Models/LibraryItem.cs b/Kyoo.Common/Models/LibraryItem.cs index 6bc61c2e..dda95343 100644 --- a/Kyoo.Common/Models/LibraryItem.cs +++ b/Kyoo.Common/Models/LibraryItem.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq.Expressions; using Kyoo.Models.Attributes; @@ -18,7 +19,7 @@ namespace Kyoo.Models /// A type union between and . /// This is used to list content put inside a library. /// - public class LibraryItem : IResource + public class LibraryItem : IResource, IThumbnails { /// public int ID { get; set; } @@ -52,13 +53,17 @@ namespace Kyoo.Models /// It can also be null if this is unknown. /// public DateTime? EndAir { get; set; } - + + /// + public Dictionary Images { get; set; } + /// /// The path of this item's poster. /// By default, the http path for this poster is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/{Type:l}/{Slug}/poster")] public string Poster { get; set; } + [SerializeAs("{HOST}/api/{Type:l}/{Slug}/poster")] + public string Poster => Images[Thumbnails.Poster]; /// /// The type of this item (ether a collection, a show or a movie). @@ -84,7 +89,7 @@ namespace Kyoo.Models Status = show.Status; StartAir = show.StartAir; EndAir = show.EndAir; - Poster = show.Poster; + Images = show.Images; Type = show.IsMovie ? ItemType.Movie : ItemType.Show; } @@ -101,7 +106,7 @@ namespace Kyoo.Models Status = Models.Status.Unknown; StartAir = null; EndAir = null; - Poster = collection.Poster; + Images = collection.Images; Type = ItemType.Collection; } @@ -117,7 +122,7 @@ namespace Kyoo.Models Status = x.Status, StartAir = x.StartAir, EndAir = x.EndAir, - Poster= x.Poster, + Images = x.Images, Type = x.IsMovie ? ItemType.Movie : ItemType.Show }; @@ -133,7 +138,7 @@ namespace Kyoo.Models Status = Models.Status.Unknown, StartAir = null, EndAir = null, - Poster = x.Poster, + Images = x.Images, Type = ItemType.Collection }; } diff --git a/Kyoo.Common/Models/MetadataID.cs b/Kyoo.Common/Models/MetadataID.cs index e3bb42a6..cda4ce11 100644 --- a/Kyoo.Common/Models/MetadataID.cs +++ b/Kyoo.Common/Models/MetadataID.cs @@ -13,27 +13,22 @@ namespace Kyoo.Models /// The ID of the resource which possess the metadata. /// [SerializeIgnore] public int ResourceID { get; set; } - - /// - /// The name of the resource type. This is only used internally to discriminate types. - /// - [SerializeIgnore] public string ResourceType { get; set; } - + /// /// The ID of the provider. /// [SerializeIgnore] public int ProviderID { get; set; } - + /// /// The provider that can do something with this ID. /// public Provider Provider { get; set; } - + /// /// The ID of the resource on the external provider. /// public string DataID { get; set; } - + /// /// The URL of the resource on the external provider. /// @@ -44,10 +39,7 @@ namespace Kyoo.Models /// public static Expression> PrimaryKey { - get - { - return x => new {First = x.ResourceID, Second = x.ProviderID, Type = x.ResourceType}; - } + get { return x => new { First = x.ResourceID, Second = x.ProviderID }; } } } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Provider.cs b/Kyoo.Common/Models/Resources/Provider.cs index e931479a..630a2880 100644 --- a/Kyoo.Common/Models/Resources/Provider.cs +++ b/Kyoo.Common/Models/Resources/Provider.cs @@ -65,7 +65,10 @@ namespace Kyoo.Models { Slug = Utility.ToSlug(name); Name = name; - Images[Thumbnails.Logo] = logo; + Images = new Dictionary + { + [Thumbnails.Logo] = logo + }; } } } \ No newline at end of file diff --git a/Kyoo.CommonAPI/DatabaseContext.cs b/Kyoo.CommonAPI/DatabaseContext.cs index d0600da1..6d1fec11 100644 --- a/Kyoo.CommonAPI/DatabaseContext.cs +++ b/Kyoo.CommonAPI/DatabaseContext.cs @@ -60,10 +60,7 @@ namespace Kyoo /// All providers of Kyoo. See . /// public DbSet Providers { get; set; } - /// - /// All metadata ids, not discriminated by type. See . - /// - public DbSet MetadataIDs { get; set; } + /// /// The list of registered users. /// @@ -87,6 +84,25 @@ namespace Kyoo /// public DbSet LibraryItems { get; set; } + /// + /// Get the name of the metadata table of the given type. + /// + /// The type related to the metadata + /// The name of the table containing the metadata. + protected abstract string MetadataName() + where T : IMetadata; + + /// + /// Get all metadataIDs (ExternalIDs) of a given resource. See . + /// + /// The metadata of this type will be returned. + /// A queryable of metadata ids for a type. + public DbSet MetadataIds() + where T : class, IMetadata + { + return Set(MetadataName()); + } + /// /// Get a generic link between two resource types. /// @@ -133,9 +149,12 @@ namespace Kyoo private void _HasMetadata(ModelBuilder modelBuilder) where T : class, IMetadata { - modelBuilder.Entity() - .HasMany(x => x.ExternalIDs) - .WithOne() + modelBuilder.SharedTypeEntity(MetadataName()) + .HasKey(MetadataID.PrimaryKey); + + modelBuilder.SharedTypeEntity(MetadataName()) + .HasOne() + .WithMany(x => x.ExternalIDs) .HasForeignKey(x => x.ResourceID) .OnDelete(DeleteBehavior.Cascade); } @@ -149,6 +168,9 @@ namespace Kyoo { base.OnModelCreating(modelBuilder); + modelBuilder.Entity() + .Ignore(x => x.ForPeople); + modelBuilder.Entity() .HasMany(x => x.Seasons) .WithOne(x => x.Show) @@ -243,14 +265,6 @@ namespace Kyoo .WithMany(x => x.ShowLinks), y => y.HasKey(Link.PrimaryKey)); - modelBuilder.Entity() - .HasKey(MetadataID.PrimaryKey); - modelBuilder.Entity() - .Property(x => x.ResourceType) - .IsRequired(); - modelBuilder.Entity() - .HasDiscriminator(x => x.ResourceType); - _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); @@ -259,7 +273,7 @@ namespace Kyoo _HasMetadata(modelBuilder); modelBuilder.Entity() - .HasKey(x => new {First = x.FirstID, Second = x.SecondID}); + .HasKey(x => new { First = x.FirstID, Second = x.SecondID }); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); diff --git a/Kyoo.Postgresql/Migrations/20210723224326_Initial.Designer.cs b/Kyoo.Postgresql/Migrations/20210728141410_Initial.Designer.cs similarity index 82% rename from Kyoo.Postgresql/Migrations/20210723224326_Initial.Designer.cs rename to Kyoo.Postgresql/Migrations/20210728141410_Initial.Designer.cs index 3016a040..8722ada5 100644 --- a/Kyoo.Postgresql/Migrations/20210723224326_Initial.Designer.cs +++ b/Kyoo.Postgresql/Migrations/20210728141410_Initial.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Postgresql.Migrations { [DbContext(typeof(PostgresContext))] - [Migration("20210723224326_Initial")] + [Migration("20210728141410_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -34,6 +34,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Name") .HasColumnType("text") .HasColumnName("name"); @@ -42,10 +46,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -77,6 +77,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("integer") .HasColumnName("episode_number"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); @@ -106,10 +110,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("slug"); - b.Property("Thumb") - .HasColumnType("text") - .HasColumnName("thumb"); - b.Property("Title") .HasColumnType("text") .HasColumnName("title"); @@ -200,14 +200,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp without time zone") .HasColumnName("end_air"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .HasColumnType("text") .HasColumnName("slug"); @@ -348,114 +348,6 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("link_user_show"); }); - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_episode"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_episode_second_id"); - - b.ToTable("metadata_id_episode"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_people"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_people_second_id"); - - b.ToTable("metadata_id_people"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_season"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_season_second_id"); - - b.ToTable("metadata_id_season"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_show_second_id"); - - b.ToTable("metadata_id_show"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") @@ -464,14 +356,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Name") .HasColumnType("text") .HasColumnName("name"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -495,10 +387,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("ForPeople") - .HasColumnType("boolean") - .HasColumnName("for_people"); - b.Property("PeopleID") .HasColumnType("integer") .HasColumnName("people_id"); @@ -535,9 +423,9 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("Logo") - .HasColumnType("text") - .HasColumnName("logo"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); b.Property("LogoExtension") .HasColumnType("text") @@ -574,14 +462,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp without time zone") .HasColumnName("end_date"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("SeasonNumber") .HasColumnType("integer") .HasColumnName("season_number"); @@ -629,22 +517,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text[]") .HasColumnName("aliases"); - b.Property("Backdrop") - .HasColumnType("text") - .HasColumnName("backdrop"); - b.Property("EndAir") .HasColumnType("timestamp without time zone") .HasColumnName("end_air"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("IsMovie") .HasColumnType("boolean") .HasColumnName("is_movie"); - b.Property("Logo") - .HasColumnType("text") - .HasColumnName("logo"); - b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); @@ -653,10 +537,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("path"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -678,10 +558,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("title"); - b.Property("TrailerUrl") - .HasColumnType("text") - .HasColumnName("trailer_url"); - b.HasKey("ID") .HasName("pk_shows"); @@ -855,6 +731,168 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("watched_episodes"); }); + modelBuilder.Entity("collection_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_collection_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_collection_metadata_id_provider_id"); + + b.ToTable("collection_metadata_id"); + }); + + modelBuilder.Entity("episode_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_episode_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_episode_metadata_id_provider_id"); + + b.ToTable("episode_metadata_id"); + }); + + modelBuilder.Entity("people_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_people_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_people_metadata_id_provider_id"); + + b.ToTable("people_metadata_id"); + }); + + modelBuilder.Entity("season_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_season_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_season_metadata_id_provider_id"); + + b.ToTable("season_metadata_id"); + }); + + modelBuilder.Entity("show_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_show_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_show_metadata_id_provider_id"); + + b.ToTable("show_metadata_id"); + }); + + modelBuilder.Entity("studio_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_studio_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_studio_metadata_id_provider_id"); + + b.ToTable("studio_metadata_id"); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -1001,90 +1039,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Second"); }); - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Episode", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_episode_episodes_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_episode_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.People", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_people_people_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_people_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Season", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_season_seasons_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_season_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_show_shows_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_show_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -1162,8 +1116,124 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Second"); }); + modelBuilder.Entity("collection_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_collection_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Collection", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_collection_metadata_id_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("episode_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_episode_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Episode", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_episode_metadata_id_episodes_episode_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("people_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_people_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.People", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_people_metadata_id_people_people_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("season_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_season_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Season", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_season_metadata_id_seasons_season_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("show_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_show_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_show_metadata_id_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("studio_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_studio_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Studio", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_studio_metadata_id_studios_studio_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { + b.Navigation("ExternalIDs"); + b.Navigation("LibraryLinks"); b.Navigation("ShowLinks"); @@ -1228,6 +1298,8 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.Studio", b => { + b.Navigation("ExternalIDs"); + b.Navigation("Shows"); }); diff --git a/Kyoo.Postgresql/Migrations/20210723224326_Initial.cs b/Kyoo.Postgresql/Migrations/20210728141410_Initial.cs similarity index 82% rename from Kyoo.Postgresql/Migrations/20210723224326_Initial.cs rename to Kyoo.Postgresql/Migrations/20210728141410_Initial.cs index 2ba22c6a..46c09222 100644 --- a/Kyoo.Postgresql/Migrations/20210723224326_Initial.cs +++ b/Kyoo.Postgresql/Migrations/20210728141410_Initial.cs @@ -23,7 +23,7 @@ namespace Kyoo.Postgresql.Migrations .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), slug = table.Column(type: "text", nullable: false), name = table.Column(type: "text", nullable: true), - poster = table.Column(type: "text", nullable: true), + images = table.Column>(type: "jsonb", nullable: true), overview = table.Column(type: "text", nullable: true) }, constraints: table => @@ -68,7 +68,7 @@ namespace Kyoo.Postgresql.Migrations .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), slug = table.Column(type: "text", nullable: false), name = table.Column(type: "text", nullable: true), - poster = table.Column(type: "text", nullable: true) + images = table.Column>(type: "jsonb", nullable: true) }, constraints: table => { @@ -83,7 +83,7 @@ namespace Kyoo.Postgresql.Migrations .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), slug = table.Column(type: "text", nullable: false), name = table.Column(type: "text", nullable: true), - logo = table.Column(type: "text", nullable: true), + images = table.Column>(type: "jsonb", nullable: true), logo_extension = table.Column(type: "text", nullable: true) }, constraints: table => @@ -147,6 +147,32 @@ namespace Kyoo.Postgresql.Migrations 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 @@ -172,26 +198,26 @@ namespace Kyoo.Postgresql.Migrations }); migrationBuilder.CreateTable( - name: "metadata_id_people", + name: "people_metadata_id", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false), + 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_metadata_id_people", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_people_metadata_id", x => new { x.resource_id, x.provider_id }); table.ForeignKey( - name: "fk_metadata_id_people_people_first_id", - column: x => x.first_id, + name: "fk_people_metadata_id_people_people_id", + column: x => x.resource_id, principalTable: "people", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_metadata_id_people_providers_second_id", - column: x => x.second_id, + name: "fk_people_metadata_id_providers_provider_id", + column: x => x.provider_id, principalTable: "providers", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -209,12 +235,9 @@ namespace Kyoo.Postgresql.Migrations path = table.Column(type: "text", nullable: true), overview = table.Column(type: "text", nullable: true), status = table.Column(type: "status", nullable: false), - trailer_url = table.Column(type: "text", nullable: true), start_air = table.Column(type: "timestamp without time zone", nullable: true), end_air = table.Column(type: "timestamp without time zone", nullable: true), - poster = table.Column(type: "text", nullable: true), - logo = table.Column(type: "text", nullable: true), - backdrop = table.Column(type: "text", 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) }, @@ -229,6 +252,32 @@ namespace Kyoo.Postgresql.Migrations 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 @@ -325,39 +374,12 @@ namespace Kyoo.Postgresql.Migrations onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "metadata_id_show", - columns: table => new - { - first_id = table.Column(type: "integer", nullable: false), - second_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_metadata_id_show", x => new { x.first_id, x.second_id }); - table.ForeignKey( - name: "fk_metadata_id_show_providers_second_id", - column: x => x.second_id, - principalTable: "providers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_metadata_id_show_shows_first_id", - column: x => x.first_id, - principalTable: "shows", - 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), - for_people = table.Column(type: "boolean", nullable: false), people_id = table.Column(type: "integer", nullable: false), show_id = table.Column(type: "integer", nullable: false), type = table.Column(type: "text", nullable: true), @@ -393,7 +415,7 @@ namespace Kyoo.Postgresql.Migrations 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), - poster = table.Column(type: "text", nullable: true) + images = table.Column>(type: "jsonb", nullable: true) }, constraints: table => { @@ -406,6 +428,32 @@ namespace Kyoo.Postgresql.Migrations 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 @@ -419,7 +467,7 @@ namespace Kyoo.Postgresql.Migrations episode_number = table.Column(type: "integer", nullable: true), absolute_number = table.Column(type: "integer", nullable: true), path = table.Column(type: "text", nullable: true), - thumb = 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) @@ -442,52 +490,52 @@ namespace Kyoo.Postgresql.Migrations }); migrationBuilder.CreateTable( - name: "metadata_id_season", + name: "season_metadata_id", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false), + 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_metadata_id_season", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_season_metadata_id", x => new { x.resource_id, x.provider_id }); table.ForeignKey( - name: "fk_metadata_id_season_providers_second_id", - column: x => x.second_id, + name: "fk_season_metadata_id_providers_provider_id", + column: x => x.provider_id, principalTable: "providers", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_metadata_id_season_seasons_first_id", - column: x => x.first_id, + name: "fk_season_metadata_id_seasons_season_id", + column: x => x.resource_id, principalTable: "seasons", principalColumn: "id", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "metadata_id_episode", + name: "episode_metadata_id", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false), + 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_metadata_id_episode", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_episode_metadata_id", x => new { x.resource_id, x.provider_id }); table.ForeignKey( - name: "fk_metadata_id_episode_episodes_first_id", - column: x => x.first_id, + name: "fk_episode_metadata_id_episodes_episode_id", + column: x => x.resource_id, principalTable: "episodes", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_metadata_id_episode_providers_second_id", - column: x => x.second_id, + name: "fk_episode_metadata_id_providers_provider_id", + column: x => x.provider_id, principalTable: "providers", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -547,12 +595,22 @@ namespace Kyoo.Postgresql.Migrations 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", @@ -612,32 +670,17 @@ namespace Kyoo.Postgresql.Migrations table: "link_user_show", column: "second_id"); - migrationBuilder.CreateIndex( - name: "ix_metadata_id_episode_second_id", - table: "metadata_id_episode", - column: "second_id"); - - migrationBuilder.CreateIndex( - name: "ix_metadata_id_people_second_id", - table: "metadata_id_people", - column: "second_id"); - - migrationBuilder.CreateIndex( - name: "ix_metadata_id_season_second_id", - table: "metadata_id_season", - column: "second_id"); - - migrationBuilder.CreateIndex( - name: "ix_metadata_id_show_second_id", - table: "metadata_id_show", - column: "second_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", @@ -654,6 +697,11 @@ namespace Kyoo.Postgresql.Migrations 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", @@ -666,6 +714,11 @@ namespace Kyoo.Postgresql.Migrations 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", @@ -677,6 +730,11 @@ namespace Kyoo.Postgresql.Migrations 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", @@ -709,6 +767,12 @@ namespace Kyoo.Postgresql.Migrations protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DropTable( + name: "collection_metadata_id"); + + migrationBuilder.DropTable( + name: "episode_metadata_id"); + migrationBuilder.DropTable( name: "link_collection_show"); @@ -728,20 +792,20 @@ namespace Kyoo.Postgresql.Migrations name: "link_user_show"); migrationBuilder.DropTable( - name: "metadata_id_episode"); - - migrationBuilder.DropTable( - name: "metadata_id_people"); - - migrationBuilder.DropTable( - name: "metadata_id_season"); - - migrationBuilder.DropTable( - name: "metadata_id_show"); + name: "people_metadata_id"); migrationBuilder.DropTable( name: "people_roles"); + migrationBuilder.DropTable( + name: "season_metadata_id"); + + migrationBuilder.DropTable( + name: "show_metadata_id"); + + migrationBuilder.DropTable( + name: "studio_metadata_id"); + migrationBuilder.DropTable( name: "tracks"); @@ -758,10 +822,10 @@ namespace Kyoo.Postgresql.Migrations name: "genres"); migrationBuilder.DropTable( - name: "providers"); + name: "people"); migrationBuilder.DropTable( - name: "people"); + name: "providers"); migrationBuilder.DropTable( name: "episodes"); diff --git a/Kyoo.Postgresql/Migrations/20210723224335_Triggers.Designer.cs b/Kyoo.Postgresql/Migrations/20210728142213_Triggers.Designer.cs similarity index 82% rename from Kyoo.Postgresql/Migrations/20210723224335_Triggers.Designer.cs rename to Kyoo.Postgresql/Migrations/20210728142213_Triggers.Designer.cs index d377a09f..708cd23b 100644 --- a/Kyoo.Postgresql/Migrations/20210723224335_Triggers.Designer.cs +++ b/Kyoo.Postgresql/Migrations/20210728142213_Triggers.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Postgresql.Migrations { [DbContext(typeof(PostgresContext))] - [Migration("20210723224335_Triggers")] + [Migration("20210728142213_Triggers")] partial class Triggers { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -34,6 +34,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Name") .HasColumnType("text") .HasColumnName("name"); @@ -42,10 +46,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -77,6 +77,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("integer") .HasColumnName("episode_number"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); @@ -106,10 +110,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("slug"); - b.Property("Thumb") - .HasColumnType("text") - .HasColumnName("thumb"); - b.Property("Title") .HasColumnType("text") .HasColumnName("title"); @@ -200,14 +200,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp without time zone") .HasColumnName("end_air"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .HasColumnType("text") .HasColumnName("slug"); @@ -348,114 +348,6 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("link_user_show"); }); - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_episode"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_episode_second_id"); - - b.ToTable("metadata_id_episode"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_people"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_people_second_id"); - - b.ToTable("metadata_id_people"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_season"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_season_second_id"); - - b.ToTable("metadata_id_season"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_show_second_id"); - - b.ToTable("metadata_id_show"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") @@ -464,14 +356,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Name") .HasColumnType("text") .HasColumnName("name"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -495,10 +387,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("ForPeople") - .HasColumnType("boolean") - .HasColumnName("for_people"); - b.Property("PeopleID") .HasColumnType("integer") .HasColumnName("people_id"); @@ -535,9 +423,9 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("Logo") - .HasColumnType("text") - .HasColumnName("logo"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); b.Property("LogoExtension") .HasColumnType("text") @@ -574,14 +462,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp without time zone") .HasColumnName("end_date"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("SeasonNumber") .HasColumnType("integer") .HasColumnName("season_number"); @@ -629,22 +517,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text[]") .HasColumnName("aliases"); - b.Property("Backdrop") - .HasColumnType("text") - .HasColumnName("backdrop"); - b.Property("EndAir") .HasColumnType("timestamp without time zone") .HasColumnName("end_air"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("IsMovie") .HasColumnType("boolean") .HasColumnName("is_movie"); - b.Property("Logo") - .HasColumnType("text") - .HasColumnName("logo"); - b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); @@ -653,10 +537,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("path"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -678,10 +558,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("title"); - b.Property("TrailerUrl") - .HasColumnType("text") - .HasColumnName("trailer_url"); - b.HasKey("ID") .HasName("pk_shows"); @@ -855,6 +731,168 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("watched_episodes"); }); + modelBuilder.Entity("collection_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_collection_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_collection_metadata_id_provider_id"); + + b.ToTable("collection_metadata_id"); + }); + + modelBuilder.Entity("episode_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_episode_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_episode_metadata_id_provider_id"); + + b.ToTable("episode_metadata_id"); + }); + + modelBuilder.Entity("people_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_people_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_people_metadata_id_provider_id"); + + b.ToTable("people_metadata_id"); + }); + + modelBuilder.Entity("season_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_season_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_season_metadata_id_provider_id"); + + b.ToTable("season_metadata_id"); + }); + + modelBuilder.Entity("show_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_show_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_show_metadata_id_provider_id"); + + b.ToTable("show_metadata_id"); + }); + + modelBuilder.Entity("studio_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_studio_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_studio_metadata_id_provider_id"); + + b.ToTable("studio_metadata_id"); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -1001,90 +1039,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Second"); }); - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Episode", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_episode_episodes_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_episode_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.People", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_people_people_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_people_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Season", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_season_seasons_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_season_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_show_shows_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_show_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -1162,8 +1116,124 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Second"); }); + modelBuilder.Entity("collection_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_collection_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Collection", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_collection_metadata_id_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("episode_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_episode_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Episode", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_episode_metadata_id_episodes_episode_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("people_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_people_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.People", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_people_metadata_id_people_people_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("season_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_season_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Season", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_season_metadata_id_seasons_season_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("show_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_show_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_show_metadata_id_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("studio_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_studio_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Studio", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_studio_metadata_id_studios_studio_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { + b.Navigation("ExternalIDs"); + b.Navigation("LibraryLinks"); b.Navigation("ShowLinks"); @@ -1228,6 +1298,8 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.Studio", b => { + b.Navigation("ExternalIDs"); + b.Navigation("Shows"); }); diff --git a/Kyoo.Postgresql/Migrations/20210723224335_Triggers.cs b/Kyoo.Postgresql/Migrations/20210728142213_Triggers.cs similarity index 98% rename from Kyoo.Postgresql/Migrations/20210723224335_Triggers.cs rename to Kyoo.Postgresql/Migrations/20210728142213_Triggers.cs index a773e02b..d1933171 100644 --- a/Kyoo.Postgresql/Migrations/20210723224335_Triggers.cs +++ b/Kyoo.Postgresql/Migrations/20210728142213_Triggers.cs @@ -141,7 +141,7 @@ namespace Kyoo.Postgresql.Migrations // language=PostgreSQL migrationBuilder.Sql(@" CREATE VIEW library_items AS - SELECT s.id, s.slug, s.title, s.overview, s.status, s.start_air, s.end_air, s.poster, CASE + SELECT s.id, s.slug, s.title, s.overview, s.status, s.start_air, s.end_air, s.images, CASE WHEN s.is_movie THEN 'movie'::item_type ELSE 'show'::item_type END AS type @@ -153,7 +153,7 @@ namespace Kyoo.Postgresql.Migrations WHERE s.id = l.second_id)) UNION ALL SELECT -c0.id, c0.slug, c0.name AS title, c0.overview, 'unknown'::status AS status, - NULL AS start_air, NULL AS end_air, c0.poster, 'collection'::item_type AS type + NULL AS start_air, NULL AS end_air, c0.images, 'collection'::item_type AS type FROM collections AS c0"); } diff --git a/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs b/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs index f2d55f24..f7db32a7 100644 --- a/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs +++ b/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs @@ -32,6 +32,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Name") .HasColumnType("text") .HasColumnName("name"); @@ -40,10 +44,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -75,6 +75,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("integer") .HasColumnName("episode_number"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); @@ -104,10 +108,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("slug"); - b.Property("Thumb") - .HasColumnType("text") - .HasColumnName("thumb"); - b.Property("Title") .HasColumnType("text") .HasColumnName("title"); @@ -198,14 +198,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp without time zone") .HasColumnName("end_air"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .HasColumnType("text") .HasColumnName("slug"); @@ -346,114 +346,6 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("link_user_show"); }); - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_episode"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_episode_second_id"); - - b.ToTable("metadata_id_episode"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_people"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_people_second_id"); - - b.ToTable("metadata_id_people"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_season"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_season_second_id"); - - b.ToTable("metadata_id_season"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_show_second_id"); - - b.ToTable("metadata_id_show"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") @@ -462,14 +354,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Name") .HasColumnType("text") .HasColumnName("name"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -493,10 +385,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("ForPeople") - .HasColumnType("boolean") - .HasColumnName("for_people"); - b.Property("PeopleID") .HasColumnType("integer") .HasColumnName("people_id"); @@ -533,9 +421,9 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("Logo") - .HasColumnType("text") - .HasColumnName("logo"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); b.Property("LogoExtension") .HasColumnType("text") @@ -572,14 +460,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp without time zone") .HasColumnName("end_date"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("SeasonNumber") .HasColumnType("integer") .HasColumnName("season_number"); @@ -627,22 +515,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text[]") .HasColumnName("aliases"); - b.Property("Backdrop") - .HasColumnType("text") - .HasColumnName("backdrop"); - b.Property("EndAir") .HasColumnType("timestamp without time zone") .HasColumnName("end_air"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("IsMovie") .HasColumnType("boolean") .HasColumnName("is_movie"); - b.Property("Logo") - .HasColumnType("text") - .HasColumnName("logo"); - b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); @@ -651,10 +535,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("path"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -676,10 +556,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("title"); - b.Property("TrailerUrl") - .HasColumnType("text") - .HasColumnName("trailer_url"); - b.HasKey("ID") .HasName("pk_shows"); @@ -853,6 +729,168 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("watched_episodes"); }); + modelBuilder.Entity("collection_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_collection_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_collection_metadata_id_provider_id"); + + b.ToTable("collection_metadata_id"); + }); + + modelBuilder.Entity("episode_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_episode_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_episode_metadata_id_provider_id"); + + b.ToTable("episode_metadata_id"); + }); + + modelBuilder.Entity("people_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_people_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_people_metadata_id_provider_id"); + + b.ToTable("people_metadata_id"); + }); + + modelBuilder.Entity("season_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_season_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_season_metadata_id_provider_id"); + + b.ToTable("season_metadata_id"); + }); + + modelBuilder.Entity("show_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_show_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_show_metadata_id_provider_id"); + + b.ToTable("show_metadata_id"); + }); + + modelBuilder.Entity("studio_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_studio_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_studio_metadata_id_provider_id"); + + b.ToTable("studio_metadata_id"); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -999,90 +1037,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Second"); }); - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Episode", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_episode_episodes_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_episode_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.People", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_people_people_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_people_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Season", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_season_seasons_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_season_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_show_shows_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_show_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -1160,8 +1114,124 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Second"); }); + modelBuilder.Entity("collection_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_collection_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Collection", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_collection_metadata_id_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("episode_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_episode_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Episode", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_episode_metadata_id_episodes_episode_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("people_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_people_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.People", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_people_metadata_id_people_people_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("season_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_season_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Season", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_season_metadata_id_seasons_season_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("show_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_show_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_show_metadata_id_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("studio_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_studio_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Studio", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_studio_metadata_id_studios_studio_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { + b.Navigation("ExternalIDs"); + b.Navigation("LibraryLinks"); b.Navigation("ShowLinks"); @@ -1226,6 +1296,8 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.Studio", b => { + b.Navigation("ExternalIDs"); + b.Navigation("Shows"); }); diff --git a/Kyoo.Postgresql/PostgresContext.cs b/Kyoo.Postgresql/PostgresContext.cs index b0e534ed..09f460bc 100644 --- a/Kyoo.Postgresql/PostgresContext.cs +++ b/Kyoo.Postgresql/PostgresContext.cs @@ -1,6 +1,8 @@ using System; +using System.Globalization; using System.Linq.Expressions; using System.Reflection; +using EFCore.NamingConventions.Internal; using Kyoo.Models; using Microsoft.EntityFrameworkCore; using Npgsql; @@ -99,9 +101,38 @@ namespace Kyoo.Postgresql .Property(x => x.ExtraData) .HasColumnType("jsonb"); + modelBuilder.Entity() + .Property(x => x.Images) + .HasColumnType("jsonb"); + modelBuilder.Entity() + .Property(x => x.Images) + .HasColumnType("jsonb"); + modelBuilder.Entity() + .Property(x => x.Images) + .HasColumnType("jsonb"); + modelBuilder.Entity() + .Property(x => x.Images) + .HasColumnType("jsonb"); + modelBuilder.Entity() + .Property(x => x.Images) + .HasColumnType("jsonb"); + modelBuilder.Entity() + .Property(x => x.Images) + .HasColumnType("jsonb"); + modelBuilder.Entity() + .Property(x => x.Images) + .HasColumnType("jsonb"); + base.OnModelCreating(modelBuilder); } + /// + protected override string MetadataName() + { + SnakeCaseNameRewriter rewriter = new(CultureInfo.InvariantCulture); + return rewriter.RewriteName(typeof(T).Name + nameof(MetadataID)); + } + /// protected override bool IsDuplicateException(Exception ex) { diff --git a/Kyoo.SqLite/Migrations/20210723224542_Initial.Designer.cs b/Kyoo.SqLite/Migrations/20210728134952_Initial.Designer.cs similarity index 84% rename from Kyoo.SqLite/Migrations/20210723224542_Initial.Designer.cs rename to Kyoo.SqLite/Migrations/20210728134952_Initial.Designer.cs index eca501eb..3f4290b7 100644 --- a/Kyoo.SqLite/Migrations/20210723224542_Initial.Designer.cs +++ b/Kyoo.SqLite/Migrations/20210728134952_Initial.Designer.cs @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Kyoo.SqLite.Migrations { [DbContext(typeof(SqLiteContext))] - [Migration("20210723224542_Initial")] + [Migration("20210728134952_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -18,21 +18,63 @@ namespace Kyoo.SqLite.Migrations modelBuilder .HasAnnotation("ProductVersion", "5.0.8"); + modelBuilder.Entity("CollectionMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("CollectionMetadataID"); + }); + + modelBuilder.Entity("EpisodeMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("EpisodeMetadataID"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Name") .HasColumnType("TEXT"); b.Property("Overview") .HasColumnType("TEXT"); - b.Property("Poster") - .HasColumnType("TEXT"); - b.Property("Slug") .IsRequired() .HasColumnType("TEXT"); @@ -57,6 +99,9 @@ namespace Kyoo.SqLite.Migrations b.Property("EpisodeNumber") .HasColumnType("INTEGER"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Overview") .HasColumnType("TEXT"); @@ -79,9 +124,6 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAddOrUpdate() .HasColumnType("TEXT"); - b.Property("Thumb") - .HasColumnType("TEXT"); - b.Property("Title") .HasColumnType("TEXT"); @@ -152,10 +194,10 @@ namespace Kyoo.SqLite.Migrations b.Property("EndAir") .HasColumnType("TEXT"); - b.Property("Overview") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Overview") .HasColumnType("TEXT"); b.Property("Slug") @@ -268,100 +310,16 @@ namespace Kyoo.SqLite.Migrations b.ToTable("Link"); }); - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Name") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Name") .HasColumnType("TEXT"); b.Property("Slug") @@ -382,9 +340,6 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("ForPeople") - .HasColumnType("INTEGER"); - b.Property("PeopleID") .HasColumnType("INTEGER"); @@ -412,7 +367,7 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Logo") + b.Property("Images") .HasColumnType("TEXT"); b.Property("LogoExtension") @@ -442,10 +397,10 @@ namespace Kyoo.SqLite.Migrations b.Property("EndDate") .HasColumnType("TEXT"); - b.Property("Overview") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Overview") .HasColumnType("TEXT"); b.Property("SeasonNumber") @@ -484,27 +439,21 @@ namespace Kyoo.SqLite.Migrations b.Property("Aliases") .HasColumnType("TEXT"); - b.Property("Backdrop") + b.Property("EndAir") .HasColumnType("TEXT"); - b.Property("EndAir") + b.Property("Images") .HasColumnType("TEXT"); b.Property("IsMovie") .HasColumnType("INTEGER"); - b.Property("Logo") - .HasColumnType("TEXT"); - b.Property("Overview") .HasColumnType("TEXT"); b.Property("Path") .HasColumnType("TEXT"); - b.Property("Poster") - .HasColumnType("TEXT"); - b.Property("Slug") .IsRequired() .HasColumnType("TEXT"); @@ -521,9 +470,6 @@ namespace Kyoo.SqLite.Migrations b.Property("Title") .HasColumnType("TEXT"); - b.Property("TrailerUrl") - .HasColumnType("TEXT"); - b.HasKey("ID"); b.HasIndex("Slug") @@ -657,6 +603,124 @@ namespace Kyoo.SqLite.Migrations b.ToTable("WatchedEpisodes"); }); + modelBuilder.Entity("PeopleMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("PeopleMetadataID"); + }); + + modelBuilder.Entity("SeasonMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("SeasonMetadataID"); + }); + + modelBuilder.Entity("ShowMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("ShowMetadataID"); + }); + + modelBuilder.Entity("StudioMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("StudioMetadataID"); + }); + + modelBuilder.Entity("CollectionMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Collection", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("EpisodeMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Episode", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -789,82 +853,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Second"); }); - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Episode", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.People", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Season", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -935,8 +923,78 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Second"); }); + modelBuilder.Entity("PeopleMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.People", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("SeasonMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Season", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("ShowMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("StudioMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Studio", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { + b.Navigation("ExternalIDs"); + b.Navigation("LibraryLinks"); b.Navigation("ShowLinks"); @@ -1001,6 +1059,8 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.Studio", b => { + b.Navigation("ExternalIDs"); + b.Navigation("Shows"); }); diff --git a/Kyoo.SqLite/Migrations/20210723224542_Initial.cs b/Kyoo.SqLite/Migrations/20210728134952_Initial.cs similarity index 82% rename from Kyoo.SqLite/Migrations/20210723224542_Initial.cs rename to Kyoo.SqLite/Migrations/20210728134952_Initial.cs index 1fa93860..b0567f79 100644 --- a/Kyoo.SqLite/Migrations/20210723224542_Initial.cs +++ b/Kyoo.SqLite/Migrations/20210728134952_Initial.cs @@ -15,7 +15,7 @@ namespace Kyoo.SqLite.Migrations .Annotation("Sqlite:Autoincrement", true), Slug = table.Column(type: "TEXT", nullable: false), Name = table.Column(type: "TEXT", nullable: true), - Poster = table.Column(type: "TEXT", nullable: true), + Images = table.Column(type: "TEXT", nullable: true), Overview = table.Column(type: "TEXT", nullable: true) }, constraints: table => @@ -60,7 +60,7 @@ namespace Kyoo.SqLite.Migrations .Annotation("Sqlite:Autoincrement", true), Slug = table.Column(type: "TEXT", nullable: false), Name = table.Column(type: "TEXT", nullable: true), - Poster = table.Column(type: "TEXT", nullable: true) + Images = table.Column(type: "TEXT", nullable: true) }, constraints: table => { @@ -75,7 +75,7 @@ namespace Kyoo.SqLite.Migrations .Annotation("Sqlite:Autoincrement", true), Slug = table.Column(type: "TEXT", nullable: false), Name = table.Column(type: "TEXT", nullable: true), - Logo = table.Column(type: "TEXT", nullable: true), + Images = table.Column(type: "TEXT", nullable: true), LogoExtension = table.Column(type: "TEXT", nullable: true) }, constraints: table => @@ -139,6 +139,32 @@ namespace Kyoo.SqLite.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "CollectionMetadataID", + columns: table => new + { + ResourceID = table.Column(type: "INTEGER", nullable: false), + ProviderID = table.Column(type: "INTEGER", nullable: false), + DataID = table.Column(type: "TEXT", nullable: true), + Link = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CollectionMetadataID", x => new { x.ResourceID, x.ProviderID }); + table.ForeignKey( + name: "FK_CollectionMetadataID_Collections_ResourceID", + column: x => x.ResourceID, + principalTable: "Collections", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CollectionMetadataID_Providers_ProviderID", + column: x => x.ProviderID, + principalTable: "Providers", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "Link", columns: table => new @@ -164,26 +190,26 @@ namespace Kyoo.SqLite.Migrations }); migrationBuilder.CreateTable( - name: "MetadataID", + name: "PeopleMetadataID", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false), + ResourceID = table.Column(type: "INTEGER", nullable: false), + ProviderID = table.Column(type: "INTEGER", nullable: false), DataID = table.Column(type: "TEXT", nullable: true), Link = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_MetadataID", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_PeopleMetadataID", x => new { x.ResourceID, x.ProviderID }); table.ForeignKey( - name: "FK_MetadataID_People_FirstID", - column: x => x.FirstID, + name: "FK_PeopleMetadataID_People_ResourceID", + column: x => x.ResourceID, principalTable: "People", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_MetadataID_Providers_SecondID", - column: x => x.SecondID, + name: "FK_PeopleMetadataID_Providers_ProviderID", + column: x => x.ProviderID, principalTable: "Providers", principalColumn: "ID", onDelete: ReferentialAction.Cascade); @@ -201,12 +227,9 @@ namespace Kyoo.SqLite.Migrations Path = table.Column(type: "TEXT", nullable: true), Overview = table.Column(type: "TEXT", nullable: true), Status = table.Column(type: "INTEGER", nullable: false), - TrailerUrl = table.Column(type: "TEXT", nullable: true), StartAir = table.Column(type: "TEXT", nullable: true), EndAir = table.Column(type: "TEXT", nullable: true), - Poster = table.Column(type: "TEXT", nullable: true), - Logo = table.Column(type: "TEXT", nullable: true), - Backdrop = table.Column(type: "TEXT", nullable: true), + Images = table.Column(type: "TEXT", nullable: true), IsMovie = table.Column(type: "INTEGER", nullable: false), StudioID = table.Column(type: "INTEGER", nullable: true) }, @@ -221,6 +244,32 @@ namespace Kyoo.SqLite.Migrations onDelete: ReferentialAction.SetNull); }); + migrationBuilder.CreateTable( + name: "StudioMetadataID", + columns: table => new + { + ResourceID = table.Column(type: "INTEGER", nullable: false), + ProviderID = table.Column(type: "INTEGER", nullable: false), + DataID = table.Column(type: "TEXT", nullable: true), + Link = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_StudioMetadataID", x => new { x.ResourceID, x.ProviderID }); + table.ForeignKey( + name: "FK_StudioMetadataID_Providers_ProviderID", + column: x => x.ProviderID, + principalTable: "Providers", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_StudioMetadataID_Studios_ResourceID", + column: x => x.ResourceID, + principalTable: "Studios", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "Link", columns: table => new @@ -317,39 +366,12 @@ namespace Kyoo.SqLite.Migrations onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "MetadataID", - columns: table => new - { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false), - DataID = table.Column(type: "TEXT", nullable: true), - Link = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_MetadataID", x => new { x.FirstID, x.SecondID }); - table.ForeignKey( - name: "FK_MetadataID_Providers_SecondID", - column: x => x.SecondID, - principalTable: "Providers", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_MetadataID_Shows_FirstID", - column: x => x.FirstID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - migrationBuilder.CreateTable( name: "PeopleRoles", columns: table => new { ID = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - ForPeople = table.Column(type: "INTEGER", nullable: false), PeopleID = table.Column(type: "INTEGER", nullable: false), ShowID = table.Column(type: "INTEGER", nullable: false), Type = table.Column(type: "TEXT", nullable: true), @@ -385,7 +407,7 @@ namespace Kyoo.SqLite.Migrations Overview = table.Column(type: "TEXT", nullable: true), StartDate = table.Column(type: "TEXT", nullable: true), EndDate = table.Column(type: "TEXT", nullable: true), - Poster = table.Column(type: "TEXT", nullable: true) + Images = table.Column(type: "TEXT", nullable: true) }, constraints: table => { @@ -398,6 +420,32 @@ namespace Kyoo.SqLite.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "ShowMetadataID", + columns: table => new + { + ResourceID = table.Column(type: "INTEGER", nullable: false), + ProviderID = table.Column(type: "INTEGER", nullable: false), + DataID = table.Column(type: "TEXT", nullable: true), + Link = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ShowMetadataID", x => new { x.ResourceID, x.ProviderID }); + table.ForeignKey( + name: "FK_ShowMetadataID_Providers_ProviderID", + column: x => x.ProviderID, + principalTable: "Providers", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ShowMetadataID_Shows_ResourceID", + column: x => x.ResourceID, + principalTable: "Shows", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "Episodes", columns: table => new @@ -411,7 +459,7 @@ namespace Kyoo.SqLite.Migrations EpisodeNumber = table.Column(type: "INTEGER", nullable: true), AbsoluteNumber = table.Column(type: "INTEGER", nullable: true), Path = table.Column(type: "TEXT", nullable: true), - Thumb = table.Column(type: "TEXT", nullable: true), + Images = table.Column(type: "TEXT", nullable: true), Title = table.Column(type: "TEXT", nullable: true), Overview = table.Column(type: "TEXT", nullable: true), ReleaseDate = table.Column(type: "TEXT", nullable: true) @@ -434,52 +482,52 @@ namespace Kyoo.SqLite.Migrations }); migrationBuilder.CreateTable( - name: "MetadataID", + name: "SeasonMetadataID", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false), + ResourceID = table.Column(type: "INTEGER", nullable: false), + ProviderID = table.Column(type: "INTEGER", nullable: false), DataID = table.Column(type: "TEXT", nullable: true), Link = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_MetadataID", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_SeasonMetadataID", x => new { x.ResourceID, x.ProviderID }); table.ForeignKey( - name: "FK_MetadataID_Providers_SecondID", - column: x => x.SecondID, + name: "FK_SeasonMetadataID_Providers_ProviderID", + column: x => x.ProviderID, principalTable: "Providers", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_MetadataID_Seasons_FirstID", - column: x => x.FirstID, + name: "FK_SeasonMetadataID_Seasons_ResourceID", + column: x => x.ResourceID, principalTable: "Seasons", principalColumn: "ID", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "MetadataID", + name: "EpisodeMetadataID", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false), + ResourceID = table.Column(type: "INTEGER", nullable: false), + ProviderID = table.Column(type: "INTEGER", nullable: false), DataID = table.Column(type: "TEXT", nullable: true), Link = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_MetadataID", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_EpisodeMetadataID", x => new { x.ResourceID, x.ProviderID }); table.ForeignKey( - name: "FK_MetadataID_Episodes_FirstID", - column: x => x.FirstID, + name: "FK_EpisodeMetadataID_Episodes_ResourceID", + column: x => x.ResourceID, principalTable: "Episodes", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_MetadataID_Providers_SecondID", - column: x => x.SecondID, + name: "FK_EpisodeMetadataID_Providers_ProviderID", + column: x => x.ProviderID, principalTable: "Providers", principalColumn: "ID", onDelete: ReferentialAction.Cascade); @@ -539,12 +587,22 @@ namespace Kyoo.SqLite.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateIndex( + name: "IX_CollectionMetadataID_ProviderID", + table: "CollectionMetadataID", + column: "ProviderID"); + migrationBuilder.CreateIndex( name: "IX_Collections_Slug", table: "Collections", column: "Slug", unique: true); + migrationBuilder.CreateIndex( + name: "IX_EpisodeMetadataID_ProviderID", + table: "EpisodeMetadataID", + column: "ProviderID"); + migrationBuilder.CreateIndex( name: "IX_Episodes_SeasonID", table: "Episodes", @@ -604,32 +662,17 @@ namespace Kyoo.SqLite.Migrations table: "Link", column: "SecondID"); - migrationBuilder.CreateIndex( - name: "IX_MetadataID_SecondID", - table: "MetadataID", - column: "SecondID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataID_SecondID", - table: "MetadataID", - column: "SecondID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataID_SecondID", - table: "MetadataID", - column: "SecondID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataID_SecondID", - table: "MetadataID", - column: "SecondID"); - migrationBuilder.CreateIndex( name: "IX_People_Slug", table: "People", column: "Slug", unique: true); + migrationBuilder.CreateIndex( + name: "IX_PeopleMetadataID_ProviderID", + table: "PeopleMetadataID", + column: "ProviderID"); + migrationBuilder.CreateIndex( name: "IX_PeopleRoles_PeopleID", table: "PeopleRoles", @@ -646,6 +689,11 @@ namespace Kyoo.SqLite.Migrations column: "Slug", unique: true); + migrationBuilder.CreateIndex( + name: "IX_SeasonMetadataID_ProviderID", + table: "SeasonMetadataID", + column: "ProviderID"); + migrationBuilder.CreateIndex( name: "IX_Seasons_ShowID_SeasonNumber", table: "Seasons", @@ -658,6 +706,11 @@ namespace Kyoo.SqLite.Migrations column: "Slug", unique: true); + migrationBuilder.CreateIndex( + name: "IX_ShowMetadataID_ProviderID", + table: "ShowMetadataID", + column: "ProviderID"); + migrationBuilder.CreateIndex( name: "IX_Shows_Slug", table: "Shows", @@ -669,6 +722,11 @@ namespace Kyoo.SqLite.Migrations table: "Shows", column: "StudioID"); + migrationBuilder.CreateIndex( + name: "IX_StudioMetadataID_ProviderID", + table: "StudioMetadataID", + column: "ProviderID"); + migrationBuilder.CreateIndex( name: "IX_Studios_Slug", table: "Studios", @@ -701,6 +759,12 @@ namespace Kyoo.SqLite.Migrations protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DropTable( + name: "CollectionMetadataID"); + + migrationBuilder.DropTable( + name: "EpisodeMetadataID"); + migrationBuilder.DropTable( name: "Link"); @@ -720,20 +784,20 @@ namespace Kyoo.SqLite.Migrations name: "Link"); migrationBuilder.DropTable( - name: "MetadataID"); - - migrationBuilder.DropTable( - name: "MetadataID"); - - migrationBuilder.DropTable( - name: "MetadataID"); - - migrationBuilder.DropTable( - name: "MetadataID"); + name: "PeopleMetadataID"); migrationBuilder.DropTable( name: "PeopleRoles"); + migrationBuilder.DropTable( + name: "SeasonMetadataID"); + + migrationBuilder.DropTable( + name: "ShowMetadataID"); + + migrationBuilder.DropTable( + name: "StudioMetadataID"); + migrationBuilder.DropTable( name: "Tracks"); @@ -750,10 +814,10 @@ namespace Kyoo.SqLite.Migrations name: "Genres"); migrationBuilder.DropTable( - name: "Providers"); + name: "People"); migrationBuilder.DropTable( - name: "People"); + name: "Providers"); migrationBuilder.DropTable( name: "Episodes"); diff --git a/Kyoo.SqLite/Migrations/20210723224550_Triggers.Designer.cs b/Kyoo.SqLite/Migrations/20210728135127_Triggers.Designer.cs similarity index 84% rename from Kyoo.SqLite/Migrations/20210723224550_Triggers.Designer.cs rename to Kyoo.SqLite/Migrations/20210728135127_Triggers.Designer.cs index 059a3aa4..43174e4b 100644 --- a/Kyoo.SqLite/Migrations/20210723224550_Triggers.Designer.cs +++ b/Kyoo.SqLite/Migrations/20210728135127_Triggers.Designer.cs @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Kyoo.SqLite.Migrations { [DbContext(typeof(SqLiteContext))] - [Migration("20210723224550_Triggers")] + [Migration("20210728135127_Triggers")] partial class Triggers { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -18,21 +18,63 @@ namespace Kyoo.SqLite.Migrations modelBuilder .HasAnnotation("ProductVersion", "5.0.8"); + modelBuilder.Entity("CollectionMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("CollectionMetadataID"); + }); + + modelBuilder.Entity("EpisodeMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("EpisodeMetadataID"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Name") .HasColumnType("TEXT"); b.Property("Overview") .HasColumnType("TEXT"); - b.Property("Poster") - .HasColumnType("TEXT"); - b.Property("Slug") .IsRequired() .HasColumnType("TEXT"); @@ -57,6 +99,9 @@ namespace Kyoo.SqLite.Migrations b.Property("EpisodeNumber") .HasColumnType("INTEGER"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Overview") .HasColumnType("TEXT"); @@ -79,9 +124,6 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAddOrUpdate() .HasColumnType("TEXT"); - b.Property("Thumb") - .HasColumnType("TEXT"); - b.Property("Title") .HasColumnType("TEXT"); @@ -152,10 +194,10 @@ namespace Kyoo.SqLite.Migrations b.Property("EndAir") .HasColumnType("TEXT"); - b.Property("Overview") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Overview") .HasColumnType("TEXT"); b.Property("Slug") @@ -268,100 +310,16 @@ namespace Kyoo.SqLite.Migrations b.ToTable("Link"); }); - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Name") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Name") .HasColumnType("TEXT"); b.Property("Slug") @@ -382,9 +340,6 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("ForPeople") - .HasColumnType("INTEGER"); - b.Property("PeopleID") .HasColumnType("INTEGER"); @@ -412,7 +367,7 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Logo") + b.Property("Images") .HasColumnType("TEXT"); b.Property("LogoExtension") @@ -442,10 +397,10 @@ namespace Kyoo.SqLite.Migrations b.Property("EndDate") .HasColumnType("TEXT"); - b.Property("Overview") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Overview") .HasColumnType("TEXT"); b.Property("SeasonNumber") @@ -484,27 +439,21 @@ namespace Kyoo.SqLite.Migrations b.Property("Aliases") .HasColumnType("TEXT"); - b.Property("Backdrop") + b.Property("EndAir") .HasColumnType("TEXT"); - b.Property("EndAir") + b.Property("Images") .HasColumnType("TEXT"); b.Property("IsMovie") .HasColumnType("INTEGER"); - b.Property("Logo") - .HasColumnType("TEXT"); - b.Property("Overview") .HasColumnType("TEXT"); b.Property("Path") .HasColumnType("TEXT"); - b.Property("Poster") - .HasColumnType("TEXT"); - b.Property("Slug") .IsRequired() .HasColumnType("TEXT"); @@ -521,9 +470,6 @@ namespace Kyoo.SqLite.Migrations b.Property("Title") .HasColumnType("TEXT"); - b.Property("TrailerUrl") - .HasColumnType("TEXT"); - b.HasKey("ID"); b.HasIndex("Slug") @@ -657,6 +603,124 @@ namespace Kyoo.SqLite.Migrations b.ToTable("WatchedEpisodes"); }); + modelBuilder.Entity("PeopleMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("PeopleMetadataID"); + }); + + modelBuilder.Entity("SeasonMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("SeasonMetadataID"); + }); + + modelBuilder.Entity("ShowMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("ShowMetadataID"); + }); + + modelBuilder.Entity("StudioMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("StudioMetadataID"); + }); + + modelBuilder.Entity("CollectionMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Collection", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("EpisodeMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Episode", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -789,82 +853,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Second"); }); - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Episode", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.People", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Season", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -935,8 +923,78 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Second"); }); + modelBuilder.Entity("PeopleMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.People", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("SeasonMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Season", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("ShowMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("StudioMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Studio", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { + b.Navigation("ExternalIDs"); + b.Navigation("LibraryLinks"); b.Navigation("ShowLinks"); @@ -1001,6 +1059,8 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.Studio", b => { + b.Navigation("ExternalIDs"); + b.Navigation("Shows"); }); diff --git a/Kyoo.SqLite/Migrations/20210723224550_Triggers.cs b/Kyoo.SqLite/Migrations/20210728135127_Triggers.cs similarity index 98% rename from Kyoo.SqLite/Migrations/20210723224550_Triggers.cs rename to Kyoo.SqLite/Migrations/20210728135127_Triggers.cs index 370fdd37..f26ab7e2 100644 --- a/Kyoo.SqLite/Migrations/20210723224550_Triggers.cs +++ b/Kyoo.SqLite/Migrations/20210728135127_Triggers.cs @@ -154,7 +154,7 @@ namespace Kyoo.SqLite.Migrations // language=SQLite migrationBuilder.Sql(@" CREATE VIEW LibraryItems AS - SELECT s.ID, s.Slug, s.Title, s.Overview, s.Status, s.StartAir, s.EndAir, s.Poster, CASE + SELECT s.ID, s.Slug, s.Title, s.Overview, s.Status, s.StartAir, s.EndAir, s.Images, CASE WHEN s.IsMovie THEN 1 ELSE 0 END AS Type @@ -166,7 +166,7 @@ namespace Kyoo.SqLite.Migrations WHERE s.ID = l.SecondID)) UNION ALL SELECT -c0.ID, c0.Slug, c0.Name AS Title, c0.Overview, 3 AS Status, - NULL AS StartAir, NULL AS EndAir, c0.Poster, 2 AS Type + NULL AS StartAir, NULL AS EndAir, c0.Images, 2 AS Type FROM collections AS c0"); } diff --git a/Kyoo.SqLite/Migrations/SqLiteContextModelSnapshot.cs b/Kyoo.SqLite/Migrations/SqLiteContextModelSnapshot.cs index 12f5d94b..fc7c6cac 100644 --- a/Kyoo.SqLite/Migrations/SqLiteContextModelSnapshot.cs +++ b/Kyoo.SqLite/Migrations/SqLiteContextModelSnapshot.cs @@ -16,21 +16,63 @@ namespace Kyoo.SqLite.Migrations modelBuilder .HasAnnotation("ProductVersion", "5.0.8"); + modelBuilder.Entity("CollectionMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("CollectionMetadataID"); + }); + + modelBuilder.Entity("EpisodeMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("EpisodeMetadataID"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Name") .HasColumnType("TEXT"); b.Property("Overview") .HasColumnType("TEXT"); - b.Property("Poster") - .HasColumnType("TEXT"); - b.Property("Slug") .IsRequired() .HasColumnType("TEXT"); @@ -55,6 +97,9 @@ namespace Kyoo.SqLite.Migrations b.Property("EpisodeNumber") .HasColumnType("INTEGER"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Overview") .HasColumnType("TEXT"); @@ -77,9 +122,6 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAddOrUpdate() .HasColumnType("TEXT"); - b.Property("Thumb") - .HasColumnType("TEXT"); - b.Property("Title") .HasColumnType("TEXT"); @@ -150,10 +192,10 @@ namespace Kyoo.SqLite.Migrations b.Property("EndAir") .HasColumnType("TEXT"); - b.Property("Overview") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Overview") .HasColumnType("TEXT"); b.Property("Slug") @@ -266,100 +308,16 @@ namespace Kyoo.SqLite.Migrations b.ToTable("Link"); }); - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Name") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Name") .HasColumnType("TEXT"); b.Property("Slug") @@ -380,9 +338,6 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("ForPeople") - .HasColumnType("INTEGER"); - b.Property("PeopleID") .HasColumnType("INTEGER"); @@ -410,7 +365,7 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Logo") + b.Property("Images") .HasColumnType("TEXT"); b.Property("LogoExtension") @@ -440,10 +395,10 @@ namespace Kyoo.SqLite.Migrations b.Property("EndDate") .HasColumnType("TEXT"); - b.Property("Overview") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Overview") .HasColumnType("TEXT"); b.Property("SeasonNumber") @@ -482,27 +437,21 @@ namespace Kyoo.SqLite.Migrations b.Property("Aliases") .HasColumnType("TEXT"); - b.Property("Backdrop") + b.Property("EndAir") .HasColumnType("TEXT"); - b.Property("EndAir") + b.Property("Images") .HasColumnType("TEXT"); b.Property("IsMovie") .HasColumnType("INTEGER"); - b.Property("Logo") - .HasColumnType("TEXT"); - b.Property("Overview") .HasColumnType("TEXT"); b.Property("Path") .HasColumnType("TEXT"); - b.Property("Poster") - .HasColumnType("TEXT"); - b.Property("Slug") .IsRequired() .HasColumnType("TEXT"); @@ -519,9 +468,6 @@ namespace Kyoo.SqLite.Migrations b.Property("Title") .HasColumnType("TEXT"); - b.Property("TrailerUrl") - .HasColumnType("TEXT"); - b.HasKey("ID"); b.HasIndex("Slug") @@ -655,6 +601,124 @@ namespace Kyoo.SqLite.Migrations b.ToTable("WatchedEpisodes"); }); + modelBuilder.Entity("PeopleMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("PeopleMetadataID"); + }); + + modelBuilder.Entity("SeasonMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("SeasonMetadataID"); + }); + + modelBuilder.Entity("ShowMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("ShowMetadataID"); + }); + + modelBuilder.Entity("StudioMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("StudioMetadataID"); + }); + + modelBuilder.Entity("CollectionMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Collection", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("EpisodeMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Episode", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -787,82 +851,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Second"); }); - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Episode", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.People", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Season", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -933,8 +921,78 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Second"); }); + modelBuilder.Entity("PeopleMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.People", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("SeasonMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Season", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("ShowMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("StudioMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Studio", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { + b.Navigation("ExternalIDs"); + b.Navigation("LibraryLinks"); b.Navigation("ShowLinks"); @@ -999,6 +1057,8 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.Studio", b => { + b.Navigation("ExternalIDs"); + b.Navigation("Shows"); }); diff --git a/Kyoo.SqLite/SqLiteContext.cs b/Kyoo.SqLite/SqLiteContext.cs index 23145cf1..e81a91ad 100644 --- a/Kyoo.SqLite/SqLiteContext.cs +++ b/Kyoo.SqLite/SqLiteContext.cs @@ -102,12 +102,38 @@ namespace Kyoo.SqLite .Property(x => x.Type) .HasConversion(); - ValueConverter, string> jsonConvertor = new( + ValueConverter, string> extraDataConvertor = new( x => JsonConvert.SerializeObject(x), x => JsonConvert.DeserializeObject>(x)); modelBuilder.Entity() .Property(x => x.ExtraData) + .HasConversion(extraDataConvertor); + + ValueConverter, string> jsonConvertor = new( + x => JsonConvert.SerializeObject(x), + x => JsonConvert.DeserializeObject>(x)); + modelBuilder.Entity() + .Property(x => x.Images) .HasConversion(jsonConvertor); + modelBuilder.Entity() + .Property(x => x.Images) + .HasConversion(jsonConvertor); + modelBuilder.Entity() + .Property(x => x.Images) + .HasConversion(jsonConvertor); + modelBuilder.Entity() + .Property(x => x.Images) + .HasConversion(jsonConvertor); + modelBuilder.Entity() + .Property(x => x.Images) + .HasConversion(jsonConvertor); + modelBuilder.Entity() + .Property(x => x.Images) + .HasConversion(jsonConvertor); + modelBuilder.Entity() + .Property(x => x.Images) + .HasConversion(jsonConvertor); + modelBuilder.Entity() .ToView("LibraryItems") @@ -115,6 +141,12 @@ namespace Kyoo.SqLite base.OnModelCreating(modelBuilder); } + /// + protected override string MetadataName() + { + return typeof(T).Name + nameof(MetadataID); + } + /// protected override bool IsDuplicateException(Exception ex) { diff --git a/Kyoo.Tests/Database/TestSample.cs b/Kyoo.Tests/Database/TestSample.cs index d81612ad..0faf16da 100644 --- a/Kyoo.Tests/Database/TestSample.cs +++ b/Kyoo.Tests/Database/TestSample.cs @@ -41,7 +41,7 @@ namespace Kyoo.Tests Slug = "collection", Name = "Collection", Overview = "A nice collection for tests", - Images = + Images = new Dictionary { [Thumbnails.Poster] = "Poster" } @@ -66,7 +66,7 @@ namespace Kyoo.Tests Status = Status.Finished, StartAir = new DateTime(2011, 1, 1), EndAir = new DateTime(2011, 1, 1), - Images = + Images = new Dictionary { [Thumbnails.Poster] = "Poster", [Thumbnails.Logo] = "Logo", @@ -88,7 +88,7 @@ namespace Kyoo.Tests Overview = "The first season", StartDate = new DateTime(2020, 06, 05), EndDate = new DateTime(2020, 07, 05), - Images = + Images = new Dictionary { [Thumbnails.Poster] = "Poster", [Thumbnails.Logo] = "Logo", @@ -108,7 +108,7 @@ namespace Kyoo.Tests EpisodeNumber = 1, AbsoluteNumber = 1, Path = "/home/kyoo/anohana-s1e1", - Images = + Images = new Dictionary { [Thumbnails.Poster] = "Poster", [Thumbnails.Logo] = "Logo", @@ -144,7 +144,7 @@ namespace Kyoo.Tests ID = 1, Slug = "the-actor", Name = "The Actor", - Images = + Images = new Dictionary { [Thumbnails.Poster] = "Poster", [Thumbnails.Logo] = "Logo", @@ -177,7 +177,7 @@ namespace Kyoo.Tests ID = 1, Slug = "tvdb", Name = "The TVDB", - Images = + Images = new Dictionary { [Thumbnails.Poster] = "Poster", [Thumbnails.Logo] = "path/tvdb.svg", @@ -282,7 +282,7 @@ namespace Kyoo.Tests EpisodeNumber = null, AbsoluteNumber = 3, Path = "/home/kyoo/anohana-3", - Images = + Images = new Dictionary { [Thumbnails.Poster] = "Poster", [Thumbnails.Logo] = "Logo", @@ -302,7 +302,7 @@ namespace Kyoo.Tests ShowSlug = "anohana", ShowID = 1, Path = "/home/kyoo/john-wick", - Images = + Images = new Dictionary { [Thumbnails.Poster] = "Poster", [Thumbnails.Logo] = "Logo", diff --git a/Kyoo.TheMovieDb/Convertors.cs b/Kyoo.TheMovieDb/Convertors.cs index b6fd4ad1..cbd63e10 100644 --- a/Kyoo.TheMovieDb/Convertors.cs +++ b/Kyoo.TheMovieDb/Convertors.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using Kyoo.Models; using TMDbLib.Objects.General; @@ -29,7 +30,7 @@ namespace Kyoo.TheMovieDb Status = movie.Status == "Released" ? Status.Finished : Status.Planned, StartAir = movie.ReleaseDate, EndAir = movie.ReleaseDate, - Images = + Images = new Dictionary { [Thumbnails.Poster] = movie.PosterPath != null ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" @@ -79,7 +80,7 @@ namespace Kyoo.TheMovieDb Status = tv.Status == "Ended" ? Status.Finished : Status.Planned, StartAir = tv.FirstAirDate, EndAir = tv.LastAirDate, - Images = + Images = new Dictionary { [Thumbnails.Poster] = tv.PosterPath != null ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" @@ -125,7 +126,7 @@ namespace Kyoo.TheMovieDb { Slug = Utility.ToSlug(collection.Name), Name = collection.Name, - Images = + Images = new Dictionary { [Thumbnails.Poster] = collection.PosterPath != null ? $"https://image.tmdb.org/t/p/original{collection.PosterPath}" @@ -152,7 +153,7 @@ namespace Kyoo.TheMovieDb Overview = movie.Overview, StartAir = movie.ReleaseDate, EndAir = movie.ReleaseDate, - Images = + Images = new Dictionary { [Thumbnails.Poster] = movie.PosterPath != null ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" @@ -188,7 +189,7 @@ namespace Kyoo.TheMovieDb Title = tv.Name, Overview = tv.Overview, StartAir = tv.FirstAirDate, - Images = + Images = new Dictionary { [Thumbnails.Poster] = tv.PosterPath != null ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" @@ -224,7 +225,7 @@ namespace Kyoo.TheMovieDb { Slug = Utility.ToSlug(cast.Name), Name = cast.Name, - Images = + Images = new Dictionary { [Thumbnails.Poster] = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" @@ -259,7 +260,7 @@ namespace Kyoo.TheMovieDb { Slug = Utility.ToSlug(cast.Name), Name = cast.Name, - Images = + Images = new Dictionary { [Thumbnails.Poster] = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" @@ -294,7 +295,7 @@ namespace Kyoo.TheMovieDb { Slug = Utility.ToSlug(crew.Name), Name = crew.Name, - Images = + Images = new Dictionary { [Thumbnails.Poster] = crew.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{crew.ProfilePath}" diff --git a/Kyoo.TheMovieDb/ProviderTmdb.cs b/Kyoo.TheMovieDb/ProviderTmdb.cs index bde0a748..d5f9f7ef 100644 --- a/Kyoo.TheMovieDb/ProviderTmdb.cs +++ b/Kyoo.TheMovieDb/ProviderTmdb.cs @@ -29,7 +29,7 @@ namespace Kyoo.TheMovieDb Slug = "the-moviedb", Name = "TheMovieDB", LogoExtension = "svg", - Images = + Images = new Dictionary { [Thumbnails.Logo] = "https://www.themoviedb.org/assets/2/v4/logos/v2/" + "blue_short-8e7b30f73a4020692ccca9c88bafe5dcb6f8a62a4c6bc55cd9ba82bb2cd95f6c.svg" diff --git a/Kyoo.TheTvdb/Convertors.cs b/Kyoo.TheTvdb/Convertors.cs index b992481d..4cff035a 100644 --- a/Kyoo.TheTvdb/Convertors.cs +++ b/Kyoo.TheTvdb/Convertors.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using Kyoo.Models; @@ -55,7 +56,7 @@ namespace Kyoo.TheTvdb Overview = result.Overview, Status = _GetStatus(result.Status), StartAir = _ParseDate(result.FirstAired), - Images = + Images = new Dictionary { [Thumbnails.Poster] = result.Poster != null ? $"https://www.thetvdb.com{result.Poster}" @@ -89,7 +90,7 @@ namespace Kyoo.TheTvdb Overview = series.Overview, Status = _GetStatus(series.Status), StartAir = _ParseDate(series.FirstAired), - Images= + Images = new Dictionary { [Thumbnails.Poster] = series.Poster != null ? $"https://www.thetvdb.com/banners/{series.Poster}" @@ -125,7 +126,7 @@ namespace Kyoo.TheTvdb { Slug = Utility.ToSlug(actor.Name), Name = actor.Name, - Images = + Images = new Dictionary { [Thumbnails.Poster] = actor.Image != null ? $"https://www.thetvdb.com/banners/{actor.Image}" @@ -161,7 +162,7 @@ namespace Kyoo.TheTvdb AbsoluteNumber = episode.AbsoluteNumber, Title = episode.EpisodeName, Overview = episode.Overview, - Images = + Images = new Dictionary { [Thumbnails.Thumbnail] = episode.Filename != null ? $"https://www.thetvdb.com/banners/{episode.Filename}" diff --git a/Kyoo.TheTvdb/ProviderTvdb.cs b/Kyoo.TheTvdb/ProviderTvdb.cs index b2ba5683..13aefa80 100644 --- a/Kyoo.TheTvdb/ProviderTvdb.cs +++ b/Kyoo.TheTvdb/ProviderTvdb.cs @@ -33,7 +33,7 @@ namespace Kyoo.TheTvdb Slug = "the-tvdb", Name = "TheTVDB", LogoExtension = "png", - Images = + Images = new Dictionary { [Thumbnails.Logo] = "https://www.thetvdb.com/images/logo.png" } diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index 6b546f07..a757d1c9 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -53,7 +53,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated collection (slug {obj.Slug} already exists)."); return obj; } @@ -66,7 +65,6 @@ namespace Kyoo.Controllers { x.Provider = await _providers.CreateIfNotExists(x.Provider); x.ProviderID = x.Provider.ID; - x.ResourceType = nameof(Collection); _database.Entry(x.Provider).State = EntityState.Detached; }); } diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 7a2b0479..51f58cd9 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -111,7 +111,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated episode (slug {obj.Slug} already exists)."); return await ValidateTracks(obj); } @@ -162,7 +161,6 @@ namespace Kyoo.Controllers { x.Provider = await _providers.CreateIfNotExists(x.Provider); x.ProviderID = x.Provider.ID; - x.ResourceType = nameof(Episode); _database.Entry(x.Provider).State = EntityState.Detached; }); } diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index 6267f52f..6ca99c37 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -62,7 +62,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated people (slug {obj.Slug} already exists)."); return obj; } @@ -75,7 +74,6 @@ namespace Kyoo.Controllers { id.Provider = await _providers.CreateIfNotExists(id.Provider); id.ProviderID = id.Provider.ID; - id.ResourceType = nameof(People); _database.Entry(id.Provider).State = EntityState.Detached; }); await resource.Roles.ForEachAsync(async role => diff --git a/Kyoo/Controllers/Repositories/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index 13525c9c..5edf451b 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -69,11 +69,9 @@ namespace Kyoo.Controllers where T : class, IMetadata { string discriminator = typeof(T).Name; - return ApplyFilters(_database.MetadataIDs - .Include(y => y.Provider) - .Where(x => x.ResourceType == discriminator), - x => _database.MetadataIDs.FirstOrDefaultAsync(y => y.ResourceID == x - && y.ResourceType == discriminator), + return ApplyFilters(_database.MetadataIds() + .Include(y => y.Provider), + x => _database.MetadataIds().FirstOrDefaultAsync(y => y.ResourceID == x), x => x.ResourceID, where, sort, diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index 45aa4554..c481e866 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -87,7 +87,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated season (slug {obj.Slug} already exists)."); return obj; } @@ -108,7 +107,6 @@ namespace Kyoo.Controllers { id.Provider = await _providers.CreateIfNotExists(id.Provider); id.ProviderID = id.Provider.ID; - id.ResourceType = nameof(Season); _database.Entry(id.Provider).State = EntityState.Detached; }); } diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 37921c4d..84923cd3 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -78,7 +78,6 @@ namespace Kyoo.Controllers _database.Entry(obj).State = EntityState.Added; obj.GenreLinks.ForEach(x => _database.Entry(x).State = EntityState.Added); obj.People.ForEach(x => _database.Entry(x).State = EntityState.Added); - obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated show (slug {obj.Slug} already exists)."); return obj; } @@ -103,7 +102,6 @@ namespace Kyoo.Controllers { id.Provider = await _providers.CreateIfNotExists(id.Provider); id.ProviderID = id.Provider.ID; - id.ResourceType = nameof(Show); _database.Entry(id.Provider).State = EntityState.Detached; }); await resource.People.ForEachAsync(async role => diff --git a/Kyoo/Controllers/Repositories/StudioRepository.cs b/Kyoo/Controllers/Repositories/StudioRepository.cs index 0422c382..ef0dfecc 100644 --- a/Kyoo/Controllers/Repositories/StudioRepository.cs +++ b/Kyoo/Controllers/Repositories/StudioRepository.cs @@ -66,7 +66,6 @@ namespace Kyoo.Controllers { x.Provider = await _providers.CreateIfNotExists(x.Provider); x.ProviderID = x.Provider.ID; - x.ResourceType = nameof(Studio); _database.Entry(x.Provider).State = EntityState.Detached; }); } From a43210365d4f85470b62b0fd186dfaa3acd51181 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 28 Jul 2021 17:38:35 +0200 Subject: [PATCH 06/34] Adding collection search/get --- .../Models/Resources/Interfaces/IMetadata.cs | 62 ++++ Kyoo.Common/Models/Resources/Show.cs | 12 - Kyoo.TheMovieDb/Convertors.cs | 319 ------------------ .../Convertors/CollectionConvertors.cs | 79 +++++ Kyoo.TheMovieDb/Convertors/MovieConvertors.cs | 101 ++++++ .../Convertors/PeopleConvertors.cs | 119 +++++++ Kyoo.TheMovieDb/Convertors/ShowConvertors.cs | 100 ++++++ Kyoo.TheMovieDb/ProviderTmdb.cs | 30 +- 8 files changed, 488 insertions(+), 334 deletions(-) delete mode 100644 Kyoo.TheMovieDb/Convertors.cs create mode 100644 Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs create mode 100644 Kyoo.TheMovieDb/Convertors/MovieConvertors.cs create mode 100644 Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs create mode 100644 Kyoo.TheMovieDb/Convertors/ShowConvertors.cs diff --git a/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs b/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs index 1c937715..ef1e6bbc 100644 --- a/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs +++ b/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs @@ -1,4 +1,7 @@ +using System; using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; using Kyoo.Models.Attributes; namespace Kyoo.Models @@ -14,4 +17,63 @@ namespace Kyoo.Models [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } } + + /// + /// A static class containing extensions method for every class. + /// This allow one to use metadata more easily. + /// + public static class MetadataExtension + { + /// + /// Retrieve the internal provider's ID of an item using it's provider slug. + /// + /// + /// This method will never return anything if the are not loaded. + /// + /// An instance of to retrieve the ID from. + /// The slug of the provider + /// The field of the asked provider. + [CanBeNull] + public static string GetID(this IMetadata self, string provider) + { + return self.ExternalIDs?.FirstOrDefault(x => x.Provider.Slug == provider)?.DataID; + } + + /// + /// Retrieve the internal provider's ID of an item using it's provider slug. + /// If the ID could be found, it is converted to the type and true is returned. + /// + /// + /// This method will never succeed if the are not loaded. + /// + /// An instance of to retrieve the ID from. + /// The slug of the provider + /// + /// The field of the asked provider parsed + /// and converted to the type. + /// It is only relevant if this method returns true. + /// + /// The type to convert the to. + /// true if this method succeeded, false otherwise. + public static bool TryGetID(this IMetadata self, string provider, out T id) + { + string dataID = self.ExternalIDs?.FirstOrDefault(x => x.Provider.Slug == provider)?.DataID; + if (dataID == null) + { + id = default; + return false; + } + + try + { + id = (T)Convert.ChangeType(dataID, typeof(T)); + } + catch + { + id = default; + return false; + } + return true; + } + } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index a32a0169..90aa98f8 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -156,18 +156,6 @@ namespace Kyoo.Models [Link] public ICollection> GenreLinks { get; set; } #endif - /// - /// Retrieve the internal provider's ID of a show using it's provider slug. - /// - /// This method will never return anything if the are not loaded. - /// The slug of the provider - /// The field of the asked provider. - [CanBeNull] - public string GetID(string provider) - { - return ExternalIDs?.FirstOrDefault(x => x.Provider.Slug == provider)?.DataID; - } - /// public void OnMerge(object merged) { diff --git a/Kyoo.TheMovieDb/Convertors.cs b/Kyoo.TheMovieDb/Convertors.cs deleted file mode 100644 index cbd63e10..00000000 --- a/Kyoo.TheMovieDb/Convertors.cs +++ /dev/null @@ -1,319 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Kyoo.Models; -using TMDbLib.Objects.General; -using TMDbLib.Objects.Movies; -using TMDbLib.Objects.Search; -using TMDbLib.Objects.TvShows; -using Genre = Kyoo.Models.Genre; -using TvCast = TMDbLib.Objects.TvShows.Cast; -using MovieCast = TMDbLib.Objects.Movies.Cast; - -namespace Kyoo.TheMovieDb -{ - public static class Convertors - { - /// - /// Convert a into a . - /// - /// The movie to convert. - /// The provider representing TheMovieDb. - /// The converted movie as a . - public static Show ToShow(this Movie movie, Provider provider) - { - return new() - { - Slug = Utility.ToSlug(movie.Title), - Title = movie.Title, - Aliases = movie.AlternativeTitles.Titles.Select(x => x.Title).ToArray(), - Overview = movie.Overview, - Status = movie.Status == "Released" ? Status.Finished : Status.Planned, - StartAir = movie.ReleaseDate, - EndAir = movie.ReleaseDate, - Images = new Dictionary - { - [Thumbnails.Poster] = movie.PosterPath != null - ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" - : null, - [Thumbnails.Thumbnail] = movie.BackdropPath != null - ? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}" - : null, - [Thumbnails.Trailer] = movie.Videos?.Results - .Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube") - .Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault(), - }, - Genres = movie.Genres.Select(x => new Genre(x.Name)).ToArray(), - Studio = !string.IsNullOrEmpty(movie.ProductionCompanies.FirstOrDefault()?.Name) - ? new Studio(movie.ProductionCompanies.First().Name) - : null, - IsMovie = true, - People = movie.Credits.Cast - .Select(x => x.ToPeople(provider)) - .Concat(movie.Credits.Crew.Select(x => x.ToPeople(provider))) - .ToArray(), - ExternalIDs = new [] - { - new MetadataID - { - Provider = provider, - Link = $"https://www.themoviedb.org/movie/{movie.Id}", - DataID = movie.Id.ToString() - } - } - }; - } - - /// - /// Convert a to a . - /// - /// The show to convert. - /// The provider representing TheMovieDb. - /// A converted as a . - public static Show ToShow(this TvShow tv, Provider provider) - { - return new() - { - Slug = Utility.ToSlug(tv.Name), - Title = tv.Name, - Aliases = tv.AlternativeTitles.Results.Select(x => x.Title).ToArray(), - Overview = tv.Overview, - Status = tv.Status == "Ended" ? Status.Finished : Status.Planned, - StartAir = tv.FirstAirDate, - EndAir = tv.LastAirDate, - Images = new Dictionary - { - [Thumbnails.Poster] = tv.PosterPath != null - ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" - : null, - [Thumbnails.Thumbnail] = tv.BackdropPath != null - ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" - : null, - [Thumbnails.Trailer] = tv.Videos?.Results - .Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube") - .Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault() - }, - Genres = tv.Genres.Select(x => new Genre(x.Name)).ToArray(), - Studio = !string.IsNullOrEmpty(tv.ProductionCompanies.FirstOrDefault()?.Name) - ? new Studio(tv.ProductionCompanies.First().Name) - : null, - IsMovie = true, - People = tv.Credits.Cast - .Select(x => x.ToPeople(provider)) - .Concat(tv.Credits.Crew.Select(x => x.ToPeople(provider))) - .ToArray(), - ExternalIDs = new [] - { - new MetadataID - { - Provider = provider, - Link = $"https://www.themoviedb.org/movie/{tv.Id}", - DataID = tv.Id.ToString() - } - } - }; - } - - - /// - /// Convert a into a . - /// - /// The collection to convert. - /// The provider representing TheMovieDb. - /// The converted collection as a . - public static Collection ToCollection(this SearchCollection collection, Provider provider) - { - return new() - { - Slug = Utility.ToSlug(collection.Name), - Name = collection.Name, - Images = new Dictionary - { - [Thumbnails.Poster] = collection.PosterPath != null - ? $"https://image.tmdb.org/t/p/original{collection.PosterPath}" - : null, - [Thumbnails.Thumbnail] = collection.BackdropPath != null - ? $"https://image.tmdb.org/t/p/original{collection.BackdropPath}" - : null - } - }; - } - - /// - /// Convert a into a . - /// - /// The movie to convert. - /// The provider representing TheMovieDb. - /// The converted movie as a . - public static Show ToShow(this SearchMovie movie, Provider provider) - { - return new() - { - Slug = Utility.ToSlug(movie.Title), - Title = movie.Title, - Overview = movie.Overview, - StartAir = movie.ReleaseDate, - EndAir = movie.ReleaseDate, - Images = new Dictionary - { - [Thumbnails.Poster] = movie.PosterPath != null - ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" - : null, - [Thumbnails.Thumbnail] = movie.BackdropPath != null - ? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}" - : null, - }, - IsMovie = true, - ExternalIDs = new [] - { - new MetadataID - { - Provider = provider, - Link = $"https://www.themoviedb.org/movie/{movie.Id}", - DataID = movie.Id.ToString() - } - } - }; - } - - /// - /// Convert a to a . - /// - /// The show to convert. - /// The provider representing TheMovieDb. - /// A converted as a . - public static Show ToShow(this SearchTv tv, Provider provider) - { - return new() - { - Slug = Utility.ToSlug(tv.Name), - Title = tv.Name, - Overview = tv.Overview, - StartAir = tv.FirstAirDate, - Images = new Dictionary - { - [Thumbnails.Poster] = tv.PosterPath != null - ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" - : null, - [Thumbnails.Thumbnail] = tv.BackdropPath != null - ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" - : null, - }, - IsMovie = true, - ExternalIDs = new [] - { - new MetadataID - { - Provider = provider, - Link = $"https://www.themoviedb.org/movie/{tv.Id}", - DataID = tv.Id.ToString() - } - } - }; - } - - /// - /// Convert a to a . - /// - /// An internal TheMovieDB cast. - /// The provider that represent TheMovieDB inside Kyoo. - /// A representing the movie cast. - public static PeopleRole ToPeople(this MovieCast cast, Provider provider) - { - return new() - { - People = new People - { - Slug = Utility.ToSlug(cast.Name), - Name = cast.Name, - Images = new Dictionary - { - [Thumbnails.Poster] = cast.ProfilePath != null - ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" - : null - }, - ExternalIDs = new[] - { - new MetadataID - { - Provider = provider, - DataID = cast.Id.ToString(), - Link = $"https://www.themoviedb.org/person/{cast.Id}" - } - } - }, - Type = "Actor", - Role = cast.Character - }; - } - - /// - /// Convert a to a . - /// - /// An internal TheMovieDB cast. - /// The provider that represent TheMovieDB inside Kyoo. - /// A representing the movie cast. - public static PeopleRole ToPeople(this TvCast cast, Provider provider) - { - return new() - { - People = new People - { - Slug = Utility.ToSlug(cast.Name), - Name = cast.Name, - Images = new Dictionary - { - [Thumbnails.Poster] = cast.ProfilePath != null - ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" - : null - }, - ExternalIDs = new[] - { - new MetadataID - { - Provider = provider, - DataID = cast.Id.ToString(), - Link = $"https://www.themoviedb.org/person/{cast.Id}" - } - } - }, - Type = "Actor", - Role = cast.Character - }; - } - - /// - /// Convert a to a . - /// - /// An internal TheMovieDB crew member. - /// The provider that represent TheMovieDB inside Kyoo. - /// A representing the movie crew. - public static PeopleRole ToPeople(this Crew crew, Provider provider) - { - return new() - { - People = new People - { - Slug = Utility.ToSlug(crew.Name), - Name = crew.Name, - Images = new Dictionary - { - [Thumbnails.Poster] = crew.ProfilePath != null - ? $"https://image.tmdb.org/t/p/original{crew.ProfilePath}" - : null - }, - ExternalIDs = new[] - { - new MetadataID - { - Provider = provider, - DataID = crew.Id.ToString(), - Link = $"https://www.themoviedb.org/person/{crew.Id}" - } - } - }, - Type = crew.Department, - Role = crew.Job - }; - } - } -} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs b/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs new file mode 100644 index 00000000..c0108b81 --- /dev/null +++ b/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using Kyoo.Models; +using TMDbLib.Objects.Search; + +namespace Kyoo.TheMovieDb +{ + /// + /// A class containing extensions methods to convert from TMDB's types to Kyoo's types. + /// + public static partial class Convertors + { + /// + /// Convert a into a . + /// + /// The collection to convert. + /// The provider representing TheMovieDb. + /// The converted collection as a . + public static Collection ToCollection(this TMDbLib.Objects.Collections.Collection collection, Provider provider) + { + return new() + { + Slug = Utility.ToSlug(collection.Name), + Name = collection.Name, + Overview = collection.Overview, + Images = new Dictionary + { + [Thumbnails.Poster] = collection.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{collection.PosterPath}" + : null, + [Thumbnails.Thumbnail] = collection.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{collection.BackdropPath}" + : null + }, + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/collection/{collection.Id}", + DataID = collection.Id.ToString() + } + } + }; + } + + /// + /// Convert a into a . + /// + /// The collection to convert. + /// The provider representing TheMovieDb. + /// The converted collection as a . + public static Collection ToCollection(this SearchCollection collection, Provider provider) + { + return new() + { + Slug = Utility.ToSlug(collection.Name), + Name = collection.Name, + Images = new Dictionary + { + [Thumbnails.Poster] = collection.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{collection.PosterPath}" + : null, + [Thumbnails.Thumbnail] = collection.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{collection.BackdropPath}" + : null + }, + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/collection/{collection.Id}", + DataID = collection.Id.ToString() + } + } + }; + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs b/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs new file mode 100644 index 00000000..06ea3108 --- /dev/null +++ b/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Linq; +using Kyoo.Models; +using TMDbLib.Objects.Movies; +using TMDbLib.Objects.Search; + +namespace Kyoo.TheMovieDb +{ + /// + /// A class containing extensions methods to convert from TMDB's types to Kyoo's types. + /// + public static partial class Convertors + { + /// + /// Convert a into a . + /// + /// The movie to convert. + /// The provider representing TheMovieDb. + /// The converted movie as a . + public static Show ToShow(this Movie movie, Provider provider) + { + return new() + { + Slug = Utility.ToSlug(movie.Title), + Title = movie.Title, + Aliases = movie.AlternativeTitles.Titles.Select(x => x.Title).ToArray(), + Overview = movie.Overview, + Status = movie.Status == "Released" ? Status.Finished : Status.Planned, + StartAir = movie.ReleaseDate, + EndAir = movie.ReleaseDate, + Images = new Dictionary + { + [Thumbnails.Poster] = movie.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" + : null, + [Thumbnails.Thumbnail] = movie.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}" + : null, + [Thumbnails.Trailer] = movie.Videos?.Results + .Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube") + .Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault(), + }, + Genres = movie.Genres.Select(x => new Genre(x.Name)).ToArray(), + Studio = !string.IsNullOrEmpty(movie.ProductionCompanies.FirstOrDefault()?.Name) + ? new Studio(movie.ProductionCompanies.First().Name) + : null, + IsMovie = true, + People = movie.Credits.Cast + .Select(x => x.ToPeople(provider)) + .Concat(movie.Credits.Crew.Select(x => x.ToPeople(provider))) + .ToArray(), + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/movie/{movie.Id}", + DataID = movie.Id.ToString() + } + } + }; + } + + /// + /// Convert a into a . + /// + /// The movie to convert. + /// The provider representing TheMovieDb. + /// The converted movie as a . + public static Show ToShow(this SearchMovie movie, Provider provider) + { + return new() + { + Slug = Utility.ToSlug(movie.Title), + Title = movie.Title, + Overview = movie.Overview, + StartAir = movie.ReleaseDate, + EndAir = movie.ReleaseDate, + Images = new Dictionary + { + [Thumbnails.Poster] = movie.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" + : null, + [Thumbnails.Thumbnail] = movie.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}" + : null, + }, + IsMovie = true, + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/movie/{movie.Id}", + DataID = movie.Id.ToString() + } + } + }; + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs b/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs new file mode 100644 index 00000000..8e1a326d --- /dev/null +++ b/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs @@ -0,0 +1,119 @@ +using System.Collections.Generic; +using Kyoo.Models; +using TMDbLib.Objects.General; +using TvCast = TMDbLib.Objects.TvShows.Cast; +using MovieCast = TMDbLib.Objects.Movies.Cast; + +namespace Kyoo.TheMovieDb +{ + /// + /// A class containing extensions methods to convert from TMDB's types to Kyoo's types. + /// + public static partial class Convertors + { + /// + /// Convert a to a . + /// + /// An internal TheMovieDB cast. + /// The provider that represent TheMovieDB inside Kyoo. + /// A representing the movie cast. + public static PeopleRole ToPeople(this MovieCast cast, Provider provider) + { + return new() + { + People = new People + { + Slug = Utility.ToSlug(cast.Name), + Name = cast.Name, + Images = new Dictionary + { + [Thumbnails.Poster] = cast.ProfilePath != null + ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" + : null + }, + ExternalIDs = new[] + { + new MetadataID + { + Provider = provider, + DataID = cast.Id.ToString(), + Link = $"https://www.themoviedb.org/person/{cast.Id}" + } + } + }, + Type = "Actor", + Role = cast.Character + }; + } + + /// + /// Convert a to a . + /// + /// An internal TheMovieDB cast. + /// The provider that represent TheMovieDB inside Kyoo. + /// A representing the movie cast. + public static PeopleRole ToPeople(this TvCast cast, Provider provider) + { + return new() + { + People = new People + { + Slug = Utility.ToSlug(cast.Name), + Name = cast.Name, + Images = new Dictionary + { + [Thumbnails.Poster] = cast.ProfilePath != null + ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" + : null + }, + ExternalIDs = new[] + { + new MetadataID + { + Provider = provider, + DataID = cast.Id.ToString(), + Link = $"https://www.themoviedb.org/person/{cast.Id}" + } + } + }, + Type = "Actor", + Role = cast.Character + }; + } + + /// + /// Convert a to a . + /// + /// An internal TheMovieDB crew member. + /// The provider that represent TheMovieDB inside Kyoo. + /// A representing the movie crew. + public static PeopleRole ToPeople(this Crew crew, Provider provider) + { + return new() + { + People = new People + { + Slug = Utility.ToSlug(crew.Name), + Name = crew.Name, + Images = new Dictionary + { + [Thumbnails.Poster] = crew.ProfilePath != null + ? $"https://image.tmdb.org/t/p/original{crew.ProfilePath}" + : null + }, + ExternalIDs = new[] + { + new MetadataID + { + Provider = provider, + DataID = crew.Id.ToString(), + Link = $"https://www.themoviedb.org/person/{crew.Id}" + } + } + }, + Type = crew.Department, + Role = crew.Job + }; + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs b/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs new file mode 100644 index 00000000..f379e19b --- /dev/null +++ b/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs @@ -0,0 +1,100 @@ +using System.Collections.Generic; +using System.Linq; +using Kyoo.Models; +using TMDbLib.Objects.Search; +using TMDbLib.Objects.TvShows; + +namespace Kyoo.TheMovieDb +{ + /// + /// A class containing extensions methods to convert from TMDB's types to Kyoo's types. + /// + public static partial class Convertors + { + /// + /// Convert a to a . + /// + /// The show to convert. + /// The provider representing TheMovieDb. + /// A converted as a . + public static Show ToShow(this TvShow tv, Provider provider) + { + return new() + { + Slug = Utility.ToSlug(tv.Name), + Title = tv.Name, + Aliases = tv.AlternativeTitles.Results.Select(x => x.Title).ToArray(), + Overview = tv.Overview, + Status = tv.Status == "Ended" ? Status.Finished : Status.Planned, + StartAir = tv.FirstAirDate, + EndAir = tv.LastAirDate, + Images = new Dictionary + { + [Thumbnails.Poster] = tv.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" + : null, + [Thumbnails.Thumbnail] = tv.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" + : null, + [Thumbnails.Trailer] = tv.Videos?.Results + .Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube") + .Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault() + }, + Genres = tv.Genres.Select(x => new Genre(x.Name)).ToArray(), + Studio = !string.IsNullOrEmpty(tv.ProductionCompanies.FirstOrDefault()?.Name) + ? new Studio(tv.ProductionCompanies.First().Name) + : null, + IsMovie = true, + People = tv.Credits.Cast + .Select(x => x.ToPeople(provider)) + .Concat(tv.Credits.Crew.Select(x => x.ToPeople(provider))) + .ToArray(), + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/movie/{tv.Id}", + DataID = tv.Id.ToString() + } + } + }; + } + + /// + /// Convert a to a . + /// + /// The show to convert. + /// The provider representing TheMovieDb. + /// A converted as a . + public static Show ToShow(this SearchTv tv, Provider provider) + { + return new() + { + Slug = Utility.ToSlug(tv.Name), + Title = tv.Name, + Overview = tv.Overview, + StartAir = tv.FirstAirDate, + Images = new Dictionary + { + [Thumbnails.Poster] = tv.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" + : null, + [Thumbnails.Thumbnail] = tv.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" + : null, + }, + IsMovie = true, + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/movie/{tv.Id}", + DataID = tv.Id.ToString() + } + } + }; + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/ProviderTmdb.cs b/Kyoo.TheMovieDb/ProviderTmdb.cs index d5f9f7ef..b2ec68da 100644 --- a/Kyoo.TheMovieDb/ProviderTmdb.cs +++ b/Kyoo.TheMovieDb/ProviderTmdb.cs @@ -52,11 +52,30 @@ namespace Kyoo.TheMovieDb { return item switch { + Collection collection => _GetCollection(collection) as Task, Show show => _GetShow(show) as Task, _ => null }; } - + + /// + /// Get a collection using it's id, if the id is not present in the collection, fallback to a name search. + /// + /// The show to collection for + /// A collection containing metadata from TheMovieDb + private async Task _GetCollection(Collection collection) + { + if (!collection.TryGetID(Provider.Slug, out int id)) + { + Collection found = (await _SearchCollections(collection.Name ?? collection.Slug)).FirstOrDefault(); + if (found?.TryGetID(Provider.Slug, out id) != true) + return found; + } + + TMDbClient client = new(_apiKey.Value.ApiKey); + return (await client.GetCollectionAsync(id)).ToCollection(Provider); + } + /// /// Get a show using it's id, if the id is not present in the show, fallback to a title search. /// @@ -64,8 +83,13 @@ namespace Kyoo.TheMovieDb /// A show containing metadata from TheMovieDb private async Task _GetShow(Show show) { - if (!int.TryParse(show.GetID(Provider.Name), out int id)) - return (await _SearchShows(show.Title ?? show.Slug)).FirstOrDefault(); + if (!show.TryGetID(Provider.Slug, out int id)) + { + Show found = (await _SearchShows(show.Title ?? show.Slug)).FirstOrDefault(); + if (found?.TryGetID(Provider.Slug, out id) != true) + return found; + } + TMDbClient client = new(_apiKey.Value.ApiKey); if (show.IsMovie) From 8152787f478b985dba1b28b0cd3b7c1bdd05c6ce Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 28 Jul 2021 19:30:28 +0200 Subject: [PATCH 07/34] MovieDB: Implement episode get --- .../Convertors/EpisodeConvertors.cs | 47 ++++++++ .../Convertors/SeasonConvertors.cs | 45 ++++++++ Kyoo.TheMovieDb/Kyoo.TheMovieDb.csproj | 1 + Kyoo.TheMovieDb/ProviderTmdb.cs | 104 ++++++++++-------- 4 files changed, 150 insertions(+), 47 deletions(-) create mode 100644 Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs create mode 100644 Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs diff --git a/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs b/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs new file mode 100644 index 00000000..97512aec --- /dev/null +++ b/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using Kyoo.Models; +using TMDbLib.Objects.TvShows; + +namespace Kyoo.TheMovieDb +{ + /// + /// A class containing extensions methods to convert from TMDB's types to Kyoo's types. + /// + public static partial class Convertors + { + /// + /// Convert a into a . + /// + /// The episode to convert. + /// The ID of the show inside TheMovieDb. + /// The provider representing TheMovieDb. + /// The converted episode as a . + public static Episode ToEpisode(this TvEpisode episode, int showID, Provider provider) + { + return new() + { + SeasonNumber = episode.SeasonNumber, + EpisodeNumber = episode.EpisodeNumber, + Title = episode.Name, + Overview = episode.Overview, + ReleaseDate = episode.AirDate, + Images = new Dictionary + { + [Thumbnails.Thumbnail] = episode.StillPath != null + ? $"https://image.tmdb.org/t/p/original{episode.StillPath}" + : null + }, + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/tv/{showID}" + + $"/season/{episode.SeasonNumber}/episode/{episode.EpisodeNumber}", + DataID = episode.Id.ToString() + } + } + }; + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs b/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs new file mode 100644 index 00000000..f7912f9f --- /dev/null +++ b/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using Kyoo.Models; +using TMDbLib.Objects.TvShows; + +namespace Kyoo.TheMovieDb +{ + /// + /// A class containing extensions methods to convert from TMDB's types to Kyoo's types. + /// + public static partial class Convertors + { + /// + /// Convert a into a . + /// + /// The season to convert. + /// The ID of the show inside TheMovieDb. + /// The provider representing TheMovieDb. + /// The converted season as a . + public static Season ToSeason(this TvSeason season, int showID, Provider provider) + { + return new() + { + SeasonNumber = season.SeasonNumber, + Title = season.Name, + Overview = season.Overview, + StartDate = season.AirDate, + Images = new Dictionary + { + [Thumbnails.Poster] = season.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{season.PosterPath}" + : null + }, + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/tv/{showID}/season/{season.SeasonNumber}", + DataID = season.Id?.ToString() + } + } + }; + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/Kyoo.TheMovieDb.csproj b/Kyoo.TheMovieDb/Kyoo.TheMovieDb.csproj index 3f50e6b3..99a49716 100644 --- a/Kyoo.TheMovieDb/Kyoo.TheMovieDb.csproj +++ b/Kyoo.TheMovieDb/Kyoo.TheMovieDb.csproj @@ -19,6 +19,7 @@ + diff --git a/Kyoo.TheMovieDb/ProviderTmdb.cs b/Kyoo.TheMovieDb/ProviderTmdb.cs index b2ec68da..deab03ab 100644 --- a/Kyoo.TheMovieDb/ProviderTmdb.cs +++ b/Kyoo.TheMovieDb/ProviderTmdb.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models; using Kyoo.TheMovieDb.Models; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using TMDbLib.Client; using TMDbLib.Objects.Movies; @@ -22,7 +23,11 @@ namespace Kyoo.TheMovieDb /// The API key used to authenticate with TheMovieDb API. /// private readonly IOptions _apiKey; - + /// + /// The logger to use in ase of issue. + /// + private readonly ILogger _logger; + /// public Provider Provider => new() { @@ -35,14 +40,16 @@ namespace Kyoo.TheMovieDb "blue_short-8e7b30f73a4020692ccca9c88bafe5dcb6f8a62a4c6bc55cd9ba82bb2cd95f6c.svg" } }; - + /// /// Create a new using the given api key. /// /// The api key - public TheMovieDbProvider(IOptions apiKey) + /// The logger to use in case of issue. + public TheMovieDbProvider(IOptions apiKey, ILogger logger) { _apiKey = apiKey; + _logger = logger; } @@ -54,6 +61,8 @@ namespace Kyoo.TheMovieDb { Collection collection => _GetCollection(collection) as Task, Show show => _GetShow(show) as Task, + Season season => _GetSeason(season) as Task, + Episode episode => _GetEpisode(episode) as Task, _ => null }; } @@ -104,6 +113,45 @@ namespace Kyoo.TheMovieDb ?.ToShow(Provider); } + /// + /// Get a season using it's show and it's season number. + /// + /// The season to retrieve metadata for. + /// A season containing metadata from TheMovieDb + private async Task _GetSeason(Season season) + { + if (season.Show == null || !season.Show.TryGetID(Provider.Slug, out int id)) + { + _logger.LogWarning("Metadata for a season was requested but it's show is not loaded. " + + "This is unsupported"); + return null; + } + TMDbClient client = new(_apiKey.Value.ApiKey); + return (await client.GetTvSeasonAsync(id, season.SeasonNumber)) + .ToSeason(id, Provider); + } + + /// + /// Get an episode using it's show, it's season number and it's episode number. + /// Absolute numbering is not supported. + /// + /// The episode to retrieve metadata for. + /// An episode containing metadata from TheMovieDb + private async Task _GetEpisode(Episode episode) + { + if (episode.Show == null || !episode.Show.TryGetID(Provider.Slug, out int id)) + { + _logger.LogWarning("Metadata for a season was requested but it's show is not loaded. " + + "This is unsupported"); + return null; + } + if (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); + } /// public async Task> Search(string query) @@ -113,6 +161,10 @@ namespace Kyoo.TheMovieDb return (await _SearchCollections(query) as ICollection)!; if (typeof(T) == typeof(Show)) return (await _SearchShows(query) as ICollection)!; + // if (typeof(T) == typeof(People)) + // return (await _SearchPeople(query) as ICollection)!; + // if (typeof(T) == typeof(Studio)) + // return (await _SearchStudios(query) as ICollection)!; return ArraySegment.Empty; } @@ -120,7 +172,7 @@ namespace Kyoo.TheMovieDb /// Search for a collection using it's name as a query. /// /// The query to search for - /// A collection containing metadata from TheMovieDb + /// A list of collections containing metadata from TheMovieDb private async Task> _SearchCollections(string query) { TMDbClient client = new(_apiKey.Value.ApiKey); @@ -134,7 +186,7 @@ namespace Kyoo.TheMovieDb /// Search for a show using it's name as a query. /// /// The query to search for - /// A show containing metadata from TheMovieDb + /// A list of shows containing metadata from TheMovieDb private async Task> _SearchShows(string query) { TMDbClient client = new(_apiKey.Value.ApiKey); @@ -152,47 +204,5 @@ namespace Kyoo.TheMovieDb .Where(x => x != null) .ToArray(); } - - // public async Task GetSeason(Show show, int seasonNumber) - // { - // string id = show?.GetID(Provider.Name); - // if (id == null) - // return await Task.FromResult(null); - // TMDbClient client = new TMDbClient(APIKey); - // TvSeason season = await client.GetTvSeasonAsync(int.Parse(id), seasonNumber); - // if (season == null) - // return null; - // return new Season(show.ID, - // seasonNumber, - // season.Name, - // season.Overview, - // season.AirDate?.Year, - // season.PosterPath != null ? "https://image.tmdb.org/t/p/original" + season.PosterPath : null, - // new[] {new MetadataID(Provider, $"{season.Id}", $"https://www.themoviedb.org/tv/{id}/season/{season.SeasonNumber}")}); - // } - // - // public async Task GetEpisode(Show show, int seasonNumber, int episodeNumber, int absoluteNumber) - // { - // if (seasonNumber == -1 || episodeNumber == -1) - // return await Task.FromResult(null); - // - // string id = show?.GetID(Provider.Name); - // if (id == null) - // return await Task.FromResult(null); - // TMDbClient client = new(APIKey); - // TvEpisode episode = await client.GetTvEpisodeAsync(int.Parse(id), seasonNumber, episodeNumber); - // if (episode == null) - // return null; - // return new Episode(seasonNumber, episodeNumber, absoluteNumber, - // episode.Name, - // episode.Overview, - // episode.AirDate, - // 0, - // episode.StillPath != null ? "https://image.tmdb.org/t/p/original" + episode.StillPath : null, - // new [] - // { - // new MetadataID(Provider, $"{episode.Id}", $"https://www.themoviedb.org/tv/{id}/season/{episode.SeasonNumber}/episode/{episode.EpisodeNumber}") - // }); - // } } } \ No newline at end of file From a4933e0a27b3f98441ebdbaaea1534c7407dfb9a Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 28 Jul 2021 19:36:41 +0200 Subject: [PATCH 08/34] MovieDB: Implementing person search --- .../Convertors/PeopleConvertors.cs | 31 +++++++++++++++++++ Kyoo.TheMovieDb/ProviderTmdb.cs | 18 +++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs b/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs index 8e1a326d..01a119fb 100644 --- a/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Kyoo.Models; using TMDbLib.Objects.General; +using TMDbLib.Objects.Search; using TvCast = TMDbLib.Objects.TvShows.Cast; using MovieCast = TMDbLib.Objects.Movies.Cast; @@ -115,5 +116,35 @@ namespace Kyoo.TheMovieDb Role = crew.Job }; } + + /// + /// Convert a to a . + /// + /// An internal TheMovieDB person. + /// The provider that represent TheMovieDB inside Kyoo. + /// A representing the person. + public static People ToPeople(this SearchPerson person, Provider provider) + { + return new() + { + Slug = Utility.ToSlug(person.Name), + Name = person.Name, + Images = new Dictionary + { + [Thumbnails.Poster] = person.ProfilePath != null + ? $"https://image.tmdb.org/t/p/original{person.ProfilePath}" + : null + }, + ExternalIDs = new[] + { + new MetadataID + { + Provider = provider, + DataID = person.Id.ToString(), + Link = $"https://www.themoviedb.org/person/{person.Id}" + } + } + }; + } } } \ No newline at end of file diff --git a/Kyoo.TheMovieDb/ProviderTmdb.cs b/Kyoo.TheMovieDb/ProviderTmdb.cs index deab03ab..3be0793b 100644 --- a/Kyoo.TheMovieDb/ProviderTmdb.cs +++ b/Kyoo.TheMovieDb/ProviderTmdb.cs @@ -161,8 +161,8 @@ namespace Kyoo.TheMovieDb return (await _SearchCollections(query) as ICollection)!; if (typeof(T) == typeof(Show)) return (await _SearchShows(query) as ICollection)!; - // if (typeof(T) == typeof(People)) - // return (await _SearchPeople(query) as ICollection)!; + if (typeof(T) == typeof(People)) + return (await _SearchPeople(query) as ICollection)!; // if (typeof(T) == typeof(Studio)) // return (await _SearchStudios(query) as ICollection)!; return ArraySegment.Empty; @@ -204,5 +204,19 @@ namespace Kyoo.TheMovieDb .Where(x => x != null) .ToArray(); } + + /// + /// Search for people using there name as a query. + /// + /// The query to search for + /// A list of people containing metadata from TheMovieDb + private async Task> _SearchPeople(string query) + { + TMDbClient client = new(_apiKey.Value.ApiKey); + return (await client.SearchPersonAsync(query)) + .Results + .Select(x => x.ToPeople(Provider)) + .ToArray(); + } } } \ No newline at end of file From 75b97c0150c0d11b62b69a31987252ee2ea5427c Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 28 Jul 2021 20:11:56 +0200 Subject: [PATCH 09/34] MovieDB: Implementing studio get/search --- Kyoo.Common/Models/Resources/Episode.cs | 2 +- .../Convertors/PeopleConvertors.cs | 31 ++++++++++ .../Convertors/StudioConvertors.cs | 60 +++++++++++++++++++ Kyoo.TheMovieDb/ProviderTmdb.cs | 58 +++++++++++++++++- 4 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 Kyoo.TheMovieDb/Convertors/StudioConvertors.cs diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index ad2b79bb..990ce5b6 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -85,7 +85,7 @@ namespace Kyoo.Models public int? SeasonNumber { get; set; } /// - /// The number of this episode is it's season. + /// The number of this episode in it's season. /// public int? EpisodeNumber { get; set; } diff --git a/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs b/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs index 01a119fb..27460cf0 100644 --- a/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Kyoo.Models; using TMDbLib.Objects.General; +using TMDbLib.Objects.People; using TMDbLib.Objects.Search; using TvCast = TMDbLib.Objects.TvShows.Cast; using MovieCast = TMDbLib.Objects.Movies.Cast; @@ -117,6 +118,36 @@ namespace Kyoo.TheMovieDb }; } + /// + /// Convert a to a . + /// + /// An internal TheMovieDB person. + /// The provider that represent TheMovieDB inside Kyoo. + /// A representing the person. + public static People ToPeople(this Person person, Provider provider) + { + return new() + { + Slug = Utility.ToSlug(person.Name), + Name = person.Name, + Images = new Dictionary + { + [Thumbnails.Poster] = person.ProfilePath != null + ? $"https://image.tmdb.org/t/p/original{person.ProfilePath}" + : null + }, + ExternalIDs = new[] + { + new MetadataID + { + Provider = provider, + DataID = person.Id.ToString(), + Link = $"https://www.themoviedb.org/person/{person.Id}" + } + } + }; + } + /// /// Convert a to a . /// diff --git a/Kyoo.TheMovieDb/Convertors/StudioConvertors.cs b/Kyoo.TheMovieDb/Convertors/StudioConvertors.cs new file mode 100644 index 00000000..c8947981 --- /dev/null +++ b/Kyoo.TheMovieDb/Convertors/StudioConvertors.cs @@ -0,0 +1,60 @@ +using Kyoo.Models; +using TMDbLib.Objects.Companies; +using TMDbLib.Objects.Search; + +namespace Kyoo.TheMovieDb +{ + /// + /// A class containing extensions methods to convert from TMDB's types to Kyoo's types. + /// + public static partial class Convertors + { + /// + /// Convert a into a . + /// + /// The company to convert. + /// The provider representing TheMovieDb. + /// The converted company as a . + public static Studio ToStudio(this Company company, Provider provider) + { + return new() + { + Slug = Utility.ToSlug(company.Name), + Name = company.Name, + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/company/{company.Id}", + DataID = company.Id.ToString() + } + } + }; + } + + /// + /// Convert a into a . + /// + /// The company to convert. + /// The provider representing TheMovieDb. + /// The converted company as a . + public static Studio ToStudio(this SearchCompany company, Provider provider) + { + return new() + { + Slug = Utility.ToSlug(company.Name), + Name = company.Name, + ExternalIDs = new[] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/company/{company.Id}", + DataID = company.Id.ToString() + } + } + }; + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/ProviderTmdb.cs b/Kyoo.TheMovieDb/ProviderTmdb.cs index 3be0793b..f47b2ef8 100644 --- a/Kyoo.TheMovieDb/ProviderTmdb.cs +++ b/Kyoo.TheMovieDb/ProviderTmdb.cs @@ -63,6 +63,8 @@ namespace Kyoo.TheMovieDb Show show => _GetShow(show) as Task, Season season => _GetSeason(season) as Task, Episode episode => _GetEpisode(episode) as Task, + People person => _GetPerson(person) as Task, + Studio studio => _GetStudio(studio) as Task, _ => null }; } @@ -70,7 +72,7 @@ namespace Kyoo.TheMovieDb /// /// Get a collection using it's id, if the id is not present in the collection, fallback to a name search. /// - /// The show to collection for + /// The collection to search for /// A collection containing metadata from TheMovieDb private async Task _GetCollection(Collection collection) { @@ -153,6 +155,42 @@ namespace Kyoo.TheMovieDb .ToEpisode(id, Provider); } + /// + /// Get a person using it's id, if the id is not present in the person, fallback to a name search. + /// + /// The person to search for + /// A person containing metadata from TheMovieDb + private async Task _GetPerson(People person) + { + if (!person.TryGetID(Provider.Slug, out int id)) + { + People found = (await _SearchPeople(person.Name ?? person.Slug)).FirstOrDefault(); + if (found?.TryGetID(Provider.Slug, out id) != true) + return found; + } + + 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. + /// + /// The studio to search for + /// A studio containing metadata from TheMovieDb + private async Task _GetStudio(Studio studio) + { + if (!studio.TryGetID(Provider.Slug, out int id)) + { + Studio found = (await _SearchStudios(studio.Name ?? studio.Slug)).FirstOrDefault(); + if (found?.TryGetID(Provider.Slug, out id) != true) + return found; + } + + TMDbClient client = new(_apiKey.Value.ApiKey); + return (await client.GetCompanyAsync(id)).ToStudio(Provider); + } + /// public async Task> Search(string query) where T : class, IResource @@ -163,8 +201,8 @@ namespace Kyoo.TheMovieDb return (await _SearchShows(query) as ICollection)!; if (typeof(T) == typeof(People)) return (await _SearchPeople(query) as ICollection)!; - // if (typeof(T) == typeof(Studio)) - // return (await _SearchStudios(query) as ICollection)!; + if (typeof(T) == typeof(Studio)) + return (await _SearchStudios(query) as ICollection)!; return ArraySegment.Empty; } @@ -218,5 +256,19 @@ namespace Kyoo.TheMovieDb .Select(x => x.ToPeople(Provider)) .ToArray(); } + + /// + /// Search for studios using there name as a query. + /// + /// The query to search for + /// A list of studios containing metadata from TheMovieDb + private async Task> _SearchStudios(string query) + { + TMDbClient client = new(_apiKey.Value.ApiKey); + return (await client.SearchCompanyAsync(query)) + .Results + .Select(x => x.ToStudio(Provider)) + .ToArray(); + } } } \ No newline at end of file From 0c594a92c6709fc01242beea3366a52bb97a04bb Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 28 Jul 2021 22:23:37 +0200 Subject: [PATCH 10/34] Fixing some issues --- Kyoo.Common/Models/LibraryItem.cs | 2 +- Kyoo.Common/Models/Resources/Collection.cs | 2 +- Kyoo.Common/Models/Resources/Episode.cs | 2 +- Kyoo.Common/Models/Resources/People.cs | 2 +- Kyoo.Common/Models/Resources/Provider.cs | 2 +- Kyoo.Common/Models/Resources/Season.cs | 2 +- Kyoo.Common/Models/Resources/Show.cs | 10 +++--- .../Migrations/20210728135127_Triggers.cs | 2 +- .../Database/SpecificTests/LibraryItemTest.cs | 2 +- .../Database/SpecificTests/ShowTests.cs | 33 +++++++++++++++++++ Kyoo.Tests/KAssert.cs | 7 +++- Kyoo.TheMovieDb/Convertors/ShowConvertors.cs | 2 -- Kyoo.TheMovieDb/ProviderTmdb.cs | 13 +++++--- Kyoo/Controllers/ThumbnailsManager.cs | 6 +++- Kyoo/Tasks/RegisterEpisode.cs | 9 ++--- 15 files changed, 70 insertions(+), 26 deletions(-) diff --git a/Kyoo.Common/Models/LibraryItem.cs b/Kyoo.Common/Models/LibraryItem.cs index dda95343..88fd799a 100644 --- a/Kyoo.Common/Models/LibraryItem.cs +++ b/Kyoo.Common/Models/LibraryItem.cs @@ -63,7 +63,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/{Type:l}/{Slug}/poster")] - public string Poster => Images[Thumbnails.Poster]; + public string Poster => Images?.GetValueOrDefault(Thumbnails.Poster); /// /// The type of this item (ether a collection, a show or a movie). diff --git a/Kyoo.Common/Models/Resources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs index a8b796f2..ad85ad34 100644 --- a/Kyoo.Common/Models/Resources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -30,7 +30,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/collection/{Slug}/poster")] - public string Poster => Images[Thumbnails.Poster]; + public string Poster => Images?.GetValueOrDefault(Thumbnails.Poster); /// /// The description of this collection. diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index 990ce5b6..2ab30437 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -108,7 +108,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/episodes/{Slug}/thumb")] - public string Thumb => Images[Thumbnails.Thumbnail]; + public string Thumb => Images?.GetValueOrDefault(Thumbnails.Thumbnail); /// /// The title of this episode. diff --git a/Kyoo.Common/Models/Resources/People.cs b/Kyoo.Common/Models/Resources/People.cs index 5b7f6864..d555a10d 100644 --- a/Kyoo.Common/Models/Resources/People.cs +++ b/Kyoo.Common/Models/Resources/People.cs @@ -28,7 +28,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/people/{Slug}/poster")] - public string Poster => Images[Thumbnails.Poster]; + public string Poster => Images?.GetValueOrDefault(Thumbnails.Poster); /// [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } diff --git a/Kyoo.Common/Models/Resources/Provider.cs b/Kyoo.Common/Models/Resources/Provider.cs index 630a2880..920b7345 100644 --- a/Kyoo.Common/Models/Resources/Provider.cs +++ b/Kyoo.Common/Models/Resources/Provider.cs @@ -31,7 +31,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/providers/{Slug}/logo")] - public string Logo => Images[Thumbnails.Logo]; + public string Logo => Images?.GetValueOrDefault(Thumbnails.Logo); /// /// The extension of the logo. This is used for http responses. diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index fddb7eb4..46639c3c 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -83,7 +83,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/seasons/{Slug}/thumb")] - public string Poster => Images[Thumbnails.Poster]; + public string Poster => Images?.GetValueOrDefault(Thumbnails.Poster); /// [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index 90aa98f8..afaaa481 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; using Kyoo.Common.Models.Attributes; using Kyoo.Controllers; using Kyoo.Models.Attributes; @@ -49,7 +47,7 @@ namespace Kyoo.Models /// An URL to a trailer. This could be any path supported by the . /// /// TODO for now, this is set to a youtube url. It should be cached and converted to a local file. - public string TrailerUrl => Images[Thumbnails.Trailer]; + public string TrailerUrl => Images?.GetValueOrDefault(Thumbnails.Trailer); /// /// The date this show started airing. It can be null if this is unknown. @@ -72,7 +70,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/shows/{Slug}/poster")] - public string Poster => Images[Thumbnails.Poster]; + public string Poster => Images?.GetValueOrDefault(Thumbnails.Poster); /// /// The path of this show's logo. @@ -80,7 +78,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/shows/{Slug}/logo")] - public string Logo => Images[Thumbnails.Logo]; + public string Logo => Images?.GetValueOrDefault(Thumbnails.Logo); /// /// The path of this show's backdrop. @@ -88,7 +86,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] - public string Backdrop => Images[Thumbnails.Thumbnail]; + public string Backdrop => Images?.GetValueOrDefault(Thumbnails.Thumbnail); /// /// True if this show represent a movie, false otherwise. diff --git a/Kyoo.SqLite/Migrations/20210728135127_Triggers.cs b/Kyoo.SqLite/Migrations/20210728135127_Triggers.cs index f26ab7e2..fe46cd26 100644 --- a/Kyoo.SqLite/Migrations/20210728135127_Triggers.cs +++ b/Kyoo.SqLite/Migrations/20210728135127_Triggers.cs @@ -165,7 +165,7 @@ namespace Kyoo.SqLite.Migrations INNER JOIN Collections AS c ON l.FirstID = c.ID WHERE s.ID = l.SecondID)) UNION ALL - SELECT -c0.ID, c0.Slug, c0.Name AS Title, c0.Overview, 3 AS Status, + SELECT -c0.ID, c0.Slug, c0.Name AS Title, c0.Overview, 0 AS Status, NULL AS StartAir, NULL AS EndAir, c0.Images, 2 AS Type FROM collections AS c0"); } diff --git a/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs b/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs index c5639dbb..e6a913d2 100644 --- a/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs +++ b/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs @@ -55,7 +55,7 @@ namespace Kyoo.Tests.Database [Fact] public async Task GetCollectionTests() { - LibraryItem expected = new(TestSample.Get()); + LibraryItem expected = new(TestSample.Get()); LibraryItem actual = await _repository.Get(-1); KAssert.DeepEqual(expected, actual); } diff --git a/Kyoo.Tests/Database/SpecificTests/ShowTests.cs b/Kyoo.Tests/Database/SpecificTests/ShowTests.cs index 42ab693c..c7b89be6 100644 --- a/Kyoo.Tests/Database/SpecificTests/ShowTests.cs +++ b/Kyoo.Tests/Database/SpecificTests/ShowTests.cs @@ -235,6 +235,39 @@ 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) + .Include(x => x.Genres) + .Include(x => x.People) + .Include(x => x.Studio) + .FirstAsync(x => x.ID == created.ID); + KAssert.DeepEqual(expected, retrieved); + } + + [Fact] + public async Task CreateWithExternalID() + { + Show expected = TestSample.Get(); + expected.ID = 0; + expected.Slug = "created-relation-test"; + expected.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + DataID = "ID" + } + }; + 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) + .FirstAsync(x => x.ID == created.ID); + KAssert.DeepEqual(expected, retrieved); + Assert.Equal(1, retrieved.ExternalIDs.Count); + Assert.Equal("ID", retrieved.ExternalIDs.First().DataID); } [Fact] diff --git a/Kyoo.Tests/KAssert.cs b/Kyoo.Tests/KAssert.cs index 80209c98..3acee11a 100644 --- a/Kyoo.Tests/KAssert.cs +++ b/Kyoo.Tests/KAssert.cs @@ -1,3 +1,5 @@ +using System.Collections; +using System.Linq; using System.Reflection; using JetBrains.Annotations; using Xunit; @@ -19,8 +21,11 @@ namespace Kyoo.Tests [AssertionMethod] public static void DeepEqual(T expected, T value) { - foreach (PropertyInfo property in typeof(T).GetProperties(BindingFlags.Instance)) + PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public); + foreach (PropertyInfo property in properties) Assert.Equal(property.GetValue(expected), property.GetValue(value)); + if (!properties.Any()) + Assert.Equal(expected, value); } /// diff --git a/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs b/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs index f379e19b..a64cfe3b 100644 --- a/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs @@ -44,7 +44,6 @@ namespace Kyoo.TheMovieDb Studio = !string.IsNullOrEmpty(tv.ProductionCompanies.FirstOrDefault()?.Name) ? new Studio(tv.ProductionCompanies.First().Name) : null, - IsMovie = true, People = tv.Credits.Cast .Select(x => x.ToPeople(provider)) .Concat(tv.Credits.Crew.Select(x => x.ToPeople(provider))) @@ -84,7 +83,6 @@ namespace Kyoo.TheMovieDb ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" : null, }, - IsMovie = true, ExternalIDs = new [] { new MetadataID diff --git a/Kyoo.TheMovieDb/ProviderTmdb.cs b/Kyoo.TheMovieDb/ProviderTmdb.cs index f47b2ef8..96bbfb5a 100644 --- a/Kyoo.TheMovieDb/ProviderTmdb.cs +++ b/Kyoo.TheMovieDb/ProviderTmdb.cs @@ -122,12 +122,16 @@ namespace Kyoo.TheMovieDb /// A season containing metadata from TheMovieDb private async Task _GetSeason(Season season) { - if (season.Show == null || !season.Show.TryGetID(Provider.Slug, out int id)) + if (season.Show == null) { _logger.LogWarning("Metadata for a season was requested but it's show is not loaded. " + "This is unsupported"); return null; } + + 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); @@ -141,13 +145,14 @@ namespace Kyoo.TheMovieDb /// An episode containing metadata from TheMovieDb private async Task _GetEpisode(Episode episode) { - if (episode.Show == null || !episode.Show.TryGetID(Provider.Slug, out int id)) + if (episode.Show == null) { - _logger.LogWarning("Metadata for a season was requested but it's show is not loaded. " + + _logger.LogWarning("Metadata for an episode was requested but it's show is not loaded. " + "This is unsupported"); return null; } - if (episode.SeasonNumber == null || episode.EpisodeNumber == null) + if (!episode.Show.TryGetID(Provider.Slug, out int id) + || episode.SeasonNumber == null || episode.EpisodeNumber == null) return null; TMDbClient client = new(_apiKey.Value.ApiKey); diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index 61d14613..bd24d975 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -1,6 +1,7 @@ using Kyoo.Models; using System; using System.IO; +using System.Linq; using System.Threading.Tasks; using Kyoo.Models.Options; using Microsoft.Extensions.Logging; @@ -87,10 +88,13 @@ namespace Kyoo.Controllers if (item == null) throw new ArgumentNullException(nameof(item)); + if (item.Images == null) + return false; + string name = item is IResource res ? res.Slug : "???"; bool ret = false; - foreach ((int id, string image) in item.Images) + foreach ((int id, string image) in item.Images.Where(x => x.Value != null)) { string localPath = await GetImagePath(item, id); if (alwaysDownload || !await _files.Exists(localPath)) diff --git a/Kyoo/Tasks/RegisterEpisode.cs b/Kyoo/Tasks/RegisterEpisode.cs index 033bfceb..1876d9c5 100644 --- a/Kyoo/Tasks/RegisterEpisode.cs +++ b/Kyoo/Tasks/RegisterEpisode.cs @@ -116,9 +116,6 @@ namespace Kyoo.Tasks else show = registeredShow; - // If they are not already loaded, load external ids to allow metadata providers to use them. - if (show.ExternalIDs == null) - await _libraryManager.Load(show, x => x.ExternalIDs); progress.Report(50); if (season != null) @@ -163,14 +160,18 @@ namespace Kyoo.Tasks /// The type of the item /// The existing or filled item. private async Task _RegisterAndFill(T item) - where T : class, IResource, IThumbnails + where T : class, IResource, IThumbnails, IMetadata { if (item == null || string.IsNullOrEmpty(item.Slug)) return null; T existing = await _libraryManager.GetOrDefault(item.Slug); if (existing != null) + { + await _libraryManager.Load(existing, x => x.ExternalIDs); return existing; + } + item = await _metadataProvider.Get(item); await _thumbnailsManager.DownloadImages(item); return await _libraryManager.CreateIfNotExists(item); From 756bfa762c1b0bb051195e7b2ec82ba765731271 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 30 Jul 2021 23:52:53 +0200 Subject: [PATCH 11/34] Removing links --- Kyoo.Common/Kyoo.Common.csproj | 2 +- Kyoo.Common/Models/Resources/Provider.cs | 7 +- Kyoo.Common/Models/Resources/User.cs | 26 +- Kyoo.CommonAPI/DatabaseContext.cs | 89 +-- ....cs => 20210730183548_Initial.Designer.cs} | 533 ++++++++---------- ...0_Initial.cs => 20210730183548_Initial.cs} | 132 ++--- ...cs => 20210730203102_Triggers.Designer.cs} | 533 ++++++++---------- ...Triggers.cs => 20210730203102_Triggers.cs} | 4 +- .../PostgresContextModelSnapshot.cs | 531 ++++++++--------- Kyoo.Postgresql/PostgresContext.cs | 10 + Kyoo.Postgresql/PostgresModule.cs | 2 +- ....cs => 20210730203155_Initial.Designer.cs} | 449 +++++++-------- ...2_Initial.cs => 20210730203155_Initial.cs} | 168 +++--- ...cs => 20210730203746_Triggers.Designer.cs} | 449 +++++++-------- ...Triggers.cs => 20210730203746_Triggers.cs} | 6 +- .../Migrations/SqLiteContextModelSnapshot.cs | 447 +++++++-------- Kyoo.SqLite/SqLiteContext.cs | 9 + Kyoo.SqLite/SqLiteModule.cs | 2 +- Kyoo.Tests/Database/RepositoryActivator.cs | 43 +- .../Database/SpecificTests/SanityTests.cs | 14 +- .../Database/SpecificTests/ShowTests.cs | 12 +- Kyoo.Tests/Database/TestSample.cs | 26 +- Kyoo.Tests/KAssert.cs | 11 +- Kyoo.Tests/Kyoo.Tests.csproj | 1 + Kyoo.TheMovieDb/ProviderTmdb.cs | 1 - Kyoo.TheTvdb/ProviderTvdb.cs | 1 - .../Repositories/CollectionRepository.cs | 1 + .../Repositories/EpisodeRepository.cs | 1 + .../Repositories/LibraryRepository.cs | 16 +- .../Repositories/PeopleRepository.cs | 1 + .../Repositories/SeasonRepository.cs | 1 + .../Repositories/ShowRepository.cs | 18 +- .../Repositories/StudioRepository.cs | 1 + Kyoo/Controllers/ThumbnailsManager.cs | 5 +- Kyoo/Kyoo.csproj | 2 +- 35 files changed, 1618 insertions(+), 1936 deletions(-) rename Kyoo.Postgresql/Migrations/{20210728141410_Initial.Designer.cs => 20210730183548_Initial.Designer.cs} (85%) rename Kyoo.Postgresql/Migrations/{20210728141410_Initial.cs => 20210730183548_Initial.cs} (90%) rename Kyoo.Postgresql/Migrations/{20210728142213_Triggers.Designer.cs => 20210730203102_Triggers.Designer.cs} (85%) rename Kyoo.Postgresql/Migrations/{20210728142213_Triggers.cs => 20210730203102_Triggers.cs} (98%) rename Kyoo.SqLite/Migrations/{20210728134952_Initial.Designer.cs => 20210730203155_Initial.Designer.cs} (81%) rename Kyoo.SqLite/Migrations/{20210728134952_Initial.cs => 20210730203155_Initial.cs} (86%) rename Kyoo.SqLite/Migrations/{20210728135127_Triggers.Designer.cs => 20210730203746_Triggers.Designer.cs} (81%) rename Kyoo.SqLite/Migrations/{20210728135127_Triggers.cs => 20210730203746_Triggers.cs} (97%) diff --git a/Kyoo.Common/Kyoo.Common.csproj b/Kyoo.Common/Kyoo.Common.csproj index fcf33415..c4c2f3d6 100644 --- a/Kyoo.Common/Kyoo.Common.csproj +++ b/Kyoo.Common/Kyoo.Common.csproj @@ -17,7 +17,7 @@ snupkg default - ENABLE_INTERNAL_LINKS + diff --git a/Kyoo.Common/Models/Resources/Provider.cs b/Kyoo.Common/Models/Resources/Provider.cs index 920b7345..35f94fc9 100644 --- a/Kyoo.Common/Models/Resources/Provider.cs +++ b/Kyoo.Common/Models/Resources/Provider.cs @@ -32,12 +32,7 @@ namespace Kyoo.Models /// [SerializeAs("{HOST}/api/providers/{Slug}/logo")] public string Logo => Images?.GetValueOrDefault(Thumbnails.Logo); - - /// - /// The extension of the logo. This is used for http responses. - /// - [SerializeIgnore] public string LogoExtension { get; set; } - + /// /// The list of libraries that uses this provider. /// diff --git a/Kyoo.Common/Models/Resources/User.cs b/Kyoo.Common/Models/Resources/User.cs index 05f56534..8cae9671 100644 --- a/Kyoo.Common/Models/Resources/User.cs +++ b/Kyoo.Common/Models/Resources/User.cs @@ -6,7 +6,7 @@ namespace Kyoo.Models /// /// A single user of the app. /// - public class User : IResource + public class User : IResource, IThumbnails { /// public int ID { get; set; } @@ -38,7 +38,10 @@ namespace Kyoo.Models /// Arbitrary extra data that can be used by specific authentication implementations. /// public Dictionary ExtraData { get; set; } - + + /// + public Dictionary Images { get; set; } + /// /// The list of shows the user has finished. /// @@ -48,7 +51,7 @@ namespace Kyoo.Models /// The list of episodes the user is watching (stopped in progress or the next episode of the show) /// public ICollection CurrentlyWatching { get; set; } - + #if ENABLE_INTERNAL_LINKS /// /// Links between Users and Shows. @@ -60,8 +63,23 @@ namespace Kyoo.Models /// /// Metadata of episode currently watching by an user /// - public class WatchedEpisode : Link + public class WatchedEpisode { + /// + /// The ID of the user that started watching this episode. + /// + public int UserID { get; set; } + + /// + /// The ID of the episode started. + /// + public int EpisodeID { get; set; } + + /// + /// The started. + /// + public Episode Episode { get; set; } + /// /// Where the player has stopped watching the episode (between 0 and 100). /// diff --git a/Kyoo.CommonAPI/DatabaseContext.cs b/Kyoo.CommonAPI/DatabaseContext.cs index 6d1fec11..d2eb8e2f 100644 --- a/Kyoo.CommonAPI/DatabaseContext.cs +++ b/Kyoo.CommonAPI/DatabaseContext.cs @@ -84,14 +84,6 @@ namespace Kyoo /// public DbSet LibraryItems { get; set; } - /// - /// Get the name of the metadata table of the given type. - /// - /// The type related to the metadata - /// The name of the table containing the metadata. - protected abstract string MetadataName() - where T : IMetadata; - /// /// Get all metadataIDs (ExternalIDs) of a given resource. See . /// @@ -131,6 +123,24 @@ namespace Kyoo : base(options) { } + /// + /// Get the name of the metadata table of the given type. + /// + /// The type related to the metadata + /// The name of the table containing the metadata. + protected abstract string MetadataName() + where T : IMetadata; + + /// + /// Get the name of the link table of the two given types. + /// + /// The owner type of the relation + /// The child type of the relation + /// The name of the table containing the links. + protected abstract string LinkName() + where T : IResource + where T2 : IResource; + /// /// Set basic configurations (like preventing query tracking) /// @@ -196,74 +206,27 @@ namespace Kyoo modelBuilder.Entity() .HasMany(x => x.Libraries) .WithMany(x => x.Providers) - .UsingEntity>( - y => y - .HasOne(x => x.First) - .WithMany(x => x.ProviderLinks), - y => y - .HasOne(x => x.Second) - .WithMany(x => x.LibraryLinks), - y => y.HasKey(Link.PrimaryKey)); - + .UsingEntity(x => x.ToTable(LinkName())); modelBuilder.Entity() .HasMany(x => x.Libraries) .WithMany(x => x.Collections) - .UsingEntity>( - y => y - .HasOne(x => x.First) - .WithMany(x => x.CollectionLinks), - y => y - .HasOne(x => x.Second) - .WithMany(x => x.LibraryLinks), - y => y.HasKey(Link.PrimaryKey)); - + .UsingEntity(x => x.ToTable(LinkName())); modelBuilder.Entity() .HasMany(x => x.Libraries) .WithMany(x => x.Shows) - .UsingEntity>( - y => y - .HasOne(x => x.First) - .WithMany(x => x.ShowLinks), - y => y - .HasOne(x => x.Second) - .WithMany(x => x.LibraryLinks), - y => y.HasKey(Link.PrimaryKey)); - + .UsingEntity(x => x.ToTable(LinkName())); modelBuilder.Entity() .HasMany(x => x.Collections) .WithMany(x => x.Shows) - .UsingEntity>( - y => y - .HasOne(x => x.First) - .WithMany(x => x.ShowLinks), - y => y - .HasOne(x => x.Second) - .WithMany(x => x.CollectionLinks), - y => y.HasKey(Link.PrimaryKey)); - + .UsingEntity(x => x.ToTable(LinkName())); modelBuilder.Entity() .HasMany(x => x.Shows) .WithMany(x => x.Genres) - .UsingEntity>( - y => y - .HasOne(x => x.First) - .WithMany(x => x.GenreLinks), - y => y - .HasOne(x => x.Second) - .WithMany(x => x.ShowLinks), - y => y.HasKey(Link.PrimaryKey)); - + .UsingEntity(x => x.ToTable(LinkName())); modelBuilder.Entity() .HasMany(x => x.Watched) - .WithMany("users") - .UsingEntity>( - y => y - .HasOne(x => x.Second) - .WithMany(), - y => y - .HasOne(x => x.First) - .WithMany(x => x.ShowLinks), - y => y.HasKey(Link.PrimaryKey)); + .WithMany("Users") + .UsingEntity(x => x.ToTable(LinkName())); _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); @@ -273,7 +236,7 @@ namespace Kyoo _HasMetadata(modelBuilder); modelBuilder.Entity() - .HasKey(x => new { First = x.FirstID, Second = x.SecondID }); + .HasKey(x => new { User = x.UserID, Episode = x.EpisodeID }); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); diff --git a/Kyoo.Postgresql/Migrations/20210728141410_Initial.Designer.cs b/Kyoo.Postgresql/Migrations/20210730183548_Initial.Designer.cs similarity index 85% rename from Kyoo.Postgresql/Migrations/20210728141410_Initial.Designer.cs rename to Kyoo.Postgresql/Migrations/20210730183548_Initial.Designer.cs index 8722ada5..10c42bb3 100644 --- a/Kyoo.Postgresql/Migrations/20210728141410_Initial.Designer.cs +++ b/Kyoo.Postgresql/Migrations/20210730183548_Initial.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Postgresql.Migrations { [DbContext(typeof(PostgresContext))] - [Migration("20210728141410_Initial")] + [Migration("20210730183548_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -26,6 +26,63 @@ namespace Kyoo.Postgresql.Migrations .HasAnnotation("ProductVersion", "5.0.8") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + modelBuilder.Entity("CollectionLibrary", b => + { + b.Property("CollectionsID") + .HasColumnType("integer") + .HasColumnName("collections_id"); + + b.Property("LibrariesID") + .HasColumnType("integer") + .HasColumnName("libraries_id"); + + b.HasKey("CollectionsID", "LibrariesID") + .HasName("pk_link_library_collection"); + + b.HasIndex("LibrariesID") + .HasDatabaseName("ix_link_library_collection_libraries_id"); + + b.ToTable("link_library_collection"); + }); + + modelBuilder.Entity("CollectionShow", b => + { + b.Property("CollectionsID") + .HasColumnType("integer") + .HasColumnName("collections_id"); + + b.Property("ShowsID") + .HasColumnType("integer") + .HasColumnName("shows_id"); + + b.HasKey("CollectionsID", "ShowsID") + .HasName("pk_link_collection_show"); + + b.HasIndex("ShowsID") + .HasDatabaseName("ix_link_collection_show_shows_id"); + + b.ToTable("link_collection_show"); + }); + + modelBuilder.Entity("GenreShow", b => + { + b.Property("GenresID") + .HasColumnType("integer") + .HasColumnName("genres_id"); + + b.Property("ShowsID") + .HasColumnType("integer") + .HasColumnName("shows_id"); + + b.HasKey("GenresID", "ShowsID") + .HasName("pk_link_show_genre"); + + b.HasIndex("ShowsID") + .HasDatabaseName("ix_link_show_genre_shows_id"); + + b.ToTable("link_show_genre"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") @@ -234,120 +291,6 @@ namespace Kyoo.Postgresql.Migrations b.ToView("library_items"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_collection_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_collection_show_second_id"); - - b.ToTable("link_collection_show"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_collection"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_collection_second_id"); - - b.ToTable("link_library_collection"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_provider"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_provider_second_id"); - - b.ToTable("link_library_provider"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_show_second_id"); - - b.ToTable("link_library_show"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_show_genre"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_show_genre_second_id"); - - b.ToTable("link_show_genre"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_user_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_user_show_second_id"); - - b.ToTable("link_user_show"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") @@ -427,10 +370,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("jsonb") .HasColumnName("images"); - b.Property("LogoExtension") - .HasColumnType("text") - .HasColumnName("logo_extension"); - b.Property("Name") .HasColumnType("text") .HasColumnName("name"); @@ -681,6 +620,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("jsonb") .HasColumnName("extra_data"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Password") .HasColumnType("text") .HasColumnName("password"); @@ -710,27 +653,84 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.Property("FirstID") + b.Property("UserID") .HasColumnType("integer") - .HasColumnName("first_id"); + .HasColumnName("user_id"); - b.Property("SecondID") + b.Property("EpisodeID") .HasColumnType("integer") - .HasColumnName("second_id"); + .HasColumnName("episode_id"); b.Property("WatchedPercentage") .HasColumnType("integer") .HasColumnName("watched_percentage"); - b.HasKey("FirstID", "SecondID") + b.HasKey("UserID", "EpisodeID") .HasName("pk_watched_episodes"); - b.HasIndex("SecondID") - .HasDatabaseName("ix_watched_episodes_second_id"); + b.HasIndex("EpisodeID") + .HasDatabaseName("ix_watched_episodes_episode_id"); b.ToTable("watched_episodes"); }); + modelBuilder.Entity("LibraryProvider", b => + { + b.Property("LibrariesID") + .HasColumnType("integer") + .HasColumnName("libraries_id"); + + b.Property("ProvidersID") + .HasColumnType("integer") + .HasColumnName("providers_id"); + + b.HasKey("LibrariesID", "ProvidersID") + .HasName("pk_link_library_provider"); + + b.HasIndex("ProvidersID") + .HasDatabaseName("ix_link_library_provider_providers_id"); + + b.ToTable("link_library_provider"); + }); + + modelBuilder.Entity("LibraryShow", b => + { + b.Property("LibrariesID") + .HasColumnType("integer") + .HasColumnName("libraries_id"); + + b.Property("ShowsID") + .HasColumnType("integer") + .HasColumnName("shows_id"); + + b.HasKey("LibrariesID", "ShowsID") + .HasName("pk_link_library_show"); + + b.HasIndex("ShowsID") + .HasDatabaseName("ix_link_library_show_shows_id"); + + b.ToTable("link_library_show"); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.Property("UsersID") + .HasColumnType("integer") + .HasColumnName("users_id"); + + b.Property("WatchedID") + .HasColumnType("integer") + .HasColumnName("watched_id"); + + b.HasKey("UsersID", "WatchedID") + .HasName("pk_link_user_show"); + + b.HasIndex("WatchedID") + .HasDatabaseName("ix_link_user_show_watched_id"); + + b.ToTable("link_user_show"); + }); + modelBuilder.Entity("collection_metadata_id", b => { b.Property("ResourceID") @@ -893,6 +893,57 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("studio_metadata_id"); }); + modelBuilder.Entity("CollectionLibrary", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") + .HasConstraintName("fk_link_library_collection_collections_collections_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .HasConstraintName("fk_link_library_collection_libraries_libraries_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("CollectionShow", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") + .HasConstraintName("fk_link_collection_show_collections_collections_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .HasConstraintName("fk_link_collection_show_shows_shows_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GenreShow", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("GenresID") + .HasConstraintName("fk_link_show_genre_genres_genres_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .HasConstraintName("fk_link_show_genre_shows_shows_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -913,132 +964,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Collection", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_collection_show_collections_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("CollectionLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_collection_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("CollectionLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_collection_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Collection", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_collection_collections_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ProviderLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_provider_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_provider_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_show_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("GenreLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_show_genre_shows_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Genre", "Second") - .WithMany("ShowLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_show_genre_genres_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_user_show_users_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_user_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -1097,23 +1022,72 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("CurrentlyWatching") - .HasForeignKey("FirstID") - .HasConstraintName("fk_watched_episodes_users_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Episode", "Second") + b.HasOne("Kyoo.Models.Episode", "Episode") .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_watched_episodes_episodes_second_id") + .HasForeignKey("EpisodeID") + .HasConstraintName("fk_watched_episodes_episodes_episode_id") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("First"); + b.HasOne("Kyoo.Models.User", null) + .WithMany("CurrentlyWatching") + .HasForeignKey("UserID") + .HasConstraintName("fk_watched_episodes_users_user_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Second"); + b.Navigation("Episode"); + }); + + modelBuilder.Entity("LibraryProvider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .HasConstraintName("fk_link_library_provider_libraries_libraries_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("ProvidersID") + .HasConstraintName("fk_link_library_provider_providers_providers_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LibraryShow", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .HasConstraintName("fk_link_library_show_libraries_libraries_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .HasConstraintName("fk_link_library_show_shows_shows_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.HasOne("Kyoo.Models.User", null) + .WithMany() + .HasForeignKey("UsersID") + .HasConstraintName("fk_link_user_show_users_users_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("WatchedID") + .HasConstraintName("fk_link_user_show_shows_watched_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("collection_metadata_id", b => @@ -1233,10 +1207,6 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Navigation("ExternalIDs"); - - b.Navigation("LibraryLinks"); - - b.Navigation("ShowLinks"); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -1246,20 +1216,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Tracks"); }); - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Navigation("CollectionLinks"); - - b.Navigation("ProviderLinks"); - - b.Navigation("ShowLinks"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Navigation("ExternalIDs"); @@ -1267,11 +1223,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Roles"); }); - modelBuilder.Entity("Kyoo.Models.Provider", b => - { - b.Navigation("LibraryLinks"); - }); - modelBuilder.Entity("Kyoo.Models.Season", b => { b.Navigation("Episodes"); @@ -1281,16 +1232,10 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.Show", b => { - b.Navigation("CollectionLinks"); - b.Navigation("Episodes"); b.Navigation("ExternalIDs"); - b.Navigation("GenreLinks"); - - b.Navigation("LibraryLinks"); - b.Navigation("People"); b.Navigation("Seasons"); @@ -1306,8 +1251,6 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.User", b => { b.Navigation("CurrentlyWatching"); - - b.Navigation("ShowLinks"); }); #pragma warning restore 612, 618 } diff --git a/Kyoo.Postgresql/Migrations/20210728141410_Initial.cs b/Kyoo.Postgresql/Migrations/20210730183548_Initial.cs similarity index 90% rename from Kyoo.Postgresql/Migrations/20210728141410_Initial.cs rename to Kyoo.Postgresql/Migrations/20210730183548_Initial.cs index 46c09222..dc37cba9 100644 --- a/Kyoo.Postgresql/Migrations/20210728141410_Initial.cs +++ b/Kyoo.Postgresql/Migrations/20210730183548_Initial.cs @@ -83,8 +83,7 @@ namespace Kyoo.Postgresql.Migrations .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), - logo_extension = table.Column(type: "text", nullable: true) + images = table.Column>(type: "jsonb", nullable: true) }, constraints: table => { @@ -116,7 +115,8 @@ namespace Kyoo.Postgresql.Migrations 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) + extra_data = table.Column>(type: "jsonb", nullable: true), + images = table.Column>(type: "jsonb", nullable: true) }, constraints: table => { @@ -127,21 +127,21 @@ namespace Kyoo.Postgresql.Migrations name: "link_library_collection", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false) + collections_id = table.Column(type: "integer", nullable: false), + libraries_id = table.Column(type: "integer", nullable: false) }, constraints: table => { - table.PrimaryKey("pk_link_library_collection", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_link_library_collection", x => new { x.collections_id, x.libraries_id }); table.ForeignKey( - name: "fk_link_library_collection_collections_second_id", - column: x => x.second_id, + name: "fk_link_library_collection_collections_collections_id", + column: x => x.collections_id, principalTable: "collections", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_link_library_collection_libraries_first_id", - column: x => x.first_id, + name: "fk_link_library_collection_libraries_libraries_id", + column: x => x.libraries_id, principalTable: "libraries", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -177,21 +177,21 @@ namespace Kyoo.Postgresql.Migrations name: "link_library_provider", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false) + libraries_id = table.Column(type: "integer", nullable: false), + providers_id = table.Column(type: "integer", nullable: false) }, constraints: table => { - table.PrimaryKey("pk_link_library_provider", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_link_library_provider", x => new { x.libraries_id, x.providers_id }); table.ForeignKey( - name: "fk_link_library_provider_libraries_first_id", - column: x => x.first_id, + name: "fk_link_library_provider_libraries_libraries_id", + column: x => x.libraries_id, principalTable: "libraries", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_link_library_provider_providers_second_id", - column: x => x.second_id, + name: "fk_link_library_provider_providers_providers_id", + column: x => x.providers_id, principalTable: "providers", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -282,21 +282,21 @@ namespace Kyoo.Postgresql.Migrations name: "link_collection_show", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false) + collections_id = table.Column(type: "integer", nullable: false), + shows_id = table.Column(type: "integer", nullable: false) }, constraints: table => { - table.PrimaryKey("pk_link_collection_show", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_link_collection_show", x => new { x.collections_id, x.shows_id }); table.ForeignKey( - name: "fk_link_collection_show_collections_first_id", - column: x => x.first_id, + name: "fk_link_collection_show_collections_collections_id", + column: x => x.collections_id, principalTable: "collections", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_link_collection_show_shows_second_id", - column: x => x.second_id, + name: "fk_link_collection_show_shows_shows_id", + column: x => x.shows_id, principalTable: "shows", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -306,21 +306,21 @@ namespace Kyoo.Postgresql.Migrations name: "link_library_show", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false) + libraries_id = table.Column(type: "integer", nullable: false), + shows_id = table.Column(type: "integer", nullable: false) }, constraints: table => { - table.PrimaryKey("pk_link_library_show", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_link_library_show", x => new { x.libraries_id, x.shows_id }); table.ForeignKey( - name: "fk_link_library_show_libraries_first_id", - column: x => x.first_id, + name: "fk_link_library_show_libraries_libraries_id", + column: x => x.libraries_id, principalTable: "libraries", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_link_library_show_shows_second_id", - column: x => x.second_id, + name: "fk_link_library_show_shows_shows_id", + column: x => x.shows_id, principalTable: "shows", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -330,21 +330,21 @@ namespace Kyoo.Postgresql.Migrations name: "link_show_genre", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false) + genres_id = table.Column(type: "integer", nullable: false), + shows_id = table.Column(type: "integer", nullable: false) }, constraints: table => { - table.PrimaryKey("pk_link_show_genre", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_link_show_genre", x => new { x.genres_id, x.shows_id }); table.ForeignKey( - name: "fk_link_show_genre_genres_second_id", - column: x => x.second_id, + name: "fk_link_show_genre_genres_genres_id", + column: x => x.genres_id, principalTable: "genres", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_link_show_genre_shows_first_id", - column: x => x.first_id, + name: "fk_link_show_genre_shows_shows_id", + column: x => x.shows_id, principalTable: "shows", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -354,21 +354,21 @@ namespace Kyoo.Postgresql.Migrations name: "link_user_show", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false) + 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.first_id, x.second_id }); + table.PrimaryKey("pk_link_user_show", x => new { x.users_id, x.watched_id }); table.ForeignKey( - name: "fk_link_user_show_shows_second_id", - column: x => x.second_id, + 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_first_id", - column: x => x.first_id, + name: "fk_link_user_show_users_users_id", + column: x => x.users_id, principalTable: "users", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -574,22 +574,22 @@ namespace Kyoo.Postgresql.Migrations name: "watched_episodes", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false), + 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.first_id, x.second_id }); + table.PrimaryKey("pk_watched_episodes", x => new { x.user_id, x.episode_id }); table.ForeignKey( - name: "fk_watched_episodes_episodes_second_id", - column: x => x.second_id, + 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_first_id", - column: x => x.first_id, + name: "fk_watched_episodes_users_user_id", + column: x => x.user_id, principalTable: "users", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -641,34 +641,34 @@ namespace Kyoo.Postgresql.Migrations unique: true); migrationBuilder.CreateIndex( - name: "ix_link_collection_show_second_id", + name: "ix_link_collection_show_shows_id", table: "link_collection_show", - column: "second_id"); + column: "shows_id"); migrationBuilder.CreateIndex( - name: "ix_link_library_collection_second_id", + name: "ix_link_library_collection_libraries_id", table: "link_library_collection", - column: "second_id"); + column: "libraries_id"); migrationBuilder.CreateIndex( - name: "ix_link_library_provider_second_id", + name: "ix_link_library_provider_providers_id", table: "link_library_provider", - column: "second_id"); + column: "providers_id"); migrationBuilder.CreateIndex( - name: "ix_link_library_show_second_id", + name: "ix_link_library_show_shows_id", table: "link_library_show", - column: "second_id"); + column: "shows_id"); migrationBuilder.CreateIndex( - name: "ix_link_show_genre_second_id", + name: "ix_link_show_genre_shows_id", table: "link_show_genre", - column: "second_id"); + column: "shows_id"); migrationBuilder.CreateIndex( - name: "ix_link_user_show_second_id", + name: "ix_link_user_show_watched_id", table: "link_user_show", - column: "second_id"); + column: "watched_id"); migrationBuilder.CreateIndex( name: "ix_people_slug", @@ -760,9 +760,9 @@ namespace Kyoo.Postgresql.Migrations unique: true); migrationBuilder.CreateIndex( - name: "ix_watched_episodes_second_id", + name: "ix_watched_episodes_episode_id", table: "watched_episodes", - column: "second_id"); + column: "episode_id"); } protected override void Down(MigrationBuilder migrationBuilder) diff --git a/Kyoo.Postgresql/Migrations/20210728142213_Triggers.Designer.cs b/Kyoo.Postgresql/Migrations/20210730203102_Triggers.Designer.cs similarity index 85% rename from Kyoo.Postgresql/Migrations/20210728142213_Triggers.Designer.cs rename to Kyoo.Postgresql/Migrations/20210730203102_Triggers.Designer.cs index 708cd23b..ff1ae33b 100644 --- a/Kyoo.Postgresql/Migrations/20210728142213_Triggers.Designer.cs +++ b/Kyoo.Postgresql/Migrations/20210730203102_Triggers.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Postgresql.Migrations { [DbContext(typeof(PostgresContext))] - [Migration("20210728142213_Triggers")] + [Migration("20210730203102_Triggers")] partial class Triggers { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -26,6 +26,63 @@ namespace Kyoo.Postgresql.Migrations .HasAnnotation("ProductVersion", "5.0.8") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + modelBuilder.Entity("CollectionLibrary", b => + { + b.Property("CollectionsID") + .HasColumnType("integer") + .HasColumnName("collections_id"); + + b.Property("LibrariesID") + .HasColumnType("integer") + .HasColumnName("libraries_id"); + + b.HasKey("CollectionsID", "LibrariesID") + .HasName("pk_link_library_collection"); + + b.HasIndex("LibrariesID") + .HasDatabaseName("ix_link_library_collection_libraries_id"); + + b.ToTable("link_library_collection"); + }); + + modelBuilder.Entity("CollectionShow", b => + { + b.Property("CollectionsID") + .HasColumnType("integer") + .HasColumnName("collections_id"); + + b.Property("ShowsID") + .HasColumnType("integer") + .HasColumnName("shows_id"); + + b.HasKey("CollectionsID", "ShowsID") + .HasName("pk_link_collection_show"); + + b.HasIndex("ShowsID") + .HasDatabaseName("ix_link_collection_show_shows_id"); + + b.ToTable("link_collection_show"); + }); + + modelBuilder.Entity("GenreShow", b => + { + b.Property("GenresID") + .HasColumnType("integer") + .HasColumnName("genres_id"); + + b.Property("ShowsID") + .HasColumnType("integer") + .HasColumnName("shows_id"); + + b.HasKey("GenresID", "ShowsID") + .HasName("pk_link_show_genre"); + + b.HasIndex("ShowsID") + .HasDatabaseName("ix_link_show_genre_shows_id"); + + b.ToTable("link_show_genre"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") @@ -234,120 +291,6 @@ namespace Kyoo.Postgresql.Migrations b.ToView("library_items"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_collection_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_collection_show_second_id"); - - b.ToTable("link_collection_show"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_collection"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_collection_second_id"); - - b.ToTable("link_library_collection"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_provider"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_provider_second_id"); - - b.ToTable("link_library_provider"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_show_second_id"); - - b.ToTable("link_library_show"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_show_genre"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_show_genre_second_id"); - - b.ToTable("link_show_genre"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_user_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_user_show_second_id"); - - b.ToTable("link_user_show"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") @@ -427,10 +370,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("jsonb") .HasColumnName("images"); - b.Property("LogoExtension") - .HasColumnType("text") - .HasColumnName("logo_extension"); - b.Property("Name") .HasColumnType("text") .HasColumnName("name"); @@ -681,6 +620,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("jsonb") .HasColumnName("extra_data"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Password") .HasColumnType("text") .HasColumnName("password"); @@ -710,27 +653,84 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.Property("FirstID") + b.Property("UserID") .HasColumnType("integer") - .HasColumnName("first_id"); + .HasColumnName("user_id"); - b.Property("SecondID") + b.Property("EpisodeID") .HasColumnType("integer") - .HasColumnName("second_id"); + .HasColumnName("episode_id"); b.Property("WatchedPercentage") .HasColumnType("integer") .HasColumnName("watched_percentage"); - b.HasKey("FirstID", "SecondID") + b.HasKey("UserID", "EpisodeID") .HasName("pk_watched_episodes"); - b.HasIndex("SecondID") - .HasDatabaseName("ix_watched_episodes_second_id"); + b.HasIndex("EpisodeID") + .HasDatabaseName("ix_watched_episodes_episode_id"); b.ToTable("watched_episodes"); }); + modelBuilder.Entity("LibraryProvider", b => + { + b.Property("LibrariesID") + .HasColumnType("integer") + .HasColumnName("libraries_id"); + + b.Property("ProvidersID") + .HasColumnType("integer") + .HasColumnName("providers_id"); + + b.HasKey("LibrariesID", "ProvidersID") + .HasName("pk_link_library_provider"); + + b.HasIndex("ProvidersID") + .HasDatabaseName("ix_link_library_provider_providers_id"); + + b.ToTable("link_library_provider"); + }); + + modelBuilder.Entity("LibraryShow", b => + { + b.Property("LibrariesID") + .HasColumnType("integer") + .HasColumnName("libraries_id"); + + b.Property("ShowsID") + .HasColumnType("integer") + .HasColumnName("shows_id"); + + b.HasKey("LibrariesID", "ShowsID") + .HasName("pk_link_library_show"); + + b.HasIndex("ShowsID") + .HasDatabaseName("ix_link_library_show_shows_id"); + + b.ToTable("link_library_show"); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.Property("UsersID") + .HasColumnType("integer") + .HasColumnName("users_id"); + + b.Property("WatchedID") + .HasColumnType("integer") + .HasColumnName("watched_id"); + + b.HasKey("UsersID", "WatchedID") + .HasName("pk_link_user_show"); + + b.HasIndex("WatchedID") + .HasDatabaseName("ix_link_user_show_watched_id"); + + b.ToTable("link_user_show"); + }); + modelBuilder.Entity("collection_metadata_id", b => { b.Property("ResourceID") @@ -893,6 +893,57 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("studio_metadata_id"); }); + modelBuilder.Entity("CollectionLibrary", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") + .HasConstraintName("fk_link_library_collection_collections_collections_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .HasConstraintName("fk_link_library_collection_libraries_libraries_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("CollectionShow", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") + .HasConstraintName("fk_link_collection_show_collections_collections_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .HasConstraintName("fk_link_collection_show_shows_shows_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GenreShow", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("GenresID") + .HasConstraintName("fk_link_show_genre_genres_genres_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .HasConstraintName("fk_link_show_genre_shows_shows_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -913,132 +964,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Collection", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_collection_show_collections_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("CollectionLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_collection_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("CollectionLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_collection_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Collection", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_collection_collections_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ProviderLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_provider_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_provider_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_show_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("GenreLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_show_genre_shows_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Genre", "Second") - .WithMany("ShowLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_show_genre_genres_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_user_show_users_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_user_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -1097,23 +1022,72 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("CurrentlyWatching") - .HasForeignKey("FirstID") - .HasConstraintName("fk_watched_episodes_users_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Episode", "Second") + b.HasOne("Kyoo.Models.Episode", "Episode") .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_watched_episodes_episodes_second_id") + .HasForeignKey("EpisodeID") + .HasConstraintName("fk_watched_episodes_episodes_episode_id") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("First"); + b.HasOne("Kyoo.Models.User", null) + .WithMany("CurrentlyWatching") + .HasForeignKey("UserID") + .HasConstraintName("fk_watched_episodes_users_user_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Second"); + b.Navigation("Episode"); + }); + + modelBuilder.Entity("LibraryProvider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .HasConstraintName("fk_link_library_provider_libraries_libraries_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("ProvidersID") + .HasConstraintName("fk_link_library_provider_providers_providers_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LibraryShow", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .HasConstraintName("fk_link_library_show_libraries_libraries_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .HasConstraintName("fk_link_library_show_shows_shows_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.HasOne("Kyoo.Models.User", null) + .WithMany() + .HasForeignKey("UsersID") + .HasConstraintName("fk_link_user_show_users_users_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("WatchedID") + .HasConstraintName("fk_link_user_show_shows_watched_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("collection_metadata_id", b => @@ -1233,10 +1207,6 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Navigation("ExternalIDs"); - - b.Navigation("LibraryLinks"); - - b.Navigation("ShowLinks"); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -1246,20 +1216,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Tracks"); }); - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Navigation("CollectionLinks"); - - b.Navigation("ProviderLinks"); - - b.Navigation("ShowLinks"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Navigation("ExternalIDs"); @@ -1267,11 +1223,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Roles"); }); - modelBuilder.Entity("Kyoo.Models.Provider", b => - { - b.Navigation("LibraryLinks"); - }); - modelBuilder.Entity("Kyoo.Models.Season", b => { b.Navigation("Episodes"); @@ -1281,16 +1232,10 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.Show", b => { - b.Navigation("CollectionLinks"); - b.Navigation("Episodes"); b.Navigation("ExternalIDs"); - b.Navigation("GenreLinks"); - - b.Navigation("LibraryLinks"); - b.Navigation("People"); b.Navigation("Seasons"); @@ -1306,8 +1251,6 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.User", b => { b.Navigation("CurrentlyWatching"); - - b.Navigation("ShowLinks"); }); #pragma warning restore 612, 618 } diff --git a/Kyoo.Postgresql/Migrations/20210728142213_Triggers.cs b/Kyoo.Postgresql/Migrations/20210730203102_Triggers.cs similarity index 98% rename from Kyoo.Postgresql/Migrations/20210728142213_Triggers.cs rename to Kyoo.Postgresql/Migrations/20210730203102_Triggers.cs index d1933171..f84b767b 100644 --- a/Kyoo.Postgresql/Migrations/20210728142213_Triggers.cs +++ b/Kyoo.Postgresql/Migrations/20210730203102_Triggers.cs @@ -149,8 +149,8 @@ namespace Kyoo.Postgresql.Migrations WHERE NOT (EXISTS ( SELECT 1 FROM link_collection_show AS l - INNER JOIN collections AS c ON l.first_id = c.id - WHERE s.id = l.second_id)) + INNER JOIN collections AS c ON l.collections_id = c.id + WHERE s.id = l.shows_id)) UNION ALL SELECT -c0.id, c0.slug, c0.name AS title, c0.overview, 'unknown'::status AS status, NULL AS start_air, NULL AS end_air, c0.images, 'collection'::item_type AS type diff --git a/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs b/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs index f7db32a7..379b8d09 100644 --- a/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs +++ b/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs @@ -24,6 +24,63 @@ namespace Kyoo.Postgresql.Migrations .HasAnnotation("ProductVersion", "5.0.8") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + modelBuilder.Entity("CollectionLibrary", b => + { + b.Property("CollectionsID") + .HasColumnType("integer") + .HasColumnName("collections_id"); + + b.Property("LibrariesID") + .HasColumnType("integer") + .HasColumnName("libraries_id"); + + b.HasKey("CollectionsID", "LibrariesID") + .HasName("pk_link_library_collection"); + + b.HasIndex("LibrariesID") + .HasDatabaseName("ix_link_library_collection_libraries_id"); + + b.ToTable("link_library_collection"); + }); + + modelBuilder.Entity("CollectionShow", b => + { + b.Property("CollectionsID") + .HasColumnType("integer") + .HasColumnName("collections_id"); + + b.Property("ShowsID") + .HasColumnType("integer") + .HasColumnName("shows_id"); + + b.HasKey("CollectionsID", "ShowsID") + .HasName("pk_link_collection_show"); + + b.HasIndex("ShowsID") + .HasDatabaseName("ix_link_collection_show_shows_id"); + + b.ToTable("link_collection_show"); + }); + + modelBuilder.Entity("GenreShow", b => + { + b.Property("GenresID") + .HasColumnType("integer") + .HasColumnName("genres_id"); + + b.Property("ShowsID") + .HasColumnType("integer") + .HasColumnName("shows_id"); + + b.HasKey("GenresID", "ShowsID") + .HasName("pk_link_show_genre"); + + b.HasIndex("ShowsID") + .HasDatabaseName("ix_link_show_genre_shows_id"); + + b.ToTable("link_show_genre"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") @@ -232,120 +289,6 @@ namespace Kyoo.Postgresql.Migrations b.ToView("library_items"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_collection_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_collection_show_second_id"); - - b.ToTable("link_collection_show"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_collection"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_collection_second_id"); - - b.ToTable("link_library_collection"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_provider"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_provider_second_id"); - - b.ToTable("link_library_provider"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_show_second_id"); - - b.ToTable("link_library_show"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_show_genre"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_show_genre_second_id"); - - b.ToTable("link_show_genre"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_user_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_user_show_second_id"); - - b.ToTable("link_user_show"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") @@ -425,10 +368,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("jsonb") .HasColumnName("images"); - b.Property("LogoExtension") - .HasColumnType("text") - .HasColumnName("logo_extension"); - b.Property("Name") .HasColumnType("text") .HasColumnName("name"); @@ -679,6 +618,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("jsonb") .HasColumnName("extra_data"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Password") .HasColumnType("text") .HasColumnName("password"); @@ -708,27 +651,84 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.Property("FirstID") + b.Property("UserID") .HasColumnType("integer") - .HasColumnName("first_id"); + .HasColumnName("user_id"); - b.Property("SecondID") + b.Property("EpisodeID") .HasColumnType("integer") - .HasColumnName("second_id"); + .HasColumnName("episode_id"); b.Property("WatchedPercentage") .HasColumnType("integer") .HasColumnName("watched_percentage"); - b.HasKey("FirstID", "SecondID") + b.HasKey("UserID", "EpisodeID") .HasName("pk_watched_episodes"); - b.HasIndex("SecondID") - .HasDatabaseName("ix_watched_episodes_second_id"); + b.HasIndex("EpisodeID") + .HasDatabaseName("ix_watched_episodes_episode_id"); b.ToTable("watched_episodes"); }); + modelBuilder.Entity("LibraryProvider", b => + { + b.Property("LibrariesID") + .HasColumnType("integer") + .HasColumnName("libraries_id"); + + b.Property("ProvidersID") + .HasColumnType("integer") + .HasColumnName("providers_id"); + + b.HasKey("LibrariesID", "ProvidersID") + .HasName("pk_link_library_provider"); + + b.HasIndex("ProvidersID") + .HasDatabaseName("ix_link_library_provider_providers_id"); + + b.ToTable("link_library_provider"); + }); + + modelBuilder.Entity("LibraryShow", b => + { + b.Property("LibrariesID") + .HasColumnType("integer") + .HasColumnName("libraries_id"); + + b.Property("ShowsID") + .HasColumnType("integer") + .HasColumnName("shows_id"); + + b.HasKey("LibrariesID", "ShowsID") + .HasName("pk_link_library_show"); + + b.HasIndex("ShowsID") + .HasDatabaseName("ix_link_library_show_shows_id"); + + b.ToTable("link_library_show"); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.Property("UsersID") + .HasColumnType("integer") + .HasColumnName("users_id"); + + b.Property("WatchedID") + .HasColumnType("integer") + .HasColumnName("watched_id"); + + b.HasKey("UsersID", "WatchedID") + .HasName("pk_link_user_show"); + + b.HasIndex("WatchedID") + .HasDatabaseName("ix_link_user_show_watched_id"); + + b.ToTable("link_user_show"); + }); + modelBuilder.Entity("collection_metadata_id", b => { b.Property("ResourceID") @@ -891,6 +891,57 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("studio_metadata_id"); }); + modelBuilder.Entity("CollectionLibrary", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") + .HasConstraintName("fk_link_library_collection_collections_collections_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .HasConstraintName("fk_link_library_collection_libraries_libraries_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("CollectionShow", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") + .HasConstraintName("fk_link_collection_show_collections_collections_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .HasConstraintName("fk_link_collection_show_shows_shows_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GenreShow", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("GenresID") + .HasConstraintName("fk_link_show_genre_genres_genres_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .HasConstraintName("fk_link_show_genre_shows_shows_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -911,132 +962,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Collection", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_collection_show_collections_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("CollectionLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_collection_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("CollectionLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_collection_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Collection", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_collection_collections_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ProviderLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_provider_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_provider_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_show_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("GenreLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_show_genre_shows_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Genre", "Second") - .WithMany("ShowLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_show_genre_genres_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_user_show_users_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_user_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -1095,23 +1020,72 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("CurrentlyWatching") - .HasForeignKey("FirstID") - .HasConstraintName("fk_watched_episodes_users_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Episode", "Second") + b.HasOne("Kyoo.Models.Episode", "Episode") .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_watched_episodes_episodes_second_id") + .HasForeignKey("EpisodeID") + .HasConstraintName("fk_watched_episodes_episodes_episode_id") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("First"); + b.HasOne("Kyoo.Models.User", null) + .WithMany("CurrentlyWatching") + .HasForeignKey("UserID") + .HasConstraintName("fk_watched_episodes_users_user_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Second"); + b.Navigation("Episode"); + }); + + modelBuilder.Entity("LibraryProvider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .HasConstraintName("fk_link_library_provider_libraries_libraries_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("ProvidersID") + .HasConstraintName("fk_link_library_provider_providers_providers_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LibraryShow", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .HasConstraintName("fk_link_library_show_libraries_libraries_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .HasConstraintName("fk_link_library_show_shows_shows_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.HasOne("Kyoo.Models.User", null) + .WithMany() + .HasForeignKey("UsersID") + .HasConstraintName("fk_link_user_show_users_users_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("WatchedID") + .HasConstraintName("fk_link_user_show_shows_watched_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("collection_metadata_id", b => @@ -1231,10 +1205,6 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Navigation("ExternalIDs"); - - b.Navigation("LibraryLinks"); - - b.Navigation("ShowLinks"); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -1244,20 +1214,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Tracks"); }); - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Navigation("CollectionLinks"); - - b.Navigation("ProviderLinks"); - - b.Navigation("ShowLinks"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Navigation("ExternalIDs"); @@ -1265,11 +1221,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Roles"); }); - modelBuilder.Entity("Kyoo.Models.Provider", b => - { - b.Navigation("LibraryLinks"); - }); - modelBuilder.Entity("Kyoo.Models.Season", b => { b.Navigation("Episodes"); @@ -1279,16 +1230,10 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.Show", b => { - b.Navigation("CollectionLinks"); - b.Navigation("Episodes"); b.Navigation("ExternalIDs"); - b.Navigation("GenreLinks"); - - b.Navigation("LibraryLinks"); - b.Navigation("People"); b.Navigation("Seasons"); @@ -1304,8 +1249,6 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.User", b => { b.Navigation("CurrentlyWatching"); - - b.Navigation("ShowLinks"); }); #pragma warning restore 612, 618 } diff --git a/Kyoo.Postgresql/PostgresContext.cs b/Kyoo.Postgresql/PostgresContext.cs index 09f460bc..eaea9927 100644 --- a/Kyoo.Postgresql/PostgresContext.cs +++ b/Kyoo.Postgresql/PostgresContext.cs @@ -122,6 +122,9 @@ namespace Kyoo.Postgresql modelBuilder.Entity() .Property(x => x.Images) .HasColumnType("jsonb"); + modelBuilder.Entity() + .Property(x => x.Images) + .HasColumnType("jsonb"); base.OnModelCreating(modelBuilder); } @@ -132,6 +135,13 @@ 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 bool IsDuplicateException(Exception ex) diff --git a/Kyoo.Postgresql/PostgresModule.cs b/Kyoo.Postgresql/PostgresModule.cs index 124df770..6b84bd93 100644 --- a/Kyoo.Postgresql/PostgresModule.cs +++ b/Kyoo.Postgresql/PostgresModule.cs @@ -66,7 +66,7 @@ namespace Kyoo.Postgresql x.UseNpgsql(_configuration.GetDatabaseConnection("postgres")); if (_environment.IsDevelopment()) x.EnableDetailedErrors().EnableSensitiveDataLogging(); - }); + }, ServiceLifetime.Transient); } /// diff --git a/Kyoo.SqLite/Migrations/20210728134952_Initial.Designer.cs b/Kyoo.SqLite/Migrations/20210730203155_Initial.Designer.cs similarity index 81% rename from Kyoo.SqLite/Migrations/20210728134952_Initial.Designer.cs rename to Kyoo.SqLite/Migrations/20210730203155_Initial.Designer.cs index 3f4290b7..48b89a25 100644 --- a/Kyoo.SqLite/Migrations/20210728134952_Initial.Designer.cs +++ b/Kyoo.SqLite/Migrations/20210730203155_Initial.Designer.cs @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Kyoo.SqLite.Migrations { [DbContext(typeof(SqLiteContext))] - [Migration("20210728134952_Initial")] + [Migration("20210730203155_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -18,6 +18,21 @@ namespace Kyoo.SqLite.Migrations modelBuilder .HasAnnotation("ProductVersion", "5.0.8"); + modelBuilder.Entity("CollectionLibrary", b => + { + b.Property("CollectionsID") + .HasColumnType("INTEGER"); + + b.Property("LibrariesID") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionsID", "LibrariesID"); + + b.HasIndex("LibrariesID"); + + b.ToTable("LinkLibraryCollection"); + }); + modelBuilder.Entity("CollectionMetadataID", b => { b.Property("ResourceID") @@ -39,6 +54,21 @@ namespace Kyoo.SqLite.Migrations b.ToTable("CollectionMetadataID"); }); + modelBuilder.Entity("CollectionShow", b => + { + b.Property("CollectionsID") + .HasColumnType("INTEGER"); + + b.Property("ShowsID") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionsID", "ShowsID"); + + b.HasIndex("ShowsID"); + + b.ToTable("LinkCollectionShow"); + }); + modelBuilder.Entity("EpisodeMetadataID", b => { b.Property("ResourceID") @@ -60,6 +90,21 @@ namespace Kyoo.SqLite.Migrations b.ToTable("EpisodeMetadataID"); }); + modelBuilder.Entity("GenreShow", b => + { + b.Property("GenresID") + .HasColumnType("INTEGER"); + + b.Property("ShowsID") + .HasColumnType("INTEGER"); + + b.HasKey("GenresID", "ShowsID"); + + b.HasIndex("ShowsID"); + + b.ToTable("LinkShowGenre"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") @@ -220,96 +265,6 @@ namespace Kyoo.SqLite.Migrations b.ToView("LibraryItems"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") @@ -370,9 +325,6 @@ namespace Kyoo.SqLite.Migrations b.Property("Images") .HasColumnType("TEXT"); - b.Property("LogoExtension") - .HasColumnType("TEXT"); - b.Property("Name") .HasColumnType("TEXT"); @@ -564,6 +516,9 @@ namespace Kyoo.SqLite.Migrations b.Property("ExtraData") .HasColumnType("TEXT"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Password") .HasColumnType("TEXT"); @@ -587,22 +542,52 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.Property("FirstID") + b.Property("UserID") .HasColumnType("INTEGER"); - b.Property("SecondID") + b.Property("EpisodeID") .HasColumnType("INTEGER"); b.Property("WatchedPercentage") .HasColumnType("INTEGER"); - b.HasKey("FirstID", "SecondID"); + b.HasKey("UserID", "EpisodeID"); - b.HasIndex("SecondID"); + b.HasIndex("EpisodeID"); b.ToTable("WatchedEpisodes"); }); + modelBuilder.Entity("LibraryProvider", b => + { + b.Property("LibrariesID") + .HasColumnType("INTEGER"); + + b.Property("ProvidersID") + .HasColumnType("INTEGER"); + + b.HasKey("LibrariesID", "ProvidersID"); + + b.HasIndex("ProvidersID"); + + b.ToTable("LinkLibraryProvider"); + }); + + modelBuilder.Entity("LibraryShow", b => + { + b.Property("LibrariesID") + .HasColumnType("INTEGER"); + + b.Property("ShowsID") + .HasColumnType("INTEGER"); + + b.HasKey("LibrariesID", "ShowsID"); + + b.HasIndex("ShowsID"); + + b.ToTable("LinkLibraryShow"); + }); + modelBuilder.Entity("PeopleMetadataID", b => { b.Property("ResourceID") @@ -666,6 +651,21 @@ namespace Kyoo.SqLite.Migrations b.ToTable("ShowMetadataID"); }); + modelBuilder.Entity("ShowUser", b => + { + b.Property("UsersID") + .HasColumnType("INTEGER"); + + b.Property("WatchedID") + .HasColumnType("INTEGER"); + + b.HasKey("UsersID", "WatchedID"); + + b.HasIndex("WatchedID"); + + b.ToTable("LinkUserShow"); + }); + modelBuilder.Entity("StudioMetadataID", b => { b.Property("ResourceID") @@ -687,6 +687,21 @@ namespace Kyoo.SqLite.Migrations b.ToTable("StudioMetadataID"); }); + modelBuilder.Entity("CollectionLibrary", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("CollectionMetadataID", b => { b.HasOne("Kyoo.Models.Provider", "Provider") @@ -704,6 +719,21 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Provider"); }); + modelBuilder.Entity("CollectionShow", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("EpisodeMetadataID", b => { b.HasOne("Kyoo.Models.Provider", "Provider") @@ -721,6 +751,21 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Provider"); }); + modelBuilder.Entity("GenreShow", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("GenresID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -739,120 +784,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Collection", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("CollectionLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("CollectionLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Collection", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ProviderLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("GenreLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Genre", "Second") - .WithMany("ShowLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -906,21 +837,49 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("CurrentlyWatching") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Episode", "Second") + b.HasOne("Kyoo.Models.Episode", "Episode") .WithMany() - .HasForeignKey("SecondID") + .HasForeignKey("EpisodeID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("First"); + b.HasOne("Kyoo.Models.User", null) + .WithMany("CurrentlyWatching") + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Second"); + b.Navigation("Episode"); + }); + + modelBuilder.Entity("LibraryProvider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("ProvidersID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LibraryShow", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("PeopleMetadataID", b => @@ -974,6 +933,21 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Provider"); }); + modelBuilder.Entity("ShowUser", b => + { + b.HasOne("Kyoo.Models.User", null) + .WithMany() + .HasForeignKey("UsersID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("WatchedID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("StudioMetadataID", b => { b.HasOne("Kyoo.Models.Provider", "Provider") @@ -994,10 +968,6 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Navigation("ExternalIDs"); - - b.Navigation("LibraryLinks"); - - b.Navigation("ShowLinks"); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -1007,20 +977,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Tracks"); }); - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Navigation("CollectionLinks"); - - b.Navigation("ProviderLinks"); - - b.Navigation("ShowLinks"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Navigation("ExternalIDs"); @@ -1028,11 +984,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Roles"); }); - modelBuilder.Entity("Kyoo.Models.Provider", b => - { - b.Navigation("LibraryLinks"); - }); - modelBuilder.Entity("Kyoo.Models.Season", b => { b.Navigation("Episodes"); @@ -1042,16 +993,10 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.Show", b => { - b.Navigation("CollectionLinks"); - b.Navigation("Episodes"); b.Navigation("ExternalIDs"); - b.Navigation("GenreLinks"); - - b.Navigation("LibraryLinks"); - b.Navigation("People"); b.Navigation("Seasons"); @@ -1067,8 +1012,6 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.User", b => { b.Navigation("CurrentlyWatching"); - - b.Navigation("ShowLinks"); }); #pragma warning restore 612, 618 } diff --git a/Kyoo.SqLite/Migrations/20210728134952_Initial.cs b/Kyoo.SqLite/Migrations/20210730203155_Initial.cs similarity index 86% rename from Kyoo.SqLite/Migrations/20210728134952_Initial.cs rename to Kyoo.SqLite/Migrations/20210730203155_Initial.cs index b0567f79..82ae1dca 100644 --- a/Kyoo.SqLite/Migrations/20210728134952_Initial.cs +++ b/Kyoo.SqLite/Migrations/20210730203155_Initial.cs @@ -75,8 +75,7 @@ namespace Kyoo.SqLite.Migrations .Annotation("Sqlite:Autoincrement", true), Slug = table.Column(type: "TEXT", nullable: false), Name = table.Column(type: "TEXT", nullable: true), - Images = table.Column(type: "TEXT", nullable: true), - LogoExtension = table.Column(type: "TEXT", nullable: true) + Images = table.Column(type: "TEXT", nullable: true) }, constraints: table => { @@ -108,7 +107,8 @@ namespace Kyoo.SqLite.Migrations Email = table.Column(type: "TEXT", nullable: true), Password = table.Column(type: "TEXT", nullable: true), Permissions = table.Column(type: "TEXT", nullable: true), - ExtraData = table.Column(type: "TEXT", nullable: true) + ExtraData = table.Column(type: "TEXT", nullable: true), + Images = table.Column(type: "TEXT", nullable: true) }, constraints: table => { @@ -116,24 +116,24 @@ namespace Kyoo.SqLite.Migrations }); migrationBuilder.CreateTable( - name: "Link", + name: "LinkLibraryCollection", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false) + CollectionsID = table.Column(type: "INTEGER", nullable: false), + LibrariesID = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_LinkLibraryCollection", x => new { x.CollectionsID, x.LibrariesID }); table.ForeignKey( - name: "FK_Link_Collections_SecondID", - column: x => x.SecondID, + name: "FK_LinkLibraryCollection_Collections_CollectionsID", + column: x => x.CollectionsID, principalTable: "Collections", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_Link_Libraries_FirstID", - column: x => x.FirstID, + name: "FK_LinkLibraryCollection_Libraries_LibrariesID", + column: x => x.LibrariesID, principalTable: "Libraries", principalColumn: "ID", onDelete: ReferentialAction.Cascade); @@ -166,24 +166,24 @@ namespace Kyoo.SqLite.Migrations }); migrationBuilder.CreateTable( - name: "Link", + name: "LinkLibraryProvider", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false) + LibrariesID = table.Column(type: "INTEGER", nullable: false), + ProvidersID = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_LinkLibraryProvider", x => new { x.LibrariesID, x.ProvidersID }); table.ForeignKey( - name: "FK_Link_Libraries_FirstID", - column: x => x.FirstID, + name: "FK_LinkLibraryProvider_Libraries_LibrariesID", + column: x => x.LibrariesID, principalTable: "Libraries", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_Link_Providers_SecondID", - column: x => x.SecondID, + name: "FK_LinkLibraryProvider_Providers_ProvidersID", + column: x => x.ProvidersID, principalTable: "Providers", principalColumn: "ID", onDelete: ReferentialAction.Cascade); @@ -271,96 +271,96 @@ namespace Kyoo.SqLite.Migrations }); migrationBuilder.CreateTable( - name: "Link", + name: "LinkCollectionShow", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false) + CollectionsID = table.Column(type: "INTEGER", nullable: false), + ShowsID = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_LinkCollectionShow", x => new { x.CollectionsID, x.ShowsID }); table.ForeignKey( - name: "FK_Link_Collections_FirstID", - column: x => x.FirstID, + name: "FK_LinkCollectionShow_Collections_CollectionsID", + column: x => x.CollectionsID, principalTable: "Collections", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_Link_Shows_SecondID", - column: x => x.SecondID, + name: "FK_LinkCollectionShow_Shows_ShowsID", + column: x => x.ShowsID, principalTable: "Shows", principalColumn: "ID", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "Link", + name: "LinkLibraryShow", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false) + LibrariesID = table.Column(type: "INTEGER", nullable: false), + ShowsID = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_LinkLibraryShow", x => new { x.LibrariesID, x.ShowsID }); table.ForeignKey( - name: "FK_Link_Libraries_FirstID", - column: x => x.FirstID, + name: "FK_LinkLibraryShow_Libraries_LibrariesID", + column: x => x.LibrariesID, principalTable: "Libraries", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_Link_Shows_SecondID", - column: x => x.SecondID, + name: "FK_LinkLibraryShow_Shows_ShowsID", + column: x => x.ShowsID, principalTable: "Shows", principalColumn: "ID", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "Link", + name: "LinkShowGenre", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false) + GenresID = table.Column(type: "INTEGER", nullable: false), + ShowsID = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_LinkShowGenre", x => new { x.GenresID, x.ShowsID }); table.ForeignKey( - name: "FK_Link_Genres_SecondID", - column: x => x.SecondID, + name: "FK_LinkShowGenre_Genres_GenresID", + column: x => x.GenresID, principalTable: "Genres", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_Link_Shows_FirstID", - column: x => x.FirstID, + name: "FK_LinkShowGenre_Shows_ShowsID", + column: x => x.ShowsID, principalTable: "Shows", principalColumn: "ID", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "Link", + name: "LinkUserShow", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false) + UsersID = table.Column(type: "INTEGER", nullable: false), + WatchedID = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_LinkUserShow", x => new { x.UsersID, x.WatchedID }); table.ForeignKey( - name: "FK_Link_Shows_SecondID", - column: x => x.SecondID, + name: "FK_LinkUserShow_Shows_WatchedID", + column: x => x.WatchedID, principalTable: "Shows", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_Link_Users_FirstID", - column: x => x.FirstID, + name: "FK_LinkUserShow_Users_UsersID", + column: x => x.UsersID, principalTable: "Users", principalColumn: "ID", onDelete: ReferentialAction.Cascade); @@ -566,22 +566,22 @@ namespace Kyoo.SqLite.Migrations name: "WatchedEpisodes", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false), + UserID = table.Column(type: "INTEGER", nullable: false), + EpisodeID = table.Column(type: "INTEGER", nullable: false), WatchedPercentage = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_WatchedEpisodes", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_WatchedEpisodes", x => new { x.UserID, x.EpisodeID }); table.ForeignKey( - name: "FK_WatchedEpisodes_Episodes_SecondID", - column: x => x.SecondID, + name: "FK_WatchedEpisodes_Episodes_EpisodeID", + column: x => x.EpisodeID, principalTable: "Episodes", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_WatchedEpisodes_Users_FirstID", - column: x => x.FirstID, + name: "FK_WatchedEpisodes_Users_UserID", + column: x => x.UserID, principalTable: "Users", principalColumn: "ID", onDelete: ReferentialAction.Cascade); @@ -633,34 +633,34 @@ namespace Kyoo.SqLite.Migrations unique: true); migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", - column: "SecondID"); + name: "IX_LinkCollectionShow_ShowsID", + table: "LinkCollectionShow", + column: "ShowsID"); migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", - column: "SecondID"); + name: "IX_LinkLibraryCollection_LibrariesID", + table: "LinkLibraryCollection", + column: "LibrariesID"); migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", - column: "SecondID"); + name: "IX_LinkLibraryProvider_ProvidersID", + table: "LinkLibraryProvider", + column: "ProvidersID"); migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", - column: "SecondID"); + name: "IX_LinkLibraryShow_ShowsID", + table: "LinkLibraryShow", + column: "ShowsID"); migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", - column: "SecondID"); + name: "IX_LinkShowGenre_ShowsID", + table: "LinkShowGenre", + column: "ShowsID"); migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", - column: "SecondID"); + name: "IX_LinkUserShow_WatchedID", + table: "LinkUserShow", + column: "WatchedID"); migrationBuilder.CreateIndex( name: "IX_People_Slug", @@ -752,9 +752,9 @@ namespace Kyoo.SqLite.Migrations unique: true); migrationBuilder.CreateIndex( - name: "IX_WatchedEpisodes_SecondID", + name: "IX_WatchedEpisodes_EpisodeID", table: "WatchedEpisodes", - column: "SecondID"); + column: "EpisodeID"); } protected override void Down(MigrationBuilder migrationBuilder) @@ -766,22 +766,22 @@ namespace Kyoo.SqLite.Migrations name: "EpisodeMetadataID"); migrationBuilder.DropTable( - name: "Link"); + name: "LinkCollectionShow"); migrationBuilder.DropTable( - name: "Link"); + name: "LinkLibraryCollection"); migrationBuilder.DropTable( - name: "Link"); + name: "LinkLibraryProvider"); migrationBuilder.DropTable( - name: "Link"); + name: "LinkLibraryShow"); migrationBuilder.DropTable( - name: "Link"); + name: "LinkShowGenre"); migrationBuilder.DropTable( - name: "Link"); + name: "LinkUserShow"); migrationBuilder.DropTable( name: "PeopleMetadataID"); diff --git a/Kyoo.SqLite/Migrations/20210728135127_Triggers.Designer.cs b/Kyoo.SqLite/Migrations/20210730203746_Triggers.Designer.cs similarity index 81% rename from Kyoo.SqLite/Migrations/20210728135127_Triggers.Designer.cs rename to Kyoo.SqLite/Migrations/20210730203746_Triggers.Designer.cs index 43174e4b..fc59fcf6 100644 --- a/Kyoo.SqLite/Migrations/20210728135127_Triggers.Designer.cs +++ b/Kyoo.SqLite/Migrations/20210730203746_Triggers.Designer.cs @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Kyoo.SqLite.Migrations { [DbContext(typeof(SqLiteContext))] - [Migration("20210728135127_Triggers")] + [Migration("20210730203746_Triggers")] partial class Triggers { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -18,6 +18,21 @@ namespace Kyoo.SqLite.Migrations modelBuilder .HasAnnotation("ProductVersion", "5.0.8"); + modelBuilder.Entity("CollectionLibrary", b => + { + b.Property("CollectionsID") + .HasColumnType("INTEGER"); + + b.Property("LibrariesID") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionsID", "LibrariesID"); + + b.HasIndex("LibrariesID"); + + b.ToTable("LinkLibraryCollection"); + }); + modelBuilder.Entity("CollectionMetadataID", b => { b.Property("ResourceID") @@ -39,6 +54,21 @@ namespace Kyoo.SqLite.Migrations b.ToTable("CollectionMetadataID"); }); + modelBuilder.Entity("CollectionShow", b => + { + b.Property("CollectionsID") + .HasColumnType("INTEGER"); + + b.Property("ShowsID") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionsID", "ShowsID"); + + b.HasIndex("ShowsID"); + + b.ToTable("LinkCollectionShow"); + }); + modelBuilder.Entity("EpisodeMetadataID", b => { b.Property("ResourceID") @@ -60,6 +90,21 @@ namespace Kyoo.SqLite.Migrations b.ToTable("EpisodeMetadataID"); }); + modelBuilder.Entity("GenreShow", b => + { + b.Property("GenresID") + .HasColumnType("INTEGER"); + + b.Property("ShowsID") + .HasColumnType("INTEGER"); + + b.HasKey("GenresID", "ShowsID"); + + b.HasIndex("ShowsID"); + + b.ToTable("LinkShowGenre"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") @@ -220,96 +265,6 @@ namespace Kyoo.SqLite.Migrations b.ToView("LibraryItems"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") @@ -370,9 +325,6 @@ namespace Kyoo.SqLite.Migrations b.Property("Images") .HasColumnType("TEXT"); - b.Property("LogoExtension") - .HasColumnType("TEXT"); - b.Property("Name") .HasColumnType("TEXT"); @@ -564,6 +516,9 @@ namespace Kyoo.SqLite.Migrations b.Property("ExtraData") .HasColumnType("TEXT"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Password") .HasColumnType("TEXT"); @@ -587,22 +542,52 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.Property("FirstID") + b.Property("UserID") .HasColumnType("INTEGER"); - b.Property("SecondID") + b.Property("EpisodeID") .HasColumnType("INTEGER"); b.Property("WatchedPercentage") .HasColumnType("INTEGER"); - b.HasKey("FirstID", "SecondID"); + b.HasKey("UserID", "EpisodeID"); - b.HasIndex("SecondID"); + b.HasIndex("EpisodeID"); b.ToTable("WatchedEpisodes"); }); + modelBuilder.Entity("LibraryProvider", b => + { + b.Property("LibrariesID") + .HasColumnType("INTEGER"); + + b.Property("ProvidersID") + .HasColumnType("INTEGER"); + + b.HasKey("LibrariesID", "ProvidersID"); + + b.HasIndex("ProvidersID"); + + b.ToTable("LinkLibraryProvider"); + }); + + modelBuilder.Entity("LibraryShow", b => + { + b.Property("LibrariesID") + .HasColumnType("INTEGER"); + + b.Property("ShowsID") + .HasColumnType("INTEGER"); + + b.HasKey("LibrariesID", "ShowsID"); + + b.HasIndex("ShowsID"); + + b.ToTable("LinkLibraryShow"); + }); + modelBuilder.Entity("PeopleMetadataID", b => { b.Property("ResourceID") @@ -666,6 +651,21 @@ namespace Kyoo.SqLite.Migrations b.ToTable("ShowMetadataID"); }); + modelBuilder.Entity("ShowUser", b => + { + b.Property("UsersID") + .HasColumnType("INTEGER"); + + b.Property("WatchedID") + .HasColumnType("INTEGER"); + + b.HasKey("UsersID", "WatchedID"); + + b.HasIndex("WatchedID"); + + b.ToTable("LinkUserShow"); + }); + modelBuilder.Entity("StudioMetadataID", b => { b.Property("ResourceID") @@ -687,6 +687,21 @@ namespace Kyoo.SqLite.Migrations b.ToTable("StudioMetadataID"); }); + modelBuilder.Entity("CollectionLibrary", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("CollectionMetadataID", b => { b.HasOne("Kyoo.Models.Provider", "Provider") @@ -704,6 +719,21 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Provider"); }); + modelBuilder.Entity("CollectionShow", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("EpisodeMetadataID", b => { b.HasOne("Kyoo.Models.Provider", "Provider") @@ -721,6 +751,21 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Provider"); }); + modelBuilder.Entity("GenreShow", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("GenresID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -739,120 +784,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Collection", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("CollectionLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("CollectionLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Collection", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ProviderLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("GenreLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Genre", "Second") - .WithMany("ShowLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -906,21 +837,49 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("CurrentlyWatching") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Episode", "Second") + b.HasOne("Kyoo.Models.Episode", "Episode") .WithMany() - .HasForeignKey("SecondID") + .HasForeignKey("EpisodeID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("First"); + b.HasOne("Kyoo.Models.User", null) + .WithMany("CurrentlyWatching") + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Second"); + b.Navigation("Episode"); + }); + + modelBuilder.Entity("LibraryProvider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("ProvidersID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LibraryShow", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("PeopleMetadataID", b => @@ -974,6 +933,21 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Provider"); }); + modelBuilder.Entity("ShowUser", b => + { + b.HasOne("Kyoo.Models.User", null) + .WithMany() + .HasForeignKey("UsersID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("WatchedID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("StudioMetadataID", b => { b.HasOne("Kyoo.Models.Provider", "Provider") @@ -994,10 +968,6 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Navigation("ExternalIDs"); - - b.Navigation("LibraryLinks"); - - b.Navigation("ShowLinks"); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -1007,20 +977,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Tracks"); }); - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Navigation("CollectionLinks"); - - b.Navigation("ProviderLinks"); - - b.Navigation("ShowLinks"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Navigation("ExternalIDs"); @@ -1028,11 +984,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Roles"); }); - modelBuilder.Entity("Kyoo.Models.Provider", b => - { - b.Navigation("LibraryLinks"); - }); - modelBuilder.Entity("Kyoo.Models.Season", b => { b.Navigation("Episodes"); @@ -1042,16 +993,10 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.Show", b => { - b.Navigation("CollectionLinks"); - b.Navigation("Episodes"); b.Navigation("ExternalIDs"); - b.Navigation("GenreLinks"); - - b.Navigation("LibraryLinks"); - b.Navigation("People"); b.Navigation("Seasons"); @@ -1067,8 +1012,6 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.User", b => { b.Navigation("CurrentlyWatching"); - - b.Navigation("ShowLinks"); }); #pragma warning restore 612, 618 } diff --git a/Kyoo.SqLite/Migrations/20210728135127_Triggers.cs b/Kyoo.SqLite/Migrations/20210730203746_Triggers.cs similarity index 97% rename from Kyoo.SqLite/Migrations/20210728135127_Triggers.cs rename to Kyoo.SqLite/Migrations/20210730203746_Triggers.cs index fe46cd26..b3e3c5ae 100644 --- a/Kyoo.SqLite/Migrations/20210728135127_Triggers.cs +++ b/Kyoo.SqLite/Migrations/20210730203746_Triggers.cs @@ -161,9 +161,9 @@ namespace Kyoo.SqLite.Migrations FROM Shows AS s WHERE NOT (EXISTS ( SELECT 1 - FROM 'Link' AS l - INNER JOIN Collections AS c ON l.FirstID = c.ID - WHERE s.ID = l.SecondID)) + FROM LinkCollectionShow AS l + INNER JOIN Collections AS c ON l.CollectionsID = c.ID + WHERE s.ID = l.ShowsID)) UNION ALL SELECT -c0.ID, c0.Slug, c0.Name AS Title, c0.Overview, 0 AS Status, NULL AS StartAir, NULL AS EndAir, c0.Images, 2 AS Type diff --git a/Kyoo.SqLite/Migrations/SqLiteContextModelSnapshot.cs b/Kyoo.SqLite/Migrations/SqLiteContextModelSnapshot.cs index fc7c6cac..28dc670c 100644 --- a/Kyoo.SqLite/Migrations/SqLiteContextModelSnapshot.cs +++ b/Kyoo.SqLite/Migrations/SqLiteContextModelSnapshot.cs @@ -16,6 +16,21 @@ namespace Kyoo.SqLite.Migrations modelBuilder .HasAnnotation("ProductVersion", "5.0.8"); + modelBuilder.Entity("CollectionLibrary", b => + { + b.Property("CollectionsID") + .HasColumnType("INTEGER"); + + b.Property("LibrariesID") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionsID", "LibrariesID"); + + b.HasIndex("LibrariesID"); + + b.ToTable("LinkLibraryCollection"); + }); + modelBuilder.Entity("CollectionMetadataID", b => { b.Property("ResourceID") @@ -37,6 +52,21 @@ namespace Kyoo.SqLite.Migrations b.ToTable("CollectionMetadataID"); }); + modelBuilder.Entity("CollectionShow", b => + { + b.Property("CollectionsID") + .HasColumnType("INTEGER"); + + b.Property("ShowsID") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionsID", "ShowsID"); + + b.HasIndex("ShowsID"); + + b.ToTable("LinkCollectionShow"); + }); + modelBuilder.Entity("EpisodeMetadataID", b => { b.Property("ResourceID") @@ -58,6 +88,21 @@ namespace Kyoo.SqLite.Migrations b.ToTable("EpisodeMetadataID"); }); + modelBuilder.Entity("GenreShow", b => + { + b.Property("GenresID") + .HasColumnType("INTEGER"); + + b.Property("ShowsID") + .HasColumnType("INTEGER"); + + b.HasKey("GenresID", "ShowsID"); + + b.HasIndex("ShowsID"); + + b.ToTable("LinkShowGenre"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") @@ -218,96 +263,6 @@ namespace Kyoo.SqLite.Migrations b.ToView("LibraryItems"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") @@ -368,9 +323,6 @@ namespace Kyoo.SqLite.Migrations b.Property("Images") .HasColumnType("TEXT"); - b.Property("LogoExtension") - .HasColumnType("TEXT"); - b.Property("Name") .HasColumnType("TEXT"); @@ -562,6 +514,9 @@ namespace Kyoo.SqLite.Migrations b.Property("ExtraData") .HasColumnType("TEXT"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Password") .HasColumnType("TEXT"); @@ -585,22 +540,52 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.Property("FirstID") + b.Property("UserID") .HasColumnType("INTEGER"); - b.Property("SecondID") + b.Property("EpisodeID") .HasColumnType("INTEGER"); b.Property("WatchedPercentage") .HasColumnType("INTEGER"); - b.HasKey("FirstID", "SecondID"); + b.HasKey("UserID", "EpisodeID"); - b.HasIndex("SecondID"); + b.HasIndex("EpisodeID"); b.ToTable("WatchedEpisodes"); }); + modelBuilder.Entity("LibraryProvider", b => + { + b.Property("LibrariesID") + .HasColumnType("INTEGER"); + + b.Property("ProvidersID") + .HasColumnType("INTEGER"); + + b.HasKey("LibrariesID", "ProvidersID"); + + b.HasIndex("ProvidersID"); + + b.ToTable("LinkLibraryProvider"); + }); + + modelBuilder.Entity("LibraryShow", b => + { + b.Property("LibrariesID") + .HasColumnType("INTEGER"); + + b.Property("ShowsID") + .HasColumnType("INTEGER"); + + b.HasKey("LibrariesID", "ShowsID"); + + b.HasIndex("ShowsID"); + + b.ToTable("LinkLibraryShow"); + }); + modelBuilder.Entity("PeopleMetadataID", b => { b.Property("ResourceID") @@ -664,6 +649,21 @@ namespace Kyoo.SqLite.Migrations b.ToTable("ShowMetadataID"); }); + modelBuilder.Entity("ShowUser", b => + { + b.Property("UsersID") + .HasColumnType("INTEGER"); + + b.Property("WatchedID") + .HasColumnType("INTEGER"); + + b.HasKey("UsersID", "WatchedID"); + + b.HasIndex("WatchedID"); + + b.ToTable("LinkUserShow"); + }); + modelBuilder.Entity("StudioMetadataID", b => { b.Property("ResourceID") @@ -685,6 +685,21 @@ namespace Kyoo.SqLite.Migrations b.ToTable("StudioMetadataID"); }); + modelBuilder.Entity("CollectionLibrary", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("CollectionMetadataID", b => { b.HasOne("Kyoo.Models.Provider", "Provider") @@ -702,6 +717,21 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Provider"); }); + modelBuilder.Entity("CollectionShow", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("EpisodeMetadataID", b => { b.HasOne("Kyoo.Models.Provider", "Provider") @@ -719,6 +749,21 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Provider"); }); + modelBuilder.Entity("GenreShow", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("GenresID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -737,120 +782,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Collection", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("CollectionLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("CollectionLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Collection", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ProviderLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("GenreLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Genre", "Second") - .WithMany("ShowLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -904,21 +835,49 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("CurrentlyWatching") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Episode", "Second") + b.HasOne("Kyoo.Models.Episode", "Episode") .WithMany() - .HasForeignKey("SecondID") + .HasForeignKey("EpisodeID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("First"); + b.HasOne("Kyoo.Models.User", null) + .WithMany("CurrentlyWatching") + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Second"); + b.Navigation("Episode"); + }); + + modelBuilder.Entity("LibraryProvider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("ProvidersID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LibraryShow", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("PeopleMetadataID", b => @@ -972,6 +931,21 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Provider"); }); + modelBuilder.Entity("ShowUser", b => + { + b.HasOne("Kyoo.Models.User", null) + .WithMany() + .HasForeignKey("UsersID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("WatchedID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("StudioMetadataID", b => { b.HasOne("Kyoo.Models.Provider", "Provider") @@ -992,10 +966,6 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Navigation("ExternalIDs"); - - b.Navigation("LibraryLinks"); - - b.Navigation("ShowLinks"); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -1005,20 +975,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Tracks"); }); - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Navigation("CollectionLinks"); - - b.Navigation("ProviderLinks"); - - b.Navigation("ShowLinks"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Navigation("ExternalIDs"); @@ -1026,11 +982,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Roles"); }); - modelBuilder.Entity("Kyoo.Models.Provider", b => - { - b.Navigation("LibraryLinks"); - }); - modelBuilder.Entity("Kyoo.Models.Season", b => { b.Navigation("Episodes"); @@ -1040,16 +991,10 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.Show", b => { - b.Navigation("CollectionLinks"); - b.Navigation("Episodes"); b.Navigation("ExternalIDs"); - b.Navigation("GenreLinks"); - - b.Navigation("LibraryLinks"); - b.Navigation("People"); b.Navigation("Seasons"); @@ -1065,8 +1010,6 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.User", b => { b.Navigation("CurrentlyWatching"); - - b.Navigation("ShowLinks"); }); #pragma warning restore 612, 618 } diff --git a/Kyoo.SqLite/SqLiteContext.cs b/Kyoo.SqLite/SqLiteContext.cs index e81a91ad..e05d0308 100644 --- a/Kyoo.SqLite/SqLiteContext.cs +++ b/Kyoo.SqLite/SqLiteContext.cs @@ -133,6 +133,9 @@ namespace Kyoo.SqLite modelBuilder.Entity() .Property(x => x.Images) .HasConversion(jsonConvertor); + modelBuilder.Entity() + .Property(x => x.Images) + .HasConversion(jsonConvertor); modelBuilder.Entity() @@ -146,6 +149,12 @@ namespace Kyoo.SqLite { return typeof(T).Name + nameof(MetadataID); } + + /// + protected override string LinkName() + { + return "Link" + typeof(T).Name + typeof(T2).Name; + } /// protected override bool IsDuplicateException(Exception ex) diff --git a/Kyoo.SqLite/SqLiteModule.cs b/Kyoo.SqLite/SqLiteModule.cs index 34802b20..96c29836 100644 --- a/Kyoo.SqLite/SqLiteModule.cs +++ b/Kyoo.SqLite/SqLiteModule.cs @@ -66,7 +66,7 @@ namespace Kyoo.SqLite x.UseSqlite(_configuration.GetDatabaseConnection("sqlite")); if (_environment.IsDevelopment()) x.EnableDetailedErrors().EnableSensitiveDataLogging(); - }); + }, ServiceLifetime.Transient); } /// diff --git a/Kyoo.Tests/Database/RepositoryActivator.cs b/Kyoo.Tests/Database/RepositoryActivator.cs index 25530f78..8e543546 100644 --- a/Kyoo.Tests/Database/RepositoryActivator.cs +++ b/Kyoo.Tests/Database/RepositoryActivator.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Kyoo.Controllers; using Xunit.Abstractions; @@ -11,29 +12,28 @@ namespace Kyoo.Tests public ILibraryManager LibraryManager { get; } - private readonly DatabaseContext _database; + private readonly List _databases = new(); public RepositoryActivator(ITestOutputHelper output, PostgresFixture postgres = null) { Context = postgres == null ? new SqLiteTestContext(output) : new PostgresTestContext(postgres, output); - _database = Context.New(); - - ProviderRepository provider = new(_database); - LibraryRepository library = new(_database, provider); - CollectionRepository collection = new(_database, provider); - GenreRepository genre = new(_database); - StudioRepository studio = new(_database, provider); - PeopleRepository people = new(_database, provider, + + ProviderRepository provider = new(_NewContext()); + LibraryRepository library = new(_NewContext(), provider); + CollectionRepository collection = new(_NewContext(), provider); + GenreRepository genre = new(_NewContext()); + StudioRepository studio = new(_NewContext(), provider); + PeopleRepository people = new(_NewContext(), provider, new Lazy(() => LibraryManager.ShowRepository)); - ShowRepository show = new(_database, studio, people, genre, provider); - SeasonRepository season = new(_database, provider); - LibraryItemRepository libraryItem = new(_database, + ShowRepository show = new(_NewContext(), studio, people, genre, provider); + SeasonRepository season = new(_NewContext(), provider); + LibraryItemRepository libraryItem = new(_NewContext(), new Lazy(() => LibraryManager.LibraryRepository)); - TrackRepository track = new(_database); - EpisodeRepository episode = new(_database, provider, track); - UserRepository user = new(_database); + TrackRepository track = new(_NewContext()); + EpisodeRepository episode = new(_NewContext(), provider, track); + UserRepository user = new(_NewContext()); LibraryManager = new LibraryManager(new IBaseRepository[] { provider, @@ -51,16 +51,25 @@ namespace Kyoo.Tests }); } + private DatabaseContext _NewContext() + { + DatabaseContext context = Context.New(); + _databases.Add(context); + return context; + } + public void Dispose() { - _database.Dispose(); + foreach (DatabaseContext context in _databases) + context.Dispose(); Context.Dispose(); GC.SuppressFinalize(this); } public async ValueTask DisposeAsync() { - await _database.DisposeAsync(); + foreach (DatabaseContext context in _databases) + await context.DisposeAsync(); await Context.DisposeAsync(); } } diff --git a/Kyoo.Tests/Database/SpecificTests/SanityTests.cs b/Kyoo.Tests/Database/SpecificTests/SanityTests.cs index 933bbf82..a071ee2b 100644 --- a/Kyoo.Tests/Database/SpecificTests/SanityTests.cs +++ b/Kyoo.Tests/Database/SpecificTests/SanityTests.cs @@ -16,13 +16,6 @@ namespace Kyoo.Tests.Database _repositories = new RepositoryActivator(output); } - [Fact] - [SuppressMessage("ReSharper", "EqualExpressionComparison")] - public void SampleTest() - { - Assert.False(ReferenceEquals(TestSample.Get(), TestSample.Get())); - } - public void Dispose() { _repositories.Dispose(); @@ -33,5 +26,12 @@ namespace Kyoo.Tests.Database { return _repositories.DisposeAsync(); } + + [Fact] + [SuppressMessage("ReSharper", "EqualExpressionComparison")] + public void SampleTest() + { + Assert.False(ReferenceEquals(TestSample.Get(), TestSample.Get())); + } } } \ No newline at end of file diff --git a/Kyoo.Tests/Database/SpecificTests/ShowTests.cs b/Kyoo.Tests/Database/SpecificTests/ShowTests.cs index c7b89be6..2f87afca 100644 --- a/Kyoo.Tests/Database/SpecificTests/ShowTests.cs +++ b/Kyoo.Tests/Database/SpecificTests/ShowTests.cs @@ -85,11 +85,11 @@ namespace Kyoo.Tests.Database await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows - .Include(x => x.Genres) + .Include(x => x.Studio) .FirstAsync(); Assert.Equal(value.Slug, show.Slug); - Assert.Equal("studio", edited.Studio.Slug); + Assert.Equal("studio", show.Studio.Slug); } [Fact] @@ -106,7 +106,7 @@ namespace Kyoo.Tests.Database Show show = await database.Shows.FirstAsync(); Assert.Equal(value.Slug, show.Slug); - Assert.Equal(value.Aliases, edited.Aliases); + Assert.Equal(value.Aliases, show.Aliases); } [Fact] @@ -135,12 +135,13 @@ namespace Kyoo.Tests.Database 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}), - edited.People.Select(x => new{x.Role, x.Slug, x.People.Name})); + show.People.Select(x => new{x.Role, x.Slug, x.People.Name})); } [Fact] @@ -235,9 +236,11 @@ 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) + .ThenInclude(x => x.Provider) .Include(x => x.Genres) .Include(x => x.People) .Include(x => x.Studio) @@ -264,6 +267,7 @@ namespace Kyoo.Tests.Database await using DatabaseContext context = Repositories.Context.New(); Show retrieved = await context.Shows .Include(x => x.ExternalIDs) + .ThenInclude(x => x.Provider) .FirstAsync(x => x.ID == created.ID); KAssert.DeepEqual(expected, retrieved); Assert.Equal(1, retrieved.ExternalIDs.Count); diff --git a/Kyoo.Tests/Database/TestSample.cs b/Kyoo.Tests/Database/TestSample.cs index 0faf16da..a3e113bf 100644 --- a/Kyoo.Tests/Database/TestSample.cs +++ b/Kyoo.Tests/Database/TestSample.cs @@ -17,6 +17,27 @@ namespace Kyoo.Tests Name = "New Library", Paths = new [] {"/a/random/path"} } + }, + { + typeof(Show), + () => new Show + { + ID = 2, + Slug = "new-show", + Title = "New Show", + Overview = "overview", + Status = Status.Planned, + StartAir = new DateTime(2011, 1, 1), + EndAir = new DateTime(2011, 1, 1), + Images = new Dictionary + { + [Thumbnails.Poster] = "Poster", + [Thumbnails.Logo] = "Logo", + [Thumbnails.Thumbnail] = "Thumbnail" + }, + IsMovie = false, + Studio = null + } } }; @@ -64,6 +85,7 @@ namespace Kyoo.Tests "In time, however, these childhood friends drifted apart, and when they became high " + "school students, they had long ceased to think of each other as friends.", Status = Status.Finished, + StudioID = 1, StartAir = new DateTime(2011, 1, 1), EndAir = new DateTime(2011, 1, 1), Images = new Dictionary @@ -182,8 +204,7 @@ namespace Kyoo.Tests [Thumbnails.Poster] = "Poster", [Thumbnails.Logo] = "path/tvdb.svg", [Thumbnails.Thumbnail] = "Thumbnail" - }, - LogoExtension = "svg" + } } }, { @@ -218,6 +239,7 @@ namespace Kyoo.Tests Show show = Get(); show.ID = 0; + show.StudioID = 0; context.Shows.Add(show); Season season = Get(); diff --git a/Kyoo.Tests/KAssert.cs b/Kyoo.Tests/KAssert.cs index 3acee11a..e4c2e28f 100644 --- a/Kyoo.Tests/KAssert.cs +++ b/Kyoo.Tests/KAssert.cs @@ -1,8 +1,5 @@ -using System.Collections; -using System.Linq; -using System.Reflection; +using FluentAssertions; using JetBrains.Annotations; -using Xunit; using Xunit.Sdk; namespace Kyoo.Tests @@ -21,11 +18,7 @@ namespace Kyoo.Tests [AssertionMethod] public static void DeepEqual(T expected, T value) { - PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public); - foreach (PropertyInfo property in properties) - Assert.Equal(property.GetValue(expected), property.GetValue(value)); - if (!properties.Any()) - Assert.Equal(expected, value); + value.Should().BeEquivalentTo(expected); } /// diff --git a/Kyoo.Tests/Kyoo.Tests.csproj b/Kyoo.Tests/Kyoo.Tests.csproj index eeaf81f9..b6b25e03 100644 --- a/Kyoo.Tests/Kyoo.Tests.csproj +++ b/Kyoo.Tests/Kyoo.Tests.csproj @@ -15,6 +15,7 @@ all + diff --git a/Kyoo.TheMovieDb/ProviderTmdb.cs b/Kyoo.TheMovieDb/ProviderTmdb.cs index 96bbfb5a..95c0cc3d 100644 --- a/Kyoo.TheMovieDb/ProviderTmdb.cs +++ b/Kyoo.TheMovieDb/ProviderTmdb.cs @@ -33,7 +33,6 @@ namespace Kyoo.TheMovieDb { Slug = "the-moviedb", Name = "TheMovieDB", - LogoExtension = "svg", Images = new Dictionary { [Thumbnails.Logo] = "https://www.themoviedb.org/assets/2/v4/logos/v2/" + diff --git a/Kyoo.TheTvdb/ProviderTvdb.cs b/Kyoo.TheTvdb/ProviderTvdb.cs index 13aefa80..66e330f9 100644 --- a/Kyoo.TheTvdb/ProviderTvdb.cs +++ b/Kyoo.TheTvdb/ProviderTvdb.cs @@ -32,7 +32,6 @@ namespace Kyoo.TheTvdb { Slug = "the-tvdb", Name = "TheTVDB", - LogoExtension = "png", Images = new Dictionary { [Thumbnails.Logo] = "https://www.thetvdb.com/images/logo.png" diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index a757d1c9..42483b08 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -53,6 +53,7 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; + obj.ExternalIDs.ForEach(x => _database.MetadataIds().Attach(x)); await _database.SaveChangesAsync($"Trying to insert a duplicated collection (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 51f58cd9..ac243a68 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -111,6 +111,7 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; + obj.ExternalIDs.ForEach(x => _database.MetadataIds().Attach(x)); await _database.SaveChangesAsync($"Trying to insert a duplicated episode (slug {obj.Slug} already exists)."); return await ValidateTracks(obj); } diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index b7b782bc..a8e552af 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -54,7 +54,8 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.ProviderLinks.ForEach(x => _database.Entry(x).State = EntityState.Added); + if (obj.Providers != null) + _database.AttachRange(obj.Providers); await _database.SaveChangesAsync($"Trying to insert a duplicated library (slug {obj.Slug} already exists)."); return obj; } @@ -63,15 +64,12 @@ namespace Kyoo.Controllers protected override async Task Validate(Library resource) { await base.Validate(resource); - resource.ProviderLinks = resource.Providers? - .Select(x => Link.Create(resource, x)) - .ToList(); - await resource.ProviderLinks.ForEachAsync(async id => + if (resource.Providers != null) { - id.Second = await _providers.CreateIfNotExists(id.Second); - id.SecondID = id.Second.ID; - _database.Entry(id.Second).State = EntityState.Detached; - }); + resource.Providers = await resource.Providers + .SelectAsync(x => _providers.CreateIfNotExists(x)) + .ToListAsync(); + } } /// diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index 6ca99c37..a55c8e0e 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -62,6 +62,7 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; + obj.ExternalIDs.ForEach(x => _database.MetadataIds().Attach(x)); await _database.SaveChangesAsync($"Trying to insert a duplicated people (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index c481e866..12900300 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -87,6 +87,7 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; + obj.ExternalIDs.ForEach(x => _database.MetadataIds().Attach(x)); await _database.SaveChangesAsync($"Trying to insert a duplicated season (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 84923cd3..c0f29cec 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -76,8 +76,10 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.GenreLinks.ForEach(x => _database.Entry(x).State = EntityState.Added); + if (obj.Genres != null) + _database.AttachRange(obj.Genres); obj.People.ForEach(x => _database.Entry(x).State = EntityState.Added); + obj.ExternalIDs.ForEach(x => _database.MetadataIds().Attach(x)); await _database.SaveChangesAsync($"Trying to insert a duplicated show (slug {obj.Slug} already exists)."); return obj; } @@ -89,15 +91,13 @@ namespace Kyoo.Controllers if (resource.Studio != null) resource.Studio = await _studios.CreateIfNotExists(resource.Studio); - resource.GenreLinks = resource.Genres? - .Select(x => Link.Create(resource, x)) - .ToList(); - await resource.GenreLinks.ForEachAsync(async id => + if (resource.Genres != null) { - id.Second = await _genres.CreateIfNotExists(id.Second); - id.SecondID = id.Second.ID; - _database.Entry(id.Second).State = EntityState.Detached; - }); + resource.Genres = await resource.Genres + .SelectAsync(x => _genres.CreateIfNotExists(x)) + .ToListAsync(); + } + await resource.ExternalIDs.ForEachAsync(async id => { id.Provider = await _providers.CreateIfNotExists(id.Provider); diff --git a/Kyoo/Controllers/Repositories/StudioRepository.cs b/Kyoo/Controllers/Repositories/StudioRepository.cs index ef0dfecc..744529bd 100644 --- a/Kyoo/Controllers/Repositories/StudioRepository.cs +++ b/Kyoo/Controllers/Repositories/StudioRepository.cs @@ -54,6 +54,7 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; + obj.ExternalIDs.ForEach(x => _database.MetadataIds().Attach(x)); await _database.SaveChangesAsync($"Trying to insert a duplicated studio (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index bd24d975..e052cfe5 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -110,6 +110,7 @@ namespace Kyoo.Controllers { if (item == null) throw new ArgumentNullException(nameof(item)); + // TODO handle extensions string imageName = imageID switch { Thumbnails.Poster => "poster.jpg", @@ -142,9 +143,7 @@ namespace Kyoo.Controllers return _files.Combine(_options.CurrentValue.PeoplePath, $"{actor.Slug}-{imageName}"); case Provider provider: - return _files.Combine( - _options.CurrentValue.ProviderPath, - $"{provider.Slug}-{imageName[..^4]}{provider.LogoExtension}"); + return _files.Combine(_options.CurrentValue.ProviderPath, $"{provider.Slug}-{imageName}"); default: throw new NotSupportedException($"The type {typeof(T).Name} is not supported."); diff --git a/Kyoo/Kyoo.csproj b/Kyoo/Kyoo.csproj index 70fae8ca..e7f4f1b4 100644 --- a/Kyoo/Kyoo.csproj +++ b/Kyoo/Kyoo.csproj @@ -40,7 +40,7 @@ - + From 47f855bdb9716629d05bc1eacf6797f9c5cbc72b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 31 Jul 2021 01:22:46 +0200 Subject: [PATCH 12/34] Fixing tests --- Kyoo.Common/Kyoo.Common.csproj | 2 -- .../Models/Attributes/LinkAttribute.cs | 13 ---------- Kyoo.Common/Models/PeopleRole.cs | 4 +-- Kyoo.Common/Models/Resources/Collection.cs | 18 +++---------- Kyoo.Common/Models/Resources/Episode.cs | 9 +++++-- Kyoo.Common/Models/Resources/Genre.cs | 10 +------- Kyoo.Common/Models/Resources/Library.cs | 18 ------------- Kyoo.Common/Models/Resources/People.cs | 4 ++- Kyoo.Common/Models/Resources/Provider.cs | 12 +++------ Kyoo.Common/Models/Resources/Season.cs | 4 ++- Kyoo.Common/Models/Resources/Show.cs | 25 +++++-------------- Kyoo.Common/Models/Resources/User.cs | 8 ------ .../Database/SpecificTests/ShowTests.cs | 19 +++++++++++++- .../Repositories/ShowRepository.cs | 3 +++ 14 files changed, 49 insertions(+), 100 deletions(-) delete mode 100644 Kyoo.Common/Models/Attributes/LinkAttribute.cs diff --git a/Kyoo.Common/Kyoo.Common.csproj b/Kyoo.Common/Kyoo.Common.csproj index c4c2f3d6..ec328eab 100644 --- a/Kyoo.Common/Kyoo.Common.csproj +++ b/Kyoo.Common/Kyoo.Common.csproj @@ -16,8 +16,6 @@ true snupkg default - - diff --git a/Kyoo.Common/Models/Attributes/LinkAttribute.cs b/Kyoo.Common/Models/Attributes/LinkAttribute.cs deleted file mode 100644 index d98ad90a..00000000 --- a/Kyoo.Common/Models/Attributes/LinkAttribute.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using JetBrains.Annotations; -using Kyoo.Models.Attributes; - -namespace Kyoo.Common.Models.Attributes -{ - /// - /// An attribute to mark Link properties on resource. - /// - [AttributeUsage(AttributeTargets.Property)] - [MeansImplicitUse] - public class LinkAttribute : SerializeIgnoreAttribute { } -} \ No newline at end of file diff --git a/Kyoo.Common/Models/PeopleRole.cs b/Kyoo.Common/Models/PeopleRole.cs index 062bb3e8..daf9c7cd 100644 --- a/Kyoo.Common/Models/PeopleRole.cs +++ b/Kyoo.Common/Models/PeopleRole.cs @@ -17,8 +17,8 @@ namespace Kyoo.Models public string Slug => ForPeople ? Show.Slug : People.Slug; /// - /// Should this role be used as a Show substitute (the value is false) or - /// as a People substitute (the value is true). + /// Should this role be used as a Show substitute (the value is true) or + /// as a People substitute (the value is false). /// public bool ForPeople { get; set; } diff --git a/Kyoo.Common/Models/Resources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs index ad85ad34..50a4ede8 100644 --- a/Kyoo.Common/Models/Resources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -1,5 +1,5 @@ -using System.Collections.Generic; -using Kyoo.Common.Models.Attributes; +using System; +using System.Collections.Generic; using Kyoo.Models.Attributes; namespace Kyoo.Models @@ -30,6 +30,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/collection/{Slug}/poster")] + [Obsolete("Use Images instead of this, this is only kept for the API response.")] public string Poster => Images?.GetValueOrDefault(Thumbnails.Poster); /// @@ -49,18 +50,5 @@ namespace Kyoo.Models /// [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } - -#if ENABLE_INTERNAL_LINKS - - /// - /// The internal link between this collection and shows in the list. - /// - [Link] public ICollection> ShowLinks { get; set; } - - /// - /// The internal link between this collection and libraries in the list. - /// - [Link] public ICollection> LibraryLinks { get; set; } -#endif } } diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index 2ab30437..3f29088f 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -74,9 +74,13 @@ namespace Kyoo.Models /// [SerializeIgnore] public int? SeasonID { get; set; } /// - /// The season that contains this episode. This must be explicitly loaded via a call to . - /// This can be null if the season is unknown and the episode is only identified by it's . + /// The season that contains this episode. + /// This must be explicitly loaded via a call to . /// + /// + /// This can be null if the season is unknown and the episode is only identified + /// by it's . + /// [LoadableRelation(nameof(SeasonID))] public Season Season { get; set; } /// @@ -108,6 +112,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/episodes/{Slug}/thumb")] + [Obsolete("Use Images instead of this, this is only kept for the API response.")] public string Thumb => Images?.GetValueOrDefault(Thumbnails.Thumbnail); /// diff --git a/Kyoo.Common/Models/Resources/Genre.cs b/Kyoo.Common/Models/Resources/Genre.cs index c7aaa76f..e5fcbed8 100644 --- a/Kyoo.Common/Models/Resources/Genre.cs +++ b/Kyoo.Common/Models/Resources/Genre.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Kyoo.Common.Models.Attributes; using Kyoo.Models.Attributes; namespace Kyoo.Models @@ -24,14 +23,7 @@ namespace Kyoo.Models /// The list of shows that have this genre. /// [LoadableRelation] public ICollection Shows { get; set; } - -#if ENABLE_INTERNAL_LINKS - /// - /// The internal link between this genre and shows in the list. - /// - [Link] public ICollection> ShowLinks { get; set; } -#endif - + /// /// Create a new, empty . /// diff --git a/Kyoo.Common/Models/Resources/Library.cs b/Kyoo.Common/Models/Resources/Library.cs index a72d6a37..6580f988 100644 --- a/Kyoo.Common/Models/Resources/Library.cs +++ b/Kyoo.Common/Models/Resources/Library.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Kyoo.Common.Models.Attributes; using Kyoo.Models.Attributes; namespace Kyoo.Models @@ -39,22 +38,5 @@ namespace Kyoo.Models /// The list of collections in this library. /// [LoadableRelation] public ICollection Collections { get; set; } - -#if ENABLE_INTERNAL_LINKS - /// - /// The internal link between this library and provider in the list. - /// - [Link] public ICollection> ProviderLinks { get; set; } - - /// - /// The internal link between this library and shows in the list. - /// - [Link] public ICollection> ShowLinks { get; set; } - - /// - /// The internal link between this library and collection in the list. - /// - [Link] public ICollection> CollectionLinks { get; set; } -#endif } } diff --git a/Kyoo.Common/Models/Resources/People.cs b/Kyoo.Common/Models/Resources/People.cs index d555a10d..ff6fd576 100644 --- a/Kyoo.Common/Models/Resources/People.cs +++ b/Kyoo.Common/Models/Resources/People.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Kyoo.Models.Attributes; namespace Kyoo.Models @@ -28,6 +29,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [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(Thumbnails.Poster); /// diff --git a/Kyoo.Common/Models/Resources/Provider.cs b/Kyoo.Common/Models/Resources/Provider.cs index 35f94fc9..db118de2 100644 --- a/Kyoo.Common/Models/Resources/Provider.cs +++ b/Kyoo.Common/Models/Resources/Provider.cs @@ -1,5 +1,5 @@ +using System; using System.Collections.Generic; -using Kyoo.Common.Models.Attributes; using Kyoo.Controllers; using Kyoo.Models.Attributes; @@ -31,20 +31,14 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/providers/{Slug}/logo")] + [Obsolete("Use Images instead of this, this is only kept for the API response.")] public string Logo => Images?.GetValueOrDefault(Thumbnails.Logo); /// /// The list of libraries that uses this provider. /// [LoadableRelation] public ICollection Libraries { get; set; } - -#if ENABLE_INTERNAL_LINKS - /// - /// The internal link between this provider and libraries in the list. - /// - [Link] public ICollection> LibraryLinks { get; set; } -#endif - + /// /// Create a new, default, /// diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index 46639c3c..2cfadfb9 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -45,7 +45,8 @@ namespace Kyoo.Models /// [SerializeIgnore] public int ShowID { get; set; } /// - /// The show that contains this season. This must be explicitly loaded via a call to . + /// The show that contains this season. + /// This must be explicitly loaded via a call to . /// [LoadableRelation(nameof(ShowID))] public Show Show { get; set; } @@ -83,6 +84,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [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(Thumbnails.Poster); /// diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index afaaa481..50059692 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Kyoo.Common.Models.Attributes; using Kyoo.Controllers; using Kyoo.Models.Attributes; @@ -47,6 +46,7 @@ namespace Kyoo.Models /// An URL to a trailer. This could be any path supported by the . /// /// 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(Thumbnails.Trailer); /// @@ -70,6 +70,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/shows/{Slug}/poster")] + [Obsolete("Use Images instead of this, this is only kept for the API response.")] public string Poster => Images?.GetValueOrDefault(Thumbnails.Poster); /// @@ -78,6 +79,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/shows/{Slug}/logo")] + [Obsolete("Use Images instead of this, this is only kept for the API response.")] public string Logo => Images?.GetValueOrDefault(Thumbnails.Logo); /// @@ -86,6 +88,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] + [Obsolete("Use Images instead of this, this is only kept for the API response.")] public string Backdrop => Images?.GetValueOrDefault(Thumbnails.Thumbnail); /// @@ -101,7 +104,8 @@ namespace Kyoo.Models /// [SerializeIgnore] public int? StudioID { get; set; } /// - /// The Studio that made this show. This must be explicitly loaded via a call to . + /// The Studio that made this show. + /// This must be explicitly loaded via a call to . /// [LoadableRelation(nameof(StudioID))] [EditableRelation] public Studio Studio { get; set; } @@ -137,23 +141,6 @@ namespace Kyoo.Models /// [LoadableRelation] public ICollection Collections { get; set; } -#if ENABLE_INTERNAL_LINKS - /// - /// The internal link between this show and libraries in the list. - /// - [Link] public ICollection> LibraryLinks { get; set; } - - /// - /// The internal link between this show and collections in the list. - /// - [Link] public ICollection> CollectionLinks { get; set; } - - /// - /// The internal link between this show and genres in the list. - /// - [Link] public ICollection> GenreLinks { get; set; } -#endif - /// public void OnMerge(object merged) { diff --git a/Kyoo.Common/Models/Resources/User.cs b/Kyoo.Common/Models/Resources/User.cs index 8cae9671..3400df62 100644 --- a/Kyoo.Common/Models/Resources/User.cs +++ b/Kyoo.Common/Models/Resources/User.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Kyoo.Common.Models.Attributes; namespace Kyoo.Models { @@ -51,13 +50,6 @@ namespace Kyoo.Models /// The list of episodes the user is watching (stopped in progress or the next episode of the show) /// public ICollection CurrentlyWatching { get; set; } - -#if ENABLE_INTERNAL_LINKS - /// - /// Links between Users and Shows. - /// - [Link] public ICollection> ShowLinks { get; set; } -#endif } /// diff --git a/Kyoo.Tests/Database/SpecificTests/ShowTests.cs b/Kyoo.Tests/Database/SpecificTests/ShowTests.cs index 2f87afca..424ca76c 100644 --- a/Kyoo.Tests/Database/SpecificTests/ShowTests.cs +++ b/Kyoo.Tests/Database/SpecificTests/ShowTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using FluentAssertions; using Kyoo.Controllers; using Kyoo.Models; using Microsoft.EntityFrameworkCore; @@ -243,9 +244,25 @@ namespace Kyoo.Tests.Database .ThenInclude(x => x.Provider) .Include(x => x.Genres) .Include(x => x.People) + .ThenInclude(x => x.People) .Include(x => x.Studio) .FirstAsync(x => x.ID == created.ID); - KAssert.DeepEqual(expected, retrieved); + 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 => + { + x.Show = null; + x.People.Roles = null; + }); + + retrieved.Should().BeEquivalentTo(expected); } [Fact] diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index c0f29cec..1c7471f7 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -89,7 +89,10 @@ namespace Kyoo.Controllers { await base.Validate(resource); if (resource.Studio != null) + { resource.Studio = await _studios.CreateIfNotExists(resource.Studio); + resource.StudioID = resource.Studio.ID; + } if (resource.Genres != null) { From 4ae28f25941ecc461087856ddb028863a3a40ec8 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 31 Jul 2021 14:18:02 +0200 Subject: [PATCH 13/34] LibraryRepository: Adding more tests --- Kyoo.Common/Controllers/IThumbnailsManager.cs | 2 +- Kyoo.Common/Models/LibraryItem.cs | 2 +- Kyoo.Common/Models/Resources/Collection.cs | 2 +- Kyoo.Common/Models/Resources/Episode.cs | 2 +- .../Resources/Interfaces/IThumbnails.cs | 4 +- Kyoo.Common/Models/Resources/People.cs | 2 +- Kyoo.Common/Models/Resources/Provider.cs | 4 +- Kyoo.Common/Models/Resources/Season.cs | 2 +- Kyoo.Common/Models/Resources/Show.cs | 8 +- .../Database/SpecificTests/LibraryTests.cs | 107 ++++++++++++++++++ Kyoo.Tests/Database/TestSample.cs | 63 +++++++---- Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs | 12 +- .../Convertors/CollectionConvertors.cs | 8 +- .../Convertors/EpisodeConvertors.cs | 2 +- Kyoo.TheMovieDb/Convertors/MovieConvertors.cs | 10 +- .../Convertors/PeopleConvertors.cs | 11 +- .../Convertors/SeasonConvertors.cs | 2 +- Kyoo.TheMovieDb/Convertors/ShowConvertors.cs | 10 +- Kyoo.TheMovieDb/ProviderTmdb.cs | 2 +- Kyoo.TheTvdb/Convertors.cs | 10 +- Kyoo.TheTvdb/ProviderTvdb.cs | 2 +- .../Repositories/LibraryRepository.cs | 31 ++--- .../Repositories/ProviderRepository.cs | 1 - Kyoo/Controllers/ThumbnailsManager.cs | 6 +- Kyoo/Views/EpisodeApi.cs | 4 +- Kyoo/Views/PeopleApi.cs | 4 +- Kyoo/Views/ProviderApi.cs | 4 +- Kyoo/Views/SeasonApi.cs | 4 +- Kyoo/Views/ShowApi.cs | 6 +- 29 files changed, 224 insertions(+), 103 deletions(-) diff --git a/Kyoo.Common/Controllers/IThumbnailsManager.cs b/Kyoo.Common/Controllers/IThumbnailsManager.cs index 023e566c..aac09210 100644 --- a/Kyoo.Common/Controllers/IThumbnailsManager.cs +++ b/Kyoo.Common/Controllers/IThumbnailsManager.cs @@ -30,7 +30,7 @@ namespace Kyoo.Controllers /// Retrieve the local path of the poster of the given item. /// /// The item to retrieve the poster from. - /// The ID of the image. See for values. + /// The ID of the image. See for values. /// The type of the item /// If the type does not have a poster /// The path of the poster for the given resource (it might or might not exists). diff --git a/Kyoo.Common/Models/LibraryItem.cs b/Kyoo.Common/Models/LibraryItem.cs index 88fd799a..18680b9e 100644 --- a/Kyoo.Common/Models/LibraryItem.cs +++ b/Kyoo.Common/Models/LibraryItem.cs @@ -63,7 +63,7 @@ namespace Kyoo.Models /// This can be disabled using the internal query flag. /// [SerializeAs("{HOST}/api/{Type:l}/{Slug}/poster")] - public string Poster => Images?.GetValueOrDefault(Thumbnails.Poster); + public string Poster => Images?.GetValueOrDefault(Models.Images.Poster); /// /// The type of this item (ether a collection, a show or a movie). diff --git a/Kyoo.Common/Models/Resources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs index 50a4ede8..20cc481b 100644 --- a/Kyoo.Common/Models/Resources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -31,7 +31,7 @@ namespace Kyoo.Models /// [SerializeAs("{HOST}/api/collection/{Slug}/poster")] [Obsolete("Use Images instead of this, this is only kept for the API response.")] - public string Poster => Images?.GetValueOrDefault(Thumbnails.Poster); + public string Poster => Images?.GetValueOrDefault(Models.Images.Poster); /// /// The description of this collection. diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index 3f29088f..eddb86ba 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -113,7 +113,7 @@ namespace Kyoo.Models /// [SerializeAs("{HOST}/api/episodes/{Slug}/thumb")] [Obsolete("Use Images instead of this, this is only kept for the API response.")] - public string Thumb => Images?.GetValueOrDefault(Thumbnails.Thumbnail); + public string Thumb => Images?.GetValueOrDefault(Models.Images.Thumbnail); /// /// The title of this episode. diff --git a/Kyoo.Common/Models/Resources/Interfaces/IThumbnails.cs b/Kyoo.Common/Models/Resources/Interfaces/IThumbnails.cs index 074751c4..ba4999de 100644 --- a/Kyoo.Common/Models/Resources/Interfaces/IThumbnails.cs +++ b/Kyoo.Common/Models/Resources/Interfaces/IThumbnails.cs @@ -13,7 +13,7 @@ namespace Kyoo.Models /// The string value should be a path supported by the . /// /// - /// An arbitrary index should not be used, instead use indexes from + /// An arbitrary index should not be used, instead use indexes from /// public Dictionary Images { get; set; } @@ -23,7 +23,7 @@ namespace Kyoo.Models /// /// A class containing constant values for images. To be used as index of a . /// - public static class Thumbnails + public static class Images { /// /// A poster is a 9/16 format image with the cover of the resource. diff --git a/Kyoo.Common/Models/Resources/People.cs b/Kyoo.Common/Models/Resources/People.cs index ff6fd576..7bd59620 100644 --- a/Kyoo.Common/Models/Resources/People.cs +++ b/Kyoo.Common/Models/Resources/People.cs @@ -30,7 +30,7 @@ namespace Kyoo.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(Thumbnails.Poster); + public string Poster => Images?.GetValueOrDefault(Models.Images.Poster); /// [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } diff --git a/Kyoo.Common/Models/Resources/Provider.cs b/Kyoo.Common/Models/Resources/Provider.cs index db118de2..31b87f3f 100644 --- a/Kyoo.Common/Models/Resources/Provider.cs +++ b/Kyoo.Common/Models/Resources/Provider.cs @@ -32,7 +32,7 @@ namespace Kyoo.Models /// [SerializeAs("{HOST}/api/providers/{Slug}/logo")] [Obsolete("Use Images instead of this, this is only kept for the API response.")] - public string Logo => Images?.GetValueOrDefault(Thumbnails.Logo); + public string Logo => Images?.GetValueOrDefault(Models.Images.Logo); /// /// The list of libraries that uses this provider. @@ -56,7 +56,7 @@ namespace Kyoo.Models Name = name; Images = new Dictionary { - [Thumbnails.Logo] = logo + [Models.Images.Logo] = logo }; } } diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index 2cfadfb9..2c5d59eb 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -85,7 +85,7 @@ namespace Kyoo.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(Thumbnails.Poster); + public string Poster => Images?.GetValueOrDefault(Models.Images.Poster); /// [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index 50059692..03c86e5e 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -47,7 +47,7 @@ namespace Kyoo.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(Thumbnails.Trailer); + public string TrailerUrl => Images?.GetValueOrDefault(Models.Images.Trailer); /// /// The date this show started airing. It can be null if this is unknown. @@ -71,7 +71,7 @@ namespace Kyoo.Models /// [SerializeAs("{HOST}/api/shows/{Slug}/poster")] [Obsolete("Use Images instead of this, this is only kept for the API response.")] - public string Poster => Images?.GetValueOrDefault(Thumbnails.Poster); + public string Poster => Images?.GetValueOrDefault(Models.Images.Poster); /// /// The path of this show's logo. @@ -80,7 +80,7 @@ namespace Kyoo.Models /// [SerializeAs("{HOST}/api/shows/{Slug}/logo")] [Obsolete("Use Images instead of this, this is only kept for the API response.")] - public string Logo => Images?.GetValueOrDefault(Thumbnails.Logo); + public string Logo => Images?.GetValueOrDefault(Models.Images.Logo); /// /// The path of this show's backdrop. @@ -89,7 +89,7 @@ namespace Kyoo.Models /// [SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] [Obsolete("Use Images instead of this, this is only kept for the API response.")] - public string Backdrop => Images?.GetValueOrDefault(Thumbnails.Thumbnail); + public string Backdrop => Images?.GetValueOrDefault(Models.Images.Thumbnail); /// /// True if this show represent a movie, false otherwise. diff --git a/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs b/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs index 079f50cf..12449b5e 100644 --- a/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs +++ b/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs @@ -1,7 +1,10 @@ +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models; +using Microsoft.EntityFrameworkCore; using Xunit; using Xunit.Abstractions; @@ -36,6 +39,39 @@ namespace Kyoo.Tests.Database _repository = Repositories.LibraryManager.LibraryRepository; } + [Fact] + public async Task CreateWithoutPathTest() + { + Library library = TestSample.GetNew(); + library.Paths = null; + await Assert.ThrowsAsync(() => _repository.Create(library)); + } + + [Fact] + public async Task CreateWithEmptySlugTest() + { + Library library = TestSample.GetNew(); + library.Slug = ""; + await Assert.ThrowsAsync(() => _repository.Create(library)); + } + + [Fact] + public async Task CreateWithNumberSlugTest() + { + Library library = TestSample.GetNew(); + library.Slug = "2"; + Library ret = await _repository.Create(library); + Assert.Equal("2!", library.Slug); + } + + [Fact] + public async Task CreateWithoutNameTest() + { + Library library = TestSample.GetNew(); + library.Name = null; + await Assert.ThrowsAsync(() => _repository.Create(library)); + } + [Fact] public async Task CreateWithProvider() { @@ -47,5 +83,76 @@ namespace Kyoo.Tests.Database Assert.Equal(1, retrieved.Providers.Count); Assert.Equal(TestSample.Get().Slug, retrieved.Providers.First().Slug); } + + [Fact] + public async Task EditTest() + { + Library value = await _repository.Get(TestSample.Get().Slug); + 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() + { + Library value = await _repository.Get(TestSample.Get().Slug); + value.Providers = new[] + { + 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() + { + Library value = await _repository.Get(TestSample.Get().Slug); + 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")] + [InlineData("title")] + [InlineData("TiTlE")] + [InlineData("SuPeR")] + public async Task SearchTest(string query) + { + Library value = new() + { + Slug = "super-test", + Name = "This is a test title", + Paths = new [] {"path"} + }; + await _repository.Create(value); + ICollection ret = await _repository.Search(query); + KAssert.DeepEqual(value, ret.First()); + } } } \ No newline at end of file diff --git a/Kyoo.Tests/Database/TestSample.cs b/Kyoo.Tests/Database/TestSample.cs index a3e113bf..d8b06333 100644 --- a/Kyoo.Tests/Database/TestSample.cs +++ b/Kyoo.Tests/Database/TestSample.cs @@ -31,13 +31,26 @@ namespace Kyoo.Tests EndAir = new DateTime(2011, 1, 1), Images = new Dictionary { - [Thumbnails.Poster] = "Poster", - [Thumbnails.Logo] = "Logo", - [Thumbnails.Thumbnail] = "Thumbnail" + [Images.Poster] = "Poster", + [Images.Logo] = "Logo", + [Images.Thumbnail] = "Thumbnail" }, IsMovie = false, Studio = null } + }, + { + typeof(Provider), + () => new Provider + { + ID = 2, + Slug = "new-provider", + Name = "Provider NewSample", + Images = new Dictionary + { + [Images.Logo] = "logo" + } + } } }; @@ -64,7 +77,7 @@ namespace Kyoo.Tests Overview = "A nice collection for tests", Images = new Dictionary { - [Thumbnails.Poster] = "Poster" + [Images.Poster] = "Poster" } } }, @@ -90,9 +103,9 @@ namespace Kyoo.Tests EndAir = new DateTime(2011, 1, 1), Images = new Dictionary { - [Thumbnails.Poster] = "Poster", - [Thumbnails.Logo] = "Logo", - [Thumbnails.Thumbnail] = "Thumbnail" + [Images.Poster] = "Poster", + [Images.Logo] = "Logo", + [Images.Thumbnail] = "Thumbnail" }, IsMovie = false, Studio = null @@ -112,9 +125,9 @@ namespace Kyoo.Tests EndDate = new DateTime(2020, 07, 05), Images = new Dictionary { - [Thumbnails.Poster] = "Poster", - [Thumbnails.Logo] = "Logo", - [Thumbnails.Thumbnail] = "Thumbnail" + [Images.Poster] = "Poster", + [Images.Logo] = "Logo", + [Images.Thumbnail] = "Thumbnail" }, } }, @@ -132,9 +145,9 @@ namespace Kyoo.Tests Path = "/home/kyoo/anohana-s1e1", Images = new Dictionary { - [Thumbnails.Poster] = "Poster", - [Thumbnails.Logo] = "Logo", - [Thumbnails.Thumbnail] = "Thumbnail" + [Images.Poster] = "Poster", + [Images.Logo] = "Logo", + [Images.Thumbnail] = "Thumbnail" }, Title = "Episode 1", Overview = "Summary of the first episode", @@ -168,9 +181,9 @@ namespace Kyoo.Tests Name = "The Actor", Images = new Dictionary { - [Thumbnails.Poster] = "Poster", - [Thumbnails.Logo] = "Logo", - [Thumbnails.Thumbnail] = "Thumbnail" + [Images.Poster] = "Poster", + [Images.Logo] = "Logo", + [Images.Thumbnail] = "Thumbnail" }, } }, @@ -201,9 +214,9 @@ namespace Kyoo.Tests Name = "The TVDB", Images = new Dictionary { - [Thumbnails.Poster] = "Poster", - [Thumbnails.Logo] = "path/tvdb.svg", - [Thumbnails.Thumbnail] = "Thumbnail" + [Images.Poster] = "Poster", + [Images.Logo] = "path/tvdb.svg", + [Images.Thumbnail] = "Thumbnail" } } }, @@ -306,9 +319,9 @@ namespace Kyoo.Tests Path = "/home/kyoo/anohana-3", Images = new Dictionary { - [Thumbnails.Poster] = "Poster", - [Thumbnails.Logo] = "Logo", - [Thumbnails.Thumbnail] = "Thumbnail" + [Images.Poster] = "Poster", + [Images.Logo] = "Logo", + [Images.Thumbnail] = "Thumbnail" }, Title = "Episode 3", Overview = "Summary of the third absolute episode", @@ -326,9 +339,9 @@ namespace Kyoo.Tests Path = "/home/kyoo/john-wick", Images = new Dictionary { - [Thumbnails.Poster] = "Poster", - [Thumbnails.Logo] = "Logo", - [Thumbnails.Thumbnail] = "Thumbnail" + [Images.Poster] = "Poster", + [Images.Logo] = "Logo", + [Images.Thumbnail] = "Thumbnail" }, Title = "John wick", Overview = "A movie episode test", diff --git a/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs b/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs index 35e389b6..58ce0a8c 100644 --- a/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs +++ b/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs @@ -32,7 +32,7 @@ namespace Kyoo.Tests.Identifier.Tvdb Assert.Equal("Aliases", show.Aliases[0]); Assert.Equal("overview", show.Overview); Assert.Equal(new DateTime(2021, 7, 23), show.StartAir); - Assert.Equal("https://www.thetvdb.com/poster", show.Poster); + Assert.Equal("https://www.thetvdb.com/poster", show.Images[Images.Poster]); Assert.Single(show.ExternalIDs); Assert.Equal("5", show.ExternalIDs.First().DataID); Assert.Equal(provider, show.ExternalIDs.First().Provider); @@ -63,7 +63,7 @@ namespace Kyoo.Tests.Identifier.Tvdb Assert.Equal("Aliases", show.Aliases[0]); Assert.Equal("overview", show.Overview); Assert.Null(show.StartAir); - Assert.Equal("https://www.thetvdb.com/poster", show.Poster); + Assert.Equal("https://www.thetvdb.com/poster", show.Images[Images.Poster]); Assert.Single(show.ExternalIDs); Assert.Equal("5", show.ExternalIDs.First().DataID); Assert.Equal(provider, show.ExternalIDs.First().Provider); @@ -100,8 +100,8 @@ namespace Kyoo.Tests.Identifier.Tvdb Assert.Equal("Aliases", show.Aliases[0]); Assert.Equal("overview", show.Overview); Assert.Equal(new DateTime(2021, 7, 23), show.StartAir); - Assert.Equal("https://www.thetvdb.com/banners/poster", show.Poster); - Assert.Equal("https://www.thetvdb.com/banners/fanart", show.Backdrop); + Assert.Equal("https://www.thetvdb.com/banners/poster", show.Images[Images.Poster]); + Assert.Equal("https://www.thetvdb.com/banners/fanart", show.Images[Images.Thumbnail]); Assert.Single(show.ExternalIDs); Assert.Equal("5", show.ExternalIDs.First().DataID); Assert.Equal(provider, show.ExternalIDs.First().Provider); @@ -130,7 +130,7 @@ namespace Kyoo.Tests.Identifier.Tvdb 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.Poster); + Assert.Equal("https://www.thetvdb.com/banners/image", people.People.Images[Images.Poster]); } [Fact] @@ -154,7 +154,7 @@ namespace Kyoo.Tests.Identifier.Tvdb Assert.Equal(3, episode.EpisodeNumber); Assert.Equal(23, episode.AbsoluteNumber); Assert.Equal("overview", episode.Overview); - Assert.Equal("https://www.thetvdb.com/banners/thumb", episode.Thumb); + Assert.Equal("https://www.thetvdb.com/banners/thumb", episode.Images[Images.Thumbnail]); } } } \ No newline at end of file diff --git a/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs b/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs index c0108b81..c5ab7104 100644 --- a/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs @@ -24,10 +24,10 @@ namespace Kyoo.TheMovieDb Overview = collection.Overview, Images = new Dictionary { - [Thumbnails.Poster] = collection.PosterPath != null + [Images.Poster] = collection.PosterPath != null ? $"https://image.tmdb.org/t/p/original{collection.PosterPath}" : null, - [Thumbnails.Thumbnail] = collection.BackdropPath != null + [Images.Thumbnail] = collection.BackdropPath != null ? $"https://image.tmdb.org/t/p/original{collection.BackdropPath}" : null }, @@ -57,10 +57,10 @@ namespace Kyoo.TheMovieDb Name = collection.Name, Images = new Dictionary { - [Thumbnails.Poster] = collection.PosterPath != null + [Images.Poster] = collection.PosterPath != null ? $"https://image.tmdb.org/t/p/original{collection.PosterPath}" : null, - [Thumbnails.Thumbnail] = collection.BackdropPath != null + [Images.Thumbnail] = collection.BackdropPath != null ? $"https://image.tmdb.org/t/p/original{collection.BackdropPath}" : null }, diff --git a/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs b/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs index 97512aec..d1dd499b 100644 --- a/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs @@ -27,7 +27,7 @@ namespace Kyoo.TheMovieDb ReleaseDate = episode.AirDate, Images = new Dictionary { - [Thumbnails.Thumbnail] = episode.StillPath != null + [Images.Thumbnail] = episode.StillPath != null ? $"https://image.tmdb.org/t/p/original{episode.StillPath}" : null }, diff --git a/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs b/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs index 06ea3108..6a0cf9ba 100644 --- a/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs @@ -30,13 +30,13 @@ namespace Kyoo.TheMovieDb EndAir = movie.ReleaseDate, Images = new Dictionary { - [Thumbnails.Poster] = movie.PosterPath != null + [Images.Poster] = movie.PosterPath != null ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" : null, - [Thumbnails.Thumbnail] = movie.BackdropPath != null + [Images.Thumbnail] = movie.BackdropPath != null ? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}" : null, - [Thumbnails.Trailer] = movie.Videos?.Results + [Images.Trailer] = movie.Videos?.Results .Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube") .Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault(), }, @@ -78,10 +78,10 @@ namespace Kyoo.TheMovieDb EndAir = movie.ReleaseDate, Images = new Dictionary { - [Thumbnails.Poster] = movie.PosterPath != null + [Images.Poster] = movie.PosterPath != null ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" : null, - [Thumbnails.Thumbnail] = movie.BackdropPath != null + [Images.Thumbnail] = movie.BackdropPath != null ? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}" : null, }, diff --git a/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs b/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs index 27460cf0..b3d47410 100644 --- a/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs @@ -3,6 +3,7 @@ using Kyoo.Models; using TMDbLib.Objects.General; using TMDbLib.Objects.People; using TMDbLib.Objects.Search; +using Images = Kyoo.Models.Images; using TvCast = TMDbLib.Objects.TvShows.Cast; using MovieCast = TMDbLib.Objects.Movies.Cast; @@ -29,7 +30,7 @@ namespace Kyoo.TheMovieDb Name = cast.Name, Images = new Dictionary { - [Thumbnails.Poster] = cast.ProfilePath != null + [Images.Poster] = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" : null }, @@ -64,7 +65,7 @@ namespace Kyoo.TheMovieDb Name = cast.Name, Images = new Dictionary { - [Thumbnails.Poster] = cast.ProfilePath != null + [Images.Poster] = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" : null }, @@ -99,7 +100,7 @@ namespace Kyoo.TheMovieDb Name = crew.Name, Images = new Dictionary { - [Thumbnails.Poster] = crew.ProfilePath != null + [Images.Poster] = crew.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{crew.ProfilePath}" : null }, @@ -132,7 +133,7 @@ namespace Kyoo.TheMovieDb Name = person.Name, Images = new Dictionary { - [Thumbnails.Poster] = person.ProfilePath != null + [Images.Poster] = person.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{person.ProfilePath}" : null }, @@ -162,7 +163,7 @@ namespace Kyoo.TheMovieDb Name = person.Name, Images = new Dictionary { - [Thumbnails.Poster] = person.ProfilePath != null + [Images.Poster] = person.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{person.ProfilePath}" : null }, diff --git a/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs b/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs index f7912f9f..1dcc88a6 100644 --- a/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs @@ -26,7 +26,7 @@ namespace Kyoo.TheMovieDb StartDate = season.AirDate, Images = new Dictionary { - [Thumbnails.Poster] = season.PosterPath != null + [Images.Poster] = season.PosterPath != null ? $"https://image.tmdb.org/t/p/original{season.PosterPath}" : null }, diff --git a/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs b/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs index a64cfe3b..7d1b69a0 100644 --- a/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs @@ -30,13 +30,13 @@ namespace Kyoo.TheMovieDb EndAir = tv.LastAirDate, Images = new Dictionary { - [Thumbnails.Poster] = tv.PosterPath != null + [Images.Poster] = tv.PosterPath != null ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" : null, - [Thumbnails.Thumbnail] = tv.BackdropPath != null + [Images.Thumbnail] = tv.BackdropPath != null ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" : null, - [Thumbnails.Trailer] = tv.Videos?.Results + [Images.Trailer] = tv.Videos?.Results .Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube") .Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault() }, @@ -76,10 +76,10 @@ namespace Kyoo.TheMovieDb StartAir = tv.FirstAirDate, Images = new Dictionary { - [Thumbnails.Poster] = tv.PosterPath != null + [Images.Poster] = tv.PosterPath != null ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" : null, - [Thumbnails.Thumbnail] = tv.BackdropPath != null + [Images.Thumbnail] = tv.BackdropPath != null ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" : null, }, diff --git a/Kyoo.TheMovieDb/ProviderTmdb.cs b/Kyoo.TheMovieDb/ProviderTmdb.cs index 95c0cc3d..5085b3b1 100644 --- a/Kyoo.TheMovieDb/ProviderTmdb.cs +++ b/Kyoo.TheMovieDb/ProviderTmdb.cs @@ -35,7 +35,7 @@ namespace Kyoo.TheMovieDb Name = "TheMovieDB", Images = new Dictionary { - [Thumbnails.Logo] = "https://www.themoviedb.org/assets/2/v4/logos/v2/" + + [Images.Logo] = "https://www.themoviedb.org/assets/2/v4/logos/v2/" + "blue_short-8e7b30f73a4020692ccca9c88bafe5dcb6f8a62a4c6bc55cd9ba82bb2cd95f6c.svg" } }; diff --git a/Kyoo.TheTvdb/Convertors.cs b/Kyoo.TheTvdb/Convertors.cs index 4cff035a..2e1f39cc 100644 --- a/Kyoo.TheTvdb/Convertors.cs +++ b/Kyoo.TheTvdb/Convertors.cs @@ -58,7 +58,7 @@ namespace Kyoo.TheTvdb StartAir = _ParseDate(result.FirstAired), Images = new Dictionary { - [Thumbnails.Poster] = result.Poster != null + [Images.Poster] = result.Poster != null ? $"https://www.thetvdb.com{result.Poster}" : null, }, @@ -92,10 +92,10 @@ namespace Kyoo.TheTvdb StartAir = _ParseDate(series.FirstAired), Images = new Dictionary { - [Thumbnails.Poster] = series.Poster != null + [Images.Poster] = series.Poster != null ? $"https://www.thetvdb.com/banners/{series.Poster}" : null, - [Thumbnails.Thumbnail] = series.FanArt != null + [Images.Thumbnail] = series.FanArt != null ? $"https://www.thetvdb.com/banners/{series.FanArt}" : null }, @@ -128,7 +128,7 @@ namespace Kyoo.TheTvdb Name = actor.Name, Images = new Dictionary { - [Thumbnails.Poster] = actor.Image != null + [Images.Poster] = actor.Image != null ? $"https://www.thetvdb.com/banners/{actor.Image}" : null }, @@ -164,7 +164,7 @@ namespace Kyoo.TheTvdb Overview = episode.Overview, Images = new Dictionary { - [Thumbnails.Thumbnail] = episode.Filename != null + [Images.Thumbnail] = episode.Filename != null ? $"https://www.thetvdb.com/banners/{episode.Filename}" : null }, diff --git a/Kyoo.TheTvdb/ProviderTvdb.cs b/Kyoo.TheTvdb/ProviderTvdb.cs index 66e330f9..e947ce25 100644 --- a/Kyoo.TheTvdb/ProviderTvdb.cs +++ b/Kyoo.TheTvdb/ProviderTvdb.cs @@ -34,7 +34,7 @@ namespace Kyoo.TheTvdb Name = "TheTVDB", Images = new Dictionary { - [Thumbnails.Logo] = "https://www.thetvdb.com/images/logo.png" + [Images.Logo] = "https://www.thetvdb.com/images/logo.png" } }; diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index a8e552af..06f57fc1 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -43,7 +43,7 @@ namespace Kyoo.Controllers public override async Task> Search(string query) { return await _database.Libraries - .Where(_database.Like(x => x.Name, $"%{query}%")) + .Where(_database.Like(x => x.Name + " " + x.Slug, $"%{query}%")) .OrderBy(DefaultSort) .Take(20) .ToListAsync(); @@ -54,8 +54,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - if (obj.Providers != null) - _database.AttachRange(obj.Providers); await _database.SaveChangesAsync($"Trying to insert a duplicated library (slug {obj.Slug} already exists)."); return obj; } @@ -64,27 +62,30 @@ namespace Kyoo.Controllers protected override async Task Validate(Library resource) { await base.Validate(resource); - if (resource.Providers != null) - { - resource.Providers = await resource.Providers - .SelectAsync(x => _providers.CreateIfNotExists(x)) - .ToListAsync(); - } - } - - /// - protected override async Task EditRelations(Library resource, Library changed, bool resetOld) - { + 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 + .SelectAsync(x => _providers.CreateIfNotExists(x)) + .ToListAsync(); + _database.AttachRange(resource.Providers); + } + } + + /// + protected override async Task EditRelations(Library resource, Library changed, bool resetOld) + { + await Validate(changed); if (changed.Providers != null || resetOld) { - await Validate(changed); await Database.Entry(resource).Collection(x => x.Providers).LoadAsync(); resource.Providers = changed.Providers; } diff --git a/Kyoo/Controllers/Repositories/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index 5edf451b..f555f5a5 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -68,7 +68,6 @@ namespace Kyoo.Controllers Pagination limit = default) where T : class, IMetadata { - string discriminator = typeof(T).Name; return ApplyFilters(_database.MetadataIds() .Include(y => y.Provider), x => _database.MetadataIds().FirstOrDefaultAsync(y => y.ResourceID == x), diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index e052cfe5..3f6ffe3a 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -113,9 +113,9 @@ namespace Kyoo.Controllers // TODO handle extensions string imageName = imageID switch { - Thumbnails.Poster => "poster.jpg", - Thumbnails.Logo => "logo.jpg", - Thumbnails.Thumbnail => "thumbnail.jpg", + Images.Poster => "poster.jpg", + Images.Logo => "logo.jpg", + Images.Thumbnail => "thumbnail.jpg", _ => $"{imageID}.jpg" }; diff --git a/Kyoo/Views/EpisodeApi.cs b/Kyoo/Views/EpisodeApi.cs index beaf0273..e98f1f6a 100644 --- a/Kyoo/Views/EpisodeApi.cs +++ b/Kyoo/Views/EpisodeApi.cs @@ -195,7 +195,7 @@ namespace Kyoo.Api try { Episode episode = await _libraryManager.Get(id); - return _files.FileResult(await _thumbnails.GetImagePath(episode, Thumbnails.Thumbnail)); + return _files.FileResult(await _thumbnails.GetImagePath(episode, Images.Thumbnail)); } catch (ItemNotFoundException) { @@ -210,7 +210,7 @@ namespace Kyoo.Api try { Episode episode = await _libraryManager.Get(slug); - return _files.FileResult(await _thumbnails.GetImagePath(episode, Thumbnails.Thumbnail)); + return _files.FileResult(await _thumbnails.GetImagePath(episode, Images.Thumbnail)); } catch (ItemNotFoundException) { diff --git a/Kyoo/Views/PeopleApi.cs b/Kyoo/Views/PeopleApi.cs index 194504ff..bd25491e 100644 --- a/Kyoo/Views/PeopleApi.cs +++ b/Kyoo/Views/PeopleApi.cs @@ -94,7 +94,7 @@ namespace Kyoo.Api People people = await _libraryManager.GetOrDefault(id); if (people == null) return NotFound(); - return _files.FileResult(await _thumbs.GetImagePath(people, Thumbnails.Poster)); + return _files.FileResult(await _thumbs.GetImagePath(people, Images.Poster)); } [HttpGet("{slug}/poster")] @@ -103,7 +103,7 @@ namespace Kyoo.Api People people = await _libraryManager.GetOrDefault(slug); if (people == null) return NotFound(); - return _files.FileResult(await _thumbs.GetImagePath(people, Thumbnails.Poster)); + return _files.FileResult(await _thumbs.GetImagePath(people, Images.Poster)); } } } \ No newline at end of file diff --git a/Kyoo/Views/ProviderApi.cs b/Kyoo/Views/ProviderApi.cs index 06c06eb8..5651f4f7 100644 --- a/Kyoo/Views/ProviderApi.cs +++ b/Kyoo/Views/ProviderApi.cs @@ -36,7 +36,7 @@ namespace Kyoo.Api Provider provider = await _libraryManager.GetOrDefault(id); if (provider == null) return NotFound(); - return _files.FileResult(await _thumbnails.GetImagePath(provider, Thumbnails.Logo)); + return _files.FileResult(await _thumbnails.GetImagePath(provider, Images.Logo)); } [HttpGet("{slug}/logo")] @@ -45,7 +45,7 @@ namespace Kyoo.Api Provider provider = await _libraryManager.GetOrDefault(slug); if (provider == null) return NotFound(); - return _files.FileResult(await _thumbnails.GetImagePath(provider, Thumbnails.Logo)); + return _files.FileResult(await _thumbnails.GetImagePath(provider, Images.Logo)); } } } \ No newline at end of file diff --git a/Kyoo/Views/SeasonApi.cs b/Kyoo/Views/SeasonApi.cs index 0241de55..fdf98959 100644 --- a/Kyoo/Views/SeasonApi.cs +++ b/Kyoo/Views/SeasonApi.cs @@ -151,7 +151,7 @@ namespace Kyoo.Api if (season == null) return NotFound(); await _libraryManager.Load(season, x => x.Show); - return _files.FileResult(await _thumbs.GetImagePath(season, Thumbnails.Poster)); + return _files.FileResult(await _thumbs.GetImagePath(season, Images.Poster)); } [HttpGet("{slug}/poster")] @@ -161,7 +161,7 @@ namespace Kyoo.Api if (season == null) return NotFound(); await _libraryManager.Load(season, x => x.Show); - return _files.FileResult(await _thumbs.GetImagePath(season, Thumbnails.Poster)); + return _files.FileResult(await _thumbs.GetImagePath(season, Images.Poster)); } } } \ No newline at end of file diff --git a/Kyoo/Views/ShowApi.cs b/Kyoo/Views/ShowApi.cs index d7162792..df12c876 100644 --- a/Kyoo/Views/ShowApi.cs +++ b/Kyoo/Views/ShowApi.cs @@ -417,7 +417,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(slug); - return _files.FileResult(await _thumbs.GetImagePath(show, Thumbnails.Poster)); + return _files.FileResult(await _thumbs.GetImagePath(show, Images.Poster)); } catch (ItemNotFoundException) { @@ -431,7 +431,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(slug); - return _files.FileResult(await _thumbs.GetImagePath(show, Thumbnails.Logo)); + return _files.FileResult(await _thumbs.GetImagePath(show, Images.Logo)); } catch (ItemNotFoundException) { @@ -446,7 +446,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(slug); - return _files.FileResult(await _thumbs.GetImagePath(show, Thumbnails.Thumbnail)); + return _files.FileResult(await _thumbs.GetImagePath(show, Images.Thumbnail)); } catch (ItemNotFoundException) { From 0fa73b1d6aa0d942f7bb70ce958ee4c37f3792f7 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 31 Jul 2021 17:22:55 +0200 Subject: [PATCH 14/34] CollectionRepository: Adding tests --- Kyoo.Common/Utility/Merger.cs | 108 ++++++++++-- Kyoo.CommonAPI/LocalRepository.cs | 19 ++- .../SpecificTests/CollectionsTests.cs | 161 ++++++++++++++++++ .../Database/SpecificTests/LibraryItemTest.cs | 5 +- .../Database/SpecificTests/LibraryTests.cs | 2 +- Kyoo.Tests/Database/TestSample.cs | 14 ++ Kyoo.Tests/Utility/MergerTests.cs | 157 +++++++++++++++++ .../Repositories/CollectionRepository.cs | 29 ++-- 8 files changed, 462 insertions(+), 33 deletions(-) diff --git a/Kyoo.Common/Utility/Merger.cs b/Kyoo.Common/Utility/Merger.cs index cd860ea3..1918ba41 100644 --- a/Kyoo.Common/Utility/Merger.cs +++ b/Kyoo.Common/Utility/Merger.cs @@ -22,12 +22,13 @@ namespace Kyoo /// The second enumerable to merge, if items from this list are equals to one from the first, they are not kept /// Equality function to compare items. If this is null, duplicated elements are kept /// The two list merged as an array - public static T[] MergeLists(IEnumerable first, - IEnumerable second, - Func isEqual = null) + [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] + public static T[] MergeLists([CanBeNull] IEnumerable first, + [CanBeNull] IEnumerable second, + [CanBeNull] Func isEqual = null) { if (first == null) - return second.ToArray(); + return second?.ToArray(); if (second == null) return first.ToArray(); if (isEqual == null) @@ -36,6 +37,31 @@ namespace Kyoo return list.Concat(second.Where(x => !list.Any(y => isEqual(x, y)))).ToArray(); } + /// + /// Merge two dictionary, if the same key is found on both dictionary, the values of the first one is kept. + /// + /// The first dictionary to merge + /// The second dictionary to merge + /// The type of the keys in dictionaries + /// The type of values in the dictionaries + /// A dictionary containing the result of the merge. + [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] + public static IDictionary MergeDictionaries([CanBeNull] IDictionary first, + [CanBeNull] IDictionary second) + { + if (first == null) + return second; + if (second == null) + return first; + Dictionary merged = new(); + merged.EnsureCapacity(first.Count + second.Count); + foreach ((T key, T2 value) in first) + merged.Add(key, value); + foreach ((T key, T2 value) in second) + merged.TryAdd(key, value); + return merged; + } + /// /// Set every fields of first to those of second. Ignore fields marked with the attribute /// At the end, the OnMerge method of first will be called if first is a @@ -63,16 +89,32 @@ namespace Kyoo } /// - /// Set every default values of first to the value of second. ex: {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "test"}. + /// 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 + /// (see for more details). /// At the end, the OnMerge method of first will be called if first is a /// - /// The object to complete - /// Missing fields of first will be completed by fields of this item. If second is null, the function no-op. - /// Filter fields that will be merged + /// + /// This does the opposite of . + /// + /// + /// {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "foo"} + /// + /// + /// The object to complete + /// + /// + /// Missing fields of first will be completed by fields of this item. If second is null, the function no-op. + /// + /// + /// Filter fields that will be merged + /// /// Fields of T will be completed /// /// If first is null - public static T Complete([NotNull] T first, [CanBeNull] T second, Func where = null) + public static T Complete([NotNull] T first, + [CanBeNull] T second, + [InstantHandle] Func where = null) { if (first == null) throw new ArgumentNullException(nameof(first)); @@ -93,7 +135,19 @@ namespace Kyoo object defaultValue = property.GetCustomAttribute()?.Value ?? property.PropertyType.GetClrDefault(); - if (value?.Equals(defaultValue) == false && value != property.GetValue(first)) + if (value?.Equals(defaultValue) != false || value == property.GetValue(first)) + continue; + if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>))) + { + Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>)) + .GenericTypeArguments; + property.SetValue(first, Utility.RunGenericMethod( + typeof(Merger), + nameof(MergeDictionaries), + dictionaryTypes, + value, property.GetValue(first))); + } + else property.SetValue(first, value); } @@ -103,17 +157,28 @@ namespace Kyoo } /// - /// An advanced function. /// This will set missing values of to the corresponding values of . - /// Enumerable will be merged (concatenated). + /// Enumerable will be merged (concatenated) and Dictionaries too. /// At the end, the OnMerge method of first will be called if first is a . /// - /// The object to complete - /// Missing fields of first will be completed by fields of this item. If second is null, the function no-op. + /// + /// {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "test"} + /// + /// + /// The object to complete + /// + /// + /// Missing fields of first will be completed by fields of this item. If second is null, the function no-op. + /// + /// + /// Filter fields that will be merged + /// /// Fields of T will be merged /// [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static T Merge([CanBeNull] T first, [CanBeNull] T second) + public static T Merge([CanBeNull] T first, + [CanBeNull] T second, + [InstantHandle] Func where = null) { if (first == null) return second; @@ -125,6 +190,9 @@ namespace Kyoo .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); @@ -133,6 +201,16 @@ namespace Kyoo if (oldValue?.Equals(defaultValue) != false) property.SetValue(first, newValue); + else if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>))) + { + Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>)) + .GenericTypeArguments; + property.SetValue(first, Utility.RunGenericMethod( + typeof(Merger), + nameof(MergeDictionaries), + dictionaryTypes, + oldValue, newValue)); + } else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType) && property.PropertyType != typeof(string)) { diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 0187e0dd..67a75e12 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -234,16 +234,23 @@ namespace Kyoo.Controllers finally { Database.ChangeTracker.LazyLoadingEnabled = lazyLoading; + Database.ChangeTracker.Clear(); } } /// /// An overridable method to edit relation of a resource. /// - /// The non edited resource - /// The new version of . This item will be saved on the databse and replace - /// A boolean to indicate if all values of resource should be discarded or not. - /// + /// + /// The non edited resource + /// + /// + /// The new version of . + /// This item will be saved on the database and replace + /// + /// + /// A boolean to indicate if all values of resource should be discarded or not. + /// protected virtual Task EditRelations(T resource, T changed, bool resetOld) { return Validate(resource); @@ -254,7 +261,9 @@ namespace Kyoo.Controllers /// It is also called on the default implementation of /// /// The resource that will be saved - /// You can throw this if the resource is illegal and should not be saved. + /// + /// You can throw this if the resource is illegal and should not be saved. + /// protected virtual Task Validate(T resource) { if (typeof(T).GetProperty(nameof(resource.Slug))!.GetCustomAttribute() != null) diff --git a/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs b/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs index 73691bf7..3fb9e72f 100644 --- a/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs +++ b/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs @@ -1,5 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models; +using Microsoft.EntityFrameworkCore; using Xunit; using Xunit.Abstractions; @@ -33,5 +38,161 @@ namespace Kyoo.Tests.Database { _repository = Repositories.LibraryManager.CollectionRepository; } + + [Fact] + public async Task CreateWithEmptySlugTest() + { + Collection collection = TestSample.GetNew(); + collection.Slug = ""; + await Assert.ThrowsAsync(() => _repository.Create(collection)); + } + + [Fact] + public async Task CreateWithNumberSlugTest() + { + Collection collection = TestSample.GetNew(); + collection.Slug = "2"; + Collection ret = await _repository.Create(collection); + Assert.Equal("2!", ret.Slug); + } + + [Fact] + public async Task CreateWithoutNameTest() + { + Collection collection = TestSample.GetNew(); + collection.Name = null; + await Assert.ThrowsAsync(() => _repository.Create(collection)); + } + + [Fact] + public async Task CreateWithExternalIdTest() + { + Collection collection = TestSample.GetNew(); + collection.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + new MetadataID + { + Provider = TestSample.GetNew(), + Link = "new-provider-link", + DataID = "new-id" + } + }; + 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() + { + Collection value = await _repository.Get(TestSample.Get().Slug); + value.Name = "New Title"; + value.Images = new Dictionary + { + [Images.Poster] = "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() + { + Collection value = await _repository.Get(TestSample.Get().Slug); + value.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + }; + 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); + } + + [Fact] + public async Task AddMetadataTest() + { + Collection value = await _repository.Get(TestSample.Get().Slug); + value.ExternalIDs = new List + { + new() + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + }; + 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); + } + + value.ExternalIDs.Add(new MetadataID + { + Provider = TestSample.GetNew(), + Link = "link", + DataID = "id" + }); + 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); + } + } + + [Theory] + [InlineData("test")] + [InlineData("super")] + [InlineData("title")] + [InlineData("TiTlE")] + [InlineData("SuPeR")] + public async Task SearchTest(string query) + { + Collection value = new() + { + Slug = "super-test", + Name = "This is a test title", + }; + await _repository.Create(value); + ICollection ret = await _repository.Search(query); + KAssert.DeepEqual(value, ret.First()); + } } } \ No newline at end of file diff --git a/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs b/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs index e6a913d2..f3e031b2 100644 --- a/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs +++ b/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs @@ -79,9 +79,10 @@ namespace Kyoo.Tests.Database [Fact] public async Task GetDuplicatedSlugTests() { - await _repositories.LibraryManager.Create(new Collection() + await _repositories.LibraryManager.Create(new Collection { - Slug = TestSample.Get().Slug + Slug = TestSample.Get().Slug, + Name = "name" }); await Assert.ThrowsAsync(() => _repository.Get(TestSample.Get().Slug)); } diff --git a/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs b/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs index 12449b5e..79277f61 100644 --- a/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs +++ b/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs @@ -61,7 +61,7 @@ namespace Kyoo.Tests.Database Library library = TestSample.GetNew(); library.Slug = "2"; Library ret = await _repository.Create(library); - Assert.Equal("2!", library.Slug); + Assert.Equal("2!", ret.Slug); } [Fact] diff --git a/Kyoo.Tests/Database/TestSample.cs b/Kyoo.Tests/Database/TestSample.cs index d8b06333..ff79ccf9 100644 --- a/Kyoo.Tests/Database/TestSample.cs +++ b/Kyoo.Tests/Database/TestSample.cs @@ -18,6 +18,20 @@ namespace Kyoo.Tests Paths = new [] {"/a/random/path"} } }, + { + typeof(Collection), + () => new Collection + { + ID = 2, + Slug = "new-collection", + Name = "New Collection", + Overview = "A collection created by new sample", + Images = new Dictionary + { + [Images.Thumbnail] = "thumbnail" + } + } + }, { typeof(Show), () => new Show diff --git a/Kyoo.Tests/Utility/MergerTests.cs b/Kyoo.Tests/Utility/MergerTests.cs index 285532c2..4e0ed6e7 100644 --- a/Kyoo.Tests/Utility/MergerTests.cs +++ b/Kyoo.Tests/Utility/MergerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Kyoo.Models; @@ -135,6 +136,68 @@ namespace Kyoo.Tests.Utility Assert.Equal(3, ret.Numbers[3]); } + private class MergeDictionaryTest + { + public int ID { get; set; } + + public Dictionary Dictionary { get; set; } + } + + [Fact] + public void GlobalMergeDictionariesTest() + { + MergeDictionaryTest test = new() + { + ID = 5, + Dictionary = new Dictionary + { + [2] = "two" + } + }; + MergeDictionaryTest test2 = new() + { + Dictionary = new Dictionary + { + [3] = "third" + } + }; + MergeDictionaryTest ret = Merger.Merge(test, test2); + Assert.True(ReferenceEquals(test, ret)); + Assert.Equal(5, ret.ID); + + Assert.Equal(2, ret.Dictionary.Count); + Assert.Equal("two", ret.Dictionary[2]); + Assert.Equal("third", ret.Dictionary[3]); + } + + [Fact] + public void GlobalMergeDictionariesDuplicatesTest() + { + MergeDictionaryTest test = new() + { + ID = 5, + Dictionary = new Dictionary + { + [2] = "two" + } + }; + MergeDictionaryTest test2 = new() + { + Dictionary = new Dictionary + { + [2] = "nope", + [3] = "third" + } + }; + MergeDictionaryTest ret = Merger.Merge(test, test2); + Assert.True(ReferenceEquals(test, ret)); + Assert.Equal(5, ret.ID); + + Assert.Equal(2, ret.Dictionary.Count); + Assert.Equal("two", ret.Dictionary[2]); + Assert.Equal("third", ret.Dictionary[3]); + } + [Fact] public void GlobalMergeListDuplicatesResourcesTest() { @@ -208,5 +271,99 @@ namespace Kyoo.Tests.Utility Assert.Equal(1, ret[0]); Assert.Equal(2, ret[1]); } + + [Fact] + public void MergeDictionariesTest() + { + Dictionary first = new() + { + [1] = "test", + [5] = "value" + }; + Dictionary second = new() + { + [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() + { + Dictionary first = new() + { + [1] = "test", + [5] = "value" + }; + Dictionary second = new() + { + [3] = "third", + [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() + { + Genre genre = new() + { + ID = 5, + Name = "merged" + }; + Genre genre2 = new() + { + Name = "test" + }; + Genre ret = Merger.Complete(genre, genre2); + Assert.True(ReferenceEquals(genre, ret)); + Assert.Equal(5, ret.ID); + Assert.Equal("test", genre.Name); + Assert.Null(genre.Slug); + } + + [Fact] + public void CompleteDictionaryTest() + { + Collection collection = new() + { + ID = 5, + Name = "merged", + Images = new Dictionary + { + [Images.Logo] = "logo", + [Images.Poster] = "poster" + } + + }; + Collection collection2 = new() + { + Name = "test", + Images = new Dictionary + { + [Images.Poster] = "new-poster", + [Images.Thumbnail] = "thumbnails" + } + }; + Collection ret = Merger.Complete(collection, collection2); + Assert.True(ReferenceEquals(collection, ret)); + Assert.Equal(5, ret.ID); + Assert.Equal("test", ret.Name); + Assert.Null(ret.Slug); + Assert.Equal(3, ret.Images.Count); + Assert.Equal("new-poster", ret.Images[Images.Poster]); + Assert.Equal("thumbnails", ret.Images[Images.Thumbnail]); + Assert.Equal("logo", ret.Images[Images.Logo]); + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index 42483b08..016028cb 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -42,7 +42,7 @@ namespace Kyoo.Controllers public override async Task> Search(string query) { return await _database.Collections - .Where(_database.Like(x => x.Name, $"%{query}%")) + .Where(_database.Like(x => x.Name + " " + x.Slug, $"%{query}%")) .OrderBy(DefaultSort) .Take(20) .ToListAsync(); @@ -53,7 +53,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.ExternalIDs.ForEach(x => _database.MetadataIds().Attach(x)); await _database.SaveChangesAsync($"Trying to insert a duplicated collection (slug {obj.Slug} already exists)."); return obj; } @@ -62,24 +61,34 @@ namespace Kyoo.Controllers protected override async Task Validate(Collection resource) { await base.Validate(resource); - await resource.ExternalIDs.ForEachAsync(async x => - { - x.Provider = await _providers.CreateIfNotExists(x.Provider); - x.ProviderID = x.Provider.ID; - _database.Entry(x.Provider).State = EntityState.Detached; - }); + + if (string.IsNullOrEmpty(resource.Slug)) + throw new ArgumentException("The collection's slug must be set and not empty"); + if (string.IsNullOrEmpty(resource.Name)) + throw new ArgumentException("The collection's name must be set and not empty"); + + if (resource.ExternalIDs != null) + { + foreach (MetadataID id in resource.ExternalIDs) + { + id.Provider = await _providers.CreateIfNotExists(id.Provider); + id.ProviderID = id.Provider.ID; + _database.Entry(id.Provider).State = EntityState.Detached; + } + _database.MetadataIds().AttachRange(resource.ExternalIDs); + } } /// protected override async Task EditRelations(Collection resource, Collection changed, bool resetOld) { + await Validate(resource); + if (changed.ExternalIDs != null || resetOld) { await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); resource.ExternalIDs = changed.ExternalIDs; } - - await base.EditRelations(resource, changed, resetOld); } /// From 181eb5ba2ed3549a343983975a954c0b77d7ca8c Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 31 Jul 2021 22:50:45 +0200 Subject: [PATCH 15/34] Merger: Fixing Dictionary complete and merge --- Kyoo.Common/Utility/Merger.cs | 111 +++++++++++++++--- Kyoo.Common/Utility/Utility.cs | 2 +- .../SpecificTests/CollectionsTests.cs | 2 +- Kyoo.Tests/Utility/MergerTests.cs | 90 ++++++++++++++ .../Repositories/CollectionRepository.cs | 4 +- 5 files changed, 191 insertions(+), 18 deletions(-) diff --git a/Kyoo.Common/Utility/Merger.cs b/Kyoo.Common/Utility/Merger.cs index 1918ba41..3c2d6247 100644 --- a/Kyoo.Common/Utility/Merger.cs +++ b/Kyoo.Common/Utility/Merger.cs @@ -44,22 +44,89 @@ namespace Kyoo /// The second dictionary to merge /// The type of the keys in dictionaries /// The type of values in the dictionaries - /// A dictionary containing the result of the merge. + /// The first dictionary with the missing elements of . + /// [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] public static IDictionary MergeDictionaries([CanBeNull] IDictionary first, [CanBeNull] IDictionary second) + { + return MergeDictionaries(first, second, out bool _); + } + + /// + /// Merge two dictionary, if the same key is found on both dictionary, the values of the first one is kept. + /// + /// The first dictionary to merge + /// The second dictionary to merge + /// + /// true if a new items has been added to the dictionary, false otherwise. + /// + /// The type of the keys in dictionaries + /// The type of values in the dictionaries + /// The first dictionary with the missing elements of . + [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] + public static IDictionary MergeDictionaries([CanBeNull] IDictionary first, + [CanBeNull] IDictionary second, + out bool hasChanged) { if (first == null) + { + hasChanged = true; return second; + } + + hasChanged = false; if (second == null) return first; - Dictionary merged = new(); - merged.EnsureCapacity(first.Count + second.Count); - foreach ((T key, T2 value) in first) - merged.Add(key, value); foreach ((T key, T2 value) in second) - merged.TryAdd(key, value); - return merged; + hasChanged |= first.TryAdd(key, value); + return first; + } + + /// + /// Merge two dictionary, if the same key is found on both dictionary, the values of the second one is kept. + /// + /// + /// The only difference in this function compared to + /// + /// is the way is calculated and the order of the arguments. + /// + /// MergeDictionaries(first, second); + /// + /// will do the same thing as + /// + /// CompleteDictionaries(second, first, out bool _); + /// + /// + /// The first dictionary to merge + /// The second dictionary to merge + /// + /// true if a new items has been added to the dictionary, false otherwise. + /// + /// The type of the keys in dictionaries + /// The type of values in the dictionaries + /// + /// A dictionary with the missing elements of + /// set to those of . + /// + [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] + public static IDictionary CompleteDictionaries([CanBeNull] IDictionary first, + [CanBeNull] IDictionary second, + out bool hasChanged) + { + if (first == null) + { + hasChanged = true; + return second; + } + + hasChanged = false; + if (second == null) + return first; + hasChanged = second.Any(x => !x.Value.Equals(first[x.Key])); + foreach ((T key, T2 value) in first) + second.TryAdd(key, value); + return second; } /// @@ -91,7 +158,9 @@ namespace Kyoo /// /// 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 - /// (see for more details). + /// (see + /// + /// for more details). /// At the end, the OnMerge method of first will be called if first is a /// /// @@ -135,17 +204,24 @@ namespace Kyoo object defaultValue = property.GetCustomAttribute()?.Value ?? property.PropertyType.GetClrDefault(); - if (value?.Equals(defaultValue) != false || value == property.GetValue(first)) + if (value?.Equals(defaultValue) != false || value.Equals(property.GetValue(first))) continue; if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>))) { Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>)) .GenericTypeArguments; - property.SetValue(first, Utility.RunGenericMethod( + object[] parameters = { + property.GetValue(first), + value, + false + }; + object newDictionary = Utility.RunGenericMethod( typeof(Merger), - nameof(MergeDictionaries), + nameof(CompleteDictionaries), dictionaryTypes, - value, property.GetValue(first))); + parameters); + if ((bool)parameters[2]) + property.SetValue(first, newDictionary); } else property.SetValue(first, value); @@ -205,11 +281,18 @@ namespace Kyoo { Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>)) .GenericTypeArguments; - property.SetValue(first, Utility.RunGenericMethod( + object[] parameters = { + oldValue, + newValue, + false + }; + object newDictionary = Utility.RunGenericMethod( typeof(Merger), nameof(MergeDictionaries), dictionaryTypes, - oldValue, newValue)); + parameters); + if ((bool)parameters[2]) + property.SetValue(first, newDictionary); } else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType) && property.PropertyType != typeof(string)) diff --git a/Kyoo.Common/Utility/Utility.cs b/Kyoo.Common/Utility/Utility.cs index 2f7e14ad..72d25fd5 100644 --- a/Kyoo.Common/Utility/Utility.cs +++ b/Kyoo.Common/Utility/Utility.cs @@ -325,7 +325,7 @@ namespace Kyoo if (types.Length < 1) throw new ArgumentException($"The {nameof(types)} array is empty. At least one type is needed."); MethodInfo method = GetMethod(owner, BindingFlags.Static, methodName, types, args); - return (T)method.MakeGenericMethod(types).Invoke(null, args.ToArray()); + return (T)method.MakeGenericMethod(types).Invoke(null, args); } /// diff --git a/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs b/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs index 3fb9e72f..f1f773ad 100644 --- a/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs +++ b/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs @@ -99,7 +99,7 @@ namespace Kyoo.Tests.Database value.Name = "New Title"; value.Images = new Dictionary { - [Images.Poster] = "poster" + [Images.Poster] = "new-poster" }; await _repository.Edit(value, false); diff --git a/Kyoo.Tests/Utility/MergerTests.cs b/Kyoo.Tests/Utility/MergerTests.cs index 4e0ed6e7..6aed3eb2 100644 --- a/Kyoo.Tests/Utility/MergerTests.cs +++ b/Kyoo.Tests/Utility/MergerTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using JetBrains.Annotations; using Kyoo.Models; using Kyoo.Models.Attributes; using Xunit; @@ -365,5 +366,94 @@ namespace Kyoo.Tests.Utility Assert.Equal("thumbnails", ret.Images[Images.Thumbnail]); Assert.Equal("logo", ret.Images[Images.Logo]); } + + [Fact] + public void CompleteDictionaryOutParam() + { + Dictionary first = new() + { + [Images.Logo] = "logo", + [Images.Poster] = "poster" + }; + Dictionary second = new() + { + [Images.Poster] = "new-poster", + [Images.Thumbnail] = "thumbnails" + }; + IDictionary ret = Merger.CompleteDictionaries(first, second, out bool changed); + Assert.True(changed); + Assert.Equal(3, ret.Count); + Assert.Equal("new-poster", ret[Images.Poster]); + Assert.Equal("thumbnails", ret[Images.Thumbnail]); + Assert.Equal("logo", ret[Images.Logo]); + } + + [Fact] + public void CompleteDictionaryEqualTest() + { + Dictionary first = new() + { + [Images.Poster] = "poster" + }; + Dictionary second = new() + { + [Images.Poster] = "new-poster", + }; + IDictionary ret = Merger.CompleteDictionaries(first, second, out bool changed); + Assert.True(changed); + Assert.Equal(1, ret.Count); + Assert.Equal("new-poster", ret[Images.Poster]); + } + + private class TestMergeSetter + { + public Dictionary Backing; + + [UsedImplicitly] public Dictionary Dictionary + { + get => Backing; + set + { + Backing = value; + KAssert.Fail(); + } + } + } + + [Fact] + public void CompleteDictionaryNoChangeNoSetTest() + { + TestMergeSetter first = new() + { + Backing = new Dictionary + { + [2] = 3 + } + }; + TestMergeSetter second = new() + { + Backing = new Dictionary() + }; + Merger.Complete(first, second); + // This should no call the setter of first so the test should pass. + } + + [Fact] + public void MergeDictionaryNoChangeNoSetTest() + { + TestMergeSetter first = new() + { + Backing = new Dictionary + { + [2] = 3 + } + }; + TestMergeSetter second = new() + { + Backing = new Dictionary() + }; + Merger.Merge(first, second); + // This should no call the setter of first so the test should pass. + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index 016028cb..13273ab3 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -73,7 +73,7 @@ namespace Kyoo.Controllers { id.Provider = await _providers.CreateIfNotExists(id.Provider); id.ProviderID = id.Provider.ID; - _database.Entry(id.Provider).State = EntityState.Detached; + _database.Entry(id.Provider).State = EntityState.Unchanged; } _database.MetadataIds().AttachRange(resource.ExternalIDs); } @@ -82,7 +82,7 @@ namespace Kyoo.Controllers /// protected override async Task EditRelations(Collection resource, Collection changed, bool resetOld) { - await Validate(resource); + await Validate(changed); if (changed.ExternalIDs != null || resetOld) { From c0b83d2d58bd2a11a69365eae29c1198c2407529 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 31 Jul 2021 23:16:36 +0200 Subject: [PATCH 16/34] ShowRepository: Fixing relation adding --- .../Database/SpecificTests/ShowTests.cs | 20 +++++++++++ .../Repositories/ShowRepository.cs | 34 +++++++++++-------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/Kyoo.Tests/Database/SpecificTests/ShowTests.cs b/Kyoo.Tests/Database/SpecificTests/ShowTests.cs index 424ca76c..d004f954 100644 --- a/Kyoo.Tests/Database/SpecificTests/ShowTests.cs +++ b/Kyoo.Tests/Database/SpecificTests/ShowTests.cs @@ -74,6 +74,26 @@ namespace Kyoo.Tests.Database 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() + { + Show value = await _repository.Get(TestSample.Get().Slug); + 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})); + + 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})); + } + [Fact] public async Task EditStudioTest() { diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 1c7471f7..458690f7 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -76,10 +76,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - if (obj.Genres != null) - _database.AttachRange(obj.Genres); - obj.People.ForEach(x => _database.Entry(x).State = EntityState.Added); - obj.ExternalIDs.ForEach(x => _database.MetadataIds().Attach(x)); await _database.SaveChangesAsync($"Trying to insert a duplicated show (slug {obj.Slug} already exists)."); return obj; } @@ -99,20 +95,30 @@ namespace Kyoo.Controllers resource.Genres = await resource.Genres .SelectAsync(x => _genres.CreateIfNotExists(x)) .ToListAsync(); + _database.AttachRange(resource.Genres); } - await resource.ExternalIDs.ForEachAsync(async id => + if (resource.ExternalIDs != null) { - id.Provider = await _providers.CreateIfNotExists(id.Provider); - id.ProviderID = id.Provider.ID; - _database.Entry(id.Provider).State = EntityState.Detached; - }); - await resource.People.ForEachAsync(async role => + foreach (MetadataID id in resource.ExternalIDs) + { + id.Provider = await _providers.CreateIfNotExists(id.Provider); + id.ProviderID = id.Provider.ID; + _database.Entry(id.Provider).State = EntityState.Detached; + } + _database.MetadataIds().AttachRange(resource.ExternalIDs); + } + + if (resource.People != null) { - role.People = await _people.CreateIfNotExists(role.People); - role.PeopleID = role.People.ID; - _database.Entry(role.People).State = EntityState.Detached; - }); + foreach (PeopleRole role in resource.People) + { + role.People = await _people.CreateIfNotExists(role.People); + role.PeopleID = role.People.ID; + _database.Entry(role.People).State = EntityState.Detached; + _database.Entry(role).State = EntityState.Added; + } + } } /// From 1472ff1cfa2f8b72ecb5891928f0a2ffb4d810fa Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 31 Jul 2021 23:39:58 +0200 Subject: [PATCH 17/34] SeasonsRepository: Adding creation/edits tests --- .../Database/SpecificTests/SeasonTests.cs | 133 ++++++++++++++++++ Kyoo.Tests/Database/TestSample.cs | 18 +++ 2 files changed, 151 insertions(+) diff --git a/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs b/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs index b1692747..4ab36f56 100644 --- a/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs +++ b/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs @@ -1,6 +1,9 @@ +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models; +using Microsoft.EntityFrameworkCore; using Xunit; using Xunit.Abstractions; @@ -75,5 +78,135 @@ namespace Kyoo.Tests.Database }); Assert.Equal($"{TestSample.Get().Slug}-s2", season.Slug); } + + [Fact] + public async Task CreateWithExternalIdTest() + { + Season season = TestSample.GetNew(); + season.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + new MetadataID + { + Provider = TestSample.GetNew(), + Link = "new-provider-link", + DataID = "new-id" + } + }; + 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() + { + Season value = await _repository.Get(TestSample.Get().Slug); + value.Title = "New Title"; + value.Images = new Dictionary + { + [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() + { + Season value = await _repository.Get(TestSample.Get().Slug); + value.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + }; + 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); + } + + [Fact] + public async Task AddMetadataTest() + { + Season value = await _repository.Get(TestSample.Get().Slug); + value.ExternalIDs = new List + { + new() + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + }; + 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); + } + + value.ExternalIDs.Add(new MetadataID + { + Provider = TestSample.GetNew(), + Link = "link", + DataID = "id" + }); + 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); + } + } + + [Theory] + [InlineData("test")] + [InlineData("super")] + [InlineData("title")] + [InlineData("TiTlE")] + [InlineData("SuPeR")] + public async Task SearchTest(string query) + { + Season value = new() + { + Title = "This is a test super title", + }; + await _repository.Create(value); + ICollection ret = await _repository.Search(query); + KAssert.DeepEqual(value, ret.First()); + } } } \ No newline at end of file diff --git a/Kyoo.Tests/Database/TestSample.cs b/Kyoo.Tests/Database/TestSample.cs index ff79ccf9..91ebdd30 100644 --- a/Kyoo.Tests/Database/TestSample.cs +++ b/Kyoo.Tests/Database/TestSample.cs @@ -53,6 +53,24 @@ namespace Kyoo.Tests Studio = null } }, + { + typeof(Season), + () => new Season + { + ID = 2, + ShowID = 1, + ShowSlug = Get().Slug, + Title = "New season", + Overview = "New overview", + EndDate = new DateTime(2000, 10, 10), + SeasonNumber = 2, + StartDate = new DateTime(2010, 10, 10), + Images = new Dictionary + { + [Images.Logo] = "logo" + } + } + }, { typeof(Provider), () => new Provider From 5152368950c19fb93fbce25698b56b4d9353eb18 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 1 Aug 2021 14:44:19 +0200 Subject: [PATCH 18/34] EpisodeRepository: Adding tests --- .../Database/SpecificTests/EpisodeTests.cs | 139 +++++++++++++++++- .../Database/SpecificTests/SeasonTests.cs | 1 + Kyoo.Tests/Database/TestSample.cs | 21 +++ .../Repositories/EpisodeRepository.cs | 26 ++-- .../Repositories/SeasonRepository.cs | 22 +-- 5 files changed, 185 insertions(+), 24 deletions(-) diff --git a/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs b/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs index d9e0e9ff..77a2c395 100644 --- a/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs +++ b/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs @@ -1,6 +1,9 @@ +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models; +using Microsoft.EntityFrameworkCore; using Xunit; using Xunit.Abstractions; @@ -92,10 +95,6 @@ namespace Kyoo.Tests.Database }); Assert.Equal($"{TestSample.Get().Slug}-s2e4", episode.Slug); } - - - // TODO absolute numbering tests - [Fact] public void AbsoluteSlugTest() @@ -188,5 +187,137 @@ namespace Kyoo.Tests.Database Episode episode = await _repository.Get(3); Assert.Equal("john-wick", episode.Slug); } + + [Fact] + public async Task CreateWithExternalIdTest() + { + Episode value = TestSample.GetNew(); + value.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + new MetadataID + { + Provider = TestSample.GetNew(), + Link = "new-provider-link", + DataID = "new-id" + } + }; + 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() + { + Episode value = await _repository.Get(TestSample.Get().Slug); + value.Title = "New Title"; + value.Images = new Dictionary + { + [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() + { + Episode value = await _repository.Get(TestSample.Get().Slug); + value.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + }; + 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); + } + + [Fact] + public async Task AddMetadataTest() + { + Episode value = await _repository.Get(TestSample.Get().Slug); + value.ExternalIDs = new List + { + new() + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + }; + 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); + } + + value.ExternalIDs.Add(new MetadataID + { + Provider = TestSample.GetNew(), + Link = "link", + DataID = "id" + }); + 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); + } + } + + [Theory] + [InlineData("test")] + [InlineData("super")] + [InlineData("title")] + [InlineData("TiTlE")] + [InlineData("SuPeR")] + public async Task SearchTest(string query) + { + Episode value = new() + { + Title = "This is a test super title", + ShowID = 1, + AbsoluteNumber = 2 + }; + await _repository.Create(value); + ICollection ret = await _repository.Search(query); + KAssert.DeepEqual(value, ret.First()); + } } } \ No newline at end of file diff --git a/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs b/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs index 4ab36f56..03f7a15f 100644 --- a/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs +++ b/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs @@ -203,6 +203,7 @@ namespace Kyoo.Tests.Database Season value = new() { Title = "This is a test super title", + ShowID = 1 }; await _repository.Create(value); ICollection ret = await _repository.Search(query); diff --git a/Kyoo.Tests/Database/TestSample.cs b/Kyoo.Tests/Database/TestSample.cs index 91ebdd30..423a5c78 100644 --- a/Kyoo.Tests/Database/TestSample.cs +++ b/Kyoo.Tests/Database/TestSample.cs @@ -71,6 +71,27 @@ namespace Kyoo.Tests } } }, + { + typeof(Episode), + () => new Episode + { + ID = 2, + ShowID = 1, + ShowSlug = Get().Slug, + SeasonID = 1, + SeasonNumber = Get().SeasonNumber, + EpisodeNumber = 3, + AbsoluteNumber = 4, + Path = "/episode-path", + Title = "New Episode Title", + ReleaseDate = new DateTime(2000, 10, 10), + Overview = "new episode overview", + Images = new Dictionary + { + [Images.Logo] = "new episode logo" + } + } + }, { typeof(Provider), () => new Provider diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index ac243a68..2d17d418 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -99,7 +99,7 @@ namespace Kyoo.Controllers public override async Task> Search(string query) { return await _database.Episodes - .Where(x => x.EpisodeNumber != null) + .Where(x => x.EpisodeNumber != null || x.AbsoluteNumber != null) .Where(_database.Like(x => x.Title, $"%{query}%")) .OrderBy(DefaultSort) .Take(20) @@ -111,7 +111,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.ExternalIDs.ForEach(x => _database.MetadataIds().Attach(x)); await _database.SaveChangesAsync($"Trying to insert a duplicated episode (slug {obj.Slug} already exists)."); return await ValidateTracks(obj); } @@ -119,9 +118,6 @@ namespace Kyoo.Controllers /// protected override async Task EditRelations(Episode resource, Episode changed, bool resetOld) { - if (resource.ShowID <= 0) - throw new InvalidOperationException($"Can't store an episode not related to any show (showID: {resource.ShowID})."); - if (changed.Tracks != null || resetOld) { await _tracks.DeleteAll(x => x.EpisodeID == resource.ID); @@ -158,12 +154,20 @@ namespace Kyoo.Controllers protected override async Task Validate(Episode resource) { await base.Validate(resource); - await resource.ExternalIDs.ForEachAsync(async x => - { - x.Provider = await _providers.CreateIfNotExists(x.Provider); - x.ProviderID = x.Provider.ID; - _database.Entry(x.Provider).State = EntityState.Detached; - }); + if (resource.ShowID <= 0) + throw new ArgumentException($"Can't store an episode not related " + + $"to any show (showID: {resource.ShowID})."); + + if (resource.ExternalIDs != null) + { + foreach (MetadataID id in resource.ExternalIDs) + { + id.Provider = await _providers.CreateIfNotExists(id.Provider); + id.ProviderID = id.Provider.ID; + _database.Entry(id.Provider).State = EntityState.Unchanged; + } + _database.MetadataIds().AttachRange(resource.ExternalIDs); + } } /// diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index 12900300..88abfaea 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -87,7 +87,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.ExternalIDs.ForEach(x => _database.MetadataIds().Attach(x)); await _database.SaveChangesAsync($"Trying to insert a duplicated season (slug {obj.Slug} already exists)."); return obj; } @@ -95,32 +94,37 @@ namespace Kyoo.Controllers /// protected override async Task Validate(Season resource) { + await base.Validate(resource); if (resource.ShowID <= 0) { if (resource.Show == null) - throw new InvalidOperationException( + throw new ArgumentException( $"Can't store a season not related to any show (showID: {resource.ShowID})."); resource.ShowID = resource.Show.ID; } - await base.Validate(resource); - await resource.ExternalIDs.ForEachAsync(async id => + if (resource.ExternalIDs != null) { - id.Provider = await _providers.CreateIfNotExists(id.Provider); - id.ProviderID = id.Provider.ID; - _database.Entry(id.Provider).State = EntityState.Detached; - }); + foreach (MetadataID id in resource.ExternalIDs) + { + id.Provider = await _providers.CreateIfNotExists(id.Provider); + id.ProviderID = id.Provider.ID; + _database.Entry(id.Provider).State = EntityState.Unchanged; + } + _database.MetadataIds().AttachRange(resource.ExternalIDs); + } } /// 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; } - await base.EditRelations(resource, changed, resetOld); } /// From 63e5c81691963ee87e457bde502d24033576b061 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 1 Aug 2021 15:00:09 +0200 Subject: [PATCH 19/34] Fixing tests --- Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs | 12 ++++++++---- Kyoo.Tests/Database/SpecificTests/SeasonTests.cs | 3 ++- Kyoo/Controllers/Repositories/EpisodeRepository.cs | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs b/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs index 77a2c395..8a477100 100644 --- a/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs +++ b/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs @@ -62,7 +62,8 @@ namespace Kyoo.Tests.Database episode = await _repository.Edit(new Episode { ID = 1, - SeasonNumber = 2 + SeasonNumber = 2, + ShowID = 1 }, false); Assert.Equal($"{TestSample.Get().Slug}-s2e1", episode.Slug); episode = await _repository.Get(1); @@ -77,7 +78,8 @@ namespace Kyoo.Tests.Database episode = await _repository.Edit(new Episode { ID = 1, - EpisodeNumber = 2 + EpisodeNumber = 2, + ShowID = 1 }, false); Assert.Equal($"{TestSample.Get().Slug}-s1e2", episode.Slug); episode = await _repository.Get(1); @@ -132,7 +134,8 @@ namespace Kyoo.Tests.Database Episode episode = await _repository.Edit(new Episode { ID = 2, - AbsoluteNumber = 56 + AbsoluteNumber = 56, + ShowID = 1 }, false); Assert.Equal($"{TestSample.Get().Slug}-56", episode.Slug); episode = await _repository.Get(2); @@ -147,7 +150,8 @@ namespace Kyoo.Tests.Database { ID = 2, SeasonNumber = 1, - EpisodeNumber = 2 + EpisodeNumber = 2, + ShowID = 1 }, false); Assert.Equal($"{TestSample.Get().Slug}-s1e2", episode.Slug); episode = await _repository.Get(2); diff --git a/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs b/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs index 03f7a15f..ae56415c 100644 --- a/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs +++ b/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs @@ -62,7 +62,8 @@ namespace Kyoo.Tests.Database await _repository.Edit(new Season { ID = 1, - SeasonNumber = 2 + SeasonNumber = 2, + ShowID = 1 }, false); season = await _repository.Get(1); Assert.Equal("anohana-s2", season.Slug); diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 2d17d418..ee5d2cdd 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -118,6 +118,8 @@ namespace Kyoo.Controllers /// protected override async Task EditRelations(Episode resource, Episode changed, bool resetOld) { + await Validate(changed); + if (changed.Tracks != null || resetOld) { await _tracks.DeleteAll(x => x.EpisodeID == resource.ID); @@ -130,8 +132,6 @@ namespace Kyoo.Controllers await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); resource.ExternalIDs = changed.ExternalIDs; } - - await Validate(resource); } /// From 7540adb8226ac94e8430277ca39532fa2327c7ab Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 1 Aug 2021 15:21:44 +0200 Subject: [PATCH 20/34] Moving unit tests to a specific folder --- Kyoo.sln | 12 +++++------ Kyoo.sln.DotSettings | 11 ---------- .../Repositories/TrackRepository.cs | 20 ++++++++++++------- .../Database/RepositoryActivator.cs | 0 .../Kyoo.Tests}/Database/RepositoryTests.cs | 0 .../SpecificTests/CollectionsTests.cs | 0 .../Database/SpecificTests/EpisodeTests.cs | 0 .../Database/SpecificTests/GenreTests.cs | 0 .../Database/SpecificTests/LibraryItemTest.cs | 0 .../Database/SpecificTests/LibraryTests.cs | 0 .../Database/SpecificTests/PeopleTests.cs | 0 .../Database/SpecificTests/ProviderTests.cs | 0 .../Database/SpecificTests/SanityTests.cs | 0 .../Database/SpecificTests/SeasonTests.cs | 0 .../Database/SpecificTests/ShowTests.cs | 0 .../Database/SpecificTests/StudioTests.cs | 0 .../Database/SpecificTests/TrackTests.cs | 0 .../Database/SpecificTests/UserTests.cs | 0 .../Kyoo.Tests}/Database/TestContext.cs | 0 .../Kyoo.Tests}/Database/TestSample.cs | 0 .../Kyoo.Tests}/Identifier/IdentifierTests.cs | 0 .../Kyoo.Tests}/Identifier/ProviderTests.cs | 0 .../Identifier/Tvdb/ConvertorTests.cs | 0 {Kyoo.Tests => tests/Kyoo.Tests}/KAssert.cs | 0 .../Kyoo.Tests}/Kyoo.Tests.csproj | 8 ++++---- .../Kyoo.Tests}/Utility/EnumerableTests.cs | 0 .../Kyoo.Tests}/Utility/MergerTests.cs | 0 .../Kyoo.Tests}/Utility/TaskTests.cs | 0 .../Kyoo.Tests}/Utility/UtilityTests.cs | 0 29 files changed, 23 insertions(+), 28 deletions(-) delete mode 100644 Kyoo.sln.DotSettings rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/RepositoryActivator.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/RepositoryTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/SpecificTests/CollectionsTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/SpecificTests/EpisodeTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/SpecificTests/GenreTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/SpecificTests/LibraryItemTest.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/SpecificTests/LibraryTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/SpecificTests/PeopleTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/SpecificTests/ProviderTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/SpecificTests/SanityTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/SpecificTests/SeasonTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/SpecificTests/ShowTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/SpecificTests/StudioTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/SpecificTests/TrackTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/SpecificTests/UserTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/TestContext.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Database/TestSample.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Identifier/IdentifierTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Identifier/ProviderTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Identifier/Tvdb/ConvertorTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/KAssert.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Kyoo.Tests.csproj (84%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Utility/EnumerableTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Utility/MergerTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Utility/TaskTests.cs (100%) rename {Kyoo.Tests => tests/Kyoo.Tests}/Utility/UtilityTests.cs (100%) diff --git a/Kyoo.sln b/Kyoo.sln index fbec126b..fdb1f643 100644 --- a/Kyoo.sln +++ b/Kyoo.sln @@ -5,8 +5,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Common", "Kyoo.Common\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.CommonAPI", "Kyoo.CommonAPI\Kyoo.CommonAPI.csproj", "{6F91B645-F785-46BB-9C4F-1EFC83E489B6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Tests", "Kyoo.Tests\Kyoo.Tests.csproj", "{D179D5FF-9F75-4B27-8E27-0DBDF1806611}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Postgresql", "Kyoo.Postgresql\Kyoo.Postgresql.csproj", "{3213C96D-0BF3-460B-A8B5-B9977229408A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Authentication", "Kyoo.Authentication\Kyoo.Authentication.csproj", "{7A841335-6523-47DB-9717-80AA7BD943FD}" @@ -17,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.TheTvdb", "Kyoo.TheTvd EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.TheMovieDb", "Kyoo.TheMovieDb\Kyoo.TheMovieDb.csproj", "{BAB270D4-E0EA-4329-BA65-512FDAB01001}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Tests", "tests\Kyoo.Tests\Kyoo.Tests.csproj", "{0C8AA7EA-E723-4532-852F-35AA4E8AFED5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,10 +35,6 @@ Global {6F91B645-F785-46BB-9C4F-1EFC83E489B6}.Debug|Any CPU.Build.0 = Debug|Any CPU {6F91B645-F785-46BB-9C4F-1EFC83E489B6}.Release|Any CPU.ActiveCfg = Release|Any CPU {6F91B645-F785-46BB-9C4F-1EFC83E489B6}.Release|Any CPU.Build.0 = Release|Any CPU - {D179D5FF-9F75-4B27-8E27-0DBDF1806611}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D179D5FF-9F75-4B27-8E27-0DBDF1806611}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D179D5FF-9F75-4B27-8E27-0DBDF1806611}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D179D5FF-9F75-4B27-8E27-0DBDF1806611}.Release|Any CPU.Build.0 = Release|Any CPU {3213C96D-0BF3-460B-A8B5-B9977229408A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3213C96D-0BF3-460B-A8B5-B9977229408A}.Debug|Any CPU.Build.0 = Debug|Any CPU {3213C96D-0BF3-460B-A8B5-B9977229408A}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -59,5 +55,9 @@ Global {BAB270D4-E0EA-4329-BA65-512FDAB01001}.Debug|Any CPU.Build.0 = Debug|Any CPU {BAB270D4-E0EA-4329-BA65-512FDAB01001}.Release|Any CPU.ActiveCfg = Release|Any CPU {BAB270D4-E0EA-4329-BA65-512FDAB01001}.Release|Any CPU.Build.0 = Release|Any CPU + {0C8AA7EA-E723-4532-852F-35AA4E8AFED5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C8AA7EA-E723-4532-852F-35AA4E8AFED5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C8AA7EA-E723-4532-852F-35AA4E8AFED5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C8AA7EA-E723-4532-852F-35AA4E8AFED5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Kyoo.sln.DotSettings b/Kyoo.sln.DotSettings deleted file mode 100644 index f0549f01..00000000 --- a/Kyoo.sln.DotSettings +++ /dev/null @@ -1,11 +0,0 @@ - - Tab - UseExplicitType - UseExplicitType - UseExplicitType - API - DB - True - True - True - True \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index 9b642a93..a462ca23 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -37,19 +37,25 @@ namespace Kyoo.Controllers throw new InvalidOperationException("Tracks do not support the search method."); } + /// + protected override async Task Validate(Track resource) + { + await base.Validate(resource); + if (resource.EpisodeID <= 0) + { + resource.EpisodeID = resource.Episode?.ID ?? 0; + if (resource.EpisodeID <= 0) + throw new ArgumentException("Can't store a track not related to any episode " + + $"(episodeID: {resource.EpisodeID})."); + } + } + /// public override async Task Create(Track obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); - if (obj.EpisodeID <= 0) - { - obj.EpisodeID = obj.Episode?.ID ?? 0; - if (obj.EpisodeID <= 0) - throw new InvalidOperationException($"Can't store a track not related to any episode (episodeID: {obj.EpisodeID})."); - } - await base.Create(obj); _database.Entry(obj).State = EntityState.Added; await _database.SaveChangesAsync(); diff --git a/Kyoo.Tests/Database/RepositoryActivator.cs b/tests/Kyoo.Tests/Database/RepositoryActivator.cs similarity index 100% rename from Kyoo.Tests/Database/RepositoryActivator.cs rename to tests/Kyoo.Tests/Database/RepositoryActivator.cs diff --git a/Kyoo.Tests/Database/RepositoryTests.cs b/tests/Kyoo.Tests/Database/RepositoryTests.cs similarity index 100% rename from Kyoo.Tests/Database/RepositoryTests.cs rename to tests/Kyoo.Tests/Database/RepositoryTests.cs diff --git a/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs diff --git a/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs diff --git a/Kyoo.Tests/Database/SpecificTests/GenreTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/GenreTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/GenreTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/GenreTests.cs diff --git a/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs b/tests/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs rename to tests/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs diff --git a/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/LibraryTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs diff --git a/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/PeopleTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs diff --git a/Kyoo.Tests/Database/SpecificTests/ProviderTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/ProviderTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/ProviderTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/ProviderTests.cs diff --git a/Kyoo.Tests/Database/SpecificTests/SanityTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/SanityTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/SanityTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/SanityTests.cs diff --git a/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/SeasonTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs diff --git a/Kyoo.Tests/Database/SpecificTests/ShowTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/ShowTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs diff --git a/Kyoo.Tests/Database/SpecificTests/StudioTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/StudioTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/StudioTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/StudioTests.cs diff --git a/Kyoo.Tests/Database/SpecificTests/TrackTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/TrackTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/TrackTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/TrackTests.cs diff --git a/Kyoo.Tests/Database/SpecificTests/UserTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/UserTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/UserTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/UserTests.cs diff --git a/Kyoo.Tests/Database/TestContext.cs b/tests/Kyoo.Tests/Database/TestContext.cs similarity index 100% rename from Kyoo.Tests/Database/TestContext.cs rename to tests/Kyoo.Tests/Database/TestContext.cs diff --git a/Kyoo.Tests/Database/TestSample.cs b/tests/Kyoo.Tests/Database/TestSample.cs similarity index 100% rename from Kyoo.Tests/Database/TestSample.cs rename to tests/Kyoo.Tests/Database/TestSample.cs diff --git a/Kyoo.Tests/Identifier/IdentifierTests.cs b/tests/Kyoo.Tests/Identifier/IdentifierTests.cs similarity index 100% rename from Kyoo.Tests/Identifier/IdentifierTests.cs rename to tests/Kyoo.Tests/Identifier/IdentifierTests.cs diff --git a/Kyoo.Tests/Identifier/ProviderTests.cs b/tests/Kyoo.Tests/Identifier/ProviderTests.cs similarity index 100% rename from Kyoo.Tests/Identifier/ProviderTests.cs rename to tests/Kyoo.Tests/Identifier/ProviderTests.cs diff --git a/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs b/tests/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs similarity index 100% rename from Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs rename to tests/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs diff --git a/Kyoo.Tests/KAssert.cs b/tests/Kyoo.Tests/KAssert.cs similarity index 100% rename from Kyoo.Tests/KAssert.cs rename to tests/Kyoo.Tests/KAssert.cs diff --git a/Kyoo.Tests/Kyoo.Tests.csproj b/tests/Kyoo.Tests/Kyoo.Tests.csproj similarity index 84% rename from Kyoo.Tests/Kyoo.Tests.csproj rename to tests/Kyoo.Tests/Kyoo.Tests.csproj index b6b25e03..f174bc55 100644 --- a/Kyoo.Tests/Kyoo.Tests.csproj +++ b/tests/Kyoo.Tests/Kyoo.Tests.csproj @@ -32,10 +32,10 @@ - - - - + + + + diff --git a/Kyoo.Tests/Utility/EnumerableTests.cs b/tests/Kyoo.Tests/Utility/EnumerableTests.cs similarity index 100% rename from Kyoo.Tests/Utility/EnumerableTests.cs rename to tests/Kyoo.Tests/Utility/EnumerableTests.cs diff --git a/Kyoo.Tests/Utility/MergerTests.cs b/tests/Kyoo.Tests/Utility/MergerTests.cs similarity index 100% rename from Kyoo.Tests/Utility/MergerTests.cs rename to tests/Kyoo.Tests/Utility/MergerTests.cs diff --git a/Kyoo.Tests/Utility/TaskTests.cs b/tests/Kyoo.Tests/Utility/TaskTests.cs similarity index 100% rename from Kyoo.Tests/Utility/TaskTests.cs rename to tests/Kyoo.Tests/Utility/TaskTests.cs diff --git a/Kyoo.Tests/Utility/UtilityTests.cs b/tests/Kyoo.Tests/Utility/UtilityTests.cs similarity index 100% rename from Kyoo.Tests/Utility/UtilityTests.cs rename to tests/Kyoo.Tests/Utility/UtilityTests.cs From 6566b717f6818235a0e2b46764881251bfe78c85 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 1 Aug 2021 19:21:23 +0200 Subject: [PATCH 21/34] ShowLink: Fixing them and testing it --- Kyoo.Common/Models/Link.cs | 163 -------- Kyoo.CommonAPI/DatabaseContext.cs | 82 ++-- ....cs => 20210801171613_Initial.Designer.cs} | 362 +++++++++--------- ...8_Initial.cs => 20210801171613_Initial.cs} | 90 ++--- ...cs => 20210801171641_Triggers.Designer.cs} | 362 +++++++++--------- ...Triggers.cs => 20210801171641_Triggers.cs} | 4 +- .../PostgresContextModelSnapshot.cs | 360 ++++++++--------- Kyoo.Postgresql/PostgresContext.cs | 7 + ....cs => 20210801171534_Initial.Designer.cs} | 240 ++++++------ ...5_Initial.cs => 20210801171534_Initial.cs} | 90 ++--- ...cs => 20210801171544_Triggers.Designer.cs} | 240 ++++++------ ...Triggers.cs => 20210801171544_Triggers.cs} | 4 +- .../Migrations/SqLiteContextModelSnapshot.cs | 238 ++++++------ Kyoo.SqLite/SqLiteContext.cs | 6 + .../Repositories/EpisodeRepository.cs | 16 +- .../Repositories/ShowRepository.cs | 9 +- .../Database/SpecificTests/ShowTests.cs | 13 + 17 files changed, 1094 insertions(+), 1192 deletions(-) delete mode 100644 Kyoo.Common/Models/Link.cs rename Kyoo.Postgresql/Migrations/{20210730183548_Initial.Designer.cs => 20210801171613_Initial.Designer.cs} (93%) rename Kyoo.Postgresql/Migrations/{20210730183548_Initial.cs => 20210801171613_Initial.cs} (93%) rename Kyoo.Postgresql/Migrations/{20210730203102_Triggers.Designer.cs => 20210801171641_Triggers.Designer.cs} (93%) rename Kyoo.Postgresql/Migrations/{20210730203102_Triggers.cs => 20210801171641_Triggers.cs} (98%) rename Kyoo.SqLite/Migrations/{20210730203155_Initial.Designer.cs => 20210801171534_Initial.Designer.cs} (93%) rename Kyoo.SqLite/Migrations/{20210730203155_Initial.cs => 20210801171534_Initial.cs} (93%) rename Kyoo.SqLite/Migrations/{20210730203746_Triggers.Designer.cs => 20210801171544_Triggers.Designer.cs} (93%) rename Kyoo.SqLite/Migrations/{20210730203746_Triggers.cs => 20210801171544_Triggers.cs} (98%) diff --git a/Kyoo.Common/Models/Link.cs b/Kyoo.Common/Models/Link.cs deleted file mode 100644 index 1f4cf4ac..00000000 --- a/Kyoo.Common/Models/Link.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using System.Linq.Expressions; -using Kyoo.Models.Attributes; - -namespace Kyoo.Models -{ - /// - /// A class representing a link between two resources. - /// - /// - /// Links should only be used on the data layer and not on other application code. - /// - public class Link - { - /// - /// The ID of the first item of the link. - /// The first item of the link should be the one to own the link. - /// - public int FirstID { get; set; } - - /// - /// The ID of the second item of this link - /// The second item of the link should be the owned resource. - /// - public int SecondID { get; set; } - - /// - /// Create a new typeless . - /// - public Link() {} - - /// - /// Create a new typeless with two IDs. - /// - /// The ID of the first resource - /// The ID of the second resource - public Link(int firstID, int secondID) - { - FirstID = firstID; - SecondID = secondID; - } - - /// - /// Create a new typeless between two resources. - /// - /// The first resource - /// The second resource - public Link(IResource first, IResource second) - { - FirstID = first.ID; - SecondID = second.ID; - } - - /// - /// Create a new typed link between two resources. - /// This method can be used instead of the constructor to make use of generic parameters deduction. - /// - /// The first resource - /// The second resource - /// The type of the first resource - /// The type of the second resource - /// A newly created typed link with both resources - public static Link Create(T first, T2 second) - where T : class, IResource - where T2 : class, IResource - { - return new(first, second); - } - - /// - /// Create a new typed link between two resources without storing references to resources. - /// This is the same as but this method does not set - /// and fields. Only IDs are stored and not references. - /// - /// The first resource - /// The second resource - /// The type of the first resource - /// The type of the second resource - /// A newly created typed link with both resources - public static Link UCreate(T first, T2 second) - where T : class, IResource - where T2 : class, IResource - { - return new(first, second, true); - } - - /// - /// The expression to retrieve the unique ID of a Link. This is an aggregate of the two resources IDs. - /// - public static Expression> PrimaryKey - { - get - { - return x => new {First = x.FirstID, Second = x.SecondID}; - } - } - } - - /// - /// A strongly typed link between two resources. - /// - /// The type of the first resource - /// The type of the second resource - public class Link : Link - where T1 : class, IResource - where T2 : class, IResource - { - /// - /// A reference of the first resource. - /// - [SerializeIgnore] public T1 First { get; set; } - - /// - /// A reference to the second resource. - /// - [SerializeIgnore] public T2 Second { get; set; } - - - /// - /// Create a new, empty, typed . - /// - public Link() {} - - - /// - /// Create a new typed link with two resources. - /// - /// The first resource - /// The second resource - /// - /// True if no reference to resources should be kept, false otherwise. - /// The default is false (references are kept). - /// - public Link(T1 first, T2 second, bool privateItems = false) - : base(first, second) - { - if (privateItems) - return; - First = first; - Second = second; - } - - /// - /// Create a new typed link with IDs only. - /// - /// The ID of the first resource - /// The ID of the second resource - public Link(int firstID, int secondID) - : base(firstID, secondID) - { } - - /// - /// The expression to retrieve the unique ID of a typed Link. This is an aggregate of the two resources IDs. - /// - public new static Expression, object>> PrimaryKey - { - get - { - return x => new {First = x.FirstID, Second = x.SecondID}; - } - } - } -} \ No newline at end of file diff --git a/Kyoo.CommonAPI/DatabaseContext.cs b/Kyoo.CommonAPI/DatabaseContext.cs index d2eb8e2f..fd850d02 100644 --- a/Kyoo.CommonAPI/DatabaseContext.cs +++ b/Kyoo.CommonAPI/DatabaseContext.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading; @@ -96,17 +97,23 @@ namespace Kyoo } /// - /// Get a generic link between two resource types. + /// Add a many to many link between two resources. /// /// Types are order dependant. You can't inverse the order. Please always put the owner first. + /// The ID of the first resource. + /// The ID of the second resource. /// The first resource type of the relation. It is the owner of the second /// The second resource type of the relation. It is the contained resource. - /// All links between the two types. - public DbSet> Links() + public async Task AddLinks(int first, int second) where T1 : class, IResource where T2 : class, IResource { - return Set>(); + await Set>(LinkName()) + .AddAsync(new Dictionary + { + [LinkNameFk()] = first, + [LinkNameFk()] = second + }); } @@ -141,6 +148,14 @@ namespace Kyoo where T : IResource where T2 : IResource; + /// + /// Get the name of a link's foreign key. + /// + /// The type that will be accessible via the navigation + /// The name of the foreign key for the given resource. + protected abstract string LinkNameFk() + where T : IResource; + /// /// Set basic configurations (like preventing query tracking) /// @@ -168,6 +183,39 @@ namespace Kyoo .HasForeignKey(x => x.ResourceID) .OnDelete(DeleteBehavior.Cascade); } + + /// + /// Create a many to many relationship between the two entities. + /// The resulting relationship will have an available method. + /// + /// The database model builder + /// The first navigation expression from T to T2 + /// 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, + Expression>> firstNavigation, + Expression>> secondNavigation) + where T : class, IResource + where T2 : class, IResource + { + modelBuilder.Entity() + .HasMany(secondNavigation) + .WithMany(firstNavigation) + .UsingEntity>( + LinkName(), + x => x + .HasOne() + .WithMany() + .HasForeignKey(LinkNameFk()) + .OnDelete(DeleteBehavior.Cascade), + x => x + .HasOne() + .WithMany() + .HasForeignKey(LinkNameFk()) + .OnDelete(DeleteBehavior.Cascade) + ); + } /// @@ -203,26 +251,12 @@ namespace Kyoo .WithMany(x => x.Shows) .OnDelete(DeleteBehavior.SetNull); - modelBuilder.Entity() - .HasMany(x => x.Libraries) - .WithMany(x => x.Providers) - .UsingEntity(x => x.ToTable(LinkName())); - modelBuilder.Entity() - .HasMany(x => x.Libraries) - .WithMany(x => x.Collections) - .UsingEntity(x => x.ToTable(LinkName())); - modelBuilder.Entity() - .HasMany(x => x.Libraries) - .WithMany(x => x.Shows) - .UsingEntity(x => x.ToTable(LinkName())); - modelBuilder.Entity() - .HasMany(x => x.Collections) - .WithMany(x => x.Shows) - .UsingEntity(x => x.ToTable(LinkName())); - modelBuilder.Entity() - .HasMany(x => x.Shows) - .WithMany(x => x.Genres) - .UsingEntity(x => x.ToTable(LinkName())); + _HasManyToMany(modelBuilder, x => x.Providers, x => x.Libraries); + _HasManyToMany(modelBuilder, x => x.Collections, x => x.Libraries); + _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") diff --git a/Kyoo.Postgresql/Migrations/20210730183548_Initial.Designer.cs b/Kyoo.Postgresql/Migrations/20210801171613_Initial.Designer.cs similarity index 93% rename from Kyoo.Postgresql/Migrations/20210730183548_Initial.Designer.cs rename to Kyoo.Postgresql/Migrations/20210801171613_Initial.Designer.cs index 10c42bb3..20b6f7bd 100644 --- a/Kyoo.Postgresql/Migrations/20210730183548_Initial.Designer.cs +++ b/Kyoo.Postgresql/Migrations/20210801171613_Initial.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Postgresql.Migrations { [DbContext(typeof(PostgresContext))] - [Migration("20210730183548_Initial")] + [Migration("20210801171613_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -26,63 +26,6 @@ namespace Kyoo.Postgresql.Migrations .HasAnnotation("ProductVersion", "5.0.8") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - modelBuilder.Entity("CollectionLibrary", b => - { - b.Property("CollectionsID") - .HasColumnType("integer") - .HasColumnName("collections_id"); - - b.Property("LibrariesID") - .HasColumnType("integer") - .HasColumnName("libraries_id"); - - b.HasKey("CollectionsID", "LibrariesID") - .HasName("pk_link_library_collection"); - - b.HasIndex("LibrariesID") - .HasDatabaseName("ix_link_library_collection_libraries_id"); - - b.ToTable("link_library_collection"); - }); - - modelBuilder.Entity("CollectionShow", b => - { - b.Property("CollectionsID") - .HasColumnType("integer") - .HasColumnName("collections_id"); - - b.Property("ShowsID") - .HasColumnType("integer") - .HasColumnName("shows_id"); - - b.HasKey("CollectionsID", "ShowsID") - .HasName("pk_link_collection_show"); - - b.HasIndex("ShowsID") - .HasDatabaseName("ix_link_collection_show_shows_id"); - - b.ToTable("link_collection_show"); - }); - - modelBuilder.Entity("GenreShow", b => - { - b.Property("GenresID") - .HasColumnType("integer") - .HasColumnName("genres_id"); - - b.Property("ShowsID") - .HasColumnType("integer") - .HasColumnName("shows_id"); - - b.HasKey("GenresID", "ShowsID") - .HasName("pk_link_show_genre"); - - b.HasIndex("ShowsID") - .HasDatabaseName("ix_link_show_genre_shows_id"); - - b.ToTable("link_show_genre"); - }); - modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") @@ -674,44 +617,6 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("watched_episodes"); }); - modelBuilder.Entity("LibraryProvider", b => - { - b.Property("LibrariesID") - .HasColumnType("integer") - .HasColumnName("libraries_id"); - - b.Property("ProvidersID") - .HasColumnType("integer") - .HasColumnName("providers_id"); - - b.HasKey("LibrariesID", "ProvidersID") - .HasName("pk_link_library_provider"); - - b.HasIndex("ProvidersID") - .HasDatabaseName("ix_link_library_provider_providers_id"); - - b.ToTable("link_library_provider"); - }); - - modelBuilder.Entity("LibraryShow", b => - { - b.Property("LibrariesID") - .HasColumnType("integer") - .HasColumnName("libraries_id"); - - b.Property("ShowsID") - .HasColumnType("integer") - .HasColumnName("shows_id"); - - b.HasKey("LibrariesID", "ShowsID") - .HasName("pk_link_library_show"); - - b.HasIndex("ShowsID") - .HasDatabaseName("ix_link_library_show_shows_id"); - - b.ToTable("link_library_show"); - }); - modelBuilder.Entity("ShowUser", b => { b.Property("UsersID") @@ -785,6 +690,101 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("episode_metadata_id"); }); + modelBuilder.Entity("link_collection_show", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("collection_id", "show_id") + .HasName("pk_link_collection_show"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_collection_show_show_id"); + + b.ToTable("link_collection_show"); + }); + + modelBuilder.Entity("link_library_collection", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); + + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.HasKey("collection_id", "library_id") + .HasName("pk_link_library_collection"); + + b.HasIndex("library_id") + .HasDatabaseName("ix_link_library_collection_library_id"); + + b.ToTable("link_library_collection"); + }); + + modelBuilder.Entity("link_library_provider", b => + { + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.Property("provider_id") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.HasKey("library_id", "provider_id") + .HasName("pk_link_library_provider"); + + b.HasIndex("provider_id") + .HasDatabaseName("ix_link_library_provider_provider_id"); + + b.ToTable("link_library_provider"); + }); + + modelBuilder.Entity("link_library_show", b => + { + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("library_id", "show_id") + .HasName("pk_link_library_show"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_library_show_show_id"); + + b.ToTable("link_library_show"); + }); + + modelBuilder.Entity("link_show_genre", b => + { + b.Property("genre_id") + .HasColumnType("integer") + .HasColumnName("genre_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("genre_id", "show_id") + .HasName("pk_link_show_genre"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_show_genre_show_id"); + + b.ToTable("link_show_genre"); + }); + modelBuilder.Entity("people_metadata_id", b => { b.Property("ResourceID") @@ -893,57 +893,6 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("studio_metadata_id"); }); - modelBuilder.Entity("CollectionLibrary", b => - { - b.HasOne("Kyoo.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsID") - .HasConstraintName("fk_link_library_collection_collections_collections_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .HasConstraintName("fk_link_library_collection_libraries_libraries_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("CollectionShow", b => - { - b.HasOne("Kyoo.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsID") - .HasConstraintName("fk_link_collection_show_collections_collections_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .HasConstraintName("fk_link_collection_show_shows_shows_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("GenreShow", b => - { - b.HasOne("Kyoo.Models.Genre", null) - .WithMany() - .HasForeignKey("GenresID") - .HasConstraintName("fk_link_show_genre_genres_genres_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .HasConstraintName("fk_link_show_genre_shows_shows_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -1039,40 +988,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Episode"); }); - modelBuilder.Entity("LibraryProvider", b => - { - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .HasConstraintName("fk_link_library_provider_libraries_libraries_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", null) - .WithMany() - .HasForeignKey("ProvidersID") - .HasConstraintName("fk_link_library_provider_providers_providers_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("LibraryShow", b => - { - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .HasConstraintName("fk_link_library_show_libraries_libraries_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .HasConstraintName("fk_link_library_show_shows_shows_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("ShowUser", b => { b.HasOne("Kyoo.Models.User", null) @@ -1128,6 +1043,91 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Provider"); }); + modelBuilder.Entity("link_collection_show", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .HasConstraintName("fk_link_collection_show_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_collection_show_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_collection", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .HasConstraintName("fk_link_library_collection_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_collection_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_provider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_provider_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("provider_id") + .HasConstraintName("fk_link_library_provider_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_show", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_show_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_library_show_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_show_genre", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("genre_id") + .HasConstraintName("fk_link_show_genre_genres_genre_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_show_genre_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("people_metadata_id", b => { b.HasOne("Kyoo.Models.Provider", "Provider") diff --git a/Kyoo.Postgresql/Migrations/20210730183548_Initial.cs b/Kyoo.Postgresql/Migrations/20210801171613_Initial.cs similarity index 93% rename from Kyoo.Postgresql/Migrations/20210730183548_Initial.cs rename to Kyoo.Postgresql/Migrations/20210801171613_Initial.cs index dc37cba9..395d9e27 100644 --- a/Kyoo.Postgresql/Migrations/20210730183548_Initial.cs +++ b/Kyoo.Postgresql/Migrations/20210801171613_Initial.cs @@ -127,21 +127,21 @@ namespace Kyoo.Postgresql.Migrations name: "link_library_collection", columns: table => new { - collections_id = table.Column(type: "integer", nullable: false), - libraries_id = table.Column(type: "integer", nullable: false) + 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.collections_id, x.libraries_id }); + table.PrimaryKey("pk_link_library_collection", x => new { x.collection_id, x.library_id }); table.ForeignKey( - name: "fk_link_library_collection_collections_collections_id", - column: x => x.collections_id, + 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_libraries_id", - column: x => x.libraries_id, + name: "fk_link_library_collection_libraries_library_id", + column: x => x.library_id, principalTable: "libraries", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -177,21 +177,21 @@ namespace Kyoo.Postgresql.Migrations name: "link_library_provider", columns: table => new { - libraries_id = table.Column(type: "integer", nullable: false), - providers_id = table.Column(type: "integer", nullable: false) + 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.libraries_id, x.providers_id }); + table.PrimaryKey("pk_link_library_provider", x => new { x.library_id, x.provider_id }); table.ForeignKey( - name: "fk_link_library_provider_libraries_libraries_id", - column: x => x.libraries_id, + 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_providers_id", - column: x => x.providers_id, + name: "fk_link_library_provider_providers_provider_id", + column: x => x.provider_id, principalTable: "providers", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -282,21 +282,21 @@ namespace Kyoo.Postgresql.Migrations name: "link_collection_show", columns: table => new { - collections_id = table.Column(type: "integer", nullable: false), - shows_id = table.Column(type: "integer", nullable: false) + 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.collections_id, x.shows_id }); + table.PrimaryKey("pk_link_collection_show", x => new { x.collection_id, x.show_id }); table.ForeignKey( - name: "fk_link_collection_show_collections_collections_id", - column: x => x.collections_id, + 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_shows_id", - column: x => x.shows_id, + name: "fk_link_collection_show_shows_show_id", + column: x => x.show_id, principalTable: "shows", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -306,21 +306,21 @@ namespace Kyoo.Postgresql.Migrations name: "link_library_show", columns: table => new { - libraries_id = table.Column(type: "integer", nullable: false), - shows_id = table.Column(type: "integer", nullable: false) + 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.libraries_id, x.shows_id }); + table.PrimaryKey("pk_link_library_show", x => new { x.library_id, x.show_id }); table.ForeignKey( - name: "fk_link_library_show_libraries_libraries_id", - column: x => x.libraries_id, + 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_shows_id", - column: x => x.shows_id, + name: "fk_link_library_show_shows_show_id", + column: x => x.show_id, principalTable: "shows", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -330,21 +330,21 @@ namespace Kyoo.Postgresql.Migrations name: "link_show_genre", columns: table => new { - genres_id = table.Column(type: "integer", nullable: false), - shows_id = table.Column(type: "integer", nullable: false) + 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.genres_id, x.shows_id }); + table.PrimaryKey("pk_link_show_genre", x => new { x.genre_id, x.show_id }); table.ForeignKey( - name: "fk_link_show_genre_genres_genres_id", - column: x => x.genres_id, + 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_shows_id", - column: x => x.shows_id, + name: "fk_link_show_genre_shows_show_id", + column: x => x.show_id, principalTable: "shows", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -641,29 +641,29 @@ namespace Kyoo.Postgresql.Migrations unique: true); migrationBuilder.CreateIndex( - name: "ix_link_collection_show_shows_id", + name: "ix_link_collection_show_show_id", table: "link_collection_show", - column: "shows_id"); + column: "show_id"); migrationBuilder.CreateIndex( - name: "ix_link_library_collection_libraries_id", + name: "ix_link_library_collection_library_id", table: "link_library_collection", - column: "libraries_id"); + column: "library_id"); migrationBuilder.CreateIndex( - name: "ix_link_library_provider_providers_id", + name: "ix_link_library_provider_provider_id", table: "link_library_provider", - column: "providers_id"); + column: "provider_id"); migrationBuilder.CreateIndex( - name: "ix_link_library_show_shows_id", + name: "ix_link_library_show_show_id", table: "link_library_show", - column: "shows_id"); + column: "show_id"); migrationBuilder.CreateIndex( - name: "ix_link_show_genre_shows_id", + name: "ix_link_show_genre_show_id", table: "link_show_genre", - column: "shows_id"); + column: "show_id"); migrationBuilder.CreateIndex( name: "ix_link_user_show_watched_id", diff --git a/Kyoo.Postgresql/Migrations/20210730203102_Triggers.Designer.cs b/Kyoo.Postgresql/Migrations/20210801171641_Triggers.Designer.cs similarity index 93% rename from Kyoo.Postgresql/Migrations/20210730203102_Triggers.Designer.cs rename to Kyoo.Postgresql/Migrations/20210801171641_Triggers.Designer.cs index ff1ae33b..fd13824a 100644 --- a/Kyoo.Postgresql/Migrations/20210730203102_Triggers.Designer.cs +++ b/Kyoo.Postgresql/Migrations/20210801171641_Triggers.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Postgresql.Migrations { [DbContext(typeof(PostgresContext))] - [Migration("20210730203102_Triggers")] + [Migration("20210801171641_Triggers")] partial class Triggers { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -26,63 +26,6 @@ namespace Kyoo.Postgresql.Migrations .HasAnnotation("ProductVersion", "5.0.8") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - modelBuilder.Entity("CollectionLibrary", b => - { - b.Property("CollectionsID") - .HasColumnType("integer") - .HasColumnName("collections_id"); - - b.Property("LibrariesID") - .HasColumnType("integer") - .HasColumnName("libraries_id"); - - b.HasKey("CollectionsID", "LibrariesID") - .HasName("pk_link_library_collection"); - - b.HasIndex("LibrariesID") - .HasDatabaseName("ix_link_library_collection_libraries_id"); - - b.ToTable("link_library_collection"); - }); - - modelBuilder.Entity("CollectionShow", b => - { - b.Property("CollectionsID") - .HasColumnType("integer") - .HasColumnName("collections_id"); - - b.Property("ShowsID") - .HasColumnType("integer") - .HasColumnName("shows_id"); - - b.HasKey("CollectionsID", "ShowsID") - .HasName("pk_link_collection_show"); - - b.HasIndex("ShowsID") - .HasDatabaseName("ix_link_collection_show_shows_id"); - - b.ToTable("link_collection_show"); - }); - - modelBuilder.Entity("GenreShow", b => - { - b.Property("GenresID") - .HasColumnType("integer") - .HasColumnName("genres_id"); - - b.Property("ShowsID") - .HasColumnType("integer") - .HasColumnName("shows_id"); - - b.HasKey("GenresID", "ShowsID") - .HasName("pk_link_show_genre"); - - b.HasIndex("ShowsID") - .HasDatabaseName("ix_link_show_genre_shows_id"); - - b.ToTable("link_show_genre"); - }); - modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") @@ -674,44 +617,6 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("watched_episodes"); }); - modelBuilder.Entity("LibraryProvider", b => - { - b.Property("LibrariesID") - .HasColumnType("integer") - .HasColumnName("libraries_id"); - - b.Property("ProvidersID") - .HasColumnType("integer") - .HasColumnName("providers_id"); - - b.HasKey("LibrariesID", "ProvidersID") - .HasName("pk_link_library_provider"); - - b.HasIndex("ProvidersID") - .HasDatabaseName("ix_link_library_provider_providers_id"); - - b.ToTable("link_library_provider"); - }); - - modelBuilder.Entity("LibraryShow", b => - { - b.Property("LibrariesID") - .HasColumnType("integer") - .HasColumnName("libraries_id"); - - b.Property("ShowsID") - .HasColumnType("integer") - .HasColumnName("shows_id"); - - b.HasKey("LibrariesID", "ShowsID") - .HasName("pk_link_library_show"); - - b.HasIndex("ShowsID") - .HasDatabaseName("ix_link_library_show_shows_id"); - - b.ToTable("link_library_show"); - }); - modelBuilder.Entity("ShowUser", b => { b.Property("UsersID") @@ -785,6 +690,101 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("episode_metadata_id"); }); + modelBuilder.Entity("link_collection_show", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("collection_id", "show_id") + .HasName("pk_link_collection_show"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_collection_show_show_id"); + + b.ToTable("link_collection_show"); + }); + + modelBuilder.Entity("link_library_collection", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); + + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.HasKey("collection_id", "library_id") + .HasName("pk_link_library_collection"); + + b.HasIndex("library_id") + .HasDatabaseName("ix_link_library_collection_library_id"); + + b.ToTable("link_library_collection"); + }); + + modelBuilder.Entity("link_library_provider", b => + { + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.Property("provider_id") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.HasKey("library_id", "provider_id") + .HasName("pk_link_library_provider"); + + b.HasIndex("provider_id") + .HasDatabaseName("ix_link_library_provider_provider_id"); + + b.ToTable("link_library_provider"); + }); + + modelBuilder.Entity("link_library_show", b => + { + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("library_id", "show_id") + .HasName("pk_link_library_show"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_library_show_show_id"); + + b.ToTable("link_library_show"); + }); + + modelBuilder.Entity("link_show_genre", b => + { + b.Property("genre_id") + .HasColumnType("integer") + .HasColumnName("genre_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("genre_id", "show_id") + .HasName("pk_link_show_genre"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_show_genre_show_id"); + + b.ToTable("link_show_genre"); + }); + modelBuilder.Entity("people_metadata_id", b => { b.Property("ResourceID") @@ -893,57 +893,6 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("studio_metadata_id"); }); - modelBuilder.Entity("CollectionLibrary", b => - { - b.HasOne("Kyoo.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsID") - .HasConstraintName("fk_link_library_collection_collections_collections_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .HasConstraintName("fk_link_library_collection_libraries_libraries_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("CollectionShow", b => - { - b.HasOne("Kyoo.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsID") - .HasConstraintName("fk_link_collection_show_collections_collections_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .HasConstraintName("fk_link_collection_show_shows_shows_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("GenreShow", b => - { - b.HasOne("Kyoo.Models.Genre", null) - .WithMany() - .HasForeignKey("GenresID") - .HasConstraintName("fk_link_show_genre_genres_genres_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .HasConstraintName("fk_link_show_genre_shows_shows_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -1039,40 +988,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Episode"); }); - modelBuilder.Entity("LibraryProvider", b => - { - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .HasConstraintName("fk_link_library_provider_libraries_libraries_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", null) - .WithMany() - .HasForeignKey("ProvidersID") - .HasConstraintName("fk_link_library_provider_providers_providers_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("LibraryShow", b => - { - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .HasConstraintName("fk_link_library_show_libraries_libraries_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .HasConstraintName("fk_link_library_show_shows_shows_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("ShowUser", b => { b.HasOne("Kyoo.Models.User", null) @@ -1128,6 +1043,91 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Provider"); }); + modelBuilder.Entity("link_collection_show", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .HasConstraintName("fk_link_collection_show_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_collection_show_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_collection", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .HasConstraintName("fk_link_library_collection_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_collection_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_provider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_provider_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("provider_id") + .HasConstraintName("fk_link_library_provider_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_show", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_show_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_library_show_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_show_genre", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("genre_id") + .HasConstraintName("fk_link_show_genre_genres_genre_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_show_genre_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("people_metadata_id", b => { b.HasOne("Kyoo.Models.Provider", "Provider") diff --git a/Kyoo.Postgresql/Migrations/20210730203102_Triggers.cs b/Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs similarity index 98% rename from Kyoo.Postgresql/Migrations/20210730203102_Triggers.cs rename to Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs index f84b767b..6ee2961c 100644 --- a/Kyoo.Postgresql/Migrations/20210730203102_Triggers.cs +++ b/Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs @@ -149,8 +149,8 @@ namespace Kyoo.Postgresql.Migrations WHERE NOT (EXISTS ( SELECT 1 FROM link_collection_show AS l - INNER JOIN collections AS c ON l.collections_id = c.id - WHERE s.id = l.shows_id)) + INNER JOIN collections AS c ON l.collection_id = c.id + WHERE s.id = l.show_id)) UNION ALL SELECT -c0.id, c0.slug, c0.name AS title, c0.overview, 'unknown'::status AS status, NULL AS start_air, NULL AS end_air, c0.images, 'collection'::item_type AS type diff --git a/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs b/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs index 379b8d09..cd338b2f 100644 --- a/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs +++ b/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs @@ -24,63 +24,6 @@ namespace Kyoo.Postgresql.Migrations .HasAnnotation("ProductVersion", "5.0.8") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - modelBuilder.Entity("CollectionLibrary", b => - { - b.Property("CollectionsID") - .HasColumnType("integer") - .HasColumnName("collections_id"); - - b.Property("LibrariesID") - .HasColumnType("integer") - .HasColumnName("libraries_id"); - - b.HasKey("CollectionsID", "LibrariesID") - .HasName("pk_link_library_collection"); - - b.HasIndex("LibrariesID") - .HasDatabaseName("ix_link_library_collection_libraries_id"); - - b.ToTable("link_library_collection"); - }); - - modelBuilder.Entity("CollectionShow", b => - { - b.Property("CollectionsID") - .HasColumnType("integer") - .HasColumnName("collections_id"); - - b.Property("ShowsID") - .HasColumnType("integer") - .HasColumnName("shows_id"); - - b.HasKey("CollectionsID", "ShowsID") - .HasName("pk_link_collection_show"); - - b.HasIndex("ShowsID") - .HasDatabaseName("ix_link_collection_show_shows_id"); - - b.ToTable("link_collection_show"); - }); - - modelBuilder.Entity("GenreShow", b => - { - b.Property("GenresID") - .HasColumnType("integer") - .HasColumnName("genres_id"); - - b.Property("ShowsID") - .HasColumnType("integer") - .HasColumnName("shows_id"); - - b.HasKey("GenresID", "ShowsID") - .HasName("pk_link_show_genre"); - - b.HasIndex("ShowsID") - .HasDatabaseName("ix_link_show_genre_shows_id"); - - b.ToTable("link_show_genre"); - }); - modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") @@ -672,44 +615,6 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("watched_episodes"); }); - modelBuilder.Entity("LibraryProvider", b => - { - b.Property("LibrariesID") - .HasColumnType("integer") - .HasColumnName("libraries_id"); - - b.Property("ProvidersID") - .HasColumnType("integer") - .HasColumnName("providers_id"); - - b.HasKey("LibrariesID", "ProvidersID") - .HasName("pk_link_library_provider"); - - b.HasIndex("ProvidersID") - .HasDatabaseName("ix_link_library_provider_providers_id"); - - b.ToTable("link_library_provider"); - }); - - modelBuilder.Entity("LibraryShow", b => - { - b.Property("LibrariesID") - .HasColumnType("integer") - .HasColumnName("libraries_id"); - - b.Property("ShowsID") - .HasColumnType("integer") - .HasColumnName("shows_id"); - - b.HasKey("LibrariesID", "ShowsID") - .HasName("pk_link_library_show"); - - b.HasIndex("ShowsID") - .HasDatabaseName("ix_link_library_show_shows_id"); - - b.ToTable("link_library_show"); - }); - modelBuilder.Entity("ShowUser", b => { b.Property("UsersID") @@ -783,6 +688,101 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("episode_metadata_id"); }); + modelBuilder.Entity("link_collection_show", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("collection_id", "show_id") + .HasName("pk_link_collection_show"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_collection_show_show_id"); + + b.ToTable("link_collection_show"); + }); + + modelBuilder.Entity("link_library_collection", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); + + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.HasKey("collection_id", "library_id") + .HasName("pk_link_library_collection"); + + b.HasIndex("library_id") + .HasDatabaseName("ix_link_library_collection_library_id"); + + b.ToTable("link_library_collection"); + }); + + modelBuilder.Entity("link_library_provider", b => + { + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.Property("provider_id") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.HasKey("library_id", "provider_id") + .HasName("pk_link_library_provider"); + + b.HasIndex("provider_id") + .HasDatabaseName("ix_link_library_provider_provider_id"); + + b.ToTable("link_library_provider"); + }); + + modelBuilder.Entity("link_library_show", b => + { + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("library_id", "show_id") + .HasName("pk_link_library_show"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_library_show_show_id"); + + b.ToTable("link_library_show"); + }); + + modelBuilder.Entity("link_show_genre", b => + { + b.Property("genre_id") + .HasColumnType("integer") + .HasColumnName("genre_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("genre_id", "show_id") + .HasName("pk_link_show_genre"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_show_genre_show_id"); + + b.ToTable("link_show_genre"); + }); + modelBuilder.Entity("people_metadata_id", b => { b.Property("ResourceID") @@ -891,57 +891,6 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("studio_metadata_id"); }); - modelBuilder.Entity("CollectionLibrary", b => - { - b.HasOne("Kyoo.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsID") - .HasConstraintName("fk_link_library_collection_collections_collections_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .HasConstraintName("fk_link_library_collection_libraries_libraries_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("CollectionShow", b => - { - b.HasOne("Kyoo.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsID") - .HasConstraintName("fk_link_collection_show_collections_collections_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .HasConstraintName("fk_link_collection_show_shows_shows_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("GenreShow", b => - { - b.HasOne("Kyoo.Models.Genre", null) - .WithMany() - .HasForeignKey("GenresID") - .HasConstraintName("fk_link_show_genre_genres_genres_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .HasConstraintName("fk_link_show_genre_shows_shows_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -1037,40 +986,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Episode"); }); - modelBuilder.Entity("LibraryProvider", b => - { - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .HasConstraintName("fk_link_library_provider_libraries_libraries_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", null) - .WithMany() - .HasForeignKey("ProvidersID") - .HasConstraintName("fk_link_library_provider_providers_providers_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("LibraryShow", b => - { - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .HasConstraintName("fk_link_library_show_libraries_libraries_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .HasConstraintName("fk_link_library_show_shows_shows_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("ShowUser", b => { b.HasOne("Kyoo.Models.User", null) @@ -1126,6 +1041,91 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Provider"); }); + modelBuilder.Entity("link_collection_show", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .HasConstraintName("fk_link_collection_show_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_collection_show_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_collection", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .HasConstraintName("fk_link_library_collection_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_collection_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_provider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_provider_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("provider_id") + .HasConstraintName("fk_link_library_provider_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_show", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_show_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_library_show_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_show_genre", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("genre_id") + .HasConstraintName("fk_link_show_genre_genres_genre_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_show_genre_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("people_metadata_id", b => { b.HasOne("Kyoo.Models.Provider", "Provider") diff --git a/Kyoo.Postgresql/PostgresContext.cs b/Kyoo.Postgresql/PostgresContext.cs index eaea9927..36f12a8c 100644 --- a/Kyoo.Postgresql/PostgresContext.cs +++ b/Kyoo.Postgresql/PostgresContext.cs @@ -142,6 +142,13 @@ namespace Kyoo.Postgresql SnakeCaseNameRewriter rewriter = new(CultureInfo.InvariantCulture); return rewriter.RewriteName("Link" + typeof(T).Name + typeof(T2).Name); } + + /// + protected override string LinkNameFk() + { + SnakeCaseNameRewriter rewriter = new(CultureInfo.InvariantCulture); + return rewriter.RewriteName(typeof(T).Name + "ID"); + } /// protected override bool IsDuplicateException(Exception ex) diff --git a/Kyoo.SqLite/Migrations/20210730203155_Initial.Designer.cs b/Kyoo.SqLite/Migrations/20210801171534_Initial.Designer.cs similarity index 93% rename from Kyoo.SqLite/Migrations/20210730203155_Initial.Designer.cs rename to Kyoo.SqLite/Migrations/20210801171534_Initial.Designer.cs index 48b89a25..0443a20d 100644 --- a/Kyoo.SqLite/Migrations/20210730203155_Initial.Designer.cs +++ b/Kyoo.SqLite/Migrations/20210801171534_Initial.Designer.cs @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Kyoo.SqLite.Migrations { [DbContext(typeof(SqLiteContext))] - [Migration("20210730203155_Initial")] + [Migration("20210801171534_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -18,21 +18,6 @@ namespace Kyoo.SqLite.Migrations modelBuilder .HasAnnotation("ProductVersion", "5.0.8"); - modelBuilder.Entity("CollectionLibrary", b => - { - b.Property("CollectionsID") - .HasColumnType("INTEGER"); - - b.Property("LibrariesID") - .HasColumnType("INTEGER"); - - b.HasKey("CollectionsID", "LibrariesID"); - - b.HasIndex("LibrariesID"); - - b.ToTable("LinkLibraryCollection"); - }); - modelBuilder.Entity("CollectionMetadataID", b => { b.Property("ResourceID") @@ -54,21 +39,6 @@ namespace Kyoo.SqLite.Migrations b.ToTable("CollectionMetadataID"); }); - modelBuilder.Entity("CollectionShow", b => - { - b.Property("CollectionsID") - .HasColumnType("INTEGER"); - - b.Property("ShowsID") - .HasColumnType("INTEGER"); - - b.HasKey("CollectionsID", "ShowsID"); - - b.HasIndex("ShowsID"); - - b.ToTable("LinkCollectionShow"); - }); - modelBuilder.Entity("EpisodeMetadataID", b => { b.Property("ResourceID") @@ -90,21 +60,6 @@ namespace Kyoo.SqLite.Migrations b.ToTable("EpisodeMetadataID"); }); - modelBuilder.Entity("GenreShow", b => - { - b.Property("GenresID") - .HasColumnType("INTEGER"); - - b.Property("ShowsID") - .HasColumnType("INTEGER"); - - b.HasKey("GenresID", "ShowsID"); - - b.HasIndex("ShowsID"); - - b.ToTable("LinkShowGenre"); - }); - modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") @@ -558,36 +513,81 @@ namespace Kyoo.SqLite.Migrations b.ToTable("WatchedEpisodes"); }); - modelBuilder.Entity("LibraryProvider", b => + modelBuilder.Entity("LinkCollectionShow", b => { - b.Property("LibrariesID") + b.Property("CollectionID") .HasColumnType("INTEGER"); - b.Property("ProvidersID") + b.Property("ShowID") .HasColumnType("INTEGER"); - b.HasKey("LibrariesID", "ProvidersID"); + b.HasKey("CollectionID", "ShowID"); - b.HasIndex("ProvidersID"); + b.HasIndex("ShowID"); + + b.ToTable("LinkCollectionShow"); + }); + + modelBuilder.Entity("LinkLibraryCollection", b => + { + b.Property("CollectionID") + .HasColumnType("INTEGER"); + + b.Property("LibraryID") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionID", "LibraryID"); + + b.HasIndex("LibraryID"); + + b.ToTable("LinkLibraryCollection"); + }); + + modelBuilder.Entity("LinkLibraryProvider", b => + { + b.Property("LibraryID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.HasKey("LibraryID", "ProviderID"); + + b.HasIndex("ProviderID"); b.ToTable("LinkLibraryProvider"); }); - modelBuilder.Entity("LibraryShow", b => + modelBuilder.Entity("LinkLibraryShow", b => { - b.Property("LibrariesID") + b.Property("LibraryID") .HasColumnType("INTEGER"); - b.Property("ShowsID") + b.Property("ShowID") .HasColumnType("INTEGER"); - b.HasKey("LibrariesID", "ShowsID"); + b.HasKey("LibraryID", "ShowID"); - b.HasIndex("ShowsID"); + b.HasIndex("ShowID"); b.ToTable("LinkLibraryShow"); }); + modelBuilder.Entity("LinkShowGenre", b => + { + b.Property("GenreID") + .HasColumnType("INTEGER"); + + b.Property("ShowID") + .HasColumnType("INTEGER"); + + b.HasKey("GenreID", "ShowID"); + + b.HasIndex("ShowID"); + + b.ToTable("LinkShowGenre"); + }); + modelBuilder.Entity("PeopleMetadataID", b => { b.Property("ResourceID") @@ -687,21 +687,6 @@ namespace Kyoo.SqLite.Migrations b.ToTable("StudioMetadataID"); }); - modelBuilder.Entity("CollectionLibrary", b => - { - b.HasOne("Kyoo.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("CollectionMetadataID", b => { b.HasOne("Kyoo.Models.Provider", "Provider") @@ -719,21 +704,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Provider"); }); - modelBuilder.Entity("CollectionShow", b => - { - b.HasOne("Kyoo.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("EpisodeMetadataID", b => { b.HasOne("Kyoo.Models.Provider", "Provider") @@ -751,21 +721,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Provider"); }); - modelBuilder.Entity("GenreShow", b => - { - b.HasOne("Kyoo.Models.Genre", null) - .WithMany() - .HasForeignKey("GenresID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -852,32 +807,77 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Episode"); }); - modelBuilder.Entity("LibraryProvider", b => + modelBuilder.Entity("LinkCollectionShow", b => { - b.HasOne("Kyoo.Models.Library", null) + b.HasOne("Kyoo.Models.Collection", null) .WithMany() - .HasForeignKey("LibrariesID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", null) - .WithMany() - .HasForeignKey("ProvidersID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("LibraryShow", b => - { - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") + .HasForeignKey("CollectionID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.HasOne("Kyoo.Models.Show", null) .WithMany() - .HasForeignKey("ShowsID") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryCollection", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryProvider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryShow", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkShowGenre", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("GenreID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); diff --git a/Kyoo.SqLite/Migrations/20210730203155_Initial.cs b/Kyoo.SqLite/Migrations/20210801171534_Initial.cs similarity index 93% rename from Kyoo.SqLite/Migrations/20210730203155_Initial.cs rename to Kyoo.SqLite/Migrations/20210801171534_Initial.cs index 82ae1dca..a7e62b03 100644 --- a/Kyoo.SqLite/Migrations/20210730203155_Initial.cs +++ b/Kyoo.SqLite/Migrations/20210801171534_Initial.cs @@ -119,21 +119,21 @@ namespace Kyoo.SqLite.Migrations name: "LinkLibraryCollection", columns: table => new { - CollectionsID = table.Column(type: "INTEGER", nullable: false), - LibrariesID = table.Column(type: "INTEGER", nullable: false) + CollectionID = table.Column(type: "INTEGER", nullable: false), + LibraryID = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_LinkLibraryCollection", x => new { x.CollectionsID, x.LibrariesID }); + table.PrimaryKey("PK_LinkLibraryCollection", x => new { x.CollectionID, x.LibraryID }); table.ForeignKey( - name: "FK_LinkLibraryCollection_Collections_CollectionsID", - column: x => x.CollectionsID, + name: "FK_LinkLibraryCollection_Collections_CollectionID", + column: x => x.CollectionID, principalTable: "Collections", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_LinkLibraryCollection_Libraries_LibrariesID", - column: x => x.LibrariesID, + name: "FK_LinkLibraryCollection_Libraries_LibraryID", + column: x => x.LibraryID, principalTable: "Libraries", principalColumn: "ID", onDelete: ReferentialAction.Cascade); @@ -169,21 +169,21 @@ namespace Kyoo.SqLite.Migrations name: "LinkLibraryProvider", columns: table => new { - LibrariesID = table.Column(type: "INTEGER", nullable: false), - ProvidersID = table.Column(type: "INTEGER", nullable: false) + LibraryID = table.Column(type: "INTEGER", nullable: false), + ProviderID = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_LinkLibraryProvider", x => new { x.LibrariesID, x.ProvidersID }); + table.PrimaryKey("PK_LinkLibraryProvider", x => new { x.LibraryID, x.ProviderID }); table.ForeignKey( - name: "FK_LinkLibraryProvider_Libraries_LibrariesID", - column: x => x.LibrariesID, + name: "FK_LinkLibraryProvider_Libraries_LibraryID", + column: x => x.LibraryID, principalTable: "Libraries", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_LinkLibraryProvider_Providers_ProvidersID", - column: x => x.ProvidersID, + name: "FK_LinkLibraryProvider_Providers_ProviderID", + column: x => x.ProviderID, principalTable: "Providers", principalColumn: "ID", onDelete: ReferentialAction.Cascade); @@ -274,21 +274,21 @@ namespace Kyoo.SqLite.Migrations name: "LinkCollectionShow", columns: table => new { - CollectionsID = table.Column(type: "INTEGER", nullable: false), - ShowsID = table.Column(type: "INTEGER", nullable: false) + CollectionID = table.Column(type: "INTEGER", nullable: false), + ShowID = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_LinkCollectionShow", x => new { x.CollectionsID, x.ShowsID }); + table.PrimaryKey("PK_LinkCollectionShow", x => new { x.CollectionID, x.ShowID }); table.ForeignKey( - name: "FK_LinkCollectionShow_Collections_CollectionsID", - column: x => x.CollectionsID, + name: "FK_LinkCollectionShow_Collections_CollectionID", + column: x => x.CollectionID, principalTable: "Collections", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_LinkCollectionShow_Shows_ShowsID", - column: x => x.ShowsID, + name: "FK_LinkCollectionShow_Shows_ShowID", + column: x => x.ShowID, principalTable: "Shows", principalColumn: "ID", onDelete: ReferentialAction.Cascade); @@ -298,21 +298,21 @@ namespace Kyoo.SqLite.Migrations name: "LinkLibraryShow", columns: table => new { - LibrariesID = table.Column(type: "INTEGER", nullable: false), - ShowsID = table.Column(type: "INTEGER", nullable: false) + LibraryID = table.Column(type: "INTEGER", nullable: false), + ShowID = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_LinkLibraryShow", x => new { x.LibrariesID, x.ShowsID }); + table.PrimaryKey("PK_LinkLibraryShow", x => new { x.LibraryID, x.ShowID }); table.ForeignKey( - name: "FK_LinkLibraryShow_Libraries_LibrariesID", - column: x => x.LibrariesID, + name: "FK_LinkLibraryShow_Libraries_LibraryID", + column: x => x.LibraryID, principalTable: "Libraries", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_LinkLibraryShow_Shows_ShowsID", - column: x => x.ShowsID, + name: "FK_LinkLibraryShow_Shows_ShowID", + column: x => x.ShowID, principalTable: "Shows", principalColumn: "ID", onDelete: ReferentialAction.Cascade); @@ -322,21 +322,21 @@ namespace Kyoo.SqLite.Migrations name: "LinkShowGenre", columns: table => new { - GenresID = table.Column(type: "INTEGER", nullable: false), - ShowsID = table.Column(type: "INTEGER", nullable: false) + GenreID = table.Column(type: "INTEGER", nullable: false), + ShowID = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_LinkShowGenre", x => new { x.GenresID, x.ShowsID }); + table.PrimaryKey("PK_LinkShowGenre", x => new { x.GenreID, x.ShowID }); table.ForeignKey( - name: "FK_LinkShowGenre_Genres_GenresID", - column: x => x.GenresID, + name: "FK_LinkShowGenre_Genres_GenreID", + column: x => x.GenreID, principalTable: "Genres", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_LinkShowGenre_Shows_ShowsID", - column: x => x.ShowsID, + name: "FK_LinkShowGenre_Shows_ShowID", + column: x => x.ShowID, principalTable: "Shows", principalColumn: "ID", onDelete: ReferentialAction.Cascade); @@ -633,29 +633,29 @@ namespace Kyoo.SqLite.Migrations unique: true); migrationBuilder.CreateIndex( - name: "IX_LinkCollectionShow_ShowsID", + name: "IX_LinkCollectionShow_ShowID", table: "LinkCollectionShow", - column: "ShowsID"); + column: "ShowID"); migrationBuilder.CreateIndex( - name: "IX_LinkLibraryCollection_LibrariesID", + name: "IX_LinkLibraryCollection_LibraryID", table: "LinkLibraryCollection", - column: "LibrariesID"); + column: "LibraryID"); migrationBuilder.CreateIndex( - name: "IX_LinkLibraryProvider_ProvidersID", + name: "IX_LinkLibraryProvider_ProviderID", table: "LinkLibraryProvider", - column: "ProvidersID"); + column: "ProviderID"); migrationBuilder.CreateIndex( - name: "IX_LinkLibraryShow_ShowsID", + name: "IX_LinkLibraryShow_ShowID", table: "LinkLibraryShow", - column: "ShowsID"); + column: "ShowID"); migrationBuilder.CreateIndex( - name: "IX_LinkShowGenre_ShowsID", + name: "IX_LinkShowGenre_ShowID", table: "LinkShowGenre", - column: "ShowsID"); + column: "ShowID"); migrationBuilder.CreateIndex( name: "IX_LinkUserShow_WatchedID", diff --git a/Kyoo.SqLite/Migrations/20210730203746_Triggers.Designer.cs b/Kyoo.SqLite/Migrations/20210801171544_Triggers.Designer.cs similarity index 93% rename from Kyoo.SqLite/Migrations/20210730203746_Triggers.Designer.cs rename to Kyoo.SqLite/Migrations/20210801171544_Triggers.Designer.cs index fc59fcf6..05c1c174 100644 --- a/Kyoo.SqLite/Migrations/20210730203746_Triggers.Designer.cs +++ b/Kyoo.SqLite/Migrations/20210801171544_Triggers.Designer.cs @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Kyoo.SqLite.Migrations { [DbContext(typeof(SqLiteContext))] - [Migration("20210730203746_Triggers")] + [Migration("20210801171544_Triggers")] partial class Triggers { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -18,21 +18,6 @@ namespace Kyoo.SqLite.Migrations modelBuilder .HasAnnotation("ProductVersion", "5.0.8"); - modelBuilder.Entity("CollectionLibrary", b => - { - b.Property("CollectionsID") - .HasColumnType("INTEGER"); - - b.Property("LibrariesID") - .HasColumnType("INTEGER"); - - b.HasKey("CollectionsID", "LibrariesID"); - - b.HasIndex("LibrariesID"); - - b.ToTable("LinkLibraryCollection"); - }); - modelBuilder.Entity("CollectionMetadataID", b => { b.Property("ResourceID") @@ -54,21 +39,6 @@ namespace Kyoo.SqLite.Migrations b.ToTable("CollectionMetadataID"); }); - modelBuilder.Entity("CollectionShow", b => - { - b.Property("CollectionsID") - .HasColumnType("INTEGER"); - - b.Property("ShowsID") - .HasColumnType("INTEGER"); - - b.HasKey("CollectionsID", "ShowsID"); - - b.HasIndex("ShowsID"); - - b.ToTable("LinkCollectionShow"); - }); - modelBuilder.Entity("EpisodeMetadataID", b => { b.Property("ResourceID") @@ -90,21 +60,6 @@ namespace Kyoo.SqLite.Migrations b.ToTable("EpisodeMetadataID"); }); - modelBuilder.Entity("GenreShow", b => - { - b.Property("GenresID") - .HasColumnType("INTEGER"); - - b.Property("ShowsID") - .HasColumnType("INTEGER"); - - b.HasKey("GenresID", "ShowsID"); - - b.HasIndex("ShowsID"); - - b.ToTable("LinkShowGenre"); - }); - modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") @@ -558,36 +513,81 @@ namespace Kyoo.SqLite.Migrations b.ToTable("WatchedEpisodes"); }); - modelBuilder.Entity("LibraryProvider", b => + modelBuilder.Entity("LinkCollectionShow", b => { - b.Property("LibrariesID") + b.Property("CollectionID") .HasColumnType("INTEGER"); - b.Property("ProvidersID") + b.Property("ShowID") .HasColumnType("INTEGER"); - b.HasKey("LibrariesID", "ProvidersID"); + b.HasKey("CollectionID", "ShowID"); - b.HasIndex("ProvidersID"); + b.HasIndex("ShowID"); + + b.ToTable("LinkCollectionShow"); + }); + + modelBuilder.Entity("LinkLibraryCollection", b => + { + b.Property("CollectionID") + .HasColumnType("INTEGER"); + + b.Property("LibraryID") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionID", "LibraryID"); + + b.HasIndex("LibraryID"); + + b.ToTable("LinkLibraryCollection"); + }); + + modelBuilder.Entity("LinkLibraryProvider", b => + { + b.Property("LibraryID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.HasKey("LibraryID", "ProviderID"); + + b.HasIndex("ProviderID"); b.ToTable("LinkLibraryProvider"); }); - modelBuilder.Entity("LibraryShow", b => + modelBuilder.Entity("LinkLibraryShow", b => { - b.Property("LibrariesID") + b.Property("LibraryID") .HasColumnType("INTEGER"); - b.Property("ShowsID") + b.Property("ShowID") .HasColumnType("INTEGER"); - b.HasKey("LibrariesID", "ShowsID"); + b.HasKey("LibraryID", "ShowID"); - b.HasIndex("ShowsID"); + b.HasIndex("ShowID"); b.ToTable("LinkLibraryShow"); }); + modelBuilder.Entity("LinkShowGenre", b => + { + b.Property("GenreID") + .HasColumnType("INTEGER"); + + b.Property("ShowID") + .HasColumnType("INTEGER"); + + b.HasKey("GenreID", "ShowID"); + + b.HasIndex("ShowID"); + + b.ToTable("LinkShowGenre"); + }); + modelBuilder.Entity("PeopleMetadataID", b => { b.Property("ResourceID") @@ -687,21 +687,6 @@ namespace Kyoo.SqLite.Migrations b.ToTable("StudioMetadataID"); }); - modelBuilder.Entity("CollectionLibrary", b => - { - b.HasOne("Kyoo.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("CollectionMetadataID", b => { b.HasOne("Kyoo.Models.Provider", "Provider") @@ -719,21 +704,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Provider"); }); - modelBuilder.Entity("CollectionShow", b => - { - b.HasOne("Kyoo.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("EpisodeMetadataID", b => { b.HasOne("Kyoo.Models.Provider", "Provider") @@ -751,21 +721,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Provider"); }); - modelBuilder.Entity("GenreShow", b => - { - b.HasOne("Kyoo.Models.Genre", null) - .WithMany() - .HasForeignKey("GenresID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -852,32 +807,77 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Episode"); }); - modelBuilder.Entity("LibraryProvider", b => + modelBuilder.Entity("LinkCollectionShow", b => { - b.HasOne("Kyoo.Models.Library", null) + b.HasOne("Kyoo.Models.Collection", null) .WithMany() - .HasForeignKey("LibrariesID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", null) - .WithMany() - .HasForeignKey("ProvidersID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("LibraryShow", b => - { - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") + .HasForeignKey("CollectionID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.HasOne("Kyoo.Models.Show", null) .WithMany() - .HasForeignKey("ShowsID") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryCollection", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryProvider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryShow", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkShowGenre", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("GenreID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); diff --git a/Kyoo.SqLite/Migrations/20210730203746_Triggers.cs b/Kyoo.SqLite/Migrations/20210801171544_Triggers.cs similarity index 98% rename from Kyoo.SqLite/Migrations/20210730203746_Triggers.cs rename to Kyoo.SqLite/Migrations/20210801171544_Triggers.cs index b3e3c5ae..789bc182 100644 --- a/Kyoo.SqLite/Migrations/20210730203746_Triggers.cs +++ b/Kyoo.SqLite/Migrations/20210801171544_Triggers.cs @@ -162,8 +162,8 @@ namespace Kyoo.SqLite.Migrations WHERE NOT (EXISTS ( SELECT 1 FROM LinkCollectionShow AS l - INNER JOIN Collections AS c ON l.CollectionsID = c.ID - WHERE s.ID = l.ShowsID)) + INNER JOIN Collections AS c ON l.CollectionID = c.ID + WHERE s.ID = l.ShowID)) UNION ALL SELECT -c0.ID, c0.Slug, c0.Name AS Title, c0.Overview, 0 AS Status, NULL AS StartAir, NULL AS EndAir, c0.Images, 2 AS Type diff --git a/Kyoo.SqLite/Migrations/SqLiteContextModelSnapshot.cs b/Kyoo.SqLite/Migrations/SqLiteContextModelSnapshot.cs index 28dc670c..50c96524 100644 --- a/Kyoo.SqLite/Migrations/SqLiteContextModelSnapshot.cs +++ b/Kyoo.SqLite/Migrations/SqLiteContextModelSnapshot.cs @@ -16,21 +16,6 @@ namespace Kyoo.SqLite.Migrations modelBuilder .HasAnnotation("ProductVersion", "5.0.8"); - modelBuilder.Entity("CollectionLibrary", b => - { - b.Property("CollectionsID") - .HasColumnType("INTEGER"); - - b.Property("LibrariesID") - .HasColumnType("INTEGER"); - - b.HasKey("CollectionsID", "LibrariesID"); - - b.HasIndex("LibrariesID"); - - b.ToTable("LinkLibraryCollection"); - }); - modelBuilder.Entity("CollectionMetadataID", b => { b.Property("ResourceID") @@ -52,21 +37,6 @@ namespace Kyoo.SqLite.Migrations b.ToTable("CollectionMetadataID"); }); - modelBuilder.Entity("CollectionShow", b => - { - b.Property("CollectionsID") - .HasColumnType("INTEGER"); - - b.Property("ShowsID") - .HasColumnType("INTEGER"); - - b.HasKey("CollectionsID", "ShowsID"); - - b.HasIndex("ShowsID"); - - b.ToTable("LinkCollectionShow"); - }); - modelBuilder.Entity("EpisodeMetadataID", b => { b.Property("ResourceID") @@ -88,21 +58,6 @@ namespace Kyoo.SqLite.Migrations b.ToTable("EpisodeMetadataID"); }); - modelBuilder.Entity("GenreShow", b => - { - b.Property("GenresID") - .HasColumnType("INTEGER"); - - b.Property("ShowsID") - .HasColumnType("INTEGER"); - - b.HasKey("GenresID", "ShowsID"); - - b.HasIndex("ShowsID"); - - b.ToTable("LinkShowGenre"); - }); - modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") @@ -556,36 +511,81 @@ namespace Kyoo.SqLite.Migrations b.ToTable("WatchedEpisodes"); }); - modelBuilder.Entity("LibraryProvider", b => + modelBuilder.Entity("LinkCollectionShow", b => { - b.Property("LibrariesID") + b.Property("CollectionID") .HasColumnType("INTEGER"); - b.Property("ProvidersID") + b.Property("ShowID") .HasColumnType("INTEGER"); - b.HasKey("LibrariesID", "ProvidersID"); + b.HasKey("CollectionID", "ShowID"); - b.HasIndex("ProvidersID"); + b.HasIndex("ShowID"); + + b.ToTable("LinkCollectionShow"); + }); + + modelBuilder.Entity("LinkLibraryCollection", b => + { + b.Property("CollectionID") + .HasColumnType("INTEGER"); + + b.Property("LibraryID") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionID", "LibraryID"); + + b.HasIndex("LibraryID"); + + b.ToTable("LinkLibraryCollection"); + }); + + modelBuilder.Entity("LinkLibraryProvider", b => + { + b.Property("LibraryID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.HasKey("LibraryID", "ProviderID"); + + b.HasIndex("ProviderID"); b.ToTable("LinkLibraryProvider"); }); - modelBuilder.Entity("LibraryShow", b => + modelBuilder.Entity("LinkLibraryShow", b => { - b.Property("LibrariesID") + b.Property("LibraryID") .HasColumnType("INTEGER"); - b.Property("ShowsID") + b.Property("ShowID") .HasColumnType("INTEGER"); - b.HasKey("LibrariesID", "ShowsID"); + b.HasKey("LibraryID", "ShowID"); - b.HasIndex("ShowsID"); + b.HasIndex("ShowID"); b.ToTable("LinkLibraryShow"); }); + modelBuilder.Entity("LinkShowGenre", b => + { + b.Property("GenreID") + .HasColumnType("INTEGER"); + + b.Property("ShowID") + .HasColumnType("INTEGER"); + + b.HasKey("GenreID", "ShowID"); + + b.HasIndex("ShowID"); + + b.ToTable("LinkShowGenre"); + }); + modelBuilder.Entity("PeopleMetadataID", b => { b.Property("ResourceID") @@ -685,21 +685,6 @@ namespace Kyoo.SqLite.Migrations b.ToTable("StudioMetadataID"); }); - modelBuilder.Entity("CollectionLibrary", b => - { - b.HasOne("Kyoo.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("CollectionMetadataID", b => { b.HasOne("Kyoo.Models.Provider", "Provider") @@ -717,21 +702,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Provider"); }); - modelBuilder.Entity("CollectionShow", b => - { - b.HasOne("Kyoo.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("EpisodeMetadataID", b => { b.HasOne("Kyoo.Models.Provider", "Provider") @@ -749,21 +719,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Provider"); }); - modelBuilder.Entity("GenreShow", b => - { - b.HasOne("Kyoo.Models.Genre", null) - .WithMany() - .HasForeignKey("GenresID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -850,32 +805,77 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Episode"); }); - modelBuilder.Entity("LibraryProvider", b => + modelBuilder.Entity("LinkCollectionShow", b => { - b.HasOne("Kyoo.Models.Library", null) + b.HasOne("Kyoo.Models.Collection", null) .WithMany() - .HasForeignKey("LibrariesID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", null) - .WithMany() - .HasForeignKey("ProvidersID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("LibraryShow", b => - { - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") + .HasForeignKey("CollectionID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.HasOne("Kyoo.Models.Show", null) .WithMany() - .HasForeignKey("ShowsID") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryCollection", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryProvider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryShow", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkShowGenre", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("GenreID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); diff --git a/Kyoo.SqLite/SqLiteContext.cs b/Kyoo.SqLite/SqLiteContext.cs index e05d0308..cbd44659 100644 --- a/Kyoo.SqLite/SqLiteContext.cs +++ b/Kyoo.SqLite/SqLiteContext.cs @@ -155,6 +155,12 @@ namespace Kyoo.SqLite { return "Link" + typeof(T).Name + typeof(T2).Name; } + + /// + protected override string LinkNameFk() + { + return typeof(T).Name + "ID"; + } /// protected override bool IsDuplicateException(Exception ex) diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index ee5d2cdd..d50fe8c4 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -141,12 +141,16 @@ namespace Kyoo.Controllers /// The parameter is returned. private async Task ValidateTracks(Episode resource) { - resource.Tracks = await TaskUtils.DefaultIfNull(resource.Tracks?.SelectAsync(x => + if (resource.Tracks == null) + return resource; + + resource.Tracks = await resource.Tracks.SelectAsync(x => { x.Episode = resource; x.EpisodeSlug = resource.Slug; return _tracks.Create(x); - }).ToListAsync()); + }).ToListAsync(); + _database.Tracks.AttachRange(resource.Tracks); return resource; } @@ -155,8 +159,12 @@ namespace Kyoo.Controllers { await base.Validate(resource); if (resource.ShowID <= 0) - throw new ArgumentException($"Can't store an episode not related " + - $"to any show (showID: {resource.ShowID})."); + { + if (resource.Show == null) + throw new ArgumentException($"Can't store an episode not related " + + $"to any show (showID: {resource.ShowID})."); + resource.ShowID = resource.Show.ID; + } if (resource.ExternalIDs != null) { diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 458690f7..d74c2fe3 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -159,21 +159,18 @@ namespace Kyoo.Controllers { if (collectionID != null) { - await _database.Links() - .AddAsync(new Link(collectionID.Value, showID)); + await _database.AddLinks(collectionID.Value, showID); await _database.SaveIfNoDuplicates(); if (libraryID != null) { - await _database.Links() - .AddAsync(new Link(libraryID.Value, collectionID.Value)); + await _database.AddLinks(libraryID.Value, collectionID.Value); await _database.SaveIfNoDuplicates(); } } if (libraryID != null) { - await _database.Links() - .AddAsync(new Link(libraryID.Value, showID)); + await _database.AddLinks(libraryID.Value, showID); await _database.SaveIfNoDuplicates(); } } diff --git a/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs index d004f954..d3111f75 100644 --- a/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs @@ -360,5 +360,18 @@ namespace Kyoo.Tests.Database Assert.Equal(0, await Repositories.LibraryManager.SeasonRepository.GetCount()); Assert.Equal(0, await Repositories.LibraryManager.EpisodeRepository.GetCount()); } + + [Fact] + public async Task AddShowLinkTest() + { + 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) + .First(x => x.ID == 1); + Assert.Contains(2, show.Libraries.Select(x => x.ID)); + } } } \ No newline at end of file From 928a8d21474f820555f07d05550609d1fec54e95 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 2 Aug 2021 00:41:05 +0200 Subject: [PATCH 22/34] EF: Fixing multiple same-entity references --- Kyoo.CommonAPI/DatabaseContext.cs | 24 +++- .../Repositories/CollectionRepository.cs | 4 +- .../Repositories/EpisodeRepository.cs | 4 +- .../Repositories/PeopleRepository.cs | 35 +++-- .../Repositories/SeasonRepository.cs | 4 +- .../Repositories/ShowRepository.cs | 8 +- .../Repositories/StudioRepository.cs | 17 ++- .../Database/SpecificTests/PeopleTests.cs | 133 ++++++++++++++++++ tests/Kyoo.Tests/Database/TestSample.cs | 14 ++ 9 files changed, 209 insertions(+), 34 deletions(-) diff --git a/Kyoo.CommonAPI/DatabaseContext.cs b/Kyoo.CommonAPI/DatabaseContext.cs index fd850d02..ad96ff78 100644 --- a/Kyoo.CommonAPI/DatabaseContext.cs +++ b/Kyoo.CommonAPI/DatabaseContext.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using Kyoo.Controllers; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -503,6 +504,23 @@ namespace Kyoo } } + /// + /// Return the first resource with the given slug that is currently tracked by this context. + /// This allow one to limit redundant calls to during the + /// same transaction and prevent fails from EF when two same entities are being tracked. + /// + /// The slug of the resource to check + /// The type of entity to check + /// The local entity representing the resource with the given slug if it exists or null. + [CanBeNull] + public T LocalEntity(string slug) + where T : class, IResource + { + return ChangeTracker.Entries() + .FirstOrDefault(x => x.Entity.Slug == slug) + ?.Entity; + } + /// /// Check if the exception is a duplicated exception. /// @@ -515,14 +533,12 @@ namespace Kyoo /// private void DiscardChanges() { - foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged - && x.State != EntityState.Detached)) + foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Detached)) { entry.State = EntityState.Detached; } } - - + /// /// Perform a case insensitive like operation. /// diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index 13273ab3..e4250c8f 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -71,9 +71,9 @@ namespace Kyoo.Controllers { foreach (MetadataID id in resource.ExternalIDs) { - id.Provider = await _providers.CreateIfNotExists(id.Provider); + id.Provider = _database.LocalEntity(id.Provider.Slug) + ?? await _providers.CreateIfNotExists(id.Provider); id.ProviderID = id.Provider.ID; - _database.Entry(id.Provider).State = EntityState.Unchanged; } _database.MetadataIds().AttachRange(resource.ExternalIDs); } diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index d50fe8c4..92a573a4 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -170,9 +170,9 @@ namespace Kyoo.Controllers { foreach (MetadataID id in resource.ExternalIDs) { - id.Provider = await _providers.CreateIfNotExists(id.Provider); + id.Provider = _database.LocalEntity(id.Provider.Slug) + ?? await _providers.CreateIfNotExists(id.Provider); id.ProviderID = id.Provider.ID; - _database.Entry(id.Provider).State = EntityState.Unchanged; } _database.MetadataIds().AttachRange(resource.ExternalIDs); } diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index a55c8e0e..3b6d3f87 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -62,7 +62,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.ExternalIDs.ForEach(x => _database.MetadataIds().Attach(x)); await _database.SaveChangesAsync($"Trying to insert a duplicated people (slug {obj.Slug} already exists)."); return obj; } @@ -71,23 +70,35 @@ namespace Kyoo.Controllers protected override async Task Validate(People resource) { await base.Validate(resource); - await resource.ExternalIDs.ForEachAsync(async id => + + if (resource.ExternalIDs != null) { - id.Provider = await _providers.CreateIfNotExists(id.Provider); - id.ProviderID = id.Provider.ID; - _database.Entry(id.Provider).State = EntityState.Detached; - }); - await resource.Roles.ForEachAsync(async role => + foreach (MetadataID id in resource.ExternalIDs) + { + id.Provider = _database.LocalEntity(id.Provider.Slug) + ?? await _providers.CreateIfNotExists(id.Provider); + id.ProviderID = id.Provider.ID; + } + _database.MetadataIds().AttachRange(resource.ExternalIDs); + } + + if (resource.Roles != null) { - role.Show = await _shows.Value.CreateIfNotExists(role.Show); - role.ShowID = role.Show.ID; - _database.Entry(role.Show).State = EntityState.Detached; - }); + foreach (PeopleRole role in resource.Roles) + { + role.Show = _database.LocalEntity(role.Show.Slug) + ?? await _shows.Value.CreateIfNotExists(role.Show); + role.ShowID = role.Show.ID; + _database.Entry(role).State = EntityState.Added; + } + } } /// 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(); @@ -98,9 +109,7 @@ namespace Kyoo.Controllers { await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); resource.ExternalIDs = changed.ExternalIDs; - } - await base.EditRelations(resource, changed, resetOld); } /// diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index 88abfaea..0ac7e759 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -107,9 +107,9 @@ namespace Kyoo.Controllers { foreach (MetadataID id in resource.ExternalIDs) { - id.Provider = await _providers.CreateIfNotExists(id.Provider); + id.Provider = _database.LocalEntity(id.Provider.Slug) + ?? await _providers.CreateIfNotExists(id.Provider); id.ProviderID = id.Provider.ID; - _database.Entry(id.Provider).State = EntityState.Unchanged; } _database.MetadataIds().AttachRange(resource.ExternalIDs); } diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index d74c2fe3..3ef8747c 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -102,9 +102,9 @@ namespace Kyoo.Controllers { foreach (MetadataID id in resource.ExternalIDs) { - id.Provider = await _providers.CreateIfNotExists(id.Provider); + id.Provider = _database.LocalEntity(id.Provider.Slug) + ?? await _providers.CreateIfNotExists(id.Provider); id.ProviderID = id.Provider.ID; - _database.Entry(id.Provider).State = EntityState.Detached; } _database.MetadataIds().AttachRange(resource.ExternalIDs); } @@ -113,9 +113,9 @@ namespace Kyoo.Controllers { foreach (PeopleRole role in resource.People) { - role.People = await _people.CreateIfNotExists(role.People); + role.People = _database.LocalEntity(role.People.Slug) + ?? await _people.CreateIfNotExists(role.People); role.PeopleID = role.People.ID; - _database.Entry(role.People).State = EntityState.Detached; _database.Entry(role).State = EntityState.Added; } } diff --git a/Kyoo/Controllers/Repositories/StudioRepository.cs b/Kyoo/Controllers/Repositories/StudioRepository.cs index 744529bd..2c807375 100644 --- a/Kyoo/Controllers/Repositories/StudioRepository.cs +++ b/Kyoo/Controllers/Repositories/StudioRepository.cs @@ -54,7 +54,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.ExternalIDs.ForEach(x => _database.MetadataIds().Attach(x)); await _database.SaveChangesAsync($"Trying to insert a duplicated studio (slug {obj.Slug} already exists)."); return obj; } @@ -63,12 +62,16 @@ namespace Kyoo.Controllers protected override async Task Validate(Studio resource) { await base.Validate(resource); - await resource.ExternalIDs.ForEachAsync(async x => - { - x.Provider = await _providers.CreateIfNotExists(x.Provider); - x.ProviderID = x.Provider.ID; - _database.Entry(x.Provider).State = EntityState.Detached; - }); + 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; + } + _database.MetadataIds().AttachRange(resource.ExternalIDs); + } } /// diff --git a/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs index 23d40bfe..3de88edf 100644 --- a/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs @@ -1,5 +1,9 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models; +using Microsoft.EntityFrameworkCore; using Xunit; using Xunit.Abstractions; @@ -33,5 +37,134 @@ namespace Kyoo.Tests.Database { _repository = Repositories.LibraryManager.PeopleRepository; } + + [Fact] + public async Task CreateWithExternalIdTest() + { + People value = TestSample.GetNew(); + value.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + new MetadataID + { + Provider = TestSample.GetNew(), + Link = "new-provider-link", + DataID = "new-id" + } + }; + 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() + { + People value = await _repository.Get(TestSample.Get().Slug); + value.Name = "New Name"; + value.Images = new Dictionary + { + [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() + { + People value = await _repository.Get(TestSample.Get().Slug); + value.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + }; + 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); + } + + [Fact] + public async Task AddMetadataTest() + { + People value = await _repository.Get(TestSample.Get().Slug); + value.ExternalIDs = new List + { + new() + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + }; + 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); + } + + value.ExternalIDs.Add(new MetadataID + { + Provider = TestSample.GetNew(), + Link = "link", + DataID = "id" + }); + 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); + } + } + + [Theory] + [InlineData("Me")] + [InlineData("me")] + [InlineData("na")] + public async Task SearchTest(string query) + { + People value = new() + { + Slug = "slug", + Name = "name", + }; + await _repository.Create(value); + ICollection ret = await _repository.Search(query); + KAssert.DeepEqual(value, ret.First()); + } } } \ No newline at end of file diff --git a/tests/Kyoo.Tests/Database/TestSample.cs b/tests/Kyoo.Tests/Database/TestSample.cs index 423a5c78..6f9a69ff 100644 --- a/tests/Kyoo.Tests/Database/TestSample.cs +++ b/tests/Kyoo.Tests/Database/TestSample.cs @@ -104,6 +104,20 @@ namespace Kyoo.Tests [Images.Logo] = "logo" } } + }, + { + typeof(People), + () => new People + { + ID = 2, + Slug = "new-person-name", + Name = "New person name", + Images = new Dictionary + { + [Images.Logo] = "Old Logo", + [Images.Poster] = "Old poster" + } + } } }; From 7f2ef68d04634b1aaac94d0c2838433d0acaf41f Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 2 Aug 2021 00:53:46 +0200 Subject: [PATCH 23/34] Fixing tests CI --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 65a3646f..a185dd3b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,7 +24,7 @@ jobs: - name: Build run: | dotnet build --no-restore '-p:SkipWebApp=true;SkipTranscoder=true' -p:CopyLocalLockFileAssemblies=true - cp ./Kyoo.Common/bin/Debug/net5.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll ./Kyoo.Tests/bin/Debug/net5.0/ + cp ./Kyoo.Common/bin/Debug/net5.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll ./tests/Kyoo.Tests/bin/Debug/net5.0/ - name: Test run: dotnet test --no-build '-p:CollectCoverage=true;CoverletOutputFormat=opencover' env: From 924d03291cd48d293c3e098c9b6d1a4e468aa01f Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 2 Aug 2021 00:57:58 +0200 Subject: [PATCH 24/34] CI: Fixing test sanitize --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a185dd3b..7c86b7b0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,7 +33,7 @@ jobs: POSTGRES_PASSWORD: postgres - name: Sanitize coverage output if: ${{ always() }} - run: sed -i "s'$(pwd)'.'" Kyoo.Tests/coverage.opencover.xml + run: sed -i "s'$(pwd)'.'" tests/Kyoo.Tests/coverage.opencover.xml - name: Upload coverage report if: ${{ always() }} uses: actions/upload-artifact@v2 From 87e1b5aca1cd763da1de987768e078843133d6af Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 4 Aug 2021 00:11:18 +0200 Subject: [PATCH 25/34] FileSystem: Reworking the extra directory --- Kyoo.Common/Controllers/IFileSystem.cs | 9 +-- Kyoo.Common/Controllers/ILibraryManager.cs | 48 ++++++++----- .../Implementations/LibraryManager.cs | 67 ++++++++++--------- .../FileSystems/FileSystemComposite.cs | 56 ++++++++++++++-- .../Controllers/FileSystems/HttpFileSystem.cs | 9 ++- .../FileSystems/LocalFileSystem.cs | 31 +++++++-- Kyoo/Controllers/ThumbnailsManager.cs | 54 +-------------- Kyoo/Controllers/Transcoder.cs | 6 +- Kyoo/Models/Options/BasicOptions.cs | 27 +++++--- Kyoo/Views/ShowApi.cs | 4 +- Kyoo/settings.json | 6 +- 11 files changed, 182 insertions(+), 135 deletions(-) diff --git a/Kyoo.Common/Controllers/IFileSystem.cs b/Kyoo.Common/Controllers/IFileSystem.cs index 2fb1c6d7..f0702e04 100644 --- a/Kyoo.Common/Controllers/IFileSystem.cs +++ b/Kyoo.Common/Controllers/IFileSystem.cs @@ -81,12 +81,13 @@ namespace Kyoo.Controllers public Task Exists([NotNull] string path); /// - /// Get the extra directory of a show. + /// Get the extra directory of a resource . /// This method is in this system to allow a filesystem to use a different metadata policy for one. /// It can be useful if the filesystem is readonly. /// - /// The show to proceed - /// The extra directory of the show - public string GetExtraDirectory([NotNull] Show show); + /// The resource to proceed + /// The type of the resource. + /// The extra directory of the resource. + public Task GetExtraDirectory([NotNull] T resource); } } \ No newline at end of file diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 938d3029..8c3442e3 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -225,42 +225,51 @@ namespace Kyoo.Controllers /// /// The source object. /// A getter function for the member to load + /// + /// true if you want to load the relation even if it is not null, false otherwise. + /// /// The type of the source object /// The related resource's type /// The param - /// - /// - /// - Task Load([NotNull] T obj, Expression> member) + /// + /// + /// + Task Load([NotNull] T obj, Expression> member, bool force = false) where T : class, IResource - where T2 : class, IResource, new(); + where T2 : class, IResource; /// /// Load a collection of related resource /// /// The source object. /// A getter function for the member to load + /// + /// true if you want to load the relation even if it is not null, false otherwise. + /// /// The type of the source object /// The related resource's type /// The param - /// - /// - /// - Task Load([NotNull] T obj, Expression>> member) + /// + /// + /// + Task Load([NotNull] T obj, Expression>> member, bool force = false) where T : class, IResource - where T2 : class, new(); + where T2 : class; /// /// Load a related resource by it's name /// /// The source object. /// The name of the resource to load (case sensitive) + /// + /// true if you want to load the relation even if it is not null, false otherwise. + /// /// The type of the source object /// The param - /// - /// - /// - Task Load([NotNull] T obj, string memberName) + /// + /// + /// + Task Load([NotNull] T obj, string memberName, bool force = false) where T : class, IResource; /// @@ -268,10 +277,13 @@ namespace Kyoo.Controllers /// /// The source object. /// The name of the resource to load (case sensitive) - /// - /// - /// - Task Load([NotNull] IResource obj, string memberName); + /// + /// true if you want to load the relation even if it is not null, false otherwise. + /// + /// + /// + /// + Task Load([NotNull] IResource obj, string memberName, bool force = false); /// /// Get items (A wrapper arround shows or collections) from a library. diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 5b051ed8..08475c32 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -162,34 +162,6 @@ namespace Kyoo.Controllers return await EpisodeRepository.GetOrDefault(showSlug, seasonNumber, episodeNumber); } - /// - public Task Load(T obj, Expression> member) - where T : class, IResource - where T2 : class, IResource, new() - { - if (member == null) - throw new ArgumentNullException(nameof(member)); - return Load(obj, Utility.GetPropertyName(member)); - } - - /// - public Task Load(T obj, Expression>> member) - where T : class, IResource - where T2 : class, new() - { - if (member == null) - throw new ArgumentNullException(nameof(member)); - return Load(obj, Utility.GetPropertyName(member)); - } - - /// - public async Task Load(T obj, string memberName) - where T : class, IResource - { - await Load(obj as IResource, memberName); - return obj; - } - /// /// Set relations between to objects. /// @@ -211,11 +183,46 @@ namespace Kyoo.Controllers } /// - public Task Load(IResource obj, string memberName) + public Task Load(T obj, Expression> member, bool force = false) + where T : class, IResource + where T2 : class, IResource + { + if (member == null) + throw new ArgumentNullException(nameof(member)); + return Load(obj, Utility.GetPropertyName(member), force); + } + + /// + public Task Load(T obj, Expression>> member, bool force = false) + where T : class, IResource + where T2 : class + { + if (member == null) + throw new ArgumentNullException(nameof(member)); + return Load(obj, Utility.GetPropertyName(member), force); + } + + /// + public async Task Load(T obj, string memberName, bool force = false) + where T : class, IResource + { + await Load(obj as IResource, memberName, force); + return obj; + } + + /// + public Task Load(IResource obj, string memberName, bool force = false) { if (obj == null) throw new ArgumentNullException(nameof(obj)); - + + object existingValue = obj.GetType() + .GetProperties() + .FirstOrDefault(x => string.Equals(x.Name, memberName, StringComparison.InvariantCultureIgnoreCase)) + ?.GetValue(obj); + if (existingValue != null && !force) + return Task.CompletedTask; + return (obj, member: memberName) switch { (Library l, nameof(Library.Providers)) => ProviderRepository diff --git a/Kyoo/Controllers/FileSystems/FileSystemComposite.cs b/Kyoo/Controllers/FileSystems/FileSystemComposite.cs index da98539f..c7bda72a 100644 --- a/Kyoo/Controllers/FileSystems/FileSystemComposite.cs +++ b/Kyoo/Controllers/FileSystems/FileSystemComposite.cs @@ -8,7 +8,9 @@ using Autofac.Features.Metadata; using JetBrains.Annotations; using Kyoo.Common.Models.Attributes; using Kyoo.Models; +using Kyoo.Models.Options; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; namespace Kyoo.Controllers { @@ -23,14 +25,31 @@ namespace Kyoo.Controllers /// private readonly ICollection, FileSystemMetadataAttribute>> _fileSystems; + /// + /// The library manager used to load shows to retrieve their path + /// (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. /// /// The list of filesystem mapped to their metadata. - public FileSystemComposite(ICollection, FileSystemMetadataAttribute>> fileSystems) + /// The library manager used to load shows to retrieve their path. + /// The options to use. + public FileSystemComposite(ICollection, FileSystemMetadataAttribute>> fileSystems, + ILibraryManager libraryManager, + IOptionsMonitor options) { _fileSystems = fileSystems; + _libraryManager = libraryManager; + _options = options; } @@ -132,12 +151,37 @@ namespace Kyoo.Controllers } /// - public string GetExtraDirectory(Show show) + public async Task GetExtraDirectory(T resource) { - if (show == null) - throw new ArgumentNullException(nameof(show)); - return _GetFileSystemForPath(show.Path, out string _) - .GetExtraDirectory(show); + switch (resource) + { + case Season season: + await _libraryManager.Load(season, x => x.Show); + break; + case Episode episode: + await _libraryManager.Load(episode, x => x.Show); + break; + case Track track: + await _libraryManager.Load(track, x => x.Episode); + await _libraryManager.Load(track.Episode, x => x.Show); + break; + } + + IFileSystem fs = resource switch + { + Show show => _GetFileSystemForPath(show.Path, out string _), + Season season => _GetFileSystemForPath(season.Show.Path, out string _), + Episode episode => _GetFileSystemForPath(episode.Show.Path, out string _), + Track track => _GetFileSystemForPath(track.Episode.Show.Path, out string _), + _ => _GetFileSystemForPath(_options.CurrentValue.MetadataPath, out string _) + }; + string path = await fs.GetExtraDirectory(resource); + if (resource is IResource res) + path ??= Combine(_options.CurrentValue.MetadataPath, res.Slug, typeof(T).Name); + else + path ??= Combine(_options.CurrentValue.MetadataPath, typeof(T).Name); + await CreateDirectory(path); + return path; } } } \ No newline at end of file diff --git a/Kyoo/Controllers/FileSystems/HttpFileSystem.cs b/Kyoo/Controllers/FileSystems/HttpFileSystem.cs index 3d2ea991..5eeebbc2 100644 --- a/Kyoo/Controllers/FileSystems/HttpFileSystem.cs +++ b/Kyoo/Controllers/FileSystems/HttpFileSystem.cs @@ -76,7 +76,7 @@ namespace Kyoo.Controllers } /// - public string GetExtraDirectory(Show show) + public Task GetExtraDirectory(T resource) { throw new NotSupportedException("Extras can not be stored inside an http filesystem."); } @@ -117,7 +117,12 @@ namespace Kyoo.Controllers public Task ExecuteResultAsync(ActionContext context) { // TODO implement that, example: https://github.com/twitchax/AspNetCore.Proxy/blob/14dd0f212d7abb43ca1bf8c890d5efb95db66acb/src/Core/Extensions/Http.cs#L15 - throw new NotImplementedException(); + + // Silence unused warnings + if (_path != null || _rangeSupport || _type == null) + throw new NotImplementedException(); + else + throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/Kyoo/Controllers/FileSystems/LocalFileSystem.cs b/Kyoo/Controllers/FileSystems/LocalFileSystem.cs index 3239c55f..9c085582 100644 --- a/Kyoo/Controllers/FileSystems/LocalFileSystem.cs +++ b/Kyoo/Controllers/FileSystems/LocalFileSystem.cs @@ -4,8 +4,10 @@ using System.IO; using System.Threading.Tasks; using Kyoo.Common.Models.Attributes; using Kyoo.Models; +using Kyoo.Models.Options; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.Options; namespace Kyoo.Controllers { @@ -20,6 +22,20 @@ namespace Kyoo.Controllers /// private FileExtensionContentTypeProvider _provider; + /// + /// 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 with the specified options. + /// + /// The options to use. + public LocalFileSystem(IOptionsMonitor options) + { + _options = options; + } + /// /// Get the content type of a file using it's extension. /// @@ -104,11 +120,18 @@ namespace Kyoo.Controllers } /// - public string GetExtraDirectory(Show show) + public Task GetExtraDirectory(T resource) { - string path = Path.Combine(show.Path, "Extra"); - Directory.CreateDirectory(path); - return path; + if (!_options.CurrentValue.MetadataInShow) + return null; + return Task.FromResult(resource switch + { + Show show => Combine(show.Path, "Extra"), + Season season => Combine(season.Show.Path, "Extra", "Season"), + Episode episode => Combine(episode.Show.Path, "Extra", "Episode"), + Track track => Combine(track.Episode.Show.Path, "Track"), + _ => null + }); } } } \ No newline at end of file diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index 3f6ffe3a..f6be0171 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -3,9 +3,7 @@ using System; using System.IO; using System.Linq; using System.Threading.Tasks; -using Kyoo.Models.Options; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; namespace Kyoo.Controllers { @@ -22,37 +20,17 @@ namespace Kyoo.Controllers /// A logger to report errors. /// private readonly ILogger _logger; - /// - /// The options containing the base path of people images and provider logos. - /// - private readonly IOptionsMonitor _options; - /// - /// A library manager used to load episode and seasons shows if they are not loaded. - /// - private readonly Lazy _library; /// /// Create a new . /// /// The file manager to use. /// A logger to report errors - /// The options to use. - /// A library manager used to load shows if they are not loaded. public ThumbnailsManager(IFileSystem files, - ILogger logger, - IOptionsMonitor options, - Lazy library) + ILogger logger) { _files = files; _logger = logger; - _options = options; - _library = library; - - options.OnChange(x => - { - _files.CreateDirectory(x.PeoplePath); - _files.CreateDirectory(x.ProviderPath); - }); } /// @@ -119,35 +97,7 @@ namespace Kyoo.Controllers _ => $"{imageID}.jpg" }; - // TODO implement a generic way, probably need to rework IFileManager.GetExtraDirectory too. - switch (item) - { - case Show show: - return _files.Combine(_files.GetExtraDirectory(show), imageName); - - case Season season: - if (season.Show == null) - await _library.Value.Load(season, x => x.Show); - return _files.Combine( - _files.GetExtraDirectory(season.Show!), - $"season-{season.SeasonNumber}-{imageName}"); - - case Episode episode: - if (episode.Show == null) - await _library.Value.Load(episode, x => x.Show); - string dir = _files.Combine(_files.GetExtraDirectory(episode.Show!), "Thumbnails"); - await _files.CreateDirectory(dir); - return _files.Combine(dir, $"{Path.GetFileNameWithoutExtension(episode.Path)}-{imageName}"); - - case People actor: - return _files.Combine(_options.CurrentValue.PeoplePath, $"{actor.Slug}-{imageName}"); - - case Provider provider: - return _files.Combine(_options.CurrentValue.ProviderPath, $"{provider.Slug}-{imageName}"); - - default: - throw new NotSupportedException($"The type {typeof(T).Name} is not supported."); - } + return _files.Combine(await _files.GetExtraDirectory(item), imageName); } } } diff --git a/Kyoo/Controllers/Transcoder.cs b/Kyoo/Controllers/Transcoder.cs index 47291753..5942cbd2 100644 --- a/Kyoo/Controllers/Transcoder.cs +++ b/Kyoo/Controllers/Transcoder.cs @@ -88,10 +88,8 @@ namespace Kyoo.Controllers public async Task ExtractInfos(Episode episode, bool reextract) { - if (episode.Show == null) - await _library.Value.Load(episode, x => x.Show); - - string dir = _files.GetExtraDirectory(episode.Show); + await _library.Value.Load(episode, x => x.Show); + string dir = await _files.GetExtraDirectory(episode.Show); if (dir == null) throw new ArgumentException("Invalid path."); return await Task.Factory.StartNew( diff --git a/Kyoo/Models/Options/BasicOptions.cs b/Kyoo/Models/Options/BasicOptions.cs index 95d4873a..60e95ee2 100644 --- a/Kyoo/Models/Options/BasicOptions.cs +++ b/Kyoo/Models/Options/BasicOptions.cs @@ -25,16 +25,6 @@ namespace Kyoo.Models.Options /// public string PluginPath { get; set; } = "plugins/"; - /// - /// The path of the people pictures. - /// - public string PeoplePath { get; set; } = "people/"; - - /// - /// The path of providers icons. - /// - public string ProviderPath { get; set; } = "providers/"; - /// /// The temporary folder to cache transmuxed file. /// @@ -44,5 +34,22 @@ namespace Kyoo.Models.Options /// The temporary folder to cache transcoded file. /// public string TranscodePath { get; set; } = "cached/transcode"; + + /// + /// true if the metadata of a show/season/episode should be stored in the same directory as video files, + /// false to save them in a kyoo specific directory. + /// + /// + /// Some file systems might discard this option to store them somewhere else. + /// For example, readonly file systems will probably store them in a kyoo specific directory. + /// + public bool MetadataInShow { get; set; } = true; + + /// + /// The path for metadata if they are not stored near show (see ). + /// Some resources can't be stored near a show and they are stored in this directory + /// (like ). + /// + public string MetadataPath { get; set; } = "metadata/"; } } \ No newline at end of file diff --git a/Kyoo/Views/ShowApi.cs b/Kyoo/Views/ShowApi.cs index df12c876..0c8dde65 100644 --- a/Kyoo/Views/ShowApi.cs +++ b/Kyoo/Views/ShowApi.cs @@ -383,7 +383,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(slug); - string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments"); + string path = _files.Combine(await _files.GetExtraDirectory(show), "Attachments"); return (await _files.ListFiles(path)) .ToDictionary(Path.GetFileNameWithoutExtension, x => $"{BaseURL}api/shows/{slug}/fonts/{Path.GetFileName(x)}"); @@ -402,7 +402,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(showSlug); - string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments", slug); + string path = _files.Combine(await _files.GetExtraDirectory(show), "Attachments", slug); return _files.FileResult(path); } catch (ItemNotFoundException) diff --git a/Kyoo/settings.json b/Kyoo/settings.json index 8529a35a..1ddc8cf1 100644 --- a/Kyoo/settings.json +++ b/Kyoo/settings.json @@ -3,10 +3,10 @@ "url": "http://*:5000", "publicUrl": "http://localhost:5000/", "pluginsPath": "plugins/", - "peoplePath": "people/", - "providerPath": "providers/", "transmuxPath": "cached/transmux", - "transcodePath": "cached/transcode" + "transcodePath": "cached/transcode", + "metadataInShow": true, + "metadataPath": "metadata/" }, "database": { From b60046eddcd930caa3457ae3208b3d4cfb729995 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 4 Aug 2021 20:19:31 +0200 Subject: [PATCH 26/34] FileSystem: Using a single folder for shows-content --- .../FileSystems/FileSystemComposite.cs | 15 +++++++++------ Kyoo/Controllers/FileSystems/LocalFileSystem.cs | 6 +++--- Kyoo/Controllers/ThumbnailsManager.cs | 11 ++++++++++- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Kyoo/Controllers/FileSystems/FileSystemComposite.cs b/Kyoo/Controllers/FileSystems/FileSystemComposite.cs index c7bda72a..20a7f67d 100644 --- a/Kyoo/Controllers/FileSystems/FileSystemComposite.cs +++ b/Kyoo/Controllers/FileSystems/FileSystemComposite.cs @@ -175,12 +175,15 @@ namespace Kyoo.Controllers Track track => _GetFileSystemForPath(track.Episode.Show.Path, out string _), _ => _GetFileSystemForPath(_options.CurrentValue.MetadataPath, out string _) }; - string path = await fs.GetExtraDirectory(resource); - if (resource is IResource res) - path ??= Combine(_options.CurrentValue.MetadataPath, res.Slug, typeof(T).Name); - else - path ??= Combine(_options.CurrentValue.MetadataPath, typeof(T).Name); - await CreateDirectory(path); + string path = await fs.GetExtraDirectory(resource) + ?? resource switch + { + Season season => await GetExtraDirectory(season.Show), + Episode episode => await GetExtraDirectory(episode.Show), + Track track => await GetExtraDirectory(track.Episode), + IResource res => Combine(_options.CurrentValue.MetadataPath, typeof(T).Name, res.Slug), + _ => Combine(_options.CurrentValue.MetadataPath, typeof(T).Name) + }; return path; } } diff --git a/Kyoo/Controllers/FileSystems/LocalFileSystem.cs b/Kyoo/Controllers/FileSystems/LocalFileSystem.cs index 9c085582..71dac41d 100644 --- a/Kyoo/Controllers/FileSystems/LocalFileSystem.cs +++ b/Kyoo/Controllers/FileSystems/LocalFileSystem.cs @@ -127,9 +127,9 @@ namespace Kyoo.Controllers return Task.FromResult(resource switch { Show show => Combine(show.Path, "Extra"), - Season season => Combine(season.Show.Path, "Extra", "Season"), - Episode episode => Combine(episode.Show.Path, "Extra", "Episode"), - Track track => Combine(track.Episode.Show.Path, "Track"), + Season season => Combine(season.Show.Path, "Extra"), + Episode episode => Combine(episode.Show.Path, "Extra"), + Track track => Combine(track.Episode.Show.Path, "Extra"), _ => null }); } diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index f6be0171..142ee236 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -94,9 +94,18 @@ namespace Kyoo.Controllers Images.Poster => "poster.jpg", Images.Logo => "logo.jpg", Images.Thumbnail => "thumbnail.jpg", + Images.Trailer => "trailer.mp4", _ => $"{imageID}.jpg" }; - + + imageName = item switch + { + Season season => $"season-{season.SeasonNumber}-{imageName}", + Episode episode => _files.Combine("Thumbnails", + $"{Path.GetFileNameWithoutExtension(episode.Path)}-{imageName}"), + _ => imageName + }; + return _files.Combine(await _files.GetExtraDirectory(item), imageName); } } From 1a33f38384d9c075822569fd5a9d7d818091e12f Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 4 Aug 2021 23:43:17 +0200 Subject: [PATCH 27/34] FileSystem: Creating an api to retrieve a file mime type --- Kyoo.Common/Controllers/IFileSystem.cs | 26 ++++++++++- Kyoo.Common/Controllers/IThumbnailsManager.cs | 8 ++-- .../FileSystems/FileSystemComposite.cs | 16 +++++-- .../Controllers/FileSystems/HttpFileSystem.cs | 11 ++++- .../FileSystems/LocalFileSystem.cs | 10 +++++ Kyoo/Controllers/ThumbnailsManager.cs | 43 +++++++++++++------ 6 files changed, 91 insertions(+), 23 deletions(-) diff --git a/Kyoo.Common/Controllers/IFileSystem.cs b/Kyoo.Common/Controllers/IFileSystem.cs index f0702e04..92e951b7 100644 --- a/Kyoo.Common/Controllers/IFileSystem.cs +++ b/Kyoo.Common/Controllers/IFileSystem.cs @@ -2,11 +2,25 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using JetBrains.Annotations; -using Kyoo.Models; using Microsoft.AspNetCore.Mvc; namespace Kyoo.Controllers { + /// + /// A class wrapping a value that will be set after the completion of the task it is related to. + /// + /// + /// This class replace the use of an out parameter on a task since tasks and out can't be combined. + /// + /// The type of the value + public class AsyncRef + { + /// + /// The value that will be set before the completion of the task. + /// + public T Value { get; set; } + } + /// /// A service to abstract the file system to allow custom file systems (like distant file systems or external providers) /// @@ -42,6 +56,16 @@ namespace Kyoo.Controllers /// If the file could not be found. /// A reader to read the file. public Task GetReader([NotNull] string path); + + /// + /// Read a file present at . The reader can be used in an arbitrary context. + /// To return files from an http endpoint, use . + /// + /// The path of the file + /// The mime type of the opened file. + /// If the file could not be found. + /// A reader to read the file. + public Task GetReader([NotNull] string path, AsyncRef mime); /// /// Create a new file at . diff --git a/Kyoo.Common/Controllers/IThumbnailsManager.cs b/Kyoo.Common/Controllers/IThumbnailsManager.cs index aac09210..61fe5345 100644 --- a/Kyoo.Common/Controllers/IThumbnailsManager.cs +++ b/Kyoo.Common/Controllers/IThumbnailsManager.cs @@ -1,5 +1,4 @@ -using System; -using Kyoo.Models; +using Kyoo.Models; using System.Threading.Tasks; using JetBrains.Annotations; @@ -27,13 +26,12 @@ namespace Kyoo.Controllers /// - /// Retrieve the local path of the poster of the given item. + /// Retrieve the local path of an image of the given item. /// /// The item to retrieve the poster from. /// The ID of the image. See for values. /// The type of the item - /// If the type does not have a poster - /// The path of the poster for the given resource (it might or might not exists). + /// The path of the image for the given resource or null if it does not exists. Task GetImagePath([NotNull] T item, int imageID) where T : IThumbnails; } diff --git a/Kyoo/Controllers/FileSystems/FileSystemComposite.cs b/Kyoo/Controllers/FileSystems/FileSystemComposite.cs index 20a7f67d..b3f4f66a 100644 --- a/Kyoo/Controllers/FileSystems/FileSystemComposite.cs +++ b/Kyoo/Controllers/FileSystems/FileSystemComposite.cs @@ -107,6 +107,15 @@ namespace Kyoo.Controllers .GetReader(relativePath); } + /// + public Task GetReader(string path, AsyncRef mime) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + return _GetFileSystemForPath(path, out string relativePath) + .GetReader(relativePath, mime); + } + /// public Task NewFile(string path) { @@ -181,10 +190,11 @@ namespace Kyoo.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, typeof(T).Name, res.Slug), - _ => Combine(_options.CurrentValue.MetadataPath, typeof(T).Name) + IResource res => Combine(_options.CurrentValue.MetadataPath, + typeof(T).Name.ToLowerInvariant(), res.Slug), + _ => Combine(_options.CurrentValue.MetadataPath, typeof(T).Name.ToLowerInvariant()) }; - return path; + return await CreateDirectory(path); } } } \ No newline at end of file diff --git a/Kyoo/Controllers/FileSystems/HttpFileSystem.cs b/Kyoo/Controllers/FileSystems/HttpFileSystem.cs index 5eeebbc2..82409cdc 100644 --- a/Kyoo/Controllers/FileSystems/HttpFileSystem.cs +++ b/Kyoo/Controllers/FileSystems/HttpFileSystem.cs @@ -4,7 +4,6 @@ using System.IO; using System.Net.Http; using System.Threading.Tasks; using Kyoo.Common.Models.Attributes; -using Kyoo.Models; using Microsoft.AspNetCore.Mvc; namespace Kyoo.Controllers @@ -44,6 +43,16 @@ namespace Kyoo.Controllers HttpClient client = _clientFactory.CreateClient(); return client.GetStreamAsync(path); } + + /// + public async Task GetReader(string path, AsyncRef mime) + { + HttpClient client = _clientFactory.CreateClient(); + HttpResponseMessage response = await client.GetAsync(path); + response.EnsureSuccessStatusCode(); + mime.Value = response.Content.Headers.ContentType?.MediaType; + return await response.Content.ReadAsStreamAsync(); + } /// public Task NewFile(string path) diff --git a/Kyoo/Controllers/FileSystems/LocalFileSystem.cs b/Kyoo/Controllers/FileSystems/LocalFileSystem.cs index 71dac41d..18d887a0 100644 --- a/Kyoo/Controllers/FileSystems/LocalFileSystem.cs +++ b/Kyoo/Controllers/FileSystems/LocalFileSystem.cs @@ -78,6 +78,16 @@ namespace Kyoo.Controllers throw new ArgumentNullException(nameof(path)); return Task.FromResult(File.OpenRead(path)); } + + /// + public Task GetReader(string path, AsyncRef mime) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + _provider.TryGetContentType(path, out string mimeValue); + mime.Value = mimeValue; + return Task.FromResult(File.OpenRead(path)); + } /// public Task NewFile(string path) diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index 142ee236..b56625bd 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -47,7 +47,9 @@ namespace Kyoo.Controllers try { - await using Stream reader = await _files.GetReader(url); + AsyncRef mime = new(); + await using Stream reader = await _files.GetReader(url, mime); + // TODO use this mime type to guess the file extension. await using Stream local = await _files.NewFile(localPath); await reader.CopyToAsync(local); return true; @@ -58,7 +60,7 @@ namespace Kyoo.Controllers return false; } } - + /// public async Task DownloadImages(T item, bool alwaysDownload = false) where T : IThumbnails @@ -74,28 +76,32 @@ namespace Kyoo.Controllers foreach ((int id, string image) in item.Images.Where(x => x.Value != null)) { - string localPath = await GetImagePath(item, id); + string localPath = await _GetPrivateImagePath(item, id); if (alwaysDownload || !await _files.Exists(localPath)) ret |= await _DownloadImage(image, localPath, $"The image n°{id} of {name}"); } return ret; } - - /// - public async Task GetImagePath(T item, int imageID) - where T : IThumbnails + + /// + /// Retrieve the local path of an image of the given item without an extension. + /// + /// The item to retrieve the poster from. + /// The ID of the image. See for values. + /// The type of the item + /// The path of the image for the given resource, even if it does not exists + private async Task _GetPrivateImagePath(T item, int imageID) { if (item == null) throw new ArgumentNullException(nameof(item)); - // TODO handle extensions string imageName = imageID switch { - Images.Poster => "poster.jpg", - Images.Logo => "logo.jpg", - Images.Thumbnail => "thumbnail.jpg", - Images.Trailer => "trailer.mp4", - _ => $"{imageID}.jpg" + Images.Poster => "poster", + Images.Logo => "logo", + Images.Thumbnail => "thumbnail", + Images.Trailer => "trailer", + _ => $"{imageID}" }; imageName = item switch @@ -108,5 +114,16 @@ namespace Kyoo.Controllers return _files.Combine(await _files.GetExtraDirectory(item), imageName); } + + /// + public async Task GetImagePath(T item, int imageID) + where T : IThumbnails + { + string basePath = await _GetPrivateImagePath(item, imageID); + string directory = Path.GetDirectoryName(basePath); + string baseFile = Path.GetFileName(basePath); + return (await _files.ListFiles(directory!)) + .FirstOrDefault(x => Path.GetFileNameWithoutExtension(x) == baseFile); + } } } From 559160ad7bb2dc20e290c804b80d9fa7f9b45b1d Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 5 Aug 2021 15:41:11 +0200 Subject: [PATCH 28/34] ThumbnailManager: Handling different file extensions --- Kyoo/Controllers/ThumbnailsManager.cs | 28 +++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index b56625bd..23469dcf 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -3,6 +3,7 @@ using System; using System.IO; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Logging; namespace Kyoo.Controllers @@ -49,8 +50,10 @@ namespace Kyoo.Controllers { AsyncRef mime = new(); await using Stream reader = await _files.GetReader(url, mime); - // TODO use this mime type to guess the file extension. - await using Stream local = await _files.NewFile(localPath); + string extension = new FileExtensionContentTypeProvider() + .Mappings.FirstOrDefault(x => x.Value == mime.Value) + .Key; + await using Stream local = await _files.NewFile(localPath + extension); await reader.CopyToAsync(local); return true; } @@ -95,6 +98,8 @@ namespace Kyoo.Controllers { if (item == null) throw new ArgumentNullException(nameof(item)); + + string directory = await _files.GetExtraDirectory(item); string imageName = imageID switch { Images.Poster => "poster", @@ -103,16 +108,19 @@ namespace Kyoo.Controllers Images.Trailer => "trailer", _ => $"{imageID}" }; - - imageName = item switch + + switch (item) { - Season season => $"season-{season.SeasonNumber}-{imageName}", - Episode episode => _files.Combine("Thumbnails", - $"{Path.GetFileNameWithoutExtension(episode.Path)}-{imageName}"), - _ => imageName - }; + case Season season: + imageName = $"season-{season.SeasonNumber}-{imageName}"; + break; + case Episode episode: + directory = await _files.CreateDirectory(_files.Combine(directory, "Thumbnails")); + imageName = $"{Path.GetFileNameWithoutExtension(episode.Path)}-{imageName}"; + break; + } - return _files.Combine(await _files.GetExtraDirectory(item), imageName); + return _files.Combine(directory, imageName); } /// From bb3e912ffab3888341a10338eb3c56c142052e46 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 5 Aug 2021 22:38:21 +0200 Subject: [PATCH 29/34] Fixing episodes, people and collection images --- Kyoo.Common/Models/Resources/Episode.cs | 2 +- Kyoo/Tasks/RegisterEpisode.cs | 7 ++++ Kyoo/Views/CollectionApi.cs | 55 ++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index eddb86ba..dd1f62e1 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -111,7 +111,7 @@ namespace Kyoo.Models /// By default, the http path for the thumbnail is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/episodes/{Slug}/thumb")] + [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); diff --git a/Kyoo/Tasks/RegisterEpisode.cs b/Kyoo/Tasks/RegisterEpisode.cs index 1876d9c5..f939b5f5 100644 --- a/Kyoo/Tasks/RegisterEpisode.cs +++ b/Kyoo/Tasks/RegisterEpisode.cs @@ -174,6 +174,13 @@ namespace Kyoo.Tasks item = await _metadataProvider.Get(item); await _thumbnailsManager.DownloadImages(item); + + if (item is Show show && show.People != null) + { + foreach (PeopleRole role in show.People) + await _thumbnailsManager.DownloadImages(role.People); + } + return await _libraryManager.CreateIfNotExists(item); } } diff --git a/Kyoo/Views/CollectionApi.cs b/Kyoo/Views/CollectionApi.cs index 783bb67b..9c26a65c 100644 --- a/Kyoo/Views/CollectionApi.cs +++ b/Kyoo/Views/CollectionApi.cs @@ -6,6 +6,7 @@ using Kyoo.Models; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; using Kyoo.CommonApi; +using Kyoo.Models.Exceptions; using Kyoo.Models.Options; using Kyoo.Models.Permissions; using Microsoft.Extensions.Options; @@ -19,11 +20,18 @@ namespace Kyoo.Api public class CollectionApi : CrudApi { private readonly ILibraryManager _libraryManager; - - public CollectionApi(ILibraryManager libraryManager, IOptions options) + private readonly IFileSystem _files; + private readonly IThumbnailsManager _thumbs; + + public CollectionApi(ILibraryManager libraryManager, + IFileSystem files, + IThumbnailsManager thumbs, + IOptions options) : base(libraryManager.CollectionRepository, options.Value.PublicUrl) { _libraryManager = libraryManager; + _files = files; + _thumbs = thumbs; } [HttpGet("{id:int}/show")] @@ -129,5 +137,48 @@ namespace Kyoo.Api return BadRequest(new {Error = ex.Message}); } } + + [HttpGet("{slug}/poster")] + public async Task GetPoster(string slug) + { + try + { + Collection collection = await _libraryManager.Get(slug); + return _files.FileResult(await _thumbs.GetImagePath(collection, Images.Poster)); + } + catch (ItemNotFoundException) + { + return NotFound(); + } + } + + [HttpGet("{slug}/logo")] + public async Task GetLogo(string slug) + { + try + { + Collection collection = await _libraryManager.Get(slug); + return _files.FileResult(await _thumbs.GetImagePath(collection, Images.Logo)); + } + catch (ItemNotFoundException) + { + return NotFound(); + } + } + + [HttpGet("{slug}/backdrop")] + [HttpGet("{slug}/thumbnail")] + public async Task GetBackdrop(string slug) + { + try + { + Collection collection = await _libraryManager.Get(slug); + return _files.FileResult(await _thumbs.GetImagePath(collection, Images.Thumbnail)); + } + catch (ItemNotFoundException) + { + return NotFound(); + } + } } } \ No newline at end of file From 6632ac5f1b74e770147b7d036d3c37256b1276a5 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 5 Aug 2021 23:08:26 +0200 Subject: [PATCH 30/34] TheMovieDB: Handling year search/get --- Kyoo.TheMovieDb/ProviderTmdb.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Kyoo.TheMovieDb/ProviderTmdb.cs b/Kyoo.TheMovieDb/ProviderTmdb.cs index 5085b3b1..08bc36ea 100644 --- a/Kyoo.TheMovieDb/ProviderTmdb.cs +++ b/Kyoo.TheMovieDb/ProviderTmdb.cs @@ -95,7 +95,8 @@ namespace Kyoo.TheMovieDb { if (!show.TryGetID(Provider.Slug, out int id)) { - Show found = (await _SearchShows(show.Title ?? show.Slug)).FirstOrDefault(); + Show found = (await _SearchShows(show.Title ?? show.Slug, show.StartAir?.Year)) + .FirstOrDefault(x => x.IsMovie == show.IsMovie); if (found?.TryGetID(Provider.Slug, out id) != true) return found; } @@ -228,11 +229,12 @@ namespace Kyoo.TheMovieDb /// Search for a show using it's name as a query. /// /// The query to search for + /// The year in witch the show has aired. /// A list of shows containing metadata from TheMovieDb - private async Task> _SearchShows(string query) + private async Task> _SearchShows(string query, int? year = null) { TMDbClient client = new(_apiKey.Value.ApiKey); - return (await client.SearchMultiAsync(query)) + return (await client.SearchMultiAsync(query, year: year ?? 0)) .Results .Select(x => { From daa8d1f04a549f360a97634104d8c6a24b1b64a4 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 5 Aug 2021 23:35:10 +0200 Subject: [PATCH 31/34] TVDB: Fixing people images --- .../Convertors/CollectionConvertors.cs | 4 +-- .../Convertors/EpisodeConvertors.cs | 2 +- Kyoo.TheMovieDb/Convertors/MovieConvertors.cs | 4 +-- .../Convertors/PeopleConvertors.cs | 10 +++---- .../Convertors/SeasonConvertors.cs | 2 +- Kyoo.TheMovieDb/Convertors/ShowConvertors.cs | 4 +-- .../Convertors/StudioConvertors.cs | 4 +-- Kyoo.TheTvdb/Convertors.cs | 30 +++++++------------ Kyoo.TheTvdb/ProviderTvdb.cs | 2 +- .../Identifier/Tvdb/ConvertorTests.cs | 3 +- 10 files changed, 27 insertions(+), 38 deletions(-) diff --git a/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs b/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs index c5ab7104..3fe71a89 100644 --- a/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs @@ -17,7 +17,7 @@ namespace Kyoo.TheMovieDb /// The converted collection as a . public static Collection ToCollection(this TMDbLib.Objects.Collections.Collection collection, Provider provider) { - return new() + return new Collection { Slug = Utility.ToSlug(collection.Name), Name = collection.Name, @@ -51,7 +51,7 @@ namespace Kyoo.TheMovieDb /// The converted collection as a . public static Collection ToCollection(this SearchCollection collection, Provider provider) { - return new() + return new Collection { Slug = Utility.ToSlug(collection.Name), Name = collection.Name, diff --git a/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs b/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs index d1dd499b..75129af3 100644 --- a/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs @@ -18,7 +18,7 @@ namespace Kyoo.TheMovieDb /// The converted episode as a . public static Episode ToEpisode(this TvEpisode episode, int showID, Provider provider) { - return new() + return new Episode { SeasonNumber = episode.SeasonNumber, EpisodeNumber = episode.EpisodeNumber, diff --git a/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs b/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs index 6a0cf9ba..450f6934 100644 --- a/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs @@ -19,7 +19,7 @@ namespace Kyoo.TheMovieDb /// The converted movie as a . public static Show ToShow(this Movie movie, Provider provider) { - return new() + return new Show { Slug = Utility.ToSlug(movie.Title), Title = movie.Title, @@ -69,7 +69,7 @@ namespace Kyoo.TheMovieDb /// The converted movie as a . public static Show ToShow(this SearchMovie movie, Provider provider) { - return new() + return new Show { Slug = Utility.ToSlug(movie.Title), Title = movie.Title, diff --git a/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs b/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs index b3d47410..861f21e0 100644 --- a/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs @@ -22,7 +22,7 @@ namespace Kyoo.TheMovieDb /// A representing the movie cast. public static PeopleRole ToPeople(this MovieCast cast, Provider provider) { - return new() + return new PeopleRole { People = new People { @@ -57,7 +57,7 @@ namespace Kyoo.TheMovieDb /// A representing the movie cast. public static PeopleRole ToPeople(this TvCast cast, Provider provider) { - return new() + return new PeopleRole { People = new People { @@ -92,7 +92,7 @@ namespace Kyoo.TheMovieDb /// A representing the movie crew. public static PeopleRole ToPeople(this Crew crew, Provider provider) { - return new() + return new PeopleRole { People = new People { @@ -127,7 +127,7 @@ namespace Kyoo.TheMovieDb /// A representing the person. public static People ToPeople(this Person person, Provider provider) { - return new() + return new People { Slug = Utility.ToSlug(person.Name), Name = person.Name, @@ -157,7 +157,7 @@ namespace Kyoo.TheMovieDb /// A representing the person. public static People ToPeople(this SearchPerson person, Provider provider) { - return new() + return new People { Slug = Utility.ToSlug(person.Name), Name = person.Name, diff --git a/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs b/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs index 1dcc88a6..a1aa4e51 100644 --- a/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs @@ -18,7 +18,7 @@ namespace Kyoo.TheMovieDb /// The converted season as a . public static Season ToSeason(this TvSeason season, int showID, Provider provider) { - return new() + return new Season { SeasonNumber = season.SeasonNumber, Title = season.Name, diff --git a/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs b/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs index 7d1b69a0..342af8e0 100644 --- a/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs @@ -19,7 +19,7 @@ namespace Kyoo.TheMovieDb /// A converted as a . public static Show ToShow(this TvShow tv, Provider provider) { - return new() + return new Show { Slug = Utility.ToSlug(tv.Name), Title = tv.Name, @@ -68,7 +68,7 @@ namespace Kyoo.TheMovieDb /// A converted as a . public static Show ToShow(this SearchTv tv, Provider provider) { - return new() + return new Show { Slug = Utility.ToSlug(tv.Name), Title = tv.Name, diff --git a/Kyoo.TheMovieDb/Convertors/StudioConvertors.cs b/Kyoo.TheMovieDb/Convertors/StudioConvertors.cs index c8947981..9839d784 100644 --- a/Kyoo.TheMovieDb/Convertors/StudioConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/StudioConvertors.cs @@ -17,7 +17,7 @@ namespace Kyoo.TheMovieDb /// The converted company as a . public static Studio ToStudio(this Company company, Provider provider) { - return new() + return new Studio { Slug = Utility.ToSlug(company.Name), Name = company.Name, @@ -41,7 +41,7 @@ namespace Kyoo.TheMovieDb /// The converted company as a . public static Studio ToStudio(this SearchCompany company, Provider provider) { - return new() + return new Studio { Slug = Utility.ToSlug(company.Name), Name = company.Name, diff --git a/Kyoo.TheTvdb/Convertors.cs b/Kyoo.TheTvdb/Convertors.cs index 2e1f39cc..185bf0d1 100644 --- a/Kyoo.TheTvdb/Convertors.cs +++ b/Kyoo.TheTvdb/Convertors.cs @@ -48,7 +48,7 @@ namespace Kyoo.TheTvdb /// A show representing the given search result. public static Show ToShow(this SeriesSearchResult result, Provider provider) { - return new() + return new Show { Slug = result.Slug, Title = result.SeriesName, @@ -58,7 +58,7 @@ namespace Kyoo.TheTvdb StartAir = _ParseDate(result.FirstAired), Images = new Dictionary { - [Images.Poster] = result.Poster != null + [Images.Poster] = string.IsNullOrEmpty(result.Poster) ? $"https://www.thetvdb.com{result.Poster}" : null, }, @@ -82,7 +82,7 @@ namespace Kyoo.TheTvdb /// A show representing the given series. public static Show ToShow(this Series series, Provider provider) { - return new() + return new Show { Slug = series.Slug, Title = series.SeriesName, @@ -92,10 +92,10 @@ namespace Kyoo.TheTvdb StartAir = _ParseDate(series.FirstAired), Images = new Dictionary { - [Images.Poster] = series.Poster != null + [Images.Poster] = string.IsNullOrEmpty(series.Poster) ? $"https://www.thetvdb.com/banners/{series.Poster}" : null, - [Images.Thumbnail] = series.FanArt != null + [Images.Thumbnail] = string.IsNullOrEmpty(series.FanArt) ? $"https://www.thetvdb.com/banners/{series.FanArt}" : null }, @@ -116,11 +116,10 @@ namespace Kyoo.TheTvdb /// Convert a tvdb actor to a kyoo . /// /// The actor to convert - /// The provider representing the tvdb inside kyoo /// A people role representing the given actor in the role they played. - public static PeopleRole ToPeopleRole(this Actor actor, Provider provider) + public static PeopleRole ToPeopleRole(this Actor actor) { - return new() + return new PeopleRole { People = new People { @@ -128,18 +127,9 @@ namespace Kyoo.TheTvdb Name = actor.Name, Images = new Dictionary { - [Images.Poster] = actor.Image != null + [Images.Poster] = string.IsNullOrEmpty(actor.Image) ? $"https://www.thetvdb.com/banners/{actor.Image}" : null - }, - ExternalIDs = new [] - { - new MetadataID - { - DataID = actor.Id.ToString(), - Link = $"https://www.thetvdb.com/people/{actor.Id}", - Provider = provider - } } }, Role = actor.Role, @@ -155,7 +145,7 @@ namespace Kyoo.TheTvdb /// A episode representing the given tvdb episode. public static Episode ToEpisode(this EpisodeRecord episode, Provider provider) { - return new() + return new Episode { SeasonNumber = episode.AiredSeason, EpisodeNumber = episode.AiredEpisodeNumber, @@ -164,7 +154,7 @@ namespace Kyoo.TheTvdb Overview = episode.Overview, Images = new Dictionary { - [Images.Thumbnail] = episode.Filename != null + [Images.Thumbnail] = string.IsNullOrEmpty(episode.Filename) ? $"https://www.thetvdb.com/banners/{episode.Filename}" : null }, diff --git a/Kyoo.TheTvdb/ProviderTvdb.cs b/Kyoo.TheTvdb/ProviderTvdb.cs index e947ce25..01411d1d 100644 --- a/Kyoo.TheTvdb/ProviderTvdb.cs +++ b/Kyoo.TheTvdb/ProviderTvdb.cs @@ -95,7 +95,7 @@ namespace Kyoo.TheTvdb Show ret = series.Data.ToShow(Provider); TvDbResponse people = await _client.Series.GetActorsAsync(id); - ret.People = people.Data.Select(x => x.ToPeopleRole(Provider)).ToArray(); + ret.People = people.Data.Select(x => x.ToPeopleRole()).ToArray(); return ret; } diff --git a/tests/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs b/tests/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs index 58ce0a8c..a761ab9e 100644 --- a/tests/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs +++ b/tests/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs @@ -124,8 +124,7 @@ namespace Kyoo.Tests.Identifier.Tvdb Name = "Name", Role = "role" }; - Provider provider = TestSample.Get(); - PeopleRole people = actor.ToPeopleRole(provider); + PeopleRole people = actor.ToPeopleRole(); Assert.Equal("name", people.Slug); Assert.Equal("Name", people.People.Name); From 14f4065921c0ea06f5681ecd923f03249f6452e5 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 6 Aug 2021 00:24:23 +0200 Subject: [PATCH 32/34] Small bugs fixs --- Kyoo.TheMovieDb/Convertors/ShowConvertors.cs | 4 ++-- Kyoo.TheTvdb/Convertors.cs | 10 +++++----- Kyoo/CoreModule.cs | 4 ++-- Kyoo/Tasks/RegisterEpisode.cs | 13 ++++++++----- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs b/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs index 342af8e0..946a8aea 100644 --- a/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs +++ b/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs @@ -53,7 +53,7 @@ namespace Kyoo.TheMovieDb new MetadataID { Provider = provider, - Link = $"https://www.themoviedb.org/movie/{tv.Id}", + Link = $"https://www.themoviedb.org/tv/{tv.Id}", DataID = tv.Id.ToString() } } @@ -88,7 +88,7 @@ namespace Kyoo.TheMovieDb new MetadataID { Provider = provider, - Link = $"https://www.themoviedb.org/movie/{tv.Id}", + Link = $"https://www.themoviedb.org/tv/{tv.Id}", DataID = tv.Id.ToString() } } diff --git a/Kyoo.TheTvdb/Convertors.cs b/Kyoo.TheTvdb/Convertors.cs index 185bf0d1..69ee4c26 100644 --- a/Kyoo.TheTvdb/Convertors.cs +++ b/Kyoo.TheTvdb/Convertors.cs @@ -58,7 +58,7 @@ namespace Kyoo.TheTvdb StartAir = _ParseDate(result.FirstAired), Images = new Dictionary { - [Images.Poster] = string.IsNullOrEmpty(result.Poster) + [Images.Poster] = !string.IsNullOrEmpty(result.Poster) ? $"https://www.thetvdb.com{result.Poster}" : null, }, @@ -92,10 +92,10 @@ namespace Kyoo.TheTvdb StartAir = _ParseDate(series.FirstAired), Images = new Dictionary { - [Images.Poster] = string.IsNullOrEmpty(series.Poster) + [Images.Poster] = !string.IsNullOrEmpty(series.Poster) ? $"https://www.thetvdb.com/banners/{series.Poster}" : null, - [Images.Thumbnail] = string.IsNullOrEmpty(series.FanArt) + [Images.Thumbnail] = !string.IsNullOrEmpty(series.FanArt) ? $"https://www.thetvdb.com/banners/{series.FanArt}" : null }, @@ -127,7 +127,7 @@ namespace Kyoo.TheTvdb Name = actor.Name, Images = new Dictionary { - [Images.Poster] = string.IsNullOrEmpty(actor.Image) + [Images.Poster] = !string.IsNullOrEmpty(actor.Image) ? $"https://www.thetvdb.com/banners/{actor.Image}" : null } @@ -154,7 +154,7 @@ namespace Kyoo.TheTvdb Overview = episode.Overview, Images = new Dictionary { - [Images.Thumbnail] = string.IsNullOrEmpty(episode.Filename) + [Images.Thumbnail] = !string.IsNullOrEmpty(episode.Filename) ? $"https://www.thetvdb.com/banners/{episode.Filename}" : null }, diff --git a/Kyoo/CoreModule.cs b/Kyoo/CoreModule.cs index 2f5c7cfb..51f4f94c 100644 --- a/Kyoo/CoreModule.cs +++ b/Kyoo/CoreModule.cs @@ -101,13 +101,13 @@ namespace Kyoo /// public void Configure(ContainerBuilder builder) { - builder.RegisterComposite(); + builder.RegisterComposite().InstancePerLifetimeScope(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().InstancePerLifetimeScope(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().InstancePerLifetimeScope(); builder.RegisterType().As().SingleInstance(); diff --git a/Kyoo/Tasks/RegisterEpisode.cs b/Kyoo/Tasks/RegisterEpisode.cs index f939b5f5..c7c7c3e3 100644 --- a/Kyoo/Tasks/RegisterEpisode.cs +++ b/Kyoo/Tasks/RegisterEpisode.cs @@ -121,8 +121,6 @@ namespace Kyoo.Tasks if (season != null) season.Show = show; season = await _RegisterAndFill(season); - if (season != null) - season.Title ??= $"Season {season.SeasonNumber}"; progress.Report(60); episode.Show = show; @@ -175,10 +173,15 @@ namespace Kyoo.Tasks item = await _metadataProvider.Get(item); await _thumbnailsManager.DownloadImages(item); - if (item is Show show && show.People != null) + switch (item) { - foreach (PeopleRole role in show.People) - await _thumbnailsManager.DownloadImages(role.People); + case Show show when show.People != null: + foreach (PeopleRole role in show.People) + await _thumbnailsManager.DownloadImages(role.People); + break; + case Season season: + season.Title ??= $"Season {season.SeasonNumber}"; + break; } return await _libraryManager.CreateIfNotExists(item); From 0ae6776812e2c90f388dd7127384cd9ecea292ba Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 6 Aug 2021 12:11:11 +0200 Subject: [PATCH 33/34] Updating the webapp --- Kyoo.WebApp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Kyoo.WebApp b/Kyoo.WebApp index c037270d..dca10903 160000 --- a/Kyoo.WebApp +++ b/Kyoo.WebApp @@ -1 +1 @@ -Subproject commit c037270d3339fcf0075984a089f353c5c332a751 +Subproject commit dca10903ff54a8999732695b5c2a0a5c94f85200 From c65c7ae9224c74f73424503123713b782ddfe73d Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 6 Aug 2021 12:45:06 +0200 Subject: [PATCH 34/34] FileSystem: Fixing SonarCloud errors --- Kyoo/Controllers/FileSystems/HttpFileSystem.cs | 10 ++++------ Kyoo/Controllers/FileSystems/LocalFileSystem.cs | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Kyoo/Controllers/FileSystems/HttpFileSystem.cs b/Kyoo/Controllers/FileSystems/HttpFileSystem.cs index 82409cdc..c7954db8 100644 --- a/Kyoo/Controllers/FileSystems/HttpFileSystem.cs +++ b/Kyoo/Controllers/FileSystems/HttpFileSystem.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net.Http; using System.Threading.Tasks; @@ -94,6 +95,8 @@ namespace Kyoo.Controllers /// /// An to proxy an http request. /// + // TODO remove this suppress message once the class has been implemented. + [SuppressMessage("ReSharper", "NotAccessedField.Local")] public class HttpForwardResult : IActionResult { /// @@ -126,12 +129,7 @@ namespace Kyoo.Controllers public Task ExecuteResultAsync(ActionContext context) { // TODO implement that, example: https://github.com/twitchax/AspNetCore.Proxy/blob/14dd0f212d7abb43ca1bf8c890d5efb95db66acb/src/Core/Extensions/Http.cs#L15 - - // Silence unused warnings - if (_path != null || _rangeSupport || _type == null) - throw new NotImplementedException(); - else - throw new NotImplementedException(); + throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/Kyoo/Controllers/FileSystems/LocalFileSystem.cs b/Kyoo/Controllers/FileSystems/LocalFileSystem.cs index 18d887a0..87575e0a 100644 --- a/Kyoo/Controllers/FileSystems/LocalFileSystem.cs +++ b/Kyoo/Controllers/FileSystems/LocalFileSystem.cs @@ -133,7 +133,7 @@ namespace Kyoo.Controllers public Task GetExtraDirectory(T resource) { if (!_options.CurrentValue.MetadataInShow) - return null; + return Task.FromResult(null); return Task.FromResult(resource switch { Show show => Combine(show.Path, "Extra"),