From 066229eb0e00adf4c40e19b43e36e796e72b0470 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 25 Jul 2023 01:09:10 +0900 Subject: [PATCH] Fix tracks slugs --- .github/workflows/tests.yml | 2 +- back/.gitignore | 1 - back/ef.rsp | 4 ++ .../Models/Resources/Episode.cs | 2 +- .../Models/Resources/Track.cs | 8 +-- .../Repositories/EpisodeRepository.cs | 20 +++---- .../Repositories/LocalRepository.cs | 9 +++ .../Repositories/SeasonRepository.cs | 10 +++- .../Repositories/TrackRepository.cs | 16 +++++- back/src/Kyoo.Postgresql/DatabaseContext.cs | 13 +---- ...20230724144449_RemoveTriggers.Designer.cs} | 15 +++-- ...rs.cs => 20230724144449_RemoveTriggers.cs} | 55 ++++++++++++++++++- .../PostgresContextModelSnapshot.cs | 6 +- .../Database/RepositoryActivator.cs | 4 +- back/tests/Kyoo.Tests/Database/TestContext.cs | 2 +- 15 files changed, 121 insertions(+), 46 deletions(-) create mode 100644 back/ef.rsp rename back/src/Kyoo.Postgresql/Migrations/{20230621074246_RemoveTrigers.Designer.cs => 20230724144449_RemoveTriggers.Designer.cs} (99%) rename back/src/Kyoo.Postgresql/Migrations/{20230621074246_RemoveTrigers.cs => 20230724144449_RemoveTriggers.cs} (62%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 762960ee..610c9663 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,7 +38,7 @@ jobs: dotnet test --no-build '-p:CollectCoverage=true;CoverletOutputFormat=opencover' --logger "trx;LogFileName=TestOutputResults.xml" env: POSTGRES_HOST: postgres - POSTGRES_USERNAME: postgres + POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres - name: Sanitize coverage output diff --git a/back/.gitignore b/back/.gitignore index a8d0951a..b6e558c2 100644 --- a/back/.gitignore +++ b/back/.gitignore @@ -80,7 +80,6 @@ StyleCopReport.xml *.ipdb *.pgc *.pgd -*.rsp *.sbr *.tlb *.tli diff --git a/back/ef.rsp b/back/ef.rsp new file mode 100644 index 00000000..fda0e4f6 --- /dev/null +++ b/back/ef.rsp @@ -0,0 +1,4 @@ +--project +src/Kyoo.Postgresql/Kyoo.Postgresql.csproj +--msbuildprojectextensionspath +out/obj/Kyoo.Postgresql diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs index d01f1fec..619b802e 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -80,7 +80,7 @@ namespace Kyoo.Abstractions.Models /// /// The slug of the Show that contain this episode. If this is not set, this episode is ill-formed. /// - [SerializeIgnore] public string ShowSlug { get; set; } + [SerializeIgnore] public string ShowSlug { private get; set; } /// /// The ID of the Show containing this episode. diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Track.cs b/back/src/Kyoo.Abstractions/Models/Resources/Track.cs index d243c0eb..f838c813 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Track.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Track.cs @@ -68,7 +68,7 @@ namespace Kyoo.Abstractions.Models { string type = Type.ToString().ToLowerInvariant(); string index = TrackIndex != 0 ? $"-{TrackIndex}" : string.Empty; - string episode = _episodeSlug ?? Episode?.Slug ?? EpisodeID.ToString(CultureInfo.InvariantCulture); + string episode = EpisodeSlug ?? Episode?.Slug ?? EpisodeID.ToString(CultureInfo.InvariantCulture); return $"{episode}.{Language ?? "und"}{index}{(IsForced ? ".forced" : string.Empty)}.{type}"; } @@ -88,7 +88,7 @@ namespace Kyoo.Abstractions.Models ); } - _episodeSlug = match.Groups["ep"].Value; + EpisodeSlug = match.Groups["ep"].Value; Language = match.Groups["lang"].Value; if (Language == "und") Language = null; @@ -154,7 +154,7 @@ namespace Kyoo.Abstractions.Models { _episode = value; if (_episode != null) - _episodeSlug = _episode.Slug; + EpisodeSlug = _episode.Slug; } } @@ -190,7 +190,7 @@ namespace Kyoo.Abstractions.Models /// /// The slug of the episode that contain this track. If this is not set, this track is ill-formed. /// - [SerializeIgnore] private string _episodeSlug; + [SerializeIgnore] public string EpisodeSlug { private get; set; } /// /// The episode that uses this track. diff --git a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs index 5a0fddae..7318f833 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs @@ -47,7 +47,7 @@ namespace Kyoo.Core.Controllers /// /// A track repository to handle creation and deletion of tracks related to the current episode. /// - private readonly ITrackRepository _tracks; + private readonly Lazy _tracks; /// // Use absolute numbers by default and fallback to season/episodes if it does not exists. @@ -67,7 +67,7 @@ namespace Kyoo.Core.Controllers public EpisodeRepository(DatabaseContext database, IShowRepository shows, IProviderRepository providers, - ITrackRepository tracks) + Lazy tracks) : base(database) { _database = database; @@ -75,15 +75,15 @@ namespace Kyoo.Core.Controllers _tracks = tracks; // Edit episode slugs when the show's slug changes. - shows.OnEdited += async (show) => + shows.OnEdited += (show) => { - foreach (Episode ep in _database.Episodes.Where(x => x.ShowID == show.ID)) + List episodes = _database.Episodes.AsTracking().Where(x => x.ShowID == show.ID).ToList(); + foreach (Episode ep in episodes) { - Console.WriteLine("BFR ID: {0}; Slug: {1}; ShowSlug: {2}", ep.ID, ep.Slug, ep.ShowSlug); ep.ShowSlug = show.Slug; - Console.WriteLine("AFT ID: {0}; Slug: {1}; ShowSlug: {2}", ep.ID, ep.Slug, ep.ShowSlug); + _database.SaveChanges(); + OnResourceEdited(ep); } - await _database.SaveChangesAsync(); }; } @@ -171,7 +171,7 @@ namespace Kyoo.Core.Controllers if (changed.Tracks != null || resetOld) { - await _tracks.DeleteAll(x => x.EpisodeID == resource.ID); + await _tracks.Value.DeleteAll(x => x.EpisodeID == resource.ID); resource.Tracks = changed.Tracks; await _ValidateTracks(resource); } @@ -196,7 +196,7 @@ namespace Kyoo.Core.Controllers resource.Tracks = await resource.Tracks.SelectAsync(x => { x.Episode = resource; - return _tracks.Create(x); + return _tracks.Value.Create(x); }).ToListAsync(); _database.Tracks.AttachRange(resource.Tracks); return resource; @@ -235,7 +235,7 @@ namespace Kyoo.Core.Controllers throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Deleted; - await obj.Tracks.ForEachAsync(x => _tracks.Delete(x)); + await obj.Tracks.ForEachAsync(x => _tracks.Value.Delete(x)); obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Deleted); await _database.SaveChangesAsync(); await base.Delete(obj); diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs index 29b91ca8..85c3b6a3 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs @@ -353,6 +353,15 @@ namespace Kyoo.Core.Controllers OnCreated?.Invoke(obj); } + /// + /// Callback that should be called after a resource has been edited. + /// + /// The resource newly edited. + protected void OnResourceEdited(T obj) + { + OnEdited?.Invoke(obj); + } + /// public virtual async Task CreateIfNotExists(T obj) { diff --git a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs index 965097f7..a723e1a5 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs @@ -61,11 +61,15 @@ namespace Kyoo.Core.Controllers _providers = providers; // Edit seasons slugs when the show's slug changes. - shows.OnEdited += async (show) => + shows.OnEdited += (show) => { - foreach (Season season in _database.Seasons.Where(x => x.ShowID == show.ID)) + List seasons = _database.Seasons.AsTracking().Where(x => x.ShowID == show.ID).ToList(); + foreach (Season season in seasons) + { season.ShowSlug = show.Slug; - await _database.SaveChangesAsync(); + _database.SaveChanges(); + OnResourceEdited(season); + } }; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/TrackRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/TrackRepository.cs index c416e6e3..6e2ef6b8 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/TrackRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/TrackRepository.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; @@ -43,10 +44,23 @@ namespace Kyoo.Core.Controllers /// Create a new . /// /// The database handle - public TrackRepository(DatabaseContext database) + /// The episode repository + public TrackRepository(DatabaseContext database, IEpisodeRepository episodes) : base(database) { _database = database; + + // Edit tracks slugs when the episodes's slug changes. + episodes.OnEdited += (ep) => + { + List tracks = _database.Tracks.AsTracking().Where(x => x.EpisodeID == ep.ID).ToList(); + foreach (Track track in tracks) + { + track.EpisodeSlug = ep.Slug; + _database.SaveChanges(); + OnResourceEdited(track); + } + }; } /// diff --git a/back/src/Kyoo.Postgresql/DatabaseContext.cs b/back/src/Kyoo.Postgresql/DatabaseContext.cs index 855ef703..ab50866b 100644 --- a/back/src/Kyoo.Postgresql/DatabaseContext.cs +++ b/back/src/Kyoo.Postgresql/DatabaseContext.cs @@ -305,6 +305,9 @@ namespace Kyoo.Postgresql modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); + modelBuilder.Entity().Property(x => x.Slug).IsRequired(); + modelBuilder.Entity().Property(x => x.Slug).IsRequired(); + modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); @@ -350,16 +353,6 @@ namespace Kyoo.Postgresql modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); - - modelBuilder.Entity() - .Property(x => x.Slug) - .ValueGeneratedOnAddOrUpdate(); - modelBuilder.Entity() - .Property(x => x.Slug) - .ValueGeneratedOnAddOrUpdate(); - modelBuilder.Entity() - .Property(x => x.Slug) - .ValueGeneratedOnAddOrUpdate(); } /// diff --git a/back/src/Kyoo.Postgresql/Migrations/20230621074246_RemoveTrigers.Designer.cs b/back/src/Kyoo.Postgresql/Migrations/20230724144449_RemoveTriggers.Designer.cs similarity index 99% rename from back/src/Kyoo.Postgresql/Migrations/20230621074246_RemoveTrigers.Designer.cs rename to back/src/Kyoo.Postgresql/Migrations/20230724144449_RemoveTriggers.Designer.cs index 84ac2c3f..896a679a 100644 --- a/back/src/Kyoo.Postgresql/Migrations/20230621074246_RemoveTrigers.Designer.cs +++ b/back/src/Kyoo.Postgresql/Migrations/20230724144449_RemoveTriggers.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using System.Collections.Generic; using Kyoo.Abstractions.Models; @@ -12,11 +12,10 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Postgresql.Migrations { [DbContext(typeof(PostgresContext))] - [Migration("20230621074246_RemoveTrigers")] - partial class RemoveTrigers + [Migration("20230724144449_RemoveTriggers")] + partial class RemoveTriggers { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) + protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder @@ -107,7 +106,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("show_id"); b.Property("Slug") - .ValueGeneratedOnAddOrUpdate() + .IsRequired() .HasColumnType("text") .HasColumnName("slug"); @@ -362,7 +361,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("show_id"); b.Property("Slug") - .ValueGeneratedOnAddOrUpdate() + .IsRequired() .HasColumnType("text") .HasColumnName("slug"); @@ -518,7 +517,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("path"); b.Property("Slug") - .ValueGeneratedOnAddOrUpdate() + .IsRequired() .HasColumnType("text") .HasColumnName("slug"); diff --git a/back/src/Kyoo.Postgresql/Migrations/20230621074246_RemoveTrigers.cs b/back/src/Kyoo.Postgresql/Migrations/20230724144449_RemoveTriggers.cs similarity index 62% rename from back/src/Kyoo.Postgresql/Migrations/20230621074246_RemoveTrigers.cs rename to back/src/Kyoo.Postgresql/Migrations/20230724144449_RemoveTriggers.cs index 50a96086..171f40ab 100644 --- a/back/src/Kyoo.Postgresql/Migrations/20230621074246_RemoveTrigers.cs +++ b/back/src/Kyoo.Postgresql/Migrations/20230724144449_RemoveTriggers.cs @@ -23,11 +23,41 @@ namespace Kyoo.Postgresql.Migrations /// /// Remove triggers /// - public partial class RemoveTrigers : Migration + public partial class RemoveTriggers : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { + migrationBuilder.AlterColumn( + name: "slug", + table: "tracks", + type: "text", + nullable: false, + defaultValue: string.Empty, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "slug", + table: "seasons", + type: "text", + nullable: false, + defaultValue: string.Empty, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "slug", + table: "episodes", + type: "text", + nullable: false, + defaultValue: string.Empty, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + // language=PostgreSQL migrationBuilder.Sql("DROP TRIGGER show_slug_trigger ON shows;"); // language=PostgreSQL @@ -53,6 +83,29 @@ namespace Kyoo.Postgresql.Migrations /// protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.AlterColumn( + name: "slug", + table: "tracks", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn( + name: "slug", + table: "seasons", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn( + name: "slug", + table: "episodes", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); } } } diff --git a/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs b/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs index 2345f84d..92dfd929 100644 --- a/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs +++ b/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs @@ -104,7 +104,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("show_id"); b.Property("Slug") - .ValueGeneratedOnAddOrUpdate() + .IsRequired() .HasColumnType("text") .HasColumnName("slug"); @@ -359,7 +359,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("show_id"); b.Property("Slug") - .ValueGeneratedOnAddOrUpdate() + .IsRequired() .HasColumnType("text") .HasColumnName("slug"); @@ -515,7 +515,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("path"); b.Property("Slug") - .ValueGeneratedOnAddOrUpdate() + .IsRequired() .HasColumnType("text") .HasColumnName("slug"); diff --git a/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs b/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs index fe9adcbc..6fe144ea 100644 --- a/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs +++ b/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs @@ -48,8 +48,8 @@ namespace Kyoo.Tests.Database SeasonRepository season = new(_NewContext(), show, provider); LibraryItemRepository libraryItem = new(_NewContext(), new Lazy(() => LibraryManager.LibraryRepository)); - TrackRepository track = new(_NewContext()); - EpisodeRepository episode = new(_NewContext(), show, provider, track); + EpisodeRepository episode = new(_NewContext(), show, provider, new Lazy(() => LibraryManager.TrackRepository)); + TrackRepository track = new(_NewContext(), episode); UserRepository user = new(_NewContext()); LibraryManager = new LibraryManager(new IBaseRepository[] { diff --git a/back/tests/Kyoo.Tests/Database/TestContext.cs b/back/tests/Kyoo.Tests/Database/TestContext.cs index 71b947ac..1b430b94 100644 --- a/back/tests/Kyoo.Tests/Database/TestContext.cs +++ b/back/tests/Kyoo.Tests/Database/TestContext.cs @@ -104,7 +104,7 @@ namespace Kyoo.Tests { string server = Environment.GetEnvironmentVariable("POSTGRES_HOST") ?? "127.0.0.1"; string port = Environment.GetEnvironmentVariable("POSTGRES_PORT") ?? "5432"; - string username = Environment.GetEnvironmentVariable("POSTGRES_USERNAME") ?? "kyoo"; + string username = Environment.GetEnvironmentVariable("POSTGRES_USER") ?? "kyoo"; string password = Environment.GetEnvironmentVariable("POSTGRES_PASSWORD") ?? "kyooPassword"; return $"Server={server};Port={port};Database={database};User ID={username};Password={password};Include Error Detail=true"; }