cache hub proxy working
This commit is contained in:
parent
3867ea8fad
commit
0d74257b89
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
@ -8,10 +8,10 @@
|
|||||||
"name": "Selector.Web",
|
"name": "Selector.Web",
|
||||||
"type": "coreclr",
|
"type": "coreclr",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"preLaunchTask": "build",
|
"preLaunchTask": "buildWeb",
|
||||||
"program": "${workspaceFolder}/Selector.Web/bin/Debug/net5.0/Selector.Web.dll",
|
"program": "${workspaceFolder}/Selector.Web/bin/Debug/net5.0/Selector.Web.dll",
|
||||||
"args": [],
|
"args": [],
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}/Selector.Web",
|
||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"serverReadyAction": {
|
"serverReadyAction": {
|
||||||
"action": "openExternally",
|
"action": "openExternally",
|
||||||
@ -29,7 +29,7 @@
|
|||||||
"name": "Selector.CLI",
|
"name": "Selector.CLI",
|
||||||
"type": "coreclr",
|
"type": "coreclr",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"preLaunchTask": "build",
|
"preLaunchTask": "buildCLI",
|
||||||
"program": "${workspaceFolder}/Selector.CLI/bin/Debug/net5.0/Selector.CLI.dll",
|
"program": "${workspaceFolder}/Selector.CLI/bin/Debug/net5.0/Selector.CLI.dll",
|
||||||
"env": {
|
"env": {
|
||||||
"DOTNET_ENVIRONMENT": "Development"
|
"DOTNET_ENVIRONMENT": "Development"
|
||||||
|
14
.vscode/tasks.json
vendored
14
.vscode/tasks.json
vendored
@ -2,7 +2,19 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"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",
|
"command": "dotnet",
|
||||||
"type": "process",
|
"type": "process",
|
||||||
"args": [
|
"args": [
|
||||||
|
@ -43,6 +43,7 @@ namespace Selector.Cache
|
|||||||
|
|
||||||
Logger.LogTrace($"Publishing current for [{e.Username}]");
|
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);
|
var receivers = await Subscriber.PublishAsync(Key.CurrentlyPlaying(e.Username), payload);
|
||||||
|
|
||||||
Logger.LogDebug($"Published current for [{e.Username}], {receivers} receivers");
|
Logger.LogDebug($"Published current for [{e.Username}], {receivers} receivers");
|
||||||
|
@ -5,7 +5,7 @@ using SpotifyAPI.Web;
|
|||||||
namespace Selector.Cache {
|
namespace Selector.Cache {
|
||||||
|
|
||||||
public class CurrentlyPlayingDTO {
|
public class CurrentlyPlayingDTO {
|
||||||
public CurrentlyPlayingContext Context { get; set; }
|
public CurrentlyPlayingContextDTO Context { get; set; }
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
|
|
||||||
public FullTrack Track { get; set; }
|
public FullTrack Track { get; set; }
|
||||||
@ -36,5 +36,38 @@ namespace Selector.Cache {
|
|||||||
throw new ArgumentException("Unknown item item");
|
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()}";
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -35,7 +35,7 @@ namespace Selector.Web.Pages
|
|||||||
|
|
||||||
public void OnGet()
|
public void OnGet()
|
||||||
{
|
{
|
||||||
HubProxy.FormMapping(MappingFactory.Get(UserManager.GetUserId(User)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,11 @@ namespace Selector.Web.Service
|
|||||||
{
|
{
|
||||||
private readonly ILogger<CacheHubProxy> Logger;
|
private readonly ILogger<CacheHubProxy> Logger;
|
||||||
private readonly ISubscriber Subscriber;
|
private readonly ISubscriber Subscriber;
|
||||||
private readonly ServiceProvider Services;
|
private readonly IServiceProvider Services;
|
||||||
|
|
||||||
public CacheHubProxy(ILogger<CacheHubProxy> logger,
|
public CacheHubProxy(ILogger<CacheHubProxy> logger,
|
||||||
ISubscriber subscriber,
|
ISubscriber subscriber,
|
||||||
ServiceProvider services
|
IServiceProvider services
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
@ -35,37 +35,4 @@ namespace Selector.Web.Service
|
|||||||
mapping.ConstructMapping(Subscriber, context);
|
mapping.ConstructMapping(Subscriber, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// public class CacheHubProxy<THub, T>
|
|
||||||
// where THub: Hub<T>
|
|
||||||
// where T: class
|
|
||||||
// {
|
|
||||||
// private readonly ILogger<CacheHubProxy<THub, T>> Logger;
|
|
||||||
// private readonly ISubscriber Subscriber;
|
|
||||||
// private readonly IHubContext<THub, T> HubContext;
|
|
||||||
// private readonly List<ICacheHubMapping<THub, T>> Mappings;
|
|
||||||
|
|
||||||
// public CacheHubProxy(ILogger<CacheHubProxy<THub, T>> logger,
|
|
||||||
// ISubscriber subscriber,
|
|
||||||
// IHubContext<THub, T> hubContext,
|
|
||||||
// IEnumerable<ICacheHubMapping<THub, T>> mappings
|
|
||||||
// )
|
|
||||||
// {
|
|
||||||
// Logger = logger;
|
|
||||||
// Subscriber = subscriber;
|
|
||||||
// HubContext = hubContext;
|
|
||||||
// Mappings = mappings.ToList();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public void FormMapping(ICacheHubMapping<THub, T> mapping)
|
|
||||||
// {
|
|
||||||
// mapping.ConstructMapping(Subscriber, HubContext);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public void AddMapping(ICacheHubMapping<THub, T> mapping)
|
|
||||||
// {
|
|
||||||
// Mappings.Add(mapping);
|
|
||||||
// FormMapping(mapping);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@ -10,19 +12,31 @@ namespace Selector.Web.Service
|
|||||||
{
|
{
|
||||||
private readonly ILogger<CacheHubProxyService> Logger;
|
private readonly ILogger<CacheHubProxyService> Logger;
|
||||||
private readonly CacheHubProxy Proxy;
|
private readonly CacheHubProxy Proxy;
|
||||||
|
private readonly IServiceScopeFactory ScopeFactory;
|
||||||
|
|
||||||
public CacheHubProxyService(
|
public CacheHubProxyService(
|
||||||
ILogger<CacheHubProxyService> logger,
|
ILogger<CacheHubProxyService> logger,
|
||||||
CacheHubProxy proxy
|
CacheHubProxy proxy,
|
||||||
|
IServiceScopeFactory scopeFactory
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
Proxy = proxy;
|
Proxy = proxy;
|
||||||
|
ScopeFactory = scopeFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Logger.LogInformation("Starting cache hub proxy");
|
Logger.LogInformation("Starting cache hub proxy");
|
||||||
|
|
||||||
|
using(var scope = ScopeFactory.CreateScope())
|
||||||
|
{
|
||||||
|
foreach(var mapping in scope.ServiceProvider.GetServices<IUserMapping>())
|
||||||
|
{
|
||||||
|
mapping.FormAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,19 +14,32 @@ namespace Selector.Web.Service
|
|||||||
{
|
{
|
||||||
public class NowPlayingMapping : ICacheHubMapping<NowPlayingHub, INowPlayingHubClient>
|
public class NowPlayingMapping : ICacheHubMapping<NowPlayingHub, INowPlayingHubClient>
|
||||||
{
|
{
|
||||||
|
private readonly ILogger<NowPlayingMapping> Logger;
|
||||||
private readonly string UserId;
|
private readonly string UserId;
|
||||||
|
private readonly string Username;
|
||||||
|
|
||||||
public NowPlayingMapping(ILogger<NowPlayingMapping> logger, string userId)
|
public NowPlayingMapping(ILogger<NowPlayingMapping> logger, string userId, string username)
|
||||||
{
|
{
|
||||||
|
Logger = logger;
|
||||||
UserId = userId;
|
UserId = userId;
|
||||||
|
Username = username;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ConstructMapping(ISubscriber subscriber, IHubContext<NowPlayingHub, INowPlayingHubClient> hub)
|
public async Task ConstructMapping(ISubscriber subscriber, IHubContext<NowPlayingHub, INowPlayingHubClient> 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<CurrentlyPlayingDTO>(message.ToString());
|
try{
|
||||||
|
var trimmedMessage = message.ToString().Substring(key.Length + 1);
|
||||||
|
var deserialised = JsonSerializer.Deserialize<CurrentlyPlayingDTO>(trimmedMessage);
|
||||||
|
Logger.LogDebug($"Received new currently playing [{deserialised.Username}] [{deserialised.Username}]");
|
||||||
await hub.Clients.User(UserId).OnNewPlaying(deserialised);
|
await hub.Clients.User(UserId).OnNewPlaying(deserialised);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError(e, $"Error parsing new currently playing [{message}]");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
|
||||||
using SpotifyAPI.Web;
|
using Selector.Web.Hubs;
|
||||||
|
|
||||||
namespace Selector.Web.Service
|
namespace Selector.Web.Service
|
||||||
{
|
{
|
||||||
public interface INowPlayingMappingFactory {
|
public interface IUserMappingFactory<out TMap, THub, T>
|
||||||
public NowPlayingMapping Get(string userId);
|
where TMap : ICacheHubMapping<THub, T>
|
||||||
|
where THub : Hub<T>
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
public TMap Get(string userId, string username);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface INowPlayingMappingFactory: IUserMappingFactory<NowPlayingMapping, NowPlayingHub, INowPlayingHubClient>
|
||||||
|
{ }
|
||||||
|
|
||||||
public class NowPlayingMappingFactory : INowPlayingMappingFactory {
|
public class NowPlayingMappingFactory : INowPlayingMappingFactory {
|
||||||
|
|
||||||
private readonly ILoggerFactory LoggerFactory;
|
private readonly ILoggerFactory LoggerFactory;
|
||||||
@ -22,11 +26,12 @@ namespace Selector.Web.Service
|
|||||||
LoggerFactory = loggerFactory;
|
LoggerFactory = loggerFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NowPlayingMapping Get(string userId)
|
public NowPlayingMapping Get(string userId, string username)
|
||||||
{
|
{
|
||||||
return new NowPlayingMapping(
|
return new NowPlayingMapping(
|
||||||
LoggerFactory?.CreateLogger<NowPlayingMapping>(),
|
LoggerFactory?.CreateLogger<NowPlayingMapping>(),
|
||||||
userId
|
userId,
|
||||||
|
username
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
37
Selector.Web/Services/Mappings/UserMapping.cs
Normal file
37
Selector.Web/Services/Mappings/UserMapping.cs
Normal file
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -117,9 +117,11 @@ namespace Selector.Web
|
|||||||
services.AddTransient<ISubscriber>(services => services.GetService<ConnectionMultiplexer>().GetSubscriber());
|
services.AddTransient<ISubscriber>(services => services.GetService<ConnectionMultiplexer>().GetSubscriber());
|
||||||
}
|
}
|
||||||
|
|
||||||
// HOSTED
|
|
||||||
services.AddSingleton<CacheHubProxy>();
|
services.AddSingleton<CacheHubProxy>();
|
||||||
services.AddSingleton<INowPlayingMappingFactory, NowPlayingMappingFactory>();
|
services.AddHostedService<CacheHubProxyService>();
|
||||||
|
|
||||||
|
services.AddTransient<INowPlayingMappingFactory, NowPlayingMappingFactory>();
|
||||||
|
services.AddScoped<IUserMapping, NowPlayingUserMapping>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
@ -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 {
|
export interface NowPlayingHub {
|
||||||
|
SendNewPlaying(context: CurrentlyPlayingDTO): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CurrentlyPlayingDTO {
|
||||||
|
context: CurrentlyPlayingContextDTO;
|
||||||
|
username: string;
|
||||||
|
track: FullTrack;
|
||||||
|
episode: FullEpisode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ListeningChangeEventArgs {
|
export interface ListeningChangeEventArgs {
|
||||||
@ -9,6 +28,18 @@ export interface ListeningChangeEventArgs {
|
|||||||
username: string;
|
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 {
|
export interface CurrentlyPlayingContext {
|
||||||
device: Device;
|
device: Device;
|
||||||
repeatState: string;
|
repeatState: string;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import * as signalR from "@microsoft/signalr";
|
import * as signalR from "@microsoft/signalr";
|
||||||
import { FullTrack } from "./HubInterfaces";
|
import { FullTrack, CurrentlyPlayingDTO } from "./HubInterfaces";
|
||||||
|
|
||||||
const connection = new signalR.HubConnectionBuilder()
|
const connection = new signalR.HubConnectionBuilder()
|
||||||
.withUrl("/hub")
|
.withUrl("/hub")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
connection.on("OnNewPlaying", context => console.log(context));
|
connection.on("OnNewPlaying", (context: CurrentlyPlayingDTO) => console.log(context));
|
||||||
|
|
||||||
connection.start().catch(err => console.error(err));
|
connection.start().catch(err => console.error(err));
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user