adding playlist watcher

This commit is contained in:
andy 2022-06-28 07:30:27 +01:00
parent fb88da4337
commit 83e071f5c3
26 changed files with 596 additions and 53 deletions

View File

@ -9,6 +9,7 @@ using Selector.Extensions;
using System;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Threading.Tasks;
namespace Selector.CLI
{
@ -31,9 +32,14 @@ namespace Selector.CLI
{
try
{
CreateHostBuilder(Environment.GetCommandLineArgs(), ConfigureDefault, ConfigureDefaultNlog)
.Build()
.Run();
var host = CreateHostBuilder(Environment.GetCommandLineArgs(),ConfigureDefault, ConfigureDefaultNlog)
.Build();
var logger = host.Services.GetRequiredService<ILogger<HostCommand>>();
var env = host.Services.GetRequiredService<IHostEnvironment>();
SetupExceptionHandling(logger, env);
host.Run();
}
catch(Exception ex)
{
@ -44,6 +50,22 @@ namespace Selector.CLI
return 0;
}
private static void SetupExceptionHandling(ILogger logger, IHostEnvironment env)
{
AppDomain.CurrentDomain.UnhandledException += (obj, e) =>
{
if(e.ExceptionObject is Exception ex)
{
logger.LogError(ex as Exception, "Unhandled exception thrown");
if (env.IsDevelopment())
{
throw ex;
}
}
};
}
public static RootOptions ConfigureOptions(HostBuilderContext context, IServiceCollection services)
{
services.Configure<RootOptions>(options =>

View File

@ -95,41 +95,48 @@ namespace Selector.CLI
watcher = await WatcherFactory.Get<PlayerWatcher>(spotifyFactory, id: watcherOption.Name, pollPeriod: watcherOption.PollPeriod);
break;
case WatcherType.Playlist:
throw new NotImplementedException("Playlist watchers not implemented");
// break;
var playlistWatcher = await WatcherFactory.Get<PlaylistWatcher>(spotifyFactory, id: watcherOption.Name, pollPeriod: watcherOption.PollPeriod) as PlaylistWatcher;
playlistWatcher.config = new() { PlaylistId = watcherOption.PlaylistUri };
watcher = playlistWatcher;
break;
}
List<IConsumer> consumers = new();
foreach(var consumer in watcherOption.Consumers)
if (watcherOption.Consumers is not null)
{
switch(consumer)
foreach (var consumer in watcherOption.Consumers)
{
case Consumers.AudioFeatures:
consumers.Add(await ServiceProvider.GetService<AudioFeatureInjectorFactory>().Get(spotifyFactory));
break;
switch (consumer)
{
case Consumers.AudioFeatures:
consumers.Add(await ServiceProvider.GetService<AudioFeatureInjectorFactory>().Get(spotifyFactory));
break;
case Consumers.AudioFeaturesCache:
consumers.Add(await ServiceProvider.GetService<CachingAudioFeatureInjectorFactory>().Get(spotifyFactory));
break;
case Consumers.AudioFeaturesCache:
consumers.Add(await ServiceProvider.GetService<CachingAudioFeatureInjectorFactory>().Get(spotifyFactory));
break;
case Consumers.CacheWriter:
consumers.Add(await ServiceProvider.GetService<CacheWriterFactory>().Get());
break;
case Consumers.CacheWriter:
consumers.Add(await ServiceProvider.GetService<CacheWriterFactory>().Get());
break;
case Consumers.Publisher:
consumers.Add(await ServiceProvider.GetService<PublisherFactory>().Get());
break;
case Consumers.Publisher:
consumers.Add(await ServiceProvider.GetService<PublisherFactory>().Get());
break;
case Consumers.PlayCounter:
if(!string.IsNullOrWhiteSpace(watcherOption.LastFmUsername))
{
consumers.Add(await ServiceProvider.GetService<PlayCounterFactory>().Get(creds: new() { Username = watcherOption.LastFmUsername }));
}
else
{
Logger.LogError("No Last.fm username provided, skipping play counter");
}
break;
case Consumers.PlayCounter:
if (!string.IsNullOrWhiteSpace(watcherOption.LastFmUsername))
{
consumers.Add(await ServiceProvider.GetService<PlayCounterFactory>().Get(creds: new() { Username = watcherOption.LastFmUsername }));
}
else
{
Logger.LogError("No Last.fm username provided, skipping play counter");
}
break;
}
}
}

View File

@ -31,5 +31,6 @@
<logger name="*" minlevel="Debug" writeTo="logfile" />
<logger name="*" minlevel="Trace" writeTo="tracefile" />
<logger name="Selector.*" minlevel="Debug" writeTo="logconsole" />
<logger name="Microsoft.*" minlevel="Warning" writeTo="logconsole" />
</rules>
</nlog>

View File

@ -10,7 +10,7 @@ using StackExchange.Redis;
namespace Selector.Cache
{
public class CacheWriter : IConsumer
public class CacheWriter : IPlayerConsumer
{
private readonly IPlayerWatcher Watcher;
private readonly IDatabaseAsync Db;

View File

@ -22,7 +22,7 @@ namespace Selector.Cache
Db = db;
}
public async Task<IConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null)
public async Task<IPlayerConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null)
{
var config = await spotifyFactory.GetConfig();
var client = new SpotifyClient(config);

View File

@ -9,7 +9,7 @@ using StackExchange.Redis;
namespace Selector.Cache
{
public interface ICacheWriterFactory {
public Task<IConsumer> Get(IPlayerWatcher watcher = null);
public Task<IPlayerConsumer> Get(IPlayerWatcher watcher = null);
}
public class CacheWriterFactory: ICacheWriterFactory {
@ -25,9 +25,9 @@ namespace Selector.Cache
LoggerFactory = loggerFactory;
}
public Task<IConsumer> Get(IPlayerWatcher watcher = null)
public Task<IPlayerConsumer> Get(IPlayerWatcher watcher = null)
{
return Task.FromResult<IConsumer>(new CacheWriter(
return Task.FromResult<IPlayerConsumer>(new CacheWriter(
watcher,
Cache,
LoggerFactory.CreateLogger<CacheWriter>()

View File

@ -28,7 +28,7 @@ namespace Selector.Cache
Creds = creds;
}
public Task<IConsumer> Get(LastfmClient fmClient = null, LastFmCredentials creds = null, IPlayerWatcher watcher = null)
public Task<IPlayerConsumer> Get(LastfmClient fmClient = null, LastFmCredentials creds = null, IPlayerWatcher watcher = null)
{
var client = fmClient ?? Client;
@ -37,7 +37,7 @@ namespace Selector.Cache
throw new ArgumentNullException("No Last.fm client provided");
}
return Task.FromResult<IConsumer>(new PlayCounterCaching(
return Task.FromResult<IPlayerConsumer>(new PlayCounterCaching(
watcher,
client.Track,
client.Album,

View File

@ -9,7 +9,7 @@ using StackExchange.Redis;
namespace Selector.Cache
{
public interface IPublisherFactory {
public Task<IConsumer> Get(IPlayerWatcher watcher = null);
public Task<IPlayerConsumer> Get(IPlayerWatcher watcher = null);
}
public class PublisherFactory: IPublisherFactory {
@ -25,9 +25,9 @@ namespace Selector.Cache
LoggerFactory = loggerFactory;
}
public Task<IConsumer> Get(IPlayerWatcher watcher = null)
public Task<IPlayerConsumer> Get(IPlayerWatcher watcher = null)
{
return Task.FromResult<IConsumer>(new Publisher(
return Task.FromResult<IPlayerConsumer>(new Publisher(
watcher,
Subscriber,
LoggerFactory.CreateLogger<Publisher>()

View File

@ -10,7 +10,7 @@ using StackExchange.Redis;
namespace Selector.Cache
{
public class Publisher : IConsumer
public class Publisher : IPlayerConsumer
{
private readonly IPlayerWatcher Watcher;
private readonly ISubscriber Subscriber;

View File

@ -3,7 +3,7 @@ using Microsoft.Extensions.Logging.Abstractions;
namespace Selector.Events
{
public class UserEventFirer : IConsumer
public class UserEventFirer : IPlayerConsumer
{
protected readonly IPlayerWatcher Watcher;
protected readonly ILogger<UserEventFirer> Logger;

View File

@ -133,5 +133,15 @@ namespace Selector.Tests
VolumePercent = volume
};
}
public static FullPlaylist FullPlaylist(string name, string id = null, string snapshotId = null)
{
return new FullPlaylist()
{
Name = name,
Id = id ?? name,
SnapshotId = snapshotId ?? id ?? name
};
}
}
}

View File

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using Xunit;
using Moq;
using FluentAssertions;
using SpotifyAPI.Web;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Sdk;
namespace Selector.Tests
{
public class PlaylistWatcherTests
{
public static IEnumerable<object[]> CurrentPlaylistData =>
new List<object[]>
{
new object[] { new List<FullPlaylist>(){
Helper.FullPlaylist("playlist1"),
Helper.FullPlaylist("playlist1"),
Helper.FullPlaylist("playlist1"),
Helper.FullPlaylist("playlist1")
}
}
};
[Theory]
[MemberData(nameof(CurrentPlaylistData))]
public async void CurrentPlaylist(List<FullPlaylist> playing)
{
var playlistDequeue = new Queue<FullPlaylist>(playing);
var spotMock = new Mock<ISpotifyClient>();
spotMock.Setup(s => s.Playlists.Get(It.IsAny<string>()).Result).Returns(playlistDequeue.Dequeue);
var config = new PlaylistWatcherConfig() { PlaylistId = "spotify:playlist:test" };
var watcher = new PlaylistWatcher(config, spotMock.Object);
for (var i = 0; i < playing.Count; i++)
{
await watcher.WatchOne();
watcher.Live.Should().Be(playing[i]);
}
}
public static IEnumerable<object[]> EventsData =>
new List<object[]>
{
// NO CHANGING
new object[] { new List<FullPlaylist>(){
Helper.FullPlaylist("Playlist", snapshotId: "snapshot1"),
Helper.FullPlaylist("Playlist", snapshotId: "snapshot1"),
Helper.FullPlaylist("Playlist", snapshotId: "snapshot1"),
},
// to raise
new List<string>(){ },
// to not raise
new List<string>(){ "SnapshotChange" }
},
// CHANGING SNAPSHOT
new object[] { new List<FullPlaylist>(){
Helper.FullPlaylist("Playlist", snapshotId: "snapshot1"),
Helper.FullPlaylist("Playlist", snapshotId: "snapshot2"),
Helper.FullPlaylist("Playlist", snapshotId: "snapshot2"),
},
// to raise
new List<string>(){ "SnapshotChange" },
// to not raise
new List<string>(){ }
}
};
[Theory]
[MemberData(nameof(EventsData))]
public async void Events(List<FullPlaylist> playing, List<string> toRaise, List<string> toNotRaise)
{
var playlistDequeue = new Queue<FullPlaylist>(playing);
var spotMock = new Mock<ISpotifyClient>();
spotMock.Setup(s => s.Playlists.Get(It.IsAny<string>()).Result).Returns(playlistDequeue.Dequeue);
var config = new PlaylistWatcherConfig() { PlaylistId = "spotify:playlist:test" };
var watcher = new PlaylistWatcher(config, spotMock.Object);
using var monitoredWatcher = watcher.Monitor();
for (var i = 0; i < playing.Count; i++)
{
await watcher.WatchOne();
}
toRaise.ForEach(r => monitoredWatcher.Should().Raise(r).WithSender(watcher));
toNotRaise.ForEach(r => monitoredWatcher.Should().NotRaise(r));
}
}
}

View File

@ -38,6 +38,15 @@ namespace Selector.Web
{
OptionsHelper.ConfigureOptions(options, Configuration);
});
services.Configure<RedisOptions>(options =>
{
Configuration.GetSection(string.Join(':', RootOptions.Key, RedisOptions.Key)).Bind(options);
});
services.Configure<NowPlayingOptions>(options =>
{
Configuration.GetSection(string.Join(':', RootOptions.Key, NowPlayingOptions.Key)).Bind(options);
});
var config = OptionsHelper.ConfigureOptions(Configuration);
services.Configure<SpotifyAppCredentials>(options =>

View File

@ -9,7 +9,7 @@ using SpotifyAPI.Web;
namespace Selector
{
public class AudioFeatureInjector : IConsumer
public class AudioFeatureInjector : IPlayerConsumer
{
protected readonly IPlayerWatcher Watcher;
protected readonly ITracksClient TrackClient;

View File

@ -10,7 +10,7 @@ namespace Selector
{
public interface IAudioFeatureInjectorFactory
{
public Task<IConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null);
public Task<IPlayerConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null);
}
public class AudioFeatureInjectorFactory: IAudioFeatureInjectorFactory {
@ -22,7 +22,7 @@ namespace Selector
LoggerFactory = loggerFactory;
}
public async Task<IConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null)
public async Task<IPlayerConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null)
{
var config = await spotifyFactory.GetConfig();
var client = new SpotifyClient(config);

View File

@ -10,7 +10,7 @@ namespace Selector
{
public interface IPlayCounterFactory
{
public Task<IConsumer> Get(LastfmClient fmClient = null, LastFmCredentials creds = null, IPlayerWatcher watcher = null);
public Task<IPlayerConsumer> Get(LastfmClient fmClient = null, LastFmCredentials creds = null, IPlayerWatcher watcher = null);
}
public class PlayCounterFactory: IPlayCounterFactory {
@ -26,7 +26,7 @@ namespace Selector
Creds = creds;
}
public Task<IConsumer> Get(LastfmClient fmClient = null, LastFmCredentials creds = null, IPlayerWatcher watcher = null)
public Task<IPlayerConsumer> Get(LastfmClient fmClient = null, LastFmCredentials creds = null, IPlayerWatcher watcher = null)
{
var client = fmClient ?? Client;
@ -35,7 +35,7 @@ namespace Selector
throw new ArgumentNullException("No Last.fm client provided");
}
return Task.FromResult<IConsumer>(new PlayCounter(
return Task.FromResult<IPlayerConsumer>(new PlayCounter(
watcher,
client.Track,
client.Album,

View File

@ -5,8 +5,18 @@ namespace Selector
{
public interface IConsumer
{
public void Callback(object sender, ListeningChangeEventArgs e);
public void Subscribe(IWatcher watch = null);
public void Unsubscribe(IWatcher watch = null);
}
public interface IConsumer<T>: IConsumer
{
public void Callback(object sender, T e);
}
public interface IPlayerConsumer: IConsumer<ListeningChangeEventArgs>
{ }
public interface IPlaylistConsumer : IConsumer<PlaylistChangeEventArgs>
{ }
}

View File

@ -13,7 +13,7 @@ using IF.Lastfm.Core.Api.Helpers;
namespace Selector
{
public class PlayCounter : IConsumer
public class PlayCounter : IPlayerConsumer
{
protected readonly IPlayerWatcher Watcher;
protected readonly ITrackApi TrackClient;

View File

@ -30,7 +30,7 @@ namespace Selector
}
}
public class WebHook : IConsumer
public class WebHook : IPlayerConsumer
{
protected readonly IPlayerWatcher Watcher;
protected readonly HttpClient HttpClient;

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using SpotifyAPI.Web;
namespace Selector.Equality
{
public class PlayableItemEqualityComparer: IEqualityComparer<PlaylistTrack<IPlayableItem>>
{
public bool Equals(PlaylistTrack<IPlayableItem> x, PlaylistTrack<IPlayableItem> y)
{
return x.GetUri().Equals(y.GetUri());
}
public int GetHashCode([DisallowNull] PlaylistTrack<IPlayableItem> obj)
{
return obj.GetUri().GetHashCode();
}
}
}

View File

@ -9,6 +9,8 @@ namespace Selector
{
public static class SpotifyExtensions
{
public static string DisplayString(this FullPlaylist playlist) => $"{playlist.Name}";
public static string DisplayString(this FullTrack track) => $"{track.Name} / {track.Album?.Name} / {track.Artists?.DisplayString()}";
public static string DisplayString(this SimpleAlbum album) => $"{album.Name} / {album.Artists?.DisplayString()}";
public static string DisplayString(this SimpleArtist artist) => artist.Name;
@ -48,5 +50,37 @@ namespace Selector
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;
public static string GetUri(this IPlayableItem y)
{
if (y is FullTrack track)
{
return track.Uri;
}
else if (y is FullEpisode episode)
{
return episode.Uri;
}
else
{
throw new ArgumentException(nameof(y));
}
}
public static string GetUri(this PlaylistTrack<IPlayableItem> y)
{
if (y.Track is FullTrack track)
{
return track.Uri;
}
else if (y.Track is FullEpisode episode)
{
return episode.Uri;
}
else
{
throw new ArgumentException(nameof(y.Track));
}
}
}
}

View File

@ -10,6 +10,10 @@
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="SpotifyAPI.Web" Version="6.2.2" />
<PackageReference Include="Inflatable.Lastfm" Version="1.2.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
</ItemGroup>
<ItemGroup>
<None Remove="System.Linq.Async" />
</ItemGroup>
</Project>

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using SpotifyAPI.Web;
namespace Selector
@ -29,4 +30,38 @@ namespace Selector
};
}
}
public class PlaylistChangeEventArgs : EventArgs
{
public FullPlaylist Previous { get; set; }
public FullPlaylist Current { get; set; }
/// <summary>
/// Spotify Username
/// </summary>
public string SpotifyUsername { get; set; }
/// <summary>
/// String Id for watcher, used to hold user Db Id
/// </summary>
/// <value></value>
public string Id { get; set; }
Timeline<FullPlaylist> Timeline { get; set; }
ICollection<PlaylistTrack<IPlayableItem>> CurrentTracks { get; set; }
ICollection<PlaylistTrack<IPlayableItem>> AddedTracks { get; set; }
ICollection<PlaylistTrack<IPlayableItem>> RemovedTracks { get; set; }
public static PlaylistChangeEventArgs From(FullPlaylist previous, FullPlaylist current, Timeline<FullPlaylist> timeline, ICollection<PlaylistTrack<IPlayableItem>> tracks = null, ICollection<PlaylistTrack<IPlayableItem>> addedTracks = null, ICollection<PlaylistTrack<IPlayableItem>> removedTracks = null, string id = null, string username = null)
{
return new PlaylistChangeEventArgs()
{
Previous = previous,
Current = current,
Timeline = timeline,
CurrentTracks = tracks,
AddedTracks = addedTracks,
RemovedTracks = removedTracks,
Id = id,
SpotifyUsername = username
};
}
}
}

View File

@ -0,0 +1,20 @@
using System;
using SpotifyAPI.Web;
namespace Selector
{
public interface IPlaylistWatcher: IWatcher
{
public event EventHandler<PlaylistChangeEventArgs> NetworkPoll;
public event EventHandler<PlaylistChangeEventArgs> SnapshotChange;
public event EventHandler<PlaylistChangeEventArgs> TracksAdded;
public event EventHandler<PlaylistChangeEventArgs> TracksRemoved;
public event EventHandler<PlaylistChangeEventArgs> NameChanged;
public event EventHandler<PlaylistChangeEventArgs> DescriptionChanged;
public FullPlaylist Live { get; }
public Timeline<FullPlaylist> Past { get; }
}
}

View File

@ -0,0 +1,256 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using SpotifyAPI.Web;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System.Linq;
using Selector.Equality;
namespace Selector
{
public class PlaylistWatcherConfig
{
public string PlaylistId { get; set; }
public bool PullTracks { get; set; } = true;
}
public class PlaylistWatcher: BaseWatcher, IPlaylistWatcher
{
new private readonly ILogger<PlaylistWatcher> Logger;
private readonly ISpotifyClient spotifyClient;
public PlaylistWatcherConfig config { get; set; }
public event EventHandler<PlaylistChangeEventArgs> NetworkPoll;
public event EventHandler<PlaylistChangeEventArgs> SnapshotChange;
public event EventHandler<PlaylistChangeEventArgs> TracksAdded;
public event EventHandler<PlaylistChangeEventArgs> TracksRemoved;
public event EventHandler<PlaylistChangeEventArgs> NameChanged;
public event EventHandler<PlaylistChangeEventArgs> DescriptionChanged;
public FullPlaylist Live { get; private set; }
private List<PlaylistTrack<IPlayableItem>> CurrentTracks { get; set; }
private ICollection<PlaylistTrack<IPlayableItem>> LastAddedTracks { get; set; }
private ICollection<PlaylistTrack<IPlayableItem>> LastRemovedTracks { get; set; }
private FullPlaylist Previous { get; set; }
public Timeline<FullPlaylist> Past { get; set; } = new();
private IEqualityComparer<PlaylistTrack<IPlayableItem>> EqualityComparer = new PlayableItemEqualityComparer();
public PlaylistWatcher(PlaylistWatcherConfig config,
ISpotifyClient spotifyClient,
ILogger<PlaylistWatcher> logger = null,
int pollPeriod = 3000
) : base(logger) {
this.spotifyClient = spotifyClient;
this.config = config;
Logger = logger ?? NullLogger<PlaylistWatcher>.Instance;
PollPeriod = pollPeriod;
}
public override Task Reset()
{
Previous = null;
Live = null;
Past = new();
return Task.CompletedTask;
}
public override async Task WatchOne(CancellationToken token = default)
{
token.ThrowIfCancellationRequested();
using var logScope = Logger.BeginScope(new Dictionary<string, object> { { "playlist_id", config.PlaylistId } });
try{
string id;
if(config.PlaylistId.Contains(':'))
{
id = config.PlaylistId.Split(':').Last();
}
else
{
id = config.PlaylistId;
}
Logger.LogTrace("Making Spotify call");
var polledCurrent = await spotifyClient.Playlists.Get(id);
Logger.LogTrace("Received Spotify call [{context}]", polledCurrent?.DisplayString());
if (polledCurrent != null) StoreCurrentPlaying(polledCurrent);
// swap new item into live and bump existing down to previous
Previous = Live;
Live = polledCurrent;
OnNetworkPoll(GetEvent());
await CheckSnapshot();
CheckStringValues();
}
catch(APIUnauthorizedException 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}]");
await Task.Delay(e.RetryAfter, token);
// throw e;
}
catch(APIException e)
{
Logger.LogDebug($"API error: [{e.Message}]");
// throw e;
}
}
private async Task CheckSnapshot()
{
switch(Previous, Live)
{
case (null, not null): // gone null
await PageLiveTracks();
break;
case (not null, null): // went non-null
break;
case (not null, not null): // continuing non-null
if (Live.SnapshotId != Previous.SnapshotId)
{
Logger.LogDebug("Snapshot Id changed: {previous} -> {current}", Previous.SnapshotId, Live.SnapshotId);
await PageLiveTracks();
OnSnapshotChange(GetEvent());
}
break;
}
}
private async Task PageLiveTracks()
{
if (config.PullTracks)
{
Logger.LogDebug("Paging current tracks");
var newCurrentTracks = await spotifyClient.Paginate(Live.Tracks).ToListAsync();
Logger.LogTrace("Completed paging current tracks");
if (CurrentTracks is not null)
{
Logger.LogDebug("Identifying diffs");
LastAddedTracks = newCurrentTracks.Except(CurrentTracks, EqualityComparer).ToArray();
LastRemovedTracks = CurrentTracks.Except(newCurrentTracks, EqualityComparer).ToArray();
if (LastAddedTracks.Count > 0)
{
OnTracksAdded(GetEvent());
}
if (LastRemovedTracks.Count > 0)
{
OnTracksRemoved(GetEvent());
}
Logger.LogTrace("Completed identifying diffs");
}
CurrentTracks = newCurrentTracks;
}
}
private PlaylistChangeEventArgs GetEvent() => PlaylistChangeEventArgs.From(Previous, Live, Past, tracks: CurrentTracks, addedTracks: LastAddedTracks, removedTracks: LastRemovedTracks, id: Id, username: SpotifyUsername);
private void CheckStringValues()
{
switch (Previous, Live)
{
case (null, not null): // gone null
break;
case (not null, null): // went non-null
break;
case (not null, not null): // continuing non-null
if (!Live.Name.Equals(Previous.Name))
{
Logger.LogDebug("Name changed: {previous} -> {current}", Previous.SnapshotId, Live.SnapshotId);
OnNameChanged(GetEvent());
}
if (!Live.Description.Equals(Previous.Description))
{
Logger.LogDebug("Description changed: {previous} -> {current}", Previous.SnapshotId, Live.SnapshotId);
OnDescriptionChanged(GetEvent());
}
break;
}
}
/// <summary>
/// Store currently playing in last plays. Determine whether new list or appending required
/// </summary>
/// <param name="current">New currently playing to store</param>
private void StoreCurrentPlaying(FullPlaylist current)
{
Past?.Add(current);
}
#region Event Firers
protected virtual void OnNetworkPoll(PlaylistChangeEventArgs args)
{
Logger.LogTrace("Firing network poll event");
NetworkPoll?.Invoke(this, args);
}
protected virtual void OnSnapshotChange(PlaylistChangeEventArgs args)
{
Logger.LogTrace("Firing snapshot change event");
SnapshotChange?.Invoke(this, args);
}
protected virtual void OnTracksAdded(PlaylistChangeEventArgs args)
{
Logger.LogTrace("Firing tracks added event");
TracksAdded?.Invoke(this, args);
}
protected virtual void OnTracksRemoved(PlaylistChangeEventArgs args)
{
Logger.LogTrace("Firing tracks removed event");
TracksRemoved?.Invoke(this, args);
}
protected virtual void OnNameChanged(PlaylistChangeEventArgs args)
{
Logger.LogTrace("Firing name changed event");
NameChanged?.Invoke(this, args);
}
protected virtual void OnDescriptionChanged(PlaylistChangeEventArgs args)
{
Logger.LogTrace("Firing description changed event");
DescriptionChanged?.Invoke(this, args);
}
#endregion
}
}

View File

@ -40,10 +40,25 @@ namespace Selector
Id = id
};
}
//else if (typeof(T).IsAssignableFrom(typeof(PlaylistWatcher)))
//{
else if (typeof(T).IsAssignableFrom(typeof(PlaylistWatcher)))
{
var config = await spotifyFactory.GetConfig();
var client = new SpotifyClient(config);
//}
// TODO: catch spotify exceptions
var user = await client.UserProfile.Current();
return new PlaylistWatcher(
new(),
client,
LoggerFactory?.CreateLogger<PlaylistWatcher>() ?? NullLogger<PlaylistWatcher>.Instance,
pollPeriod: pollPeriod
)
{
SpotifyUsername = user.DisplayName,
Id = id
};
}
else
{
throw new ArgumentException("Type unsupported");