mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Fixing track register and directory exist
This commit is contained in:
parent
f1887d1fab
commit
5a480402e1
@ -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);
|
||||
}
|
||||
}
|
@ -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).
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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'
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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 />
|
||||
|
@ -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.");
|
||||
|
@ -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 =>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user