adding consumer, audio features injector
This commit is contained in:
parent
b5956ef4a0
commit
9dfad73397
@ -27,6 +27,7 @@ namespace Selector.CLI
|
|||||||
{
|
{
|
||||||
public const string Key = "Watcher";
|
public const string Key = "Watcher";
|
||||||
|
|
||||||
|
public bool Enabled { get; set; } = true;
|
||||||
public List<WatcherInstanceOptions> Instances { get; set; } = new();
|
public List<WatcherInstanceOptions> Instances { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,6 +40,7 @@ namespace Selector.CLI
|
|||||||
public string RefreshKey { get; set; }
|
public string RefreshKey { get; set; }
|
||||||
public int PollPeriod { get; set; } = 5000;
|
public int PollPeriod { get; set; } = 5000;
|
||||||
public WatcherType Type { get; set; } = WatcherType.Player;
|
public WatcherType Type { get; set; } = WatcherType.Player;
|
||||||
|
public List<Consumers> Consumers { get; set; } = default;
|
||||||
#nullable enable
|
#nullable enable
|
||||||
public string? PlaylistUri { get; set; }
|
public string? PlaylistUri { get; set; }
|
||||||
public string? WatcherCollection { get; set; }
|
public string? WatcherCollection { get; set; }
|
||||||
@ -49,4 +51,9 @@ namespace Selector.CLI
|
|||||||
{
|
{
|
||||||
Player, Playlist
|
Player, Playlist
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Consumers
|
||||||
|
{
|
||||||
|
AudioFeatures
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,14 +20,20 @@ namespace Selector.CLI
|
|||||||
=> Host.CreateDefaultBuilder(args)
|
=> Host.CreateDefaultBuilder(args)
|
||||||
.ConfigureServices((context, services) => {
|
.ConfigureServices((context, services) => {
|
||||||
|
|
||||||
|
Console.WriteLine("~~~ Selector CLI ~~~");
|
||||||
|
Console.WriteLine("");
|
||||||
|
|
||||||
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Console.WriteLine("Adding Services...");
|
||||||
// SERVICES
|
// SERVICES
|
||||||
services.AddSingleton<IWatcherFactory, WatcherFactory>();
|
services.AddSingleton<IWatcherFactory, WatcherFactory>();
|
||||||
|
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>();
|
||||||
@ -35,15 +41,25 @@ namespace Selector.CLI
|
|||||||
switch(context.Configuration.GetValue<EqualityChecker>("selector:equality"))
|
switch(context.Configuration.GetValue<EqualityChecker>("selector:equality"))
|
||||||
{
|
{
|
||||||
case EqualityChecker.Uri:
|
case EqualityChecker.Uri:
|
||||||
|
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");
|
||||||
services.AddTransient<IEqual, StringEqual>();
|
services.AddTransient<IEqual, StringEqual>();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// HOSTED SERVICES
|
// HOSTED SERVICES
|
||||||
services.AddHostedService<WatcherService>();
|
if(context.Configuration
|
||||||
|
.GetSection($"{RootOptions.Key}:{WatcherOptions.Key}")
|
||||||
|
.Get<WatcherOptions>()
|
||||||
|
.Enabled)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Adding Watcher Service");
|
||||||
|
services.AddHostedService<WatcherService>();
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
.ConfigureLogging((context, builder) => {
|
.ConfigureLogging((context, builder) => {
|
||||||
builder.ClearProviders();
|
builder.ClearProviders();
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"DOTNET_ENVIRONMENT": "Development"
|
"DOTNET_ENVIRONMENT": "Development"
|
||||||
}
|
},
|
||||||
|
"nativeDebugging": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,6 +17,7 @@ namespace Selector.CLI
|
|||||||
private const string ConfigInstanceKey = "localconfig";
|
private const string ConfigInstanceKey = "localconfig";
|
||||||
|
|
||||||
private readonly ILogger<WatcherService> Logger;
|
private readonly ILogger<WatcherService> Logger;
|
||||||
|
private readonly ILoggerFactory LoggerFactory;
|
||||||
private readonly RootOptions Config;
|
private readonly RootOptions Config;
|
||||||
private readonly IWatcherFactory WatcherFactory;
|
private readonly IWatcherFactory WatcherFactory;
|
||||||
private readonly IWatcherCollectionFactory WatcherCollectionFactory;
|
private readonly IWatcherCollectionFactory WatcherCollectionFactory;
|
||||||
@ -28,10 +29,11 @@ namespace Selector.CLI
|
|||||||
IWatcherFactory watcherFactory,
|
IWatcherFactory watcherFactory,
|
||||||
IWatcherCollectionFactory watcherCollectionFactory,
|
IWatcherCollectionFactory watcherCollectionFactory,
|
||||||
IRefreshTokenFactoryProvider spotifyFactory,
|
IRefreshTokenFactoryProvider spotifyFactory,
|
||||||
ILogger<WatcherService> logger,
|
ILoggerFactory loggerFactory,
|
||||||
IOptions<RootOptions> config
|
IOptions<RootOptions> config
|
||||||
) {
|
) {
|
||||||
Logger = logger;
|
Logger = loggerFactory.CreateLogger<WatcherService>();
|
||||||
|
LoggerFactory = loggerFactory;
|
||||||
Config = config.Value;
|
Config = config.Value;
|
||||||
WatcherFactory = watcherFactory;
|
WatcherFactory = watcherFactory;
|
||||||
WatcherCollectionFactory = watcherCollectionFactory;
|
WatcherCollectionFactory = watcherCollectionFactory;
|
||||||
@ -42,7 +44,7 @@ namespace Selector.CLI
|
|||||||
|
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Logger.LogInformation("Starting up");
|
Logger.LogInformation("Starting watcher service...");
|
||||||
|
|
||||||
Logger.LogInformation("Loading config instances...");
|
Logger.LogInformation("Loading config instances...");
|
||||||
var watcherIndices = await InitialiseConfigInstances();
|
var watcherIndices = await InitialiseConfigInstances();
|
||||||
@ -60,11 +62,11 @@ namespace Selector.CLI
|
|||||||
var logMsg = new StringBuilder();
|
var logMsg = new StringBuilder();
|
||||||
if (!string.IsNullOrWhiteSpace(watcherOption.Name))
|
if (!string.IsNullOrWhiteSpace(watcherOption.Name))
|
||||||
{
|
{
|
||||||
logMsg.Append($"Creating {watcherOption.Name} watcher [{watcherOption.Type}]");
|
logMsg.Append($"Creating [{watcherOption.Name}] watcher [{watcherOption.Type}]");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logMsg.Append($"Creating new {watcherOption.Type} watcher");
|
logMsg.Append($"Creating new [{watcherOption.Type}] watcher");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(watcherOption.PlaylistUri)) logMsg.Append($" [{ watcherOption.PlaylistUri}]");
|
if (!string.IsNullOrWhiteSpace(watcherOption.PlaylistUri)) logMsg.Append($" [{ watcherOption.PlaylistUri}]");
|
||||||
@ -92,7 +94,19 @@ namespace Selector.CLI
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
watcherCollection.Add(watcher);
|
List<IConsumer> consumers = new();
|
||||||
|
foreach(var consumer in watcherOption.Consumers)
|
||||||
|
{
|
||||||
|
switch(consumer)
|
||||||
|
{
|
||||||
|
case Consumers.AudioFeatures:
|
||||||
|
var factory = new AudioFeatureInjectorFactory(LoggerFactory);
|
||||||
|
consumers.Add(await factory.Get(spotifyFactory));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watcherCollection.Add(watcher, consumers);
|
||||||
}
|
}
|
||||||
|
|
||||||
return indices;
|
return indices;
|
||||||
@ -104,6 +118,7 @@ namespace Selector.CLI
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Logger.LogInformation($"Starting watcher collection [{index}]");
|
||||||
Watchers[index].Start();
|
Watchers[index].Start();
|
||||||
}
|
}
|
||||||
catch (KeyNotFoundException)
|
catch (KeyNotFoundException)
|
||||||
@ -119,7 +134,7 @@ namespace Selector.CLI
|
|||||||
|
|
||||||
foreach((var key, var watcher) in Watchers)
|
foreach((var key, var watcher) in Watchers)
|
||||||
{
|
{
|
||||||
Logger.LogInformation($"Stopping watcher collection: {key}");
|
Logger.LogInformation($"Stopping watcher collection [{key}]");
|
||||||
watcher.Stop();
|
watcher.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,16 +6,17 @@
|
|||||||
"Watcher": {
|
"Watcher": {
|
||||||
"Instances": [
|
"Instances": [
|
||||||
{
|
{
|
||||||
"name": "player watcher",
|
"name": "Player Watcher",
|
||||||
"type": "player",
|
"type": "player",
|
||||||
"pollperiod": 1000
|
"pollperiod": 2000,
|
||||||
|
"consumers": [ "audiofeatures" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information"
|
"Default": "Trace"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,6 +16,10 @@
|
|||||||
name="logfile"
|
name="logfile"
|
||||||
fileName=".\selector.log"
|
fileName=".\selector.log"
|
||||||
layout="${format}" />
|
layout="${format}" />
|
||||||
|
<target xsi:type="File"
|
||||||
|
name="tracefile"
|
||||||
|
fileName=".\selector.trace.log"
|
||||||
|
layout="${format}" />
|
||||||
<target xsi:type="Console"
|
<target xsi:type="Console"
|
||||||
name="logconsole"
|
name="logconsole"
|
||||||
layout="${format}" />
|
layout="${format}" />
|
||||||
@ -23,6 +27,9 @@
|
|||||||
|
|
||||||
<!-- rules to map from logger name to target -->
|
<!-- rules to map from logger name to target -->
|
||||||
<rules>
|
<rules>
|
||||||
<logger name="Selector.*" minlevel="Trace" writeTo="logfile,logconsole" />
|
<logger name="*" minlevel="Debug" writeTo="logfile" />
|
||||||
|
<logger name="*" minlevel="Trace" writeTo="tracefile" />
|
||||||
|
|
||||||
|
<logger name="Selector.*" minlevel="Debug" writeTo="logconsole" />
|
||||||
</rules>
|
</rules>
|
||||||
</nlog>
|
</nlog>
|
103
Selector/Consumers/AudioFeatureInjector.cs
Normal file
103
Selector/Consumers/AudioFeatureInjector.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using SpotifyAPI.Web;
|
||||||
|
|
||||||
|
namespace Selector
|
||||||
|
{
|
||||||
|
public class AudioFeatureInjector : IConsumer
|
||||||
|
{
|
||||||
|
private readonly IPlayerWatcher Watcher;
|
||||||
|
private readonly ITracksClient TrackClient;
|
||||||
|
private readonly ILogger<AudioFeatureInjector> Logger;
|
||||||
|
|
||||||
|
public CancellationToken CancelToken { get; set; }
|
||||||
|
|
||||||
|
public AnalysedTrackTimeline Timeline { get; set; } = new();
|
||||||
|
|
||||||
|
public AudioFeatureInjector(
|
||||||
|
IPlayerWatcher watcher,
|
||||||
|
ITracksClient trackClient,
|
||||||
|
ILogger<AudioFeatureInjector> logger = null,
|
||||||
|
CancellationToken token = default
|
||||||
|
){
|
||||||
|
Watcher = watcher;
|
||||||
|
TrackClient = trackClient;
|
||||||
|
Logger = logger ?? NullLogger<AudioFeatureInjector>.Instance;
|
||||||
|
CancelToken = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Callback(object sender, ListeningChangeEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Current is null) return;
|
||||||
|
|
||||||
|
Task.Run(() => { return AsyncCallback(e); }, CancelToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AsyncCallback(ListeningChangeEventArgs e)
|
||||||
|
{
|
||||||
|
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()}]");
|
||||||
|
|
||||||
|
Timeline.Add(AnalysedTrack.From(track, audioFeatures));
|
||||||
|
}
|
||||||
|
else if (e.Current.Item is FullEpisode episode)
|
||||||
|
{
|
||||||
|
Logger.LogDebug($"Ignoring podcast episdoe [{episode.DisplayString()}]");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogError($"Unknown item pulled from API [{e.Current.Item}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Subscribe(IWatcher watch = null)
|
||||||
|
{
|
||||||
|
var watcher = watch ?? Watcher ?? throw new ArgumentNullException("No watcher provided");
|
||||||
|
|
||||||
|
if (watcher is IPlayerWatcher watcherCast)
|
||||||
|
{
|
||||||
|
watcherCast.ItemChange += Callback;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Provided watcher is not a PlayerWatcher");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unsubscribe(IWatcher watch = null)
|
||||||
|
{
|
||||||
|
var watcher = watch ?? Watcher ?? throw new ArgumentNullException("No watcher provided");
|
||||||
|
|
||||||
|
if (watcher is IPlayerWatcher watcherCast)
|
||||||
|
{
|
||||||
|
watcherCast.ItemChange -= Callback;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Provided watcher is not a PlayerWatcher");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AnalysedTrack {
|
||||||
|
public FullTrack Track { get; set; }
|
||||||
|
public TrackAudioFeatures Features { get; set; }
|
||||||
|
|
||||||
|
public static AnalysedTrack From(FullTrack track, TrackAudioFeatures features)
|
||||||
|
{
|
||||||
|
return new AnalysedTrack()
|
||||||
|
{
|
||||||
|
Track = track,
|
||||||
|
Features = features
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
Selector/Consumers/AudioFeatureInjectorFactory.cs
Normal file
32
Selector/Consumers/AudioFeatureInjectorFactory.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
using SpotifyAPI.Web;
|
||||||
|
|
||||||
|
namespace Selector
|
||||||
|
{
|
||||||
|
public class AudioFeatureInjectorFactory: IConsumerFactory {
|
||||||
|
|
||||||
|
private readonly ILoggerFactory LoggerFactory;
|
||||||
|
|
||||||
|
public AudioFeatureInjectorFactory(ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
LoggerFactory = loggerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null)
|
||||||
|
{
|
||||||
|
var config = await spotifyFactory.GetConfig();
|
||||||
|
var client = new SpotifyClient(config);
|
||||||
|
|
||||||
|
return new AudioFeatureInjector(
|
||||||
|
watcher,
|
||||||
|
client.Tracks,
|
||||||
|
LoggerFactory.CreateLogger<AudioFeatureInjector>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
Selector/Consumers/IConsumer.cs
Normal file
12
Selector/Consumers/IConsumer.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Selector
|
||||||
|
{
|
||||||
|
public interface IConsumer
|
||||||
|
{
|
||||||
|
public void Callback(object sender, ListeningChangeEventArgs e);
|
||||||
|
public void Subscribe(IWatcher watch = null);
|
||||||
|
public void Unsubscribe(IWatcher watch = null);
|
||||||
|
}
|
||||||
|
}
|
10
Selector/Consumers/IConsumerFactory.cs
Normal file
10
Selector/Consumers/IConsumerFactory.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Selector
|
||||||
|
{
|
||||||
|
public interface IConsumerFactory
|
||||||
|
{
|
||||||
|
public Task<IConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher);
|
||||||
|
}
|
||||||
|
}
|
@ -5,20 +5,23 @@ using System.Linq;
|
|||||||
|
|
||||||
using SpotifyAPI.Web;
|
using SpotifyAPI.Web;
|
||||||
|
|
||||||
namespace Selector.Helpers
|
namespace Selector
|
||||||
{
|
{
|
||||||
public static class SpotifyExtensions
|
public static class SpotifyExtensions
|
||||||
{
|
{
|
||||||
public static string ToString(this FullTrack track) => $"{track.Name} - {track.Album.Name} - {track.Artists}";
|
public static string DisplayString(this FullTrack track) => $"{track.Name} / {track.Album.Name} / {track.Artists.DisplayString()}";
|
||||||
public static string ToString(this SimpleAlbum album) => $"{album.Name} - {album.Artists}";
|
public static string DisplayString(this SimpleAlbum album) => $"{album.Name} / {album.Artists.DisplayString()}";
|
||||||
public static string ToString(this SimpleArtist artist) => $"{artist.Name}";
|
public static string DisplayString(this SimpleArtist artist) => artist.Name;
|
||||||
|
|
||||||
public static string ToString(this FullEpisode ep) => $"{ep.Name} - {ep.Show}";
|
public static string DisplayString(this FullEpisode ep) => $"{ep.Name} / {ep.Show.DisplayString()}";
|
||||||
public static string ToString(this SimpleShow show) => $"{show.Name} - {show.Publisher}";
|
public static string DisplayString(this SimpleShow show) => $"{show.Name} / {show.Publisher}";
|
||||||
|
|
||||||
public static string ToString(this CurrentlyPlayingContext context) => $"{context.IsPlaying}, {context.Item}, {context.Device}";
|
|
||||||
public static string ToString(this Device device) => $"{device.Id}: {device.Name} {device.VolumePercent}%";
|
|
||||||
|
|
||||||
public static string ToString(this IEnumerable<SimpleArtist> artists) => string.Join("/", artists.Select(a => a.Name));
|
public static string DisplayString(this CurrentlyPlayingContext currentPlaying) => $"{currentPlaying.IsPlaying}, {currentPlaying.Item}, {currentPlaying.Device.DisplayString()}";
|
||||||
|
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 IEnumerable<SimpleArtist> artists) => string.Join(", ", artists.Select(a => a.DisplayString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
50
Selector/Timeline/AnalysedTrackTimeline.cs
Normal file
50
Selector/Timeline/AnalysedTrackTimeline.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using SpotifyAPI.Web;
|
||||||
|
|
||||||
|
namespace Selector
|
||||||
|
{
|
||||||
|
public class AnalysedTrackTimeline
|
||||||
|
: BaseTimeline<AnalysedTrack>, ITrackTimeline<AnalysedTrack>
|
||||||
|
{
|
||||||
|
public IEqual EqualityChecker { get; set; }
|
||||||
|
|
||||||
|
public AnalysedTrack Get(FullTrack track)
|
||||||
|
=> GetAll(track)
|
||||||
|
.LastOrDefault();
|
||||||
|
|
||||||
|
public IEnumerable<AnalysedTrack> GetAll(FullTrack track)
|
||||||
|
=> GetAllTimelineItems(track)
|
||||||
|
.Select(t => t.Item);
|
||||||
|
|
||||||
|
public IEnumerable<TimelineItem<AnalysedTrack>> GetAllTimelineItems(FullTrack track)
|
||||||
|
=> Recent
|
||||||
|
.Where(i => EqualityChecker.IsEqual(i.Item.Track, track));
|
||||||
|
|
||||||
|
public AnalysedTrack Get(SimpleAlbum album)
|
||||||
|
=> GetAll(album)
|
||||||
|
.LastOrDefault();
|
||||||
|
|
||||||
|
public IEnumerable<AnalysedTrack> GetAll(SimpleAlbum album)
|
||||||
|
=> GetAllTimelineItems(album)
|
||||||
|
.Select(t => t.Item);
|
||||||
|
|
||||||
|
public IEnumerable<TimelineItem<AnalysedTrack>> GetAllTimelineItems(SimpleAlbum album)
|
||||||
|
=> Recent
|
||||||
|
.Where(i => EqualityChecker.IsEqual(i.Item.Track.Album, album));
|
||||||
|
|
||||||
|
public AnalysedTrack Get(SimpleArtist artist)
|
||||||
|
=> GetAll(artist)
|
||||||
|
.LastOrDefault();
|
||||||
|
|
||||||
|
public IEnumerable<AnalysedTrack> GetAll(SimpleArtist artist)
|
||||||
|
=> GetAllTimelineItems(artist)
|
||||||
|
.Select(t => t.Item);
|
||||||
|
|
||||||
|
public IEnumerable<TimelineItem<AnalysedTrack>> GetAllTimelineItems(SimpleArtist artist)
|
||||||
|
=> Recent
|
||||||
|
.Where(i => EqualityChecker.IsEqual(i.Item.Track.Artists[0], artist));
|
||||||
|
}
|
||||||
|
}
|
61
Selector/Timeline/BaseTimeline.cs
Normal file
61
Selector/Timeline/BaseTimeline.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Selector
|
||||||
|
{
|
||||||
|
public abstract class BaseTimeline<T> : ITimeline<T>, IEnumerable<TimelineItem<T>> where T : class
|
||||||
|
{
|
||||||
|
protected List<TimelineItem<T>> Recent = new();
|
||||||
|
public int Count { get => Recent.Count; }
|
||||||
|
public void Clear() => Recent.Clear();
|
||||||
|
public bool SortOnBackDate { get; set; } = true;
|
||||||
|
|
||||||
|
private int? max = 1000;
|
||||||
|
public int? MaxSize
|
||||||
|
{
|
||||||
|
get => max;
|
||||||
|
set => max = value is null ? value : Math.Max(1, (int) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Add(T item) => Add(item, DateTime.UtcNow);
|
||||||
|
public virtual void Add(T item, DateTime timestamp)
|
||||||
|
{
|
||||||
|
Recent.Add(TimelineItem<T>.From(item, timestamp));
|
||||||
|
|
||||||
|
if (timestamp < Recent.Last().Time && SortOnBackDate)
|
||||||
|
{
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Sort()
|
||||||
|
{
|
||||||
|
Recent = Recent.OrderBy(i => i.Time).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void CheckSize()
|
||||||
|
{
|
||||||
|
if (MaxSize is int maxSize && Count > maxSize)
|
||||||
|
{
|
||||||
|
Recent.RemoveRange(0, Count - maxSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Get()
|
||||||
|
=> Recent.Last().Item;
|
||||||
|
|
||||||
|
public T Get(DateTime at)
|
||||||
|
=> GetTimelineItem(at).Item;
|
||||||
|
public TimelineItem<T> GetTimelineItem(DateTime at)
|
||||||
|
=> Recent
|
||||||
|
.Where(i => i.Time <= at).LastOrDefault();
|
||||||
|
|
||||||
|
public IEnumerator<TimelineItem<T>> GetEnumerator() => Recent.GetEnumerator();
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
21
Selector/Timeline/Interfaces/ITrackTimeline.cs
Normal file
21
Selector/Timeline/Interfaces/ITrackTimeline.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using SpotifyAPI.Web;
|
||||||
|
|
||||||
|
namespace Selector
|
||||||
|
{
|
||||||
|
public interface ITrackTimeline<T>: ITimeline<T>
|
||||||
|
{
|
||||||
|
public T Get(FullTrack track);
|
||||||
|
public IEnumerable<T> GetAll(FullTrack track);
|
||||||
|
public IEnumerable<TimelineItem<T>> GetAllTimelineItems(FullTrack track);
|
||||||
|
|
||||||
|
public T Get(SimpleAlbum album);
|
||||||
|
public IEnumerable<T> GetAll(SimpleAlbum album);
|
||||||
|
public IEnumerable<TimelineItem<T>> GetAllTimelineItems(SimpleAlbum album);
|
||||||
|
|
||||||
|
public T Get(SimpleArtist artist);
|
||||||
|
public IEnumerable<T> GetAll(SimpleArtist artist);
|
||||||
|
public IEnumerable<TimelineItem<T>> GetAllTimelineItems(SimpleArtist artist);
|
||||||
|
}
|
||||||
|
}
|
@ -7,67 +7,11 @@ using SpotifyAPI.Web;
|
|||||||
namespace Selector
|
namespace Selector
|
||||||
{
|
{
|
||||||
public class PlayerTimeline
|
public class PlayerTimeline
|
||||||
: ITimeline<CurrentlyPlayingContext>,
|
: BaseTimeline<CurrentlyPlayingContext>, ITrackTimeline<CurrentlyPlayingContext>
|
||||||
IEnumerable<TimelineItem<CurrentlyPlayingContext>>
|
|
||||||
{
|
{
|
||||||
|
|
||||||
private List<TimelineItem<CurrentlyPlayingContext>> recentlyPlayed = new();
|
|
||||||
public IEqual EqualityChecker { get; set; }
|
public IEqual EqualityChecker { get; set; }
|
||||||
public bool SortOnBackDate { get; set; } = true;
|
|
||||||
public int Count { get => recentlyPlayed.Count; }
|
|
||||||
|
|
||||||
private int? max = 1000;
|
public override void Add(CurrentlyPlayingContext item) => Add(item, DateHelper.FromUnixMilli(item.Timestamp));
|
||||||
public int? MaxSize {
|
|
||||||
get => max;
|
|
||||||
set {
|
|
||||||
if(value is null)
|
|
||||||
{
|
|
||||||
max = value;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
max = Math.Max(1, (int) value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(CurrentlyPlayingContext item) => Add(item, DateHelper.FromUnixMilli(item.Timestamp));
|
|
||||||
public void Add(CurrentlyPlayingContext item, DateTime timestamp)
|
|
||||||
{
|
|
||||||
recentlyPlayed.Add(TimelineItem<CurrentlyPlayingContext>.From(item, timestamp));
|
|
||||||
|
|
||||||
if (timestamp < recentlyPlayed.Last().Time && SortOnBackDate)
|
|
||||||
{
|
|
||||||
Sort();
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Sort()
|
|
||||||
{
|
|
||||||
recentlyPlayed = recentlyPlayed
|
|
||||||
.OrderBy(i => i.Time)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CheckSize()
|
|
||||||
{
|
|
||||||
if (MaxSize is int maxSize && Count > maxSize) {
|
|
||||||
recentlyPlayed.RemoveRange(0, Count - maxSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear() => recentlyPlayed.Clear();
|
|
||||||
|
|
||||||
public CurrentlyPlayingContext Get()
|
|
||||||
=> recentlyPlayed.Last().Item;
|
|
||||||
|
|
||||||
public CurrentlyPlayingContext Get(DateTime at)
|
|
||||||
=> GetTimelineItem(at)?.Item;
|
|
||||||
public TimelineItem<CurrentlyPlayingContext> GetTimelineItem(DateTime at)
|
|
||||||
=> recentlyPlayed
|
|
||||||
.Where(i => i.Time <= at).LastOrDefault();
|
|
||||||
|
|
||||||
public CurrentlyPlayingContext Get(FullTrack track)
|
public CurrentlyPlayingContext Get(FullTrack track)
|
||||||
=> GetAll(track)
|
=> GetAll(track)
|
||||||
@ -77,8 +21,8 @@ namespace Selector
|
|||||||
=> GetAllTimelineItems(track)
|
=> GetAllTimelineItems(track)
|
||||||
.Select(t => t.Item);
|
.Select(t => t.Item);
|
||||||
|
|
||||||
private IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(FullTrack track)
|
public IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(FullTrack track)
|
||||||
=> recentlyPlayed
|
=> Recent
|
||||||
.Where(i => i.Item.Item is FullTrack iterTrack
|
.Where(i => i.Item.Item is FullTrack iterTrack
|
||||||
&& EqualityChecker.IsEqual(iterTrack, track));
|
&& EqualityChecker.IsEqual(iterTrack, track));
|
||||||
|
|
||||||
@ -90,8 +34,8 @@ namespace Selector
|
|||||||
=> GetAllTimelineItems(ep)
|
=> GetAllTimelineItems(ep)
|
||||||
.Select(t => t.Item);
|
.Select(t => t.Item);
|
||||||
|
|
||||||
private IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(FullEpisode ep)
|
public IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(FullEpisode ep)
|
||||||
=> recentlyPlayed
|
=> Recent
|
||||||
.Where(i => i.Item.Item is FullEpisode iterEp
|
.Where(i => i.Item.Item is FullEpisode iterEp
|
||||||
&& EqualityChecker.IsEqual(iterEp, ep));
|
&& EqualityChecker.IsEqual(iterEp, ep));
|
||||||
|
|
||||||
@ -103,8 +47,8 @@ namespace Selector
|
|||||||
=> GetAllTimelineItems(album)
|
=> GetAllTimelineItems(album)
|
||||||
.Select(t => t.Item);
|
.Select(t => t.Item);
|
||||||
|
|
||||||
private IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(SimpleAlbum album)
|
public IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(SimpleAlbum album)
|
||||||
=> recentlyPlayed
|
=> Recent
|
||||||
.Where(i => i.Item.Item is FullTrack iterTrack
|
.Where(i => i.Item.Item is FullTrack iterTrack
|
||||||
&& EqualityChecker.IsEqual(iterTrack.Album, album));
|
&& EqualityChecker.IsEqual(iterTrack.Album, album));
|
||||||
|
|
||||||
@ -116,8 +60,8 @@ namespace Selector
|
|||||||
=> GetAllTimelineItems(artist)
|
=> GetAllTimelineItems(artist)
|
||||||
.Select(t => t.Item);
|
.Select(t => t.Item);
|
||||||
|
|
||||||
private IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(SimpleArtist artist)
|
public IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(SimpleArtist artist)
|
||||||
=> recentlyPlayed
|
=> Recent
|
||||||
.Where(i => i.Item.Item is FullTrack iterTrack
|
.Where(i => i.Item.Item is FullTrack iterTrack
|
||||||
&& EqualityChecker.IsEqual(iterTrack.Artists[0], artist));
|
&& EqualityChecker.IsEqual(iterTrack.Artists[0], artist));
|
||||||
|
|
||||||
@ -129,8 +73,8 @@ namespace Selector
|
|||||||
=> GetAllTimelineItems(device)
|
=> GetAllTimelineItems(device)
|
||||||
.Select(t => t.Item);
|
.Select(t => t.Item);
|
||||||
|
|
||||||
private IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(Device device)
|
public IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(Device device)
|
||||||
=> recentlyPlayed
|
=> Recent
|
||||||
.Where(i => EqualityChecker.IsEqual(i.Item.Device, device));
|
.Where(i => EqualityChecker.IsEqual(i.Item.Device, device));
|
||||||
|
|
||||||
public CurrentlyPlayingContext Get(Context context)
|
public CurrentlyPlayingContext Get(Context context)
|
||||||
@ -141,11 +85,8 @@ namespace Selector
|
|||||||
=> GetAllTimelineItems(context)
|
=> GetAllTimelineItems(context)
|
||||||
.Select(t => t.Item);
|
.Select(t => t.Item);
|
||||||
|
|
||||||
private IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(Context context)
|
public IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(Context context)
|
||||||
=> recentlyPlayed
|
=> Recent
|
||||||
.Where(i => EqualityChecker.IsEqual(i.Item.Context, context));
|
.Where(i => EqualityChecker.IsEqual(i.Item.Context, context));
|
||||||
|
|
||||||
public IEnumerator<TimelineItem<CurrentlyPlayingContext>> GetEnumerator() => recentlyPlayed.GetEnumerator();
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,17 +5,29 @@ using System.Text;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
|
||||||
namespace Selector
|
namespace Selector
|
||||||
{
|
{
|
||||||
public abstract class BaseWatcher: IWatcher
|
public abstract class BaseWatcher: IWatcher
|
||||||
{
|
{
|
||||||
|
protected readonly ILogger<BaseWatcher> Logger;
|
||||||
|
|
||||||
|
public BaseWatcher(ILogger<BaseWatcher> logger = null)
|
||||||
|
{
|
||||||
|
Logger = logger ?? NullLogger<BaseWatcher>.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
public abstract Task WatchOne(CancellationToken token);
|
public abstract Task WatchOne(CancellationToken token);
|
||||||
|
|
||||||
public async Task Watch(CancellationToken cancelToken)
|
public async Task Watch(CancellationToken cancelToken)
|
||||||
{
|
{
|
||||||
|
Logger.LogDebug("Starting watcher");
|
||||||
while (true) {
|
while (true) {
|
||||||
cancelToken.ThrowIfCancellationRequested();
|
cancelToken.ThrowIfCancellationRequested();
|
||||||
await WatchOne(cancelToken);
|
await WatchOne(cancelToken);
|
||||||
|
Logger.LogTrace($"Finished watch one, delaying {PollPeriod}ms...");
|
||||||
await Task.Delay(PollPeriod, cancelToken);
|
await Task.Delay(PollPeriod, cancelToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ namespace Selector
|
|||||||
{
|
{
|
||||||
public class PlayerWatcher: BaseWatcher, IPlayerWatcher
|
public class PlayerWatcher: BaseWatcher, IPlayerWatcher
|
||||||
{
|
{
|
||||||
private readonly ILogger<PlayerWatcher> Logger;
|
new private readonly ILogger<PlayerWatcher> Logger;
|
||||||
private readonly IPlayerClient spotifyClient;
|
private readonly IPlayerClient spotifyClient;
|
||||||
private readonly IEqual eq;
|
private readonly IEqual eq;
|
||||||
|
|
||||||
@ -26,12 +26,13 @@ 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; private set; }
|
public PlayerTimeline Past { get; set; }
|
||||||
|
|
||||||
public PlayerWatcher(IPlayerClient spotifyClient,
|
public PlayerWatcher(IPlayerClient spotifyClient,
|
||||||
IEqual equalityChecker,
|
IEqual equalityChecker,
|
||||||
ILogger<PlayerWatcher> logger = null,
|
ILogger<PlayerWatcher> logger = null,
|
||||||
int pollPeriod = 3000) {
|
int pollPeriod = 3000
|
||||||
|
) : base(logger) {
|
||||||
|
|
||||||
this.spotifyClient = spotifyClient;
|
this.spotifyClient = spotifyClient;
|
||||||
eq = equalityChecker;
|
eq = equalityChecker;
|
||||||
@ -44,7 +45,9 @@ namespace Selector
|
|||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
try{
|
try{
|
||||||
|
Logger.LogTrace("Making Spotify call");
|
||||||
var polledCurrent = await spotifyClient.GetCurrentPlayback();
|
var polledCurrent = await spotifyClient.GetCurrentPlayback();
|
||||||
|
Logger.LogTrace($"Received Spotify call [{polledCurrent?.DisplayString()}]");
|
||||||
|
|
||||||
if (polledCurrent != null) StoreCurrentPlaying(polledCurrent);
|
if (polledCurrent != null) StoreCurrentPlaying(polledCurrent);
|
||||||
|
|
||||||
@ -70,14 +73,14 @@ namespace Selector
|
|||||||
if(previous is null
|
if(previous is null
|
||||||
&& (Live.Item is FullTrack || Live.Item is FullEpisode))
|
&& (Live.Item is FullTrack || Live.Item is FullEpisode))
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"Playback started: {Live}");
|
Logger.LogDebug($"Playback started: {Live.DisplayString()}");
|
||||||
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live));
|
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live));
|
||||||
}
|
}
|
||||||
// STOPPED PLAYBACK
|
// STOPPED PLAYBACK
|
||||||
else if((previous.Item is FullTrack || previous.Item is FullEpisode)
|
else if((previous.Item is FullTrack || previous.Item is FullEpisode)
|
||||||
&& Live is null)
|
&& Live is null)
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"Playback stopped: {previous}");
|
Logger.LogDebug($"Playback stopped: {previous.DisplayString()}");
|
||||||
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live));
|
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live));
|
||||||
}
|
}
|
||||||
// CONTINUING PLAYBACK
|
// CONTINUING PLAYBACK
|
||||||
@ -88,17 +91,17 @@ namespace Selector
|
|||||||
&& 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} -> {currentTrack}");
|
Logger.LogDebug($"Track changed: {previousTrack.DisplayString()} -> {currentTrack.DisplayString()}");
|
||||||
OnItemChange(ListeningChangeEventArgs.From(previous, Live));
|
OnItemChange(ListeningChangeEventArgs.From(previous, Live));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!eq.IsEqual(previousTrack.Album, currentTrack.Album)) {
|
if(!eq.IsEqual(previousTrack.Album, currentTrack.Album)) {
|
||||||
Logger.LogDebug($"Album changed: {previousTrack.Album} -> {currentTrack.Album}");
|
Logger.LogDebug($"Album changed: {previousTrack.Album.DisplayString()} -> {currentTrack.Album.DisplayString()}");
|
||||||
OnAlbumChange(ListeningChangeEventArgs.From(previous, Live));
|
OnAlbumChange(ListeningChangeEventArgs.From(previous, Live));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!eq.IsEqual(previousTrack.Artists[0], currentTrack.Artists[0])) {
|
if(!eq.IsEqual(previousTrack.Artists[0], currentTrack.Artists[0])) {
|
||||||
Logger.LogDebug($"Artist changed: {previousTrack.Artists[0]} -> {currentTrack.Artists[0]}");
|
Logger.LogDebug($"Artist changed: {previousTrack.Artists.DisplayString()} -> {currentTrack.Artists.DisplayString()}");
|
||||||
OnArtistChange(ListeningChangeEventArgs.From(previous, Live));
|
OnArtistChange(ListeningChangeEventArgs.From(previous, Live));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,7 +118,7 @@ namespace Selector
|
|||||||
&& 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} -> {currentEp}");
|
Logger.LogDebug($"Podcast changed: {previousEp.DisplayString()} -> {currentEp.DisplayString()}");
|
||||||
OnItemChange(ListeningChangeEventArgs.From(previous, Live));
|
OnItemChange(ListeningChangeEventArgs.From(previous, Live));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,13 +128,13 @@ namespace Selector
|
|||||||
|
|
||||||
// CONTEXT
|
// CONTEXT
|
||||||
if(!eq.IsEqual(previous.Context, Live.Context)) {
|
if(!eq.IsEqual(previous.Context, Live.Context)) {
|
||||||
Logger.LogDebug($"Context changed: {previous.Context} -> {Live.Context}");
|
Logger.LogDebug($"Context changed: {previous.Context.DisplayString()} -> {Live.Context.DisplayString()}");
|
||||||
OnContextChange(ListeningChangeEventArgs.From(previous, Live));
|
OnContextChange(ListeningChangeEventArgs.From(previous, Live));
|
||||||
}
|
}
|
||||||
|
|
||||||
// DEVICE
|
// DEVICE
|
||||||
if(!eq.IsEqual(previous?.Device, Live?.Device)) {
|
if(!eq.IsEqual(previous?.Device, Live?.Device)) {
|
||||||
Logger.LogDebug($"Device changed: {previous?.Device} -> {Live?.Device}");
|
Logger.LogDebug($"Device changed: {previous?.Device.DisplayString()} -> {Live?.Device.DisplayString()}");
|
||||||
OnDeviceChange(ListeningChangeEventArgs.From(previous, Live));
|
OnDeviceChange(ListeningChangeEventArgs.From(previous, Live));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ namespace Selector
|
|||||||
{
|
{
|
||||||
public bool IsRunning { get; }
|
public bool IsRunning { get; }
|
||||||
public void Add(IWatcher watcher);
|
public void Add(IWatcher watcher);
|
||||||
|
public void Add(IWatcher watcher, List<IConsumer> consumers);
|
||||||
|
|
||||||
public void Start();
|
public void Start();
|
||||||
public void Stop();
|
public void Stop();
|
||||||
|
@ -34,7 +34,12 @@ namespace Selector
|
|||||||
|
|
||||||
public void Add(IWatcher watcher)
|
public void Add(IWatcher watcher)
|
||||||
{
|
{
|
||||||
var context = WatcherContext.From(watcher);
|
Add(watcher, default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(IWatcher watcher, List<IConsumer> consumers)
|
||||||
|
{
|
||||||
|
var context = WatcherContext.From(watcher, consumers);
|
||||||
if (IsRunning) context.Start();
|
if (IsRunning) context.Start();
|
||||||
|
|
||||||
Watchers.Add(context);
|
Watchers.Add(context);
|
||||||
@ -45,7 +50,7 @@ namespace Selector
|
|||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"Starting {Count} watchers");
|
Logger.LogDebug($"Starting {Count} watcher(s)");
|
||||||
foreach(var watcher in Watchers)
|
foreach(var watcher in Watchers)
|
||||||
{
|
{
|
||||||
watcher.Start();
|
watcher.Start();
|
||||||
@ -55,7 +60,7 @@ namespace Selector
|
|||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"Stopping {Count} watchers");
|
Logger.LogDebug($"Stopping {Count} watcher(s)");
|
||||||
foreach (var watcher in Watchers)
|
foreach (var watcher in Watchers)
|
||||||
{
|
{
|
||||||
watcher.Stop();
|
watcher.Stop();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -9,6 +10,7 @@ namespace Selector
|
|||||||
public class WatcherContext: IDisposable
|
public class WatcherContext: IDisposable
|
||||||
{
|
{
|
||||||
public IWatcher Watcher { get; set; }
|
public IWatcher Watcher { get; set; }
|
||||||
|
private List<IConsumer> Consumers { get; set; } = new();
|
||||||
public bool IsRunning { get; private set; }
|
public bool IsRunning { get; private set; }
|
||||||
public Task Task { get; set; }
|
public Task Task { get; set; }
|
||||||
public CancellationTokenSource TokenSource { get; set; }
|
public CancellationTokenSource TokenSource { get; set; }
|
||||||
@ -18,11 +20,30 @@ namespace Selector
|
|||||||
Watcher = watcher;
|
Watcher = watcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public WatcherContext(IWatcher watcher, List<IConsumer> consumers)
|
||||||
|
{
|
||||||
|
Watcher = watcher;
|
||||||
|
Consumers = consumers ?? new();
|
||||||
|
}
|
||||||
|
|
||||||
public static WatcherContext From(IWatcher watcher)
|
public static WatcherContext From(IWatcher watcher)
|
||||||
{
|
{
|
||||||
return new(watcher);
|
return new(watcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static WatcherContext From(IWatcher watcher, List<IConsumer> consumers)
|
||||||
|
{
|
||||||
|
return new(watcher, consumers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddConsumer(IConsumer consumer)
|
||||||
|
{
|
||||||
|
if (IsRunning)
|
||||||
|
consumer.Subscribe(Watcher);
|
||||||
|
|
||||||
|
Consumers.Add(consumer);
|
||||||
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
if (IsRunning)
|
if (IsRunning)
|
||||||
@ -30,6 +51,9 @@ namespace Selector
|
|||||||
|
|
||||||
IsRunning = true;
|
IsRunning = true;
|
||||||
TokenSource = new();
|
TokenSource = new();
|
||||||
|
|
||||||
|
Consumers.ForEach(c => c.Subscribe(Watcher));
|
||||||
|
|
||||||
Task = Watcher.Watch(TokenSource.Token);
|
Task = Watcher.Watch(TokenSource.Token);
|
||||||
Task.ContinueWith(t =>
|
Task.ContinueWith(t =>
|
||||||
{
|
{
|
||||||
@ -39,6 +63,8 @@ namespace Selector
|
|||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
|
Consumers.ForEach(c => c.Unsubscribe(Watcher));
|
||||||
|
|
||||||
TokenSource.Cancel();
|
TokenSource.Cancel();
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user