From 5a480402e197f888e200460900895ab3285a9fc9 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 18 Jul 2021 18:43:14 +0200 Subject: [PATCH] Fixing track register and directory exist --- Kyoo.Common/Controllers/IIdentifier.cs | 18 +++++++++++---- Kyoo.Common/Models/LibraryItem.cs | 2 +- Kyoo.Common/Models/Resources/Track.cs | 10 +++++---- Kyoo.CommonAPI/JsonSerializer.cs | 22 +++++++++++++++---- .../Migrations/20210627141941_Triggers.cs | 6 ++--- Kyoo.Postgresql/PostgresModule.cs | 5 +++++ .../Migrations/20210626141347_Triggers.cs | 8 +++---- .../Library/SpecificTests/TrackTests.cs | 15 +++++++++++++ Kyoo/Controllers/FileManager.cs | 2 +- Kyoo/Controllers/RegexIdentifier.cs | 8 +++---- .../Repositories/SeasonRepository.cs | 7 +++++- Kyoo/Tasks/Crawler.cs | 6 +++-- Kyoo/Tasks/Housekeeping.cs | 9 ++++++++ Kyoo/Tasks/RegisterEpisode.cs | 7 +++++- Kyoo/Tasks/RegisterSubtitle.cs | 7 ++++-- 15 files changed, 101 insertions(+), 31 deletions(-) diff --git a/Kyoo.Common/Controllers/IIdentifier.cs b/Kyoo.Common/Controllers/IIdentifier.cs index b841a63b..5033bb77 100644 --- a/Kyoo.Common/Controllers/IIdentifier.cs +++ b/Kyoo.Common/Controllers/IIdentifier.cs @@ -12,22 +12,32 @@ namespace Kyoo.Controllers /// /// Identify a path and return the parsed metadata. /// - /// The path of the episode file to parse. + /// + /// The path of the episode file to parse. + /// + /// + /// The path of the episode file relative to the library root. It starts with a /. + /// /// The identifier could not work for the given path. /// /// A tuple of models representing parsed metadata. /// If no metadata could be parsed for a type, null can be returned. /// - Task<(Collection, Show, Season, Episode)> Identify(string path); + Task<(Collection, Show, Season, Episode)> Identify(string path, string relativePath); /// /// Identify an external subtitle or track file from it's path and return the parsed metadata. /// - /// The path of the external track file to parse. + /// + /// The path of the external track file to parse. + /// + /// + /// The path of the episode file relative to the library root. It starts with a /. + /// /// The identifier could not work for the given path. /// /// The metadata of the track identified. /// - Task IdentifyTrack(string path); + Task IdentifyTrack(string path, string relativePath); } } \ No newline at end of file diff --git a/Kyoo.Common/Models/LibraryItem.cs b/Kyoo.Common/Models/LibraryItem.cs index 4df07770..6bc61c2e 100644 --- a/Kyoo.Common/Models/LibraryItem.cs +++ b/Kyoo.Common/Models/LibraryItem.cs @@ -58,7 +58,7 @@ namespace Kyoo.Models /// By default, the http path for this poster is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/{Type}/{Slug}/poster")] public string Poster { get; set; } + [SerializeAs("{HOST}/api/{Type:l}/{Slug}/poster")] public string Poster { get; set; } /// /// The type of this item (ether a collection, a show or a movie). diff --git a/Kyoo.Common/Models/Resources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs index f093699b..df00e94e 100644 --- a/Kyoo.Common/Models/Resources/Track.cs +++ b/Kyoo.Common/Models/Resources/Track.cs @@ -35,8 +35,8 @@ namespace Kyoo.Models { string type = Type.ToString().ToLower(); string index = TrackIndex != 0 ? $"-{TrackIndex}" : string.Empty; - string episode = EpisodeSlug ?? Episode.Slug ?? EpisodeID.ToString(); - return $"{episode}.{Language}{index}{(IsForced ? ".forced" : "")}.{type}"; + string episode = EpisodeSlug ?? Episode?.Slug ?? EpisodeID.ToString(); + return $"{episode}.{Language ?? "und"}{index}{(IsForced ? ".forced" : "")}.{type}"; } [UsedImplicitly] private set { @@ -47,11 +47,13 @@ namespace Kyoo.Models if (!match.Success) throw new ArgumentException("Invalid track slug. " + - "Format: {episodeSlug}.{language}[-{index}][-forced].{type}[.{extension}]"); + "Format: {episodeSlug}.{language}[-{index}][.forced].{type}[.{extension}]"); EpisodeSlug = match.Groups["ep"].Value; Language = match.Groups["lang"].Value; - TrackIndex = int.Parse(match.Groups["index"].Value); + if (Language == "und") + Language = null; + TrackIndex = match.Groups["index"].Success ? int.Parse(match.Groups["index"].Value) : 0; IsForced = match.Groups["forced"].Success; Type = Enum.Parse(match.Groups["type"].Value, true); } diff --git a/Kyoo.CommonAPI/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs index bf65a32a..3a2caac7 100644 --- a/Kyoo.CommonAPI/JsonSerializer.cs +++ b/Kyoo.CommonAPI/JsonSerializer.cs @@ -115,9 +115,10 @@ namespace Kyoo.Controllers public object GetValue(object target) { - return Regex.Replace(_format, @"(? + return Regex.Replace(_format, @"(? { string value = x.Groups[1].Value; + string modifier = x.Groups[3].Value; if (value == "HOST") return _host; @@ -127,9 +128,22 @@ namespace Kyoo.Controllers .FirstOrDefault(y => y.Name == value); if (properties == null) return null; - if (properties.GetValue(target) is string ret) - return ret; - throw new ArgumentException($"Invalid serializer replacement {value}"); + object objValue = properties.GetValue(target); + if (objValue is not string ret) + ret = objValue?.ToString(); + if (ret == null) + throw new ArgumentException($"Invalid serializer replacement {value}"); + + foreach (char modification in modifier) + { + ret = modification switch + { + 'l' => ret.ToLowerInvariant(), + 'u' => ret.ToUpperInvariant(), + _ => throw new ArgumentException($"Invalid serializer modificator {modification}.") + }; + } + return ret; }); } diff --git a/Kyoo.Postgresql/Migrations/20210627141941_Triggers.cs b/Kyoo.Postgresql/Migrations/20210627141941_Triggers.cs index 16569748..a773e02b 100644 --- a/Kyoo.Postgresql/Migrations/20210627141941_Triggers.cs +++ b/Kyoo.Postgresql/Migrations/20210627141941_Triggers.cs @@ -91,7 +91,7 @@ namespace Kyoo.Postgresql.Migrations END, CASE (is_forced) WHEN false THEN '' - ELSE '-forced' + ELSE '.forced' END, '.', type ) WHERE episode_id = NEW.id; @@ -117,14 +117,14 @@ namespace Kyoo.Postgresql.Migrations END IF; NEW.slug := CONCAT( (SELECT slug FROM episodes WHERE id = NEW.episode_id), - '.', NEW.language, + '.', COALESCE(NEW.language, 'und'), CASE (NEW.track_index) WHEN 0 THEN '' ELSE CONCAT('-', NEW.track_index) END, CASE (NEW.is_forced) WHEN false THEN '' - ELSE '-forced' + ELSE '.forced' END, '.', NEW.type ); diff --git a/Kyoo.Postgresql/PostgresModule.cs b/Kyoo.Postgresql/PostgresModule.cs index f0c8f23c..124df770 100644 --- a/Kyoo.Postgresql/PostgresModule.cs +++ b/Kyoo.Postgresql/PostgresModule.cs @@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Npgsql; namespace Kyoo.Postgresql { @@ -73,6 +74,10 @@ namespace Kyoo.Postgresql { DatabaseContext context = provider.GetRequiredService(); context.Database.Migrate(); + + using NpgsqlConnection conn = (NpgsqlConnection)context.Database.GetDbConnection(); + conn.Open(); + conn.ReloadTypes(); } } } \ No newline at end of file diff --git a/Kyoo.SqLite/Migrations/20210626141347_Triggers.cs b/Kyoo.SqLite/Migrations/20210626141347_Triggers.cs index f3ae8325..370fdd37 100644 --- a/Kyoo.SqLite/Migrations/20210626141347_Triggers.cs +++ b/Kyoo.SqLite/Migrations/20210626141347_Triggers.cs @@ -61,14 +61,14 @@ namespace Kyoo.SqLite.Migrations 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 || + '.' || COALESCE(Language, 'und') || CASE (TrackIndex) WHEN 0 THEN '' ELSE '-' || (TrackIndex) END || CASE (IsForced) WHEN false THEN '' - ELSE '-forced' + ELSE '.forced' END || CASE (Type) WHEN 1 THEN '.video' @@ -98,7 +98,7 @@ namespace Kyoo.SqLite.Migrations END || CASE (IsForced) WHEN false THEN '' - ELSE '-forced' + ELSE '.forced' END || CASE (Type) WHEN 1 THEN '.video' @@ -123,7 +123,7 @@ namespace Kyoo.SqLite.Migrations END || CASE (IsForced) WHEN false THEN '' - ELSE '-forced' + ELSE '.forced' END || CASE (Type) WHEN 1 THEN '.video' diff --git a/Kyoo.Tests/Library/SpecificTests/TrackTests.cs b/Kyoo.Tests/Library/SpecificTests/TrackTests.cs index 3c2e2043..3aebaef9 100644 --- a/Kyoo.Tests/Library/SpecificTests/TrackTests.cs +++ b/Kyoo.Tests/Library/SpecificTests/TrackTests.cs @@ -47,5 +47,20 @@ namespace Kyoo.Tests.Library Track track = await _repository.Get(1); Assert.Equal("new-slug-s1e1.eng-1.subtitle", track.Slug); } + + [Fact] + public async Task UndefinedLanguageSlugTest() + { + await _repository.Create(new Track + { + ID = 5, + TrackIndex = 0, + Type = StreamType.Video, + Language = null, + EpisodeID = TestSample.Get().ID + }); + Track track = await _repository.Get(5); + Assert.Equal("anohana-s1e1.und.video", track.Slug); + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/FileManager.cs b/Kyoo/Controllers/FileManager.cs index bd7ee4c2..4aa89467 100644 --- a/Kyoo/Controllers/FileManager.cs +++ b/Kyoo/Controllers/FileManager.cs @@ -98,7 +98,7 @@ namespace Kyoo.Controllers /// public Task Exists(string path) { - return Task.FromResult(File.Exists(path)); + return Task.FromResult(File.Exists(path) || Directory.Exists(path)); } /// diff --git a/Kyoo/Controllers/RegexIdentifier.cs b/Kyoo/Controllers/RegexIdentifier.cs index 002ca5d7..3e5d82fe 100644 --- a/Kyoo/Controllers/RegexIdentifier.cs +++ b/Kyoo/Controllers/RegexIdentifier.cs @@ -30,10 +30,10 @@ namespace Kyoo.Controllers } /// - public Task<(Collection, Show, Season, Episode)> Identify(string path) + public Task<(Collection, Show, Season, Episode)> Identify(string path, string relativePath) { Regex regex = new(_configuration.Value.Regex, RegexOptions.IgnoreCase | RegexOptions.Compiled); - Match match = regex.Match(path); + Match match = regex.Match(relativePath); if (!match.Success) throw new IdentificationFailed($"The episode at {path} does not match the episode's regex."); @@ -84,10 +84,10 @@ namespace Kyoo.Controllers } /// - public Task IdentifyTrack(string path) + public Task IdentifyTrack(string path, string relativePath) { Regex regex = new(_configuration.Value.SubtitleRegex, RegexOptions.IgnoreCase | RegexOptions.Compiled); - Match match = regex.Match(path); + Match match = regex.Match(relativePath); if (!match.Success) throw new IdentificationFailed($"The subtitle at {path} does not match the subtitle's regex."); diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index fe042e66..e0036982 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -96,7 +96,12 @@ namespace Kyoo.Controllers protected override async Task Validate(Season resource) { if (resource.ShowID <= 0) - throw new InvalidOperationException($"Can't store a season not related to any show (showID: {resource.ShowID})."); + { + if (resource.Show == null) + throw new InvalidOperationException( + $"Can't store a season not related to any show (showID: {resource.ShowID})."); + resource.ShowID = resource.Show.ID; + } await base.Validate(resource); await resource.ExternalIDs.ForEachAsync(async id => diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index 6f4cf9eb..a2e28493 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -140,7 +140,8 @@ namespace Kyoo.Tasks { TaskManager.StartTask(reporter, new Dictionary { - ["path"] = episodePath[path.Length..], + ["path"] = episodePath, + ["relativePath"] = episodePath[path.Length..], ["library"] = library }, cancellationToken); percent += 100f / paths.Length; @@ -162,7 +163,8 @@ namespace Kyoo.Tasks { TaskManager.StartTask(reporter, new Dictionary { - ["path"] = trackPath + ["path"] = trackPath, + ["relativePath"] = trackPath[path.Length..] }, cancellationToken); percent += 100f / subtitles.Length; } diff --git a/Kyoo/Tasks/Housekeeping.cs b/Kyoo/Tasks/Housekeeping.cs index 661f0be0..03084aaa 100644 --- a/Kyoo/Tasks/Housekeeping.cs +++ b/Kyoo/Tasks/Housekeeping.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models; using Kyoo.Models.Attributes; +using Microsoft.Extensions.Logging; namespace Kyoo.Tasks { @@ -39,6 +40,10 @@ namespace Kyoo.Tasks /// The file manager used walk inside directories and check they existences. /// [Injected] public IFileManager FileManager { private get; set; } + /// + /// The logger used to inform the user that episodes has been removed. + /// + [Injected] public ILogger Logger { private get; set; } /// @@ -55,6 +60,8 @@ namespace Kyoo.Tasks if (await FileManager.Exists(show.Path)) continue; + Logger.LogWarning("Show {Name}'s folder has been deleted (was {Path}), removing it from kyoo", + show.Title, show.Path); await LibraryManager.Delete(show); } @@ -65,6 +72,8 @@ namespace Kyoo.Tasks if (await FileManager.Exists(episode.Path)) continue; + Logger.LogWarning("Episode {Slug}'s file has been deleted (was {Path}), removing it from kyoo", + episode.Slug, episode.Path); await LibraryManager.Delete(episode); } diff --git a/Kyoo/Tasks/RegisterEpisode.cs b/Kyoo/Tasks/RegisterEpisode.cs index 6ef512e6..3c01c03d 100644 --- a/Kyoo/Tasks/RegisterEpisode.cs +++ b/Kyoo/Tasks/RegisterEpisode.cs @@ -62,6 +62,8 @@ namespace Kyoo.Tasks return new() { TaskParameter.CreateRequired("path", "The path of the episode file"), + TaskParameter.CreateRequired("relativePath", + "The path of the episode file relative to the library root. It starts with a /."), TaskParameter.CreateRequired("library", "The library in witch the episode is") }; } @@ -70,13 +72,15 @@ namespace Kyoo.Tasks public async Task Run(TaskParameters arguments, IProgress progress, CancellationToken cancellationToken) { string path = arguments["path"].As(); + string relativePath = arguments["relativePath"].As(); Library library = arguments["library"].As(); progress.Report(0); if (library.Providers == null) await LibraryManager.Load(library, x => x.Providers); MetadataProvider.UseProviders(library.Providers); - (Collection collection, Show show, Season season, Episode episode) = await Identifier.Identify(path); + (Collection collection, Show show, Season season, Episode episode) = await Identifier.Identify(path, + relativePath); progress.Report(15); collection = await _RegisterAndFill(collection); @@ -105,6 +109,7 @@ namespace Kyoo.Tasks if (season != null) season.Show = show; + season = await _RegisterAndFill(season); progress.Report(60); diff --git a/Kyoo/Tasks/RegisterSubtitle.cs b/Kyoo/Tasks/RegisterSubtitle.cs index 524977ed..66dd5a48 100644 --- a/Kyoo/Tasks/RegisterSubtitle.cs +++ b/Kyoo/Tasks/RegisterSubtitle.cs @@ -48,7 +48,9 @@ namespace Kyoo.Tasks { return new() { - TaskParameter.CreateRequired("path", "The path of the episode file"), + TaskParameter.CreateRequired("path", "The path of the subtitle file"), + TaskParameter.CreateRequired("relativePath", + "The path of the subtitle file relative to the library root. It starts with a /.") }; } @@ -56,9 +58,10 @@ namespace Kyoo.Tasks public async Task Run(TaskParameters arguments, IProgress progress, CancellationToken cancellationToken) { string path = arguments["path"].As(); + string relativePath = arguments["relativePath"].As(); progress.Report(0); - Track track = await Identifier.IdentifyTrack(path); + Track track = await Identifier.IdentifyTrack(path, relativePath); progress.Report(25); if (track.Episode == null)