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}.")
};
}
public Sort<TValue> To<TValue>()
{
return new Sort<TValue>(Key.Convert<Func<TValue, object>>(), Descendant);
}
}
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 DuplicatedItemException() {}
public DuplicatedItemException()
{
Message = "Already exists in the databse.";
}
public DuplicatedItemException(string message)
{

View File

@ -1,31 +1,17 @@
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
public class Collection : IResource
{
[JsonIgnore] public int ID { get; set; }
public int ID { get; set; }
public string Slug { get; set; }
public string Name { get; set; }
public string Poster { get; set; }
public string Overview { get; set; }
[NotMergable] [JsonIgnore] public virtual IEnumerable<CollectionLink> Links { get; set; }
[JsonIgnore] public virtual IEnumerable<Show> Shows
{
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));
}
[JsonIgnore] public virtual IEnumerable<Show> Shows { get; set; }
[JsonIgnore] public virtual IEnumerable<Library> Libraries { get; set; }
public Collection() { }

View File

@ -7,10 +7,10 @@ namespace Kyoo.Models
{
public class Episode : IResource, IOnMerge
{
[JsonIgnore] public int ID { get; set; }
[JsonIgnore] public int ShowID { get; set; }
public int ID { get; set; }
public int ShowID { 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; }
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
[JsonIgnore] public string ImgPrimary { get; set; }
[JsonIgnore] public string Poster { get; set; }
public virtual IEnumerable<MetadataID> ExternalIDs { 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 Thumb
{
@ -36,7 +36,7 @@ namespace Kyoo.Models
{
if (Show != null)
return "thumb/" + Slug;
return ImgPrimary;
return Poster;
}
}
@ -50,7 +50,7 @@ namespace Kyoo.Models
string overview,
DateTime? releaseDate,
int runtime,
string imgPrimary,
string poster,
IEnumerable<MetadataID> externalIDs)
{
SeasonNumber = seasonNumber;
@ -60,7 +60,7 @@ namespace Kyoo.Models
Overview = overview;
ReleaseDate = releaseDate;
Runtime = runtime;
ImgPrimary = imgPrimary;
Poster = poster;
ExternalIDs = externalIDs;
}
@ -74,7 +74,7 @@ namespace Kyoo.Models
string overview,
DateTime? releaseDate,
int runtime,
string imgPrimary,
string poster,
IEnumerable<MetadataID> externalIDs)
{
ShowID = showID;
@ -87,7 +87,7 @@ namespace Kyoo.Models
Overview = overview;
ReleaseDate = releaseDate;
Runtime = runtime;
ImgPrimary = imgPrimary;
Poster = poster;
ExternalIDs = externalIDs;
}

View File

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

View File

@ -1,6 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using Kyoo.Models.Attributes;
using Newtonsoft.Json;
namespace Kyoo.Models
@ -12,28 +10,10 @@ namespace Kyoo.Models
public string Name { get; set; }
public IEnumerable<string> Paths { get; set; }
public IEnumerable<ProviderID> Providers
{
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; }
public virtual IEnumerable<ProviderID> Providers { get; set; }
[JsonIgnore] public 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));
}
[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));
}
[JsonIgnore] public virtual IEnumerable<Show> Shows { get; set; }
[JsonIgnore] public virtual IEnumerable<Collection> Collections { get; set; }
public Library() { }

View File

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

View File

@ -3,9 +3,11 @@ using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Kyoo.Models;
using Kyoo.Models.Attributes;
@ -71,6 +73,21 @@ namespace Kyoo
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)
{
Type type = typeof(T);
@ -149,7 +166,7 @@ namespace Kyoo
[NotNull] Type owner,
[NotNull] string methodName,
[NotNull] Type type,
IEnumerable<object> args)
params object[] args)
{
if (owner == null)
throw new ArgumentNullException(nameof(owner));
@ -157,7 +174,7 @@ namespace Kyoo
throw new ArgumentNullException(nameof(methodName));
if (type == null)
throw new ArgumentNullException(nameof(type));
MethodInfo method = owner.GetMethod(methodName);
MethodInfo method = owner.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (method == null)
throw new NullReferenceException($"A method named {methodName} could not be found on {owner.FullName}");
return method.MakeGenericMethod(type).Invoke(null, args?.ToArray());
@ -167,7 +184,7 @@ namespace Kyoo
[NotNull] object instance,
[NotNull] string methodName,
[NotNull] Type type,
IEnumerable<object> args)
params object[] args)
{
if (instance == null)
throw new ArgumentNullException(nameof(instance));
@ -175,7 +192,7 @@ namespace Kyoo
throw new ArgumentNullException(nameof(methodName));
if (type == null)
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)
throw new NullReferenceException($"A method named {methodName} could not be found on {instance.GetType().FullName}");
return method.MakeGenericMethod(type).Invoke(instance, args?.ToArray());
@ -230,5 +247,66 @@ namespace Kyoo
return string.Empty;
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.Exceptions;
using Microsoft.EntityFrameworkCore;
using Npgsql;
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;
protected abstract Expression<Func<T, object>> DefaultSort { get; }
protected abstract Expression<Func<TInternal, object>> DefaultSort { get; }
protected LocalRepository(DbContext database)
@ -35,31 +36,57 @@ namespace Kyoo.Controllers
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 Task<T> Get(string slug)
{
return _Get(slug).Cast<T>();
}
protected virtual Task<TInternal> _Get(int id)
{
return _database.Set<TInternal>().FirstOrDefaultAsync(x => x.ID == id);
}
public virtual Task<T> Get(string slug)
protected virtual Task<TInternal> _Get(string slug)
{
return _database.Set<T>().FirstOrDefaultAsync(x => x.Slug == slug);
return _database.Set<TInternal>().FirstOrDefaultAsync(x => x.Slug == slug);
}
public abstract Task<ICollection<T>> Search(string query);
public virtual Task<ICollection<T>> GetAll(Expression<Func<T, bool>> where = null,
Sort<T> sort = 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,
Sort<T> sort = 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,
@ -125,7 +152,7 @@ namespace Kyoo.Controllers
if (edited == null)
throw new ArgumentNullException(nameof(edited));
T old = await Get(edited.Slug);
TInternal old = (TInternal)await Get(edited.Slug);
if (old == null)
throw new ItemNotFound($"No ressource found with the slug {edited.Slug}.");
@ -138,9 +165,9 @@ namespace Kyoo.Controllers
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)
&& !typeof(string).IsAssignableFrom(x.PropertyType)))
{

View File

@ -93,7 +93,7 @@ namespace Kyoo.Controllers
show.Slug = Utility.ToSlug(showName);
show.Title ??= showName;
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();
return show;
}

View File

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

View File

@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore;
namespace Kyoo.Controllers
{
public class EpisodeRepository : LocalRepository<Episode>, IEpisodeRepository
public class EpisodeRepository : LocalRepository<Episode, Episode>, IEpisodeRepository
{
private readonly DatabaseContext _database;
private readonly IProviderRepository _providers;
@ -133,7 +133,7 @@ namespace Kyoo.Controllers
Sort<Episode> sort = 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,
sort,
limit);

View File

@ -10,12 +10,12 @@ using Microsoft.Extensions.DependencyInjection;
namespace Kyoo.Controllers
{
public class LibraryRepository : LocalRepository<Library>, ILibraryRepository
public class LibraryRepository : LocalRepository<Library, LibraryDE>, ILibraryRepository
{
private readonly DatabaseContext _database;
private readonly IProviderRepository _providers;
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)
@ -47,13 +47,14 @@ namespace Kyoo.Controllers
return await _database.Libraries
.Where(x => EF.Functions.ILike(x.Name, $"%{query}%"))
.Take(20)
.ToListAsync();
.ToListAsync<Library>();
}
public override async Task<Library> Create(Library obj)
public override async Task<Library> Create(Library item)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
if (item == null)
throw new ArgumentNullException(nameof(item));
LibraryDE obj = new LibraryDE(item);
await Validate(obj);
_database.Entry(obj).State = EntityState.Added;
@ -65,7 +66,7 @@ namespace Kyoo.Controllers
return obj;
}
protected override async Task Validate(Library obj)
protected override async Task Validate(LibraryDE obj)
{
if (string.IsNullOrEmpty(obj.Slug))
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);
}
public override async Task Delete(Library obj)
public override async Task Delete(Library item)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
if (item == null)
throw new ArgumentNullException(nameof(item));
LibraryDE obj = new LibraryDE(item);
_database.Entry(obj).State = EntityState.Deleted;
if (obj.ProviderLinks != null)
@ -103,7 +105,7 @@ namespace Kyoo.Controllers
{
ICollection<Library> libraries = await ApplyFilters(_database.LibraryLinks
.Where(x => x.ShowID == showID)
.Select(x => x.Library),
.Select(x => x.Library as LibraryDE),
where,
sort,
limit);
@ -119,7 +121,7 @@ namespace Kyoo.Controllers
{
ICollection<Library> libraries = await ApplyFilters(_database.LibraryLinks
.Where(x => x.Show.Slug == showSlug)
.Select(x => x.Library),
.Select(x => x.Library as LibraryDE),
where,
sort,
limit);
@ -135,7 +137,7 @@ namespace Kyoo.Controllers
{
ICollection<Library> libraries = await ApplyFilters(_database.LibraryLinks
.Where(x => x.CollectionID == id)
.Select(x => x.Library),
.Select(x => x.Library as LibraryDE),
where,
sort,
limit);
@ -151,7 +153,7 @@ namespace Kyoo.Controllers
{
ICollection<Library> libraries = await ApplyFilters(_database.LibraryLinks
.Where(x => x.Collection.Slug == slug)
.Select(x => x.Library),
.Select(x => x.Library as LibraryDE),
where,
sort,
limit);

View File

@ -10,7 +10,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace Kyoo.Controllers
{
public class ShowRepository : LocalRepository<Show>, IShowRepository
public class ShowRepository : LocalRepository<Show, ShowDE>, IShowRepository
{
private readonly DatabaseContext _database;
private readonly IStudioRepository _studios;
@ -21,7 +21,7 @@ namespace Kyoo.Controllers
private readonly Lazy<IEpisodeRepository> _episodes;
private readonly Lazy<ILibraryRepository> _libraries;
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,
IStudioRepository studios,
@ -83,13 +83,14 @@ namespace Kyoo.Controllers
.Where(x => EF.Functions.ILike(x.Title, query)
/*|| x.Aliases.Any(y => EF.Functions.ILike(y, query))*/) // NOT TRANSLATABLE.
.Take(20)
.ToListAsync();
.ToListAsync<Show>();
}
public override async Task<Show> Create(Show obj)
public override async Task<Show> Create(Show item)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
if (item == null)
throw new ArgumentNullException(nameof(item));
ShowDE obj = new ShowDE(item);
await Validate(obj);
_database.Entry(obj).State = EntityState.Added;
@ -107,7 +108,7 @@ namespace Kyoo.Controllers
return obj;
}
protected override async Task Validate(Show obj)
protected override async Task Validate(ShowDE 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)
throw new ArgumentNullException(nameof(obj));
if (item == null)
throw new ArgumentNullException(nameof(item));
ShowDE obj = new ShowDE(item);
_database.Entry(obj).State = EntityState.Deleted;
@ -190,7 +192,7 @@ namespace Kyoo.Controllers
{
ICollection<Show> shows = await ApplyFilters(_database.LibraryLinks
.Where(x => x.LibraryID == id && x.ShowID != null)
.Select(x => x.Show),
.Select(x => x.Show as ShowDE),
where,
sort,
limit);
@ -206,7 +208,7 @@ namespace Kyoo.Controllers
{
ICollection<Show> shows = await ApplyFilters(_database.LibraryLinks
.Where(x => x.Library.Slug == slug && x.ShowID != null)
.Select(x => x.Show),
.Select(x => x.Show as ShowDE),
where,
sort,
limit);
@ -222,7 +224,7 @@ namespace Kyoo.Controllers
{
ICollection<Show> shows = await ApplyFilters(_database.CollectionLinks
.Where(x => x.CollectionID== id)
.Select(x => x.Show),
.Select(x => x.Show as ShowDE),
where,
sort,
limit);
@ -238,7 +240,7 @@ namespace Kyoo.Controllers
{
ICollection<Show> shows = await ApplyFilters(_database.CollectionLinks
.Where(x => x.Collection.Slug == slug)
.Select(x => x.Show),
.Select(x => x.Show as ShowDE),
where,
sort,
limit);
@ -249,12 +251,16 @@ namespace Kyoo.Controllers
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)
{
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)
return default;
if (episode.ImgPrimary != null)
if (episode.Poster != null)
{
string localPath = Path.ChangeExtension(episode.Path, "jpg");
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;
}

View File

@ -1,69 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
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.Exceptions;
using Kyoo.Models.Watch;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.Extensions.Options;
using Npgsql;
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 DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
public DbSet<Library> Libraries { get; set; }
public DbSet<Collection> Collections { get; set; }
public DbSet<Show> Shows { get; set; }
public DbSet<LibraryDE> Libraries { get; set; }
public DbSet<CollectionDE> Collections { get; set; }
public DbSet<ShowDE> Shows { get; set; }
public DbSet<Season> Seasons { get; set; }
public DbSet<Episode> Episodes { 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<Studio> Studios { get; set; }
public DbSet<ProviderID> Providers { get; set; }
@ -72,7 +30,6 @@ namespace Kyoo
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<CollectionLink> CollectionLinks { 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