Zwei Titel je Film, und die Sprache lässt sich wählen
Die Mediathek sah zweisprachig aus — "Despicable Me" neben "Ich einfach unverbesserlich". Es gab nur ein Titelfeld, und welche Sprache darin landete, entschied die Einstellung zum Zeitpunkt des Scans. Movie und Series tragen jetzt zusätzlich OriginalTitle. Gesucht wird über beide: wer "Despicable Me" tippt, findet "Ich einfach unverbesserlich". Auf der Detailseite steht der Originaltitel unter dem übersetzten, wenn er abweicht. Die Sprache der Metadaten ist in der Verwaltung eine Auswahl statt eines Textfelds. Vorher musste man "de-DE" wissen, und ein Tippfehler darin nahm TMDB stillschweigend als Englisch an. Dazu eine eigene Sprache je Benutzer. Das Ehrliche daran steht im Dialog: die Daten liegen in einer Sprache vor, und wer eine andere wählt, bekommt, was übersetzt vorliegt — für den Rest die Serversprache, nicht ein leeres Feld. ItemTranslation hält die zweite Sprache, gefüllt über eine Sammelaktion. Nicht nebenbei beim Öffnen: das verteilte die Wartezeit über Wochen und träfe immer den, der gerade etwas sucht. Alle Sprachen für alle Titel vorzuhalten wäre der Bestand mal Sprachen an Abrufen und Speicher, für einen Haushalt mit zweien. Deshalb auf Ansage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f359807561
commit
921533050b
@@ -38,6 +38,7 @@ public sealed class FlaroContext(DbContextOptions<FlaroContext> options) : DbCon
|
||||
public DbSet<GenreLink> GenreLinks => Set<GenreLink>();
|
||||
public DbSet<Person> People => Set<Person>();
|
||||
public DbSet<Credit> Credits => Set<Credit>();
|
||||
public DbSet<ItemTranslation> Translations => Set<ItemTranslation>();
|
||||
|
||||
/// <summary>Gilt für jede Zeitangabe im Modell, ohne sie einzeln zu nennen.</summary>
|
||||
///
|
||||
@@ -62,6 +63,7 @@ public sealed class FlaroContext(DbContextOptions<FlaroContext> options) : DbCon
|
||||
// Zwei Konten „Guido" und „guido" wären eine Falle, keine Funktion.
|
||||
e.HasIndex(x => x.UserName).IsUnique();
|
||||
e.Property(x => x.UserName).HasMaxLength(64).UseCollation("NOCASE");
|
||||
e.Property(x => x.Language).HasMaxLength(16);
|
||||
e.Property(x => x.PasswordHash).HasMaxLength(256);
|
||||
});
|
||||
|
||||
@@ -142,6 +144,7 @@ public sealed class FlaroContext(DbContextOptions<FlaroContext> options) : DbCon
|
||||
.HasFilter("\"TmdbId\" IS NOT NULL");
|
||||
|
||||
e.Property(x => x.Title).HasMaxLength(512);
|
||||
e.Property(x => x.OriginalTitle).HasMaxLength(512);
|
||||
e.Property(x => x.SortTitleValue).HasMaxLength(512);
|
||||
e.Property(x => x.ImdbId).HasMaxLength(32);
|
||||
e.Property(x => x.Tagline).HasMaxLength(512);
|
||||
@@ -170,6 +173,7 @@ public sealed class FlaroContext(DbContextOptions<FlaroContext> options) : DbCon
|
||||
.HasFilter("\"TmdbId\" IS NOT NULL");
|
||||
|
||||
e.Property(x => x.Title).HasMaxLength(512);
|
||||
e.Property(x => x.OriginalTitle).HasMaxLength(512);
|
||||
e.Property(x => x.SortTitleValue).HasMaxLength(512);
|
||||
e.Property(x => x.ImdbId).HasMaxLength(32);
|
||||
e.Property(x => x.Tagline).HasMaxLength(512);
|
||||
@@ -234,6 +238,23 @@ public sealed class FlaroContext(DbContextOptions<FlaroContext> options) : DbCon
|
||||
e.HasOne<Series>().WithMany().HasForeignKey(x => x.SeriesId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
b.Entity<ItemTranslation>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Language).HasMaxLength(16);
|
||||
e.Property(x => x.Title).HasMaxLength(512);
|
||||
e.Property(x => x.Tagline).HasMaxLength(512);
|
||||
|
||||
// Je Titel und Sprache genau eine Zeile — sonst sammeln sich
|
||||
// Übersetzungen an, und welche gezeigt wird, entscheidet der Zufall.
|
||||
e.HasIndex(x => new { x.MovieId, x.Language }).IsUnique()
|
||||
.HasFilter("\"MovieId\" IS NOT NULL");
|
||||
e.HasIndex(x => new { x.SeriesId, x.Language }).IsUnique()
|
||||
.HasFilter("\"SeriesId\" IS NOT NULL");
|
||||
e.HasIndex(x => new { x.EpisodeId, x.Language }).IsUnique()
|
||||
.HasFilter("\"EpisodeId\" IS NOT NULL");
|
||||
});
|
||||
|
||||
b.Entity<UserItemState>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
|
||||
@@ -104,6 +104,7 @@ public sealed class MetadataEnricher(
|
||||
film.TmdbId = gefunden.ProviderId;
|
||||
film.ImdbId = gefunden.ImdbId;
|
||||
film.Title = gefunden.Title;
|
||||
film.OriginalTitle = gefunden.OriginalTitle;
|
||||
film.SortTitleValue = SortTitle.For(gefunden.Title);
|
||||
film.Year = gefunden.Year ?? film.Year;
|
||||
film.Overview = gefunden.Overview;
|
||||
@@ -180,6 +181,7 @@ public sealed class MetadataEnricher(
|
||||
serie.TmdbId = gefunden.ProviderId;
|
||||
serie.ImdbId = gefunden.ImdbId;
|
||||
serie.Title = gefunden.Title;
|
||||
serie.OriginalTitle = gefunden.OriginalTitle;
|
||||
serie.SortTitleValue = SortTitle.For(gefunden.Title);
|
||||
serie.Year = gefunden.Year ?? serie.Year;
|
||||
serie.Overview = gefunden.Overview;
|
||||
|
||||
+852
@@ -0,0 +1,852 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Flaro.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Flaro.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(FlaroContext))]
|
||||
[Migration("20261004102022_SpracheUndTitel")]
|
||||
partial class SpracheUndTitel
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.12");
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Auth.AuthCode", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("ApprovedByUserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long?>("ConsumedAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("CreatedAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeviceName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("ExpiresAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("HandleFingerprint")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Code");
|
||||
|
||||
b.HasIndex("HandleFingerprint")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("AuthCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("CreatedAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeviceName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("LastSeenAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long?>("RevokedAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("TokenFingerprint")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TokenFingerprint")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("DeviceTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Libraries.Library", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("CreatedAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Kind")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Libraries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.Credit", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Character")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("MovieId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("PersonId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Role")
|
||||
.IsRequired()
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("SeriesId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MovieId");
|
||||
|
||||
b.HasIndex("PersonId");
|
||||
|
||||
b.HasIndex("SeriesId");
|
||||
|
||||
b.ToTable("Credits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("AddedAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("EpisodeNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Overview")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SeasonNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("SeriesId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StillImage")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SeriesId", "SeasonNumber", "EpisodeNumber")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Episodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.Genre", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT")
|
||||
.UseCollation("NOCASE");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Genres");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.GenreLink", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("GenreId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("MovieId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("SeriesId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MovieId");
|
||||
|
||||
b.HasIndex("SeriesId");
|
||||
|
||||
b.HasIndex("GenreId", "MovieId")
|
||||
.IsUnique()
|
||||
.HasFilter("\"MovieId\" IS NOT NULL");
|
||||
|
||||
b.HasIndex("GenreId", "SeriesId")
|
||||
.IsUnique()
|
||||
.HasFilter("\"SeriesId\" IS NOT NULL");
|
||||
|
||||
b.ToTable("GenreLinks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.ItemTranslation", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("EpisodeId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("FetchedAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("MovieId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Overview")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("SeriesId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Tagline")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("EpisodeId", "Language")
|
||||
.IsUnique()
|
||||
.HasFilter("\"EpisodeId\" IS NOT NULL");
|
||||
|
||||
b.HasIndex("MovieId", "Language")
|
||||
.IsUnique()
|
||||
.HasFilter("\"MovieId\" IS NOT NULL");
|
||||
|
||||
b.HasIndex("SeriesId", "Language")
|
||||
.IsUnique()
|
||||
.HasFilter("\"SeriesId\" IS NOT NULL");
|
||||
|
||||
b.ToTable("Translations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long?>("BitRate")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Container")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long?>("Duration")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid?>("EpisodeId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("LibraryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long?>("MissingSince")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("ModifiedAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid?>("MovieId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long?>("ProbedAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("SeenAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("SizeBytes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("VersionLabel")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("EpisodeId");
|
||||
|
||||
b.HasIndex("MovieId");
|
||||
|
||||
b.HasIndex("LibraryId", "Path")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("MediaFiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.MediaStream", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("Channels")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Codec")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("Height")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Index")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsDefault")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsForced")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Kind")
|
||||
.IsRequired()
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("MediaFileId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("Width")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MediaFileId", "Index")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("MediaStreams");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("AddedAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("BackdropImage")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ContentRating")
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ImdbId")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("LibraryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LogoImage")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("MatchLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("OriginalTitle")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Overview")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ParsedTitle")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("ParsedYear")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("PosterImage")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<double?>("Rating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int?>("RuntimeMinutes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SortTitleValue")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Tagline")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("TmdbId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("Year")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("LibraryId", "SortTitleValue");
|
||||
|
||||
b.HasIndex("LibraryId", "TmdbId")
|
||||
.IsUnique()
|
||||
.HasFilter("\"TmdbId\" IS NOT NULL");
|
||||
|
||||
b.ToTable("Movies");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.Person", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Image")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("TmdbId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TmdbId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("People");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.Series", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("AddedAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("BackdropImage")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ContentRating")
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ImdbId")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("LibraryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LogoImage")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("MatchLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("OriginalTitle")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Overview")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ParsedTitle")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("ParsedYear")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("PosterImage")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<double?>("Rating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int?>("RuntimeMinutes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SortTitleValue")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Tagline")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("TmdbId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("Year")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("LibraryId", "SortTitleValue");
|
||||
|
||||
b.HasIndex("LibraryId", "TmdbId")
|
||||
.IsUnique()
|
||||
.HasFilter("\"TmdbId\" IS NOT NULL");
|
||||
|
||||
b.ToTable("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Playback.UserItemState", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Dismissed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid?>("EpisodeId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Favourite")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("InProgress")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long?>("LastPlayedAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid?>("MovieId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("PlayCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Position")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("Rating")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long?>("Runtime")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("UpdatedAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Watched")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("EpisodeId");
|
||||
|
||||
b.HasIndex("MovieId");
|
||||
|
||||
b.HasIndex("UserId", "EpisodeId")
|
||||
.IsUnique()
|
||||
.HasFilter("\"EpisodeId\" IS NOT NULL");
|
||||
|
||||
b.HasIndex("UserId", "MovieId")
|
||||
.IsUnique()
|
||||
.HasFilter("\"MovieId\" IS NOT NULL");
|
||||
|
||||
b.HasIndex("UserId", "InProgress", "UpdatedAt");
|
||||
|
||||
b.ToTable("UserItemStates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.ServerSetting", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Users.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("CreatedAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsAdministrator")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT")
|
||||
.UseCollation("NOCASE");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserName")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
|
||||
{
|
||||
b.HasOne("Flaro.Core.Users.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.Credit", b =>
|
||||
{
|
||||
b.HasOne("Flaro.Core.Library.Movie", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MovieId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("Flaro.Core.Library.Person", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PersonId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Flaro.Core.Library.Series", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SeriesId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
|
||||
{
|
||||
b.HasOne("Flaro.Core.Library.Series", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SeriesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.GenreLink", b =>
|
||||
{
|
||||
b.HasOne("Flaro.Core.Library.Genre", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("GenreId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Flaro.Core.Library.Movie", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MovieId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("Flaro.Core.Library.Series", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SeriesId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
|
||||
{
|
||||
b.HasOne("Flaro.Core.Libraries.Library", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("LibraryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.MediaStream", b =>
|
||||
{
|
||||
b.HasOne("Flaro.Core.Library.MediaFile", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MediaFileId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
|
||||
{
|
||||
b.HasOne("Flaro.Core.Libraries.Library", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("LibraryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.Series", b =>
|
||||
{
|
||||
b.HasOne("Flaro.Core.Libraries.Library", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("LibraryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Playback.UserItemState", b =>
|
||||
{
|
||||
b.HasOne("Flaro.Core.Library.Episode", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("EpisodeId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("Flaro.Core.Library.Movie", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MovieId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("Flaro.Core.Users.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Flaro.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SpracheUndTitel : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Language",
|
||||
table: "Users",
|
||||
type: "TEXT",
|
||||
maxLength: 16,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "OriginalTitle",
|
||||
table: "Series",
|
||||
type: "TEXT",
|
||||
maxLength: 512,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "OriginalTitle",
|
||||
table: "Movies",
|
||||
type: "TEXT",
|
||||
maxLength: 512,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Translations",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
MovieId = table.Column<Guid>(type: "TEXT", nullable: true),
|
||||
SeriesId = table.Column<Guid>(type: "TEXT", nullable: true),
|
||||
EpisodeId = table.Column<Guid>(type: "TEXT", nullable: true),
|
||||
Language = table.Column<string>(type: "TEXT", maxLength: 16, nullable: false),
|
||||
Title = table.Column<string>(type: "TEXT", maxLength: 512, nullable: true),
|
||||
Overview = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Tagline = table.Column<string>(type: "TEXT", maxLength: 512, nullable: true),
|
||||
FetchedAt = table.Column<long>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Translations", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Translations_EpisodeId_Language",
|
||||
table: "Translations",
|
||||
columns: new[] { "EpisodeId", "Language" },
|
||||
unique: true,
|
||||
filter: "\"EpisodeId\" IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Translations_MovieId_Language",
|
||||
table: "Translations",
|
||||
columns: new[] { "MovieId", "Language" },
|
||||
unique: true,
|
||||
filter: "\"MovieId\" IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Translations_SeriesId_Language",
|
||||
table: "Translations",
|
||||
columns: new[] { "SeriesId", "Language" },
|
||||
unique: true,
|
||||
filter: "\"SeriesId\" IS NOT NULL");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Translations");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Language",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "OriginalTitle",
|
||||
table: "Series");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "OriginalTitle",
|
||||
table: "Movies");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,6 +263,57 @@ namespace Flaro.Data.Migrations
|
||||
b.ToTable("GenreLinks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.ItemTranslation", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("EpisodeId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("FetchedAt")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("MovieId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Overview")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("SeriesId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Tagline")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("EpisodeId", "Language")
|
||||
.IsUnique()
|
||||
.HasFilter("\"EpisodeId\" IS NOT NULL");
|
||||
|
||||
b.HasIndex("MovieId", "Language")
|
||||
.IsUnique()
|
||||
.HasFilter("\"MovieId\" IS NOT NULL");
|
||||
|
||||
b.HasIndex("SeriesId", "Language")
|
||||
.IsUnique()
|
||||
.HasFilter("\"SeriesId\" IS NOT NULL");
|
||||
|
||||
b.ToTable("Translations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -405,6 +456,10 @@ namespace Flaro.Data.Migrations
|
||||
b.Property<bool>("MatchLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("OriginalTitle")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Overview")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@@ -509,6 +564,10 @@ namespace Flaro.Data.Migrations
|
||||
b.Property<bool>("MatchLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("OriginalTitle")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Overview")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@@ -651,6 +710,10 @@ namespace Flaro.Data.Migrations
|
||||
b.Property<bool>("IsAdministrator")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
using Flaro.Core.Library;
|
||||
using Flaro.Core.Metadata;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Flaro.Data;
|
||||
|
||||
/// <summary>Was ein Durchgang an Übersetzungen geholt hat.</summary>
|
||||
public sealed record Uebersetzungsbericht(int Filme, int Serien, int Fehlend)
|
||||
{
|
||||
public int Gesamt => Filme + Serien;
|
||||
}
|
||||
|
||||
/// <summary>Holt Titel und Beschreibungen in einer zweiten Sprache.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Warum überhaupt zweisprachig.</b> 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.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Als Sammelaktion, nicht nebenbei.</b> 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.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Was fehlt, erscheint in der Sprache des Servers</b> — 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.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class Uebersetzer(
|
||||
FlaroContext db, IMetadataProvider anbieter, TimeProvider uhr, ILogger<Uebersetzer> log)
|
||||
{
|
||||
/// <summary>Holt für alle Titel, was in dieser Sprache fehlt.</summary>
|
||||
public async Task<Uebersetzungsbericht> HolenAsync(
|
||||
string sprache, IProgress<Scanschritt>? 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);
|
||||
}
|
||||
|
||||
/// <summary>Wie weit eine Sprache abgedeckt ist — für den Hinweis beim Umstellen.</summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user