diff --git a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs
index 792e1978..194fc2b4 100644
--- a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs
+++ b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs
@@ -85,11 +85,6 @@ namespace Kyoo.Abstractions.Controllers
///
IGenreRepository GenreRepository { get; }
- ///
- /// The repository that handle providers.
- ///
- IProviderRepository ProviderRepository { get; }
-
///
/// The repository that handle users.
///
diff --git a/back/src/Kyoo.Abstractions/Controllers/IRepository.cs b/back/src/Kyoo.Abstractions/Controllers/IRepository.cs
index a45827f0..5c8e438d 100644
--- a/back/src/Kyoo.Abstractions/Controllers/IRepository.cs
+++ b/back/src/Kyoo.Abstractions/Controllers/IRepository.cs
@@ -446,25 +446,6 @@ namespace Kyoo.Abstractions.Controllers
Pagination limit = default);
}
- ///
- /// A repository to handle providers.
- ///
- public interface IProviderRepository : IRepository
- {
- ///
- /// Get a list of external ids that match all filters
- ///
- /// A predicate to add arbitrary filter
- /// Sort information (sort order and sort by)
- /// Pagination information (where to start and how many to get)
- /// The type of metadata to retrieve
- /// A filtered list of external ids.
- Task> GetMetadataID(Expression> where = null,
- Sort sort = default,
- Pagination limit = default)
- where T : class, IMetadata;
- }
-
///
/// A repository to handle users.
///
diff --git a/back/src/Kyoo.Abstractions/Controllers/IThumbnailsManager.cs b/back/src/Kyoo.Abstractions/Controllers/IThumbnailsManager.cs
index 69ffe0bc..8ef426c5 100644
--- a/back/src/Kyoo.Abstractions/Controllers/IThumbnailsManager.cs
+++ b/back/src/Kyoo.Abstractions/Controllers/IThumbnailsManager.cs
@@ -35,22 +35,20 @@ namespace Kyoo.Abstractions.Controllers
///
/// The item to cache images.
///
- ///
- /// true if images should be downloaded even if they already exists locally, false otherwise.
- ///
/// The type of the item
/// true if an image has been downloaded, false otherwise.
- Task DownloadImages(T item, bool alwaysDownload = false)
+ Task DownloadImages(T item)
where T : IThumbnails;
///
/// 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 ID of the image.
+ /// The quality of the image
/// The type of the item
/// The path of the image for the given resource or null if it does not exists.
- string? GetImagePath(T item, int imageId)
+ string GetImagePath(T item, string image, ImageQuality quality)
where T : IThumbnails;
}
}
diff --git a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs
index fd9a5b85..852e48f9 100644
--- a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs
+++ b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs
@@ -85,7 +85,13 @@ namespace Kyoo.Abstractions.Models
public DateTime? EndAir { get; set; }
///
- public Dictionary Images { get; set; }
+ public Image Poster { get; set; }
+
+ ///
+ public Image Thumbnail { get; set; }
+
+ ///
+ public Image Logo { get; set; }
///
/// The type of this item (ether a collection, a show or a movie).
@@ -110,7 +116,9 @@ namespace Kyoo.Abstractions.Models
Status = show.Status;
StartAir = show.StartAir;
EndAir = show.EndAir;
- Images = show.Images;
+ Poster = show.Poster;
+ Thumbnail = show.Thumbnail;
+ Logo = show.Logo;
Type = show.IsMovie ? ItemType.Movie : ItemType.Show;
}
@@ -127,7 +135,9 @@ namespace Kyoo.Abstractions.Models
Status = Models.Status.Unknown;
StartAir = null;
EndAir = null;
- Images = collection.Images;
+ Poster = collection.Poster;
+ Thumbnail = collection.Thumbnail;
+ Logo = collection.Logo;
Type = ItemType.Collection;
}
@@ -143,7 +153,9 @@ namespace Kyoo.Abstractions.Models
Status = x.Status,
StartAir = x.StartAir,
EndAir = x.EndAir,
- Images = x.Images,
+ Poster = x.Poster,
+ Thumbnail = x.Thumbnail,
+ Logo = x.Logo,
Type = x.IsMovie ? ItemType.Movie : ItemType.Show
};
@@ -159,7 +171,9 @@ namespace Kyoo.Abstractions.Models
Status = Models.Status.Unknown,
StartAir = null,
EndAir = null,
- Images = x.Images,
+ Poster = x.Poster,
+ Thumbnail = x.Thumbnail,
+ Logo = x.Logo,
Type = ItemType.Collection
};
diff --git a/back/src/Kyoo.Abstractions/Models/MetadataID.cs b/back/src/Kyoo.Abstractions/Models/MetadataID.cs
index dbf00a3e..0ab9b8fb 100644
--- a/back/src/Kyoo.Abstractions/Models/MetadataID.cs
+++ b/back/src/Kyoo.Abstractions/Models/MetadataID.cs
@@ -14,11 +14,6 @@
// 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;
-using System.Linq.Expressions;
-using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Abstractions.Models
{
@@ -27,29 +22,6 @@ namespace Kyoo.Abstractions.Models
///
public class MetadataID
{
- ///
- /// The expression to retrieve the unique ID of a MetadataID. This is an aggregate of the two resources IDs.
- ///
- public static Expression> PrimaryKey
- {
- get { return x => new { First = x.ResourceID, Second = x.ProviderID }; }
- }
-
- ///
- /// The ID of the resource which possess the metadata.
- ///
- [SerializeIgnore] public int ResourceID { 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.
///
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs
index 079c5eef..e3fa14cf 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs
@@ -39,7 +39,13 @@ namespace Kyoo.Abstractions.Models
public string Name { get; set; }
///
- public Dictionary Images { get; set; }
+ public Image Poster { get; set; }
+
+ ///
+ public Image Thumbnail { get; set; }
+
+ ///
+ public Image Logo { get; set; }
///
/// The description of this collection.
@@ -57,6 +63,6 @@ namespace Kyoo.Abstractions.Models
[LoadableRelation] public ICollection Libraries { get; set; }
///
- [EditableRelation][LoadableRelation] public ICollection ExternalIDs { get; set; }
+ public Dictionary ExternalId { get; set; }
}
}
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs
index f6dcb41e..86e11442 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs
@@ -127,9 +127,6 @@ namespace Kyoo.Abstractions.Models
///
public string Path { get; set; }
- ///
- public Dictionary Images { get; set; }
-
///
/// The title of this episode.
///
@@ -146,7 +143,16 @@ namespace Kyoo.Abstractions.Models
public DateTime? ReleaseDate { get; set; }
///
- [EditableRelation][LoadableRelation] public ICollection ExternalIDs { get; set; }
+ public Image Poster { get; set; }
+
+ ///
+ public Image Thumbnail { get; set; }
+
+ ///
+ public Image Logo { get; set; }
+
+ ///
+ public Dictionary ExternalId { get; set; }
///
/// Get the slug of an episode.
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs
index 44f072fa..d46a029e 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs
@@ -16,11 +16,7 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see .
-using System;
using System.Collections.Generic;
-using System.Linq;
-using JetBrains.Annotations;
-using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Abstractions.Models
{
@@ -32,67 +28,6 @@ namespace Kyoo.Abstractions.Models
///
/// The link to metadata providers that this show has. See for more information.
///
- [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;
- }
+ public Dictionary ExternalId { get; set; }
}
}
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs
index 071ddc35..c69c984d 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs
@@ -16,8 +16,7 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see .
-using System.Collections.Generic;
-using Kyoo.Abstractions.Controllers;
+using System.ComponentModel.DataAnnotations;
namespace Kyoo.Abstractions.Models
{
@@ -25,53 +24,56 @@ namespace Kyoo.Abstractions.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.
- ///
- ///
- /// An arbitrary index should not be used, instead use indexes from
- ///
- /// {"0": "example.com/dune/poster"}
- public Dictionary Images { get; set; }
- }
-
- ///
- /// A class containing constant values for images. To be used as index of a .
- ///
- public static class Images
{
///
/// A poster is a 9/16 format image with the cover of the resource.
///
- public const int Poster = 0;
+ public Image Poster { get; set; }
///
/// 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;
+ public Image Thumbnail { get; set; }
///
/// A logo is a small image representing the resource.
///
- public const int Logo = 2;
+ public Image Logo { get; set; }
+ }
+
+ public class Image
+ {
+ ///
+ /// The original image from another server.
+ ///
+ public string Source { get; set; }
///
- /// A video of a few minutes that tease the content.
+ /// A hash to display as placeholder while the image is loading.
///
- public const int Trailer = 3;
+ [MaxLength(32)]
+ public string Blurhash { get; set; }
+ }
+
+ ///
+ /// The quality of an image
+ ///
+ public enum ImageQuality
+ {
+ ///
+ /// Small
+ ///
+ Small,
///
- /// Retrieve the name of an image using it's ID. It is also used by the serializer to retrieve all named images.
- /// If a plugin adds a new image type, it should add it's value and name here to allow the serializer to add it.
+ /// Medium
///
- public static Dictionary ImageName { get; } = new()
- {
- [Poster] = nameof(Poster),
- [Thumbnail] = nameof(Thumbnail),
- [Logo] = nameof(Logo),
- [Trailer] = nameof(Trailer)
- };
+ Medium,
+
+ ///
+ /// Large
+ ///
+ Large,
}
}
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Library.cs b/back/src/Kyoo.Abstractions/Models/Resources/Library.cs
index e56bb352..0c4129f4 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Library.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Library.cs
@@ -42,11 +42,6 @@ namespace Kyoo.Abstractions.Models
///
public string[] Paths { get; set; }
- ///
- /// The list of used for items in this library.
- ///
- [EditableRelation][LoadableRelation] public ICollection Providers { get; set; }
-
///
/// The list of shows in this library.
///
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/People.cs b/back/src/Kyoo.Abstractions/Models/Resources/People.cs
index 9ed2cea3..c5a32429 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/People.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/People.cs
@@ -38,10 +38,16 @@ namespace Kyoo.Abstractions.Models
public string Name { get; set; }
///
- public Dictionary Images { get; set; }
+ public Image Poster { get; set; }
///
- [EditableRelation][LoadableRelation] public ICollection ExternalIDs { get; set; }
+ public Image Thumbnail { get; set; }
+
+ ///
+ public Image Logo { get; set; }
+
+ ///
+ public Dictionary ExternalId { get; set; }
///
/// The list of roles this person has played in. See for more information.
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Provider.cs b/back/src/Kyoo.Abstractions/Models/Resources/Provider.cs
deleted file mode 100644
index 4e419027..00000000
--- a/back/src/Kyoo.Abstractions/Models/Resources/Provider.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-// 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.Collections.Generic;
-using Kyoo.Abstractions.Models.Attributes;
-using Kyoo.Utils;
-
-namespace Kyoo.Abstractions.Models
-{
- ///
- /// A dead class that will be removed later.
- ///
- // TODO: Delete this class
- public class Provider : IResource, IThumbnails
- {
- ///
- public int ID { get; set; }
-
- ///
- public string Slug { get; set; }
-
- ///
- /// The name of this provider.
- ///
- public string Name { get; set; }
-
- ///
- public Dictionary Images { get; set; }
-
- ///
- /// The list of libraries that uses this provider.
- ///
- [LoadableRelation] public ICollection Libraries { get; set; }
-
- ///
- /// Create a new, default,
- ///
- public Provider() { }
-
- ///
- /// Create a new and specify it's .
- /// The is automatically calculated from it's name.
- ///
- /// The name of this provider.
- /// The logo of this provider.
- public Provider(string name, string logo)
- {
- Slug = Utility.ToSlug(name);
- Name = name;
- Images = new Dictionary
- {
- [Models.Images.Logo] = logo
- };
- }
- }
-}
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs
index 91063b8b..1b31d2c3 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs
@@ -99,10 +99,16 @@ namespace Kyoo.Abstractions.Models
public DateTime? EndDate { get; set; }
///
- public Dictionary Images { get; set; }
+ public Image Poster { get; set; }
///
- [EditableRelation][LoadableRelation] public ICollection ExternalIDs { get; set; }
+ public Image Thumbnail { get; set; }
+
+ ///
+ public Image Logo { get; set; }
+
+ ///
+ public Dictionary ExternalId { 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 646dee9f..0dc30b30 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs
@@ -59,13 +59,6 @@ namespace Kyoo.Abstractions.Models
///
public Status Status { get; set; }
- ///
- /// An URL to a trailer.
- ///
- /// TODO for now, this is set to a youtube url. It should be cached and converted to a local file.
- [Obsolete("Use Images instead of this, this is only kept for the API response.")]
- public string TrailerUrl => Images?.GetValueOrDefault(Models.Images.Trailer);
-
///
/// The date this show started airing. It can be null if this is unknown.
///
@@ -78,16 +71,27 @@ namespace Kyoo.Abstractions.Models
///
public DateTime? EndAir { get; set; }
- ///
- public Dictionary Images { get; set; }
-
///
/// True if this show represent a movie, false otherwise.
///
public bool IsMovie { get; set; }
///
- [EditableRelation][LoadableRelation] public ICollection ExternalIDs { get; set; }
+ public Image Poster { get; set; }
+
+ ///
+ public Image Thumbnail { get; set; }
+
+ ///
+ public Image Logo { get; set; }
+
+ ///
+ /// A video of a few minutes that tease the content.
+ ///
+ public string Trailer { get; set; }
+
+ ///
+ public Dictionary ExternalId { get; set; }
///
/// The ID of the Studio that made this show.
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Studio.cs b/back/src/Kyoo.Abstractions/Models/Resources/Studio.cs
index 973404d2..e3923d3a 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Studio.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Studio.cs
@@ -44,7 +44,7 @@ namespace Kyoo.Abstractions.Models
[LoadableRelation] public ICollection Shows { get; set; }
///
- [EditableRelation][LoadableRelation] public ICollection ExternalIDs { get; set; }
+ public Dictionary ExternalId { get; set; }
///
/// Create a new, empty, .
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/User.cs b/back/src/Kyoo.Abstractions/Models/Resources/User.cs
index cf9f5368..2fde3468 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/User.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/User.cs
@@ -24,7 +24,7 @@ namespace Kyoo.Abstractions.Models
///
/// A single user of the app.
///
- public class User : IResource, IThumbnails
+ public class User : IResource
{
///
public int ID { get; set; }
@@ -59,8 +59,10 @@ namespace Kyoo.Abstractions.Models
[SerializeIgnore]
public Dictionary ExtraData { get; set; }
- ///
- public Dictionary Images { get; set; }
+ ///
+ /// A logo is a small image representing the resource.
+ ///
+ public Image Logo { get; set; }
///
/// The list of shows the user has finished.
diff --git a/back/src/Kyoo.Abstractions/Models/WatchItem.cs b/back/src/Kyoo.Abstractions/Models/WatchItem.cs
index ca87d1e7..7ec67e4b 100644
--- a/back/src/Kyoo.Abstractions/Models/WatchItem.cs
+++ b/back/src/Kyoo.Abstractions/Models/WatchItem.cs
@@ -103,7 +103,13 @@ namespace Kyoo.Abstractions.Models
public bool IsMovie { get; set; }
///
- public Dictionary Images { get; set; }
+ public Image Poster { get; set; }
+
+ ///
+ public Image Thumbnail { get; set; }
+
+ ///
+ public Image Logo { get; set; }
///
/// The transcoder's info for this item. This include subtitles, fonts, chapters...
@@ -145,7 +151,9 @@ namespace Kyoo.Abstractions.Models
Title = ep.Title,
Overview = ep.Overview,
ReleaseDate = ep.ReleaseDate,
- Images = ep.Show.Images,
+ Poster = ep.Poster,
+ Thumbnail = ep.Thumbnail,
+ Logo = ep.Logo,
PreviousEpisode = ep.Show.IsMovie
? null
: (await library.GetAll(
diff --git a/back/src/Kyoo.Core/Controllers/LibraryManager.cs b/back/src/Kyoo.Core/Controllers/LibraryManager.cs
index 453d6b10..edced1e7 100644
--- a/back/src/Kyoo.Core/Controllers/LibraryManager.cs
+++ b/back/src/Kyoo.Core/Controllers/LibraryManager.cs
@@ -66,9 +66,6 @@ namespace Kyoo.Core.Controllers
///
public IGenreRepository GenreRepository { get; }
- ///
- public IProviderRepository ProviderRepository { get; }
-
///
public IUserRepository UserRepository { get; }
@@ -89,7 +86,6 @@ namespace Kyoo.Core.Controllers
PeopleRepository = GetRepository() as IPeopleRepository;
StudioRepository = GetRepository() as IStudioRepository;
GenreRepository = GetRepository() as IGenreRepository;
- ProviderRepository = GetRepository() as IProviderRepository;
UserRepository = GetRepository() as IUserRepository;
}
@@ -259,10 +255,6 @@ namespace Kyoo.Core.Controllers
return (obj, member: memberName) switch
{
- (Library l, nameof(Library.Providers)) => ProviderRepository
- .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID))
- .Then(x => l.Providers = x),
-
(Library l, nameof(Library.Shows)) => ShowRepository
.GetAll(x => x.Libraries.Any(y => y.ID == obj.ID))
.Then(x => l.Shows = x),
@@ -272,11 +264,6 @@ namespace Kyoo.Core.Controllers
.Then(x => l.Collections = x),
- (Collection c, nameof(Collection.ExternalIDs)) => _SetRelation(c,
- ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID),
- (x, y) => x.ExternalIDs = y,
- (x, y) => { x.ResourceID = y.ID; }),
-
(Collection c, nameof(Collection.Shows)) => ShowRepository
.GetAll(x => x.Collections.Any(y => y.ID == obj.ID))
.Then(x => c.Shows = x),
@@ -286,11 +273,6 @@ namespace Kyoo.Core.Controllers
.Then(x => c.Libraries = x),
- (Show s, nameof(Show.ExternalIDs)) => _SetRelation(s,
- ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID),
- (x, y) => x.ExternalIDs = y,
- (x, y) => { x.ResourceID = y.ID; }),
-
(Show s, nameof(Show.Genres)) => GenreRepository
.GetAll(x => x.Shows.Any(y => y.ID == obj.ID))
.Then(x => s.Genres = x),
@@ -326,11 +308,6 @@ namespace Kyoo.Core.Controllers
}),
- (Season s, nameof(Season.ExternalIDs)) => _SetRelation(s,
- ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID),
- (x, y) => x.ExternalIDs = y,
- (x, y) => { x.ResourceID = y.ID; }),
-
(Season s, nameof(Season.Episodes)) => _SetRelation(s,
EpisodeRepository.GetAll(x => x.Season.ID == obj.ID),
(x, y) => x.Episodes = y,
@@ -345,11 +322,6 @@ namespace Kyoo.Core.Controllers
}),
- (Episode e, nameof(Episode.ExternalIDs)) => _SetRelation(e,
- ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID),
- (x, y) => x.ExternalIDs = y,
- (x, y) => { x.ResourceID = y.ID; }),
-
(Episode e, nameof(Episode.Show)) => ShowRepository
.GetOrDefault(x => x.Episodes.Any(y => y.ID == obj.ID))
.Then(x =>
@@ -376,27 +348,10 @@ namespace Kyoo.Core.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.ResourceID == obj.ID),
- (x, y) => x.ExternalIDs = y,
- (x, y) => { x.ResourceID = y.ID; }),
-
(People p, nameof(People.Roles)) => PeopleRepository
.GetFromPeople(obj.ID)
.Then(x => p.Roles = x),
-
- (Provider p, nameof(Provider.Libraries)) => LibraryRepository
- .GetAll(x => x.Providers.Any(y => y.ID == obj.ID))
- .Then(x => p.Libraries = x),
-
-
_ => throw new ArgumentException($"Couldn't find a way to load {memberName} of {obj.Slug}.")
};
}
diff --git a/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs
index 53f8df1b..d8489e10 100644
--- a/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs
+++ b/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs
@@ -37,11 +37,6 @@ namespace Kyoo.Core.Controllers
///
private readonly DatabaseContext _database;
- ///
- /// A provider repository to handle externalID creation and deletion
- ///
- private readonly IProviderRepository _providers;
-
///
protected override Sort DefaultSort => new Sort.By(nameof(Collection.Name));
@@ -49,12 +44,10 @@ namespace Kyoo.Core.Controllers
/// Create a new .
///
/// The database handle to use
- /// /// A provider repository
- public CollectionRepository(DatabaseContext database, IProviderRepository providers)
+ public CollectionRepository(DatabaseContext database)
: base(database)
{
_database = database;
- _providers = providers;
}
///
@@ -82,33 +75,8 @@ namespace Kyoo.Core.Controllers
{
await base.Validate(resource);
- if (string.IsNullOrEmpty(resource.Slug))
- throw new ArgumentException("The collection's slug must be set and not empty");
if (string.IsNullOrEmpty(resource.Name))
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 = _database.LocalEntity(id.Provider.Slug)
- ?? await _providers.CreateIfNotExists(id.Provider);
- id.ProviderID = id.Provider.ID;
- }
- _database.MetadataIds().AttachRange(resource.ExternalIDs);
- }
- }
-
- ///
- protected override async Task EditRelations(Collection resource, Collection changed, bool resetOld)
- {
- await Validate(changed);
-
- if (changed.ExternalIDs != null || resetOld)
- {
- await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
- resource.ExternalIDs = changed.ExternalIDs;
- }
}
///
diff --git a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs
index c3fc9ec8..eb58fe98 100644
--- a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs
+++ b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs
@@ -39,11 +39,6 @@ namespace Kyoo.Core.Controllers
///
private readonly DatabaseContext _database;
- ///
- /// A provider repository to handle externalID creation and deletion
- ///
- private readonly IProviderRepository _providers;
-
private readonly IShowRepository _shows;
///
@@ -59,14 +54,11 @@ namespace Kyoo.Core.Controllers
///
/// The database handle to use.
/// A show repository
- /// A provider repository
public EpisodeRepository(DatabaseContext database,
- IShowRepository shows,
- IProviderRepository providers)
+ IShowRepository shows)
: base(database)
{
_database = database;
- _providers = providers;
_shows = shows;
// Edit episode slugs when the show's slug changes.
@@ -160,18 +152,6 @@ namespace Kyoo.Core.Controllers
return obj;
}
- ///
- protected override async Task EditRelations(Episode resource, Episode changed, bool resetOld)
- {
- await Validate(changed);
-
- if (changed.ExternalIDs != null || resetOld)
- {
- await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
- resource.ExternalIDs = changed.ExternalIDs;
- }
- }
-
///
protected override async Task Validate(Episode resource)
{
@@ -185,17 +165,6 @@ namespace Kyoo.Core.Controllers
}
resource.ShowID = resource.Show.ID;
}
-
- 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);
- }
}
///
@@ -206,7 +175,6 @@ namespace Kyoo.Core.Controllers
int epCount = await _database.Episodes.Where(x => x.ShowID == obj.ShowID).Take(2).CountAsync();
_database.Entry(obj).State = EntityState.Deleted;
- obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Deleted);
await _database.SaveChangesAsync();
await base.Delete(obj);
if (epCount == 1)
diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs
index dfc76442..59e7083c 100644
--- a/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs
+++ b/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs
@@ -38,11 +38,6 @@ namespace Kyoo.Core.Controllers
///
private readonly DatabaseContext _database;
- ///
- /// A provider repository to handle externalID creation and deletion
- ///
- private readonly IProviderRepository _providers;
-
///
protected override Sort DefaultSort => new Sort.By(x => x.ID);
@@ -50,12 +45,10 @@ namespace Kyoo.Core.Controllers
/// Create a new instance.
///
/// The database handle
- /// The provider repository
- public LibraryRepository(DatabaseContext database, IProviderRepository providers)
+ public LibraryRepository(DatabaseContext database)
: base(database)
{
_database = database;
- _providers = providers;
}
///
@@ -90,29 +83,6 @@ namespace Kyoo.Core.Controllers
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(async x =>
- _database.LocalEntity(x.Slug)
- ?? await _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 Database.Entry(resource).Collection(x => x.Providers).LoadAsync();
- resource.Providers = changed.Providers;
- }
}
///
diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs
index 85c3b6a3..f2b4cf56 100644
--- a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs
+++ b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs
@@ -341,6 +341,8 @@ namespace Kyoo.Core.Controllers
if (obj == null)
throw new ArgumentNullException(nameof(obj));
await Validate(obj);
+ // if (obj is IThumbnails thumbs)
+ // await _thumbnailsManager.DownloadImages(thumbs);
return obj;
}
diff --git a/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs
index bd631ed1..766629e0 100644
--- a/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs
+++ b/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs
@@ -39,11 +39,6 @@ namespace Kyoo.Core.Controllers
///
private readonly DatabaseContext _database;
- ///
- /// A provider repository to handle externalID creation and deletion
- ///
- private readonly IProviderRepository _providers;
-
///
/// A lazy loaded show repository to validate requests from shows.
///
@@ -56,15 +51,12 @@ namespace Kyoo.Core.Controllers
/// Create a new
///
/// The database handle
- /// A provider repository
/// A lazy loaded show repository
public PeopleRepository(DatabaseContext database,
- IProviderRepository providers,
Lazy shows)
: base(database)
{
_database = database;
- _providers = providers;
_shows = shows;
}
@@ -94,17 +86,6 @@ namespace Kyoo.Core.Controllers
{
await base.Validate(resource);
- 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);
- }
-
if (resource.Roles != null)
{
foreach (PeopleRole role in resource.Roles)
@@ -127,12 +108,6 @@ namespace Kyoo.Core.Controllers
await Database.Entry(resource).Collection(x => x.Roles).LoadAsync();
resource.Roles = changed.Roles;
}
-
- if (changed.ExternalIDs != null || resetOld)
- {
- await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
- resource.ExternalIDs = changed.ExternalIDs;
- }
}
///
@@ -142,7 +117,6 @@ namespace Kyoo.Core.Controllers
throw new ArgumentNullException(nameof(obj));
_database.Entry(obj).State = EntityState.Deleted;
- obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Deleted);
obj.Roles.ForEach(x => _database.Entry(x).State = EntityState.Deleted);
await _database.SaveChangesAsync();
await base.Delete(obj);
diff --git a/back/src/Kyoo.Core/Controllers/Repositories/ProviderRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/ProviderRepository.cs
deleted file mode 100644
index 7435e0fa..00000000
--- a/back/src/Kyoo.Core/Controllers/Repositories/ProviderRepository.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-// 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;
-using System.Collections.Generic;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Threading.Tasks;
-using Kyoo.Abstractions.Controllers;
-using Kyoo.Abstractions.Models;
-using Kyoo.Postgresql;
-using Microsoft.EntityFrameworkCore;
-
-namespace Kyoo.Core.Controllers
-{
- ///
- /// A local repository to handle providers.
- ///
- public class ProviderRepository : LocalRepository, IProviderRepository
- {
- ///
- /// The database handle
- ///
- private readonly DatabaseContext _database;
-
- ///
- /// Create a new .
- ///
- /// The database handle
- public ProviderRepository(DatabaseContext database)
- : base(database)
- {
- _database = database;
- }
-
- ///
- protected override Sort DefaultSort => new Sort.By(x => x.Slug);
-
- ///
- public override async Task> Search(string query)
- {
- return await Sort(
- _database.Providers
- .Where(_database.Like(x => x.Name, $"%{query}%"))
- )
- .Take(20)
- .ToListAsync();
- }
-
- ///
- public override async Task Create(Provider obj)
- {
- await base.Create(obj);
- _database.Entry(obj).State = EntityState.Added;
- await _database.SaveChangesAsync(() => Get(obj.Slug));
- OnResourceCreated(obj);
- return obj;
- }
-
- ///
- public override async Task Delete(Provider obj)
- {
- if (obj == null)
- throw new ArgumentNullException(nameof(obj));
-
- _database.Entry(obj).State = EntityState.Deleted;
- await _database.SaveChangesAsync();
- await base.Delete(obj);
- }
-
- ///
- public async Task> GetMetadataID(Expression> where = null,
- Sort sort = default,
- Pagination limit = default)
- where T : class, IMetadata
- {
- return await _database.MetadataIds()
- .Include(y => y.Provider)
- .Where(where)
- .ToListAsync();
- }
- }
-}
diff --git a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs
index de070572..7b12cf73 100644
--- a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs
+++ b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs
@@ -38,11 +38,6 @@ namespace Kyoo.Core.Controllers
///
private readonly DatabaseContext _database;
- ///
- /// A provider repository to handle externalID creation and deletion
- ///
- private readonly IProviderRepository _providers;
-
///
protected override Sort DefaultSort => new Sort.By(x => x.SeasonNumber);
@@ -51,14 +46,11 @@ namespace Kyoo.Core.Controllers
///
/// The database handle that will be used
/// A shows repository
- /// A provider repository
public SeasonRepository(DatabaseContext database,
- IShowRepository shows,
- IProviderRepository providers)
+ IShowRepository shows)
: base(database)
{
_database = database;
- _providers = providers;
// Edit seasons slugs when the show's slug changes.
shows.OnEdited += (show) =>
@@ -140,29 +132,6 @@ namespace Kyoo.Core.Controllers
}
resource.ShowID = resource.Show.ID;
}
-
- 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);
- }
- }
-
- ///
- 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;
- }
}
///
diff --git a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs
index e9ea389e..4e2092b2 100644
--- a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs
+++ b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs
@@ -52,11 +52,6 @@ namespace Kyoo.Core.Controllers
///
private readonly IGenreRepository _genres;
- ///
- /// A provider repository to handle externalID creation and deletion
- ///
- private readonly IProviderRepository _providers;
-
///
protected override Sort DefaultSort => new Sort.By(x => x.Title);
@@ -67,19 +62,16 @@ namespace Kyoo.Core.Controllers
/// A studio repository
/// A people repository
/// A genres repository
- /// A provider repository
public ShowRepository(DatabaseContext database,
IStudioRepository studios,
IPeopleRepository people,
- IGenreRepository genres,
- IProviderRepository providers)
+ IGenreRepository genres)
: base(database)
{
_database = database;
_studios = studios;
_people = people;
_genres = genres;
- _providers = providers;
}
///
@@ -124,17 +116,6 @@ namespace Kyoo.Core.Controllers
_database.AttachRange(resource.Genres);
}
- 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);
- }
-
if (resource.People != null)
{
foreach (PeopleRole role in resource.People)
@@ -172,12 +153,6 @@ namespace Kyoo.Core.Controllers
await Database.Entry(resource).Collection(x => x.People).LoadAsync();
resource.People = changed.People;
}
-
- if (changed.ExternalIDs != null || resetOld)
- {
- await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
- resource.ExternalIDs = changed.ExternalIDs;
- }
}
///
diff --git a/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs
index 138c7ae1..738f3fb4 100644
--- a/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs
+++ b/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs
@@ -38,11 +38,6 @@ namespace Kyoo.Core.Controllers
///
private readonly DatabaseContext _database;
- ///
- /// A provider repository to handle externalID creation and deletion
- ///
- private readonly IProviderRepository _providers;
-
///
protected override Sort DefaultSort => new Sort.By(x => x.Name);
@@ -50,12 +45,10 @@ namespace Kyoo.Core.Controllers
/// Create a new .
///
/// The database handle
- /// A provider repository
- public StudioRepository(DatabaseContext database, IProviderRepository providers)
+ public StudioRepository(DatabaseContext database)
: base(database)
{
_database = database;
- _providers = providers;
}
///
@@ -83,30 +76,7 @@ namespace Kyoo.Core.Controllers
protected override async Task Validate(Studio resource)
{
resource.Slug ??= Utility.ToSlug(resource.Name);
-
await base.Validate(resource);
- 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);
- }
- }
-
- ///
- 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);
}
///
diff --git a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs
index 15ba7def..4d561b2a 100644
--- a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs
+++ b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs
@@ -18,13 +18,12 @@
using System;
using System.IO;
-using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
-using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Logging;
+using SkiaSharp;
#nullable enable
@@ -54,18 +53,18 @@ namespace Kyoo.Core.Controllers
_logger = logger;
}
- ///
- /// An helper function to download an image.
- ///
- /// The distant url of the image
- /// The local path of the image
- /// What is currently downloaded (used for errors)
- /// true if an image has been downloaded, false otherwise.
- private async Task _DownloadImage(string url, string localPath, string what)
+ private static async Task _WriteTo(SKBitmap bitmap, string path)
{
- if (url == localPath)
- return false;
+ SKData data = bitmap.Encode(SKEncodedImageFormat.Jpeg, 18);
+ await using Stream reader = data.AsStream();
+ await using Stream file = File.Create(path);
+ await reader.CopyToAsync(file);
+ }
+ private async Task _DownloadImage(string? url, string localPath, string what)
+ {
+ if (url == null)
+ return;
try
{
_logger.LogInformation("Downloading image {What}", what);
@@ -73,86 +72,53 @@ namespace Kyoo.Core.Controllers
HttpClient client = _clientFactory.CreateClient();
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
- string mime = response.Content.Headers.ContentType?.MediaType!;
await using Stream reader = await response.Content.ReadAsStreamAsync();
+ SKBitmap bitmap = SKBitmap.Decode(reader);
- string extension = new FileExtensionContentTypeProvider()
- .Mappings.FirstOrDefault(x => x.Value == mime)
- .Key;
- await using Stream local = File.Create(localPath + extension);
- await reader.CopyToAsync(local);
- return true;
+ bitmap.Resize(new SKSizeI(bitmap.Width, bitmap.Height), SKFilterQuality.High);
+ await _WriteTo(bitmap, $"{localPath}.{ImageQuality.Large.ToString().ToLowerInvariant()}.jpg");
+
+ bitmap.Resize(new SKSizeI(bitmap.Width, bitmap.Height), SKFilterQuality.Medium);
+ await _WriteTo(bitmap, $"{localPath}.{ImageQuality.Medium.ToString().ToLowerInvariant()}.jpg");
+
+ bitmap.Resize(new SKSizeI(bitmap.Width, bitmap.Height), SKFilterQuality.Low);
+ await _WriteTo(bitmap, $"{localPath}.{ImageQuality.Small.ToString().ToLowerInvariant()}.jpg");
}
catch (Exception ex)
{
_logger.LogError(ex, "{What} could not be downloaded", what);
- return false;
}
}
///
- public async Task DownloadImages(T item, bool alwaysDownload = false)
+ public async Task DownloadImages(T item)
where T : IThumbnails
{
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.Where(x => x.Value != null))
- {
- string localPath = _GetPrivateImagePath(item, id);
- if (alwaysDownload || !Path.Exists(localPath))
- ret |= await _DownloadImage(image, localPath, $"The image n {id} of {name}");
- }
-
- return ret;
+ await _DownloadImage(item.Poster.Source, _GetBaseImagePath(item, "poster"), $"The poster of {name}");
+ await _DownloadImage(item.Thumbnail.Source, _GetBaseImagePath(item, "thumbnail"), $"The poster of {name}");
+ await _DownloadImage(item.Logo.Source, _GetBaseImagePath(item, "logo"), $"The poster of {name}");
}
- ///
- /// 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 static string _GetPrivateImagePath(T item, int imageId)
+ private static string _GetBaseImagePath(T item, string image)
{
- if (item == null)
- throw new ArgumentNullException(nameof(item));
-
string directory = item switch
{
IResource res => Path.Combine("./metadata", typeof(T).Name.ToLowerInvariant(), res.Slug),
_ => Path.Combine("./metadata", typeof(T).Name.ToLowerInvariant())
};
Directory.CreateDirectory(directory);
- string imageName = imageId switch
- {
- Images.Poster => "poster",
- Images.Logo => "logo",
- Images.Thumbnail => "thumbnail",
- Images.Trailer => "trailer",
- _ => $"{imageId}"
- };
- return Path.Combine(directory, imageName);
+ return Path.Combine(directory, image);
}
///
- public string? GetImagePath(T item, int imageId)
+ public string GetImagePath(T item, string image, ImageQuality quality)
where T : IThumbnails
{
- string basePath = _GetPrivateImagePath(item, imageId);
- string directory = Path.GetDirectoryName(basePath)!;
- string baseFile = Path.GetFileName(basePath);
- if (!Directory.Exists(directory))
- return null;
- return Directory.GetFiles(directory, "*", SearchOption.TopDirectoryOnly)
- .FirstOrDefault(x => Path.GetFileNameWithoutExtension(x) == baseFile);
+ return $"{_GetBaseImagePath(item, image)}.{quality.ToString().ToLowerInvariant()}.jpg";
}
}
}
diff --git a/back/src/Kyoo.Core/CoreModule.cs b/back/src/Kyoo.Core/CoreModule.cs
index 5c55c01b..162ce4bc 100644
--- a/back/src/Kyoo.Core/CoreModule.cs
+++ b/back/src/Kyoo.Core/CoreModule.cs
@@ -57,7 +57,6 @@ namespace Kyoo.Core
builder.RegisterRepository();
builder.RegisterRepository();
builder.RegisterRepository();
- builder.RegisterRepository();
builder.RegisterRepository();
}
diff --git a/back/src/Kyoo.Core/Kyoo.Core.csproj b/back/src/Kyoo.Core/Kyoo.Core.csproj
index 917b6986..0839dfe5 100644
--- a/back/src/Kyoo.Core/Kyoo.Core.csproj
+++ b/back/src/Kyoo.Core/Kyoo.Core.csproj
@@ -6,9 +6,11 @@
+
+
diff --git a/back/src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs b/back/src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs
index 20a9715c..e2c9d6c4 100644
--- a/back/src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs
+++ b/back/src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs
@@ -16,7 +16,6 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see .
-using System;
using System.IO;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
@@ -25,7 +24,6 @@ using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Abstractions.Models.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.StaticFiles;
using static Kyoo.Abstractions.Models.Utils.Constants;
namespace Kyoo.Core.Api
@@ -59,37 +57,7 @@ namespace Kyoo.Core.Api
_thumbs = thumbs;
}
- ///
- /// Get the content type of a file using it's extension.
- ///
- /// The path of the file
- /// The extension of the file is not known.
- /// The content type of the file
- private static string _GetContentType(string path)
- {
- FileExtensionContentTypeProvider provider = new();
- if (provider.TryGetContentType(path, out string contentType))
- return contentType;
- throw new NotImplementedException($"Can't get the content type of the file at: {path}");
- }
-
- ///
- /// Get image
- ///
- ///
- /// Get an image for the specified item.
- /// List of commonly available images:
- /// - Poster: Image 0, also available at /poster
- /// - Thumbnail: Image 1, also available at /thumbnail
- /// - Logo: Image 3, also available at /logo
- ///
- /// Other images can be arbitrarily added by plugins so any image number can be specified from this endpoint.
- ///
- /// The ID or slug of the resource to get the image for.
- /// The number of the image to retrieve.
- /// The image asked.
- /// No item exist with the specific identifier or the image does not exists on kyoo.
- private async Task _GetImage(Identifier identifier, int image)
+ private async Task _GetImage(Identifier identifier, string image, ImageQuality? quality)
{
T resource = await identifier.Match(
id => Repository.GetOrDefault(id),
@@ -97,10 +65,10 @@ namespace Kyoo.Core.Api
);
if (resource == null)
return NotFound();
- string path = _thumbs.GetImagePath(resource, image);
+ string path = _thumbs.GetImagePath(resource, image, quality ?? ImageQuality.Large);
if (path == null || !System.IO.File.Exists(path))
return NotFound();
- return PhysicalFile(Path.GetFullPath(path), _GetContentType(path), true);
+ return PhysicalFile(Path.GetFullPath(path), "image/jpeg", true);
}
///
@@ -110,17 +78,18 @@ namespace Kyoo.Core.Api
/// Get the poster for the specified item.
///
/// The ID or slug of the resource to get the image for.
+ /// The quality of the image to retrieve.
/// The image asked.
///
/// No item exist with the specific identifier or the image does not exists on kyoo.
///
- [HttpGet("{identifier:id}/poster", Order = AlternativeRoute)]
+ [HttpGet("{identifier:id}/poster")]
[PartialPermission(Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public Task GetPoster(Identifier identifier)
+ public Task GetPoster(Identifier identifier, [FromQuery] ImageQuality? quality)
{
- return _GetImage(identifier, Images.Poster);
+ return _GetImage(identifier, "poster", quality);
}
///
@@ -130,17 +99,18 @@ namespace Kyoo.Core.Api
/// Get the logo for the specified item.
///
/// The ID or slug of the resource to get the image for.
+ /// The quality of the image to retrieve.
/// The image asked.
///
/// No item exist with the specific identifier or the image does not exists on kyoo.
///
- [HttpGet("{identifier:id}/logo", Order = AlternativeRoute)]
+ [HttpGet("{identifier:id}/logo")]
[PartialPermission(Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public Task GetLogo(Identifier identifier)
+ public Task GetLogo(Identifier identifier, [FromQuery] ImageQuality? quality)
{
- return _GetImage(identifier, Images.Logo);
+ return _GetImage(identifier, "logo", quality);
}
///
@@ -150,15 +120,16 @@ namespace Kyoo.Core.Api
/// Get the thumbnail for the specified item.
///
/// The ID or slug of the resource to get the image for.
+ /// The quality of the image to retrieve.
/// The image asked.
///
/// No item exist with the specific identifier or the image does not exists on kyoo.
///
+ [HttpGet("{identifier:id}/thumbnail")]
[HttpGet("{identifier:id}/backdrop", Order = AlternativeRoute)]
- [HttpGet("{identifier:id}/thumbnail", Order = AlternativeRoute)]
- public Task GetBackdrop(Identifier identifier)
+ public Task GetBackdrop(Identifier identifier, [FromQuery] ImageQuality? quality)
{
- return _GetImage(identifier, Images.Thumbnail);
+ return _GetImage(identifier, "thumbnail", quality);
}
///
diff --git a/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs b/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs
index a1394b87..c0b1d8bb 100644
--- a/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs
+++ b/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs
@@ -79,28 +79,28 @@ namespace Kyoo.Core.Api
protected override IList CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList properties = base.CreateProperties(type, memberSerialization);
- if (!type.IsAssignableTo(typeof(IThumbnails)))
+ if (!type.IsAssignableTo(typeof(Image)))
return properties;
- foreach ((int id, string image) in Images.ImageName)
- {
- properties.Add(new JsonProperty
- {
- DeclaringType = type,
- PropertyName = image.ToLower(),
- UnderlyingName = image,
- PropertyType = typeof(string),
- Readable = true,
- Writable = false,
- ItemIsReference = false,
- TypeNameHandling = TypeNameHandling.None,
- ShouldSerialize = x =>
- {
- IThumbnails thumb = (IThumbnails)x;
- return thumb?.Images?.ContainsKey(id) == true;
- },
- ValueProvider = new ThumbnailProvider(id)
- });
- }
+ // foreach ((int id, string image) in Images.ImageName)
+ // {
+ // properties.Add(new JsonProperty
+ // {
+ // DeclaringType = type,
+ // PropertyName = image.ToLower(),
+ // UnderlyingName = image,
+ // PropertyType = typeof(string),
+ // Readable = true,
+ // Writable = false,
+ // ItemIsReference = false,
+ // TypeNameHandling = TypeNameHandling.None,
+ // ShouldSerialize = x =>
+ // {
+ // IThumbnails thumb = (IThumbnails)x;
+ // return thumb?.Images?.ContainsKey(id) == true;
+ // },
+ // ValueProvider = new ThumbnailProvider(id)
+ // });
+ // }
return properties;
}
@@ -128,23 +128,22 @@ namespace Kyoo.Core.Api
///
public void SetValue(object target, object value)
{
- if (target is not IThumbnails thumb)
- throw new ArgumentException($"The given object is not an Thumbnail.");
- thumb.Images[_imageIndex] = value as string;
+ throw new NotSupportedException();
}
///
public object GetValue(object target)
{
- string slug = (target as IResource)?.Slug ?? (target as ICustomTypeDescriptor)?.GetComponentName();
- if (target is not IThumbnails thumb
- || slug == null
- || string.IsNullOrEmpty(thumb.Images?.GetValueOrDefault(_imageIndex)))
- return null;
- string type = target is ICustomTypeDescriptor descriptor
- ? descriptor.GetClassName()
- : target.GetType().Name;
- return $"/{type}/{slug}/{Images.ImageName[_imageIndex]}".ToLowerInvariant();
+ return null;
+ // string slug = (target as IResource)?.Slug ?? (target as ICustomTypeDescriptor)?.GetComponentName();
+ // if (target is not IThumbnails thumb
+ // || slug == null
+ // || string.IsNullOrEmpty(thumb.Images?.GetValueOrDefault(_imageIndex)))
+ // return null;
+ // string type = target is ICustomTypeDescriptor descriptor
+ // ? descriptor.GetClassName()
+ // : target.GetType().Name;
+ // return $"/{type}/{slug}/{Images.ImageName[_imageIndex]}".ToLowerInvariant();
}
}
}
diff --git a/back/src/Kyoo.Postgresql/DatabaseContext.cs b/back/src/Kyoo.Postgresql/DatabaseContext.cs
index 19303e75..b5a15d25 100644
--- a/back/src/Kyoo.Postgresql/DatabaseContext.cs
+++ b/back/src/Kyoo.Postgresql/DatabaseContext.cs
@@ -20,6 +20,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
@@ -80,11 +81,6 @@ namespace Kyoo.Postgresql
///
public DbSet Studios { get; set; }
- ///
- /// All providers of Kyoo. See .
- ///
- public DbSet Providers { get; set; }
-
///
/// The list of registered users.
///
@@ -108,17 +104,6 @@ namespace Kyoo.Postgresql
///
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, IMetadata
- {
- return Set(MetadataName());
- }
-
///
/// Add a many to many link between two resources.
///
@@ -194,17 +179,33 @@ namespace Kyoo.Postgresql
///
/// The database model builder
/// The type to add metadata to.
- private void _HasMetadata(ModelBuilder modelBuilder)
+ private static void _HasMetadata(ModelBuilder modelBuilder)
where T : class, IMetadata
{
- modelBuilder.SharedTypeEntity(MetadataName())
- .HasKey(MetadataID.PrimaryKey);
+ // TODO: Waiting for https://github.com/dotnet/efcore/issues/29825
+ // modelBuilder.Entity()
+ // .OwnsOne(x => x.ExternalIDs, x =>
+ // {
+ // x.ToJson();
+ // });
+ modelBuilder.Entity()
+ .Property(x => x.ExternalId)
+ .HasConversion(
+ v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
+ v => JsonSerializer.Deserialize>(v, (JsonSerializerOptions)null)
+ )
+ .HasColumnType("json");
+ }
- modelBuilder.SharedTypeEntity(MetadataName())
- .HasOne()
- .WithMany(x => x.ExternalIDs)
- .HasForeignKey(x => x.ResourceID)
- .OnDelete(DeleteBehavior.Cascade);
+ private static void _HasImages(ModelBuilder modelBuilder)
+ where T : class, IThumbnails
+ {
+ modelBuilder.Entity()
+ .OwnsOne(x => x.Poster);
+ modelBuilder.Entity()
+ .OwnsOne(x => x.Thumbnail);
+ modelBuilder.Entity()
+ .OwnsOne(x => x.Logo);
}
///
@@ -269,7 +270,6 @@ namespace Kyoo.Postgresql
.WithMany(x => x.Shows)
.OnDelete(DeleteBehavior.SetNull);
- _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);
@@ -287,6 +287,15 @@ namespace Kyoo.Postgresql
_HasMetadata(modelBuilder);
_HasMetadata(modelBuilder);
+ _HasImages(modelBuilder);
+ _HasImages(modelBuilder);
+ _HasImages(modelBuilder);
+ _HasImages(modelBuilder);
+ _HasImages(modelBuilder);
+ _HasImages(modelBuilder);
+
+ modelBuilder.Entity().OwnsOne(x => x.Logo);
+
modelBuilder.Entity()
.HasKey(x => new { User = x.UserID, Episode = x.EpisodeID });
@@ -294,7 +303,6 @@ namespace Kyoo.Postgresql
modelBuilder.Entity().Property(x => x.Slug).IsRequired();
modelBuilder.Entity().Property(x => x.Slug).IsRequired();
modelBuilder.Entity().Property(x => x.Slug).IsRequired();
- modelBuilder.Entity().Property(x => x.Slug).IsRequired();
modelBuilder.Entity().Property(x => x.Slug).IsRequired();
modelBuilder.Entity().Property(x => x.Slug).IsRequired();
modelBuilder.Entity().Property(x => x.Slug).IsRequired();
@@ -319,9 +327,6 @@ namespace Kyoo.Postgresql
modelBuilder.Entity()
.HasIndex(x => x.Slug)
.IsUnique();
- modelBuilder.Entity()
- .HasIndex(x => x.Slug)
- .IsUnique();
modelBuilder.Entity()
.HasIndex(x => new { x.ShowID, x.SeasonNumber })
.IsUnique();
diff --git a/back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.Designer.cs b/back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.Designer.cs
new file mode 100644
index 00000000..cc630b9f
--- /dev/null
+++ b/back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.Designer.cs
@@ -0,0 +1,1339 @@
+//
+using System;
+using System.Collections.Generic;
+using Kyoo.Abstractions.Models;
+using Kyoo.Postgresql;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Kyoo.Postgresql.Migrations
+{
+ [DbContext(typeof(PostgresContext))]
+ [Migration("20230804143919_AddBlurhash")]
+ partial class AddBlurhash
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "7.0.9")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "item_type", new[] { "show", "movie", "collection" });
+ NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "status", new[] { "unknown", "finished", "airing", "planned" });
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID"));
+
+ b.Property("ExternalIDs")
+ .HasColumnType("json")
+ .HasColumnName("external_i_ds");
+
+ b.Property("Name")
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.Property("Overview")
+ .HasColumnType("text")
+ .HasColumnName("overview");
+
+ b.Property("Slug")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("slug");
+
+ b.HasKey("ID")
+ .HasName("pk_collections");
+
+ b.HasIndex("Slug")
+ .IsUnique()
+ .HasDatabaseName("ix_collections_slug");
+
+ b.ToTable("collections", (string)null);
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.Episode", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID"));
+
+ b.Property("AbsoluteNumber")
+ .HasColumnType("integer")
+ .HasColumnName("absolute_number");
+
+ b.Property("EpisodeNumber")
+ .HasColumnType("integer")
+ .HasColumnName("episode_number");
+
+ b.Property("ExternalIDs")
+ .HasColumnType("json")
+ .HasColumnName("external_i_ds");
+
+ b.Property("Overview")
+ .HasColumnType("text")
+ .HasColumnName("overview");
+
+ b.Property("Path")
+ .HasColumnType("text")
+ .HasColumnName("path");
+
+ b.Property("ReleaseDate")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("release_date");
+
+ b.Property("SeasonID")
+ .HasColumnType("integer")
+ .HasColumnName("season_id");
+
+ b.Property("SeasonNumber")
+ .HasColumnType("integer")
+ .HasColumnName("season_number");
+
+ b.Property("ShowID")
+ .HasColumnType("integer")
+ .HasColumnName("show_id");
+
+ b.Property("Slug")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("slug");
+
+ b.Property("Title")
+ .HasColumnType("text")
+ .HasColumnName("title");
+
+ b.HasKey("ID")
+ .HasName("pk_episodes");
+
+ b.HasIndex("SeasonID")
+ .HasDatabaseName("ix_episodes_season_id");
+
+ b.HasIndex("Slug")
+ .IsUnique()
+ .HasDatabaseName("ix_episodes_slug");
+
+ b.HasIndex("ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber")
+ .IsUnique()
+ .HasDatabaseName("ix_episodes_show_id_season_number_episode_number_absolute_numb");
+
+ b.ToTable("episodes", (string)null);
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.Genre", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID"));
+
+ b.Property("Name")
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.Property("Slug")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("slug");
+
+ b.HasKey("ID")
+ .HasName("pk_genres");
+
+ b.HasIndex("Slug")
+ .IsUnique()
+ .HasDatabaseName("ix_genres_slug");
+
+ b.ToTable("genres", (string)null);
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.Library", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID"));
+
+ b.Property("Name")
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.Property("Paths")
+ .HasColumnType("text[]")
+ .HasColumnName("paths");
+
+ b.Property("Slug")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("slug");
+
+ b.HasKey("ID")
+ .HasName("pk_libraries");
+
+ b.HasIndex("Slug")
+ .IsUnique()
+ .HasDatabaseName("ix_libraries_slug");
+
+ b.ToTable("libraries", (string)null);
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.LibraryItem", b =>
+ {
+ b.Property("ID")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b.Property("EndAir")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("end_air");
+
+ b.Property("Overview")
+ .HasColumnType("text")
+ .HasColumnName("overview");
+
+ b.Property("Slug")
+ .HasColumnType("text")
+ .HasColumnName("slug");
+
+ b.Property("StartAir")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("start_air");
+
+ b.Property("Status")
+ .HasColumnType("status")
+ .HasColumnName("status");
+
+ b.Property("Title")
+ .HasColumnType("text")
+ .HasColumnName("title");
+
+ b.Property("Type")
+ .HasColumnType("item_type")
+ .HasColumnName("type");
+
+ b.HasKey("ID")
+ .HasName("pk_library_items");
+
+ b.ToTable((string)null);
+
+ b.ToView("library_items", (string)null);
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.People", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID"));
+
+ b.Property("ExternalIDs")
+ .HasColumnType("json")
+ .HasColumnName("external_i_ds");
+
+ b.Property("Name")
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.Property("Slug")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("slug");
+
+ b.HasKey("ID")
+ .HasName("pk_people");
+
+ b.HasIndex("Slug")
+ .IsUnique()
+ .HasDatabaseName("ix_people_slug");
+
+ b.ToTable("people", (string)null);
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.PeopleRole", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID"));
+
+ b.Property("PeopleID")
+ .HasColumnType("integer")
+ .HasColumnName("people_id");
+
+ b.Property("Role")
+ .HasColumnType("text")
+ .HasColumnName("role");
+
+ b.Property("ShowID")
+ .HasColumnType("integer")
+ .HasColumnName("show_id");
+
+ b.Property("Type")
+ .HasColumnType("text")
+ .HasColumnName("type");
+
+ b.HasKey("ID")
+ .HasName("pk_people_roles");
+
+ b.HasIndex("PeopleID")
+ .HasDatabaseName("ix_people_roles_people_id");
+
+ b.HasIndex("ShowID")
+ .HasDatabaseName("ix_people_roles_show_id");
+
+ b.ToTable("people_roles", (string)null);
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.Season", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID"));
+
+ b.Property("EndDate")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("end_date");
+
+ b.Property("ExternalIDs")
+ .HasColumnType("json")
+ .HasColumnName("external_i_ds");
+
+ b.Property("Overview")
+ .HasColumnType("text")
+ .HasColumnName("overview");
+
+ b.Property("SeasonNumber")
+ .HasColumnType("integer")
+ .HasColumnName("season_number");
+
+ b.Property("ShowID")
+ .HasColumnType("integer")
+ .HasColumnName("show_id");
+
+ b.Property("Slug")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("slug");
+
+ b.Property("StartDate")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("start_date");
+
+ b.Property("Title")
+ .HasColumnType("text")
+ .HasColumnName("title");
+
+ b.HasKey("ID")
+ .HasName("pk_seasons");
+
+ b.HasIndex("Slug")
+ .IsUnique()
+ .HasDatabaseName("ix_seasons_slug");
+
+ b.HasIndex("ShowID", "SeasonNumber")
+ .IsUnique()
+ .HasDatabaseName("ix_seasons_show_id_season_number");
+
+ b.ToTable("seasons", (string)null);
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.Show", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID"));
+
+ b.Property("Aliases")
+ .HasColumnType("text[]")
+ .HasColumnName("aliases");
+
+ b.Property("EndAir")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("end_air");
+
+ b.Property("ExternalIDs")
+ .HasColumnType("json")
+ .HasColumnName("external_i_ds");
+
+ b.Property("IsMovie")
+ .HasColumnType("boolean")
+ .HasColumnName("is_movie");
+
+ b.Property("Overview")
+ .HasColumnType("text")
+ .HasColumnName("overview");
+
+ b.Property("Path")
+ .HasColumnType("text")
+ .HasColumnName("path");
+
+ b.Property("Slug")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("slug");
+
+ b.Property("StartAir")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("start_air");
+
+ b.Property("Status")
+ .HasColumnType("status")
+ .HasColumnName("status");
+
+ b.Property("StudioID")
+ .HasColumnType("integer")
+ .HasColumnName("studio_id");
+
+ b.Property("Title")
+ .HasColumnType("text")
+ .HasColumnName("title");
+
+ b.Property("Trailer")
+ .HasColumnType("text")
+ .HasColumnName("trailer");
+
+ b.HasKey("ID")
+ .HasName("pk_shows");
+
+ b.HasIndex("Slug")
+ .IsUnique()
+ .HasDatabaseName("ix_shows_slug");
+
+ b.HasIndex("StudioID")
+ .HasDatabaseName("ix_shows_studio_id");
+
+ b.ToTable("shows", (string)null);
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.Studio", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID"));
+
+ b.Property("ExternalIDs")
+ .HasColumnType("json")
+ .HasColumnName("external_i_ds");
+
+ b.Property("Name")
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.Property("Slug")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("slug");
+
+ b.HasKey("ID")
+ .HasName("pk_studios");
+
+ b.HasIndex("Slug")
+ .IsUnique()
+ .HasDatabaseName("ix_studios_slug");
+
+ b.ToTable("studios", (string)null);
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.User", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID"));
+
+ b.Property("Email")
+ .HasColumnType("text")
+ .HasColumnName("email");
+
+ b.Property>("ExtraData")
+ .HasColumnType("jsonb")
+ .HasColumnName("extra_data");
+
+ b.Property("Password")
+ .HasColumnType("text")
+ .HasColumnName("password");
+
+ b.Property("Permissions")
+ .HasColumnType("text[]")
+ .HasColumnName("permissions");
+
+ b.Property("Slug")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("slug");
+
+ b.Property("Username")
+ .HasColumnType("text")
+ .HasColumnName("username");
+
+ b.HasKey("ID")
+ .HasName("pk_users");
+
+ b.HasIndex("Slug")
+ .IsUnique()
+ .HasDatabaseName("ix_users_slug");
+
+ b.ToTable("users", (string)null);
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.WatchedEpisode", b =>
+ {
+ b.Property("UserID")
+ .HasColumnType("integer")
+ .HasColumnName("user_id");
+
+ b.Property("EpisodeID")
+ .HasColumnType("integer")
+ .HasColumnName("episode_id");
+
+ b.Property("WatchedPercentage")
+ .HasColumnType("integer")
+ .HasColumnName("watched_percentage");
+
+ b.HasKey("UserID", "EpisodeID")
+ .HasName("pk_watched_episodes");
+
+ b.HasIndex("EpisodeID")
+ .HasDatabaseName("ix_watched_episodes_episode_id");
+
+ b.ToTable("watched_episodes", (string)null);
+ });
+
+ 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", (string)null);
+ });
+
+ 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", (string)null);
+ });
+
+ 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", (string)null);
+ });
+
+ 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", (string)null);
+ });
+
+ 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", (string)null);
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b =>
+ {
+ b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 =>
+ {
+ b1.Property("CollectionID")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b1.Property("Blurhash")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("logo_blurhash");
+
+ b1.Property("Source")
+ .HasColumnType("text")
+ .HasColumnName("logo_source");
+
+ b1.HasKey("CollectionID");
+
+ b1.ToTable("collections");
+
+ b1.WithOwner()
+ .HasForeignKey("CollectionID")
+ .HasConstraintName("fk_collections_collections_id");
+ });
+
+ b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 =>
+ {
+ b1.Property("CollectionID")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b1.Property("Blurhash")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("poster_blurhash");
+
+ b1.Property("Source")
+ .HasColumnType("text")
+ .HasColumnName("poster_source");
+
+ b1.HasKey("CollectionID");
+
+ b1.ToTable("collections");
+
+ b1.WithOwner()
+ .HasForeignKey("CollectionID")
+ .HasConstraintName("fk_collections_collections_id");
+ });
+
+ b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 =>
+ {
+ b1.Property("CollectionID")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b1.Property("Blurhash")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("thumbnail_blurhash");
+
+ b1.Property("Source")
+ .HasColumnType("text")
+ .HasColumnName("thumbnail_source");
+
+ b1.HasKey("CollectionID");
+
+ b1.ToTable("collections");
+
+ b1.WithOwner()
+ .HasForeignKey("CollectionID")
+ .HasConstraintName("fk_collections_collections_id");
+ });
+
+ b.Navigation("Logo");
+
+ b.Navigation("Poster");
+
+ b.Navigation("Thumbnail");
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.Episode", b =>
+ {
+ b.HasOne("Kyoo.Abstractions.Models.Season", "Season")
+ .WithMany("Episodes")
+ .HasForeignKey("SeasonID")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("fk_episodes_seasons_season_id");
+
+ b.HasOne("Kyoo.Abstractions.Models.Show", "Show")
+ .WithMany("Episodes")
+ .HasForeignKey("ShowID")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("fk_episodes_shows_show_id");
+
+ b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 =>
+ {
+ b1.Property("EpisodeID")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b1.Property("Blurhash")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("logo_blurhash");
+
+ b1.Property("Source")
+ .HasColumnType("text")
+ .HasColumnName("logo_source");
+
+ b1.HasKey("EpisodeID");
+
+ b1.ToTable("episodes");
+
+ b1.WithOwner()
+ .HasForeignKey("EpisodeID")
+ .HasConstraintName("fk_episodes_episodes_id");
+ });
+
+ b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 =>
+ {
+ b1.Property("EpisodeID")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b1.Property("Blurhash")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("poster_blurhash");
+
+ b1.Property("Source")
+ .HasColumnType("text")
+ .HasColumnName("poster_source");
+
+ b1.HasKey("EpisodeID");
+
+ b1.ToTable("episodes");
+
+ b1.WithOwner()
+ .HasForeignKey("EpisodeID")
+ .HasConstraintName("fk_episodes_episodes_id");
+ });
+
+ b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 =>
+ {
+ b1.Property("EpisodeID")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b1.Property("Blurhash")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("thumbnail_blurhash");
+
+ b1.Property("Source")
+ .HasColumnType("text")
+ .HasColumnName("thumbnail_source");
+
+ b1.HasKey("EpisodeID");
+
+ b1.ToTable("episodes");
+
+ b1.WithOwner()
+ .HasForeignKey("EpisodeID")
+ .HasConstraintName("fk_episodes_episodes_id");
+ });
+
+ b.Navigation("Logo");
+
+ b.Navigation("Poster");
+
+ b.Navigation("Season");
+
+ b.Navigation("Show");
+
+ b.Navigation("Thumbnail");
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.LibraryItem", b =>
+ {
+ b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 =>
+ {
+ b1.Property("LibraryItemID")
+ .HasColumnType("integer")
+ .HasColumnName("library_item_id");
+
+ b1.Property("Blurhash")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("blurhash");
+
+ b1.Property("Source")
+ .HasColumnType("text")
+ .HasColumnName("source");
+
+ b1.HasKey("LibraryItemID");
+
+ b1.ToTable((string)null);
+
+ b1.ToView("library_items");
+
+ b1.WithOwner()
+ .HasForeignKey("LibraryItemID");
+ });
+
+ b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 =>
+ {
+ b1.Property("LibraryItemID")
+ .HasColumnType("integer")
+ .HasColumnName("library_item_id");
+
+ b1.Property("Blurhash")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("blurhash");
+
+ b1.Property("Source")
+ .HasColumnType("text")
+ .HasColumnName("source");
+
+ b1.HasKey("LibraryItemID");
+
+ b1.ToTable((string)null);
+
+ b1.ToView("library_items");
+
+ b1.WithOwner()
+ .HasForeignKey("LibraryItemID");
+ });
+
+ b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 =>
+ {
+ b1.Property("LibraryItemID")
+ .HasColumnType("integer")
+ .HasColumnName("library_item_id");
+
+ b1.Property("Blurhash")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("blurhash");
+
+ b1.Property("Source")
+ .HasColumnType("text")
+ .HasColumnName("source");
+
+ b1.HasKey("LibraryItemID");
+
+ b1.ToTable((string)null);
+
+ b1.ToView("library_items");
+
+ b1.WithOwner()
+ .HasForeignKey("LibraryItemID");
+ });
+
+ b.Navigation("Logo");
+
+ b.Navigation("Poster");
+
+ b.Navigation("Thumbnail");
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.People", b =>
+ {
+ b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 =>
+ {
+ b1.Property("PeopleID")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b1.Property("Blurhash")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("logo_blurhash");
+
+ b1.Property("Source")
+ .HasColumnType("text")
+ .HasColumnName("logo_source");
+
+ b1.HasKey("PeopleID");
+
+ b1.ToTable("people");
+
+ b1.WithOwner()
+ .HasForeignKey("PeopleID")
+ .HasConstraintName("fk_people_people_id");
+ });
+
+ b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 =>
+ {
+ b1.Property("PeopleID")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b1.Property("Blurhash")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("poster_blurhash");
+
+ b1.Property("Source")
+ .HasColumnType("text")
+ .HasColumnName("poster_source");
+
+ b1.HasKey("PeopleID");
+
+ b1.ToTable("people");
+
+ b1.WithOwner()
+ .HasForeignKey("PeopleID")
+ .HasConstraintName("fk_people_people_id");
+ });
+
+ b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 =>
+ {
+ b1.Property("PeopleID")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b1.Property("Blurhash")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("thumbnail_blurhash");
+
+ b1.Property("Source")
+ .HasColumnType("text")
+ .HasColumnName("thumbnail_source");
+
+ b1.HasKey("PeopleID");
+
+ b1.ToTable("people");
+
+ b1.WithOwner()
+ .HasForeignKey("PeopleID")
+ .HasConstraintName("fk_people_people_id");
+ });
+
+ b.Navigation("Logo");
+
+ b.Navigation("Poster");
+
+ b.Navigation("Thumbnail");
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.PeopleRole", b =>
+ {
+ b.HasOne("Kyoo.Abstractions.Models.People", "People")
+ .WithMany("Roles")
+ .HasForeignKey("PeopleID")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("fk_people_roles_people_people_id");
+
+ b.HasOne("Kyoo.Abstractions.Models.Show", "Show")
+ .WithMany("People")
+ .HasForeignKey("ShowID")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("fk_people_roles_shows_show_id");
+
+ b.Navigation("People");
+
+ b.Navigation("Show");
+ });
+
+ modelBuilder.Entity("Kyoo.Abstractions.Models.Season", b =>
+ {
+ b.HasOne("Kyoo.Abstractions.Models.Show", "Show")
+ .WithMany("Seasons")
+ .HasForeignKey("ShowID")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("fk_seasons_shows_show_id");
+
+ b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 =>
+ {
+ b1.Property("SeasonID")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b1.Property("Blurhash")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("logo_blurhash");
+
+ b1.Property("Source")
+ .HasColumnType("text")
+ .HasColumnName("logo_source");
+
+ b1.HasKey("SeasonID");
+
+ b1.ToTable("seasons");
+
+ b1.WithOwner()
+ .HasForeignKey("SeasonID")
+ .HasConstraintName("fk_seasons_seasons_id");
+ });
+
+ b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 =>
+ {
+ b1.Property("SeasonID")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b1.Property("Blurhash")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("poster_blurhash");
+
+ b1.Property("Source")
+ .HasColumnType("text")
+ .HasColumnName("poster_source");
+
+ b1.HasKey("SeasonID");
+
+ b1.ToTable("seasons");
+
+ b1.WithOwner()
+ .HasForeignKey("SeasonID")
+ .HasConstraintName("fk_seasons_seasons_id");
+ });
+
+ b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 =>
+ {
+ b1.Property("SeasonID")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b1.Property