Halo heisst ab hier Flaro — die Marke stand im Weg
Der Upload zu App Store Connect scheiterte am Namen. „Halo" ist eine eingetragene Marke von Microsoft; im App Store heisst zwar keine App exakt so, aber reservierte und markenrechtlich belegte Namen tauchen in keiner Suche auf. Der einzige verlässliche Test war die Eingabe dort, und die ging daneben. **Der Name wurde diesmal vorher geprüft.** Zwanzig Kandidaten gegen den deutschen und amerikanischen App Store, auf exakte Treffer und auf ähnliche Namen. „Kavo" wäre dieselbe Wand gewesen — exakt belegt in beiden Stores, in Deutschland von KaVo Dental GmbH. „Flare" ist mit 42 ähnlichen Apps je Store der am dichtesten besetzte Name der ganzen Runde. **Flaro** ist in beiden frei und liest sich als eigenes Wort, nicht als Flare mit Tippfehler. Das bleibt eine Store-Suche und keine Markenrecherche — reservierte Namen ohne veröffentlichte App sieht sie nicht. Aber es ist mehr Prüfung, als beim letzten Mal stattfand. --- 2160 Textvorkommen in 273 Dateien, 49 Pfade. Betroffen ist diesmal auch der Server: `Flaro.Api`, `Flaro.Core`, `Flaro.Data`, `Flaro.Media`, `Flaro.Metadata`, `FlaroContext`, `FlaroPaths`, `flaro.db`, die Variablen `FLARO_DATA`, `FLARO_CACHE`, `FLARO_TRANSCODE`, `FLARO_WRITE_SIDECARS`, und das Image. **Vor dem Ersetzen geprüft**, ob „halo" irgendwo als Teil eines anderen Wortes steckt — und ob es im TMDB-Token vorkommt. Tat es nicht; ein blindes Ersetzen hätte den sonst zerlegt. `docs/superpowers/specs/` bleibt wie beim letzten Mal unangetastet: datierte Entwurfsdokumente sind ein Protokoll getroffener Entscheidungen. Das Server-Dokument hat stattdessen einen Nachtrag bekommen, damit es niemanden in die Irre führt. **Die Bundle-ID wandert mit**, zum zweiten Mal an einem Tag: `com.scarriffleservices.flaro`. Wer die vorige Fassung installiert hat, hat jetzt zwei Karteileichen auf dem Gerät — beide löschen, dann ist Ruhe. 308 Server-Tests grün, 368 im Swift-Paket, null Warnungen. iOS und tvOS bauen, das Image baut, und ein frischer Container läuft durch Einrichtung und Anmeldung. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f98b995050
commit
a7cf6c5def
@@ -0,0 +1,223 @@
|
||||
using Flaro.Core.Library;
|
||||
|
||||
namespace Flaro.Core.Tests;
|
||||
|
||||
/// <summary>Was Flaro 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 Flaro 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 Flaro 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/flaro.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>"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user