mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Adding episodes triggers
This commit is contained in:
parent
d2f774e32d
commit
37c752229e
10
Kyoo.Common/Models/Attributes/ComputedAttribute.cs
Normal file
10
Kyoo.Common/Models/Attributes/ComputedAttribute.cs
Normal 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 { }
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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)
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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 _))
|
||||||
|
@ -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;");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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 />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user