namespace Flaro.Core.Playback; /// Was ein Benutzer mit einem Titel zu tun hatte. /// /// /// /// Je Benutzer, nicht je Server. Zwei Leute im selben Haushalt stehen /// an verschiedenen Stellen desselben Films, und wessen Stand gilt, darf /// nicht davon abhängen, wer zuletzt geschaut hat. /// /// /// Der Verweis zeigt auf Film oder Folge, wie bei /// MediaFile. Eine gemeinsame Kennung mit einem Typfeld daneben wäre /// kürzer, aber die Datenbank könnte dann nicht mehr dafür einstehen, dass /// der Verweis irgendwohin zeigt. /// /// public sealed class UserItemState { public Guid Id { get; init; } = Guid.CreateVersion7(); public required Guid UserId { get; init; } public Guid? MovieId { get; init; } public Guid? EpisodeId { get; init; } /// Die Serie als Ganzes — für Favorit, Bewertung und das Häkchen. /// /// /// /// Eine Serie wird nicht angeschaut, ihre Folgen werden es — das /// bleibt richtig, und deshalb steht hier kein Fortschritt. Was hier steht, /// ist, was sich an einer Folge nicht festmachen lässt: ob die Serie ein /// Favorit ist, wie der Benutzer sie bewertet, und ob er sie für erledigt /// erklärt hat. /// /// /// Warum überhaupt eine Zeile und nicht nur gezählte Folgen. Ohne /// sie lief jede Aktion auf einer Serienkachel — Häkchen, Favorit — in eine /// 404: der Server kannte zu dieser Kennung keinen Zustand. Sichtbar war /// davon nichts; der Klick tat einfach nichts. /// /// public Guid? SeriesId { get; init; } /// Wo der Benutzer steht. /// /// /// Ab der ersten Sekunde, ohne untere Schwelle. Das ist der /// Unterschied, um dessentwillen dieser Server existiert — siehe /// . /// public TimeSpan Position { get; set; } /// Wie lang der Titel ist, so wie der Client es gemeldet hat. /// /// /// Hier abgelegt, damit „wie weit ist er" ohne einen Blick in die Dateien /// zu beantworten ist. Bei zwei Fassungen eines Films — 4K und 1080p — /// können sich die Laufzeiten um Sekunden unterscheiden; was zählt, ist /// die, gegen die gemessen wurde. /// public TimeSpan? Runtime { get; set; } public bool Watched { get; set; } public int PlayCount { get; set; } public DateTimeOffset? LastPlayedAt { get; set; } /// Aus „Weiterschauen" genommen, ohne als gesehen zu gelten. /// /// /// Der Fall, für den es sonst nichts gibt. Nach einer halben Stunde /// keine Lust mehr — der Film ist weder gesehen noch ungesehen, er soll /// nur nicht weiter oben stehen. „Als gesehen" wäre eine Lüge, „als /// ungesehen" täte nichts: die Position bliebe. /// /// Die Position bleibt auch hier erhalten. Wer doch weitermacht, fängt /// nicht von vorn an. /// public bool Dismissed { get; set; } public bool Favourite { get; set; } /// Eine eigene Bewertung von 1 bis 10, oder keine. public int? Rating { get; set; } /// Ob dieser Titel in „Weiterschauen" gehört. /// /// /// Kein zweites Urteil, sondern das festgehaltene Ergebnis des /// ersten. Geschrieben wird es ausschliesslich aus /// , bei jeder /// Änderung an dieser Zeile. /// /// Warum überhaupt eine Spalte: die Regel vergleicht die Stelle mit einem /// Anteil der Laufzeit, und diese Rechnung lässt sich nicht nach SQL /// übersetzen. Die Regel daneben ein zweites Mal als Abfrage zu schreiben /// wäre die Variante, bei der beide irgendwann auseinanderlaufen — und /// zwar unbemerkt, weil keine von beiden dabei falsch aussieht. /// public bool InProgress { get; set; } public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow; }