Fixing track register and directory exist

This commit is contained in:
Zoe Roux 2021-07-18 18:43:14 +02:00
parent f1887d1fab
commit 5a480402e1
15 changed files with 101 additions and 31 deletions

View File

@ -12,22 +12,32 @@ namespace Kyoo.Controllers
/// <summary>
/// Identify a path and return the parsed metadata.
/// </summary>
/// <param name="path">The path of the episode file to parse.</param>
/// <param name="path">
/// The path of the episode file to parse.
/// </param>
/// <param name="relativePath">
/// The path of the episode file relative to the library root. It starts with a <c>/</c>.
/// </param>
/// <exception cref="IdentificationFailed">The identifier could not work for the given path.</exception>
/// <returns>
/// A tuple of models representing parsed metadata.
/// If no metadata could be parsed for a type, null can be returned.
/// </returns>
Task<(Collection, Show, Season, Episode)> Identify(string path);
Task<(Collection, Show, Season, Episode)> Identify(string path, string relativePath);
/// <summary>
/// Identify an external subtitle or track file from it's path and return the parsed metadata.
/// </summary>
/// <param name="path">The path of the external track file to parse.</param>
/// <param name="path">
/// The path of the external track file to parse.
/// </param>
/// <param name="relativePath">
/// The path of the episode file relative to the library root. It starts with a <c>/</c>.
/// </param>
/// <exception cref="IdentificationFailed">The identifier could not work for the given path.</exception>
/// <returns>
/// The metadata of the track identified.
/// </returns>
Task<Track> IdentifyTrack(string path);
Task<Track> IdentifyTrack(string path, string relativePath);
}
}

View File

@ -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.
/// </summary>
[SerializeAs("{HOST}/api/{Type}/{Slug}/poster")] public string Poster { get; set; }
[SerializeAs("{HOST}/api/{Type:l}/{Slug}/poster")] public string Poster { get; set; }
/// <summary>
/// The type of this item (ether a collection, a show or a movie).

View File

@ -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<StreamType>(match.Groups["type"].Value, true);
}

View File

@ -115,9 +115,10 @@ namespace Kyoo.Controllers
public object GetValue(object target)
{
return Regex.Replace(_format, @"(?<!{){(\w+)}", x =>
return Regex.Replace(_format, @"(?<!{){(\w+)(:(\w+))?}", x =>
{
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;
});
}

View File

@ -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
);

View File

@ -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<DatabaseContext>();
context.Database.Migrate();
using NpgsqlConnection conn = (NpgsqlConnection)context.Database.GetDbConnection();
conn.Open();
conn.ReloadTypes();
}
}
}

View File

@ -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'

View File

@ -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<Episode>().ID
});
Track track = await _repository.Get(5);
Assert.Equal("anohana-s1e1.und.video", track.Slug);
}
}
}

View File

@ -98,7 +98,7 @@ namespace Kyoo.Controllers
/// <inheritdoc />
public Task<bool> Exists(string path)
{
return Task.FromResult(File.Exists(path));
return Task.FromResult(File.Exists(path) || Directory.Exists(path));
}
/// <inheritdoc />

View File

@ -30,10 +30,10 @@ namespace Kyoo.Controllers
}
/// <inheritdoc />
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
}
/// <inheritdoc />
public Task<Track> IdentifyTrack(string path)
public Task<Track> 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.");

View File

@ -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 =>

View File

@ -140,7 +140,8 @@ namespace Kyoo.Tasks
{
TaskManager.StartTask<RegisterEpisode>(reporter, new Dictionary<string, object>
{
["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<RegisterSubtitle>(reporter, new Dictionary<string, object>
{
["path"] = trackPath
["path"] = trackPath,
["relativePath"] = trackPath[path.Length..]
}, cancellationToken);
percent += 100f / subtitles.Length;
}

View File

@ -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.
/// </summary>
[Injected] public IFileManager FileManager { private get; set; }
/// <summary>
/// The logger used to inform the user that episodes has been removed.
/// </summary>
[Injected] public ILogger<Housekeeping> Logger { private get; set; }
/// <inheritdoc />
@ -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);
}

View File

@ -62,6 +62,8 @@ namespace Kyoo.Tasks
return new()
{
TaskParameter.CreateRequired<string>("path", "The path of the episode file"),
TaskParameter.CreateRequired<string>("relativePath",
"The path of the episode file relative to the library root. It starts with a /."),
TaskParameter.CreateRequired<Library>("library", "The library in witch the episode is")
};
}
@ -70,13 +72,15 @@ namespace Kyoo.Tasks
public async Task Run(TaskParameters arguments, IProgress<float> progress, CancellationToken cancellationToken)
{
string path = arguments["path"].As<string>();
string relativePath = arguments["relativePath"].As<string>();
Library library = arguments["library"].As<Library>();
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);

View File

@ -48,7 +48,9 @@ namespace Kyoo.Tasks
{
return new()
{
TaskParameter.CreateRequired<string>("path", "The path of the episode file"),
TaskParameter.CreateRequired<string>("path", "The path of the subtitle file"),
TaskParameter.CreateRequired<string>("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<float> progress, CancellationToken cancellationToken)
{
string path = arguments["path"].As<string>();
string relativePath = arguments["relativePath"].As<string>();
progress.Report(0);
Track track = await Identifier.IdentifyTrack(path);
Track track = await Identifier.IdentifyTrack(path, relativePath);
progress.Report(25);
if (track.Episode == null)