From 0d74257b891c003f3149e9594027fedb6e668585 Mon Sep 17 00:00:00 2001 From: andy Date: Fri, 5 Nov 2021 07:58:48 +0000 Subject: [PATCH] cache hub proxy working --- .vscode/launch.json | 6 +-- .vscode/tasks.json | 14 ++++++- Selector.Cache/Consumer/PublisherConsumer.cs | 1 + Selector.Cache/DTO.cs | 35 +++++++++++++++++- Selector.Web/Pages/Now.cs | 2 +- Selector.Web/Services/CacheHubProxy.cs | 37 +------------------ Selector.Web/Services/CacheHubProxyService.cs | 16 +++++++- .../Services/Mappings/NowPlayingMapping.cs | 21 +++++++++-- ...Factory.cs => NowPlayingMappingFactory.cs} | 23 +++++++----- Selector.Web/Services/Mappings/UserMapping.cs | 37 +++++++++++++++++++ Selector.Web/Startup.cs | 6 ++- Selector.Web/scripts/HubInterfaces.ts | 33 ++++++++++++++++- Selector.Web/scripts/now.ts | 4 +- 13 files changed, 175 insertions(+), 60 deletions(-) rename Selector.Web/Services/Mappings/{MappingFactory.cs => NowPlayingMappingFactory.cs} (51%) create mode 100644 Selector.Web/Services/Mappings/UserMapping.cs diff --git a/.vscode/launch.json b/.vscode/launch.json index f17329b..18a728b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,10 +8,10 @@ "name": "Selector.Web", "type": "coreclr", "request": "launch", - "preLaunchTask": "build", + "preLaunchTask": "buildWeb", "program": "${workspaceFolder}/Selector.Web/bin/Debug/net5.0/Selector.Web.dll", "args": [], - "cwd": "${workspaceFolder}", + "cwd": "${workspaceFolder}/Selector.Web", "stopAtEntry": false, "serverReadyAction": { "action": "openExternally", @@ -29,7 +29,7 @@ "name": "Selector.CLI", "type": "coreclr", "request": "launch", - "preLaunchTask": "build", + "preLaunchTask": "buildCLI", "program": "${workspaceFolder}/Selector.CLI/bin/Debug/net5.0/Selector.CLI.dll", "env": { "DOTNET_ENVIRONMENT": "Development" diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d4b5bd8..35640e1 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,7 +2,19 @@ "version": "2.0.0", "tasks": [ { - "label": "build", + "label": "buildWeb", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Selector.Web/Selector.Web.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "buildCLI", "command": "dotnet", "type": "process", "args": [ diff --git a/Selector.Cache/Consumer/PublisherConsumer.cs b/Selector.Cache/Consumer/PublisherConsumer.cs index 4e8c7f0..6330679 100644 --- a/Selector.Cache/Consumer/PublisherConsumer.cs +++ b/Selector.Cache/Consumer/PublisherConsumer.cs @@ -43,6 +43,7 @@ namespace Selector.Cache Logger.LogTrace($"Publishing current for [{e.Username}]"); + // TODO: currently using spotify username for cache key, use db username var receivers = await Subscriber.PublishAsync(Key.CurrentlyPlaying(e.Username), payload); Logger.LogDebug($"Published current for [{e.Username}], {receivers} receivers"); diff --git a/Selector.Cache/DTO.cs b/Selector.Cache/DTO.cs index 7338e2f..fea13f9 100644 --- a/Selector.Cache/DTO.cs +++ b/Selector.Cache/DTO.cs @@ -5,7 +5,7 @@ using SpotifyAPI.Web; namespace Selector.Cache { public class CurrentlyPlayingDTO { - public CurrentlyPlayingContext Context { get; set; } + public CurrentlyPlayingContextDTO Context { get; set; } public string Username { get; set; } public FullTrack Track { get; set; } @@ -36,5 +36,38 @@ namespace Selector.Cache { throw new ArgumentException("Unknown item item"); } } + + public override string ToString() => $"[{Username}] [{Context}]"; + } + + public class CurrentlyPlayingContextDTO + { + public Device Device { get; set; } + public string RepeatState { get; set; } + public bool ShuffleState { get; set; } + public Context Context { get; set; } + public long Timestamp { get; set; } + public int ProgressMs { get; set; } + public bool IsPlaying { get; set; } + + public string CurrentlyPlayingType { get; set; } + public Actions Actions { get; set; } + + public static implicit operator CurrentlyPlayingContextDTO(CurrentlyPlayingContext context) + { + return new CurrentlyPlayingContextDTO { + Device = context.Device, + RepeatState = context.RepeatState, + ShuffleState = context.ShuffleState, + Context = context.Context, + Timestamp = context.Timestamp, + ProgressMs = context.ProgressMs, + IsPlaying = context.IsPlaying, + CurrentlyPlayingType = context.CurrentlyPlayingType, + Actions = context.Actions + }; + } + + public override string ToString() => $"{IsPlaying}, {Device?.DisplayString()}"; } } \ No newline at end of file diff --git a/Selector.Web/Pages/Now.cs b/Selector.Web/Pages/Now.cs index b38c158..61c7442 100644 --- a/Selector.Web/Pages/Now.cs +++ b/Selector.Web/Pages/Now.cs @@ -35,7 +35,7 @@ namespace Selector.Web.Pages public void OnGet() { - HubProxy.FormMapping(MappingFactory.Get(UserManager.GetUserId(User))); + } } } diff --git a/Selector.Web/Services/CacheHubProxy.cs b/Selector.Web/Services/CacheHubProxy.cs index a6a181d..4eccf99 100644 --- a/Selector.Web/Services/CacheHubProxy.cs +++ b/Selector.Web/Services/CacheHubProxy.cs @@ -17,11 +17,11 @@ namespace Selector.Web.Service { private readonly ILogger Logger; private readonly ISubscriber Subscriber; - private readonly ServiceProvider Services; + private readonly IServiceProvider Services; public CacheHubProxy(ILogger logger, ISubscriber subscriber, - ServiceProvider services + IServiceProvider services ) { Logger = logger; @@ -35,37 +35,4 @@ namespace Selector.Web.Service mapping.ConstructMapping(Subscriber, context); } } - - // public class CacheHubProxy - // where THub: Hub - // where T: class - // { - // private readonly ILogger> Logger; - // private readonly ISubscriber Subscriber; - // private readonly IHubContext HubContext; - // private readonly List> Mappings; - - // public CacheHubProxy(ILogger> logger, - // ISubscriber subscriber, - // IHubContext hubContext, - // IEnumerable> mappings - // ) - // { - // Logger = logger; - // Subscriber = subscriber; - // HubContext = hubContext; - // Mappings = mappings.ToList(); - // } - - // public void FormMapping(ICacheHubMapping mapping) - // { - // mapping.ConstructMapping(Subscriber, HubContext); - // } - - // public void AddMapping(ICacheHubMapping mapping) - // { - // Mappings.Add(mapping); - // FormMapping(mapping); - // } - // } } \ No newline at end of file diff --git a/Selector.Web/Services/CacheHubProxyService.cs b/Selector.Web/Services/CacheHubProxyService.cs index 11aa828..9d13c86 100644 --- a/Selector.Web/Services/CacheHubProxyService.cs +++ b/Selector.Web/Services/CacheHubProxyService.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -10,19 +12,31 @@ namespace Selector.Web.Service { private readonly ILogger Logger; private readonly CacheHubProxy Proxy; + private readonly IServiceScopeFactory ScopeFactory; public CacheHubProxyService( ILogger logger, - CacheHubProxy proxy + CacheHubProxy proxy, + IServiceScopeFactory scopeFactory ) { Logger = logger; Proxy = proxy; + ScopeFactory = scopeFactory; } public Task StartAsync(CancellationToken cancellationToken) { Logger.LogInformation("Starting cache hub proxy"); + + using(var scope = ScopeFactory.CreateScope()) + { + foreach(var mapping in scope.ServiceProvider.GetServices()) + { + mapping.FormAll(); + } + } + return Task.CompletedTask; } diff --git a/Selector.Web/Services/Mappings/NowPlayingMapping.cs b/Selector.Web/Services/Mappings/NowPlayingMapping.cs index c73c7dc..c93e713 100644 --- a/Selector.Web/Services/Mappings/NowPlayingMapping.cs +++ b/Selector.Web/Services/Mappings/NowPlayingMapping.cs @@ -14,19 +14,32 @@ namespace Selector.Web.Service { public class NowPlayingMapping : ICacheHubMapping { + private readonly ILogger Logger; private readonly string UserId; + private readonly string Username; - public NowPlayingMapping(ILogger logger, string userId) + public NowPlayingMapping(ILogger logger, string userId, string username) { + Logger = logger; UserId = userId; + Username = username; } public async Task ConstructMapping(ISubscriber subscriber, IHubContext hub) { - (await subscriber.SubscribeAsync(Key.CurrentlyPlaying(UserId))).OnMessage(async message => { + var key = Key.CurrentlyPlaying(Username); + (await subscriber.SubscribeAsync(key)).OnMessage(async message => { - var deserialised = JsonSerializer.Deserialize(message.ToString()); - await hub.Clients.User(UserId).OnNewPlaying(deserialised); + try{ + var trimmedMessage = message.ToString().Substring(key.Length + 1); + var deserialised = JsonSerializer.Deserialize(trimmedMessage); + Logger.LogDebug($"Received new currently playing [{deserialised.Username}] [{deserialised.Username}]"); + await hub.Clients.User(UserId).OnNewPlaying(deserialised); + } + catch(Exception e) + { + Logger.LogError(e, $"Error parsing new currently playing [{message}]"); + } }); } } diff --git a/Selector.Web/Services/Mappings/MappingFactory.cs b/Selector.Web/Services/Mappings/NowPlayingMappingFactory.cs similarity index 51% rename from Selector.Web/Services/Mappings/MappingFactory.cs rename to Selector.Web/Services/Mappings/NowPlayingMappingFactory.cs index 9518fa1..305d504 100644 --- a/Selector.Web/Services/Mappings/MappingFactory.cs +++ b/Selector.Web/Services/Mappings/NowPlayingMappingFactory.cs @@ -1,18 +1,22 @@ using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using SpotifyAPI.Web; + +using Selector.Web.Hubs; namespace Selector.Web.Service { - public interface INowPlayingMappingFactory { - public NowPlayingMapping Get(string userId); + public interface IUserMappingFactory + where TMap : ICacheHubMapping + where THub : Hub + where T : class + { + public TMap Get(string userId, string username); } + public interface INowPlayingMappingFactory: IUserMappingFactory + { } + public class NowPlayingMappingFactory : INowPlayingMappingFactory { private readonly ILoggerFactory LoggerFactory; @@ -22,11 +26,12 @@ namespace Selector.Web.Service LoggerFactory = loggerFactory; } - public NowPlayingMapping Get(string userId) + public NowPlayingMapping Get(string userId, string username) { return new NowPlayingMapping( LoggerFactory?.CreateLogger(), - userId + userId, + username ); } } diff --git a/Selector.Web/Services/Mappings/UserMapping.cs b/Selector.Web/Services/Mappings/UserMapping.cs new file mode 100644 index 0000000..18d49e7 --- /dev/null +++ b/Selector.Web/Services/Mappings/UserMapping.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; + +using Selector.Model; + +namespace Selector.Web.Service +{ + public interface IUserMapping { + public void FormAll(); + } + + public class NowPlayingUserMapping: IUserMapping + { + private readonly ApplicationDbContext Db; + private readonly CacheHubProxy Proxy; + private readonly INowPlayingMappingFactory NowPlayingMappingFactory; + + public NowPlayingUserMapping( + ApplicationDbContext db, + CacheHubProxy proxy, + INowPlayingMappingFactory nowPlayingMappingFactory + ) + { + Db = db; + Proxy = proxy; + NowPlayingMappingFactory = nowPlayingMappingFactory; + } + + public void FormAll() + { + foreach(var user in Db.Users) + { + Proxy.FormMapping(NowPlayingMappingFactory.Get(user.Id, user.UserName)); + } + } + } +} \ No newline at end of file diff --git a/Selector.Web/Startup.cs b/Selector.Web/Startup.cs index edc70fd..576b93d 100644 --- a/Selector.Web/Startup.cs +++ b/Selector.Web/Startup.cs @@ -117,9 +117,11 @@ namespace Selector.Web services.AddTransient(services => services.GetService().GetSubscriber()); } - // HOSTED services.AddSingleton(); - services.AddSingleton(); + services.AddHostedService(); + + services.AddTransient(); + services.AddScoped(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/Selector.Web/scripts/HubInterfaces.ts b/Selector.Web/scripts/HubInterfaces.ts index 79be6cd..69bc215 100644 --- a/Selector.Web/scripts/HubInterfaces.ts +++ b/Selector.Web/scripts/HubInterfaces.ts @@ -1,6 +1,25 @@ +export interface SignalR { + nowPlayingHub: nowPlayingProxy; +} +export interface nowPlayingProxy { + client: NowPlayingHubClient; + server: NowPlayingHub; +} + +export interface NowPlayingHubClient { + OnNewPlaying: (context: CurrentlyPlayingDTO) => void; +} + export interface NowPlayingHub { - + SendNewPlaying(context: CurrentlyPlayingDTO): void; +} + +export interface CurrentlyPlayingDTO { + context: CurrentlyPlayingContextDTO; + username: string; + track: FullTrack; + episode: FullEpisode; } export interface ListeningChangeEventArgs { @@ -9,6 +28,18 @@ export interface ListeningChangeEventArgs { username: string; } +export interface CurrentlyPlayingContextDTO { + device: Device; + repeatState: string; + shuffleState: boolean; + context: Context; + timestamp: number; + progressMs: number; + isPlaying: boolean; + currentlyPlayingType: string; + actions: Actions; +} + export interface CurrentlyPlayingContext { device: Device; repeatState: string; diff --git a/Selector.Web/scripts/now.ts b/Selector.Web/scripts/now.ts index 7c213f8..049d0ea 100644 --- a/Selector.Web/scripts/now.ts +++ b/Selector.Web/scripts/now.ts @@ -1,11 +1,11 @@ import * as signalR from "@microsoft/signalr"; -import { FullTrack } from "./HubInterfaces"; +import { FullTrack, CurrentlyPlayingDTO } from "./HubInterfaces"; const connection = new signalR.HubConnectionBuilder() .withUrl("/hub") .build(); -connection.on("OnNewPlaying", context => console.log(context)); +connection.on("OnNewPlaying", (context: CurrentlyPlayingDTO) => console.log(context)); connection.start().catch(err => console.error(err));