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);