not throwing expired token exceptions, typed config in set up

fixes #5
This commit is contained in:
andy 2021-10-15 19:58:07 +01:00
parent 9dfad73397
commit d139dce719
8 changed files with 83 additions and 35 deletions

View File

@ -23,40 +23,39 @@ namespace Selector.CLI
Console.WriteLine("~~~ Selector CLI ~~~"); Console.WriteLine("~~~ Selector CLI ~~~");
Console.WriteLine(""); Console.WriteLine("");
Console.WriteLine("Configuring..."); Console.WriteLine("> Configuring...");
// CONFIG // CONFIG
services.Configure<RootOptions>(options => { services.Configure<RootOptions>(options => {
context.Configuration.GetSection(RootOptions.Key).Bind(options); context.Configuration.GetSection(RootOptions.Key).Bind(options);
context.Configuration.GetSection($"{RootOptions.Key}:{WatcherOptions.Key}").Bind(options.WatcherOptions); context.Configuration.GetSection($"{RootOptions.Key}:{WatcherOptions.Key}").Bind(options.WatcherOptions);
}); });
var config = context.Configuration.GetSection(RootOptions.Key).Get<RootOptions>();
Console.WriteLine("Adding Services..."); Console.WriteLine("> Adding Services...");
// SERVICES // SERVICES
services.AddSingleton<IWatcherFactory, WatcherFactory>(); services.AddSingleton<IWatcherFactory, WatcherFactory>();
services.AddSingleton<IConsumerFactory, AudioFeatureInjectorFactory>(); services.AddSingleton<IConsumerFactory, AudioFeatureInjectorFactory>();
services.AddSingleton<IWatcherCollectionFactory, WatcherCollectionFactory>(); services.AddSingleton<IWatcherCollectionFactory, WatcherCollectionFactory>();
// For generating spotify clients // For generating spotify clients
services.AddSingleton<IRefreshTokenFactoryProvider, RefreshTokenFactoryProvider>(); //services.AddSingleton<IRefreshTokenFactoryProvider, RefreshTokenFactoryProvider>();
services.AddSingleton<IRefreshTokenFactoryProvider, CachingRefreshTokenFactoryProvider>();
switch(context.Configuration.GetValue<EqualityChecker>("selector:equality")) switch(config.Equality)
{ {
case EqualityChecker.Uri: case EqualityChecker.Uri:
Console.WriteLine("Using Uri Equality"); Console.WriteLine("> Using Uri Equality");
services.AddTransient<IEqual, UriEqual>(); services.AddTransient<IEqual, UriEqual>();
break; break;
case EqualityChecker.String: case EqualityChecker.String:
Console.WriteLine("Using String Equality"); Console.WriteLine("> Using String Equality");
services.AddTransient<IEqual, StringEqual>(); services.AddTransient<IEqual, StringEqual>();
break; break;
} }
// HOSTED SERVICES // HOSTED SERVICES
if(context.Configuration if(config.WatcherOptions.Enabled)
.GetSection($"{RootOptions.Key}:{WatcherOptions.Key}")
.Get<WatcherOptions>()
.Enabled)
{ {
Console.WriteLine("Adding Watcher Service"); Console.WriteLine("> Adding Watcher Service");
services.AddHostedService<WatcherService>(); services.AddHostedService<WatcherService>();
} }

View File

@ -1,26 +1,27 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<!-- XSD manual extracted from package NLog.Schema: https://www.nuget.org/packages/NLog.Schema--> <!-- XSD manual extracted from package NLog.Schema: https://www.nuget.org/packages/NLog.Schema-->
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xsi:schemaLocation="NLog NLog.xsd" <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xsi:schemaLocation="NLog NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true" autoReload="true"
internalLogFile=".\selector.nlog.log" throwConfigExceptions="true"
internalLogLevel="Info" > internalLogFile=".\log\selector.nlog.log"
internalLogLevel="Info" >
<variable name="format" <variable name="format"
value="${longdate}|${level:uppercase=true}|${callsite}:${callsite-linenumber}|${message}${onexception:inner=${newline}}${exception:format=tostring,data:exceptionDataSeparator=\r\n}"/> value="${longdate}|${level:uppercase=true}|${callsite}:${callsite-linenumber}|${message}${onexception:inner=${newline}}${exception:format=tostring,data:exceptionDataSeparator=\r\n}"/>
<!-- the targets to write to --> <!-- the targets to write to -->
<targets> <targets>
<!-- write logs to file --> <!-- write logs to file -->
<target xsi:type="File" <target xsi:type="File"
name="logfile" name="logfile"
fileName=".\selector.log" fileName=".\log\selector-${shortdate}.log"
layout="${format}" /> layout="${format}" />
<target xsi:type="File" <target xsi:type="File"
name="tracefile" name="tracefile"
fileName=".\selector.trace.log" fileName=".\log\selector.trace-${shortdate}.log"
layout="${format}" /> layout="${format}" />
<target xsi:type="Console" <target xsi:type="Console"
name="logconsole" name="logconsole"
layout="${format}" /> layout="${format}" />
</targets> </targets>
@ -29,7 +30,6 @@
<rules> <rules>
<logger name="*" minlevel="Debug" writeTo="logfile" /> <logger name="*" minlevel="Debug" writeTo="logfile" />
<logger name="*" minlevel="Trace" writeTo="tracefile" /> <logger name="*" minlevel="Trace" writeTo="tracefile" />
<logger name="Selector.*" minlevel="Debug" writeTo="logconsole" /> <logger name="Selector.*" minlevel="Debug" writeTo="logconsole" />
</rules> </rules>
</nlog> </nlog>

View File

@ -42,11 +42,28 @@ namespace Selector
{ {
if (e.Current.Item is FullTrack track) if (e.Current.Item is FullTrack track)
{ {
Logger.LogTrace("Making Spotify call"); try {
var audioFeatures = await TrackClient.GetAudioFeatures(track.Id); Logger.LogTrace("Making Spotify call");
Logger.LogDebug($"Adding audio features [{track.DisplayString()}]: [{audioFeatures.DisplayString()}]"); 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) else if (e.Current.Item is FullEpisode episode)
{ {

View File

@ -17,11 +17,32 @@ namespace Selector
public static string DisplayString(this SimpleShow show) => $"{show.Name} / {show.Publisher}"; 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 Context context) => $"{context.Type}, {context.Uri}";
public static string DisplayString(this Device device) => $"{device.Name} ({device.Id}) {device.VolumePercent}%"; 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<SimpleArtist> artists) => string.Join(", ", artists.Select(a => a.DisplayString())); public static string DisplayString(this IEnumerable<SimpleArtist> 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;
} }
} }

View File

@ -32,7 +32,7 @@ namespace Selector
TokenType = refreshed.TokenType, TokenType = refreshed.TokenType,
ExpiresIn = refreshed.ExpiresIn, ExpiresIn = refreshed.ExpiresIn,
Scope = refreshed.Scope, Scope = refreshed.Scope,
RefreshToken = refreshed.RefreshToken, RefreshToken = refreshed.RefreshToken ?? RefreshToken,
CreatedAt = refreshed.CreatedAt CreatedAt = refreshed.CreatedAt
})); }));

View File

@ -9,11 +9,18 @@ namespace Selector
{ {
public class CachingRefreshTokenFactoryProvider : RefreshTokenFactoryProvider public class CachingRefreshTokenFactoryProvider : RefreshTokenFactoryProvider
{ {
protected readonly ILogger<CachingRefreshTokenFactoryProvider> Logger;
public CachingRefreshTokenFactoryProvider(ILogger<CachingRefreshTokenFactoryProvider> logger)
{
Logger = logger;
}
protected Dictionary<string, RefreshTokenFactory> Configs = new(); protected Dictionary<string, RefreshTokenFactory> Configs = new();
public RefreshTokenFactory GetUserConfig(string userId) => Configs.ContainsKey(userId) ? Configs[userId] : null; public RefreshTokenFactory GetUserConfig(string userId) => Configs.ContainsKey(userId) ? Configs[userId] : null;
new public async Task<RefreshTokenFactory> GetFactory(string refreshToken) public override async Task<RefreshTokenFactory> GetFactory(string refreshToken)
{ {
var configProvider = await base.GetFactory(refreshToken); var configProvider = await base.GetFactory(refreshToken);
var newConfig = await configProvider.GetConfig(); var newConfig = await configProvider.GetConfig();
@ -27,6 +34,7 @@ namespace Selector
} }
else else
{ {
Logger.LogDebug($"New user token factory added [{userDetails.DisplayName}]");
Configs[userDetails.Id] = configProvider; Configs[userDetails.Id] = configProvider;
return configProvider; return configProvider;
} }

View File

@ -22,7 +22,7 @@ namespace Selector
public bool Initialised => !string.IsNullOrWhiteSpace(ClientId) && !string.IsNullOrWhiteSpace(ClientSecret); public bool Initialised => !string.IsNullOrWhiteSpace(ClientId) && !string.IsNullOrWhiteSpace(ClientSecret);
public Task<RefreshTokenFactory> GetFactory(string refreshToken) public virtual Task<RefreshTokenFactory> GetFactory(string refreshToken)
{ {
if(!Initialised) throw new InvalidOperationException("Factory not initialised"); if(!Initialised) throw new InvalidOperationException("Factory not initialised");
if(string.IsNullOrEmpty(refreshToken)) throw new ArgumentException("Null or empty refresh key provided"); if(string.IsNullOrEmpty(refreshToken)) throw new ArgumentException("Null or empty refresh key provided");

View File

@ -26,7 +26,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; }
public PlayerTimeline Past { get; set; } public PlayerTimeline Past { get; set; } = new();
public PlayerWatcher(IPlayerClient spotifyClient, public PlayerWatcher(IPlayerClient spotifyClient,
IEqual equalityChecker, IEqual equalityChecker,
@ -154,14 +154,17 @@ namespace Selector
} }
catch(APIUnauthorizedException e) catch(APIUnauthorizedException e)
{ {
throw e; Logger.LogDebug($"Unauthorised error: [{e.Message}] (should be refreshed and retried?)");
//throw e;
} }
catch(APITooManyRequestsException e) catch(APITooManyRequestsException e)
{ {
Logger.LogDebug($"Too many requests error: [{e.Message}]");
throw e; throw e;
} }
catch(APIException e) catch(APIException e)
{ {
Logger.LogDebug($"API error: [{e.Message}]");
throw e; throw e;
} }
} }