working without redis, player watcher event tweaking, graceful exception handling

This commit is contained in:
andy 2021-12-04 12:49:09 +00:00
parent 35eee0f068
commit a401280edf
20 changed files with 167 additions and 124 deletions

View File

@ -44,12 +44,14 @@ namespace Selector.CLI
IAudioFeatureInjectorFactory audioFeatureInjectorFactory, IAudioFeatureInjectorFactory audioFeatureInjectorFactory,
IPlayCounterFactory playCounterFactory, IPlayCounterFactory playCounterFactory,
IPublisherFactory publisherFactory,
ICacheWriterFactory cacheWriterFactory,
ILogger<DbWatcherService> logger, ILogger<DbWatcherService> logger,
IServiceProvider serviceProvider IServiceProvider serviceProvider,
) {
IPublisherFactory publisherFactory = null,
ICacheWriterFactory cacheWriterFactory = null
)
{
Logger = logger; Logger = logger;
ServiceProvider = serviceProvider; ServiceProvider = serviceProvider;
@ -105,8 +107,8 @@ namespace Selector.CLI
watcher = await WatcherFactory.Get<PlayerWatcher>(spotifyFactory, id: dbWatcher.UserId, pollPeriod: PollPeriod); watcher = await WatcherFactory.Get<PlayerWatcher>(spotifyFactory, id: dbWatcher.UserId, pollPeriod: PollPeriod);
consumers.Add(await AudioFeatureInjectorFactory.Get(spotifyFactory)); consumers.Add(await AudioFeatureInjectorFactory.Get(spotifyFactory));
consumers.Add(await CacheWriterFactory.Get()); if (CacheWriterFactory is not null) consumers.Add(await CacheWriterFactory.Get());
consumers.Add(await PublisherFactory.Get()); if (PublisherFactory is not null) consumers.Add(await PublisherFactory.Get());
if (!string.IsNullOrWhiteSpace(dbWatcher.User.LastFmUsername)) if (!string.IsNullOrWhiteSpace(dbWatcher.User.LastFmUsername))
{ {

View File

@ -123,7 +123,7 @@ namespace Selector.CLI
case Consumers.PlayCounter: case Consumers.PlayCounter:
if(!string.IsNullOrWhiteSpace(watcherOption.LastFmUsername)) if(!string.IsNullOrWhiteSpace(watcherOption.LastFmUsername))
{ {
consumers.Add(await ServiceProvider.GetService<PlayCounterCachingFactory>().Get(creds: new() { Username = watcherOption.LastFmUsername })); consumers.Add(await ServiceProvider.GetService<PlayCounterFactory>().Get(creds: new() { Username = watcherOption.LastFmUsername }));
} }
else else
{ {

View File

@ -55,8 +55,13 @@ namespace Selector.CLI
Console.WriteLine("> Adding Last.fm credentials..."); Console.WriteLine("> Adding Last.fm credentials...");
services.AddLastFm(config.LastfmClient, config.LastfmSecret); services.AddLastFm(config.LastfmClient, config.LastfmSecret);
if(config.RedisOptions.Enabled)
{
Console.WriteLine("> Adding caching Last.fm consumers...");
services.AddCachingLastFm(); services.AddCachingLastFm();
} }
}
else else
{ {
Console.WriteLine("> No Last.fm credentials, skipping init..."); Console.WriteLine("> No Last.fm credentials, skipping init...");
@ -101,11 +106,19 @@ namespace Selector.CLI
Console.WriteLine("> Adding Services..."); Console.WriteLine("> Adding Services...");
// SERVICES // SERVICES
services.AddConsumerFactories(); services.AddConsumerFactories();
if (config.RedisOptions.Enabled)
{
Console.WriteLine("> Adding caching consumers...");
services.AddCachingConsumerFactories(); services.AddCachingConsumerFactories();
}
services.AddWatcher(); services.AddWatcher();
services.AddSpotify(); services.AddSpotify();
if (config.RedisOptions.Enabled) {
Console.WriteLine("> Adding caching Spotify consumers...");
services.AddCachingSpotify(); services.AddCachingSpotify();
}
ConfigureLastFm(config, services); ConfigureLastFm(config, services);
ConfigureDb(config, services); ConfigureDb(config, services);
@ -140,6 +153,8 @@ namespace Selector.CLI
static IHostBuilder CreateHostBuilder(string[] args, Action<HostBuilderContext, IServiceCollection> BuildServices, Action<HostBuilderContext, ILoggingBuilder> BuildLogs) static IHostBuilder CreateHostBuilder(string[] args, Action<HostBuilderContext, IServiceCollection> BuildServices, Action<HostBuilderContext, ILoggingBuilder> BuildLogs)
=> Host.CreateDefaultBuilder(args) => Host.CreateDefaultBuilder(args)
.UseWindowsService()
.UseSystemd()
.ConfigureServices((context, services) => BuildServices(context, services)) .ConfigureServices((context, services) => BuildServices(context, services))
.ConfigureLogging((context, builder) => BuildLogs(context, builder)); .ConfigureLogging((context, builder) => BuildLogs(context, builder));
} }

View File

@ -10,6 +10,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
<PackageReference Include="NLog" Version="4.7.12" /> <PackageReference Include="NLog" Version="4.7.12" />
@ -26,7 +28,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="appsettings.Development.json"> <None Update="appsettings.Development.json" Condition="Exists('appsettings.Development.json')">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="appsettings.Release.json" Condition="Exists('appsettings.Release.json')">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Update="appsettings.json"> <None Update="appsettings.json">

View File

@ -57,7 +57,6 @@ namespace Selector.Cache
if (watcher is IPlayerWatcher watcherCast) if (watcher is IPlayerWatcher watcherCast)
{ {
watcherCast.ItemChange += Callback; watcherCast.ItemChange += Callback;
watcherCast.PlayingChange += Callback;
} }
else else
{ {
@ -72,7 +71,6 @@ namespace Selector.Cache
if (watcher is IPlayerWatcher watcherCast) if (watcher is IPlayerWatcher watcherCast)
{ {
watcherCast.ItemChange -= Callback; watcherCast.ItemChange -= Callback;
watcherCast.PlayingChange -= Callback;
} }
else else
{ {

View File

@ -27,8 +27,8 @@ namespace Selector.Cache.Extensions
public static void AddCachingConsumerFactories(this IServiceCollection services) public static void AddCachingConsumerFactories(this IServiceCollection services)
{ {
services.AddTransient<IAudioFeatureInjectorFactory, AudioFeatureInjectorFactory>(); services.AddTransient<IAudioFeatureInjectorFactory, CachingAudioFeatureInjectorFactory>();
services.AddTransient<AudioFeatureInjectorFactory>(); services.AddTransient<CachingAudioFeatureInjectorFactory>();
services.AddTransient<IPlayCounterFactory, PlayCounterCachingFactory>(); services.AddTransient<IPlayCounterFactory, PlayCounterCachingFactory>();
services.AddTransient<PlayCounterCachingFactory>(); services.AddTransient<PlayCounterCachingFactory>();

View File

@ -13,7 +13,7 @@ namespace Selector.Cache
public AudioFeaturePuller( public AudioFeaturePuller(
IRefreshTokenFactoryProvider spotifyFactory, IRefreshTokenFactoryProvider spotifyFactory,
IDatabaseAsync cache IDatabaseAsync cache = null
) )
{ {
SpotifyFactory = spotifyFactory; SpotifyFactory = spotifyFactory;
@ -24,8 +24,8 @@ namespace Selector.Cache
{ {
if(string.IsNullOrWhiteSpace(trackId)) throw new ArgumentNullException("No track Id provided"); if(string.IsNullOrWhiteSpace(trackId)) throw new ArgumentNullException("No track Id provided");
var track = await Cache.StringGetAsync(Key.AudioFeature(trackId)); var track = await Cache?.StringGetAsync(Key.AudioFeature(trackId));
if (track == RedisValue.Null) if (Cache is null || track == RedisValue.Null)
{ {
if(!string.IsNullOrWhiteSpace(refreshToken)) if(!string.IsNullOrWhiteSpace(refreshToken))
{ {

View File

@ -23,13 +23,13 @@ namespace Selector.Cache
protected readonly IUserApi UserClient; protected readonly IUserApi UserClient;
public PlayCountPuller( public PlayCountPuller(
IDatabaseAsync cache,
ILogger<PlayCountPuller> logger, ILogger<PlayCountPuller> logger,
ITrackApi trackClient, ITrackApi trackClient,
IAlbumApi albumClient, IAlbumApi albumClient,
IArtistApi artistClient, IArtistApi artistClient,
IUserApi userClient IUserApi userClient,
IDatabaseAsync cache = null
) )
{ {
Cache = cache; Cache = cache;
@ -45,14 +45,14 @@ namespace Selector.Cache
{ {
if (string.IsNullOrWhiteSpace(username)) throw new ArgumentNullException("No username provided"); if (string.IsNullOrWhiteSpace(username)) throw new ArgumentNullException("No username provided");
var trackCache = Cache.StringGetAsync(Key.TrackPlayCount(track, artist)); var trackCache = Cache?.StringGetAsync(Key.TrackPlayCount(track, artist));
var albumCache = Cache.StringGetAsync(Key.AlbumPlayCount(album, albumArtist)); var albumCache = Cache?.StringGetAsync(Key.AlbumPlayCount(album, albumArtist));
var artistCache = Cache.StringGetAsync(Key.ArtistPlayCount(artist)); var artistCache = Cache?.StringGetAsync(Key.ArtistPlayCount(artist));
var userCache = Cache.StringGetAsync(Key.UserPlayCount(username)); var userCache = Cache?.StringGetAsync(Key.UserPlayCount(username));
var cacheTasks = new Task[] { trackCache, albumCache, artistCache, userCache }; 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() PlayCount playCount = new()
{ {
@ -64,64 +64,36 @@ namespace Selector.Cache
Task<LastResponse<LastArtist>> artistHttp = null; Task<LastResponse<LastArtist>> artistHttp = null;
Task<LastResponse<LastUser>> userHttp = null; Task<LastResponse<LastUser>> 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 else
{ {
trackHttp = TrackClient.GetInfoAsync(track, artist, username); 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) playCount.Album = (int) albumCache.Result;
{
albumHttp = AlbumClient.GetInfoAsync(albumArtist, album, username: username);
}
else
{
playCount.Album = (int)albumCache.Result;
}
} }
else else
{ {
albumHttp = AlbumClient.GetInfoAsync(albumArtist, album, username: username); 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) playCount.Artist = (int) artistCache.Result;
{
artistHttp = ArtistClient.GetInfoAsync(artist);
}
else
{
playCount.Artist = (int)artistCache.Result;
}
} }
else else
{ {
artistHttp = ArtistClient.GetInfoAsync(artist); artistHttp = ArtistClient.GetInfoAsync(artist);
} }
if (userCache.IsCompletedSuccessfully) if (userCache is not null && userCache.IsCompletedSuccessfully && userCache.Result != RedisValue.Null)
{ {
if (userCache.Result == RedisValue.Null) playCount.User = (int) userCache.Result;
{
userHttp = UserClient.GetInfoAsync(username);
}
else
{
playCount.User = (int)userCache.Result;
}
} }
else else
{ {

View File

@ -64,11 +64,28 @@ namespace Selector.Model
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext> public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{ {
private static string GetPath(string env) => $"{@Directory.GetCurrentDirectory()}/../Selector.Web/appsettings.{env}.json";
public ApplicationDbContext CreateDbContext(string[] args) 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() IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory()) .SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(@Directory.GetCurrentDirectory() + "/../Selector.Web/appsettings.Development.json") .AddJsonFile(configFile)
.Build(); .Build();
var builder = new DbContextOptionsBuilder<ApplicationDbContext>(); var builder = new DbContextOptionsBuilder<ApplicationDbContext>();

View File

@ -22,7 +22,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.0" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -85,7 +85,7 @@ namespace Selector.Web.Areas.Identity.Pages.Account
var result = await _signInManager.PasswordSignInAsync(Input.Username, Input.Password, Input.RememberMe, lockoutOnFailure: false); var result = await _signInManager.PasswordSignInAsync(Input.Username, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded) if (result.Succeeded)
{ {
_logger.LogInformation("User logged in."); _logger.LogInformation($"[{Input.Username}] logged in.");
return LocalRedirect(returnUrl); return LocalRedirect(returnUrl);
} }
if (result.RequiresTwoFactor) if (result.RequiresTwoFactor)
@ -94,7 +94,7 @@ namespace Selector.Web.Areas.Identity.Pages.Account
} }
if (result.IsLockedOut) if (result.IsLockedOut)
{ {
_logger.LogWarning("User account locked out."); _logger.LogWarning($"[{Input.Username}] locked out.");
return RedirectToPage("./Lockout"); return RedirectToPage("./Lockout");
} }
else else

View File

@ -18,19 +18,10 @@ namespace Selector.Web.Pages
public class NowModel : PageModel public class NowModel : PageModel
{ {
private readonly ILogger<NowModel> Logger; private readonly ILogger<NowModel> Logger;
private readonly INowPlayingMappingFactory MappingFactory;
private readonly CacheHubProxy HubProxy;
private readonly UserManager<ApplicationUser> UserManager;
public NowModel(ILogger<NowModel> logger, public NowModel(ILogger<NowModel> logger)
INowPlayingMappingFactory mappingFactory,
CacheHubProxy hubProxy,
UserManager<ApplicationUser> userManager)
{ {
Logger = logger; Logger = logger;
MappingFactory = mappingFactory;
HubProxy = hubProxy;
UserManager = userManager;
} }
public void OnGet() public void OnGet()

View File

@ -34,7 +34,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="appsettings.Development.json"> <None Update="appsettings.Development.json" Condition="Exists('appsettings.Development.json')">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="appsettings.Release.json" Condition="Exists('appsettings.Release.json')">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Update="appsettings.json"> <None Update="appsettings.json">

View File

@ -97,11 +97,14 @@ namespace Selector.Web
services.AddRedisServices(config.RedisOptions.ConnectionString); services.AddRedisServices(config.RedisOptions.ConnectionString);
services.AddSpotify(); services.AddSpotify();
if (config.RedisOptions.Enabled)
{
Console.WriteLine("> Adding caching Spotify consumers...");
services.AddCachingSpotify(); services.AddCachingSpotify();
services.AddCacheHubProxy();
}
ConfigureLastFm(config, services); ConfigureLastFm(config, services);
services.AddCacheHubProxy();
} }
// 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.
@ -141,8 +144,13 @@ namespace Selector.Web
Console.WriteLine("> Adding Last.fm credentials..."); Console.WriteLine("> Adding Last.fm credentials...");
services.AddLastFm(config.LastfmClient, config.LastfmSecret); services.AddLastFm(config.LastfmClient, config.LastfmSecret);
if (config.RedisOptions.Enabled)
{
Console.WriteLine("> Adding caching Last.fm consumers...");
services.AddCachingLastFm(); services.AddCachingLastFm();
} }
}
else else
{ {
Console.WriteLine("> No Last.fm credentials, skipping init..."); Console.WriteLine("> No Last.fm credentials, skipping init...");

View File

@ -22,6 +22,7 @@ namespace Selector
} }
public abstract Task WatchOne(CancellationToken token); public abstract Task WatchOne(CancellationToken token);
public abstract Task Reset();
public async Task Watch(CancellationToken cancelToken) public async Task Watch(CancellationToken cancelToken)
{ {

View File

@ -13,7 +13,7 @@ namespace Selector
public class WatcherCollection: IWatcherCollection, IDisposable, IEnumerable<WatcherContext> public class WatcherCollection: IWatcherCollection, IDisposable, IEnumerable<WatcherContext>
{ {
private readonly ILogger<WatcherCollection> Logger; private readonly ILogger<WatcherCollection> Logger;
public bool IsRunning { get; private set; } = true; public bool IsRunning { get; private set; } = false;
private List<WatcherContext> Watchers { get; set; } = new(); private List<WatcherContext> Watchers { get; set; } = new();
public WatcherCollection(ILogger<WatcherCollection> logger = null) public WatcherCollection(ILogger<WatcherCollection> logger = null)

View File

@ -58,10 +58,24 @@ namespace Selector
Consumers.ForEach(c => c.Subscribe(Watcher)); 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 = Watcher.Watch(TokenSource.Token);
Task.ContinueWith(t => Task.ContinueWith(t =>
{ {
if (t.Exception != null) throw t.Exception; if (t.Exception != null) throw t.Exception;
Watcher.Reset();
Reset();
}, TaskContinuationOptions.OnlyOnFaulted); }, TaskContinuationOptions.OnlyOnFaulted);
} }

View File

@ -8,6 +8,7 @@ namespace Selector
/// <summary> /// <summary>
/// Track or episode changes /// Track or episode changes
/// </summary> /// </summary>
public event EventHandler<ListeningChangeEventArgs> NetworkPoll;
public event EventHandler<ListeningChangeEventArgs> ItemChange; public event EventHandler<ListeningChangeEventArgs> ItemChange;
public event EventHandler<ListeningChangeEventArgs> AlbumChange; public event EventHandler<ListeningChangeEventArgs> AlbumChange;
public event EventHandler<ListeningChangeEventArgs> ArtistChange; public event EventHandler<ListeningChangeEventArgs> ArtistChange;

View File

@ -18,6 +18,8 @@ namespace Selector
/// <returns></returns> /// <returns></returns>
public Task Watch(CancellationToken cancelToken); public Task Watch(CancellationToken cancelToken);
public Task Reset();
/// <summary> /// <summary>
/// Time interval in ms between polls from Watch() /// Time interval in ms between polls from Watch()
/// </summary> /// </summary>

View File

@ -15,6 +15,7 @@ namespace Selector
private readonly IPlayerClient spotifyClient; private readonly IPlayerClient spotifyClient;
private readonly IEqual eq; private readonly IEqual eq;
public event EventHandler<ListeningChangeEventArgs> NetworkPoll;
public event EventHandler<ListeningChangeEventArgs> ItemChange; public event EventHandler<ListeningChangeEventArgs> ItemChange;
public event EventHandler<ListeningChangeEventArgs> AlbumChange; public event EventHandler<ListeningChangeEventArgs> AlbumChange;
public event EventHandler<ListeningChangeEventArgs> ArtistChange; public event EventHandler<ListeningChangeEventArgs> ArtistChange;
@ -26,6 +27,7 @@ namespace Selector
public event EventHandler<ListeningChangeEventArgs> PlayingChange; public event EventHandler<ListeningChangeEventArgs> PlayingChange;
public CurrentlyPlayingContext Live { get; private set; } public CurrentlyPlayingContext Live { get; private set; }
private CurrentlyPlayingContext Previous { get; set; }
public PlayerTimeline Past { get; set; } = new(); public PlayerTimeline Past { get; set; } = new();
public PlayerWatcher(IPlayerClient spotifyClient, public PlayerWatcher(IPlayerClient spotifyClient,
@ -40,6 +42,15 @@ namespace Selector
PollPeriod = pollPeriod; PollPeriod = pollPeriod;
} }
public override Task Reset()
{
Previous = null;
Live = null;
Past = new();
return Task.CompletedTask;
}
public override async Task WatchOne(CancellationToken token = default) public override async Task WatchOne(CancellationToken token = default)
{ {
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
@ -52,104 +63,100 @@ namespace Selector
if (polledCurrent != null) StoreCurrentPlaying(polledCurrent); if (polledCurrent != null) StoreCurrentPlaying(polledCurrent);
// swap new item into live and bump existing down to previous // swap new item into live and bump existing down to previous
CurrentlyPlayingContext previous; Previous = Live;
if(Live is null) {
Live = polledCurrent; Live = polledCurrent;
previous = polledCurrent;
} OnNetworkPoll(GetEvent());
else {
previous = Live;
Live = polledCurrent;
}
// NOT PLAYING // NOT PLAYING
if(previous is null && Live is null) if(Previous is null && Live is null)
{ {
// Console.WriteLine("not playing"); // Console.WriteLine("not playing");
} }
else else
{ {
// STARTED PLAYBACK // STARTED PLAYBACK
if(previous is null if(Previous is null && Live is not null)
&& (Live.Item is FullTrack || Live.Item is FullEpisode))
{ {
Logger.LogDebug($"Playback started: {Live.DisplayString()}"); Logger.LogDebug($"Playback started: {Live.DisplayString()}");
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); OnPlayingChange(GetEvent());
OnItemChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); OnItemChange(GetEvent());
OnContextChange(GetEvent());
} }
// STOPPED PLAYBACK // STOPPED PLAYBACK
else if((previous.Item is FullTrack || previous.Item is FullEpisode) else if(Previous is not null && Live is null)
&& Live is null)
{ {
Logger.LogDebug($"Playback stopped: {previous.DisplayString()}"); Logger.LogDebug($"Playback stopped: {Previous.DisplayString()}");
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); OnPlayingChange(GetEvent());
OnItemChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); OnItemChange(GetEvent());
OnContextChange(GetEvent());
} }
// CONTINUING PLAYBACK // CONTINUING PLAYBACK
else { else {
// MUSIC // MUSIC
if(previous.Item is FullTrack previousTrack if(Previous.Item is FullTrack previousTrack
&& Live.Item is FullTrack currentTrack) && Live.Item is FullTrack currentTrack)
{ {
if(!eq.IsEqual(previousTrack, currentTrack)) { if(!eq.IsEqual(previousTrack, currentTrack)) {
Logger.LogDebug($"Track changed: {previousTrack.DisplayString()} -> {currentTrack.DisplayString()}"); 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)) { if(!eq.IsEqual(previousTrack.Album, currentTrack.Album)) {
Logger.LogDebug($"Album changed: {previousTrack.Album.DisplayString()} -> {currentTrack.Album.DisplayString()}"); 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])) { if(!eq.IsEqual(previousTrack.Artists[0], currentTrack.Artists[0])) {
Logger.LogDebug($"Artist changed: {previousTrack.Artists.DisplayString()} -> {currentTrack.Artists.DisplayString()}"); Logger.LogDebug($"Artist changed: {previousTrack.Artists.DisplayString()} -> {currentTrack.Artists.DisplayString()}");
OnArtistChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); OnArtistChange(GetEvent());
} }
} }
// CHANGED CONTENT // CHANGED CONTENT TYPE
else if((previous.Item is FullTrack && Live.Item is FullEpisode) else if((Previous.Item is FullTrack && Live.Item is FullEpisode)
|| (previous.Item is FullEpisode && Live.Item is FullTrack)) || (Previous.Item is FullEpisode && Live.Item is FullTrack))
{ {
Logger.LogDebug($"Media type changed: {previous.Item}, {previous.Item}"); Logger.LogDebug($"Media type changed: {Previous.Item}, {Previous.Item}");
OnContentChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); OnContentChange(GetEvent());
OnItemChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); OnItemChange(GetEvent());
} }
// PODCASTS // PODCASTS
else if(previous.Item is FullEpisode previousEp else if(Previous.Item is FullEpisode previousEp
&& Live.Item is FullEpisode currentEp) && Live.Item is FullEpisode currentEp)
{ {
if(!eq.IsEqual(previousEp, currentEp)) { if(!eq.IsEqual(previousEp, currentEp)) {
Logger.LogDebug($"Podcast changed: {previousEp.DisplayString()} -> {currentEp.DisplayString()}"); Logger.LogDebug($"Podcast changed: {previousEp.DisplayString()} -> {currentEp.DisplayString()}");
OnItemChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); OnItemChange(GetEvent());
} }
} }
else { else {
Logger.LogError($"Unknown combination of previous and current playing contexts, [{Previous.DisplayString()}] [{Live.DisplayString()}]");
throw new NotSupportedException("Unknown item combination"); throw new NotSupportedException("Unknown item combination");
} }
// CONTEXT // CONTEXT
if(!eq.IsEqual(previous.Context, Live.Context)) { if(!eq.IsEqual(Previous.Context, Live.Context)) {
Logger.LogDebug($"Context changed: {previous.Context.DisplayString()} -> {Live.Context.DisplayString()}"); Logger.LogDebug($"Context changed: {Previous.Context.DisplayString()} -> {Live.Context.DisplayString()}");
OnContextChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); OnContextChange(GetEvent());
} }
// DEVICE // DEVICE
if(!eq.IsEqual(previous?.Device, Live?.Device)) { if(!eq.IsEqual(Previous?.Device, Live?.Device)) {
Logger.LogDebug($"Device changed: {previous?.Device.DisplayString()} -> {Live?.Device.DisplayString()}"); Logger.LogDebug($"Device changed: {Previous?.Device.DisplayString()} -> {Live?.Device.DisplayString()}");
OnDeviceChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); OnDeviceChange(GetEvent());
} }
// IS PLAYING // IS PLAYING
if(previous.IsPlaying != Live.IsPlaying) { if(Previous.IsPlaying != Live.IsPlaying) {
Logger.LogDebug($"Playing state changed: {previous.IsPlaying} -> {Live.IsPlaying}"); Logger.LogDebug($"Playing state changed: {Previous.IsPlaying} -> {Live.IsPlaying}");
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); OnPlayingChange(GetEvent());
} }
// VOLUME // VOLUME
if(previous.Device.VolumePercent != Live.Device.VolumePercent) { if(Previous.Device.VolumePercent != Live.Device.VolumePercent) {
Logger.LogDebug($"Volume changed: {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)); OnVolumeChange(GetEvent());
} }
} }
} }
@ -172,6 +179,8 @@ namespace Selector
} }
} }
private ListeningChangeEventArgs GetEvent() => ListeningChangeEventArgs.From(Previous, Live, Past, id: Id, username: SpotifyUsername);
/// <summary> /// <summary>
/// Store currently playing in last plays. Determine whether new list or appending required /// Store currently playing in last plays. Determine whether new list or appending required
/// </summary> /// </summary>
@ -182,6 +191,11 @@ namespace Selector
} }
#region Event Firers #region Event Firers
protected virtual void OnNetworkPoll(ListeningChangeEventArgs args)
{
NetworkPoll?.Invoke(this, args);
}
protected virtual void OnItemChange(ListeningChangeEventArgs args) protected virtual void OnItemChange(ListeningChangeEventArgs args)
{ {
ItemChange?.Invoke(this, args); ItemChange?.Invoke(this, args);