namespace Flaro.Core.Library; /// Welche Dateien Flaro neben eine Mediendatei legen darf — und sonst keine. /// /// /// /// Eine Erlaubnisliste, und sie ist der ganze Schutz. Damit Plakate und /// Metadaten im Filmordner landen können, muss die Einhängung schreibbar /// sein — und damit kann der Server im Grundsatz alles dort anstellen. Dass /// er es nicht tut, hängt an dieser Liste und an der Prüfung darunter. /// /// /// Mediendateien werden nie angefasst. Nicht geändert, nicht /// umbenannt, nicht gelöscht. Es gibt im ganzen Projekt keine Stelle, die das /// könnte, und diese Liste ist der Grund: was nicht daraufsteht, wird /// abgelehnt — auch wenn jemand es ausdrücklich verlangt. /// /// /// Transcode-Zwischenstände gehören ausdrücklich nicht hierher. Die /// liegen unter FLARO_TRANSCODE, auf einem eigenen Volume. Ein /// Medienordner ist kein Transcode-Cache. /// /// public static class SidecarNames { public const string MoviePoster = "poster.jpg"; public const string MovieBackdrop = "fanart.jpg"; public const string MovieLogo = "logo.png"; public const string MovieNfo = "movie.nfo"; public const string SeriesPoster = "poster.jpg"; public const string SeriesBackdrop = "fanart.jpg"; public const string SeriesNfo = "tvshow.nfo"; /// Was zu einer Folge gehört, hängt am Namen ihrer Datei. /// /// /// Aus Dark - S01E01.mkv werden Dark - S01E01.nfo und /// Dark - S01E01-thumb.jpg. So erwarten es Kodi, Jellyfin und Plex /// — und so findet auch ein Mensch sie wieder. /// public static string EpisodeNfo(string mediendatei) => Path.GetFileNameWithoutExtension(mediendatei) + ".nfo"; public static string EpisodeThumb(string mediendatei) => Path.GetFileNameWithoutExtension(mediendatei) + "-thumb.jpg"; /// Beiwerk für einen Film, der sich den Ordner mit anderen teilt. /// /// /// Sonst überschreiben sich zwei Filme gegenseitig das Plakat. Wer /// je Film einen Ordner anlegt, bekommt die schlichten Namen; wer zehn /// Filme nebeneinanderlegt, bekommt sie mit dem Dateinamen davor. Kodi und /// Jellyfin lesen beide Formen. /// public static string Prefixed(string mediendatei, string beiwerk) => Path.GetFileNameWithoutExtension(mediendatei) + "-" + beiwerk; private static readonly HashSet Feste = new(StringComparer.OrdinalIgnoreCase) { MoviePoster, MovieBackdrop, MovieLogo, MovieNfo, SeriesNfo }; private static readonly string[] Endungen = [".nfo", "-thumb.jpg", "-poster.jpg", "-fanart.jpg", "-logo.png"]; /// Ob dieser Dateiname geschrieben werden darf. /// /// /// Geprüft wird der Name, nicht die Absicht. Ein Pfadbestandteil, /// ein Verzeichniswechsel oder ein führender Punkt kommen hier nicht /// durch — und „film.mkv" erst recht nicht, auch wenn es auf .nfo /// endete. /// public static bool MayWrite(string dateiname) { if (string.IsNullOrWhiteSpace(dateiname)) return false; // Kein Pfad, nur ein Name. Alles andere könnte aus dem Ordner führen. if (dateiname.Contains('/') || dateiname.Contains('\\')) return false; if (dateiname != Path.GetFileName(dateiname)) return false; if (dateiname.StartsWith('.')) return false; if (dateiname.Contains("..", StringComparison.Ordinal)) return false; if (Feste.Contains(dateiname)) return true; // Je Folge: beliebiger Rumpf, aber nur mit diesen Endungen. return Endungen.Any(e => dateiname.EndsWith(e, StringComparison.OrdinalIgnoreCase) && dateiname.Length > e.Length); } }