2021-10-31 19:55:00 +00:00
|
|
|
using System;
|
2022-06-18 10:56:34 +01:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Data.SqlTypes;
|
|
|
|
using System.Diagnostics;
|
2021-11-10 23:54:28 +00:00
|
|
|
using System.Linq;
|
|
|
|
using System.Text.Json;
|
2021-10-31 19:55:00 +00:00
|
|
|
using System.Threading.Tasks;
|
|
|
|
using Microsoft.AspNetCore.SignalR;
|
2021-11-10 23:54:28 +00:00
|
|
|
using Microsoft.EntityFrameworkCore;
|
2022-06-18 10:56:34 +01:00
|
|
|
using Microsoft.Extensions.Options;
|
2021-10-31 19:55:00 +00:00
|
|
|
using Selector.Cache;
|
2021-11-10 23:54:28 +00:00
|
|
|
using Selector.Model;
|
2022-02-16 23:38:45 +00:00
|
|
|
using Selector.Model.Extensions;
|
2023-01-22 10:28:52 +00:00
|
|
|
using Selector.SignalR;
|
2022-06-02 00:53:57 +01:00
|
|
|
using Selector.Web.NowPlaying;
|
2022-06-18 10:56:34 +01:00
|
|
|
using SpotifyAPI.Web;
|
|
|
|
using StackExchange.Redis;
|
2021-10-31 19:55:00 +00:00
|
|
|
|
|
|
|
namespace Selector.Web.Hubs
|
|
|
|
{
|
2023-01-22 10:28:52 +00:00
|
|
|
public class NowPlayingHub : Hub<INowPlayingHubClient>, INowPlayingHub
|
2021-10-31 19:55:00 +00:00
|
|
|
{
|
2021-11-09 20:58:02 +00:00
|
|
|
private readonly IDatabaseAsync Cache;
|
2021-11-10 23:54:28 +00:00
|
|
|
private readonly AudioFeaturePuller AudioFeaturePuller;
|
2021-11-30 20:38:26 +00:00
|
|
|
private readonly PlayCountPuller PlayCountPuller;
|
2022-06-22 08:01:59 +01:00
|
|
|
private readonly DBPlayCountPuller DBPlayCountPuller;
|
2021-11-10 23:54:28 +00:00
|
|
|
private readonly ApplicationDbContext Db;
|
2022-02-24 23:32:02 +00:00
|
|
|
private readonly IScrobbleRepository ScrobbleRepository;
|
2021-11-09 20:58:02 +00:00
|
|
|
|
2022-06-02 00:53:57 +01:00
|
|
|
private readonly IOptions<NowPlayingOptions> nowOptions;
|
|
|
|
|
2021-11-30 20:38:26 +00:00
|
|
|
public NowPlayingHub(
|
2023-01-22 10:28:52 +00:00
|
|
|
IDatabaseAsync cache,
|
|
|
|
AudioFeaturePuller featurePuller,
|
2021-11-30 20:38:26 +00:00
|
|
|
ApplicationDbContext db,
|
2022-02-24 23:32:02 +00:00
|
|
|
IScrobbleRepository scrobbleRepository,
|
2022-06-02 00:53:57 +01:00
|
|
|
IOptions<NowPlayingOptions> options,
|
2022-06-22 08:01:59 +01:00
|
|
|
DBPlayCountPuller dbPlayCountPuller,
|
2021-11-30 20:38:26 +00:00
|
|
|
PlayCountPuller playCountPuller = null
|
|
|
|
)
|
2021-11-09 20:58:02 +00:00
|
|
|
{
|
|
|
|
Cache = cache;
|
2021-11-30 20:38:26 +00:00
|
|
|
AudioFeaturePuller = featurePuller;
|
|
|
|
PlayCountPuller = playCountPuller;
|
2022-06-22 08:01:59 +01:00
|
|
|
DBPlayCountPuller = dbPlayCountPuller;
|
2021-11-10 23:54:28 +00:00
|
|
|
Db = db;
|
2022-02-24 23:32:02 +00:00
|
|
|
ScrobbleRepository = scrobbleRepository;
|
2022-06-02 00:53:57 +01:00
|
|
|
nowOptions = options;
|
2021-11-09 20:58:02 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 22:47:49 +00:00
|
|
|
public async Task OnConnected()
|
|
|
|
{
|
|
|
|
await SendNewPlaying();
|
|
|
|
}
|
|
|
|
|
2021-11-09 20:58:02 +00:00
|
|
|
public async Task SendNewPlaying()
|
2021-10-31 19:55:00 +00:00
|
|
|
{
|
2021-11-09 20:58:02 +00:00
|
|
|
var nowPlaying = await Cache.StringGetAsync(Key.CurrentlyPlaying(Context.UserIdentifier));
|
2021-11-10 23:54:28 +00:00
|
|
|
if (nowPlaying != RedisValue.Null)
|
|
|
|
{
|
2021-12-21 22:22:52 +00:00
|
|
|
var deserialised = JsonSerializer.Deserialize(nowPlaying, JsonContext.Default.CurrentlyPlayingDTO);
|
2021-11-10 23:54:28 +00:00
|
|
|
await Clients.Caller.OnNewPlaying(deserialised);
|
|
|
|
}
|
2021-10-31 19:55:00 +00:00
|
|
|
}
|
2021-11-10 01:46:30 +00:00
|
|
|
|
2021-11-10 23:54:28 +00:00
|
|
|
public async Task SendAudioFeatures(string trackId)
|
|
|
|
{
|
2022-06-18 10:56:34 +01:00
|
|
|
if (string.IsNullOrWhiteSpace(trackId)) return;
|
|
|
|
|
2021-11-10 23:54:28 +00:00
|
|
|
var user = Db.Users
|
|
|
|
.AsNoTracking()
|
|
|
|
.Where(u => u.Id == Context.UserIdentifier)
|
2023-01-22 10:28:52 +00:00
|
|
|
.SingleOrDefault()
|
2021-11-10 23:54:28 +00:00
|
|
|
?? throw new SqlNullValueException("No user returned");
|
|
|
|
var watcher = Db.Watcher
|
|
|
|
.AsNoTracking()
|
|
|
|
.Where(w => w.UserId == Context.UserIdentifier
|
|
|
|
&& w.Type == WatcherType.Player)
|
2023-01-22 10:28:52 +00:00
|
|
|
.SingleOrDefault()
|
2021-11-10 23:54:28 +00:00
|
|
|
?? throw new SqlNullValueException($"No player watcher found for [{user.UserName}]");
|
2023-01-22 10:28:52 +00:00
|
|
|
|
2021-11-10 23:54:28 +00:00
|
|
|
var feature = await AudioFeaturePuller.Get(user.SpotifyRefreshToken, trackId);
|
|
|
|
|
|
|
|
if (feature is not null)
|
|
|
|
{
|
|
|
|
await Clients.Caller.OnNewAudioFeature(feature);
|
|
|
|
}
|
|
|
|
}
|
2021-11-30 20:38:26 +00:00
|
|
|
|
|
|
|
public async Task SendPlayCount(string track, string artist, string album, string albumArtist)
|
|
|
|
{
|
2023-01-22 10:28:52 +00:00
|
|
|
if (PlayCountPuller is not null)
|
2021-11-30 20:38:26 +00:00
|
|
|
{
|
|
|
|
var user = Db.Users
|
|
|
|
.AsNoTracking()
|
|
|
|
.Where(u => u.Id == Context.UserIdentifier)
|
|
|
|
.SingleOrDefault()
|
|
|
|
?? throw new SqlNullValueException("No user returned");
|
|
|
|
|
2022-02-24 23:32:02 +00:00
|
|
|
if (user.LastFmConnected())
|
2021-11-30 20:38:26 +00:00
|
|
|
{
|
2022-06-22 08:01:59 +01:00
|
|
|
PlayCount playCount;
|
2021-11-30 20:38:26 +00:00
|
|
|
|
2022-02-24 23:32:02 +00:00
|
|
|
if (user.ScrobbleSavingEnabled())
|
|
|
|
{
|
2022-06-22 08:01:59 +01:00
|
|
|
playCount = await DBPlayCountPuller.Get(user.UserName, track, artist, album, albumArtist);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
playCount = await PlayCountPuller.Get(user.LastFmUsername, track, artist, album, albumArtist);
|
2021-11-30 20:38:26 +00:00
|
|
|
}
|
2022-06-18 10:56:34 +01:00
|
|
|
|
|
|
|
await Clients.Caller.OnNewPlayCount(playCount);
|
2021-11-30 20:38:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-06-02 00:53:57 +01:00
|
|
|
|
|
|
|
public async Task SendFacts(string track, string artist, string album, string albumArtist)
|
|
|
|
{
|
2023-01-22 10:28:52 +00:00
|
|
|
await PlayDensityFacts(track, artist, album, albumArtist);
|
2022-06-18 10:56:34 +01:00
|
|
|
}
|
|
|
|
|
2023-01-22 10:28:52 +00:00
|
|
|
public async Task PlayDensityFacts(string track, string artist, string album, string albumArtist)
|
2022-06-18 10:56:34 +01:00
|
|
|
{
|
2023-01-22 10:28:52 +00:00
|
|
|
var user = await Db.Users.AsNoTracking().FirstOrDefaultAsync(u => u.Id == Context.UserIdentifier);
|
|
|
|
|
2022-06-02 00:53:57 +01:00
|
|
|
if (user.ScrobbleSavingEnabled())
|
|
|
|
{
|
|
|
|
var artistScrobbles = ScrobbleRepository.GetAll(userId: user.Id, artistName: artist, from: GetMaximumWindow()).ToArray();
|
2022-06-18 10:56:34 +01:00
|
|
|
var artistDensity = artistScrobbles.Density(nowOptions.Value.ArtistDensityWindow);
|
|
|
|
|
|
|
|
var tasks = new List<Task>(3);
|
2022-06-02 00:53:57 +01:00
|
|
|
|
|
|
|
if (artistDensity > nowOptions.Value.ArtistDensityThreshold)
|
|
|
|
{
|
2023-01-22 10:28:52 +00:00
|
|
|
tasks.Add(Clients.Caller.OnNewCard(new Card()
|
2022-06-02 00:53:57 +01:00
|
|
|
{
|
|
|
|
Content = $"You're on a {artist} binge! {artistDensity} plays/day recently"
|
2022-06-18 10:56:34 +01:00
|
|
|
}));
|
2022-06-02 00:53:57 +01:00
|
|
|
}
|
|
|
|
|
2022-06-18 10:56:34 +01:00
|
|
|
var albumDensity = artistScrobbles.Where(s => s.AlbumName.Equals(album, StringComparison.InvariantCultureIgnoreCase)).Density(nowOptions.Value.AlbumDensityWindow);
|
2022-06-02 00:53:57 +01:00
|
|
|
|
|
|
|
if (albumDensity > nowOptions.Value.AlbumDensityThreshold)
|
|
|
|
{
|
2023-01-22 10:28:52 +00:00
|
|
|
tasks.Add(Clients.Caller.OnNewCard(new Card()
|
2022-06-02 00:53:57 +01:00
|
|
|
{
|
|
|
|
Content = $"You're on a {album} binge! {albumDensity} plays/day recently"
|
2022-06-18 10:56:34 +01:00
|
|
|
}));
|
2022-06-02 00:53:57 +01:00
|
|
|
}
|
|
|
|
|
2022-06-18 10:56:34 +01:00
|
|
|
var trackDensity = artistScrobbles.Where(s => s.TrackName.Equals(track, StringComparison.InvariantCultureIgnoreCase)).Density(nowOptions.Value.TrackDensityWindow);
|
2022-06-02 00:53:57 +01:00
|
|
|
|
|
|
|
if (albumDensity > nowOptions.Value.TrackDensityThreshold)
|
|
|
|
{
|
2023-01-22 10:28:52 +00:00
|
|
|
tasks.Add(Clients.Caller.OnNewCard(new Card()
|
2022-06-02 00:53:57 +01:00
|
|
|
{
|
|
|
|
Content = $"You're on a {track} binge! {trackDensity} plays/day recently"
|
2022-06-18 10:56:34 +01:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2023-01-22 10:28:52 +00:00
|
|
|
if (tasks.Any())
|
2022-06-18 10:56:34 +01:00
|
|
|
{
|
|
|
|
await Task.WhenAll(tasks);
|
2022-06-02 00:53:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private DateTime GetMaximumWindow() => GetMaximumWindow(new TimeSpan[] { nowOptions.Value.ArtistDensityWindow, nowOptions.Value.AlbumDensityWindow, nowOptions.Value.TrackDensityWindow });
|
|
|
|
private DateTime GetMaximumWindow(IEnumerable<TimeSpan> windows) => windows.Select(w => DateTime.UtcNow - w).Min();
|
2021-10-31 19:55:00 +00:00
|
|
|
}
|
|
|
|
}
|