Blog/Models/RvrbSnapshot.cs 4.2 K · 132 lines · raw · history

1 using System.ComponentModel;
2
3 namespace Blog.Models;
4
5 /// <summary>
6 /// What <c>Rvrb.Stats.snapshot/0</c> answers with: the rvrb bot's room as it is right now, and
7 /// what it has played since the plays table started filling up. Decoded from the Erlang term in
8 /// <see cref="Services.RvrbSnapshotReader"/>.
9 /// </summary>
10 public sealed record RvrbSnapshot(
11 DateTimeOffset GeneratedAt,
12 RvrbLive? Live,
13 RvrbTotals Totals,
14 IReadOnlyList<RvrbDay> PlaysPerDay,
15 IReadOnlyList<RvrbDj> TopDjs,
16 IReadOnlyList<RvrbTrack> TopTracks,
17 IReadOnlyList<RvrbArtist> TopArtists,
18 IReadOnlyList<RvrbPlay> RecentPlays);
19
20 /// <summary>
21 /// The room as the bot's websocket currently sees it. Null in a snapshot means the bot is not
22 /// connected to RVRB — the historic half is still there, since it only needs the database.
23 /// </summary>
24 public sealed record RvrbLive(
25 string? ChannelId,
26 RvrbCurrentTrack? CurrentTrack,
27 int Dopes,
28 int Stars,
29 bool AutoDoped,
30 bool AutoStarred,
31 int QueuedTracks,
32 int KnownBots,
33 IReadOnlyList<RvrbDjSlot> Djs,
34 TimeSpan Lap);
35
36 /// <summary>The track playing right now.</summary>
37 public sealed record RvrbCurrentTrack(
38 string? SpotifyTrackId,
39 string? Name,
40 IReadOnlyList<string> ArtistNames,
41 IReadOnlyList<string> SpotifyArtistIds,
42 TimeSpan? Duration,
43 string? AlbumArt,
44 TimeSpan? Elapsed,
45 TimeSpan? Remaining)
46 {
47 /// <summary>
48 /// How far through the track we are, 0 to 1, or null when either half of that is unknown —
49 /// a track that arrived without a duration, or one already playing when the bot connected.
50 /// </summary>
51 public double? Progress => Duration is { TotalMilliseconds: > 0 } total && Elapsed is { } elapsed
52 ? Math.Clamp(elapsed.TotalMilliseconds / total.TotalMilliseconds, 0, 1)
53 : null;
54 }
55
56 /// <summary>One slot in the DJ queue, with the wait until that DJ next plays.</summary>
57 public sealed record RvrbDjSlot(
58 string RvrbId,
59 string? Name,
60 TimeSpan AverageTrack,
61 int PlayCount,
62 bool Measured,
63 bool Current,
64 TimeSpan Wait);
65
66 /// <summary>Room-wide counts.</summary>
67 public sealed record RvrbTotals(
68 long Plays,
69 long Votes,
70 long Dopes,
71 long Stars,
72 long Users,
73 long Djs,
74 long Tracks,
75 long Artists,
76 DateTimeOffset? FirstPlayAt,
77 DateTimeOffset? LastPlayAt);
78
79 /// <summary>Plays on one day. Days nothing was played on are present, with a count of zero.</summary>
80 public sealed record RvrbDay(DateOnly Date, int Plays);
81
82 /// <summary>A DJ on the leaderboard, and what their plays earned.</summary>
83 public sealed record RvrbDj(string Name, string? UserName, int Plays, int Dopes, int Stars, int Score);
84
85 /// <summary>A track on the leaderboard.</summary>
86 public sealed record RvrbTrack(
87 string TrackName,
88 IReadOnlyList<string> ArtistNames,
89 string? SpotifyTrackId,
90 IReadOnlyList<string> SpotifyArtistIds,
91 int Plays,
92 int Dopes,
93 int Stars,
94 int Score);
95
96 /// <summary>An artist on the leaderboard.</summary>
97 public sealed record RvrbArtist(
98 string ArtistName,
99 string? SpotifyArtistId,
100 int Plays,
101 int Dopes,
102 int Stars,
103 int Score);
104
105 /// <summary>One played track in the recent history.</summary>
106 public sealed record RvrbPlay(
107 DateTimeOffset PlayedAt,
108 string? Dj,
109 string TrackName,
110 IReadOnlyList<string> ArtistNames,
111 string? SpotifyTrackId,
112 IReadOnlyList<string> SpotifyArtistIds,
113 TimeSpan? Duration,
114 int Dopes,
115 int Stars,
116 int Score);
117
118 /// <summary>
119 /// The result of asking the bot for a snapshot: either one, or why there isn't one. A bot that is
120 /// down is the normal thing a status page is there to report, so it is a value here rather than an
121 /// exception the page has to catch.
122 /// </summary>
123 // Nothing here is ever mutated after it is built, and saying so is what lets HybridCache hand the
124 // same instance to every request instead of serializing a copy per reader.
125 [ImmutableObject(true)]
126 public sealed record RvrbStatus(DateTimeOffset FetchedAt, RvrbSnapshot? Snapshot, string? Error)
127 {
128 public static RvrbStatus Reachable(RvrbSnapshot snapshot) =>
129 new(DateTimeOffset.UtcNow, snapshot, null);
130
131 public static RvrbStatus Unreachable(string error) => new(DateTimeOffset.UtcNow, null, error);
132 }