From dc42ed031fefbf966a41d069dfdb82aec9675dfa Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 13 Jul 2021 15:18:25 +0200 Subject: [PATCH] Handling tracks slugs --- .github/workflows/analysis.yml | 2 +- .github/workflows/tests.yml | 4 +- Kyoo.Common/Controllers/ILibraryManager.cs | 19 --- Kyoo.Common/Controllers/IRepository.cs | 20 +-- .../Implementations/LibraryManager.cs | 12 -- Kyoo.Common/Models/Resources/Track.cs | 68 +++++---- Kyoo.CommonAPI/DatabaseContext.cs | 48 ------- .../Migrations/20210627141941_Triggers.cs | 92 ++++++++++-- .../Migrations/20210626141347_Triggers.cs | 133 ++++++++++++++---- Kyoo.Tests/KAssert.cs | 1 - .../Library/SpecificTests/TrackTests.cs | 13 ++ .../Repositories/EpisodeRepository.cs | 10 +- .../Repositories/TrackRepository.cs | 67 +-------- Kyoo/Startup.cs | 1 - Kyoo/Views/SubtitleApi.cs | 17 +-- 15 files changed, 258 insertions(+), 249 deletions(-) diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index ea7b58e3..1924ffdf 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -35,7 +35,7 @@ jobs: check-name: tests repo-token: ${{secrets.GITHUB_TOKEN}} running-workflow-name: analysis - allowed-conclusions: success,skipped,cancelled,neutral,failed + allowed-conclusions: success,skipped,cancelled,neutral,failure - name: Download coverage report uses: dawidd6/action-download-artifact@v2 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bd010b83..60cafa6a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,9 +29,11 @@ jobs: POSTGRES_USERNAME: postgres POSTGRES_PASSWORD: postgres - name: Sanitize coverage output + if: ${{ always() }} run: sed -i "s'$(pwd)'.'" Kyoo.Tests/coverage.opencover.xml - name: Upload coverage report + if: ${{ always() }} uses: actions/upload-artifact@v2 with: name: coverage.xml - path: "**/coverage.opencover.xml" \ No newline at end of file + path: "**/coverage.opencover.xml" diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 53c7061a..490f8073 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -149,16 +149,6 @@ namespace Kyoo.Controllers [ItemNotNull] Task Get(string showSlug, int seasonNumber, int episodeNumber); - /// - /// Get a track from it's slug and it's type. - /// - /// The slug of the track - /// The type (Video, Audio or Subtitle) - /// If the item is not found - /// The track found - [ItemNotNull] - Task Get(string slug, StreamType type = StreamType.Unknown); - /// /// Get the resource by it's ID or null if it is not found. /// @@ -224,15 +214,6 @@ namespace Kyoo.Controllers [ItemCanBeNull] Task GetOrDefault(string showSlug, int seasonNumber, int episodeNumber); - /// - /// Get a track from it's slug and it's type or null if it is not found. - /// - /// The slug of the track - /// The type (Video, Audio or Subtitle) - /// The track found - [ItemCanBeNull] - Task GetOrDefault(string slug, StreamType type = StreamType.Unknown); - /// /// Load a related resource diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 352ebe04..f7be69ad 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -375,25 +375,7 @@ namespace Kyoo.Controllers /// /// A repository to handle tracks /// - public interface ITrackRepository : IRepository - { - /// - /// Get a track from it's slug and it's type. - /// - /// The slug of the track - /// The type (Video, Audio or Subtitle) - /// If the item is not found - /// The track found - Task Get(string slug, StreamType type = StreamType.Unknown); - - /// - /// Get a track from it's slug and it's type or null if it is not found. - /// - /// The slug of the track - /// The type (Video, Audio or Subtitle) - /// The track found - Task GetOrDefault(string slug, StreamType type = StreamType.Unknown); - } + public interface ITrackRepository : IRepository { } /// /// A repository to handle libraries. diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 66581157..bc30e756 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -114,12 +114,6 @@ namespace Kyoo.Controllers return EpisodeRepository.Get(showSlug, seasonNumber, episodeNumber); } - /// - public Task Get(string slug, StreamType type = StreamType.Unknown) - { - return TrackRepository.Get(slug, type); - } - /// public async Task GetOrDefault(int id) where T : class, IResource @@ -165,12 +159,6 @@ namespace Kyoo.Controllers return await EpisodeRepository.GetOrDefault(showSlug, seasonNumber, episodeNumber); } - /// - public async Task GetOrDefault(string slug, StreamType type = StreamType.Unknown) - { - return await TrackRepository.GetOrDefault(slug, type); - } - /// public Task Load(T obj, Expression> member) where T : class, IResource diff --git a/Kyoo.Common/Models/Resources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs index 00ae745c..f093699b 100644 --- a/Kyoo.Common/Models/Resources/Track.cs +++ b/Kyoo.Common/Models/Resources/Track.cs @@ -33,42 +33,27 @@ namespace Kyoo.Models { get { - string type = Type switch - { - StreamType.Subtitle => "", - StreamType.Video => "video.", - StreamType.Audio => "audio.", - StreamType.Attachment => "font.", - _ => "" - }; + string type = Type.ToString().ToLower(); string index = TrackIndex != 0 ? $"-{TrackIndex}" : string.Empty; - string codec = Codec switch - { - "subrip" => ".srt", - {} x => $".{x}" - }; - return $"{EpisodeSlug}.{type}{Language}{index}{(IsForced ? "-forced" : "")}{codec}"; + string episode = EpisodeSlug ?? Episode.Slug ?? EpisodeID.ToString(); + return $"{episode}.{Language}{index}{(IsForced ? ".forced" : "")}.{type}"; } [UsedImplicitly] private set { - Match match = Regex.Match(value, @"(?.*)-s(?\d+)e(?\d+)" - + @"(\.(?\w*))?\.(?.{0,3})(?-forced)?(\..\w)?"); + if (value == null) + throw new ArgumentNullException(nameof(value)); + Match match = Regex.Match(value, + @"(?[^\.]+)\.(?\w{0,3})(-(?\d+))?(\.(?forced))?\.(?\w+)(\.\w*)?"); if (!match.Success) - { - match = Regex.Match(value, @"(?.*)\.(?.{0,3})(?-forced)?(\..\w)?"); - if (!match.Success) - throw new ArgumentException("Invalid track slug. " + - "Format: {episodeSlug}.{language}[-forced][.{extension}]"); - } + throw new ArgumentException("Invalid track slug. " + + "Format: {episodeSlug}.{language}[-{index}][-forced].{type}[.{extension}]"); - EpisodeSlug = Episode.GetSlug(match.Groups["show"].Value, - match.Groups["season"].Success ? int.Parse(match.Groups["season"].Value) : null, - match.Groups["episode"].Success ? int.Parse(match.Groups["episode"].Value) : null); - Language = match.Groups["language"].Value; + EpisodeSlug = match.Groups["ep"].Value; + Language = match.Groups["lang"].Value; + TrackIndex = int.Parse(match.Groups["index"].Value); IsForced = match.Groups["forced"].Success; - if (match.Groups["type"].Success) - Type = Enum.Parse(match.Groups["type"].Value, true); + Type = Enum.Parse(match.Groups["type"].Value, true); } } @@ -167,5 +152,32 @@ namespace Kyoo.Models _ => mkvLanguage }; } + + /// + /// Utility method to edit a track slug (this only return a slug with the modification, nothing is stored) + /// + /// The slug to edit + /// The new type of this + /// + /// + /// + /// + public static string EditSlug(string baseSlug, + StreamType type = StreamType.Unknown, + string language = null, + int? index = null, + bool? forced = null) + { + Track track = new() {Slug = baseSlug}; + if (type != StreamType.Unknown) + track.Type = type; + if (language != null) + track.Language = language; + if (index != null) + track.TrackIndex = index.Value; + if (forced != null) + track.IsForced = forced.Value; + return track.Slug; + } } } diff --git a/Kyoo.CommonAPI/DatabaseContext.cs b/Kyoo.CommonAPI/DatabaseContext.cs index 5bf9e87a..2213d807 100644 --- a/Kyoo.CommonAPI/DatabaseContext.cs +++ b/Kyoo.CommonAPI/DatabaseContext.cs @@ -330,8 +330,6 @@ namespace Kyoo modelBuilder.Entity() .Property(x => x.Slug) .ValueGeneratedOnAddOrUpdate(); - - // modelBuilder.Ignore(); } /// @@ -502,52 +500,6 @@ namespace Kyoo } } - /// - /// Save items or retry with a custom method if a duplicate is found. - /// - /// The item to save (other changes of this context will also be saved) - /// A function to run on fail, the param wil be mapped. - /// The second parameter is the current retry number. - /// A to observe while waiting for the task to complete - /// The type of the item to save - /// The number of state entries written to the database. - public Task SaveOrRetry(T obj, Func onFail, CancellationToken cancellationToken = new()) - { - return SaveOrRetry(obj, onFail, 0, cancellationToken); - } - - /// - /// Save items or retry with a custom method if a duplicate is found. - /// - /// The item to save (other changes of this context will also be saved) - /// A function to run on fail, the param wil be mapped. - /// The second parameter is the current retry number. - /// The current retry number. - /// A to observe while waiting for the task to complete - /// The type of the item to save - /// The number of state entries written to the database. - private async Task SaveOrRetry(T obj, - Func onFail, - int recurse, - CancellationToken cancellationToken = new()) - { - try - { - await base.SaveChangesAsync(true, cancellationToken); - return obj; - } - catch (DbUpdateException ex) when (IsDuplicateException(ex)) - { - recurse++; - return await SaveOrRetry(onFail(obj, recurse), onFail, recurse, cancellationToken); - } - catch (DbUpdateException) - { - DiscardChanges(); - throw; - } - } - /// /// Check if the exception is a duplicated exception. /// diff --git a/Kyoo.Postgresql/Migrations/20210627141941_Triggers.cs b/Kyoo.Postgresql/Migrations/20210627141941_Triggers.cs index 51763bb8..16569748 100644 --- a/Kyoo.Postgresql/Migrations/20210627141941_Triggers.cs +++ b/Kyoo.Postgresql/Migrations/20210627141941_Triggers.cs @@ -13,8 +13,8 @@ namespace Kyoo.Postgresql.Migrations LANGUAGE PLPGSQL AS $$ BEGIN - NEW.slug := CONCAT( - (SELECT slug FROM shows WHERE id = NEW.show_id), + NEW.slug := CONCAT( + (SELECT slug FROM shows WHERE id = NEW.show_id), '-s', NEW.season_number ); @@ -38,10 +38,10 @@ namespace Kyoo.Postgresql.Migrations NEW.slug := CONCAT( (SELECT slug FROM shows WHERE id = NEW.show_id), CASE - WHEN NEW.season_number IS NULL AND NEW.episode_number IS NULL THEN NULL - WHEN NEW.season_number IS NULL THEN CONCAT('-', NEW.absolute_number) - ELSE CONCAT('-s', NEW.season_number, 'e', NEW.episode_number) - END + WHEN NEW.season_number IS NULL AND NEW.episode_number IS NULL THEN NULL + WHEN NEW.season_number IS NULL THEN CONCAT('-', NEW.absolute_number) + ELSE CONCAT('-s', NEW.season_number, 'e', NEW.episode_number) + END ); RETURN NEW; END @@ -63,21 +63,81 @@ namespace Kyoo.Postgresql.Migrations BEGIN UPDATE seasons SET slug = CONCAT(NEW.slug, '-s', season_number) WHERE show_id = NEW.id; UPDATE episodes SET slug = CASE - WHEN season_number IS NULL AND episode_number IS NULL THEN NEW.slug - WHEN season_number IS NULL THEN CONCAT(NEW.slug, '-', absolute_number) - ELSE CONCAT(NEW.slug, '-s', season_number, 'e', episode_number) - END - WHERE show_id = NEW.id; + WHEN season_number IS NULL AND episode_number IS NULL THEN NEW.slug + WHEN season_number IS NULL THEN CONCAT(NEW.slug, '-', absolute_number) + ELSE CONCAT(NEW.slug, '-s', season_number, 'e', episode_number) + END WHERE show_id = NEW.id; RETURN NEW; END $$;"); - // language=PostgreSQL migrationBuilder.Sql(@" CREATE TRIGGER show_slug_trigger AFTER UPDATE OF slug ON shows FOR EACH ROW EXECUTE PROCEDURE show_slug_update();"); + // language=PostgreSQL + migrationBuilder.Sql(@" + CREATE FUNCTION episode_update_tracks_slug() + RETURNS TRIGGER + LANGUAGE PLPGSQL + AS $$ + BEGIN + UPDATE tracks SET slug = CONCAT( + NEW.slug, + '.', language, + CASE (track_index) + WHEN 0 THEN '' + ELSE CONCAT('-', track_index) + END, + CASE (is_forced) + WHEN false THEN '' + ELSE '-forced' + END, + '.', type + ) WHERE episode_id = NEW.id; + RETURN NEW; + END; + $$;"); + // language=PostgreSQL + migrationBuilder.Sql(@" + CREATE TRIGGER episode_track_slug_trigger AFTER UPDATE OF slug ON episodes + FOR EACH ROW EXECUTE PROCEDURE episode_update_tracks_slug();"); + // language=PostgreSQL + migrationBuilder.Sql(@" + CREATE FUNCTION track_slug_update() + RETURNS TRIGGER + LANGUAGE PLPGSQL + AS $$ + BEGIN + IF NEW.track_index = 0 THEN + NEW.track_index := (SELECT COUNT(*) FROM tracks + WHERE episode_id = NEW.episode_id AND type = NEW.type + AND language = NEW.language AND is_forced = NEW.is_forced); + END IF; + NEW.slug := CONCAT( + (SELECT slug FROM episodes WHERE id = NEW.episode_id), + '.', NEW.language, + CASE (NEW.track_index) + WHEN 0 THEN '' + ELSE CONCAT('-', NEW.track_index) + END, + CASE (NEW.is_forced) + WHEN false THEN '' + ELSE '-forced' + END, + '.', NEW.type + ); + RETURN NEW; + END + $$;"); + // language=PostgreSQL + migrationBuilder.Sql(@" + CREATE TRIGGER track_slug_trigger + BEFORE INSERT OR UPDATE OF episode_id, is_forced, language, track_index, type ON tracks + FOR EACH ROW EXECUTE PROCEDURE track_slug_update();"); + + // language=PostgreSQL migrationBuilder.Sql(@" CREATE VIEW library_items AS @@ -112,6 +172,14 @@ namespace Kyoo.Postgresql.Migrations // language=PostgreSQL migrationBuilder.Sql(@"DROP FUNCTION episode_slug_update;"); // language=PostgreSQL + migrationBuilder.Sql("DROP TRIGGER track_slug_trigger ON tracks;"); + // language=PostgreSQL + migrationBuilder.Sql(@"DROP FUNCTION track_slug_update;"); + // language=PostgreSQL + migrationBuilder.Sql("DROP TRIGGER episode_track_slug_trigger ON episodes;"); + // language=PostgreSQL + migrationBuilder.Sql(@"DROP FUNCTION episode_update_tracks_slug;"); + // language=PostgreSQL migrationBuilder.Sql(@"DROP VIEW library_items;"); } } diff --git a/Kyoo.SqLite/Migrations/20210626141347_Triggers.cs b/Kyoo.SqLite/Migrations/20210626141347_Triggers.cs index 6a70c8b1..f3ae8325 100644 --- a/Kyoo.SqLite/Migrations/20210626141347_Triggers.cs +++ b/Kyoo.SqLite/Migrations/20210626141347_Triggers.cs @@ -25,13 +25,13 @@ namespace Kyoo.SqLite.Migrations migrationBuilder.Sql(@" CREATE TRIGGER EpisodeSlugInsert AFTER INSERT ON Episodes FOR EACH ROW BEGIN - UPDATE Episodes - SET Slug = (SELECT Slug from Shows WHERE ID = ShowID) || - CASE - WHEN SeasonNumber IS NULL AND AbsoluteNumber IS NULL THEN '' - WHEN SeasonNumber IS NULL THEN '-' || AbsoluteNumber - ELSE '-s' || SeasonNumber || 'e' || EpisodeNumber - END + UPDATE Episodes + SET Slug = (SELECT Slug from Shows WHERE ID = ShowID) || + CASE + WHEN SeasonNumber IS NULL AND AbsoluteNumber IS NULL THEN '' + WHEN SeasonNumber IS NULL THEN '-' || AbsoluteNumber + ELSE '-s' || SeasonNumber || 'e' || EpisodeNumber + END WHERE ID == new.ID; END"); // language=SQLite @@ -39,29 +39,114 @@ namespace Kyoo.SqLite.Migrations CREATE TRIGGER EpisodeSlugUpdate AFTER UPDATE OF AbsoluteNumber, EpisodeNumber, SeasonNumber, ShowID ON Episodes FOR EACH ROW BEGIN - UPDATE Episodes - SET Slug = (SELECT Slug from Shows WHERE ID = ShowID) || - CASE - WHEN SeasonNumber IS NULL AND AbsoluteNumber IS NULL THEN '' - WHEN SeasonNumber IS NULL THEN '-' || AbsoluteNumber - ELSE '-s' || SeasonNumber || 'e' || EpisodeNumber - END + UPDATE Episodes + SET Slug = (SELECT Slug from Shows WHERE ID = ShowID) || + CASE + WHEN SeasonNumber IS NULL AND AbsoluteNumber IS NULL THEN '' + WHEN SeasonNumber IS NULL THEN '-' || AbsoluteNumber + ELSE '-s' || SeasonNumber || 'e' || EpisodeNumber + END WHERE ID == new.ID; END"); - - + + // language=SQLite + migrationBuilder.Sql(@" + CREATE TRIGGER TrackSlugInsert + AFTER INSERT ON Tracks + FOR EACH ROW + BEGIN + UPDATE Tracks SET TrackIndex = ( + SELECT COUNT(*) FROM Tracks + WHERE EpisodeID = new.EpisodeID AND Type = new.Type + AND Language = new.Language AND IsForced = new.IsForced + ) WHERE ID = new.ID AND TrackIndex = 0; + UPDATE Tracks SET Slug = (SELECT Slug FROM Episodes WHERE ID = EpisodeID) || + '.' || Language || + CASE (TrackIndex) + WHEN 0 THEN '' + ELSE '-' || (TrackIndex) + END || + CASE (IsForced) + WHEN false THEN '' + ELSE '-forced' + END || + CASE (Type) + WHEN 1 THEN '.video' + WHEN 2 THEN '.audio' + WHEN 3 THEN '.subtitle' + ELSE '.' || Type + END + WHERE ID = new.ID; + END;"); + // language=SQLite + migrationBuilder.Sql(@" + CREATE TRIGGER TrackSlugUpdate + AFTER UPDATE OF EpisodeID, IsForced, Language, TrackIndex, Type ON Tracks + FOR EACH ROW + BEGIN + UPDATE Tracks SET TrackIndex = ( + SELECT COUNT(*) FROM Tracks + WHERE EpisodeID = new.EpisodeID AND Type = new.Type + AND Language = new.Language AND IsForced = new.IsForced + ) WHERE ID = new.ID AND TrackIndex = 0; + UPDATE Tracks SET Slug = + (SELECT Slug FROM Episodes WHERE ID = EpisodeID) || + '.' || Language || + CASE (TrackIndex) + WHEN 0 THEN '' + ELSE '-' || (TrackIndex) + END || + CASE (IsForced) + WHEN false THEN '' + ELSE '-forced' + END || + CASE (Type) + WHEN 1 THEN '.video' + WHEN 2 THEN '.audio' + WHEN 3 THEN '.subtitle' + ELSE '.' || Type + END + WHERE ID = new.ID; + END;"); + // language=SQLite + migrationBuilder.Sql(@" + CREATE TRIGGER EpisodeUpdateTracksSlug + AFTER UPDATE OF Slug ON Episodes + FOR EACH ROW + BEGIN + UPDATE Tracks SET Slug = + NEW.Slug || + '.' || Language || + CASE (TrackIndex) + WHEN 0 THEN '' + ELSE '-' || TrackIndex + END || + CASE (IsForced) + WHEN false THEN '' + ELSE '-forced' + END || + CASE (Type) + WHEN 1 THEN '.video' + WHEN 2 THEN '.audio' + WHEN 3 THEN '.subtitle' + ELSE '.' || Type + END + WHERE EpisodeID = NEW.ID; + END;"); + + // language=SQLite migrationBuilder.Sql(@" CREATE TRIGGER ShowSlugUpdate AFTER UPDATE OF Slug ON Shows FOR EACH ROW BEGIN - UPDATE Seasons SET Slug = new.Slug || '-s' || SeasonNumber WHERE ShowID = new.ID; - UPDATE Episodes - SET Slug = new.Slug || - CASE - WHEN SeasonNumber IS NULL AND AbsoluteNumber IS NULL THEN '' - WHEN SeasonNumber IS NULL THEN '-' || AbsoluteNumber - ELSE '-s' || SeasonNumber || 'e' || EpisodeNumber - END + UPDATE Seasons SET Slug = new.Slug || '-s' || SeasonNumber WHERE ShowID = new.ID; + UPDATE Episodes + SET Slug = new.Slug || + CASE + WHEN SeasonNumber IS NULL AND AbsoluteNumber IS NULL THEN '' + WHEN SeasonNumber IS NULL THEN '-' || AbsoluteNumber + ELSE '-s' || SeasonNumber || 'e' || EpisodeNumber + END WHERE ShowID = new.ID; END;"); diff --git a/Kyoo.Tests/KAssert.cs b/Kyoo.Tests/KAssert.cs index fa38160d..80209c98 100644 --- a/Kyoo.Tests/KAssert.cs +++ b/Kyoo.Tests/KAssert.cs @@ -1,4 +1,3 @@ -using System.Linq; using System.Reflection; using JetBrains.Annotations; using Xunit; diff --git a/Kyoo.Tests/Library/SpecificTests/TrackTests.cs b/Kyoo.Tests/Library/SpecificTests/TrackTests.cs index 6506e9d4..3c2e2043 100644 --- a/Kyoo.Tests/Library/SpecificTests/TrackTests.cs +++ b/Kyoo.Tests/Library/SpecificTests/TrackTests.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models; using Xunit; @@ -34,5 +35,17 @@ namespace Kyoo.Tests.Library { _repository = repositories.LibraryManager.TrackRepository; } + + [Fact] + public async Task SlugEditTest() + { + await Repositories.LibraryManager.ShowRepository.Edit(new Show + { + ID = 1, + Slug = "new-slug" + }, false); + Track track = await _repository.Get(1); + Assert.Equal("new-slug-s1e1.eng-1.subtitle", track.Slug); + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 39356960..2cf7ff1f 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -127,7 +127,7 @@ namespace Kyoo.Controllers if (changed.Tracks != null || resetOld) { - await Database.Entry(resource).Collection(x => x.Tracks).LoadAsync(); + await _tracks.DeleteAll(x => x.EpisodeID == resource.ID); resource.Tracks = changed.Tracks; await ValidateTracks(resource); } @@ -148,14 +148,10 @@ namespace Kyoo.Controllers /// The parameter is returned. private async Task ValidateTracks(Episode resource) { - resource.Tracks = await TaskUtils.DefaultIfNull(resource.Tracks?.MapAsync((x, i) => + resource.Tracks = await TaskUtils.DefaultIfNull(resource.Tracks?.SelectAsync(x => { x.Episode = resource; - // TODO use a trigger for the next line. - x.TrackIndex = resource.Tracks.Take(i).Count(y => x.Language == y.Language - && x.IsForced == y.IsForced - && x.Codec == y.Codec - && x.Type == y.Type); + x.EpisodeSlug = resource.Slug; return _tracks.Create(x); }).ToListAsync()); return resource; diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index 98b05f30..9b642a93 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -1,11 +1,8 @@ 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; using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers @@ -33,56 +30,6 @@ namespace Kyoo.Controllers { _database = database; } - - - /// - Task IRepository.Get(string slug) - { - return Get(slug); - } - - /// - public async Task Get(string slug, StreamType type = StreamType.Unknown) - { - Track ret = await GetOrDefault(slug, type); - if (ret == null) - throw new ItemNotFoundException($"No track found with the slug {slug} and the type {type}."); - return ret; - } - - /// - public Task GetOrDefault(string slug, StreamType type = StreamType.Unknown) - { - Match match = Regex.Match(slug, - @"(?.*)-s(?\d+)e(?\d+)(\.(?\w*))?\.(?.{0,3})(?-forced)?(\..*)?"); - - if (!match.Success) - { - if (int.TryParse(slug, out int id)) - return GetOrDefault(id); - match = Regex.Match(slug, @"(?.*)\.(?.{0,3})(?-forced)?(\..*)?"); - if (!match.Success) - throw new ArgumentException("Invalid track slug. " + - "Format: {episodeSlug}.{language}[-forced][.{extension}]"); - } - - string showSlug = match.Groups["show"].Value; - int? seasonNumber = match.Groups["season"].Success ? int.Parse(match.Groups["season"].Value) : null; - int? episodeNumber = match.Groups["episode"].Success ? int.Parse(match.Groups["episode"].Value) : null; - string language = match.Groups["language"].Value; - bool forced = match.Groups["forced"].Success; - if (match.Groups["type"].Success) - type = Enum.Parse(match.Groups["type"].Value, true); - - IQueryable query = _database.Tracks.Where(x => x.Episode.Show.Slug == showSlug - && x.Episode.SeasonNumber == seasonNumber - && x.Episode.EpisodeNumber == episodeNumber - && x.Language == language - && x.IsForced == forced); - if (type != StreamType.Unknown) - return query.FirstOrDefaultAsync(x => x.Type == type); - return query.FirstOrDefaultAsync(); - } /// public override Task> Search(string query) @@ -93,23 +40,19 @@ namespace Kyoo.Controllers /// public override async Task Create(Track obj) { + if (obj == null) + throw new ArgumentNullException(nameof(obj)); + if (obj.EpisodeID <= 0) { 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})."); } - + await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local - await _database.SaveOrRetry(obj, (x, i) => - { - if (i > 10) - throw new DuplicatedItemException($"More than 10 same tracks exists {x.Slug}. Aborting..."); - x.TrackIndex++; - return x; - }); + await _database.SaveChangesAsync(); return obj; } diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 983d1d6c..4d5995ef 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -5,7 +5,6 @@ using Kyoo.Controllers; using Kyoo.Models; using Kyoo.Models.Options; using Kyoo.Postgresql; -using Kyoo.SqLite; using Kyoo.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; diff --git a/Kyoo/Views/SubtitleApi.cs b/Kyoo/Views/SubtitleApi.cs index 5e680a73..7f05eedf 100644 --- a/Kyoo/Views/SubtitleApi.cs +++ b/Kyoo/Views/SubtitleApi.cs @@ -1,5 +1,4 @@ -using System; -using Kyoo.Models; +using Kyoo.Models; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.IO; @@ -27,19 +26,9 @@ namespace Kyoo.Api [Permission(nameof(SubtitleApi), Kind.Read)] public async Task GetSubtitle(string slug, string extension) { - Track subtitle; - try - { - subtitle = await _libraryManager.GetOrDefault(slug, StreamType.Subtitle); - } - catch (ArgumentException ex) - { - return BadRequest(new {error = ex.Message}); - } - - if (subtitle is not {Type: StreamType.Subtitle}) + Track subtitle = await _libraryManager.GetOrDefault(Track.EditSlug(slug, StreamType.Subtitle)); + if (subtitle == null) return NotFound(); - if (subtitle.Codec == "subrip" && extension == "vtt") return new ConvertSubripToVtt(subtitle.Path, _files); return _files.FileResult(subtitle.Path);