Fixing composed slug handling & page field serialization

This commit is contained in:
Zoe Roux 2021-03-07 21:20:03 +01:00
parent fab9a3f6a1
commit b3fdee4bcd
11 changed files with 152 additions and 56 deletions

View File

@ -109,6 +109,8 @@ namespace Kyoo.Controllers
public interface IShowRepository : IRepository<Show>
{
Task AddShowLink(int showID, int? libraryID, int? collectionID);
Task<string> GetSlug(int showID);
}
public interface ISeasonRepository : IRepository<Season>

View File

@ -1,7 +0,0 @@
using System;
namespace Kyoo.Models.Attributes
{
[AttributeUsage(AttributeTargets.Class)]
public class ComposedSlugAttribute : Attribute { }
}

View File

@ -5,11 +5,11 @@ using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
[ComposedSlug]
public class Episode : IResource, IOnMerge
{
public int ID { get; set; }
public string Slug => Show != null ? GetSlug(Show.Slug, SeasonNumber, EpisodeNumber) : ID.ToString();
public string Slug => GetSlug(ShowSlug, SeasonNumber, EpisodeNumber);
[SerializeIgnore] public string ShowSlug { private get; set; }
[SerializeIgnore] public int ShowID { get; set; }
[LoadableRelation(nameof(ShowID))] public virtual Show Show { get; set; }
[SerializeIgnore] public int? SeasonID { get; set; }
@ -31,8 +31,6 @@ namespace Kyoo.Models
[LoadableRelation] public virtual ICollection<Track> Tracks { get; set; }
public string ShowTitle => Show?.Title;
public Episode() { }
@ -78,6 +76,8 @@ namespace Kyoo.Models
public static string GetSlug(string showSlug, int seasonNumber, int episodeNumber)
{
if (showSlug == null)
throw new ArgumentException("Show's slug is null. Can't find episode's slug.");
if (seasonNumber == -1)
return showSlug;
return $"{showSlug}-s{seasonNumber}e{episodeNumber}";

View File

@ -4,15 +4,16 @@ using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
[ComposedSlug]
public class Season : IResource
{
public int ID { get; set; }
public string Slug => $"{ShowSlug}-s{SeasonNumber}";
[SerializeIgnore] public int ShowID { get; set; }
[SerializeIgnore] public string ShowSlug { private get; set; }
[LoadableRelation(nameof(ShowID))] public virtual Show Show { get; set; }
public int SeasonNumber { get; set; } = -1;
public string Slug => Show != null ? $"{Show.Slug}-s{SeasonNumber}" : ID.ToString();
public string Title { get; set; }
public string Overview { get; set; }
public int? Year { get; set; }
@ -21,7 +22,6 @@ namespace Kyoo.Models
public string Thumb => $"/api/seasons/{Slug}/thumb";
[EditableRelation] [LoadableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; }
[LoadableRelation(nameof(ShowID))] public virtual Show Show { get; set; }
[LoadableRelation] public virtual ICollection<Episode> Episodes { get; set; }
public Season() { }

View File

@ -25,7 +25,7 @@ namespace Kyoo.Models
public class WatchItem
{
public readonly int EpisodeID = -1;
public readonly int EpisodeID;
public string ShowTitle;
public string ShowSlug;
@ -41,9 +41,9 @@ namespace Kyoo.Models
public string Container;
public Track Video;
public IEnumerable<Track> Audios;
public IEnumerable<Track> Subtitles;
public IEnumerable<Chapter> Chapters;
public ICollection<Track> Audios;
public ICollection<Track> Subtitles;
public ICollection<Chapter> Chapters;
public WatchItem() { }
@ -78,8 +78,8 @@ namespace Kyoo.Models
DateTime? releaseDate,
string path,
Track video,
IEnumerable<Track> audios,
IEnumerable<Track> subtitles)
ICollection<Track> audios,
ICollection<Track> subtitles)
: this(episodeID, showTitle, showSlug, seasonNumber, episodeNumber, title, releaseDate, path)
{
Video = video;
@ -120,8 +120,8 @@ namespace Kyoo.Models
ep.ReleaseDate,
ep.Path,
ep.Tracks.FirstOrDefault(x => x.Type == StreamType.Video),
ep.Tracks.Where(x => x.Type == StreamType.Audio),
ep.Tracks.Where(x => x.Type == StreamType.Subtitle))
ep.Tracks.Where(x => x.Type == StreamType.Audio).ToArray(),
ep.Tracks.Where(x => x.Type == StreamType.Subtitle).ToArray())
{
IsMovie = show.IsMovie,
PreviousEpisode = previous,
@ -130,7 +130,7 @@ namespace Kyoo.Models
};
}
private static async Task<IEnumerable<Chapter>> GetChapters(string episodePath)
private static async Task<ICollection<Chapter>> GetChapters(string episodePath)
{
string path = PathIO.Combine(
PathIO.GetDirectoryName(episodePath)!,

View File

@ -1,5 +1,7 @@
using System;
using System.Collections;
using System.Reflection;
using Kyoo.Models;
using Kyoo.Models.Attributes;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
@ -40,8 +42,13 @@ namespace Kyoo.Controllers
protected override JsonContract CreateContract(Type objectType)
{
JsonContract contract = base.CreateContract(objectType);
contract.OnSerializingCallbacks.Add((_, _) => _depth++);
contract.OnSerializedCallbacks.Add((_, _) => _depth--);
if (Utility.GetGenericDefinition(objectType, typeof(Page<>)) == null
&& !objectType.IsAssignableTo(typeof(IEnumerable)))
{
contract.OnSerializingCallbacks.Add((_, _) => _depth++);
contract.OnSerializedCallbacks.Add((_, _) => _depth--);
}
return contract;
}
}

View File

@ -209,7 +209,7 @@ namespace Kyoo.Controllers
{
if (string.IsNullOrEmpty(resource.Slug))
throw new ArgumentException("Resource can't have null as a slug.");
if (int.TryParse(resource.Slug, out int _) && typeof(T).GetCustomAttribute<ComposedSlugAttribute>() == null)
if (int.TryParse(resource.Slug, out int _))
{
try
{

View File

@ -5,7 +5,6 @@ using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Kyoo.Models;
using Kyoo.Models.Exceptions;
using Microsoft.EntityFrameworkCore;
namespace Kyoo.Controllers
@ -15,13 +14,16 @@ namespace Kyoo.Controllers
private bool _disposed;
private readonly DatabaseContext _database;
private readonly IProviderRepository _providers;
private readonly IShowRepository _shows;
protected override Expression<Func<Episode, object>> DefaultSort => x => x.EpisodeNumber;
public EpisodeRepository(DatabaseContext database, IProviderRepository providers) : base(database)
public EpisodeRepository(DatabaseContext database, IProviderRepository providers, IShowRepository shows)
: base(database)
{
_database = database;
_providers = providers;
_shows = shows;
}
@ -32,6 +34,7 @@ namespace Kyoo.Controllers
_disposed = true;
_database.Dispose();
_providers.Dispose();
_shows.Dispose();
GC.SuppressFinalize(this);
}
@ -42,6 +45,15 @@ namespace Kyoo.Controllers
_disposed = true;
await _database.DisposeAsync();
await _providers.DisposeAsync();
await _shows.DisposeAsync();
}
public override async Task<Episode> Get(int id)
{
Episode ret = await base.Get(id);
if (ret != null)
ret.ShowSlug = await _shows.GetSlug(ret.ShowID);
return ret;
}
public override Task<Episode> Get(string slug)
@ -55,44 +67,80 @@ namespace Kyoo.Controllers
int.Parse(match.Groups["episode"].Value));
}
public Task<Episode> Get(string showSlug, int seasonNumber, int episodeNumber)
public override async Task<Episode> Get(Expression<Func<Episode, bool>> predicate)
{
return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug
&& x.SeasonNumber == seasonNumber
&& x.EpisodeNumber == episodeNumber);
Episode ret = await base.Get(predicate);
if (ret != null)
ret.ShowSlug = await _shows.GetSlug(ret.ShowID);
return ret;
}
public Task<Episode> Get(int showID, int seasonNumber, int episodeNumber)
public async Task<Episode> Get(string showSlug, int seasonNumber, int episodeNumber)
{
return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID
&& x.SeasonNumber == seasonNumber
&& x.EpisodeNumber == episodeNumber);
Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug
&& x.SeasonNumber == seasonNumber
&& x.EpisodeNumber == episodeNumber);
if (ret != null)
ret.ShowSlug = showSlug;
return ret;
}
public Task<Episode> Get(int seasonID, int episodeNumber)
public async Task<Episode> Get(int showID, int seasonNumber, int episodeNumber)
{
return _database.Episodes.FirstOrDefaultAsync(x => x.SeasonID == seasonID
&& x.EpisodeNumber == episodeNumber);
Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID
&& x.SeasonNumber == seasonNumber
&& x.EpisodeNumber == episodeNumber);
if (ret != null)
ret.ShowSlug = await _shows.GetSlug(showID);
return ret;
}
public Task<Episode> GetAbsolute(int showID, int absoluteNumber)
public async Task<Episode> Get(int seasonID, int episodeNumber)
{
return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID
&& x.AbsoluteNumber == absoluteNumber);
Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.SeasonID == seasonID
&& x.EpisodeNumber == episodeNumber);
if (ret != null)
ret.ShowSlug = await _shows.GetSlug(ret.ShowID);
return ret;
}
public Task<Episode> GetAbsolute(string showSlug, int absoluteNumber)
public async Task<Episode> GetAbsolute(int showID, int absoluteNumber)
{
return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug
&& x.AbsoluteNumber == absoluteNumber);
Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID
&& x.AbsoluteNumber == absoluteNumber);
if (ret != null)
ret.ShowSlug = await _shows.GetSlug(showID);
return ret;
}
public async Task<Episode> GetAbsolute(string showSlug, int absoluteNumber)
{
Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug
&& x.AbsoluteNumber == absoluteNumber);
if (ret != null)
ret.ShowSlug = showSlug;
return ret;
}
public override async Task<ICollection<Episode>> Search(string query)
{
return await _database.Episodes
List<Episode> episodes = await _database.Episodes
.Where(x => EF.Functions.ILike(x.Title, $"%{query}%"))
.Take(20)
.ToListAsync();
foreach (Episode episode in episodes)
episode.ShowSlug = await _shows.GetSlug(episode.ShowID);
return episodes;
}
public override async Task<ICollection<Episode>> GetAll(Expression<Func<Episode, bool>> where = null,
Sort<Episode> sort = default,
Pagination limit = default)
{
ICollection<Episode> episodes = await base.GetAll(where, sort, limit);
foreach (Episode episode in episodes)
episode.ShowSlug = await _shows.GetSlug(episode.ShowID);
return episodes;
}
public override async Task<Episode> Create(Episode obj)

View File

@ -5,7 +5,6 @@ using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Kyoo.Models;
using Kyoo.Models.Exceptions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
@ -16,17 +15,20 @@ namespace Kyoo.Controllers
private bool _disposed;
private readonly DatabaseContext _database;
private readonly IProviderRepository _providers;
private readonly IShowRepository _shows;
private readonly Lazy<IEpisodeRepository> _episodes;
protected override Expression<Func<Season, object>> DefaultSort => x => x.SeasonNumber;
public SeasonRepository(DatabaseContext database,
IProviderRepository providers,
IShowRepository shows,
IServiceProvider services)
: base(database)
{
_database = database;
_providers = providers;
_shows = shows;
_episodes = new Lazy<IEpisodeRepository>(services.GetRequiredService<IEpisodeRepository>);
}
@ -38,6 +40,7 @@ namespace Kyoo.Controllers
_disposed = true;
_database.Dispose();
_providers.Dispose();
_shows.Dispose();
if (_episodes.IsValueCreated)
_episodes.Value.Dispose();
GC.SuppressFinalize(this);
@ -50,10 +53,27 @@ namespace Kyoo.Controllers
_disposed = true;
await _database.DisposeAsync();
await _providers.DisposeAsync();
await _shows.DisposeAsync();
if (_episodes.IsValueCreated)
await _episodes.Value.DisposeAsync();
}
public override async Task<Season> Get(int id)
{
Season ret = await base.Get(id);
if (ret != null)
ret.ShowSlug = await _shows.GetSlug(ret.ShowID);
return ret;
}
public override async Task<Season> Get(Expression<Func<Season, bool>> predicate)
{
Season ret = await base.Get(predicate);
if (ret != null)
ret.ShowSlug = await _shows.GetSlug(ret.ShowID);
return ret;
}
public override Task<Season> Get(string slug)
{
Match match = Regex.Match(slug, @"(?<show>.*)-s(?<season>\d*)");
@ -63,24 +83,43 @@ namespace Kyoo.Controllers
return Get(match.Groups["show"].Value, int.Parse(match.Groups["season"].Value));
}
public Task<Season> Get(int showID, int seasonNumber)
public async Task<Season> Get(int showID, int seasonNumber)
{
return _database.Seasons.FirstOrDefaultAsync(x => x.ShowID == showID
&& x.SeasonNumber == seasonNumber);
Season ret = await _database.Seasons.FirstOrDefaultAsync(x => x.ShowID == showID
&& x.SeasonNumber == seasonNumber);
if (ret != null)
ret.ShowSlug = await _shows.GetSlug(showID);
return ret;
}
public Task<Season> Get(string showSlug, int seasonNumber)
public async Task<Season> Get(string showSlug, int seasonNumber)
{
return _database.Seasons.FirstOrDefaultAsync(x => x.Show.Slug == showSlug
Season ret = await _database.Seasons.FirstOrDefaultAsync(x => x.Show.Slug == showSlug
&& x.SeasonNumber == seasonNumber);
if (ret != null)
ret.ShowSlug = showSlug;
return ret;
}
public override async Task<ICollection<Season>> Search(string query)
{
return await _database.Seasons
List<Season> seasons = await _database.Seasons
.Where(x => EF.Functions.ILike(x.Title, $"%{query}%"))
.Take(20)
.ToListAsync();
foreach (Season season in seasons)
season.ShowSlug = await _shows.GetSlug(season.ShowID);
return seasons;
}
public override async Task<ICollection<Season>> GetAll(Expression<Func<Season, bool>> where = null,
Sort<Season> sort = default,
Pagination limit = default)
{
ICollection<Season> seasons = await base.GetAll(where, sort, limit);
foreach (Season season in seasons)
season.ShowSlug = await _shows.GetSlug(season.ShowID);
return seasons;
}
public override async Task<Season> Create(Season obj)

View File

@ -151,6 +151,13 @@ namespace Kyoo.Controllers
}
}
public Task<string> GetSlug(int showID)
{
return _database.Shows.Where(x => x.ID == showID)
.Select(x => x.Slug)
.FirstOrDefaultAsync();
}
public override async Task Delete(Show obj)
{
if (obj == null)

View File

@ -82,7 +82,7 @@ namespace Kyoo.Controllers
{
if (obj.EpisodeID <= 0)
{
obj.EpisodeID = obj.Episode?.ID ?? -1;
obj.EpisodeID = obj.Episode?.ID ?? 0;
if (obj.EpisodeID <= 0)
throw new InvalidOperationException($"Can't store a track not related to any episode (episodeID: {obj.EpisodeID}).");
}