parent
9dfad73397
commit
d139dce719
@ -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>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
<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"
|
||||||
|
internalLogFile=".\log\selector.nlog.log"
|
||||||
internalLogLevel="Info" >
|
internalLogLevel="Info" >
|
||||||
|
|
||||||
<variable name="format"
|
<variable name="format"
|
||||||
@ -14,11 +15,11 @@
|
|||||||
<!-- 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"
|
||||||
@ -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>
|
@ -42,11 +42,28 @@ namespace Selector
|
|||||||
{
|
{
|
||||||
if (e.Current.Item is FullTrack track)
|
if (e.Current.Item is FullTrack track)
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
Logger.LogTrace("Making Spotify call");
|
Logger.LogTrace("Making Spotify call");
|
||||||
var audioFeatures = await TrackClient.GetAudioFeatures(track.Id);
|
var audioFeatures = await TrackClient.GetAudioFeatures(track.Id);
|
||||||
Logger.LogDebug($"Adding audio features [{track.DisplayString()}]: [{audioFeatures.DisplayString()}]");
|
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)
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user