From a401280edff05736e5f40e1a3d66fb5c432279e6 Mon Sep 17 00:00:00 2001 From: andy Date: Sat, 4 Dec 2021 12:49:09 +0000 Subject: [PATCH] working without redis, player watcher event tweaking, graceful exception handling --- Selector.CLI/DbWatcherService.cs | 14 +-- Selector.CLI/LocalWatcherService.cs | 2 +- Selector.CLI/Program.cs | 21 +++- Selector.CLI/Selector.CLI.csproj | 7 +- .../Consumer/CacheWriterConsumer.cs | 2 - .../Extensions/ServiceExtensions.cs | 4 +- Selector.Cache/Services/AudioFeaturePuller.cs | 6 +- Selector.Cache/Services/PlayCountPuller.cs | 58 +++------- Selector.Model/ApplicationDbContext.cs | 19 +++- Selector.Model/Selector.Model.csproj | 2 +- .../Identity/Pages/Account/Login.cshtml.cs | 4 +- Selector.Web/Pages/Now.cshtml.cs | 11 +- Selector.Web/Selector.Web.csproj | 5 +- Selector.Web/Startup.cs | 16 ++- Selector/Watcher/BaseWatcher.cs | 1 + .../Watcher/Collection/WatcherCollection.cs | 2 +- Selector/Watcher/Context/WatcherContext.cs | 14 +++ Selector/Watcher/Interfaces/IPlayerWatcher.cs | 1 + Selector/Watcher/Interfaces/IWatcher.cs | 2 + Selector/Watcher/PlayerWatcher.cs | 100 ++++++++++-------- 20 files changed, 167 insertions(+), 124 deletions(-) diff --git a/Selector.CLI/DbWatcherService.cs b/Selector.CLI/DbWatcherService.cs index 7f57d5f..e34ffbc 100644 --- a/Selector.CLI/DbWatcherService.cs +++ b/Selector.CLI/DbWatcherService.cs @@ -44,12 +44,14 @@ namespace Selector.CLI IAudioFeatureInjectorFactory audioFeatureInjectorFactory, IPlayCounterFactory playCounterFactory, - IPublisherFactory publisherFactory, - ICacheWriterFactory cacheWriterFactory, ILogger logger, - IServiceProvider serviceProvider - ) { + IServiceProvider serviceProvider, + + IPublisherFactory publisherFactory = null, + ICacheWriterFactory cacheWriterFactory = null + ) + { Logger = logger; ServiceProvider = serviceProvider; @@ -105,8 +107,8 @@ namespace Selector.CLI watcher = await WatcherFactory.Get(spotifyFactory, id: dbWatcher.UserId, pollPeriod: PollPeriod); consumers.Add(await AudioFeatureInjectorFactory.Get(spotifyFactory)); - consumers.Add(await CacheWriterFactory.Get()); - consumers.Add(await PublisherFactory.Get()); + if (CacheWriterFactory is not null) consumers.Add(await CacheWriterFactory.Get()); + if (PublisherFactory is not null) consumers.Add(await PublisherFactory.Get()); if (!string.IsNullOrWhiteSpace(dbWatcher.User.LastFmUsername)) { diff --git a/Selector.CLI/LocalWatcherService.cs b/Selector.CLI/LocalWatcherService.cs index 1ba6aba..02616f3 100644 --- a/Selector.CLI/LocalWatcherService.cs +++ b/Selector.CLI/LocalWatcherService.cs @@ -123,7 +123,7 @@ namespace Selector.CLI case Consumers.PlayCounter: if(!string.IsNullOrWhiteSpace(watcherOption.LastFmUsername)) { - consumers.Add(await ServiceProvider.GetService().Get(creds: new() { Username = watcherOption.LastFmUsername })); + consumers.Add(await ServiceProvider.GetService().Get(creds: new() { Username = watcherOption.LastFmUsername })); } else { diff --git a/Selector.CLI/Program.cs b/Selector.CLI/Program.cs index 72d790c..f580305 100644 --- a/Selector.CLI/Program.cs +++ b/Selector.CLI/Program.cs @@ -55,7 +55,12 @@ namespace Selector.CLI Console.WriteLine("> Adding Last.fm credentials..."); services.AddLastFm(config.LastfmClient, config.LastfmSecret); - services.AddCachingLastFm(); + + if(config.RedisOptions.Enabled) + { + Console.WriteLine("> Adding caching Last.fm consumers..."); + services.AddCachingLastFm(); + } } else { @@ -101,11 +106,19 @@ namespace Selector.CLI Console.WriteLine("> Adding Services..."); // SERVICES services.AddConsumerFactories(); - services.AddCachingConsumerFactories(); + if (config.RedisOptions.Enabled) + { + Console.WriteLine("> Adding caching consumers..."); + services.AddCachingConsumerFactories(); + } services.AddWatcher(); services.AddSpotify(); - services.AddCachingSpotify(); + if (config.RedisOptions.Enabled) { + Console.WriteLine("> Adding caching Spotify consumers..."); + services.AddCachingSpotify(); + } + ConfigureLastFm(config, services); ConfigureDb(config, services); @@ -140,6 +153,8 @@ namespace Selector.CLI static IHostBuilder CreateHostBuilder(string[] args, Action BuildServices, Action BuildLogs) => Host.CreateDefaultBuilder(args) + .UseWindowsService() + .UseSystemd() .ConfigureServices((context, services) => BuildServices(context, services)) .ConfigureLogging((context, builder) => BuildLogs(context, builder)); } diff --git a/Selector.CLI/Selector.CLI.csproj b/Selector.CLI/Selector.CLI.csproj index ca25d85..2b240fa 100644 --- a/Selector.CLI/Selector.CLI.csproj +++ b/Selector.CLI/Selector.CLI.csproj @@ -10,6 +10,8 @@ + + @@ -26,7 +28,10 @@ - + + PreserveNewest + + PreserveNewest diff --git a/Selector.Cache/Consumer/CacheWriterConsumer.cs b/Selector.Cache/Consumer/CacheWriterConsumer.cs index ec55bb9..5baff09 100644 --- a/Selector.Cache/Consumer/CacheWriterConsumer.cs +++ b/Selector.Cache/Consumer/CacheWriterConsumer.cs @@ -57,7 +57,6 @@ namespace Selector.Cache if (watcher is IPlayerWatcher watcherCast) { watcherCast.ItemChange += Callback; - watcherCast.PlayingChange += Callback; } else { @@ -72,7 +71,6 @@ namespace Selector.Cache if (watcher is IPlayerWatcher watcherCast) { watcherCast.ItemChange -= Callback; - watcherCast.PlayingChange -= Callback; } else { diff --git a/Selector.Cache/Extensions/ServiceExtensions.cs b/Selector.Cache/Extensions/ServiceExtensions.cs index 75a0ff8..52f17f1 100644 --- a/Selector.Cache/Extensions/ServiceExtensions.cs +++ b/Selector.Cache/Extensions/ServiceExtensions.cs @@ -27,8 +27,8 @@ namespace Selector.Cache.Extensions public static void AddCachingConsumerFactories(this IServiceCollection services) { - services.AddTransient(); - services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/Selector.Cache/Services/AudioFeaturePuller.cs b/Selector.Cache/Services/AudioFeaturePuller.cs index 0454de5..fe0c0b8 100644 --- a/Selector.Cache/Services/AudioFeaturePuller.cs +++ b/Selector.Cache/Services/AudioFeaturePuller.cs @@ -13,7 +13,7 @@ namespace Selector.Cache public AudioFeaturePuller( IRefreshTokenFactoryProvider spotifyFactory, - IDatabaseAsync cache + IDatabaseAsync cache = null ) { SpotifyFactory = spotifyFactory; @@ -24,8 +24,8 @@ namespace Selector.Cache { if(string.IsNullOrWhiteSpace(trackId)) throw new ArgumentNullException("No track Id provided"); - var track = await Cache.StringGetAsync(Key.AudioFeature(trackId)); - if (track == RedisValue.Null) + var track = await Cache?.StringGetAsync(Key.AudioFeature(trackId)); + if (Cache is null || track == RedisValue.Null) { if(!string.IsNullOrWhiteSpace(refreshToken)) { diff --git a/Selector.Cache/Services/PlayCountPuller.cs b/Selector.Cache/Services/PlayCountPuller.cs index 1bba903..49c98a3 100644 --- a/Selector.Cache/Services/PlayCountPuller.cs +++ b/Selector.Cache/Services/PlayCountPuller.cs @@ -23,13 +23,13 @@ namespace Selector.Cache protected readonly IUserApi UserClient; public PlayCountPuller( - IDatabaseAsync cache, ILogger logger, ITrackApi trackClient, IAlbumApi albumClient, IArtistApi artistClient, - IUserApi userClient + IUserApi userClient, + IDatabaseAsync cache = null ) { Cache = cache; @@ -45,14 +45,14 @@ namespace Selector.Cache { if (string.IsNullOrWhiteSpace(username)) throw new ArgumentNullException("No username provided"); - var trackCache = Cache.StringGetAsync(Key.TrackPlayCount(track, artist)); - var albumCache = Cache.StringGetAsync(Key.AlbumPlayCount(album, albumArtist)); - var artistCache = Cache.StringGetAsync(Key.ArtistPlayCount(artist)); - var userCache = Cache.StringGetAsync(Key.UserPlayCount(username)); + var trackCache = Cache?.StringGetAsync(Key.TrackPlayCount(track, artist)); + var albumCache = Cache?.StringGetAsync(Key.AlbumPlayCount(album, albumArtist)); + var artistCache = Cache?.StringGetAsync(Key.ArtistPlayCount(artist)); + var userCache = Cache?.StringGetAsync(Key.UserPlayCount(username)); var cacheTasks = new Task[] { trackCache, albumCache, artistCache, userCache }; - await Task.WhenAll(cacheTasks); + await Task.WhenAll(cacheTasks.Where(t => t is not null)); PlayCount playCount = new() { @@ -64,64 +64,36 @@ namespace Selector.Cache Task> artistHttp = null; Task> userHttp = null; - if (trackCache.IsCompletedSuccessfully) + if (trackCache is not null && trackCache.IsCompletedSuccessfully && trackCache.Result != RedisValue.Null) { - if(trackCache.Result == RedisValue.Null) - { - trackHttp = TrackClient.GetInfoAsync(track, artist, username); - } - else - { - playCount.Track = (int) trackCache.Result; - } + playCount.Track = (int) trackCache.Result; } else { trackHttp = TrackClient.GetInfoAsync(track, artist, username); } - if (albumCache.IsCompletedSuccessfully) + if (albumCache is not null && albumCache.IsCompletedSuccessfully && albumCache.Result != RedisValue.Null) { - if (albumCache.Result == RedisValue.Null) - { - albumHttp = AlbumClient.GetInfoAsync(albumArtist, album, username: username); - } - else - { - playCount.Album = (int)albumCache.Result; - } + playCount.Album = (int) albumCache.Result; } else { albumHttp = AlbumClient.GetInfoAsync(albumArtist, album, username: username); } - if (artistCache.IsCompletedSuccessfully) + if (artistCache is not null && artistCache.IsCompletedSuccessfully && artistCache.Result != RedisValue.Null) { - if (artistCache.Result == RedisValue.Null) - { - artistHttp = ArtistClient.GetInfoAsync(artist); - } - else - { - playCount.Artist = (int)artistCache.Result; - } + playCount.Artist = (int) artistCache.Result; } else { artistHttp = ArtistClient.GetInfoAsync(artist); } - if (userCache.IsCompletedSuccessfully) + if (userCache is not null && userCache.IsCompletedSuccessfully && userCache.Result != RedisValue.Null) { - if (userCache.Result == RedisValue.Null) - { - userHttp = UserClient.GetInfoAsync(username); - } - else - { - playCount.User = (int)userCache.Result; - } + playCount.User = (int) userCache.Result; } else { diff --git a/Selector.Model/ApplicationDbContext.cs b/Selector.Model/ApplicationDbContext.cs index 69f565d..0701c62 100644 --- a/Selector.Model/ApplicationDbContext.cs +++ b/Selector.Model/ApplicationDbContext.cs @@ -64,11 +64,28 @@ namespace Selector.Model public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory { + private static string GetPath(string env) => $"{@Directory.GetCurrentDirectory()}/../Selector.Web/appsettings.{env}.json"; + public ApplicationDbContext CreateDbContext(string[] args) { + string configFile; + + if(File.Exists(GetPath("Development"))) + { + configFile = GetPath("Development"); + } + else if(File.Exists(GetPath("Release"))) + { + configFile = GetPath("Release"); + } + else + { + throw new FileNotFoundException("No config file available to load a connection string from"); + } + IConfigurationRoot configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile(@Directory.GetCurrentDirectory() + "/../Selector.Web/appsettings.Development.json") + .AddJsonFile(configFile) .Build(); var builder = new DbContextOptionsBuilder(); diff --git a/Selector.Model/Selector.Model.csproj b/Selector.Model/Selector.Model.csproj index 5698975..953a1e9 100644 --- a/Selector.Model/Selector.Model.csproj +++ b/Selector.Model/Selector.Model.csproj @@ -22,7 +22,7 @@ - + diff --git a/Selector.Web/Areas/Identity/Pages/Account/Login.cshtml.cs b/Selector.Web/Areas/Identity/Pages/Account/Login.cshtml.cs index 8d0bda8..4b38d4b 100644 --- a/Selector.Web/Areas/Identity/Pages/Account/Login.cshtml.cs +++ b/Selector.Web/Areas/Identity/Pages/Account/Login.cshtml.cs @@ -85,7 +85,7 @@ namespace Selector.Web.Areas.Identity.Pages.Account var result = await _signInManager.PasswordSignInAsync(Input.Username, Input.Password, Input.RememberMe, lockoutOnFailure: false); if (result.Succeeded) { - _logger.LogInformation("User logged in."); + _logger.LogInformation($"[{Input.Username}] logged in."); return LocalRedirect(returnUrl); } if (result.RequiresTwoFactor) @@ -94,7 +94,7 @@ namespace Selector.Web.Areas.Identity.Pages.Account } if (result.IsLockedOut) { - _logger.LogWarning("User account locked out."); + _logger.LogWarning($"[{Input.Username}] locked out."); return RedirectToPage("./Lockout"); } else diff --git a/Selector.Web/Pages/Now.cshtml.cs b/Selector.Web/Pages/Now.cshtml.cs index 61c7442..9dce6f7 100644 --- a/Selector.Web/Pages/Now.cshtml.cs +++ b/Selector.Web/Pages/Now.cshtml.cs @@ -18,19 +18,10 @@ namespace Selector.Web.Pages public class NowModel : PageModel { private readonly ILogger Logger; - private readonly INowPlayingMappingFactory MappingFactory; - private readonly CacheHubProxy HubProxy; - private readonly UserManager UserManager; - public NowModel(ILogger logger, - INowPlayingMappingFactory mappingFactory, - CacheHubProxy hubProxy, - UserManager userManager) + public NowModel(ILogger logger) { Logger = logger; - MappingFactory = mappingFactory; - HubProxy = hubProxy; - UserManager = userManager; } public void OnGet() diff --git a/Selector.Web/Selector.Web.csproj b/Selector.Web/Selector.Web.csproj index 5c45605..7d71e5e 100644 --- a/Selector.Web/Selector.Web.csproj +++ b/Selector.Web/Selector.Web.csproj @@ -34,7 +34,10 @@ - + + PreserveNewest + + PreserveNewest diff --git a/Selector.Web/Startup.cs b/Selector.Web/Startup.cs index 767c14f..9c5ea7d 100644 --- a/Selector.Web/Startup.cs +++ b/Selector.Web/Startup.cs @@ -97,11 +97,14 @@ namespace Selector.Web services.AddRedisServices(config.RedisOptions.ConnectionString); services.AddSpotify(); - services.AddCachingSpotify(); + if (config.RedisOptions.Enabled) + { + Console.WriteLine("> Adding caching Spotify consumers..."); + services.AddCachingSpotify(); + services.AddCacheHubProxy(); + } ConfigureLastFm(config, services); - - services.AddCacheHubProxy(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -141,7 +144,12 @@ namespace Selector.Web Console.WriteLine("> Adding Last.fm credentials..."); services.AddLastFm(config.LastfmClient, config.LastfmSecret); - services.AddCachingLastFm(); + + if (config.RedisOptions.Enabled) + { + Console.WriteLine("> Adding caching Last.fm consumers..."); + services.AddCachingLastFm(); + } } else { diff --git a/Selector/Watcher/BaseWatcher.cs b/Selector/Watcher/BaseWatcher.cs index 706588b..44132a2 100644 --- a/Selector/Watcher/BaseWatcher.cs +++ b/Selector/Watcher/BaseWatcher.cs @@ -22,6 +22,7 @@ namespace Selector } public abstract Task WatchOne(CancellationToken token); + public abstract Task Reset(); public async Task Watch(CancellationToken cancelToken) { diff --git a/Selector/Watcher/Collection/WatcherCollection.cs b/Selector/Watcher/Collection/WatcherCollection.cs index 973a95b..8f2e880 100644 --- a/Selector/Watcher/Collection/WatcherCollection.cs +++ b/Selector/Watcher/Collection/WatcherCollection.cs @@ -13,7 +13,7 @@ namespace Selector public class WatcherCollection: IWatcherCollection, IDisposable, IEnumerable { private readonly ILogger Logger; - public bool IsRunning { get; private set; } = true; + public bool IsRunning { get; private set; } = false; private List Watchers { get; set; } = new(); public WatcherCollection(ILogger logger = null) diff --git a/Selector/Watcher/Context/WatcherContext.cs b/Selector/Watcher/Context/WatcherContext.cs index 7943df6..d4245ff 100644 --- a/Selector/Watcher/Context/WatcherContext.cs +++ b/Selector/Watcher/Context/WatcherContext.cs @@ -58,10 +58,24 @@ namespace Selector Consumers.ForEach(c => c.Subscribe(Watcher)); + Reset(); + } + + private void Reset() + { + if(Task is not null && !Task.IsCompleted) + { + TokenSource.Cancel(); + } + + TokenSource = new(); + Task = Watcher.Watch(TokenSource.Token); Task.ContinueWith(t => { if (t.Exception != null) throw t.Exception; + Watcher.Reset(); + Reset(); }, TaskContinuationOptions.OnlyOnFaulted); } diff --git a/Selector/Watcher/Interfaces/IPlayerWatcher.cs b/Selector/Watcher/Interfaces/IPlayerWatcher.cs index 7eecdc6..3c2cde1 100644 --- a/Selector/Watcher/Interfaces/IPlayerWatcher.cs +++ b/Selector/Watcher/Interfaces/IPlayerWatcher.cs @@ -8,6 +8,7 @@ namespace Selector /// /// Track or episode changes /// + public event EventHandler NetworkPoll; public event EventHandler ItemChange; public event EventHandler AlbumChange; public event EventHandler ArtistChange; diff --git a/Selector/Watcher/Interfaces/IWatcher.cs b/Selector/Watcher/Interfaces/IWatcher.cs index 4f9da00..9e9d33a 100644 --- a/Selector/Watcher/Interfaces/IWatcher.cs +++ b/Selector/Watcher/Interfaces/IWatcher.cs @@ -18,6 +18,8 @@ namespace Selector /// public Task Watch(CancellationToken cancelToken); + public Task Reset(); + /// /// Time interval in ms between polls from Watch() /// diff --git a/Selector/Watcher/PlayerWatcher.cs b/Selector/Watcher/PlayerWatcher.cs index b5f2830..b613e73 100644 --- a/Selector/Watcher/PlayerWatcher.cs +++ b/Selector/Watcher/PlayerWatcher.cs @@ -15,6 +15,7 @@ namespace Selector private readonly IPlayerClient spotifyClient; private readonly IEqual eq; + public event EventHandler NetworkPoll; public event EventHandler ItemChange; public event EventHandler AlbumChange; public event EventHandler ArtistChange; @@ -26,6 +27,7 @@ namespace Selector public event EventHandler PlayingChange; public CurrentlyPlayingContext Live { get; private set; } + private CurrentlyPlayingContext Previous { get; set; } public PlayerTimeline Past { get; set; } = new(); public PlayerWatcher(IPlayerClient spotifyClient, @@ -40,6 +42,15 @@ namespace Selector PollPeriod = pollPeriod; } + public override Task Reset() + { + Previous = null; + Live = null; + Past = new(); + + return Task.CompletedTask; + } + public override async Task WatchOne(CancellationToken token = default) { token.ThrowIfCancellationRequested(); @@ -52,104 +63,100 @@ namespace Selector if (polledCurrent != null) StoreCurrentPlaying(polledCurrent); // swap new item into live and bump existing down to previous - CurrentlyPlayingContext previous; - if(Live is null) { - Live = polledCurrent; - previous = polledCurrent; - } - else { - previous = Live; - Live = polledCurrent; - } + Previous = Live; + Live = polledCurrent; + + OnNetworkPoll(GetEvent()); // NOT PLAYING - if(previous is null && Live is null) + if(Previous is null && Live is null) { // Console.WriteLine("not playing"); } else { // STARTED PLAYBACK - if(previous is null - && (Live.Item is FullTrack || Live.Item is FullEpisode)) + if(Previous is null && Live is not null) { Logger.LogDebug($"Playback started: {Live.DisplayString()}"); - OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); - OnItemChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); + OnPlayingChange(GetEvent()); + OnItemChange(GetEvent()); + OnContextChange(GetEvent()); } // STOPPED PLAYBACK - else if((previous.Item is FullTrack || previous.Item is FullEpisode) - && Live is null) + else if(Previous is not null && Live is null) { - Logger.LogDebug($"Playback stopped: {previous.DisplayString()}"); - OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); - OnItemChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); + Logger.LogDebug($"Playback stopped: {Previous.DisplayString()}"); + OnPlayingChange(GetEvent()); + OnItemChange(GetEvent()); + OnContextChange(GetEvent()); } // CONTINUING PLAYBACK else { // MUSIC - if(previous.Item is FullTrack previousTrack + if(Previous.Item is FullTrack previousTrack && Live.Item is FullTrack currentTrack) { if(!eq.IsEqual(previousTrack, currentTrack)) { Logger.LogDebug($"Track changed: {previousTrack.DisplayString()} -> {currentTrack.DisplayString()}"); - OnItemChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); + OnItemChange(GetEvent()); } if(!eq.IsEqual(previousTrack.Album, currentTrack.Album)) { Logger.LogDebug($"Album changed: {previousTrack.Album.DisplayString()} -> {currentTrack.Album.DisplayString()}"); - OnAlbumChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); + OnAlbumChange(GetEvent()); } if(!eq.IsEqual(previousTrack.Artists[0], currentTrack.Artists[0])) { Logger.LogDebug($"Artist changed: {previousTrack.Artists.DisplayString()} -> {currentTrack.Artists.DisplayString()}"); - OnArtistChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); + OnArtistChange(GetEvent()); } } - // CHANGED CONTENT - else if((previous.Item is FullTrack && Live.Item is FullEpisode) - || (previous.Item is FullEpisode && Live.Item is FullTrack)) + // CHANGED CONTENT TYPE + else if((Previous.Item is FullTrack && Live.Item is FullEpisode) + || (Previous.Item is FullEpisode && Live.Item is FullTrack)) { - Logger.LogDebug($"Media type changed: {previous.Item}, {previous.Item}"); - OnContentChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); - OnItemChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); + Logger.LogDebug($"Media type changed: {Previous.Item}, {Previous.Item}"); + OnContentChange(GetEvent()); + OnItemChange(GetEvent()); } // PODCASTS - else if(previous.Item is FullEpisode previousEp + else if(Previous.Item is FullEpisode previousEp && Live.Item is FullEpisode currentEp) { if(!eq.IsEqual(previousEp, currentEp)) { Logger.LogDebug($"Podcast changed: {previousEp.DisplayString()} -> {currentEp.DisplayString()}"); - OnItemChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); + OnItemChange(GetEvent()); } } else { + Logger.LogError($"Unknown combination of previous and current playing contexts, [{Previous.DisplayString()}] [{Live.DisplayString()}]"); throw new NotSupportedException("Unknown item combination"); } // CONTEXT - if(!eq.IsEqual(previous.Context, Live.Context)) { - Logger.LogDebug($"Context changed: {previous.Context.DisplayString()} -> {Live.Context.DisplayString()}"); - OnContextChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); + if(!eq.IsEqual(Previous.Context, Live.Context)) { + Logger.LogDebug($"Context changed: {Previous.Context.DisplayString()} -> {Live.Context.DisplayString()}"); + OnContextChange(GetEvent()); } // DEVICE - if(!eq.IsEqual(previous?.Device, Live?.Device)) { - Logger.LogDebug($"Device changed: {previous?.Device.DisplayString()} -> {Live?.Device.DisplayString()}"); - OnDeviceChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); + if(!eq.IsEqual(Previous?.Device, Live?.Device)) { + Logger.LogDebug($"Device changed: {Previous?.Device.DisplayString()} -> {Live?.Device.DisplayString()}"); + OnDeviceChange(GetEvent()); } // IS PLAYING - if(previous.IsPlaying != Live.IsPlaying) { - Logger.LogDebug($"Playing state changed: {previous.IsPlaying} -> {Live.IsPlaying}"); - OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); + if(Previous.IsPlaying != Live.IsPlaying) { + Logger.LogDebug($"Playing state changed: {Previous.IsPlaying} -> {Live.IsPlaying}"); + OnPlayingChange(GetEvent()); } // VOLUME - if(previous.Device.VolumePercent != Live.Device.VolumePercent) { - Logger.LogDebug($"Volume changed: {previous.Device.VolumePercent}% -> {Live.Device.VolumePercent}%"); - OnVolumeChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); + if(Previous.Device.VolumePercent != Live.Device.VolumePercent) { + Logger.LogDebug($"Volume changed: {Previous.Device.VolumePercent}% -> {Live.Device.VolumePercent}%"); + OnVolumeChange(GetEvent()); } } } @@ -172,6 +179,8 @@ namespace Selector } } + private ListeningChangeEventArgs GetEvent() => ListeningChangeEventArgs.From(Previous, Live, Past, id: Id, username: SpotifyUsername); + /// /// Store currently playing in last plays. Determine whether new list or appending required /// @@ -182,6 +191,11 @@ namespace Selector } #region Event Firers + protected virtual void OnNetworkPoll(ListeningChangeEventArgs args) + { + NetworkPoll?.Invoke(this, args); + } + protected virtual void OnItemChange(ListeningChangeEventArgs args) { ItemChange?.Invoke(this, args);