Keeps Radarr/Sonarr quality upgrades out of "Recently Added" by keeping a ledger of the first date added per logical medium (movie or episode, identified by provider ids and the series) and restoring it on upgrades. - core logic without Jellyfin types (Reconciler, MediaKeys, GuardProcessor) - thread-safe ledger with atomic, debounced persistence - event processing via channel and background worker, seed on first start - scheduled task "Ledger synchronisieren", status API, configuration page - xUnit tests and a Jellyfin 12.2 integration test - Gitea workflows: build/test, release with jprm and manifest branch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
89 lines
3.6 KiB
C#
89 lines
3.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
|
|
namespace Jellyfin.Plugin.RecentlyAddedGuard.Core;
|
|
|
|
/// <summary>
|
|
/// Builds the alias keys that identify a logical medium independent of its file path.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Keys are compared case-insensitively by the ledger, so provider ids are only trimmed.
|
|
/// </remarks>
|
|
public static class MediaKeys
|
|
{
|
|
/// <summary>
|
|
/// Builds the keys of a movie from its provider ids.
|
|
/// </summary>
|
|
/// <param name="tmdbId">The TMDb id, if known.</param>
|
|
/// <param name="imdbId">The IMDb id, if known.</param>
|
|
/// <param name="tvdbId">The TVDb id, if known.</param>
|
|
/// <returns>The keys; empty if the movie has no provider id yet.</returns>
|
|
public static IReadOnlyList<string> ForMovie(string? tmdbId, string? imdbId, string? tvdbId)
|
|
{
|
|
var keys = new List<string>(3);
|
|
AddIfPresent(keys, "movie:tmdb:", tmdbId, string.Empty);
|
|
AddIfPresent(keys, "movie:imdb:", imdbId, string.Empty);
|
|
AddIfPresent(keys, "movie:tvdb:", tvdbId, string.Empty);
|
|
return keys;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds the episode part of an episode key, e.g. <c>s1e5</c> or <c>s1e5-6</c> for a double episode.
|
|
/// </summary>
|
|
/// <param name="seasonNumber">The season number (<c>ParentIndexNumber</c>).</param>
|
|
/// <param name="episodeNumber">The episode number (<c>IndexNumber</c>).</param>
|
|
/// <param name="episodeNumberEnd">The last episode number of a multi-episode file (<c>IndexNumberEnd</c>).</param>
|
|
/// <returns>The episode part, or <c>null</c> if season or episode number is missing.</returns>
|
|
public static string? EpisodePart(int? seasonNumber, int? episodeNumber, int? episodeNumberEnd)
|
|
{
|
|
if (seasonNumber is not { } season || episodeNumber is not { } episode)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return episodeNumberEnd is { } end && end > episode
|
|
? string.Create(CultureInfo.InvariantCulture, $"s{season}e{episode}-{end}")
|
|
: string.Create(CultureInfo.InvariantCulture, $"s{season}e{episode}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds the keys of an episode.
|
|
/// </summary>
|
|
/// <param name="episodePart">The episode part from <see cref="EpisodePart"/>.</param>
|
|
/// <param name="seriesTvdbId">The series' TVDb id, if known.</param>
|
|
/// <param name="seriesTmdbId">The series' TMDb id, if known.</param>
|
|
/// <param name="seriesImdbId">The series' IMDb id, if known.</param>
|
|
/// <param name="seriesId">The Jellyfin id of the series; <see cref="Guid.Empty"/> if unknown.</param>
|
|
/// <returns>The keys; contains the series-guid key whenever the series id is known.</returns>
|
|
public static IReadOnlyList<string> ForEpisode(
|
|
string episodePart,
|
|
string? seriesTvdbId,
|
|
string? seriesTmdbId,
|
|
string? seriesImdbId,
|
|
Guid seriesId)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(episodePart);
|
|
|
|
var suffix = ":" + episodePart;
|
|
var keys = new List<string>(4);
|
|
AddIfPresent(keys, "ep:tvdb:", seriesTvdbId, suffix);
|
|
AddIfPresent(keys, "ep:tmdb:", seriesTmdbId, suffix);
|
|
AddIfPresent(keys, "ep:imdb:", seriesImdbId, suffix);
|
|
if (seriesId != Guid.Empty)
|
|
{
|
|
keys.Add("ep:series-guid:" + seriesId.ToString("N", CultureInfo.InvariantCulture) + suffix);
|
|
}
|
|
|
|
return keys;
|
|
}
|
|
|
|
private static void AddIfPresent(List<string> keys, string prefix, string? id, string suffix)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(id))
|
|
{
|
|
keys.Add(prefix + id.Trim() + suffix);
|
|
}
|
|
}
|
|
}
|