Adding episodes triggers

This commit is contained in:
Zoe Roux 2021-06-22 23:19:32 +02:00
parent d2f774e32d
commit 37c752229e
11 changed files with 132 additions and 18 deletions

View File

@ -0,0 +1,10 @@
using System;
namespace Kyoo.Models.Attributes
{
/// <summary>
/// An attribute to inform that the property is computed automatically and can't be assigned manually.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ComputedAttribute : NotMergeableAttribute { }
}

View File

@ -17,12 +17,17 @@ namespace Kyoo.Models
public int ID { get; set; } public int ID { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public string Slug [Computed] public string Slug
{ {
get => GetSlug(ShowSlug, SeasonNumber, EpisodeNumber, AbsoluteNumber); get
{
if (ShowSlug == null && Show == null)
return GetSlug(ShowID.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber);
return GetSlug(ShowSlug ?? Show.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber);
}
[UsedImplicitly] private set [UsedImplicitly] private set
{ {
Match match = Regex.Match(value, @"(?<show>.*)-s(?<season>\d*)e(?<episode>\d*)"); Match match = Regex.Match(value, @"(?<show>.+)-s(?<season>\d+)e(?<episode>\d+)");
if (match.Success) if (match.Success)
{ {
@ -45,7 +50,7 @@ namespace Kyoo.Models
} }
} }
} }
/// <summary> /// <summary>
/// The slug of the Show that contain this episode. If this is not set, this episode is ill-formed. /// The slug of the Show that contain this episode. If this is not set, this episode is ill-formed.
/// </summary> /// </summary>

View File

@ -16,12 +16,17 @@ namespace Kyoo.Models
public int ID { get; set; } public int ID { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public string Slug [Computed] public string Slug
{ {
get => $"{ShowSlug}-s{SeasonNumber}"; get
{
if (ShowSlug == null && Show == null)
return $"{ShowID}-s{SeasonNumber}";
return $"{ShowSlug ?? Show?.Slug}-s{SeasonNumber}";
}
[UsedImplicitly] private set [UsedImplicitly] private set
{ {
Match match = Regex.Match(value, @"(?<show>.*)-s(?<season>\d*)"); Match match = Regex.Match(value ?? "", @"(?<show>.+)-s(?<season>\d+)");
if (!match.Success) if (!match.Success)
throw new ArgumentException("Invalid season slug. Format: {showSlug}-s{seasonNumber}"); throw new ArgumentException("Invalid season slug. Format: {showSlug}-s{seasonNumber}");
@ -29,7 +34,7 @@ namespace Kyoo.Models
SeasonNumber = int.Parse(match.Groups["season"].Value); SeasonNumber = int.Parse(match.Groups["season"].Value);
} }
} }
/// <summary> /// <summary>
/// The slug of the Show that contain this episode. If this is not set, this season is ill-formed. /// The slug of the Show that contain this episode. If this is not set, this season is ill-formed.
/// </summary> /// </summary>

View File

@ -29,7 +29,7 @@ namespace Kyoo.Models
public int ID { get; set; } public int ID { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public string Slug [Computed] public string Slug
{ {
get get
{ {

View File

@ -79,7 +79,7 @@ namespace Kyoo
Type type = typeof(T); Type type = typeof(T);
IEnumerable<PropertyInfo> properties = type.GetProperties() IEnumerable<PropertyInfo> properties = type.GetProperties()
.Where(x => x.CanRead && x.CanWrite .Where(x => x.CanRead && x.CanWrite
&& Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null);
if (where != null) if (where != null)

View File

@ -330,6 +330,16 @@ namespace Kyoo
modelBuilder.Entity<Track>() modelBuilder.Entity<Track>()
.Property(x => x.Slug) .Property(x => x.Slug)
.ValueGeneratedOnAddOrUpdate(); .ValueGeneratedOnAddOrUpdate();
modelBuilder.Entity<Episode>()
.Property(x => x.EpisodeNumber)
.HasDefaultValue(-1);
modelBuilder.Entity<Episode>()
.Property(x => x.SeasonNumber)
.HasDefaultValue(-1);
modelBuilder.Entity<Episode>()
.Property(x => x.AbsoluteNumber)
.HasDefaultValue(-1);
} }
/// <summary> /// <summary>

View File

@ -14,6 +14,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.7" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.7" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
</ItemGroup> </ItemGroup>

View File

@ -257,6 +257,8 @@ namespace Kyoo.Controllers
/// <exception cref="ArgumentException">You can throw this if the resource is illegal and should not be saved.</exception> /// <exception cref="ArgumentException">You can throw this if the resource is illegal and should not be saved.</exception>
protected virtual Task Validate(T resource) protected virtual Task Validate(T resource)
{ {
if (typeof(T).GetProperty(nameof(resource.Slug))!.GetCustomAttribute<ComputedAttribute>() != null)
return Task.CompletedTask;
if (string.IsNullOrEmpty(resource.Slug)) if (string.IsNullOrEmpty(resource.Slug))
throw new ArgumentException("Resource can't have null as a slug."); throw new ArgumentException("Resource can't have null as a slug.");
if (int.TryParse(resource.Slug, out int _)) if (int.TryParse(resource.Slug, out int _))

View File

@ -18,10 +18,26 @@ namespace Kyoo.SqLite.Migrations
UPDATE Seasons SET Slug = (SELECT Slug from Shows WHERE ID = ShowID) || '-s' || SeasonNumber UPDATE Seasons SET Slug = (SELECT Slug from Shows WHERE ID = ShowID) || '-s' || SeasonNumber
WHERE ID == new.ID; WHERE ID == new.ID;
END"); END");
migrationBuilder.Sql(@"
CREATE TRIGGER EpisodeSlugInsert AFTER INSERT ON Episodes FOR EACH ROW
BEGIN
UPDATE Episodes SET Slug = (SELECT Slug from Shows WHERE ID = ShowID) || '-s' || SeasonNumber || 'e' || EpisodeNumber
WHERE ID == new.ID;
END");
migrationBuilder.Sql(@"
CREATE TRIGGER EpisodeSlugUpdate AFTER UPDATE OF EpisodeNumber, SeasonNumber, ShowID ON Episodes FOR EACH ROW
BEGIN
UPDATE Episodes SET Slug = (SELECT Slug from Shows WHERE ID = ShowID) || '-s' || SeasonNumber || 'e' || EpisodeNumber
WHERE ID == new.ID;
END");
migrationBuilder.Sql(@" migrationBuilder.Sql(@"
CREATE TRIGGER ShowSlugUpdate AFTER UPDATE OF Slug ON Shows FOR EACH ROW CREATE TRIGGER ShowSlugUpdate AFTER UPDATE OF Slug ON Shows FOR EACH ROW
BEGIN BEGIN
UPDATE Seasons SET Slug = new.Slug || '-s' || SeasonNumber WHERE ShowID = new.ID; UPDATE Seasons SET Slug = new.Slug || '-s' || SeasonNumber WHERE ShowID = new.ID;
UPDATE Episodes SET Slug = new.Slug || '-s' || SeasonNumber || 'e' || EpisodeNumber WHERE ShowID = new.ID;
END;"); END;");
} }
@ -29,6 +45,8 @@ namespace Kyoo.SqLite.Migrations
{ {
migrationBuilder.Sql("DROP TRIGGER SeasonSlugInsert;"); migrationBuilder.Sql("DROP TRIGGER SeasonSlugInsert;");
migrationBuilder.Sql("DROP TRIGGER SeasonSlugUpdate;"); migrationBuilder.Sql("DROP TRIGGER SeasonSlugUpdate;");
migrationBuilder.Sql("DROP TRIGGER EpisodeSlugInsert;");
migrationBuilder.Sql("DROP TRIGGER EpisodeSlugUpdate;");
migrationBuilder.Sql("DROP TRIGGER ShowSlugUpdate;"); migrationBuilder.Sql("DROP TRIGGER ShowSlugUpdate;");
} }
} }

View File

@ -1,3 +1,5 @@
using System.Threading.Tasks;
using Kyoo.Controllers;
using Kyoo.Models; using Kyoo.Models;
using Xunit; using Xunit;
@ -25,8 +27,70 @@ namespace Kyoo.Tests.Library
public abstract class AEpisodeTests : RepositoryTests<Episode> public abstract class AEpisodeTests : RepositoryTests<Episode>
{ {
protected AEpisodeTests(RepositoryActivator repositories) private readonly IEpisodeRepository _repository;
: base(repositories)
{ } protected AEpisodeTests(RepositoryActivator repositories)
: base(repositories)
{
_repository = repositories.LibraryManager.EpisodeRepository;
}
[Fact]
public async Task SlugEditTest()
{
Episode episode = await _repository.Get(1);
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e1", episode.Slug);
Show show = new()
{
ID = episode.ShowID,
Slug = "new-slug"
};
await Repositories.LibraryManager.ShowRepository.Edit(show, false);
episode = await _repository.Get(1);
Assert.Equal("new-slug-s1e1", episode.Slug);
}
[Fact]
public async Task SeasonNumberEditTest()
{
Episode episode = await _repository.Get(1);
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e1", episode.Slug);
await _repository.Edit(new Episode
{
ID = 1,
SeasonNumber = 2
}, false);
episode = await _repository.Get(1);
Assert.Equal($"{TestSample.Get<Show>().Slug}-s2e1", episode.Slug);
}
[Fact]
public async Task EpisodeNumberEditTest()
{
Episode episode = await _repository.Get(1);
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e1", episode.Slug);
await _repository.Edit(new Episode
{
ID = 1,
EpisodeNumber = 2
}, false);
episode = await _repository.Get(1);
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
}
[Fact]
public async Task EpisodeCreationSlugTest()
{
Episode season = await _repository.Create(new Episode
{
ShowID = TestSample.Get<Show>().ID,
SeasonNumber = 2,
EpisodeNumber = 4
});
Assert.Equal($"{TestSample.Get<Show>().Slug}-s2e4", season.Slug);
}
// TODO absolute numbering tests
} }
} }

View File

@ -145,7 +145,7 @@ namespace Kyoo.Controllers
/// <returns>The <see cref="resource"/> parameter is returned.</returns> /// <returns>The <see cref="resource"/> parameter is returned.</returns>
private async Task<Episode> ValidateTracks(Episode resource) private async Task<Episode> ValidateTracks(Episode resource)
{ {
resource.Tracks = await resource.Tracks.MapAsync((x, i) => resource.Tracks = await TaskUtils.DefaultIfNull(resource.Tracks?.MapAsync((x, i) =>
{ {
x.Episode = resource; x.Episode = resource;
x.TrackIndex = resource.Tracks.Take(i).Count(y => x.Language == y.Language x.TrackIndex = resource.Tracks.Take(i).Count(y => x.Language == y.Language
@ -153,7 +153,7 @@ namespace Kyoo.Controllers
&& x.Codec == y.Codec && x.Codec == y.Codec
&& x.Type == y.Type); && x.Type == y.Type);
return _tracks.Create(x); return _tracks.Create(x);
}).ToListAsync(); }).ToListAsync());
return resource; return resource;
} }
@ -161,13 +161,12 @@ namespace Kyoo.Controllers
protected override async Task Validate(Episode resource) protected override async Task Validate(Episode resource)
{ {
await base.Validate(resource); await base.Validate(resource);
resource.ExternalIDs = await resource.ExternalIDs.SelectAsync(async x => await resource.ExternalIDs.ForEachAsync(async x =>
{ {
x.Second = await _providers.CreateIfNotExists(x.Second); x.Second = await _providers.CreateIfNotExists(x.Second);
x.SecondID = x.Second.ID; x.SecondID = x.Second.ID;
_database.Entry(x.Second).State = EntityState.Detached; _database.Entry(x.Second).State = EntityState.Detached;
return x; });
}).ToListAsync();
} }
/// <inheritdoc /> /// <inheritdoc />