From e41d525b0b731b6f0a40a8d9e126da4b0c6ac572 Mon Sep 17 00:00:00 2001 From: andy Date: Mon, 29 Nov 2021 21:04:15 +0000 Subject: [PATCH] more DI for consumer factories, caching play counter, service extensions --- Selector.CLI/DbWatcherService.cs | 56 ++++++++--------- Selector.CLI/LocalWatcherService.cs | 49 ++++++--------- Selector.CLI/Options.cs | 14 ++--- Selector.CLI/Program.cs | 34 +++-------- .../Consumer/Factory/AudioInjectorCaching.cs | 9 +-- .../Factory/PlayCounterCachingFactory.cs | 52 ++++++++++++++++ Selector.Cache/Consumer/PlayCounterCaching.cs | 61 +++++++++++++++++++ .../Extensions/ServiceExtensions.cs | 37 +++++++++++ Selector.Cache/Key.cs | 2 + .../Extensions/ServiceExtensions.cs | 30 +++++++++ Selector.Web/Startup.cs | 34 ++--------- .../Factory/AudioFeatureInjectorFactory.cs | 2 +- Selector/Consumers/PlayCounter.cs | 12 +++- Selector/Extensions/ServiceExtensions.cs | 16 +++++ .../SpotifyExtensions.cs | 0 .../Interfaces}/IWatcherCollection.cs | 0 .../Interfaces}/IWatcherCollectionFactory.cs | 0 .../WatcherCollection.cs | 0 .../WatcherCollectionFactory.cs | 0 .../Context/Interfaces/IWatcherContext.cs | 13 ++++ .../WatcherContext.cs | 2 +- 21 files changed, 288 insertions(+), 135 deletions(-) create mode 100644 Selector.Cache/Consumer/Factory/PlayCounterCachingFactory.cs create mode 100644 Selector.Cache/Consumer/PlayCounterCaching.cs create mode 100644 Selector.Cache/Extensions/ServiceExtensions.cs create mode 100644 Selector.Model/Extensions/ServiceExtensions.cs create mode 100644 Selector/Extensions/ServiceExtensions.cs rename Selector/{Helpers => Extensions}/SpotifyExtensions.cs (100%) rename Selector/Watcher/{WatcherCollection => Collection/Interfaces}/IWatcherCollection.cs (100%) rename Selector/Watcher/{WatcherCollection => Collection/Interfaces}/IWatcherCollectionFactory.cs (100%) rename Selector/Watcher/{WatcherCollection => Collection}/WatcherCollection.cs (100%) rename Selector/Watcher/{WatcherCollection => Collection}/WatcherCollectionFactory.cs (100%) create mode 100644 Selector/Watcher/Context/Interfaces/IWatcherContext.cs rename Selector/Watcher/{WatcherCollection => Context}/WatcherContext.cs (97%) diff --git a/Selector.CLI/DbWatcherService.cs b/Selector.CLI/DbWatcherService.cs index 1288a9d..ab31f95 100644 --- a/Selector.CLI/DbWatcherService.cs +++ b/Selector.CLI/DbWatcherService.cs @@ -25,38 +25,45 @@ namespace Selector.CLI private readonly ILogger Logger; private readonly ILoggerFactory LoggerFactory; private readonly IServiceProvider ServiceProvider; - private readonly RootOptions Config; + private readonly IWatcherFactory WatcherFactory; private readonly IWatcherCollectionFactory WatcherCollectionFactory; private readonly IRefreshTokenFactoryProvider SpotifyFactory; - private readonly LastAuth LastAuth; - private readonly IDatabaseAsync Cache; - private readonly ISubscriber Subscriber; + private readonly IAudioFeatureInjectorFactory AudioFeatureInjectorFactory; + private readonly IPlayCounterFactory PlayCounterFactory; + private readonly IPublisherFactory PublisherFactory; + private readonly ICacheWriterFactory CacheWriterFactory; private Dictionary Watchers { get; set; } = new(); public DbWatcherService( IWatcherFactory watcherFactory, IWatcherCollectionFactory watcherCollectionFactory, IRefreshTokenFactoryProvider spotifyFactory, + + IAudioFeatureInjectorFactory audioFeatureInjectorFactory, + IPlayCounterFactory playCounterFactory, + + IPublisherFactory publisherFactory, + ICacheWriterFactory cacheWriterFactory, + ILoggerFactory loggerFactory, - IServiceProvider serviceProvider, - IOptions config, - LastAuth lastAuth = null, - IDatabaseAsync cache = null, - ISubscriber subscriber = null + IServiceProvider serviceProvider ) { Logger = loggerFactory.CreateLogger(); LoggerFactory = loggerFactory; - Config = config.Value; + ServiceProvider = serviceProvider; + WatcherFactory = watcherFactory; WatcherCollectionFactory = watcherCollectionFactory; SpotifyFactory = spotifyFactory; - LastAuth = lastAuth; - ServiceProvider = serviceProvider; - Cache = cache; - Subscriber = subscriber; + + AudioFeatureInjectorFactory = audioFeatureInjectorFactory; + PlayCounterFactory = playCounterFactory; + + PublisherFactory = publisherFactory; + CacheWriterFactory = cacheWriterFactory; } public async Task StartAsync(CancellationToken cancellationToken) @@ -99,26 +106,13 @@ namespace Selector.CLI case WatcherType.Player: watcher = await WatcherFactory.Get(spotifyFactory, id: dbWatcher.UserId, pollPeriod: PollPeriod); - var featureInjector = new AudioFeatureInjectorFactory(LoggerFactory); - consumers.Add(await featureInjector.Get(spotifyFactory)); - - 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()); + consumers.Add(await AudioFeatureInjectorFactory.Get(spotifyFactory)); + consumers.Add(await CacheWriterFactory.Get()); + consumers.Add(await PublisherFactory.Get()); if (!string.IsNullOrWhiteSpace(dbWatcher.User.LastFmUsername)) { - if (LastAuth is null) throw new ArgumentNullException("No Last Auth Injected"); - - var client = new LastfmClient(LastAuth); - - var playCount = new PlayCounterFactory(LoggerFactory, client: client, creds: new() { Username = dbWatcher.User.LastFmUsername }); - consumers.Add(await playCount.Get()); + consumers.Add(await PlayCounterFactory.Get(creds: new() { Username = dbWatcher.User.LastFmUsername })); } else { diff --git a/Selector.CLI/LocalWatcherService.cs b/Selector.CLI/LocalWatcherService.cs index e883ce7..1ba6aba 100644 --- a/Selector.CLI/LocalWatcherService.cs +++ b/Selector.CLI/LocalWatcherService.cs @@ -4,15 +4,13 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; -using IF.Lastfm.Core.Api; -using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Selector.Cache; -using StackExchange.Redis; namespace Selector.CLI { @@ -21,15 +19,12 @@ namespace Selector.CLI private const string ConfigInstanceKey = "localconfig"; private readonly ILogger Logger; - private readonly ILoggerFactory LoggerFactory; private readonly RootOptions Config; private readonly IWatcherFactory WatcherFactory; private readonly IWatcherCollectionFactory WatcherCollectionFactory; private readonly IRefreshTokenFactoryProvider SpotifyFactory; - private readonly LastAuth LastAuth; - - private readonly IDatabaseAsync Cache; - private readonly ISubscriber Subscriber; + + private readonly IServiceProvider ServiceProvider; private Dictionary Watchers { get; set; } = new(); @@ -37,21 +32,20 @@ namespace Selector.CLI IWatcherFactory watcherFactory, IWatcherCollectionFactory watcherCollectionFactory, IRefreshTokenFactoryProvider spotifyFactory, - ILoggerFactory loggerFactory, - IOptions config, - LastAuth lastAuth = null, - IDatabaseAsync cache = null, - ISubscriber subscriber = null + + IServiceProvider serviceProvider, + + ILogger logger, + IOptions config ) { - Logger = loggerFactory.CreateLogger(); - LoggerFactory = loggerFactory; + Logger = logger; Config = config.Value; + WatcherFactory = watcherFactory; WatcherCollectionFactory = watcherCollectionFactory; SpotifyFactory = spotifyFactory; - LastAuth = lastAuth; - Cache = cache; - Subscriber = subscriber; + + ServiceProvider = serviceProvider; } public async Task StartAsync(CancellationToken cancellationToken) @@ -111,34 +105,25 @@ namespace Selector.CLI switch(consumer) { case Consumers.AudioFeatures: - var featureInjector = new AudioFeatureInjectorFactory(LoggerFactory); - consumers.Add(await featureInjector.Get(spotifyFactory)); + consumers.Add(await ServiceProvider.GetService().Get(spotifyFactory)); break; case Consumers.AudioFeaturesCache: - var featureInjectorCache = new CachingAudioFeatureInjectorFactory(LoggerFactory, Cache); - consumers.Add(await featureInjectorCache.Get(spotifyFactory)); + consumers.Add(await ServiceProvider.GetService().Get(spotifyFactory)); break; case Consumers.CacheWriter: - var cacheWriter = new CacheWriterFactory(Cache, LoggerFactory); - consumers.Add(await cacheWriter.Get()); + consumers.Add(await ServiceProvider.GetService().Get()); break; case Consumers.Publisher: - var pub = new PublisherFactory(Subscriber, LoggerFactory); - consumers.Add(await pub.Get()); + consumers.Add(await ServiceProvider.GetService().Get()); break; case Consumers.PlayCounter: if(!string.IsNullOrWhiteSpace(watcherOption.LastFmUsername)) { - if(LastAuth is null) throw new ArgumentNullException("No Last Auth Injected"); - - var client = new LastfmClient(LastAuth); - - var playCount = new PlayCounterFactory(LoggerFactory, client: client, creds: new(){ Username = watcherOption.LastFmUsername }); - consumers.Add(await playCount.Get()); + consumers.Add(await ServiceProvider.GetService().Get(creds: new() { Username = watcherOption.LastFmUsername })); } else { diff --git a/Selector.CLI/Options.cs b/Selector.CLI/Options.cs index 2f941df..74b5f96 100644 --- a/Selector.CLI/Options.cs +++ b/Selector.CLI/Options.cs @@ -22,7 +22,7 @@ namespace Selector.CLI public static string FormatKeys(string[] args) => string.Join(":", args); } - class RootOptions + public class RootOptions { public const string Key = "Selector"; @@ -42,12 +42,12 @@ namespace Selector.CLI public EqualityChecker Equality { get; set; } = EqualityChecker.Uri; } - enum EqualityChecker + public enum EqualityChecker { Uri, String } - class WatcherOptions + public class WatcherOptions { public const string Key = "Watcher"; @@ -56,7 +56,7 @@ namespace Selector.CLI public List Instances { get; set; } = new(); } - class WatcherInstanceOptions + public class WatcherInstanceOptions { public const string Key = "Instances"; @@ -73,19 +73,19 @@ namespace Selector.CLI #nullable disable } - enum Consumers + public enum Consumers { AudioFeatures, AudioFeaturesCache, CacheWriter, Publisher, PlayCounter } - class DatabaseOptions { + public class DatabaseOptions { public const string Key = "Database"; public bool Enabled { get; set; } = false; public string ConnectionString { get; set; } } - class RedisOptions + public class RedisOptions { public const string Key = "Redis"; diff --git a/Selector.CLI/Program.cs b/Selector.CLI/Program.cs index b0dab88..30b26ef 100644 --- a/Selector.CLI/Program.cs +++ b/Selector.CLI/Program.cs @@ -11,12 +11,12 @@ using NLog.Extensions.Logging; using Selector.Model; using Selector.Cache; +using Selector.Cache.Extensions; using IF.Lastfm.Core.Api; -using StackExchange.Redis; namespace Selector.CLI { - class Program + public static class Program { public static async Task Main(string[] args) { @@ -55,7 +55,8 @@ namespace Selector.CLI Console.WriteLine("> Adding Last.fm credentials..."); var lastAuth = new LastAuth(config.LastfmClient, config.LastfmSecret); - services.AddSingleton(lastAuth); + services.AddSingleton(lastAuth); + services.AddTransient(sp => new LastfmClient(sp.GetService())); } 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(services => services.GetService().GetDatabase()); - services.AddTransient(services => services.GetService().GetSubscriber()); - } - } - public static void ConfigureEqual(RootOptions config, IServiceCollection services) { switch (config.Equality) @@ -119,8 +101,9 @@ namespace Selector.CLI Console.WriteLine("> Adding Services..."); // SERVICES + services.AddCachingConsumerFactories(); + services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); // For generating spotify clients //services.AddSingleton(); @@ -128,7 +111,10 @@ namespace Selector.CLI ConfigureLastFm(config, services); ConfigureDb(config, services); - ConfigureRedis(config, services); + + if (config.RedisOptions.Enabled) + services.AddRedisServices(config.RedisOptions.ConnectionString); + ConfigureEqual(config, services); // HOSTED SERVICES diff --git a/Selector.Cache/Consumer/Factory/AudioInjectorCaching.cs b/Selector.Cache/Consumer/Factory/AudioInjectorCaching.cs index 13c9055..048342e 100644 --- a/Selector.Cache/Consumer/Factory/AudioInjectorCaching.cs +++ b/Selector.Cache/Consumer/Factory/AudioInjectorCaching.cs @@ -8,13 +8,8 @@ using SpotifyAPI.Web; using StackExchange.Redis; namespace Selector.Cache -{ - public interface ICachingAudioFeatureInjectorFactory - { - public Task Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher); - } - - public class CachingAudioFeatureInjectorFactory: ICachingAudioFeatureInjectorFactory { +{ + public class CachingAudioFeatureInjectorFactory: IAudioFeatureInjectorFactory { private readonly ILoggerFactory LoggerFactory; private readonly IDatabaseAsync Db; diff --git a/Selector.Cache/Consumer/Factory/PlayCounterCachingFactory.cs b/Selector.Cache/Consumer/Factory/PlayCounterCachingFactory.cs new file mode 100644 index 0000000..8ebfa42 --- /dev/null +++ b/Selector.Cache/Consumer/Factory/PlayCounterCachingFactory.cs @@ -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 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() + ); + } + } +} diff --git a/Selector.Cache/Consumer/PlayCounterCaching.cs b/Selector.Cache/Consumer/PlayCounterCaching.cs new file mode 100644 index 0000000..7fb4887 --- /dev/null +++ b/Selector.Cache/Consumer/PlayCounterCaching.cs @@ -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 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()}]"); + } + } +} diff --git a/Selector.Cache/Extensions/ServiceExtensions.cs b/Selector.Cache/Extensions/ServiceExtensions.cs new file mode 100644 index 0000000..1c3a245 --- /dev/null +++ b/Selector.Cache/Extensions/ServiceExtensions.cs @@ -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(services => services.GetService().GetDatabase()); + services.AddTransient(services => services.GetService().GetSubscriber()); + } + + public static void AddCachingConsumerFactories(this IServiceCollection services) + { + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + } + } +} diff --git a/Selector.Cache/Key.cs b/Selector.Cache/Key.cs index ef83340..9788571 100644 --- a/Selector.Cache/Key.cs +++ b/Selector.Cache/Key.cs @@ -11,6 +11,7 @@ namespace Selector.Cache public const string TrackName = "Track"; public const string AlbumName = "Album"; public const string ArtistName = "Artist"; + public const string UserName = "User"; public const string AudioFeatureName = "AudioFeature"; 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 AlbumPlayCount(string name, string artist) => Namespace(AlbumName, artist, 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); diff --git a/Selector.Model/Extensions/ServiceExtensions.cs b/Selector.Model/Extensions/ServiceExtensions.cs new file mode 100644 index 0000000..f26a5b1 --- /dev/null +++ b/Selector.Model/Extensions/ServiceExtensions.cs @@ -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(); + services.AddSingleton(); + + services.AddScoped(); + services.AddSingleton(); + } + } +} diff --git a/Selector.Web/Startup.cs b/Selector.Web/Startup.cs index 0c5f5ae..11cce1d 100644 --- a/Selector.Web/Startup.cs +++ b/Selector.Web/Startup.cs @@ -13,13 +13,12 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; -using StackExchange.Redis; - using Selector.Web.Service; using Selector.Web.Hubs; using Selector.Model; -using Selector.Model.Authorisation; +using Selector.Model.Extensions; using Selector.Cache; +using Selector.Cache.Extensions; namespace Selector.Web { @@ -93,35 +92,10 @@ namespace Selector.Web options.SlidingExpiration = true; }); - // AUTH - services.AddAuthorization(options => - { - options.FallbackPolicy = new AuthorizationPolicyBuilder() - .RequireAuthenticatedUser() - .Build(); - }); - services.AddScoped(); - services.AddSingleton(); + services.AddAuthorisationHandlers(); - services.AddScoped(); - services.AddSingleton(); - - // REDIS 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(services => services.GetService().GetDatabase()); - services.AddTransient(services => services.GetService().GetSubscriber()); - } + services.AddRedisServices(config.RedisOptions.ConnectionString); services.AddSingleton(); services.AddSingleton(); diff --git a/Selector/Consumers/Factory/AudioFeatureInjectorFactory.cs b/Selector/Consumers/Factory/AudioFeatureInjectorFactory.cs index 2aa044e..f3c7ba5 100644 --- a/Selector/Consumers/Factory/AudioFeatureInjectorFactory.cs +++ b/Selector/Consumers/Factory/AudioFeatureInjectorFactory.cs @@ -10,7 +10,7 @@ namespace Selector { public interface IAudioFeatureInjectorFactory { - public Task Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher); + public Task Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null); } public class AudioFeatureInjectorFactory: IAudioFeatureInjectorFactory { diff --git a/Selector/Consumers/PlayCounter.cs b/Selector/Consumers/PlayCounter.cs index 0ed7bd7..a0c52d9 100644 --- a/Selector/Consumers/PlayCounter.cs +++ b/Selector/Consumers/PlayCounter.cs @@ -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}"); - OnNewPlayCount(new() + PlayCount playCount = new() { Track = trackCount, Album = albumCount, Artist = artistCount, User = userCount, - }); + ListeningEvent = e + }; + + if (!string.IsNullOrWhiteSpace(Credentials.Username)) + playCount.Username = Credentials.Username; + + OnNewPlayCount(playCount); } else if (e.Current.Item is FullEpisode episode) { @@ -181,6 +187,8 @@ namespace Selector public int? Album { get; set; } public int? Artist { get; set; } public int? User { get; set; } + public string Username { get; set; } + public ListeningChangeEventArgs ListeningEvent { get; set; } } public class LastFmCredentials diff --git a/Selector/Extensions/ServiceExtensions.cs b/Selector/Extensions/ServiceExtensions.cs new file mode 100644 index 0000000..06ff65f --- /dev/null +++ b/Selector/Extensions/ServiceExtensions.cs @@ -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(); + services.AddTransient(); + } + } +} diff --git a/Selector/Helpers/SpotifyExtensions.cs b/Selector/Extensions/SpotifyExtensions.cs similarity index 100% rename from Selector/Helpers/SpotifyExtensions.cs rename to Selector/Extensions/SpotifyExtensions.cs diff --git a/Selector/Watcher/WatcherCollection/IWatcherCollection.cs b/Selector/Watcher/Collection/Interfaces/IWatcherCollection.cs similarity index 100% rename from Selector/Watcher/WatcherCollection/IWatcherCollection.cs rename to Selector/Watcher/Collection/Interfaces/IWatcherCollection.cs diff --git a/Selector/Watcher/WatcherCollection/IWatcherCollectionFactory.cs b/Selector/Watcher/Collection/Interfaces/IWatcherCollectionFactory.cs similarity index 100% rename from Selector/Watcher/WatcherCollection/IWatcherCollectionFactory.cs rename to Selector/Watcher/Collection/Interfaces/IWatcherCollectionFactory.cs diff --git a/Selector/Watcher/WatcherCollection/WatcherCollection.cs b/Selector/Watcher/Collection/WatcherCollection.cs similarity index 100% rename from Selector/Watcher/WatcherCollection/WatcherCollection.cs rename to Selector/Watcher/Collection/WatcherCollection.cs diff --git a/Selector/Watcher/WatcherCollection/WatcherCollectionFactory.cs b/Selector/Watcher/Collection/WatcherCollectionFactory.cs similarity index 100% rename from Selector/Watcher/WatcherCollection/WatcherCollectionFactory.cs rename to Selector/Watcher/Collection/WatcherCollectionFactory.cs diff --git a/Selector/Watcher/Context/Interfaces/IWatcherContext.cs b/Selector/Watcher/Context/Interfaces/IWatcherContext.cs new file mode 100644 index 0000000..446505a --- /dev/null +++ b/Selector/Watcher/Context/Interfaces/IWatcherContext.cs @@ -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(); + } +} diff --git a/Selector/Watcher/WatcherCollection/WatcherContext.cs b/Selector/Watcher/Context/WatcherContext.cs similarity index 97% rename from Selector/Watcher/WatcherCollection/WatcherContext.cs rename to Selector/Watcher/Context/WatcherContext.cs index b02220d..7943df6 100644 --- a/Selector/Watcher/WatcherCollection/WatcherContext.cs +++ b/Selector/Watcher/Context/WatcherContext.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Selector { - public class WatcherContext: IDisposable + public class WatcherContext: IDisposable, IWatcherContext { public IWatcher Watcher { get; set; } private List Consumers { get; set; } = new();