Plakate, Hintergründe und NFO-Dateien landen jetzt neben dem Film — und eine
falsche Zuordnung lässt sich richtigstellen, ohne dass der nächste Scan sie
wieder überschreibt.
Der Untergang (2004)/
├── Der Untergang (2004).mkv
├── poster.jpg
├── fanart.jpg
└── movie.nfo
Bei Serien liegt `tvshow.nfo` eine Ebene über dem Staffelordner, je Folge
kommen NFO und Standbild daneben. Liegen mehrere Filme in einem Ordner, heisst
das Beiwerk `Titel (Jahr)-poster.jpg` — sonst überschrieben sie sich
gegenseitig.
**Die Erlaubnisliste ist der ganze Schutz.** Damit das geht, muss die
Medieneinhängung ohne `:ro` auskommen, und damit könnte der Server dort alles
anstellen. Dass er es nicht tut, hängt an `SidecarNames`: sechs Arten von
Datei, nichts sonst. Ein Name, der nicht daraufsteht, wirft — laut, nicht
still, denn das wäre ein Fehler im Programm. Mediendateien werden nie
geändert, umbenannt oder gelöscht; es gibt keine Stelle, die das könnte.
Geprüft an echten Dateien, einschliesslich der Versuche mit `../`, mit
absoluten Pfaden und mit einer Mediendatei als Ziel.
Standardmässig **aus**. In fremde Medienordner zu schreiben ist nichts, was
ein Server ungefragt tun sollte.
**Gelesen wird auch ohne Schreiberlaubnis.** Eine vorhandene NFO ist eine
Zuordnung, die jemand getroffen hat — Halo nimmt sie und sucht gar nicht
erst. Dasselbe gilt für Ordnernamen der Form `[tmdbid-613]`. Halo liest sie
und benennt **nichts** um: Umbenennen ist der einzige Eingriff, der eine
Bibliothek wirklich zerlegen kann.
Nebenbei: eine NFO aus dem Medienordner hat Halo nicht geschrieben. DTDs sind
beim Lesen abgeschaltet — eine untergeschobene Datei mit externer Entität
könnte sonst den Server dazu bringen, `/config/halo.db` einzulesen und als
Titel auszuliefern. Mit Prüfung.
---
**Der Fehler, den erst der echte Lauf zeigte** — und er ist die zweite
Auflage eines schon einmal behobenen:
Nach der Korrektur stand im Ordner „Dark (2017)", in der Datenbank aber 2004.
Der nächste Scan suchte nach *(Dark, 2017)*, fand nichts, legte den Film neu
an und löschte den korrigierten als verwaist. Überlebt hat die Zuordnung nur,
weil sie in der NFO stand — das System hat sich selbst gerettet, aber der
Eintrag war ein anderer und das Jahr wieder falsch.
Dieselbe Klasse wie bei `ParsedTitle`: zugeordnet wurde über einen Wert, den
die Metadatenquelle verändert. Jetzt gibt es `ParsedYear`, und das ändert sich
nie. Mit Prüfung, die genau diesen Ablauf festhält.
---
Dazu von TMDB: die IMDb-Kennung wird nachgeschlagen, damit sie in der NFO
steht — wer später auf ein anderes Werkzeug wechselt, findet denselben Titel
wieder, auch wenn das TMDB gar nicht kennt.
308 Tests grün. Im Container nachgewiesen: Zuordnung falsch, korrigiert,
zweiter Scan — derselbe Eintrag, dieselbe Kennung, Jahr bleibt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
224 lines
8.2 KiB
C#
224 lines
8.2 KiB
C#
using Halo.Core.Library;
|
|
|
|
namespace Halo.Core.Tests;
|
|
|
|
/// <summary>Was Halo in einen Medienordner schreiben darf.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// <b>Die wichtigste Prüfdatei des Projekts.</b> Damit Plakate neben dem Film
|
|
/// landen können, ist die Einhängung schreibbar — und damit hängt der Schutz
|
|
/// des gesamten Bestands an dieser einen Liste.
|
|
/// </remarks>
|
|
public class SidecarNamesTests
|
|
{
|
|
[TestCase("poster.jpg")]
|
|
[TestCase("fanart.jpg")]
|
|
[TestCase("logo.png")]
|
|
[TestCase("movie.nfo")]
|
|
[TestCase("tvshow.nfo")]
|
|
[TestCase("Dark - S01E01.nfo")]
|
|
[TestCase("Dark - S01E01-thumb.jpg")]
|
|
[TestCase("POSTER.JPG")]
|
|
public void Beiwerk_darf_geschrieben_werden(string name)
|
|
{
|
|
Assert.That(SidecarNames.MayWrite(name), Is.True);
|
|
}
|
|
|
|
/// <summary>Eine Mediendatei wird nie angefasst — auch nicht, wenn jemand es verlangt.</summary>
|
|
[TestCase("film.mkv")]
|
|
[TestCase("Der Untergang (2004).mp4")]
|
|
[TestCase("film.mkv.nfo.mkv")]
|
|
public void Mediendateien_niemals(string name)
|
|
{
|
|
Assert.That(SidecarNames.MayWrite(name), Is.False);
|
|
}
|
|
|
|
/// <summary>Kein Weg aus dem Ordner heraus.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// Käme hier ein Pfadbestandteil durch, liesse sich über einen
|
|
/// erfundenen „Dateinamen" in jedes Verzeichnis des Containers
|
|
/// schreiben — auch in das, in dem die Datenbank liegt.
|
|
/// </remarks>
|
|
[TestCase("../poster.jpg")]
|
|
[TestCase("../../etc/passwd")]
|
|
[TestCase("/etc/poster.jpg")]
|
|
[TestCase("unterordner/poster.jpg")]
|
|
[TestCase("..\\poster.jpg")]
|
|
[TestCase("ordner\\movie.nfo")]
|
|
[TestCase("..movie.nfo")]
|
|
public void Kein_Pfad_kommt_durch(string versuch)
|
|
{
|
|
Assert.That(SidecarNames.MayWrite(versuch), Is.False);
|
|
}
|
|
|
|
[TestCase("")]
|
|
[TestCase(" ")]
|
|
[TestCase(".nfo")]
|
|
[TestCase("-thumb.jpg")]
|
|
[TestCase(".versteckt.nfo")]
|
|
[TestCase("poster.png")]
|
|
[TestCase("beliebig.txt")]
|
|
public void Alles_andere_wird_abgelehnt(string versuch)
|
|
{
|
|
Assert.That(SidecarNames.MayWrite(versuch), Is.False);
|
|
}
|
|
|
|
/// <summary>Zwei Filme im selben Ordner dürfen sich nicht überschreiben.</summary>
|
|
[Test]
|
|
public void Beiwerk_mit_Dateinamen_davor_ist_auch_erlaubt()
|
|
{
|
|
Assert.That(SidecarNames.Prefixed("Matrix (1999).mkv", SidecarNames.MoviePoster),
|
|
Is.EqualTo("Matrix (1999)-poster.jpg"));
|
|
|
|
foreach (var beiwerk in new[] { SidecarNames.MoviePoster, SidecarNames.MovieBackdrop, SidecarNames.MovieLogo })
|
|
Assert.That(SidecarNames.MayWrite(SidecarNames.Prefixed("Matrix (1999).mkv", beiwerk)), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void Folgen_Beiwerk_heisst_wie_die_Folge()
|
|
{
|
|
Assert.That(SidecarNames.EpisodeNfo("Dark - S01E01.mkv"), Is.EqualTo("Dark - S01E01.nfo"));
|
|
Assert.That(SidecarNames.EpisodeThumb("Dark - S01E01.mkv"), Is.EqualTo("Dark - S01E01-thumb.jpg"));
|
|
}
|
|
|
|
/// <summary>Was erzeugt wird, muss auch erlaubt sein.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// Die beiden Hälften könnten auseinanderlaufen — jemand ändert das Muster
|
|
/// für Folgen und vergisst die Liste. Dann schriebe Halo nichts mehr und
|
|
/// niemand wüsste, warum.
|
|
/// </remarks>
|
|
[TestCase("Dark - S01E01.mkv")]
|
|
[TestCase("Notruf.Hafenkante.S01E01.German.1080p.mkv")]
|
|
[TestCase("Serie 1x01.avi")]
|
|
public void Die_erzeugten_Namen_stehen_auf_der_Liste(string mediendatei)
|
|
{
|
|
Assert.That(SidecarNames.MayWrite(SidecarNames.EpisodeNfo(mediendatei)), Is.True);
|
|
Assert.That(SidecarNames.MayWrite(SidecarNames.EpisodeThumb(mediendatei)), Is.True);
|
|
}
|
|
}
|
|
|
|
public class ProviderIdParserTests
|
|
{
|
|
[Test]
|
|
public void Eine_TMDB_Kennung_im_Ordnernamen_wird_gelesen()
|
|
{
|
|
var kennungen = ProviderIdParser.FromPath("/media/Filme/Der Untergang (2004) [tmdbid-613]/film.mkv");
|
|
Assert.That(kennungen.Tmdb, Is.EqualTo(613));
|
|
}
|
|
|
|
[Test]
|
|
public void Eine_IMDb_Kennung_wird_gelesen_und_kleingeschrieben()
|
|
{
|
|
var kennungen = ProviderIdParser.FromPath("/media/Filme/Matrix (1999) [imdbid-TT0133093]/film.mkv");
|
|
Assert.That(kennungen.Imdb, Is.EqualTo("tt0133093"));
|
|
}
|
|
|
|
[Test]
|
|
public void Beide_nebeneinander_gehen_auch()
|
|
{
|
|
var kennungen = ProviderIdParser.FromPath("/m/Film (2004) [tmdbid-613] [imdbid-tt0363163]/f.mkv");
|
|
Assert.That((kennungen.Tmdb, kennungen.Imdb), Is.EqualTo((613, "tt0363163")));
|
|
}
|
|
|
|
[TestCase("/media/Filme/Der Untergang (2004)/film.mkv")]
|
|
[TestCase("/media/Filme/Film [1080p]/film.mkv")]
|
|
[TestCase("")]
|
|
[TestCase("/media/Film [tmdbid-]/f.mkv")]
|
|
[TestCase("/media/Film [imdbid-nichts]/f.mkv")]
|
|
public void Ohne_Kennung_kommt_nichts_zurueck(string pfad)
|
|
{
|
|
Assert.That(ProviderIdParser.FromPath(pfad).Any, Is.False);
|
|
}
|
|
}
|
|
|
|
public class NfoTests
|
|
{
|
|
[Test]
|
|
public void Ein_Film_wird_geschrieben_und_wieder_gelesen()
|
|
{
|
|
var xml = Nfo.Write(new MovieNfo(
|
|
"Der Untergang", 2004, "Berlin, April 1945.",
|
|
new ProviderIds(613, "tt0363163"), Locked: true));
|
|
|
|
Assert.That(xml, Does.StartWith("<?xml"));
|
|
Assert.That(xml, Does.Contain("<title>Der Untergang</title>"));
|
|
Assert.That(xml, Does.Contain("<year>2004</year>"));
|
|
|
|
var gelesen = Nfo.ReadIds(xml);
|
|
Assert.That((gelesen.Tmdb, gelesen.Imdb), Is.EqualTo((613, "tt0363163")));
|
|
Assert.That(Nfo.ReadLocked(xml), Is.True);
|
|
}
|
|
|
|
/// <summary>Kennungen stehen doppelt drin, für alte wie neue Werkzeuge.</summary>
|
|
[Test]
|
|
public void Die_Kennungen_stehen_in_beiden_Formen_da()
|
|
{
|
|
var xml = Nfo.Write(new MovieNfo("Egal", null, null, new ProviderIds(613, "tt0363163"), false));
|
|
|
|
Assert.That(xml, Does.Contain("""<uniqueid type="tmdb" default="true">613</uniqueid>"""));
|
|
Assert.That(xml, Does.Contain("<tmdbid>613</tmdbid>"));
|
|
Assert.That(xml, Does.Contain("""<uniqueid type="imdb">tt0363163</uniqueid>"""));
|
|
Assert.That(xml, Does.Contain("<imdbid>tt0363163</imdbid>"));
|
|
}
|
|
|
|
[Test]
|
|
public void Die_aeltere_Form_allein_wird_auch_gelesen()
|
|
{
|
|
var gelesen = Nfo.ReadIds("<movie><tmdbid>613</tmdbid><imdbid>tt0363163</imdbid></movie>");
|
|
Assert.That((gelesen.Tmdb, gelesen.Imdb), Is.EqualTo((613, "tt0363163")));
|
|
}
|
|
|
|
[Test]
|
|
public void Sonderzeichen_im_Titel_werden_richtig_geschrieben()
|
|
{
|
|
var xml = Nfo.Write(new MovieNfo("Tom & Jerry <der Film>", null, null, ProviderIds.None, false));
|
|
|
|
Assert.That(xml, Does.Contain("Tom & Jerry <der Film>"));
|
|
Assert.That(Nfo.ReadIds(xml).Any, Is.False, "Und die Datei bleibt lesbar");
|
|
}
|
|
|
|
/// <summary>Eine von Hand geschriebene NFO ist oft kaputt — das darf nichts abbrechen.</summary>
|
|
[TestCase("")]
|
|
[TestCase("kein xml")]
|
|
[TestCase("<movie><title>offen")]
|
|
[TestCase("<movie><tmdbid>keine zahl</tmdbid></movie>")]
|
|
public void Unlesbares_ergibt_nichts_statt_eines_Absturzes(string kaputt)
|
|
{
|
|
Assert.That(Nfo.ReadIds(kaputt).Any, Is.False);
|
|
Assert.That(Nfo.ReadLocked(kaputt), Is.False);
|
|
}
|
|
|
|
/// <summary>Eine NFO aus dem Medienordner hat Halo nicht geschrieben.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// <b>Ohne abgeschaltete DTDs wäre das ein Weg, fremde Dateien zu lesen.</b>
|
|
/// Eine untergeschobene NFO mit einer externen Entität könnte den Server
|
|
/// dazu bringen, <c>/config/halo.db</c> einzulesen und den Inhalt als
|
|
/// Titel auszuliefern.
|
|
/// </remarks>
|
|
[Test]
|
|
public void Eine_NFO_mit_externer_Entitaet_wird_nicht_ausgewertet()
|
|
{
|
|
var angriff = """
|
|
<?xml version="1.0"?>
|
|
<!DOCTYPE movie [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
|
|
<movie><title>&xxe;</title><tmdbid>613</tmdbid></movie>
|
|
""";
|
|
|
|
Assert.That(Nfo.ReadIds(angriff).Any, Is.False, "Die Datei wird gar nicht erst verarbeitet");
|
|
Assert.That(Nfo.ReadLocked(angriff), Is.False);
|
|
}
|
|
|
|
[Test]
|
|
public void Eine_Folge_bekommt_Staffel_und_Nummer()
|
|
{
|
|
var xml = Nfo.Write(new EpisodeNfo("Geheimnisse", 1, 1, "Ein Junge verschwindet."));
|
|
|
|
Assert.That(xml, Does.Contain("<season>1</season>"));
|
|
Assert.That(xml, Does.Contain("<episode>1</episode>"));
|
|
Assert.That(xml, Does.Contain("<title>Geheimnisse</title>"));
|
|
}
|
|
}
|