using Flaro.Core.Library;
using Flaro.Core.Metadata;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
namespace Flaro.Api;
/// Schreibt eine korrigierte Zuordnung überallhin, wo sie hingehört.
///
///
/// An drei Stellen, und alle drei sind nötig. In die Datenbank, damit
/// es sofort stimmt. In die NFO-Datei, damit es einen Neuaufbau und einen
/// Umzug übersteht. Und als Sperre, damit kein automatischer Lauf die
/// Entscheidung wieder überstimmt.
///
public sealed class MatchApplier(
FlaroContext db,
IMetadataProvider anbieter,
IImageStore bilder,
IImageReader bildLeser,
ISidecarStore beiwerk,
ILogger log)
{
public async Task ApplyAsync(Movie film, MovieMetadata gefunden, CancellationToken ct = default)
{
film.TmdbId = gefunden.ProviderId;
film.ImdbId = gefunden.ImdbId;
film.Title = gefunden.Title;
film.SortTitleValue = SortTitle.For(gefunden.Title);
film.Year = gefunden.Year ?? film.Year;
film.Overview = gefunden.Overview;
film.PosterImage = await Ablegen(gefunden.Poster, ct);
film.BackdropImage = await Ablegen(gefunden.Backdrop, ct);
film.MatchLocked = true;
await db.SaveChangesAsync(ct);
var datei = await db.MediaFiles.AsNoTracking().FirstOrDefaultAsync(f => f.MovieId == film.Id, ct);
if (datei is null || !beiwerk.Enabled) return;
var allein = beiwerk.IsAloneInFolder(datei.Path);
await BildNeben(datei.Path, Name(allein, datei.Path, SidecarNames.MoviePoster), film.PosterImage, ct);
await BildNeben(datei.Path, Name(allein, datei.Path, SidecarNames.MovieBackdrop), film.BackdropImage, ct);
await beiwerk.WriteTextAsync(datei.Path, SidecarNames.MovieNfo, Nfo.Write(new MovieNfo(
film.Title, film.Year, film.Overview, new ProviderIds(film.TmdbId, film.ImdbId), true)), ct);
log.LogInformation("Zuordnung von Hand gesetzt: „{Titel}“ → TMDB {Kennung}", film.Title, film.TmdbId);
}
public async Task ApplyAsync(Series serie, SeriesMetadata gefunden, CancellationToken ct = default)
{
serie.TmdbId = gefunden.ProviderId;
serie.ImdbId = gefunden.ImdbId;
serie.Title = gefunden.Title;
serie.SortTitleValue = SortTitle.For(gefunden.Title);
serie.Year = gefunden.Year ?? serie.Year;
serie.Overview = gefunden.Overview;
serie.PosterImage = await Ablegen(gefunden.Poster, ct);
serie.BackdropImage = await Ablegen(gefunden.Backdrop, ct);
serie.MatchLocked = true;
// **Die Folgen gehörten zur alten Serie.** Ihre Beschreibungen und
// Standbilder sind jetzt falsch; sie bleiben stehen, bis sie durch die
// der richtigen Serie ersetzt sind.
var folgen = await db.Episodes.Where(f => f.SeriesId == serie.Id)
.OrderBy(f => f.SeasonNumber).ThenBy(f => f.EpisodeNumber).ToListAsync(ct);
foreach (var folge in folgen)
{
ct.ThrowIfCancellationRequested();
var neu = await anbieter.FindEpisodeAsync(gefunden.ProviderId, folge.SeasonNumber, folge.EpisodeNumber, ct);
folge.Title = neu?.Title;
folge.Overview = neu?.Overview;
folge.StillImage = await Ablegen(neu?.Still, ct);
}
await db.SaveChangesAsync(ct);
if (!beiwerk.Enabled) return;
await SerienBeiwerk(serie, folgen, ct);
log.LogInformation("Zuordnung von Hand gesetzt: „{Titel}“ → TMDB {Kennung}, {Anzahl} Folgen neu geholt",
serie.Title, serie.TmdbId, folgen.Count);
}
private async Task SerienBeiwerk(Series serie, List folgen, CancellationToken ct)
{
var folgenKennungen = folgen.Select(f => f.Id).ToList();
var dateien = await db.MediaFiles.AsNoTracking()
.Where(d => d.EpisodeId != null && folgenKennungen.Contains(d.EpisodeId!.Value))
.ToListAsync(ct);
if (dateien.Count == 0) return;
var staffel = Path.GetDirectoryName(dateien[0].Path);
var serienOrdner = staffel is null ? null : Path.GetDirectoryName(staffel);
if (serienOrdner is not null)
{
var anker = Path.Combine(serienOrdner, "egal.mkv");
await BildNeben(anker, SidecarNames.SeriesPoster, serie.PosterImage, ct);
await BildNeben(anker, SidecarNames.SeriesBackdrop, serie.BackdropImage, ct);
await beiwerk.WriteTextAsync(anker, SidecarNames.SeriesNfo, Nfo.Write(new SeriesNfo(
serie.Title, serie.Year, serie.Overview, new ProviderIds(serie.TmdbId, serie.ImdbId), true)), ct);
}
foreach (var datei in dateien)
{
var folge = folgen.First(f => f.Id == datei.EpisodeId);
await BildNeben(datei.Path, SidecarNames.EpisodeThumb(datei.Path), folge.StillImage, ct);
await beiwerk.WriteTextAsync(datei.Path, SidecarNames.EpisodeNfo(datei.Path), Nfo.Write(
new EpisodeNfo(folge.Title, folge.SeasonNumber, folge.EpisodeNumber, folge.Overview)), ct);
}
}
private async Task Ablegen(RemoteImage? bild, CancellationToken ct) =>
bild is null ? null : await bilder.StoreAsync(bild, ct);
private async Task BildNeben(string anker, string name, string? schlüssel, CancellationToken ct)
{
if (schlüssel is null) return;
await using var strom = await bildLeser.OpenAsync(schlüssel, ct);
if (strom is null) return;
await beiwerk.WriteStreamAsync(anker, name, strom, ct);
}
private static string Name(bool allein, string mediendatei, string beiwerkName) =>
allein ? beiwerkName : SidecarNames.Prefixed(mediendatei, beiwerkName);
}