| 1 |
using System.Globalization; |
| 2 |
using BeamSharp.Terms; |
| 3 |
using Blog.Models; |
| 4 |
|
| 5 |
namespace Blog.Services; |
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
internal static class RvrbSnapshotReader |
| 18 |
{ |
| 19 |
public static RvrbSnapshot Read(ErlTerm term) |
| 20 |
{ |
| 21 |
var root = AsMap(term) ?? throw new FormatException($"expected a map, got {term}"); |
| 22 |
|
| 23 |
return new RvrbSnapshot( |
| 24 |
GeneratedAt: Time(root.Get("generated_at")) ?? DateTimeOffset.UtcNow, |
| 25 |
Live: ReadLive(AsMap(root.Get("live"))), |
| 26 |
Totals: ReadTotals(AsMap(root.Get("totals"))), |
| 27 |
PlaysPerDay: ReadList(root.Get("plays_per_day"), ReadDay), |
| 28 |
TopDjs: ReadList(root.Get("top_djs"), ReadDj), |
| 29 |
TopTracks: ReadList(root.Get("top_tracks"), ReadTrack), |
| 30 |
TopArtists: ReadList(root.Get("top_artists"), ReadArtist), |
| 31 |
RecentPlays: ReadList(root.Get("recent_plays"), ReadPlay)); |
| 32 |
} |
| 33 |
|
| 34 |
private static RvrbLive? ReadLive(ErlMap? live) => live is null |
| 35 |
? null |
| 36 |
: new RvrbLive( |
| 37 |
ChannelId: Text(live.Get("channel_id")), |
| 38 |
CurrentTrack: ReadCurrentTrack(AsMap(live.Get("current_track"))), |
| 39 |
Dopes: (int)Int(live.Get("dopes")), |
| 40 |
Stars: (int)Int(live.Get("stars")), |
| 41 |
AutoDoped: Bool(live.Get("auto_doped")), |
| 42 |
AutoStarred: Bool(live.Get("auto_starred")), |
| 43 |
QueuedTracks: (int)Int(live.Get("queued_tracks")), |
| 44 |
KnownBots: (int)Int(live.Get("known_bots")), |
| 45 |
Djs: ReadList(live.Get("djs"), ReadDjSlot), |
| 46 |
Lap: Duration(live.Get("lap_ms")) ?? TimeSpan.Zero); |
| 47 |
|
| 48 |
private static RvrbCurrentTrack? ReadCurrentTrack(ErlMap? track) => track is null |
| 49 |
? null |
| 50 |
: new RvrbCurrentTrack( |
| 51 |
SpotifyTrackId: Text(track.Get("spotify_track_id")), |
| 52 |
Name: Text(track.Get("name")), |
| 53 |
ArtistNames: ReadStrings(track.Get("artist_names")), |
| 54 |
Duration: Duration(track.Get("duration_ms")), |
| 55 |
AlbumArt: Text(track.Get("album_art")), |
| 56 |
Elapsed: Duration(track.Get("elapsed_ms")), |
| 57 |
Remaining: Duration(track.Get("remaining_ms"))); |
| 58 |
|
| 59 |
private static RvrbDjSlot ReadDjSlot(ErlMap dj) => new( |
| 60 |
RvrbId: Text(dj.Get("rvrb_id")) ?? "", |
| 61 |
Name: Text(dj.Get("name")), |
| 62 |
AverageTrack: Duration(dj.Get("avg_track_ms")) ?? TimeSpan.Zero, |
| 63 |
PlayCount: (int)Int(dj.Get("play_count")), |
| 64 |
Measured: Bool(dj.Get("measured")), |
| 65 |
Current: Bool(dj.Get("current")), |
| 66 |
Wait: Duration(dj.Get("wait_ms")) ?? TimeSpan.Zero); |
| 67 |
|
| 68 |
private static RvrbTotals ReadTotals(ErlMap? totals) => new( |
| 69 |
Plays: Int(totals?.Get("plays")), |
| 70 |
Votes: Int(totals?.Get("votes")), |
| 71 |
Dopes: Int(totals?.Get("dopes")), |
| 72 |
Stars: Int(totals?.Get("stars")), |
| 73 |
Users: Int(totals?.Get("users")), |
| 74 |
Djs: Int(totals?.Get("djs")), |
| 75 |
Tracks: Int(totals?.Get("tracks")), |
| 76 |
Artists: Int(totals?.Get("artists")), |
| 77 |
FirstPlayAt: Time(totals?.Get("first_play_at")), |
| 78 |
LastPlayAt: Time(totals?.Get("last_play_at"))); |
| 79 |
|
| 80 |
private static RvrbDay ReadDay(ErlMap day) => new( |
| 81 |
Date: Text(day.Get("date")) is { } date |
| 82 |
? DateOnly.ParseExact(date, "yyyy-MM-dd", CultureInfo.InvariantCulture) |
| 83 |
: default, |
| 84 |
Plays: (int)Int(day.Get("plays"))); |
| 85 |
|
| 86 |
private static RvrbDj ReadDj(ErlMap dj) => new( |
| 87 |
Name: Text(dj.Get("name")) ?? "someone", |
| 88 |
UserName: Text(dj.Get("user_name")), |
| 89 |
Plays: (int)Int(dj.Get("plays")), |
| 90 |
Dopes: (int)Int(dj.Get("dopes")), |
| 91 |
Stars: (int)Int(dj.Get("stars")), |
| 92 |
Score: (int)Int(dj.Get("score"))); |
| 93 |
|
| 94 |
private static RvrbTrack ReadTrack(ErlMap track) => new( |
| 95 |
TrackName: Text(track.Get("track_name")) ?? "unknown track", |
| 96 |
ArtistNames: ReadStrings(track.Get("artist_names")), |
| 97 |
Plays: (int)Int(track.Get("plays")), |
| 98 |
Dopes: (int)Int(track.Get("dopes")), |
| 99 |
Stars: (int)Int(track.Get("stars")), |
| 100 |
Score: (int)Int(track.Get("score"))); |
| 101 |
|
| 102 |
private static RvrbArtist ReadArtist(ErlMap artist) => new( |
| 103 |
ArtistName: Text(artist.Get("artist_name")) ?? "unknown artist", |
| 104 |
Plays: (int)Int(artist.Get("plays")), |
| 105 |
Dopes: (int)Int(artist.Get("dopes")), |
| 106 |
Stars: (int)Int(artist.Get("stars")), |
| 107 |
Score: (int)Int(artist.Get("score"))); |
| 108 |
|
| 109 |
private static RvrbPlay ReadPlay(ErlMap play) => new( |
| 110 |
PlayedAt: Time(play.Get("played_at")) ?? default, |
| 111 |
Dj: Text(play.Get("dj")), |
| 112 |
TrackName: Text(play.Get("track_name")) ?? "unknown track", |
| 113 |
ArtistNames: ReadStrings(play.Get("artist_names")), |
| 114 |
Duration: Duration(play.Get("duration_ms")), |
| 115 |
Dopes: (int)Int(play.Get("dopes")), |
| 116 |
Stars: (int)Int(play.Get("stars")), |
| 117 |
Score: (int)Int(play.Get("score"))); |
| 118 |
|
| 119 |
|
| 120 |
|
| 121 |
|
| 122 |
|
| 123 |
private static ErlMap? AsMap(ErlTerm? term) => term as ErlMap; |
| 124 |
|
| 125 |
private static string? Text(ErlTerm? term) => term switch |
| 126 |
{ |
| 127 |
ErlBinary binary => binary.AsString(), |
| 128 |
ErlAtom { Name: "nil" or "undefined" } => null, |
| 129 |
ErlAtom atom => atom.Name, |
| 130 |
_ => null |
| 131 |
}; |
| 132 |
|
| 133 |
private static long Int(ErlTerm? term) => term is ErlInt value ? (long)value.Value : 0; |
| 134 |
|
| 135 |
private static bool Bool(ErlTerm? term) => term is ErlAtom { Name: "true" }; |
| 136 |
|
| 137 |
private static TimeSpan? Duration(ErlTerm? term) => |
| 138 |
term is ErlInt ms ? TimeSpan.FromMilliseconds((double)ms.Value) : null; |
| 139 |
|
| 140 |
private static DateTimeOffset? Time(ErlTerm? term) => |
| 141 |
Text(term) is { } text && |
| 142 |
DateTimeOffset.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var at) |
| 143 |
? at.ToUniversalTime() |
| 144 |
: null; |
| 145 |
|
| 146 |
private static IReadOnlyList<string> ReadStrings(ErlTerm? term) => |
| 147 |
term is ErlList list |
| 148 |
? list.ToArray().Select(item => Text(item) ?? "").ToArray() |
| 149 |
: []; |
| 150 |
|
| 151 |
private static IReadOnlyList<T> ReadList<T>(ErlTerm? term, Func<ErlMap, T> read) => |
| 152 |
term is ErlList list |
| 153 |
? list.ToArray().OfType<ErlMap>().Select(read).ToArray() |
| 154 |
: []; |
| 155 |
} |