Adding slugs setters

This commit is contained in:
Zoe Roux 2021-06-13 15:50:27 +02:00
parent af39793b7c
commit 32f12ae833
16 changed files with 144 additions and 168 deletions

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
using Kyoo.Controllers;
using Kyoo.Models.Attributes;
@ -14,9 +15,36 @@ namespace Kyoo.Models
{
/// <inheritdoc />
public int ID { get; set; }
/// <inheritdoc />
public string Slug => GetSlug(ShowSlug, SeasonNumber, EpisodeNumber, AbsoluteNumber);
public string Slug
{
get => GetSlug(ShowSlug, SeasonNumber, EpisodeNumber, AbsoluteNumber);
set
{
Match match = Regex.Match(value, @"(?<show>.*)-s(?<season>\d*)e(?<episode>\d*)");
if (match.Success)
{
ShowSlug = match.Groups["show"].Value;
SeasonNumber = int.Parse(match.Groups["season"].Value);
EpisodeNumber = int.Parse(match.Groups["episode"].Value);
}
else
{
match = Regex.Match(value, @"(?<show>.*)-(?<absolute>\d*)");
if (match.Success)
{
ShowSlug = match.Groups["Show"].Value;
AbsoluteNumber = int.Parse(match.Groups["absolute"].Value);
}
else
ShowSlug = value;
SeasonNumber = -1;
EpisodeNumber = -1;
}
}
}
/// <summary>
/// The slug of the Show that contain this episode. If this is not set, this episode is ill-formed.

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Kyoo.Controllers;
using Kyoo.Models.Attributes;
@ -12,9 +13,21 @@ namespace Kyoo.Models
{
/// <inheritdoc />
public int ID { get; set; }
/// <inheritdoc />
public string Slug => $"{ShowSlug}-s{SeasonNumber}";
public string Slug
{
get => $"{ShowSlug}-s{SeasonNumber}";
set
{
Match match = Regex.Match(value, @"(?<show>.*)-s(?<season>\d*)");
if (!match.Success)
throw new ArgumentException("Invalid season slug. Format: {showSlug}-s{seasonNumber}");
ShowSlug = match.Groups["show"].Value;
SeasonNumber = int.Parse(match.Groups["season"].Value);
}
}
/// <summary>
/// The slug of the Show that contain this episode. If this is not set, this season is ill-formed.

View File

@ -1,5 +1,7 @@
using System.Globalization;
using System;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Kyoo.Models.Attributes;
namespace Kyoo.Models
@ -44,10 +46,36 @@ namespace Kyoo.Models
"subrip" => ".srt",
{} x => $".{x}"
};
return $"{Episode.Slug}.{type}{Language}{index}{(IsForced ? "-forced" : "")}{codec}";
return $"{EpisodeSlug}.{type}{Language}{index}{(IsForced ? "-forced" : "")}{codec}";
}
set
{
Match match = Regex.Match(value, @"(?<show>.*)-s(?<season>\d+)e(?<episode>\d+)"
+ @"(\.(?<type>\w*))?\.(?<language>.{0,3})(?<forced>-forced)?(\..\w)?");
if (!match.Success)
{
match = Regex.Match(value, @"(?<show>.*)\.(?<language>.{0,3})(?<forced>-forced)?(\..\w)?");
if (!match.Success)
throw new ArgumentException("Invalid track slug. " +
"Format: {episodeSlug}.{language}[-forced][.{extension}]");
}
EpisodeSlug = Episode.GetSlug(match.Groups["show"].Value,
match.Groups["season"].Success ? int.Parse(match.Groups["season"].Value) : -1,
match.Groups["episode"].Success ? int.Parse(match.Groups["episode"].Value) : -1);
Language = match.Groups["language"].Value;
IsForced = match.Groups["forced"].Success;
if (match.Groups["type"].Success)
Type = Enum.Parse<StreamType>(match.Groups["type"].Value, true);
}
}
/// <summary>
/// The slug of the episode that contain this track. If this is not set, this track is ill-formed.
/// </summary>
[SerializeIgnore] public string EpisodeSlug { private get; set; }
/// <summary>
/// The title of the stream.
/// </summary>

View File

@ -260,7 +260,7 @@ namespace Kyoo
modelBuilder.Entity<Show>().Property(x => x.Slug).IsRequired();
modelBuilder.Entity<Studio>().Property(x => x.Slug).IsRequired();
modelBuilder.Entity<User>().Property(x => x.Slug).IsRequired();
modelBuilder.Entity<Collection>()
.HasIndex(x => x.Slug)
.IsUnique();

View File

@ -12,8 +12,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
</ItemGroup>

View File

@ -18,7 +18,7 @@
<!-- </PropertyGroup>-->
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.6">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -19,11 +19,11 @@
<!-- </PropertyGroup>-->
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.6">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.7" />
</ItemGroup>
<ItemGroup>

View File

@ -82,10 +82,6 @@ namespace Kyoo.SqLite
/// <param name="modelBuilder">The database's model builder.</param>
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// modelBuilder.HasPostgresEnum<Status>();
// modelBuilder.HasPostgresEnum<ItemType>();
// modelBuilder.HasPostgresEnum<StreamType>();
ValueConverter<string[], string> arrayConvertor = new(
x => string.Join(";", x),
x => x.Split(';', StringSplitOptions.None));
@ -112,7 +108,6 @@ namespace Kyoo.SqLite
modelBuilder.Entity<User>()
.Property(x => x.ExtraData)
.HasConversion(jsonConvertor);
base.OnModelCreating(modelBuilder);
}
@ -127,7 +122,7 @@ namespace Kyoo.SqLite
public override Expression<Func<T, bool>> Like<T>(Expression<Func<T, string>> query, string format)
{
MethodInfo iLike = MethodOfUtils.MethodOf<string, string, bool>(EF.Functions.Like);
MethodCallExpression call = Expression.Call(iLike, query.Body, Expression.Constant(format));
MethodCallExpression call = Expression.Call(iLike, Expression.Constant(EF.Functions), query.Body, Expression.Constant(format));
return Expression.Lambda<Func<T, bool>>(call, query.Parameters);
}

View File

@ -25,13 +25,13 @@ namespace Kyoo.Tests
PeopleRepository people = new(_database, provider,
new Lazy<IShowRepository>(() => LibraryManager.ShowRepository));
ShowRepository show = new(_database, studio, people, genre, provider);
SeasonRepository season = new(_database, provider, show);
SeasonRepository season = new(_database, provider);
LibraryItemRepository libraryItem = new(_database,
new Lazy<ILibraryRepository>(() => LibraryManager.LibraryRepository),
new Lazy<IShowRepository>(() => LibraryManager.ShowRepository),
new Lazy<ICollectionRepository>(() => LibraryManager.CollectionRepository));
TrackRepository track = new(_database);
EpisodeRepository episode = new(_database, provider, show, track);
EpisodeRepository episode = new(_database, provider, track);
LibraryManager = new LibraryManager(new IBaseRepository[] {
provider,

View File

@ -0,0 +1,15 @@
using Kyoo.Controllers;
using Kyoo.Models;
namespace Kyoo.Tests.SpecificTests
{
public class SeasonTests : RepositoryTests<Season>
{
private readonly ISeasonRepository _repository;
public SeasonTests()
{
_repository = Repositories.LibraryManager.SeasonRepository;
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Kyoo.Controllers;
@ -233,5 +234,23 @@ namespace Kyoo.Tests.SpecificTests
Show reference = TestSample.Get<Show>();
Assert.Equal(reference.Slug, await _repository.GetSlug(reference.ID));
}
[Theory]
[InlineData("test")]
[InlineData("super")]
[InlineData("title")]
[InlineData("TiTlE")]
[InlineData("SuPeR")]
public async Task SearchTest(string query)
{
Show value = new()
{
Slug = "super-test",
Title = "This is a test title²"
};
await _repository.Create(value);
ICollection<Show> ret = await _repository.Search(query);
KAssert.DeepEqual(value, ret.First());
}
}
}

View File

@ -45,6 +45,7 @@ namespace Kyoo.Tests
SeasonNumber = 1,
Title = "Season 1",
Overview = "The first season",
Show = Get<Show>(),
StartDate = new DateTime(2020, 06, 05),
EndDate = new DateTime(2020, 07, 05),
Poster = "poster"

View File

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Kyoo.Models;
using Kyoo.Models.Exceptions;
@ -16,7 +15,7 @@ namespace Kyoo.Controllers
public class EpisodeRepository : LocalRepository<Episode>, IEpisodeRepository
{
/// <summary>
/// The databse handle
/// The database handle
/// </summary>
private readonly DatabaseContext _database;
/// <summary>
@ -24,10 +23,6 @@ namespace Kyoo.Controllers
/// </summary>
private readonly IProviderRepository _providers;
/// <summary>
/// A show repository to get show's slug from their ID and keep the slug in each episode.
/// </summary>
private readonly IShowRepository _shows;
/// <summary>
/// A track repository to handle creation and deletion of tracks related to the current episode.
/// </summary>
private readonly ITrackRepository _tracks;
@ -41,66 +36,31 @@ namespace Kyoo.Controllers
/// </summary>
/// <param name="database">The database handle to use.</param>
/// <param name="providers">A provider repository</param>
/// <param name="shows">A show repository</param>
/// <param name="tracks">A track repository</param>
public EpisodeRepository(DatabaseContext database,
IProviderRepository providers,
IShowRepository shows,
ITrackRepository tracks)
: base(database)
{
_database = database;
_providers = providers;
_shows = shows;
_tracks = tracks;
}
/// <inheritdoc />
public override async Task<Episode> GetOrDefault(int id)
public Task<Episode> GetOrDefault(int showID, int seasonNumber, int episodeNumber)
{
Episode ret = await base.GetOrDefault(id);
if (ret != null)
ret.ShowSlug = await _shows.GetSlug(ret.ShowID);
return ret;
return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID
&& x.SeasonNumber == seasonNumber
&& x.EpisodeNumber == episodeNumber);
}
/// <inheritdoc />
public override async Task<Episode> GetOrDefault(string slug)
public Task<Episode> GetOrDefault(string showSlug, int seasonNumber, int episodeNumber)
{
Match match = Regex.Match(slug, @"(?<show>.*)-s(?<season>\d*)e(?<episode>\d*)");
if (match.Success)
{
return await GetOrDefault(match.Groups["show"].Value,
int.Parse(match.Groups["season"].Value),
int.Parse(match.Groups["episode"].Value));
}
Episode episode = await _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == slug);
if (episode != null)
episode.ShowSlug = slug;
return episode;
}
/// <inheritdoc />
public override async Task<Episode> GetOrDefault(Expression<Func<Episode, bool>> where)
{
Episode ret = await base.GetOrDefault(where);
if (ret != null)
ret.ShowSlug = await _shows.GetSlug(ret.ShowID);
return ret;
}
/// <inheritdoc />
public async Task<Episode> GetOrDefault(string showSlug, int seasonNumber, int 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;
return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug
&& x.SeasonNumber == seasonNumber
&& x.EpisodeNumber == episodeNumber);
}
/// <inheritdoc />
@ -122,61 +82,30 @@ namespace Kyoo.Controllers
}
/// <inheritdoc />
public async Task<Episode> GetOrDefault(int showID, int seasonNumber, int episodeNumber)
public Task<Episode> GetAbsolute(int showID, int absoluteNumber)
{
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;
return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID
&& x.AbsoluteNumber == absoluteNumber);
}
/// <inheritdoc />
public async Task<Episode> GetAbsolute(int showID, int absoluteNumber)
public Task<Episode> GetAbsolute(string showSlug, int 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;
}
/// <inheritdoc />
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;
return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug
&& x.AbsoluteNumber == absoluteNumber);
}
/// <inheritdoc />
public override async Task<ICollection<Episode>> Search(string query)
{
List<Episode> episodes = await _database.Episodes
return await _database.Episodes
.Where(x => x.EpisodeNumber != -1)
.Where(_database.Like<Episode>(x => x.Title, $"%{query}%"))
.OrderBy(DefaultSort)
.Take(20)
.ToListAsync();
foreach (Episode episode in episodes)
episode.ShowSlug = await _shows.GetSlug(episode.ShowID);
return episodes;
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public override async Task<Episode> Create(Episode obj)
{

View File

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Kyoo.Models;
using Kyoo.Models.Exceptions;
@ -23,66 +22,30 @@ namespace Kyoo.Controllers
/// A provider repository to handle externalID creation and deletion
/// </summary>
private readonly IProviderRepository _providers;
/// <summary>
/// A show repository to get show's slug from their ID and keep the slug in each episode.
/// </summary>
private readonly IShowRepository _shows;
/// <inheritdoc/>
protected override Expression<Func<Season, object>> DefaultSort => x => x.SeasonNumber;
/// <summary>
/// Create a new <see cref="SeasonRepository"/> using the provided handle, a provider & a show repository and
/// a service provider to lazilly request an episode repository.
/// Create a new <see cref="SeasonRepository"/>.
/// </summary>
/// <param name="database">The database handle that will be used</param>
/// <param name="providers">A provider repository</param>
/// <param name="shows">A show repository</param>
public SeasonRepository(DatabaseContext database,
IProviderRepository providers,
IShowRepository shows)
IProviderRepository providers)
: base(database)
{
_database = database;
_providers = providers;
_shows = shows;
}
/// <inheritdoc/>
public override async Task<Season> Get(int id)
{
Season ret = await base.Get(id);
ret.ShowSlug = await _shows.GetSlug(ret.ShowID);
return ret;
}
/// <inheritdoc/>
public override async Task<Season> Get(Expression<Func<Season, bool>> where)
{
Season ret = await base.Get(where);
ret.ShowSlug = await _shows.GetSlug(ret.ShowID);
return ret;
}
/// <inheritdoc/>
public override Task<Season> Get(string slug)
{
Match match = Regex.Match(slug, @"(?<show>.*)-s(?<season>\d*)");
if (!match.Success)
throw new ArgumentException("Invalid season slug. Format: {showSlug}-s{seasonNumber}");
return Get(match.Groups["show"].Value, int.Parse(match.Groups["season"].Value));
}
/// <inheritdoc/>
public async Task<Season> Get(int showID, int seasonNumber)
{
Season ret = await GetOrDefault(showID, seasonNumber);
if (ret == null)
throw new ItemNotFoundException($"No season {seasonNumber} found for the show {showID}");
ret.ShowSlug = await _shows.GetSlug(showID);
return ret;
}
@ -92,7 +55,6 @@ namespace Kyoo.Controllers
Season ret = await GetOrDefault(showSlug, seasonNumber);
if (ret == null)
throw new ItemNotFoundException($"No season {seasonNumber} found for the show {showSlug}");
ret.ShowSlug = showSlug;
return ret;
}
@ -113,27 +75,13 @@ namespace Kyoo.Controllers
/// <inheritdoc/>
public override async Task<ICollection<Season>> Search(string query)
{
List<Season> seasons = await _database.Seasons
return await _database.Seasons
.Where(_database.Like<Season>(x => x.Title, $"%{query}%"))
.OrderBy(DefaultSort)
.Take(20)
.ToListAsync();
foreach (Season season in seasons)
season.ShowSlug = await _shows.GetSlug(season.ShowID);
return seasons;
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
public override async Task<Season> Create(Season obj)
{

View File

@ -16,7 +16,7 @@ namespace Kyoo.Controllers
public class TrackRepository : LocalRepository<Track>, ITrackRepository
{
/// <summary>
/// The databse handle
/// The database handle
/// </summary>
private readonly DatabaseContext _database;
@ -27,7 +27,7 @@ namespace Kyoo.Controllers
/// <summary>
/// Create a new <see cref="TrackRepository"/>.
/// </summary>
/// <param name="database">The datatabse handle</param>
/// <param name="database">The database handle</param>
public TrackRepository(DatabaseContext database)
: base(database)
{

View File

@ -35,9 +35,9 @@
<ProjectReference Include="../Kyoo.Common/Kyoo.Common.csproj" />
<ProjectReference Include="../Kyoo.CommonAPI/Kyoo.CommonAPI.csproj" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.6" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="3.1.15" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.7" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="3.1.16" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.7" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>