Starting to rework models

This commit is contained in:
Zoe Roux 2020-08-15 01:33:41 +02:00
parent 00ef372767
commit 8f6578dfb9
25 changed files with 403 additions and 203 deletions

View File

@ -66,6 +66,11 @@ namespace Kyoo.Controllers
_ => throw new ArgumentException($"The sort order, if set, should be :asc or :desc but it was :{order}.") _ => throw new ArgumentException($"The sort order, if set, should be :asc or :desc but it was :{order}.")
}; };
} }
public Sort<TValue> To<TValue>()
{
return new Sort<TValue>(Key.Convert<Func<TValue, object>>(), Descendant);
}
} }
public interface IRepository<T> : IDisposable, IAsyncDisposable where T : IResource public interface IRepository<T> : IDisposable, IAsyncDisposable where T : IResource

View File

@ -6,7 +6,10 @@ namespace Kyoo.Models.Exceptions
{ {
public override string Message { get; } public override string Message { get; }
public DuplicatedItemException() {} public DuplicatedItemException()
{
Message = "Already exists in the databse.";
}
public DuplicatedItemException(string message) public DuplicatedItemException(string message)
{ {

View File

@ -1,31 +1,17 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Kyoo.Models.Attributes;
namespace Kyoo.Models namespace Kyoo.Models
{ {
public class Collection : IResource public class Collection : IResource
{ {
[JsonIgnore] public int ID { get; set; } public int ID { get; set; }
public string Slug { get; set; } public string Slug { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Poster { get; set; } public string Poster { get; set; }
public string Overview { get; set; } public string Overview { get; set; }
[NotMergable] [JsonIgnore] public virtual IEnumerable<CollectionLink> Links { get; set; } [JsonIgnore] public virtual IEnumerable<Show> Shows { get; set; }
[JsonIgnore] public virtual IEnumerable<Show> Shows [JsonIgnore] public virtual IEnumerable<Library> Libraries { get; set; }
{
get => Links.Select(x => x.Show);
set => Links = value.Select(x => new CollectionLink(this, x));
}
[NotMergable] [JsonIgnore] public virtual IEnumerable<LibraryLink> LibraryLinks { get; set; }
[NotMergable] [JsonIgnore] public IEnumerable<Library> Libraries
{
get => LibraryLinks?.Select(x => x.Library);
set => LibraryLinks = value?.Select(x => new LibraryLink(x, this));
}
public Collection() { } public Collection() { }

View File

@ -7,10 +7,10 @@ namespace Kyoo.Models
{ {
public class Episode : IResource, IOnMerge public class Episode : IResource, IOnMerge
{ {
[JsonIgnore] public int ID { get; set; } public int ID { get; set; }
[JsonIgnore] public int ShowID { get; set; } public int ShowID { get; set; }
[JsonIgnore] public virtual Show Show { get; set; } [JsonIgnore] public virtual Show Show { get; set; }
[JsonIgnore] public int? SeasonID { get; set; } public int? SeasonID { get; set; }
[JsonIgnore] public virtual Season Season { get; set; } [JsonIgnore] public virtual Season Season { get; set; }
public int SeasonNumber { get; set; } = -1; public int SeasonNumber { get; set; } = -1;
@ -23,12 +23,12 @@ namespace Kyoo.Models
public int Runtime { get; set; } //This runtime variable should be in minutes public int Runtime { get; set; } //This runtime variable should be in minutes
[JsonIgnore] public string ImgPrimary { get; set; } [JsonIgnore] public string Poster { get; set; }
public virtual IEnumerable<MetadataID> ExternalIDs { get; set; } public virtual IEnumerable<MetadataID> ExternalIDs { get; set; }
[JsonIgnore] public virtual IEnumerable<Track> Tracks { get; set; } [JsonIgnore] public virtual IEnumerable<Track> Tracks { get; set; }
public string ShowTitle => Show.Title; // Used in the API response only public string ShowTitle => Show.Title;
public string Slug => GetSlug(Show.Slug, SeasonNumber, EpisodeNumber); public string Slug => GetSlug(Show.Slug, SeasonNumber, EpisodeNumber);
public string Thumb public string Thumb
{ {
@ -36,7 +36,7 @@ namespace Kyoo.Models
{ {
if (Show != null) if (Show != null)
return "thumb/" + Slug; return "thumb/" + Slug;
return ImgPrimary; return Poster;
} }
} }
@ -50,7 +50,7 @@ namespace Kyoo.Models
string overview, string overview,
DateTime? releaseDate, DateTime? releaseDate,
int runtime, int runtime,
string imgPrimary, string poster,
IEnumerable<MetadataID> externalIDs) IEnumerable<MetadataID> externalIDs)
{ {
SeasonNumber = seasonNumber; SeasonNumber = seasonNumber;
@ -60,7 +60,7 @@ namespace Kyoo.Models
Overview = overview; Overview = overview;
ReleaseDate = releaseDate; ReleaseDate = releaseDate;
Runtime = runtime; Runtime = runtime;
ImgPrimary = imgPrimary; Poster = poster;
ExternalIDs = externalIDs; ExternalIDs = externalIDs;
} }
@ -74,7 +74,7 @@ namespace Kyoo.Models
string overview, string overview,
DateTime? releaseDate, DateTime? releaseDate,
int runtime, int runtime,
string imgPrimary, string poster,
IEnumerable<MetadataID> externalIDs) IEnumerable<MetadataID> externalIDs)
{ {
ShowID = showID; ShowID = showID;
@ -87,7 +87,7 @@ namespace Kyoo.Models
Overview = overview; Overview = overview;
ReleaseDate = releaseDate; ReleaseDate = releaseDate;
Runtime = runtime; Runtime = runtime;
ImgPrimary = imgPrimary; Poster = poster;
ExternalIDs = externalIDs; ExternalIDs = externalIDs;
} }

View File

@ -1,6 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Kyoo.Models.Attributes;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Kyoo.Models namespace Kyoo.Models
@ -11,13 +9,7 @@ namespace Kyoo.Models
public string Slug { get; set; } public string Slug { get; set; }
public string Name { get; set; } public string Name { get; set; }
[NotMergable] [JsonIgnore] public virtual IEnumerable<GenreLink> Links { get; set; } [JsonIgnore] public virtual IEnumerable<Show> Shows { get; set; }
[NotMergable] [JsonIgnore] public IEnumerable<Show> Shows
{
get => Links.Select(x => x.Show);
set => Links = value?.Select(x => new GenreLink(x, this));
}
public Genre() {} public Genre() {}

View File

@ -1,6 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Kyoo.Models.Attributes;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Kyoo.Models namespace Kyoo.Models
@ -12,28 +10,10 @@ namespace Kyoo.Models
public string Name { get; set; } public string Name { get; set; }
public IEnumerable<string> Paths { get; set; } public IEnumerable<string> Paths { get; set; }
public IEnumerable<ProviderID> Providers public virtual IEnumerable<ProviderID> Providers { get; set; }
{
get => ProviderLinks?.Select(x => x.Provider);
set => ProviderLinks = value.Select(x => new ProviderLink(x, this)).ToList();
}
[NotMergable] [JsonIgnore] public virtual IEnumerable<ProviderLink> ProviderLinks { get; set; }
[NotMergable] [JsonIgnore] public virtual IEnumerable<LibraryLink> Links { get; set; }
[JsonIgnore] public IEnumerable<Show> Shows [JsonIgnore] public virtual IEnumerable<Show> Shows { get; set; }
{ [JsonIgnore] public virtual IEnumerable<Collection> Collections { get; set; }
get => Links?.Where(x => x.Show != null).Select(x => x.Show);
set => Links = Utility.MergeLists(
value?.Select(x => new LibraryLink(this, x)),
Links?.Where(x => x.Show == null));
}
[JsonIgnore] public IEnumerable<Collection> Collections
{
get => Links?.Where(x => x.Collection != null).Select(x => x.Collection);
set => Links = Utility.MergeLists(
value?.Select(x => new LibraryLink(this, x)),
Links?.Where(x => x.Collection == null));
}
public Library() { } public Library() { }

View File

@ -7,8 +7,7 @@ namespace Kyoo.Models
{ {
public class Show : IResource, IOnMerge public class Show : IResource, IOnMerge
{ {
[JsonIgnore] public int ID { get; set; } public int ID { get; set; }
public string Slug { get; set; } public string Slug { get; set; }
public string Title { get; set; } public string Title { get; set; }
public IEnumerable<string> Aliases { get; set; } public IEnumerable<string> Aliases { get; set; }
@ -24,38 +23,19 @@ namespace Kyoo.Models
public string Logo { get; set; } public string Logo { get; set; }
public string Backdrop { get; set; } public string Backdrop { get; set; }
public virtual IEnumerable<MetadataID> ExternalIDs { get; set; }
public bool IsMovie { get; set; } public bool IsMovie { get; set; }
public virtual IEnumerable<MetadataID> ExternalIDs { get; set; }
public virtual IEnumerable<Genre> Genres
{
get => GenreLinks?.Select(x => x.Genre);
set => GenreLinks = value?.Select(x => new GenreLink(this, x)).ToList();
}
[NotMergable] [JsonIgnore] public virtual IEnumerable<GenreLink> GenreLinks { get; set; }
[JsonIgnore] public int? StudioID { get; set; } [JsonIgnore] public int? StudioID { get; set; }
public virtual Studio Studio { get; set; } [JsonIgnore] public virtual Studio Studio { get; set; }
[JsonIgnore] public virtual IEnumerable<Genre> Genres { get; set; }
[JsonIgnore] public virtual IEnumerable<PeopleRole> People { get; set; } [JsonIgnore] public virtual IEnumerable<PeopleRole> People { get; set; }
[JsonIgnore] public virtual IEnumerable<Season> Seasons { get; set; } [JsonIgnore] public virtual IEnumerable<Season> Seasons { get; set; }
[JsonIgnore] public virtual IEnumerable<Episode> Episodes { get; set; } [JsonIgnore] public virtual IEnumerable<Episode> Episodes { get; set; }
[JsonIgnore] public virtual IEnumerable<Library> Libraries { get; set; }
[NotMergable] [JsonIgnore] public virtual IEnumerable<LibraryLink> LibraryLinks { get; set; } [JsonIgnore] public virtual IEnumerable<Collection> Collections { get; set; }
[NotMergable] [JsonIgnore] public IEnumerable<Library> Libraries
{
get => LibraryLinks?.Select(x => x.Library);
set => LibraryLinks = value?.Select(x => new LibraryLink(x, this));
}
[NotMergable] [JsonIgnore] public virtual IEnumerable<CollectionLink> CollectionLinks { get; set; }
[NotMergable] [JsonIgnore] public IEnumerable<Collection> Collections
{
get => CollectionLinks?.Select(x => x.Collection);
set => CollectionLinks = value?.Select(x => new CollectionLink(x, this));
}
public Show() { } public Show() { }
@ -117,14 +97,11 @@ namespace Kyoo.Models
return ExternalIDs?.FirstOrDefault(x => x.Provider.Name == provider)?.DataID; return ExternalIDs?.FirstOrDefault(x => x.Provider.Name == provider)?.DataID;
} }
public void OnMerge(object merged) public virtual void OnMerge(object merged)
{ {
if (ExternalIDs != null) if (ExternalIDs != null)
foreach (MetadataID id in ExternalIDs) foreach (MetadataID id in ExternalIDs)
id.Show = this; id.Show = this;
if (GenreLinks != null)
foreach (GenreLink genre in GenreLinks)
genre.Show = this;
if (People != null) if (People != null)
foreach (PeopleRole link in People) foreach (PeopleRole link in People)
link.Show = this; link.Show = this;

View File

@ -3,9 +3,11 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Kyoo.Models; using Kyoo.Models;
using Kyoo.Models.Attributes; using Kyoo.Models.Attributes;
@ -71,6 +73,21 @@ namespace Kyoo
return list.Concat(second.Where(x => !list.Any(y => isEqual(x, y)))).ToList(); return list.Concat(second.Where(x => !list.Any(y => isEqual(x, y)))).ToList();
} }
public static T Assign<T>(T first, T second)
{
Type type = typeof(T);
foreach (PropertyInfo property in type.GetProperties())
{
if (!property.CanRead || !property.CanWrite)
continue;
object value = property.GetValue(second);
property.SetValue(first, value);
}
return first;
}
public static T Complete<T>(T first, T second) public static T Complete<T>(T first, T second)
{ {
Type type = typeof(T); Type type = typeof(T);
@ -149,7 +166,7 @@ namespace Kyoo
[NotNull] Type owner, [NotNull] Type owner,
[NotNull] string methodName, [NotNull] string methodName,
[NotNull] Type type, [NotNull] Type type,
IEnumerable<object> args) params object[] args)
{ {
if (owner == null) if (owner == null)
throw new ArgumentNullException(nameof(owner)); throw new ArgumentNullException(nameof(owner));
@ -157,7 +174,7 @@ namespace Kyoo
throw new ArgumentNullException(nameof(methodName)); throw new ArgumentNullException(nameof(methodName));
if (type == null) if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
MethodInfo method = owner.GetMethod(methodName); MethodInfo method = owner.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (method == null) if (method == null)
throw new NullReferenceException($"A method named {methodName} could not be found on {owner.FullName}"); throw new NullReferenceException($"A method named {methodName} could not be found on {owner.FullName}");
return method.MakeGenericMethod(type).Invoke(null, args?.ToArray()); return method.MakeGenericMethod(type).Invoke(null, args?.ToArray());
@ -167,7 +184,7 @@ namespace Kyoo
[NotNull] object instance, [NotNull] object instance,
[NotNull] string methodName, [NotNull] string methodName,
[NotNull] Type type, [NotNull] Type type,
IEnumerable<object> args) params object[] args)
{ {
if (instance == null) if (instance == null)
throw new ArgumentNullException(nameof(instance)); throw new ArgumentNullException(nameof(instance));
@ -175,7 +192,7 @@ namespace Kyoo
throw new ArgumentNullException(nameof(methodName)); throw new ArgumentNullException(nameof(methodName));
if (type == null) if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
MethodInfo method = instance.GetType().GetMethod(methodName); MethodInfo method = instance.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (method == null) if (method == null)
throw new NullReferenceException($"A method named {methodName} could not be found on {instance.GetType().FullName}"); throw new NullReferenceException($"A method named {methodName} could not be found on {instance.GetType().FullName}");
return method.MakeGenericMethod(type).Invoke(instance, args?.ToArray()); return method.MakeGenericMethod(type).Invoke(instance, args?.ToArray());
@ -230,5 +247,66 @@ namespace Kyoo
return string.Empty; return string.Empty;
return "?" + string.Join('&', query.Select(x => $"{x.Key}={x.Value}")); return "?" + string.Join('&', query.Select(x => $"{x.Key}={x.Value}"));
} }
public static Task<T> Cast<T>(this Task task)
{
return (Task<T>)task;
}
public static Expression<T> Convert<T>(this Expression expr)
where T : Delegate
{
if (expr is LambdaExpression lambda)
return new ExpressionConverter<T>(lambda).VisitAndConvert();
throw new ArgumentException("Can't convert a non lambda.");
}
private class ExpressionConverter<TTo> : ExpressionVisitor
where TTo : Delegate
{
private readonly LambdaExpression _expression;
private readonly ParameterExpression[] _newParams;
internal ExpressionConverter(LambdaExpression expression)
{
_expression = expression;
Type[] paramTypes = typeof(TTo).GetGenericArguments()[..^1];
if (paramTypes.Length != _expression.Parameters.Count)
throw new ArgumentException("Parameter count from internal and external lambda are not matched.");
_newParams = new ParameterExpression[paramTypes.Length];
for (int i = 0; i < paramTypes.Length; i++)
{
if (_expression.Parameters[i].Type == paramTypes[i])
_newParams[i] = _expression.Parameters[i];
else
_newParams[i] = Expression.Parameter(paramTypes[i], _expression.Parameters[i].Name);
}
}
internal Expression<TTo> VisitAndConvert()
{
return (Expression<TTo>)RunGenericMethod(
this,
"VisitLambda",
_expression.GetType().GetGenericArguments().First(),
_expression);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
Type returnType = _expression.Type.GetGenericArguments().Last();
Expression body = node.ReturnType == returnType
? Visit(node.Body)
: Expression.Convert(Visit(node.Body)!, returnType);
return Expression.Lambda<TTo>(body!, _newParams);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return _newParams.First(x => x.Name == node.Name);
}
}
} }
} }

View File

@ -9,15 +9,16 @@ using Kyoo.CommonApi;
using Kyoo.Models; using Kyoo.Models;
using Kyoo.Models.Exceptions; using Kyoo.Models.Exceptions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Npgsql;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
public abstract class LocalRepository<T> : IRepository<T> where T : class, IResource public abstract class LocalRepository<T, TInternal> : IRepository<T>
where T : class, IResource
where TInternal : class, T
{ {
private readonly DbContext _database; private readonly DbContext _database;
protected abstract Expression<Func<T, object>> DefaultSort { get; } protected abstract Expression<Func<TInternal, object>> DefaultSort { get; }
protected LocalRepository(DbContext database) protected LocalRepository(DbContext database)
@ -35,14 +36,24 @@ namespace Kyoo.Controllers
return _database.DisposeAsync(); return _database.DisposeAsync();
} }
public virtual Task<T> Get(int id) public Task<T> Get(int id)
{ {
return _database.Set<T>().FirstOrDefaultAsync(x => x.ID == id); return _Get(id).Cast<T>();
} }
public virtual Task<T> Get(string slug) public Task<T> Get(string slug)
{ {
return _database.Set<T>().FirstOrDefaultAsync(x => x.Slug == slug); return _Get(slug).Cast<T>();
}
protected virtual Task<TInternal> _Get(int id)
{
return _database.Set<TInternal>().FirstOrDefaultAsync(x => x.ID == id);
}
protected virtual Task<TInternal> _Get(string slug)
{
return _database.Set<TInternal>().FirstOrDefaultAsync(x => x.Slug == slug);
} }
public abstract Task<ICollection<T>> Search(string query); public abstract Task<ICollection<T>> Search(string query);
@ -51,15 +62,31 @@ namespace Kyoo.Controllers
Sort<T> sort = default, Sort<T> sort = default,
Pagination limit = default) Pagination limit = default)
{ {
return ApplyFilters(_database.Set<T>(), where, sort, limit); return ApplyFilters(_database.Set<TInternal>(), where, sort, limit);
} }
protected Task<ICollection<T>> ApplyFilters(IQueryable<T> query, protected async Task<ICollection<T>> ApplyFilters(IQueryable<TInternal> query,
Expression<Func<T, bool>> where = null, Expression<Func<T, bool>> where = null,
Sort<T> sort = default, Sort<T> sort = default,
Pagination limit = default) Pagination limit = default)
{ {
return ApplyFilters(query, Get, DefaultSort, where, sort, limit); ICollection<TInternal> items = await ApplyFilters(query,
_Get,
DefaultSort,
where.Convert<Func<TInternal, bool>>(),
sort.To<TInternal>(),
limit);
return items.ToList<T>();
}
protected async Task<ICollection<T>> ApplyFilters(IQueryable<TInternal> query,
Expression<Func<TInternal, bool>> where = null,
Sort<TInternal> sort = default,
Pagination limit = default)
{
ICollection<TInternal> items = await ApplyFilters(query, _Get, DefaultSort, where, sort, limit);
return items.ToList<T>();
} }
protected async Task<ICollection<TValue>> ApplyFilters<TValue>(IQueryable<TValue> query, protected async Task<ICollection<TValue>> ApplyFilters<TValue>(IQueryable<TValue> query,
@ -125,7 +152,7 @@ namespace Kyoo.Controllers
if (edited == null) if (edited == null)
throw new ArgumentNullException(nameof(edited)); throw new ArgumentNullException(nameof(edited));
T old = await Get(edited.Slug); TInternal old = (TInternal)await Get(edited.Slug);
if (old == null) if (old == null)
throw new ItemNotFound($"No ressource found with the slug {edited.Slug}."); throw new ItemNotFound($"No ressource found with the slug {edited.Slug}.");
@ -138,9 +165,9 @@ namespace Kyoo.Controllers
return old; return old;
} }
protected virtual Task Validate(T ressource) protected virtual Task Validate(TInternal ressource)
{ {
foreach (PropertyInfo property in typeof(T).GetProperties() foreach (PropertyInfo property in typeof(TInternal).GetProperties()
.Where(x => typeof(IEnumerable).IsAssignableFrom(x.PropertyType) .Where(x => typeof(IEnumerable).IsAssignableFrom(x.PropertyType)
&& !typeof(string).IsAssignableFrom(x.PropertyType))) && !typeof(string).IsAssignableFrom(x.PropertyType)))
{ {

View File

@ -93,7 +93,7 @@ namespace Kyoo.Controllers
show.Slug = Utility.ToSlug(showName); show.Slug = Utility.ToSlug(showName);
show.Title ??= showName; show.Title ??= showName;
show.IsMovie = isMovie; show.IsMovie = isMovie;
show.GenreLinks = show.GenreLinks?.GroupBy(x => x.Genre.Slug).Select(x => x.First()).ToList(); show.Genres = show.Genres?.GroupBy(x => x.Slug).Select(x => x.First()).ToList();
show.People = show.People?.GroupBy(x => x.Slug).Select(x => x.First()).ToList(); show.People = show.People?.GroupBy(x => x.Slug).Select(x => x.First()).ToList();
return show; return show;
} }

View File

@ -10,12 +10,12 @@ using Microsoft.Extensions.DependencyInjection;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
public class CollectionRepository : LocalRepository<Collection>, ICollectionRepository public class CollectionRepository : LocalRepository<Collection, CollectionDE>, ICollectionRepository
{ {
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
private readonly Lazy<IShowRepository> _shows; private readonly Lazy<IShowRepository> _shows;
private readonly Lazy<ILibraryRepository> _libraries; private readonly Lazy<ILibraryRepository> _libraries;
protected override Expression<Func<Collection, object>> DefaultSort => x => x.Name; protected override Expression<Func<CollectionDE, object>> DefaultSort => x => x.Name;
public CollectionRepository(DatabaseContext database, IServiceProvider services) : base(database) public CollectionRepository(DatabaseContext database, IServiceProvider services) : base(database)
{ {
@ -47,7 +47,7 @@ namespace Kyoo.Controllers
return await _database.Collections return await _database.Collections
.Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) .Where(x => EF.Functions.ILike(x.Name, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync<Collection>();
} }
public override async Task<Collection> Create(Collection obj) public override async Task<Collection> Create(Collection obj)
@ -60,10 +60,11 @@ namespace Kyoo.Controllers
return obj; return obj;
} }
public override async Task Delete(Collection obj) public override async Task Delete(Collection item)
{ {
if (obj == null) if (item == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(item));
CollectionDE obj = new CollectionDE(item);
_database.Entry(obj).State = EntityState.Deleted; _database.Entry(obj).State = EntityState.Deleted;
if (obj.Links != null) if (obj.Links != null)
@ -82,7 +83,7 @@ namespace Kyoo.Controllers
{ {
ICollection<Collection> collections = await ApplyFilters(_database.CollectionLinks ICollection<Collection> collections = await ApplyFilters(_database.CollectionLinks
.Where(x => x.ShowID == showID) .Where(x => x.ShowID == showID)
.Select(x => x.Collection), .Select(x => x.Collection as CollectionDE),
where, where,
sort, sort,
limit); limit);
@ -98,7 +99,7 @@ namespace Kyoo.Controllers
{ {
ICollection<Collection> collections = await ApplyFilters(_database.CollectionLinks ICollection<Collection> collections = await ApplyFilters(_database.CollectionLinks
.Where(x => x.Show.Slug == showSlug) .Where(x => x.Show.Slug == showSlug)
.Select(x => x.Collection), .Select(x => x.Collection as CollectionDE),
where, where,
sort, sort,
limit); limit);
@ -114,7 +115,7 @@ namespace Kyoo.Controllers
{ {
ICollection<Collection> collections = await ApplyFilters(_database.LibraryLinks ICollection<Collection> collections = await ApplyFilters(_database.LibraryLinks
.Where(x => x.LibraryID == id && x.CollectionID != null) .Where(x => x.LibraryID == id && x.CollectionID != null)
.Select(x => x.Collection), .Select(x => x.Collection as CollectionDE),
where, where,
sort, sort,
limit); limit);
@ -130,7 +131,7 @@ namespace Kyoo.Controllers
{ {
ICollection<Collection> collections = await ApplyFilters(_database.LibraryLinks ICollection<Collection> collections = await ApplyFilters(_database.LibraryLinks
.Where(x => x.Library.Slug == slug && x.CollectionID != null) .Where(x => x.Library.Slug == slug && x.CollectionID != null)
.Select(x => x.Collection), .Select(x => x.Collection as CollectionDE),
where, where,
sort, sort,
limit); limit);

View File

@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
public class EpisodeRepository : LocalRepository<Episode>, IEpisodeRepository public class EpisodeRepository : LocalRepository<Episode, Episode>, IEpisodeRepository
{ {
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
private readonly IProviderRepository _providers; private readonly IProviderRepository _providers;
@ -133,7 +133,7 @@ namespace Kyoo.Controllers
Sort<Episode> sort = default, Sort<Episode> sort = default,
Pagination limit = default) Pagination limit = default)
{ {
ICollection<Episode> episodes = await ApplyFilters(_database.Episodes.Where(x => x.ShowID == showID), ICollection<Episode> episodes = await ApplyFilters<Episode>(_database.Episodes.Where(x => x.ShowID == showID),
where, where,
sort, sort,
limit); limit);

View File

@ -10,12 +10,12 @@ using Microsoft.Extensions.DependencyInjection;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
public class LibraryRepository : LocalRepository<Library>, ILibraryRepository public class LibraryRepository : LocalRepository<Library, LibraryDE>, ILibraryRepository
{ {
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
private readonly IProviderRepository _providers; private readonly IProviderRepository _providers;
private readonly Lazy<IShowRepository> _shows; private readonly Lazy<IShowRepository> _shows;
protected override Expression<Func<Library, object>> DefaultSort => x => x.ID; protected override Expression<Func<LibraryDE, object>> DefaultSort => x => x.ID;
public LibraryRepository(DatabaseContext database, IProviderRepository providers, IServiceProvider services) public LibraryRepository(DatabaseContext database, IProviderRepository providers, IServiceProvider services)
@ -47,13 +47,14 @@ namespace Kyoo.Controllers
return await _database.Libraries return await _database.Libraries
.Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) .Where(x => EF.Functions.ILike(x.Name, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync<Library>();
} }
public override async Task<Library> Create(Library obj) public override async Task<Library> Create(Library item)
{ {
if (obj == null) if (item == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(item));
LibraryDE obj = new LibraryDE(item);
await Validate(obj); await Validate(obj);
_database.Entry(obj).State = EntityState.Added; _database.Entry(obj).State = EntityState.Added;
@ -65,7 +66,7 @@ namespace Kyoo.Controllers
return obj; return obj;
} }
protected override async Task Validate(Library obj) protected override async Task Validate(LibraryDE obj)
{ {
if (string.IsNullOrEmpty(obj.Slug)) if (string.IsNullOrEmpty(obj.Slug))
throw new ArgumentException("The library's slug must be set and not empty"); throw new ArgumentException("The library's slug must be set and not empty");
@ -81,10 +82,11 @@ namespace Kyoo.Controllers
link.Provider = await _providers.CreateIfNotExists(link.Provider); link.Provider = await _providers.CreateIfNotExists(link.Provider);
} }
public override async Task Delete(Library obj) public override async Task Delete(Library item)
{ {
if (obj == null) if (item == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(item));
LibraryDE obj = new LibraryDE(item);
_database.Entry(obj).State = EntityState.Deleted; _database.Entry(obj).State = EntityState.Deleted;
if (obj.ProviderLinks != null) if (obj.ProviderLinks != null)
@ -103,7 +105,7 @@ namespace Kyoo.Controllers
{ {
ICollection<Library> libraries = await ApplyFilters(_database.LibraryLinks ICollection<Library> libraries = await ApplyFilters(_database.LibraryLinks
.Where(x => x.ShowID == showID) .Where(x => x.ShowID == showID)
.Select(x => x.Library), .Select(x => x.Library as LibraryDE),
where, where,
sort, sort,
limit); limit);
@ -119,7 +121,7 @@ namespace Kyoo.Controllers
{ {
ICollection<Library> libraries = await ApplyFilters(_database.LibraryLinks ICollection<Library> libraries = await ApplyFilters(_database.LibraryLinks
.Where(x => x.Show.Slug == showSlug) .Where(x => x.Show.Slug == showSlug)
.Select(x => x.Library), .Select(x => x.Library as LibraryDE),
where, where,
sort, sort,
limit); limit);
@ -135,7 +137,7 @@ namespace Kyoo.Controllers
{ {
ICollection<Library> libraries = await ApplyFilters(_database.LibraryLinks ICollection<Library> libraries = await ApplyFilters(_database.LibraryLinks
.Where(x => x.CollectionID == id) .Where(x => x.CollectionID == id)
.Select(x => x.Library), .Select(x => x.Library as LibraryDE),
where, where,
sort, sort,
limit); limit);
@ -151,7 +153,7 @@ namespace Kyoo.Controllers
{ {
ICollection<Library> libraries = await ApplyFilters(_database.LibraryLinks ICollection<Library> libraries = await ApplyFilters(_database.LibraryLinks
.Where(x => x.Collection.Slug == slug) .Where(x => x.Collection.Slug == slug)
.Select(x => x.Library), .Select(x => x.Library as LibraryDE),
where, where,
sort, sort,
limit); limit);

View File

@ -10,7 +10,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
public class ShowRepository : LocalRepository<Show>, IShowRepository public class ShowRepository : LocalRepository<Show, ShowDE>, IShowRepository
{ {
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
private readonly IStudioRepository _studios; private readonly IStudioRepository _studios;
@ -21,7 +21,7 @@ namespace Kyoo.Controllers
private readonly Lazy<IEpisodeRepository> _episodes; private readonly Lazy<IEpisodeRepository> _episodes;
private readonly Lazy<ILibraryRepository> _libraries; private readonly Lazy<ILibraryRepository> _libraries;
private readonly Lazy<ICollectionRepository> _collections; private readonly Lazy<ICollectionRepository> _collections;
protected override Expression<Func<Show, object>> DefaultSort => x => x.Title; protected override Expression<Func<ShowDE, object>> DefaultSort => x => x.Title;
public ShowRepository(DatabaseContext database, public ShowRepository(DatabaseContext database,
IStudioRepository studios, IStudioRepository studios,
@ -83,13 +83,14 @@ namespace Kyoo.Controllers
.Where(x => EF.Functions.ILike(x.Title, query) .Where(x => EF.Functions.ILike(x.Title, query)
/*|| x.Aliases.Any(y => EF.Functions.ILike(y, query))*/) // NOT TRANSLATABLE. /*|| x.Aliases.Any(y => EF.Functions.ILike(y, query))*/) // NOT TRANSLATABLE.
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync<Show>();
} }
public override async Task<Show> Create(Show obj) public override async Task<Show> Create(Show item)
{ {
if (obj == null) if (item == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(item));
ShowDE obj = new ShowDE(item);
await Validate(obj); await Validate(obj);
_database.Entry(obj).State = EntityState.Added; _database.Entry(obj).State = EntityState.Added;
@ -107,7 +108,7 @@ namespace Kyoo.Controllers
return obj; return obj;
} }
protected override async Task Validate(Show obj) protected override async Task Validate(ShowDE obj)
{ {
await base.Validate(obj); await base.Validate(obj);
@ -147,10 +148,11 @@ namespace Kyoo.Controllers
} }
} }
public override async Task Delete(Show obj) public override async Task Delete(Show item)
{ {
if (obj == null) if (item == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(item));
ShowDE obj = new ShowDE(item);
_database.Entry(obj).State = EntityState.Deleted; _database.Entry(obj).State = EntityState.Deleted;
@ -190,7 +192,7 @@ namespace Kyoo.Controllers
{ {
ICollection<Show> shows = await ApplyFilters(_database.LibraryLinks ICollection<Show> shows = await ApplyFilters(_database.LibraryLinks
.Where(x => x.LibraryID == id && x.ShowID != null) .Where(x => x.LibraryID == id && x.ShowID != null)
.Select(x => x.Show), .Select(x => x.Show as ShowDE),
where, where,
sort, sort,
limit); limit);
@ -206,7 +208,7 @@ namespace Kyoo.Controllers
{ {
ICollection<Show> shows = await ApplyFilters(_database.LibraryLinks ICollection<Show> shows = await ApplyFilters(_database.LibraryLinks
.Where(x => x.Library.Slug == slug && x.ShowID != null) .Where(x => x.Library.Slug == slug && x.ShowID != null)
.Select(x => x.Show), .Select(x => x.Show as ShowDE),
where, where,
sort, sort,
limit); limit);
@ -222,7 +224,7 @@ namespace Kyoo.Controllers
{ {
ICollection<Show> shows = await ApplyFilters(_database.CollectionLinks ICollection<Show> shows = await ApplyFilters(_database.CollectionLinks
.Where(x => x.CollectionID== id) .Where(x => x.CollectionID== id)
.Select(x => x.Show), .Select(x => x.Show as ShowDE),
where, where,
sort, sort,
limit); limit);
@ -238,7 +240,7 @@ namespace Kyoo.Controllers
{ {
ICollection<Show> shows = await ApplyFilters(_database.CollectionLinks ICollection<Show> shows = await ApplyFilters(_database.CollectionLinks
.Where(x => x.Collection.Slug == slug) .Where(x => x.Collection.Slug == slug)
.Select(x => x.Show), .Select(x => x.Show as ShowDE),
where, where,
sort, sort,
limit); limit);
@ -249,12 +251,16 @@ namespace Kyoo.Controllers
public Task<Show> GetFromSeason(int seasonID) public Task<Show> GetFromSeason(int seasonID)
{ {
return _database.Shows.FirstOrDefaultAsync(x => x.Seasons.Any(y => y.ID == seasonID)); return _database.Shows
.FirstOrDefaultAsync(x => x.Seasons.Any(y => y.ID == seasonID))
.Cast<Show>();
} }
public Task<Show> GetFromEpisode(int episodeID) public Task<Show> GetFromEpisode(int episodeID)
{ {
return _database.Shows.FirstOrDefaultAsync(x => x.Episodes.Any(y => y.ID == episodeID)); return _database.Shows
.FirstOrDefaultAsync(x => x.Episodes.Any(y => y.ID == episodeID))
.Cast<Show>();
} }
} }
} }

View File

@ -96,11 +96,11 @@ namespace Kyoo.Controllers
if (episode?.Path == null) if (episode?.Path == null)
return default; return default;
if (episode.ImgPrimary != null) if (episode.Poster != null)
{ {
string localPath = Path.ChangeExtension(episode.Path, "jpg"); string localPath = Path.ChangeExtension(episode.Path, "jpg");
if (alwaysDownload || !File.Exists(localPath)) if (alwaysDownload || !File.Exists(localPath))
await DownloadImage(episode.ImgPrimary, localPath, $"The thumbnail of {episode.Show.Title}"); await DownloadImage(episode.Poster, localPath, $"The thumbnail of {episode.Show.Title}");
} }
return episode; return episode;
} }

View File

@ -1,69 +1,27 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using IdentityServer4.EntityFramework.Entities;
using IdentityServer4.EntityFramework.Extensions;
using IdentityServer4.EntityFramework.Interfaces;
using IdentityServer4.EntityFramework.Options;
using Kyoo.Models; using Kyoo.Models;
using Kyoo.Models.Exceptions; using Kyoo.Models.Exceptions;
using Kyoo.Models.Watch; using Kyoo.Models.Watch;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.Extensions.Options;
using Npgsql; using Npgsql;
namespace Kyoo namespace Kyoo
{ {
public class IdentityDatabase : IdentityDbContext<User>, IPersistedGrantDbContext
{
private readonly IOptions<OperationalStoreOptions> _operationalStoreOptions;
public IdentityDatabase(DbContextOptions<IdentityDatabase> options, IOptions<OperationalStoreOptions> operationalStoreOptions)
: base(options)
{
_operationalStoreOptions = operationalStoreOptions;
}
public DbSet<User> Accounts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value);
modelBuilder.Entity<User>().ToTable("User");
modelBuilder.Entity<IdentityUserRole<string>>().ToTable("UserRole");
modelBuilder.Entity<IdentityUserLogin<string>>().ToTable("UserLogin");
modelBuilder.Entity<IdentityUserClaim<string>>().ToTable("UserClaim");
modelBuilder.Entity<IdentityRole>().ToTable("UserRoles");
modelBuilder.Entity<IdentityRoleClaim<string>>().ToTable("UserRoleClaim");
modelBuilder.Entity<IdentityUserToken<string>>().ToTable("UserToken");
}
public Task<int> SaveChangesAsync() => base.SaveChangesAsync();
public DbSet<PersistedGrant> PersistedGrants { get; set; }
public DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; }
}
public class DatabaseContext : DbContext public class DatabaseContext : DbContext
{ {
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { } public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
public DbSet<Library> Libraries { get; set; } public DbSet<LibraryDE> Libraries { get; set; }
public DbSet<Collection> Collections { get; set; } public DbSet<CollectionDE> Collections { get; set; }
public DbSet<Show> Shows { get; set; } public DbSet<ShowDE> Shows { get; set; }
public DbSet<Season> Seasons { get; set; } public DbSet<Season> Seasons { get; set; }
public DbSet<Episode> Episodes { get; set; } public DbSet<Episode> Episodes { get; set; }
public DbSet<Track> Tracks { get; set; } public DbSet<Track> Tracks { get; set; }
public DbSet<Genre> Genres { get; set; } public DbSet<GenreDE> Genres { get; set; }
public DbSet<People> People { get; set; } public DbSet<People> People { get; set; }
public DbSet<Studio> Studios { get; set; } public DbSet<Studio> Studios { get; set; }
public DbSet<ProviderID> Providers { get; set; } public DbSet<ProviderID> Providers { get; set; }
@ -72,7 +30,6 @@ namespace Kyoo
public DbSet<PeopleRole> PeopleRoles { get; set; } public DbSet<PeopleRole> PeopleRoles { get; set; }
// This is used because EF doesn't support Many-To-Many relationships so for now we need to override the getter/setters to store this.
public DbSet<LibraryLink> LibraryLinks { get; set; } public DbSet<LibraryLink> LibraryLinks { get; set; }
public DbSet<CollectionLink> CollectionLinks { get; set; } public DbSet<CollectionLink> CollectionLinks { get; set; }
public DbSet<GenreLink> GenreLinks { get; set; } public DbSet<GenreLink> GenreLinks { get; set; }

View File

@ -0,0 +1,46 @@
using System.Threading.Tasks;
using IdentityServer4.EntityFramework.Entities;
using IdentityServer4.EntityFramework.Extensions;
using IdentityServer4.EntityFramework.Interfaces;
using IdentityServer4.EntityFramework.Options;
using Kyoo.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
namespace Kyoo
{
public class IdentityDatabase : IdentityDbContext<User>, IPersistedGrantDbContext
{
private readonly IOptions<OperationalStoreOptions> _operationalStoreOptions;
public IdentityDatabase(DbContextOptions<IdentityDatabase> options, IOptions<OperationalStoreOptions> operationalStoreOptions)
: base(options)
{
_operationalStoreOptions = operationalStoreOptions;
}
public DbSet<User> Accounts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value);
modelBuilder.Entity<User>().ToTable("User");
modelBuilder.Entity<IdentityUserRole<string>>().ToTable("UserRole");
modelBuilder.Entity<IdentityUserLogin<string>>().ToTable("UserLogin");
modelBuilder.Entity<IdentityUserClaim<string>>().ToTable("UserClaim");
modelBuilder.Entity<IdentityRole>().ToTable("UserRoles");
modelBuilder.Entity<IdentityRoleClaim<string>>().ToTable("UserRoleClaim");
modelBuilder.Entity<IdentityUserToken<string>>().ToTable("UserToken");
}
public Task<int> SaveChangesAsync() => base.SaveChangesAsync();
public DbSet<PersistedGrant> PersistedGrants { get; set; }
public DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; }
}
}

View File

@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Linq;
using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
public class CollectionDE : Collection
{
[NotMergable] public virtual IEnumerable<CollectionLink> Links { get; set; }
public override IEnumerable<Show> Shows
{
get => Links.Select(x => x.Show);
set => Links = value.Select(x => new CollectionLink(this, x));
}
[NotMergable] public virtual IEnumerable<LibraryLink> LibraryLinks { get; set; }
public override IEnumerable<Library> Libraries
{
get => LibraryLinks?.Select(x => x.Library);
set => LibraryLinks = value?.Select(x => new LibraryLink(x, this));
}
public CollectionDE() {}
public CollectionDE(Collection collection)
{
Utility.Assign(this, collection);
}
}
}

View File

@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.Linq;
using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
public class GenreDE : Genre
{
[NotMergable] public virtual IEnumerable<GenreLink> Links { get; set; }
[NotMergable] public override IEnumerable<Show> Shows
{
get => Links.Select(x => x.Show);
set => Links = value?.Select(x => new GenreLink(x, this));
}
public GenreDE() {}
public GenreDE(Genre item)
{
Utility.Assign(this, item);
}
}
}

View File

@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Linq;
using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
public class LibraryDE : Library
{
[NotMergable] public virtual IEnumerable<ProviderLink> ProviderLinks { get; set; }
public override IEnumerable<ProviderID> Providers
{
get => ProviderLinks?.Select(x => x.Provider);
set => ProviderLinks = value.Select(x => new ProviderLink(x, this)).ToList();
}
[NotMergable] public virtual IEnumerable<LibraryLink> Links { get; set; }
public override IEnumerable<Show> Shows
{
get => Links?.Where(x => x.Show != null).Select(x => x.Show);
set => Links = Utility.MergeLists(
value?.Select(x => new LibraryLink(this, x)),
Links?.Where(x => x.Show == null));
}
public override IEnumerable<Collection> Collections
{
get => Links?.Where(x => x.Collection != null).Select(x => x.Collection);
set => Links = Utility.MergeLists(
value?.Select(x => new LibraryLink(this, x)),
Links?.Where(x => x.Collection == null));
}
public LibraryDE() {}
public LibraryDE(Library item)
{
Utility.Assign(this, item);
}
}
}

View File

@ -0,0 +1,47 @@
using System.Collections.Generic;
using System.Linq;
using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
public class ShowDE : Show
{
[NotMergable] public virtual IEnumerable<GenreLink> GenreLinks { get; set; }
public override IEnumerable<Genre> Genres
{
get => GenreLinks?.Select(x => x.Genre);
set => GenreLinks = value?.Select(x => new GenreLink(this, x)).ToList();
}
[NotMergable] public virtual IEnumerable<LibraryLink> LibraryLinks { get; set; }
public override IEnumerable<Library> Libraries
{
get => LibraryLinks?.Select(x => x.Library);
set => LibraryLinks = value?.Select(x => new LibraryLink(x, this));
}
[NotMergable] public virtual IEnumerable<CollectionLink> CollectionLinks { get; set; }
public override IEnumerable<Collection> Collections
{
get => CollectionLinks?.Select(x => x.Collection);
set => CollectionLinks = value?.Select(x => new CollectionLink(x, this));
}
public override void OnMerge(object merged)
{
base.OnMerge(merged);
if (GenreLinks != null)
foreach (GenreLink genre in GenreLinks)
genre.Show = this;
}
public ShowDE() {}
public ShowDE(Show show)
{
Utility.Assign(this, show);
}
}
}

@ -1 +1 @@
Subproject commit d4202623007f0cefb22aa64e3c638142ba7d7831 Subproject commit 2e44a86c4b9004012c10d259ae90925adcd66aa7