using Flaro.Core.Library;
using Flaro.Core.Metadata;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Flaro.Data;
/// Was ein Durchgang an Übersetzungen geholt hat.
public sealed record Uebersetzungsbericht(int Filme, int Serien, int Fehlend)
{
public int Gesamt => Filme + Serien;
}
/// Holt Titel und Beschreibungen in einer zweiten Sprache.
///
///
///
/// Warum überhaupt zweisprachig. Metadaten werden in der Sprache des
/// Servers geholt und gespeichert — eine Sprache, ein Satz Daten. Will jemand
/// im Haushalt Englisch sehen, hilft kein Umschalten in der Oberfläche: die
/// deutschen Titel sind alles, was da ist.
///
///
/// Als Sammelaktion, nicht nebenbei. Jeden Titel beim Öffnen einzeln
/// nachzuladen verteilt die Wartezeit über Wochen und trifft immer den, der
/// gerade etwas sucht. Einmal angestossen, einmal gewartet, danach ist es da.
///
///
/// Was fehlt, erscheint in der Sprache des Servers — nicht leer. Ein
/// Film ohne englische Übersetzung heisst dann eben weiter „Das Boot"; das
/// ist richtiger als ein leeres Feld und ehrlicher als ein Platzhalter.
///
///
public sealed class Uebersetzer(
FlaroContext db, IMetadataProvider anbieter, TimeProvider uhr, ILogger log)
{
/// Holt für alle Titel, was in dieser Sprache fehlt.
public async Task HolenAsync(
string sprache, IProgress? fortschritt = null, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(sprache)) return new Uebersetzungsbericht(0, 0, 0);
if (!await anbieter.IsConfiguredAsync(ct)) return new Uebersetzungsbericht(0, 0, 0);
var filme = await db.Movies
.Where(m => m.TmdbId != null
&& !db.Translations.Any(t => t.MovieId == m.Id && t.Language == sprache))
.Select(m => new { m.Id, TmdbId = m.TmdbId!.Value, m.Title })
.ToListAsync(ct);
var serien = await db.Series
.Where(s => s.TmdbId != null
&& !db.Translations.Any(t => t.SeriesId == s.Id && t.Language == sprache))
.Select(s => new { s.Id, TmdbId = s.TmdbId!.Value, s.Title })
.ToListAsync(ct);
int fertigFilme = 0, fertigSerien = 0, fehlend = 0, durch = 0;
var gesamt = filme.Count + serien.Count;
foreach (var film in filme)
{
ct.ThrowIfCancellationRequested();
fortschritt?.Report(new Scanschritt(Scanphase.Holen, film.Title, ++durch, gesamt));
var gefunden = await anbieter.GetMovieAsync(film.TmdbId, sprache, ct);
if (gefunden is null) { fehlend++; continue; }
db.Translations.Add(new ItemTranslation
{
MovieId = film.Id,
Language = sprache,
Title = gefunden.Title,
Overview = gefunden.Overview,
Tagline = gefunden.Tagline,
FetchedAt = uhr.GetUtcNow(),
});
fertigFilme++;
// In Häppchen schreiben: ein Abbruch nach dreihundert Titeln soll
// nicht alles Geholte verwerfen.
if (fertigFilme % 25 == 0) await db.SaveChangesAsync(ct);
}
foreach (var serie in serien)
{
ct.ThrowIfCancellationRequested();
fortschritt?.Report(new Scanschritt(Scanphase.Holen, serie.Title, ++durch, gesamt));
var gefunden = await anbieter.GetSeriesAsync(serie.TmdbId, sprache, ct);
if (gefunden is null) { fehlend++; continue; }
db.Translations.Add(new ItemTranslation
{
SeriesId = serie.Id,
Language = sprache,
Title = gefunden.Title,
Overview = gefunden.Overview,
Tagline = gefunden.Tagline,
FetchedAt = uhr.GetUtcNow(),
});
fertigSerien++;
if (fertigSerien % 25 == 0) await db.SaveChangesAsync(ct);
}
await db.SaveChangesAsync(ct);
log.LogInformation(
"Übersetzungen für {Sprache}: {Filme} Filme, {Serien} Serien, {Fehlend} ohne Treffer.",
sprache, fertigFilme, fertigSerien, fehlend);
return new Uebersetzungsbericht(fertigFilme, fertigSerien, fehlend);
}
/// Wie weit eine Sprache abgedeckt ist — für den Hinweis beim Umstellen.
public async Task<(int Vorhanden, int Gesamt)> AbdeckungAsync(
string sprache, CancellationToken ct = default)
{
var gesamt = await db.Movies.CountAsync(m => m.TmdbId != null, ct)
+ await db.Series.CountAsync(s => s.TmdbId != null, ct);
var vorhanden = await db.Translations.CountAsync(t => t.Language == sprache, ct);
return (vorhanden, gesamt);
}
}