more DI for consumer factories, caching play counter, service extensions
This commit is contained in:
parent
975ee772dd
commit
e41d525b0b
@ -25,38 +25,45 @@ namespace Selector.CLI
|
|||||||
private readonly ILogger<LocalWatcherService> Logger;
|
private readonly ILogger<LocalWatcherService> Logger;
|
||||||
private readonly ILoggerFactory LoggerFactory;
|
private readonly ILoggerFactory LoggerFactory;
|
||||||
private readonly IServiceProvider ServiceProvider;
|
private readonly IServiceProvider ServiceProvider;
|
||||||
private readonly RootOptions Config;
|
|
||||||
private readonly IWatcherFactory WatcherFactory;
|
private readonly IWatcherFactory WatcherFactory;
|
||||||
private readonly IWatcherCollectionFactory WatcherCollectionFactory;
|
private readonly IWatcherCollectionFactory WatcherCollectionFactory;
|
||||||
private readonly IRefreshTokenFactoryProvider SpotifyFactory;
|
private readonly IRefreshTokenFactoryProvider SpotifyFactory;
|
||||||
private readonly LastAuth LastAuth;
|
|
||||||
|
|
||||||
private readonly IDatabaseAsync Cache;
|
private readonly IAudioFeatureInjectorFactory AudioFeatureInjectorFactory;
|
||||||
private readonly ISubscriber Subscriber;
|
private readonly IPlayCounterFactory PlayCounterFactory;
|
||||||
|
|
||||||
|
private readonly IPublisherFactory PublisherFactory;
|
||||||
|
private readonly ICacheWriterFactory CacheWriterFactory;
|
||||||
private Dictionary<string, IWatcherCollection> Watchers { get; set; } = new();
|
private Dictionary<string, IWatcherCollection> Watchers { get; set; } = new();
|
||||||
|
|
||||||
public DbWatcherService(
|
public DbWatcherService(
|
||||||
IWatcherFactory watcherFactory,
|
IWatcherFactory watcherFactory,
|
||||||
IWatcherCollectionFactory watcherCollectionFactory,
|
IWatcherCollectionFactory watcherCollectionFactory,
|
||||||
IRefreshTokenFactoryProvider spotifyFactory,
|
IRefreshTokenFactoryProvider spotifyFactory,
|
||||||
|
|
||||||
|
IAudioFeatureInjectorFactory audioFeatureInjectorFactory,
|
||||||
|
IPlayCounterFactory playCounterFactory,
|
||||||
|
|
||||||
|
IPublisherFactory publisherFactory,
|
||||||
|
ICacheWriterFactory cacheWriterFactory,
|
||||||
|
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider
|
||||||
IOptions<RootOptions> config,
|
|
||||||
LastAuth lastAuth = null,
|
|
||||||
IDatabaseAsync cache = null,
|
|
||||||
ISubscriber subscriber = null
|
|
||||||
) {
|
) {
|
||||||
Logger = loggerFactory.CreateLogger<LocalWatcherService>();
|
Logger = loggerFactory.CreateLogger<LocalWatcherService>();
|
||||||
LoggerFactory = loggerFactory;
|
LoggerFactory = loggerFactory;
|
||||||
Config = config.Value;
|
ServiceProvider = serviceProvider;
|
||||||
|
|
||||||
WatcherFactory = watcherFactory;
|
WatcherFactory = watcherFactory;
|
||||||
WatcherCollectionFactory = watcherCollectionFactory;
|
WatcherCollectionFactory = watcherCollectionFactory;
|
||||||
SpotifyFactory = spotifyFactory;
|
SpotifyFactory = spotifyFactory;
|
||||||
LastAuth = lastAuth;
|
|
||||||
ServiceProvider = serviceProvider;
|
AudioFeatureInjectorFactory = audioFeatureInjectorFactory;
|
||||||
Cache = cache;
|
PlayCounterFactory = playCounterFactory;
|
||||||
Subscriber = subscriber;
|
|
||||||
|
PublisherFactory = publisherFactory;
|
||||||
|
CacheWriterFactory = cacheWriterFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
@ -99,26 +106,13 @@ namespace Selector.CLI
|
|||||||
case WatcherType.Player:
|
case WatcherType.Player:
|
||||||
watcher = await WatcherFactory.Get<PlayerWatcher>(spotifyFactory, id: dbWatcher.UserId, pollPeriod: PollPeriod);
|
watcher = await WatcherFactory.Get<PlayerWatcher>(spotifyFactory, id: dbWatcher.UserId, pollPeriod: PollPeriod);
|
||||||
|
|
||||||
var featureInjector = new AudioFeatureInjectorFactory(LoggerFactory);
|
consumers.Add(await AudioFeatureInjectorFactory.Get(spotifyFactory));
|
||||||
consumers.Add(await featureInjector.Get(spotifyFactory));
|
consumers.Add(await CacheWriterFactory.Get());
|
||||||
|
consumers.Add(await PublisherFactory.Get());
|
||||||
var featureInjectorCache = new CachingAudioFeatureInjectorFactory(LoggerFactory, Cache);
|
|
||||||
consumers.Add(await featureInjectorCache.Get(spotifyFactory));
|
|
||||||
|
|
||||||
var cacheWriter = new CacheWriterFactory(Cache, LoggerFactory);
|
|
||||||
consumers.Add(await cacheWriter.Get());
|
|
||||||
|
|
||||||
var pub = new PublisherFactory(Subscriber, LoggerFactory);
|
|
||||||
consumers.Add(await pub.Get());
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(dbWatcher.User.LastFmUsername))
|
if (!string.IsNullOrWhiteSpace(dbWatcher.User.LastFmUsername))
|
||||||
{
|
{
|
||||||
if (LastAuth is null) throw new ArgumentNullException("No Last Auth Injected");
|
consumers.Add(await PlayCounterFactory.Get(creds: new() { Username = dbWatcher.User.LastFmUsername }));
|
||||||
|
|
||||||
var client = new LastfmClient(LastAuth);
|
|
||||||
|
|
||||||
var playCount = new PlayCounterFactory(LoggerFactory, client: client, creds: new() { Username = dbWatcher.User.LastFmUsername });
|
|
||||||
consumers.Add(await playCount.Get());
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -4,15 +4,13 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using IF.Lastfm.Core.Api;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
using Selector.Cache;
|
using Selector.Cache;
|
||||||
using StackExchange.Redis;
|
|
||||||
|
|
||||||
namespace Selector.CLI
|
namespace Selector.CLI
|
||||||
{
|
{
|
||||||
@ -21,15 +19,12 @@ namespace Selector.CLI
|
|||||||
private const string ConfigInstanceKey = "localconfig";
|
private const string ConfigInstanceKey = "localconfig";
|
||||||
|
|
||||||
private readonly ILogger<LocalWatcherService> Logger;
|
private readonly ILogger<LocalWatcherService> Logger;
|
||||||
private readonly ILoggerFactory LoggerFactory;
|
|
||||||
private readonly RootOptions Config;
|
private readonly RootOptions Config;
|
||||||
private readonly IWatcherFactory WatcherFactory;
|
private readonly IWatcherFactory WatcherFactory;
|
||||||
private readonly IWatcherCollectionFactory WatcherCollectionFactory;
|
private readonly IWatcherCollectionFactory WatcherCollectionFactory;
|
||||||
private readonly IRefreshTokenFactoryProvider SpotifyFactory;
|
private readonly IRefreshTokenFactoryProvider SpotifyFactory;
|
||||||
private readonly LastAuth LastAuth;
|
|
||||||
|
|
||||||
private readonly IDatabaseAsync Cache;
|
private readonly IServiceProvider ServiceProvider;
|
||||||
private readonly ISubscriber Subscriber;
|
|
||||||
|
|
||||||
private Dictionary<string, IWatcherCollection> Watchers { get; set; } = new();
|
private Dictionary<string, IWatcherCollection> Watchers { get; set; } = new();
|
||||||
|
|
||||||
@ -37,21 +32,20 @@ namespace Selector.CLI
|
|||||||
IWatcherFactory watcherFactory,
|
IWatcherFactory watcherFactory,
|
||||||
IWatcherCollectionFactory watcherCollectionFactory,
|
IWatcherCollectionFactory watcherCollectionFactory,
|
||||||
IRefreshTokenFactoryProvider spotifyFactory,
|
IRefreshTokenFactoryProvider spotifyFactory,
|
||||||
ILoggerFactory loggerFactory,
|
|
||||||
IOptions<RootOptions> config,
|
IServiceProvider serviceProvider,
|
||||||
LastAuth lastAuth = null,
|
|
||||||
IDatabaseAsync cache = null,
|
ILogger<LocalWatcherService> logger,
|
||||||
ISubscriber subscriber = null
|
IOptions<RootOptions> config
|
||||||
) {
|
) {
|
||||||
Logger = loggerFactory.CreateLogger<LocalWatcherService>();
|
Logger = logger;
|
||||||
LoggerFactory = loggerFactory;
|
|
||||||
Config = config.Value;
|
Config = config.Value;
|
||||||
|
|
||||||
WatcherFactory = watcherFactory;
|
WatcherFactory = watcherFactory;
|
||||||
WatcherCollectionFactory = watcherCollectionFactory;
|
WatcherCollectionFactory = watcherCollectionFactory;
|
||||||
SpotifyFactory = spotifyFactory;
|
SpotifyFactory = spotifyFactory;
|
||||||
LastAuth = lastAuth;
|
|
||||||
Cache = cache;
|
ServiceProvider = serviceProvider;
|
||||||
Subscriber = subscriber;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
@ -111,34 +105,25 @@ namespace Selector.CLI
|
|||||||
switch(consumer)
|
switch(consumer)
|
||||||
{
|
{
|
||||||
case Consumers.AudioFeatures:
|
case Consumers.AudioFeatures:
|
||||||
var featureInjector = new AudioFeatureInjectorFactory(LoggerFactory);
|
consumers.Add(await ServiceProvider.GetService<AudioFeatureInjectorFactory>().Get(spotifyFactory));
|
||||||
consumers.Add(await featureInjector.Get(spotifyFactory));
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Consumers.AudioFeaturesCache:
|
case Consumers.AudioFeaturesCache:
|
||||||
var featureInjectorCache = new CachingAudioFeatureInjectorFactory(LoggerFactory, Cache);
|
consumers.Add(await ServiceProvider.GetService<CachingAudioFeatureInjectorFactory>().Get(spotifyFactory));
|
||||||
consumers.Add(await featureInjectorCache.Get(spotifyFactory));
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Consumers.CacheWriter:
|
case Consumers.CacheWriter:
|
||||||
var cacheWriter = new CacheWriterFactory(Cache, LoggerFactory);
|
consumers.Add(await ServiceProvider.GetService<CacheWriterFactory>().Get());
|
||||||
consumers.Add(await cacheWriter.Get());
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Consumers.Publisher:
|
case Consumers.Publisher:
|
||||||
var pub = new PublisherFactory(Subscriber, LoggerFactory);
|
consumers.Add(await ServiceProvider.GetService<PublisherFactory>().Get());
|
||||||
consumers.Add(await pub.Get());
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Consumers.PlayCounter:
|
case Consumers.PlayCounter:
|
||||||
if(!string.IsNullOrWhiteSpace(watcherOption.LastFmUsername))
|
if(!string.IsNullOrWhiteSpace(watcherOption.LastFmUsername))
|
||||||
{
|
{
|
||||||
if(LastAuth is null) throw new ArgumentNullException("No Last Auth Injected");
|
consumers.Add(await ServiceProvider.GetService<PlayCounterCachingFactory>().Get(creds: new() { Username = watcherOption.LastFmUsername }));
|
||||||
|
|
||||||
var client = new LastfmClient(LastAuth);
|
|
||||||
|
|
||||||
var playCount = new PlayCounterFactory(LoggerFactory, client: client, creds: new(){ Username = watcherOption.LastFmUsername });
|
|
||||||
consumers.Add(await playCount.Get());
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -22,7 +22,7 @@ namespace Selector.CLI
|
|||||||
public static string FormatKeys(string[] args) => string.Join(":", args);
|
public static string FormatKeys(string[] args) => string.Join(":", args);
|
||||||
}
|
}
|
||||||
|
|
||||||
class RootOptions
|
public class RootOptions
|
||||||
{
|
{
|
||||||
public const string Key = "Selector";
|
public const string Key = "Selector";
|
||||||
|
|
||||||
@ -42,12 +42,12 @@ namespace Selector.CLI
|
|||||||
public EqualityChecker Equality { get; set; } = EqualityChecker.Uri;
|
public EqualityChecker Equality { get; set; } = EqualityChecker.Uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum EqualityChecker
|
public enum EqualityChecker
|
||||||
{
|
{
|
||||||
Uri, String
|
Uri, String
|
||||||
}
|
}
|
||||||
|
|
||||||
class WatcherOptions
|
public class WatcherOptions
|
||||||
{
|
{
|
||||||
public const string Key = "Watcher";
|
public const string Key = "Watcher";
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ namespace Selector.CLI
|
|||||||
public List<WatcherInstanceOptions> Instances { get; set; } = new();
|
public List<WatcherInstanceOptions> Instances { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
class WatcherInstanceOptions
|
public class WatcherInstanceOptions
|
||||||
{
|
{
|
||||||
public const string Key = "Instances";
|
public const string Key = "Instances";
|
||||||
|
|
||||||
@ -73,19 +73,19 @@ namespace Selector.CLI
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Consumers
|
public enum Consumers
|
||||||
{
|
{
|
||||||
AudioFeatures, AudioFeaturesCache, CacheWriter, Publisher, PlayCounter
|
AudioFeatures, AudioFeaturesCache, CacheWriter, Publisher, PlayCounter
|
||||||
}
|
}
|
||||||
|
|
||||||
class DatabaseOptions {
|
public class DatabaseOptions {
|
||||||
public const string Key = "Database";
|
public const string Key = "Database";
|
||||||
|
|
||||||
public bool Enabled { get; set; } = false;
|
public bool Enabled { get; set; } = false;
|
||||||
public string ConnectionString { get; set; }
|
public string ConnectionString { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
class RedisOptions
|
public class RedisOptions
|
||||||
{
|
{
|
||||||
public const string Key = "Redis";
|
public const string Key = "Redis";
|
||||||
|
|
||||||
|
@ -11,12 +11,12 @@ using NLog.Extensions.Logging;
|
|||||||
|
|
||||||
using Selector.Model;
|
using Selector.Model;
|
||||||
using Selector.Cache;
|
using Selector.Cache;
|
||||||
|
using Selector.Cache.Extensions;
|
||||||
using IF.Lastfm.Core.Api;
|
using IF.Lastfm.Core.Api;
|
||||||
using StackExchange.Redis;
|
|
||||||
|
|
||||||
namespace Selector.CLI
|
namespace Selector.CLI
|
||||||
{
|
{
|
||||||
class Program
|
public static class Program
|
||||||
{
|
{
|
||||||
public static async Task Main(string[] args)
|
public static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
@ -55,7 +55,8 @@ namespace Selector.CLI
|
|||||||
Console.WriteLine("> Adding Last.fm credentials...");
|
Console.WriteLine("> Adding Last.fm credentials...");
|
||||||
|
|
||||||
var lastAuth = new LastAuth(config.LastfmClient, config.LastfmSecret);
|
var lastAuth = new LastAuth(config.LastfmClient, config.LastfmSecret);
|
||||||
services.AddSingleton<LastAuth>(lastAuth);
|
services.AddSingleton(lastAuth);
|
||||||
|
services.AddTransient(sp => new LastfmClient(sp.GetService<LastAuth>()));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -74,25 +75,6 @@ namespace Selector.CLI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ConfigureRedis(RootOptions config, IServiceCollection services)
|
|
||||||
{
|
|
||||||
if (config.RedisOptions.Enabled)
|
|
||||||
{
|
|
||||||
Console.WriteLine("> Configuring Redis...");
|
|
||||||
|
|
||||||
if(string.IsNullOrWhiteSpace(config.RedisOptions.ConnectionString))
|
|
||||||
{
|
|
||||||
Console.WriteLine("> No Redis configuration string provided, exiting...");
|
|
||||||
Environment.Exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
var connMulti = ConnectionMultiplexer.Connect(config.RedisOptions.ConnectionString);
|
|
||||||
services.AddSingleton(connMulti);
|
|
||||||
services.AddTransient<IDatabaseAsync>(services => services.GetService<ConnectionMultiplexer>().GetDatabase());
|
|
||||||
services.AddTransient<ISubscriber>(services => services.GetService<ConnectionMultiplexer>().GetSubscriber());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ConfigureEqual(RootOptions config, IServiceCollection services)
|
public static void ConfigureEqual(RootOptions config, IServiceCollection services)
|
||||||
{
|
{
|
||||||
switch (config.Equality)
|
switch (config.Equality)
|
||||||
@ -119,8 +101,9 @@ namespace Selector.CLI
|
|||||||
|
|
||||||
Console.WriteLine("> Adding Services...");
|
Console.WriteLine("> Adding Services...");
|
||||||
// SERVICES
|
// SERVICES
|
||||||
|
services.AddCachingConsumerFactories();
|
||||||
|
|
||||||
services.AddSingleton<IWatcherFactory, WatcherFactory>();
|
services.AddSingleton<IWatcherFactory, WatcherFactory>();
|
||||||
services.AddSingleton<IAudioFeatureInjectorFactory, AudioFeatureInjectorFactory>();
|
|
||||||
services.AddSingleton<IWatcherCollectionFactory, WatcherCollectionFactory>();
|
services.AddSingleton<IWatcherCollectionFactory, WatcherCollectionFactory>();
|
||||||
// For generating spotify clients
|
// For generating spotify clients
|
||||||
//services.AddSingleton<IRefreshTokenFactoryProvider, RefreshTokenFactoryProvider>();
|
//services.AddSingleton<IRefreshTokenFactoryProvider, RefreshTokenFactoryProvider>();
|
||||||
@ -128,7 +111,10 @@ namespace Selector.CLI
|
|||||||
|
|
||||||
ConfigureLastFm(config, services);
|
ConfigureLastFm(config, services);
|
||||||
ConfigureDb(config, services);
|
ConfigureDb(config, services);
|
||||||
ConfigureRedis(config, services);
|
|
||||||
|
if (config.RedisOptions.Enabled)
|
||||||
|
services.AddRedisServices(config.RedisOptions.ConnectionString);
|
||||||
|
|
||||||
ConfigureEqual(config, services);
|
ConfigureEqual(config, services);
|
||||||
|
|
||||||
// HOSTED SERVICES
|
// HOSTED SERVICES
|
||||||
|
@ -9,12 +9,7 @@ using StackExchange.Redis;
|
|||||||
|
|
||||||
namespace Selector.Cache
|
namespace Selector.Cache
|
||||||
{
|
{
|
||||||
public interface ICachingAudioFeatureInjectorFactory
|
public class CachingAudioFeatureInjectorFactory: IAudioFeatureInjectorFactory {
|
||||||
{
|
|
||||||
public Task<IConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CachingAudioFeatureInjectorFactory: ICachingAudioFeatureInjectorFactory {
|
|
||||||
|
|
||||||
private readonly ILoggerFactory LoggerFactory;
|
private readonly ILoggerFactory LoggerFactory;
|
||||||
private readonly IDatabaseAsync Db;
|
private readonly IDatabaseAsync Db;
|
||||||
|
52
Selector.Cache/Consumer/Factory/PlayCounterCachingFactory.cs
Normal file
52
Selector.Cache/Consumer/Factory/PlayCounterCachingFactory.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
using StackExchange.Redis;
|
||||||
|
using IF.Lastfm.Core.Api;
|
||||||
|
|
||||||
|
namespace Selector.Cache
|
||||||
|
{
|
||||||
|
public class PlayCounterCachingFactory: IPlayCounterFactory
|
||||||
|
{
|
||||||
|
private readonly ILoggerFactory LoggerFactory;
|
||||||
|
private readonly IDatabaseAsync Cache;
|
||||||
|
private readonly LastfmClient Client;
|
||||||
|
private readonly LastFmCredentials Creds;
|
||||||
|
|
||||||
|
public PlayCounterCachingFactory(
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
IDatabaseAsync cache,
|
||||||
|
LastfmClient client = null,
|
||||||
|
LastFmCredentials creds = null)
|
||||||
|
{
|
||||||
|
LoggerFactory = loggerFactory;
|
||||||
|
Cache = cache;
|
||||||
|
Client = client;
|
||||||
|
Creds = creds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IConsumer> Get(LastfmClient fmClient = null, LastFmCredentials creds = null, IPlayerWatcher watcher = null)
|
||||||
|
{
|
||||||
|
var client = fmClient ?? Client;
|
||||||
|
|
||||||
|
if (client is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("No Last.fm client provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PlayCounterCaching(
|
||||||
|
watcher,
|
||||||
|
client.Track,
|
||||||
|
client.Album,
|
||||||
|
client.Artist,
|
||||||
|
client.User,
|
||||||
|
Cache,
|
||||||
|
credentials: creds ?? Creds,
|
||||||
|
logger: LoggerFactory.CreateLogger<PlayCounterCaching>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
Selector.Cache/Consumer/PlayCounterCaching.cs
Normal file
61
Selector.Cache/Consumer/PlayCounterCaching.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
using IF.Lastfm.Core.Api;
|
||||||
|
using StackExchange.Redis;
|
||||||
|
using SpotifyAPI.Web;
|
||||||
|
|
||||||
|
namespace Selector.Cache
|
||||||
|
{
|
||||||
|
public class PlayCounterCaching: PlayCounter
|
||||||
|
{
|
||||||
|
private readonly IDatabaseAsync Db;
|
||||||
|
public TimeSpan CacheExpiry { get; set; } = TimeSpan.FromDays(14);
|
||||||
|
|
||||||
|
public PlayCounterCaching(
|
||||||
|
IPlayerWatcher watcher,
|
||||||
|
ITrackApi trackClient,
|
||||||
|
IAlbumApi albumClient,
|
||||||
|
IArtistApi artistClient,
|
||||||
|
IUserApi userClient,
|
||||||
|
IDatabaseAsync db,
|
||||||
|
LastFmCredentials credentials = null,
|
||||||
|
ILogger<PlayCounterCaching> logger = null,
|
||||||
|
CancellationToken token = default
|
||||||
|
) : base(watcher, trackClient, albumClient, artistClient, userClient, credentials, logger, token)
|
||||||
|
{
|
||||||
|
Db = db;
|
||||||
|
|
||||||
|
NewPlayCount += CacheCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CacheCallback(object sender, PlayCount e)
|
||||||
|
{
|
||||||
|
Task.Run(() => { return AsyncCacheCallback(e); }, CancelToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AsyncCacheCallback(PlayCount e)
|
||||||
|
{
|
||||||
|
var track = e.ListeningEvent.Current.Item as FullTrack;
|
||||||
|
Logger.LogTrace($"Caching play count for [{track.DisplayString()}]");
|
||||||
|
|
||||||
|
var tasks = new Task[]
|
||||||
|
{
|
||||||
|
Db.StringSetAsync(Key.TrackPlayCount(track.Name, track.Artists[0].Name), e.Track, expiry: CacheExpiry),
|
||||||
|
Db.StringSetAsync(Key.AlbumPlayCount(track.Album.Name, track.Album.Artists[0].Name), e.Album, expiry: CacheExpiry),
|
||||||
|
Db.StringSetAsync(Key.ArtistPlayCount(track.Artists[0].Name), e.Artist, expiry: CacheExpiry),
|
||||||
|
Db.StringSetAsync(Key.UserPlayCount(e.Username), e.User, expiry: CacheExpiry),
|
||||||
|
};
|
||||||
|
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
|
||||||
|
Logger.LogDebug($"Cached audio feature for [{track.DisplayString()}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
Selector.Cache/Extensions/ServiceExtensions.cs
Normal file
37
Selector.Cache/Extensions/ServiceExtensions.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
using StackExchange.Redis;
|
||||||
|
|
||||||
|
namespace Selector.Cache.Extensions
|
||||||
|
{
|
||||||
|
public static class ServiceExtensions
|
||||||
|
{
|
||||||
|
public static void AddRedisServices(this IServiceCollection services, string connectionStr)
|
||||||
|
{
|
||||||
|
Console.WriteLine("> Configuring Redis...");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(connectionStr))
|
||||||
|
{
|
||||||
|
Console.WriteLine("> No Redis configuration string provided, exiting...");
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var connMulti = ConnectionMultiplexer.Connect(connectionStr);
|
||||||
|
services.AddSingleton(connMulti);
|
||||||
|
services.AddTransient<IDatabaseAsync>(services => services.GetService<ConnectionMultiplexer>().GetDatabase());
|
||||||
|
services.AddTransient<ISubscriber>(services => services.GetService<ConnectionMultiplexer>().GetSubscriber());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddCachingConsumerFactories(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddTransient<IAudioFeatureInjectorFactory, AudioFeatureInjectorFactory>();
|
||||||
|
services.AddTransient<IPlayCounterFactory, PlayCounterCachingFactory>();
|
||||||
|
|
||||||
|
services.AddTransient<ICacheWriterFactory, CacheWriterFactory>();
|
||||||
|
services.AddTransient<IPublisherFactory, PublisherFactory>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ namespace Selector.Cache
|
|||||||
public const string TrackName = "Track";
|
public const string TrackName = "Track";
|
||||||
public const string AlbumName = "Album";
|
public const string AlbumName = "Album";
|
||||||
public const string ArtistName = "Artist";
|
public const string ArtistName = "Artist";
|
||||||
|
public const string UserName = "User";
|
||||||
|
|
||||||
public const string AudioFeatureName = "AudioFeature";
|
public const string AudioFeatureName = "AudioFeature";
|
||||||
public const string PlayCountName = "PlayCount";
|
public const string PlayCountName = "PlayCount";
|
||||||
@ -30,6 +31,7 @@ namespace Selector.Cache
|
|||||||
public static string TrackPlayCount(string name, string artist) => Namespace(TrackName, artist, name, PlayCountName);
|
public static string TrackPlayCount(string name, string artist) => Namespace(TrackName, artist, name, PlayCountName);
|
||||||
public static string AlbumPlayCount(string name, string artist) => Namespace(AlbumName, artist, name, PlayCountName);
|
public static string AlbumPlayCount(string name, string artist) => Namespace(AlbumName, artist, name, PlayCountName);
|
||||||
public static string ArtistPlayCount(string name) => Namespace(ArtistName, name, PlayCountName);
|
public static string ArtistPlayCount(string name) => Namespace(ArtistName, name, PlayCountName);
|
||||||
|
public static string UserPlayCount(string username) => Namespace(UserName, username, PlayCountName);
|
||||||
|
|
||||||
public static string WatcherReserved(int id) => Namespace(WatcherName, id.ToString(), ReservedName);
|
public static string WatcherReserved(int id) => Namespace(WatcherName, id.ToString(), ReservedName);
|
||||||
|
|
||||||
|
30
Selector.Model/Extensions/ServiceExtensions.cs
Normal file
30
Selector.Model/Extensions/ServiceExtensions.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
using Selector.Model.Authorisation;
|
||||||
|
|
||||||
|
namespace Selector.Model.Extensions
|
||||||
|
{
|
||||||
|
public static class ServiceExtensions
|
||||||
|
{
|
||||||
|
public static void AddAuthorisationHandlers(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddAuthorization(options =>
|
||||||
|
{
|
||||||
|
options.FallbackPolicy = new AuthorizationPolicyBuilder()
|
||||||
|
.RequireAuthenticatedUser()
|
||||||
|
.Build();
|
||||||
|
});
|
||||||
|
services.AddScoped<IAuthorizationHandler, WatcherIsOwnerAuthHandler>();
|
||||||
|
services.AddSingleton<IAuthorizationHandler, WatcherIsAdminAuthHandler>();
|
||||||
|
|
||||||
|
services.AddScoped<IAuthorizationHandler, UserIsSelfAuthHandler>();
|
||||||
|
services.AddSingleton<IAuthorizationHandler, UserIsAdminAuthHandler>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,13 +13,12 @@ using Microsoft.AspNetCore.Identity;
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
using StackExchange.Redis;
|
|
||||||
|
|
||||||
using Selector.Web.Service;
|
using Selector.Web.Service;
|
||||||
using Selector.Web.Hubs;
|
using Selector.Web.Hubs;
|
||||||
using Selector.Model;
|
using Selector.Model;
|
||||||
using Selector.Model.Authorisation;
|
using Selector.Model.Extensions;
|
||||||
using Selector.Cache;
|
using Selector.Cache;
|
||||||
|
using Selector.Cache.Extensions;
|
||||||
|
|
||||||
namespace Selector.Web
|
namespace Selector.Web
|
||||||
{
|
{
|
||||||
@ -93,35 +92,10 @@ namespace Selector.Web
|
|||||||
options.SlidingExpiration = true;
|
options.SlidingExpiration = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// AUTH
|
services.AddAuthorisationHandlers();
|
||||||
services.AddAuthorization(options =>
|
|
||||||
{
|
|
||||||
options.FallbackPolicy = new AuthorizationPolicyBuilder()
|
|
||||||
.RequireAuthenticatedUser()
|
|
||||||
.Build();
|
|
||||||
});
|
|
||||||
services.AddScoped<IAuthorizationHandler, WatcherIsOwnerAuthHandler>();
|
|
||||||
services.AddSingleton<IAuthorizationHandler, WatcherIsAdminAuthHandler>();
|
|
||||||
|
|
||||||
services.AddScoped<IAuthorizationHandler, UserIsSelfAuthHandler>();
|
|
||||||
services.AddSingleton<IAuthorizationHandler, UserIsAdminAuthHandler>();
|
|
||||||
|
|
||||||
// REDIS
|
|
||||||
if (config.RedisOptions.Enabled)
|
if (config.RedisOptions.Enabled)
|
||||||
{
|
services.AddRedisServices(config.RedisOptions.ConnectionString);
|
||||||
Console.WriteLine("> Configuring Redis...");
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(config.RedisOptions.ConnectionString))
|
|
||||||
{
|
|
||||||
Console.WriteLine("> No Redis configuration string provided, exiting...");
|
|
||||||
Environment.Exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
var connMulti = ConnectionMultiplexer.Connect(config.RedisOptions.ConnectionString);
|
|
||||||
services.AddSingleton(connMulti);
|
|
||||||
services.AddTransient<IDatabaseAsync>(services => services.GetService<ConnectionMultiplexer>().GetDatabase());
|
|
||||||
services.AddTransient<ISubscriber>(services => services.GetService<ConnectionMultiplexer>().GetSubscriber());
|
|
||||||
}
|
|
||||||
|
|
||||||
services.AddSingleton<IRefreshTokenFactoryProvider, CachingRefreshTokenFactoryProvider>();
|
services.AddSingleton<IRefreshTokenFactoryProvider, CachingRefreshTokenFactoryProvider>();
|
||||||
services.AddSingleton<AudioFeaturePuller>();
|
services.AddSingleton<AudioFeaturePuller>();
|
||||||
|
@ -10,7 +10,7 @@ namespace Selector
|
|||||||
{
|
{
|
||||||
public interface IAudioFeatureInjectorFactory
|
public interface IAudioFeatureInjectorFactory
|
||||||
{
|
{
|
||||||
public Task<IConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher);
|
public Task<IConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AudioFeatureInjectorFactory: IAudioFeatureInjectorFactory {
|
public class AudioFeatureInjectorFactory: IAudioFeatureInjectorFactory {
|
||||||
|
@ -123,13 +123,19 @@ namespace Selector
|
|||||||
|
|
||||||
Logger.LogDebug($"Adding Last.fm data [{e.Id}/{e.SpotifyUsername}/{Credentials.Username}] [{track.DisplayString()}], track: {trackCount}, album: {albumCount}, artist: {artistCount}, user: {userCount}");
|
Logger.LogDebug($"Adding Last.fm data [{e.Id}/{e.SpotifyUsername}/{Credentials.Username}] [{track.DisplayString()}], track: {trackCount}, album: {albumCount}, artist: {artistCount}, user: {userCount}");
|
||||||
|
|
||||||
OnNewPlayCount(new()
|
PlayCount playCount = new()
|
||||||
{
|
{
|
||||||
Track = trackCount,
|
Track = trackCount,
|
||||||
Album = albumCount,
|
Album = albumCount,
|
||||||
Artist = artistCount,
|
Artist = artistCount,
|
||||||
User = userCount,
|
User = userCount,
|
||||||
});
|
ListeningEvent = e
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(Credentials.Username))
|
||||||
|
playCount.Username = Credentials.Username;
|
||||||
|
|
||||||
|
OnNewPlayCount(playCount);
|
||||||
}
|
}
|
||||||
else if (e.Current.Item is FullEpisode episode)
|
else if (e.Current.Item is FullEpisode episode)
|
||||||
{
|
{
|
||||||
@ -181,6 +187,8 @@ namespace Selector
|
|||||||
public int? Album { get; set; }
|
public int? Album { get; set; }
|
||||||
public int? Artist { get; set; }
|
public int? Artist { get; set; }
|
||||||
public int? User { get; set; }
|
public int? User { get; set; }
|
||||||
|
public string Username { get; set; }
|
||||||
|
public ListeningChangeEventArgs ListeningEvent { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LastFmCredentials
|
public class LastFmCredentials
|
||||||
|
16
Selector/Extensions/ServiceExtensions.cs
Normal file
16
Selector/Extensions/ServiceExtensions.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Selector.Extensions
|
||||||
|
{
|
||||||
|
public static class ServiceExtensions
|
||||||
|
{
|
||||||
|
public static void AddConsumerFactories(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddTransient<IAudioFeatureInjectorFactory, AudioFeatureInjectorFactory>();
|
||||||
|
services.AddTransient<IPlayCounterFactory, PlayCounterFactory>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
Selector/Watcher/Context/Interfaces/IWatcherContext.cs
Normal file
13
Selector/Watcher/Context/Interfaces/IWatcherContext.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Selector
|
||||||
|
{
|
||||||
|
public interface IWatcherContext
|
||||||
|
{
|
||||||
|
void AddConsumer(IConsumer consumer);
|
||||||
|
void Start();
|
||||||
|
void Stop();
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Selector
|
namespace Selector
|
||||||
{
|
{
|
||||||
public class WatcherContext: IDisposable
|
public class WatcherContext: IDisposable, IWatcherContext
|
||||||
{
|
{
|
||||||
public IWatcher Watcher { get; set; }
|
public IWatcher Watcher { get; set; }
|
||||||
private List<IConsumer> Consumers { get; set; } = new();
|
private List<IConsumer> Consumers { get; set; } = new();
|
Loading…
Reference in New Issue
Block a user