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); } }