working without redis, player watcher event tweaking, graceful exception handling
This commit is contained in:
parent
35eee0f068
commit
a401280edf
@ -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))
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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">
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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>();
|
||||||
|
|
||||||
|
@ -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))
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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>();
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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">
|
||||||
|
@ -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...");
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user