From 5a9b846f7c15be629d3178d5d93b9e260cd9a418 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 14 Apr 2024 23:04:56 +0200 Subject: [PATCH] Add next update date in db --- back/src/Kyoo.Abstractions/Models/INews.cs | 4 +- .../Kyoo.Abstractions/Models/MetadataID.cs | 26 ++++ .../Models/Resources/Collection.cs | 5 +- .../Models/Resources/Episode.cs | 7 +- .../Resources/Interfaces/IRefreshable.cs | 29 +++++ .../Models/Resources/Movie.cs | 4 + .../Models/Resources/Season.cs | 5 +- .../Models/Resources/Show.cs | 4 + back/src/Kyoo.Postgresql/DatabaseContext.cs | 120 ++++++------------ 9 files changed, 120 insertions(+), 84 deletions(-) create mode 100644 back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IRefreshable.cs diff --git a/back/src/Kyoo.Abstractions/Models/INews.cs b/back/src/Kyoo.Abstractions/Models/INews.cs index 5b25eaca..b5642f5d 100644 --- a/back/src/Kyoo.Abstractions/Models/INews.cs +++ b/back/src/Kyoo.Abstractions/Models/INews.cs @@ -24,8 +24,8 @@ namespace Kyoo.Abstractions.Models; /// /// A show, a movie or a collection. /// -[OneOf(Types = new[] { typeof(Episode), typeof(Movie) })] -public interface INews : IResource, IThumbnails, IMetadata, IAddedDate, IQuery +[OneOf(Types = [typeof(Episode), typeof(Movie)])] +public interface INews : IResource, IThumbnails, IAddedDate, IQuery { static Sort IQuery.DefaultSort => new Sort.By(nameof(AddedDate), true); } diff --git a/back/src/Kyoo.Abstractions/Models/MetadataID.cs b/back/src/Kyoo.Abstractions/Models/MetadataID.cs index 37919c10..ec384ec8 100644 --- a/back/src/Kyoo.Abstractions/Models/MetadataID.cs +++ b/back/src/Kyoo.Abstractions/Models/MetadataID.cs @@ -33,3 +33,29 @@ public class MetadataId /// public string? Link { get; set; } } + +/// +/// ID informations about an episode. +/// +public class EpisodeId +{ + /// + /// The Id of the show on the metadata database. + /// + public string ShowId { get; set; } + + /// + /// The season number or null if absolute numbering is used in this database. + /// + public int? Season { get; set; } + + /// + /// The episode number or absolute number if Season is null. + /// + public int Episode { get; set; } + + /// + /// The URL of the resource on the external provider. + /// + public string? Link { get; set; } +} diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs index eb1bda94..f2539a55 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs @@ -28,7 +28,7 @@ namespace Kyoo.Abstractions.Models; /// /// A class representing collections of . /// -public class Collection : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, ILibraryItem +public class Collection : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, IRefreshable, ILibraryItem { public static Sort DefaultSort => new Sort.By(nameof(Collection.Name)); @@ -76,6 +76,9 @@ public class Collection : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, /// public Dictionary ExternalId { get; set; } = new(); + /// + public DateTime? NextMetadataRefresh { get; set; } + public Collection() { } [JsonConstructor] diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs index 536decaf..f32bf741 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -31,7 +31,7 @@ namespace Kyoo.Abstractions.Models; /// /// A class to represent a single show's episode. /// -public class Episode : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, INews +public class Episode : IQuery, IResource, IThumbnails, IAddedDate, IRefreshable, INews { // Use absolute numbers by default and fallback to season/episodes if it does not exists. public static Sort DefaultSort => @@ -166,7 +166,10 @@ public class Episode : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, IN public Image? Logo { get; set; } /// - public Dictionary ExternalId { get; set; } = new(); + public Dictionary ExternalId { get; set; } = []; + + /// + public DateTime? NextMetadataRefresh { get; set; } /// /// The previous episode that should be seen before viewing this one. diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IRefreshable.cs b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IRefreshable.cs new file mode 100644 index 00000000..cef2b663 --- /dev/null +++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IRefreshable.cs @@ -0,0 +1,29 @@ +// Kyoo - A portable and vast media library solution. +// Copyright (c) Kyoo. +// +// See AUTHORS.md and LICENSE file in the project root for full license information. +// +// Kyoo is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// Kyoo is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Kyoo. If not, see . + +using System; + +namespace Kyoo.Abstractions.Models; + +public interface IRefreshable +{ + /// + /// The date of the next metadata refresh. Null if auto-refresh is disabled. + /// + public DateTime? NextMetadataRefresh { get; set; } +} diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs index 4e1d0282..18d49946 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs @@ -38,6 +38,7 @@ public class Movie IMetadata, IThumbnails, IAddedDate, + IRefreshable, ILibraryItem, INews, IWatchlist @@ -134,6 +135,9 @@ public class Movie /// public Dictionary ExternalId { get; set; } = new(); + /// + public DateTime? NextMetadataRefresh { get; set; } + /// /// The ID of the Studio that made this show. /// diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs index 8d3e0489..d94e6514 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs @@ -31,7 +31,7 @@ namespace Kyoo.Abstractions.Models; /// /// A season of a . /// -public class Season : IQuery, IResource, IMetadata, IThumbnails, IAddedDate +public class Season : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, IRefreshable { public static Sort DefaultSort => new Sort.By(x => x.SeasonNumber); @@ -119,6 +119,9 @@ public class Season : IQuery, IResource, IMetadata, IThumbnails, IAddedDate /// public Dictionary ExternalId { get; set; } = new(); + /// + public DateTime? NextMetadataRefresh { get; set; } + /// /// The list of episodes that this season contains. /// diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs index edee8866..b3af184f 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs @@ -39,6 +39,7 @@ public class Show IOnMerge, IThumbnails, IAddedDate, + IRefreshable, ILibraryItem, IWatchlist { @@ -126,6 +127,9 @@ public class Show /// public Dictionary ExternalId { get; set; } = new(); + /// + public DateTime? NextMetadataRefresh { get; set; } + /// /// The ID of the Studio that made this show. /// diff --git a/back/src/Kyoo.Postgresql/DatabaseContext.cs b/back/src/Kyoo.Postgresql/DatabaseContext.cs index db5de9c2..6f655a4b 100644 --- a/back/src/Kyoo.Postgresql/DatabaseContext.cs +++ b/back/src/Kyoo.Postgresql/DatabaseContext.cs @@ -157,21 +157,11 @@ public abstract class DatabaseContext : DbContext optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); } - private static ValueComparer> _GetComparer() - { - return new( - (c1, c2) => c1!.SequenceEqual(c2!), - c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())) - ); - } - - /// - /// Build the metadata model for the given type. - /// - /// The database model builder - /// The type to add metadata to. - private static void _HasMetadata(ModelBuilder modelBuilder) - where T : class, IMetadata + private static void _HasJson( + ModelBuilder builder, + Expression>> property + ) + where T : class { // TODO: Waiting for https://github.com/dotnet/efcore/issues/29825 // modelBuilder.Entity() @@ -179,22 +169,33 @@ public abstract class DatabaseContext : DbContext // { // x.ToJson(); // }); - modelBuilder + builder .Entity() - .Property(x => x.ExternalId) + .Property(property) .HasConversion( v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null), v => - JsonSerializer.Deserialize>( + JsonSerializer.Deserialize>( v, (JsonSerializerOptions?)null )! ) .HasColumnType("json"); - modelBuilder + builder .Entity() - .Property(x => x.ExternalId) - .Metadata.SetValueComparer(_GetComparer()); + .Property(property) + .Metadata.SetValueComparer( + new ValueComparer>( + (c1, c2) => c1!.SequenceEqual(c2!), + c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())) + ) + ); + } + + private static void _HasMetadata(ModelBuilder modelBuilder) + where T : class, IMetadata + { + _HasJson(modelBuilder, x => x.ExternalId); } private static void _HasImages(ModelBuilder modelBuilder) @@ -215,6 +216,16 @@ public abstract class DatabaseContext : DbContext .ValueGeneratedOnAdd(); } + private static void _HasRefreshDate(ModelBuilder builder) + where T : class, IRefreshable + { + // schedule a refresh soon since metadata can change frequently for recently added items ond online databases + builder.Entity() + .Property(x => x.NextMetadataRefresh) + .HasDefaultValueSql("now() at time zone 'utc' + interval '2 hours'") + .ValueGeneratedOnAdd(); + } + /// /// Create a many to many relationship between the two entities. /// The resulting relationship will have an available method. @@ -296,8 +307,8 @@ public abstract class DatabaseContext : DbContext _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); - _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); + _HasJson(modelBuilder, x => x.ExternalId); _HasImages(modelBuilder); _HasImages(modelBuilder); @@ -313,6 +324,12 @@ public abstract class DatabaseContext : DbContext _HasAddedDate(modelBuilder); _HasAddedDate(modelBuilder); + _HasRefreshDate(modelBuilder); + _HasRefreshDate(modelBuilder); + _HasRefreshDate(modelBuilder); + _HasRefreshDate(modelBuilder); + _HasRefreshDate(modelBuilder); + modelBuilder .Entity() .HasKey(x => new { User = x.UserId, Movie = x.MovieId }); @@ -389,62 +406,9 @@ public abstract class DatabaseContext : DbContext modelBuilder.Entity().HasKey(x => new { x.Domain, x.Cause }); - // TODO: Waiting for https://github.com/dotnet/efcore/issues/29825 - // modelBuilder.Entity() - // .OwnsOne(x => x.ExternalId, x => - // { - // x.ToJson(); - // }); - modelBuilder - .Entity() - .Property(x => x.Settings) - .HasConversion( - v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null), - v => - JsonSerializer.Deserialize>( - v, - (JsonSerializerOptions?)null - )! - ) - .HasColumnType("json"); - modelBuilder - .Entity() - .Property(x => x.Settings) - .Metadata.SetValueComparer(_GetComparer()); - - modelBuilder - .Entity() - .Property(x => x.ExternalId) - .HasConversion( - v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null), - v => - JsonSerializer.Deserialize>( - v, - (JsonSerializerOptions?)null - )! - ) - .HasColumnType("json"); - modelBuilder - .Entity() - .Property(x => x.ExternalId) - .Metadata.SetValueComparer(_GetComparer()); - - modelBuilder - .Entity() - .Property(x => x.Extra) - .HasConversion( - v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null), - v => - JsonSerializer.Deserialize>( - v, - (JsonSerializerOptions?)null - )! - ) - .HasColumnType("json"); - modelBuilder - .Entity() - .Property(x => x.Extra) - .Metadata.SetValueComparer(_GetComparer()); + _HasJson(modelBuilder, x => x.Settings); + _HasJson(modelBuilder, x => x.ExternalId); + _HasJson(modelBuilder, x => x.Extra); } ///