diff --git a/Selector.CLI/Program.cs b/Selector.CLI/Program.cs index fa74e73..5182086 100644 --- a/Selector.CLI/Program.cs +++ b/Selector.CLI/Program.cs @@ -23,40 +23,39 @@ namespace Selector.CLI Console.WriteLine("~~~ Selector CLI ~~~"); Console.WriteLine(""); - Console.WriteLine("Configuring..."); + Console.WriteLine("> Configuring..."); // CONFIG services.Configure(options => { context.Configuration.GetSection(RootOptions.Key).Bind(options); context.Configuration.GetSection($"{RootOptions.Key}:{WatcherOptions.Key}").Bind(options.WatcherOptions); }); + var config = context.Configuration.GetSection(RootOptions.Key).Get(); - Console.WriteLine("Adding Services..."); + Console.WriteLine("> Adding Services..."); // SERVICES services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); // For generating spotify clients - services.AddSingleton(); + //services.AddSingleton(); + services.AddSingleton(); - switch(context.Configuration.GetValue("selector:equality")) + switch(config.Equality) { case EqualityChecker.Uri: - Console.WriteLine("Using Uri Equality"); + Console.WriteLine("> Using Uri Equality"); services.AddTransient(); break; case EqualityChecker.String: - Console.WriteLine("Using String Equality"); + Console.WriteLine("> Using String Equality"); services.AddTransient(); break; } // HOSTED SERVICES - if(context.Configuration - .GetSection($"{RootOptions.Key}:{WatcherOptions.Key}") - .Get() - .Enabled) + if(config.WatcherOptions.Enabled) { - Console.WriteLine("Adding Watcher Service"); + Console.WriteLine("> Adding Watcher Service"); services.AddHostedService(); } diff --git a/Selector.CLI/nlog.config b/Selector.CLI/nlog.config index 9bc7668..002c204 100644 --- a/Selector.CLI/nlog.config +++ b/Selector.CLI/nlog.config @@ -1,26 +1,27 @@ - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + autoReload="true" + throwConfigExceptions="true" + internalLogFile=".\log\selector.nlog.log" + internalLogLevel="Info" > + - - - @@ -29,7 +30,6 @@ - \ No newline at end of file diff --git a/Selector/Consumers/AudioFeatureInjector.cs b/Selector/Consumers/AudioFeatureInjector.cs index 9b079b6..2e12910 100644 --- a/Selector/Consumers/AudioFeatureInjector.cs +++ b/Selector/Consumers/AudioFeatureInjector.cs @@ -42,11 +42,28 @@ namespace Selector { if (e.Current.Item is FullTrack track) { - Logger.LogTrace("Making Spotify call"); - var audioFeatures = await TrackClient.GetAudioFeatures(track.Id); - Logger.LogDebug($"Adding audio features [{track.DisplayString()}]: [{audioFeatures.DisplayString()}]"); + try { + Logger.LogTrace("Making Spotify call"); + var audioFeatures = await TrackClient.GetAudioFeatures(track.Id); + Logger.LogDebug($"Adding audio features [{track.DisplayString()}]: [{audioFeatures.DisplayString()}]"); - Timeline.Add(AnalysedTrack.From(track, audioFeatures)); + Timeline.Add(AnalysedTrack.From(track, audioFeatures), DateHelper.FromUnixMilli(e.Current.Timestamp)); + } + catch (APIUnauthorizedException ex) + { + Logger.LogDebug($"Unauthorised error: [{ex.Message}] (should be refreshed and retried?)"); + //throw ex; + } + catch (APITooManyRequestsException ex) + { + Logger.LogDebug($"Too many requests error: [{ex.Message}]"); + throw ex; + } + catch (APIException ex) + { + Logger.LogDebug($"API error: [{ex.Message}]"); + throw ex; + } } else if (e.Current.Item is FullEpisode episode) { diff --git a/Selector/Helpers/SpotifyExtensions.cs b/Selector/Helpers/SpotifyExtensions.cs index 76b954d..cbab6ec 100644 --- a/Selector/Helpers/SpotifyExtensions.cs +++ b/Selector/Helpers/SpotifyExtensions.cs @@ -17,11 +17,32 @@ namespace Selector public static string DisplayString(this SimpleShow show) => $"{show.Name} / {show.Publisher}"; - public static string DisplayString(this CurrentlyPlayingContext currentPlaying) => $"{currentPlaying.IsPlaying}, {currentPlaying.Item}, {currentPlaying.Device.DisplayString()}"; + public static string DisplayString(this CurrentlyPlayingContext currentPlaying) { + + if (currentPlaying.Item is FullTrack track) + { + return $"{currentPlaying.IsPlaying}, {track.DisplayString()}, {currentPlaying.Device.DisplayString()}"; + } + else if (currentPlaying.Item is FullEpisode episode) + { + return $"{currentPlaying.IsPlaying}, {episode.DisplayString()}, {currentPlaying.Device.DisplayString()}"; + } + else + { + throw new ArgumentException("Unknown playing type"); + } + } + public static string DisplayString(this Context context) => $"{context.Type}, {context.Uri}"; public static string DisplayString(this Device device) => $"{device.Name} ({device.Id}) {device.VolumePercent}%"; - public static string DisplayString(this TrackAudioFeatures feature) => $"Acou. {feature.Acousticness}, Dance {feature.Danceability}, Energy {feature.Energy}, Instru. {feature.Instrumentalness}, Key {feature.Key}, Live {feature.Liveness}, Loud {feature.Loudness}, Mode {feature.Mode}, Speech {feature.Speechiness}, Tempo {feature.Tempo}, Valence {feature.Valence}"; + public static string DisplayString(this TrackAudioFeatures feature) => $"Acou. {feature.Acousticness}, Dance {feature.Danceability}, Energy {feature.Energy}, Instru. {feature.Instrumentalness}, Key {feature.Key}, Live {feature.Liveness}, Loud {feature.Loudness}dB, Mode {feature.Mode}, Speech {feature.Speechiness}, Tempo {feature.Tempo}BPM, Time Sig. {feature.TimeSignature}, Valence {feature.Valence}"; public static string DisplayString(this IEnumerable artists) => string.Join(", ", artists.Select(a => a.DisplayString())); + + public static bool IsInstrumental(this TrackAudioFeatures feature) => feature.Instrumentalness > 0.5; + public static bool IsLive(this TrackAudioFeatures feature) => feature.Liveness > 0.8f; + public static bool IsSpokenWord(this TrackAudioFeatures feature) => feature.Speechiness > 0.66f; + public static bool IsSpeechAndMusic(this TrackAudioFeatures feature) => feature.Speechiness is >= 0.33f and <= 0.66f; + public static bool IsNotSpeech(this TrackAudioFeatures feature) => feature.Speechiness < 0.33f; } } diff --git a/Selector/Spotify/ConfigFactory/RefreshTokenFactory.cs b/Selector/Spotify/ConfigFactory/RefreshTokenFactory.cs index 45a4a1f..3321ed3 100644 --- a/Selector/Spotify/ConfigFactory/RefreshTokenFactory.cs +++ b/Selector/Spotify/ConfigFactory/RefreshTokenFactory.cs @@ -32,7 +32,7 @@ namespace Selector TokenType = refreshed.TokenType, ExpiresIn = refreshed.ExpiresIn, Scope = refreshed.Scope, - RefreshToken = refreshed.RefreshToken, + RefreshToken = refreshed.RefreshToken ?? RefreshToken, CreatedAt = refreshed.CreatedAt })); diff --git a/Selector/Spotify/FactoryProvider/CachingRefreshTokenFactoryProvider.cs b/Selector/Spotify/FactoryProvider/CachingRefreshTokenFactoryProvider.cs index e5c9469..07648ce 100644 --- a/Selector/Spotify/FactoryProvider/CachingRefreshTokenFactoryProvider.cs +++ b/Selector/Spotify/FactoryProvider/CachingRefreshTokenFactoryProvider.cs @@ -9,11 +9,18 @@ namespace Selector { public class CachingRefreshTokenFactoryProvider : RefreshTokenFactoryProvider { + protected readonly ILogger Logger; + + public CachingRefreshTokenFactoryProvider(ILogger logger) + { + Logger = logger; + } + protected Dictionary Configs = new(); public RefreshTokenFactory GetUserConfig(string userId) => Configs.ContainsKey(userId) ? Configs[userId] : null; - new public async Task GetFactory(string refreshToken) + public override async Task GetFactory(string refreshToken) { var configProvider = await base.GetFactory(refreshToken); var newConfig = await configProvider.GetConfig(); @@ -27,6 +34,7 @@ namespace Selector } else { + Logger.LogDebug($"New user token factory added [{userDetails.DisplayName}]"); Configs[userDetails.Id] = configProvider; return configProvider; } diff --git a/Selector/Spotify/FactoryProvider/RefreshTokenFactoryProvider.cs b/Selector/Spotify/FactoryProvider/RefreshTokenFactoryProvider.cs index cd45310..46ccf5f 100644 --- a/Selector/Spotify/FactoryProvider/RefreshTokenFactoryProvider.cs +++ b/Selector/Spotify/FactoryProvider/RefreshTokenFactoryProvider.cs @@ -22,7 +22,7 @@ namespace Selector public bool Initialised => !string.IsNullOrWhiteSpace(ClientId) && !string.IsNullOrWhiteSpace(ClientSecret); - public Task GetFactory(string refreshToken) + public virtual Task GetFactory(string refreshToken) { if(!Initialised) throw new InvalidOperationException("Factory not initialised"); if(string.IsNullOrEmpty(refreshToken)) throw new ArgumentException("Null or empty refresh key provided"); diff --git a/Selector/Watcher/PlayerWatcher.cs b/Selector/Watcher/PlayerWatcher.cs index deee447..becbe6e 100644 --- a/Selector/Watcher/PlayerWatcher.cs +++ b/Selector/Watcher/PlayerWatcher.cs @@ -26,7 +26,7 @@ namespace Selector public event EventHandler PlayingChange; public CurrentlyPlayingContext Live { get; private set; } - public PlayerTimeline Past { get; set; } + public PlayerTimeline Past { get; set; } = new(); public PlayerWatcher(IPlayerClient spotifyClient, IEqual equalityChecker, @@ -154,14 +154,17 @@ namespace Selector } catch(APIUnauthorizedException e) { - throw e; + Logger.LogDebug($"Unauthorised error: [{e.Message}] (should be refreshed and retried?)"); + //throw e; } catch(APITooManyRequestsException e) { + Logger.LogDebug($"Too many requests error: [{e.Message}]"); throw e; } catch(APIException e) { + Logger.LogDebug($"API error: [{e.Message}]"); throw e; } }