added null CurrentlyPlaying from api, better testing

This commit is contained in:
andy 2021-09-26 15:25:54 +01:00
parent 55bba58fca
commit 823c1368e7
10 changed files with 312 additions and 70 deletions

View File

@ -1,4 +1,4 @@
name: dotnet package
name: ci
on: [push]

View File

@ -1,3 +1,5 @@
# Selector
![ci](https://github.com/sarsoo/Selector/actions/workflows/ci.yml/badge.svg)
Investigating a Spotify listening agent

View File

@ -140,6 +140,31 @@ namespace Selector.Tests
var eq = new UriEquality();
eq.Artist(artist1, artist2).Should().Be(shouldEqual);
}
public static IEnumerable<object[]> EpisodeData =>
new List<object[]>
{
// SAME
new object[] {
Helper.FullEpisode("1"),
Helper.FullEpisode("1"),
true
},
// DIFFERENT
new object[] {
Helper.FullEpisode("1"),
Helper.FullEpisode("2"),
false
}
};
[Theory]
[MemberData(nameof(EpisodeData))]
public void EpisodeEquality(FullEpisode episode1, FullEpisode episode2, bool shouldEqual)
{
var eq = new UriEquality();
eq.Episode(episode1, episode2).Should().Be(shouldEqual);
}
}
public class StringEqualityTests
@ -273,5 +298,42 @@ namespace Selector.Tests
var eq = new StringEquality();
eq.Artist(artist1, artist2).Should().Be(shouldEqual);
}
public static IEnumerable<object[]> EpisodeData =>
new List<object[]>
{
// SAME
new object[] {
Helper.FullEpisode("1", "1", "1"),
Helper.FullEpisode("1", "1", "1"),
true
},
// DIFFERENT PUBLISHER
new object[] {
Helper.FullEpisode("1", "1", "1"),
Helper.FullEpisode("1", "1", "2"),
false
},
// DIFFERENT SHOW
new object[] {
Helper.FullEpisode("1", "1", "1"),
Helper.FullEpisode("1", "2", "1"),
false
},
// DIFFERENT EPISODE
new object[] {
Helper.FullEpisode("1", "1", "1"),
Helper.FullEpisode("2", "1", "1"),
false
},
};
[Theory]
[MemberData(nameof(EpisodeData))]
public void EpisodeEquality(FullEpisode episode1, FullEpisode episode2, bool shouldEqual)
{
var eq = new StringEquality();
eq.Episode(episode1, episode2).Should().Be(shouldEqual);
}
}
}

View File

@ -26,6 +26,16 @@ namespace Selector.Tests
return FullTrack(name, album, new List<string>() { artist });
}
public static FullEpisode FullEpisode(string name, string show = null, string publisher = null)
{
return new FullEpisode()
{
Name = name,
Uri = name,
Show = SimpleShow(show ?? name, publisher: publisher)
};
}
public static SimpleAlbum SimpleAlbum(string name, List<string> artists)
{
return new SimpleAlbum()
@ -50,6 +60,16 @@ namespace Selector.Tests
};
}
public static SimpleShow SimpleShow(string name, string publisher = null)
{
return new SimpleShow()
{
Name = name,
Publisher = publisher ?? name,
Uri = name
};
}
public static CurrentlyPlaying CurrentlyPlaying(FullTrack track, bool isPlaying = true, string context = null)
{
return new CurrentlyPlaying()
@ -60,6 +80,16 @@ namespace Selector.Tests
};
}
public static CurrentlyPlaying CurrentlyPlaying(FullEpisode episode, bool isPlaying = true, string context = null)
{
return new CurrentlyPlaying()
{
Context = Context(context ?? episode.Uri),
IsPlaying = isPlaying,
Item = episode
};
}
public static Context Context(string uri)
{
return new Context()

View File

@ -5,7 +5,7 @@ using Moq;
using FluentAssertions;
using SpotifyAPI.Web;
using Selector;
using System.Threading;
namespace Selector.Tests
{
@ -29,12 +29,11 @@ namespace Selector.Tests
var playingQueue = new Queue<CurrentlyPlaying>(playing);
var spotMock = new Mock<IPlayerClient>();
var scheduleMock = new Mock<IScheduler>();
var eq = new UriEquality();
spotMock.Setup(s => s.GetCurrentlyPlaying(It.IsAny<PlayerCurrentlyPlayingRequest>()).Result).Returns(playingQueue.Dequeue);
var watcher = new PlayerWatcher(spotMock.Object, scheduleMock.Object, eq);
var watcher = new PlayerWatcher(spotMock.Object, eq);
for(var i = 0; i < playing.Count; i++)
{
@ -56,7 +55,7 @@ namespace Selector.Tests
// to raise
new List<string>(){ },
// to not raise
new List<string>(){ "TrackChange", "AlbumChange", "ArtistChange", "ContextChange", "PlayingChange" }
new List<string>(){ "ItemChange", "AlbumChange", "ArtistChange", "ContextChange", "PlayingChange" }
},
// TRACK CHANGE
new object[] { new List<CurrentlyPlaying>(){
@ -64,7 +63,7 @@ namespace Selector.Tests
Helper.CurrentlyPlaying(Helper.FullTrack("track2", "album1", "artist1"))
},
// to raise
new List<string>(){ "TrackChange" },
new List<string>(){ "ItemChange" },
// to not raise
new List<string>(){ "AlbumChange", "ArtistChange" }
},
@ -74,7 +73,7 @@ namespace Selector.Tests
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album2", "artist1"))
},
// to raise
new List<string>(){ "TrackChange", "AlbumChange" },
new List<string>(){ "ItemChange", "AlbumChange" },
// to not raise
new List<string>(){ "ArtistChange" }
},
@ -84,7 +83,7 @@ namespace Selector.Tests
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist2"))
},
// to raise
new List<string>(){ "TrackChange", "AlbumChange", "ArtistChange" },
new List<string>(){ "ItemChange", "AlbumChange", "ArtistChange" },
// to not raise
new List<string>(){ }
},
@ -96,7 +95,7 @@ namespace Selector.Tests
// to raise
new List<string>(){ "ContextChange" },
// to not raise
new List<string>(){ "TrackChange", "AlbumChange", "ArtistChange" }
new List<string>(){ "ItemChange", "AlbumChange", "ArtistChange" }
},
// PLAYING CHANGE
new object[] { new List<CurrentlyPlaying>(){
@ -106,7 +105,37 @@ namespace Selector.Tests
// to raise
new List<string>(){ "PlayingChange" },
// to not raise
new List<string>(){ "TrackChange", "AlbumChange", "ArtistChange", "ContextChange" }
new List<string>(){ "ItemChange", "AlbumChange", "ArtistChange", "ContextChange" }
},
// PLAYING CHANGE
new object[] { new List<CurrentlyPlaying>(){
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1"), isPlaying: false, context: "context1"),
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1"), isPlaying: true, context: "context1")
},
// to raise
new List<string>(){ "PlayingChange" },
// to not raise
new List<string>(){ "ItemChange", "AlbumChange", "ArtistChange", "ContextChange" }
},
// CONTENT CHANGE
new object[] { new List<CurrentlyPlaying>(){
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1"), isPlaying: true, context: "context1"),
Helper.CurrentlyPlaying(Helper.FullEpisode("ep1", "show1", "pub1"), isPlaying: true, context: "context2")
},
// to raise
new List<string>(){ "ContentChange", "ContextChange", "ItemChange" },
// to not raise
new List<string>(){ "AlbumChange", "ArtistChange", "PlayingChange" }
},
// CONTENT CHANGE
new object[] { new List<CurrentlyPlaying>(){
Helper.CurrentlyPlaying(Helper.FullEpisode("ep1", "show1", "pub1"), isPlaying: true, context: "context2"),
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1"), isPlaying: true, context: "context1")
},
// to raise
new List<string>(){ "ContentChange", "ContextChange", "ItemChange" },
// to not raise
new List<string>(){ "AlbumChange", "ArtistChange", "PlayingChange" }
}
};
@ -117,12 +146,11 @@ namespace Selector.Tests
var playingQueue = new Queue<CurrentlyPlaying>(playing);
var spotMock = new Mock<IPlayerClient>();
var scheduleMock = new Mock<IScheduler>();
var eq = new UriEquality();
spotMock.Setup(s => s.GetCurrentlyPlaying(It.IsAny<PlayerCurrentlyPlayingRequest>()).Result).Returns(playingQueue.Dequeue);
var watcher = new PlayerWatcher(spotMock.Object, scheduleMock.Object, eq);
var watcher = new PlayerWatcher(spotMock.Object, eq);
using var monitoredWatcher = watcher.Monitor();
for(var i = 0; i < playing.Count; i++)
@ -137,5 +165,16 @@ namespace Selector.Tests
monitoredWatcher.Should().NotRaise(notRraise);
}
}
// [Fact]
// public async void Auth()
// {
// var spot = new SpotifyClient("");
// var eq = new UriEquality();
// var watch = new PlayerWatcher(spot.Player, eq);
// var token = new CancellationTokenSource();
// await watch.Watch(token.Token);
// }
}
}

View File

@ -7,11 +7,13 @@ namespace Selector {
public bool Track(FullTrack track1, FullTrack track2, bool includingAlbum);
public bool Episode(FullEpisode ep1, FullEpisode ep2);
public bool Album(FullAlbum album1, FullAlbum album2);
public bool Show(FullShow show1, FullShow show2);
public bool Artist(FullArtist artist1, FullArtist artist2);
public bool Track(SimpleTrack track1, SimpleTrack track2);
public bool Episode(SimpleEpisode ep1, SimpleEpisode ep2);
public bool Album(SimpleAlbum album1, SimpleAlbum album2);
public bool Show(SimpleShow show1, SimpleShow show2);
public bool Artist(SimpleArtist artist1, SimpleArtist artist2);
public bool Context(Context context1, Context context2);

View File

@ -18,7 +18,8 @@ namespace Selector {
new public bool Episode(FullEpisode ep1, FullEpisode ep2)
{
return ep1.Uri == ep2.Uri;
return ep1.Uri == ep2.Uri
&& Show(ep1.Show, ep2.Show);
}
new public bool Album(FullAlbum album1, FullAlbum album2)
@ -27,6 +28,12 @@ namespace Selector {
&& Enumerable.SequenceEqual(album1.Artists.Select(a => a.Name), album2.Artists.Select(a => a.Name));
}
new public bool Show(FullShow show1, FullShow show2)
{
return show1.Name == show2.Name
&& show1.Publisher == show2.Publisher;
}
new public bool Artist(FullArtist artist1, FullArtist artist2)
{
return artist1.Name == artist2.Name;
@ -48,6 +55,12 @@ namespace Selector {
return album1.Name == album2.Name
&& Enumerable.SequenceEqual(album1.Artists.Select(a => a.Name), album2.Artists.Select(a => a.Name));
}
new public bool Show(SimpleShow show1, SimpleShow show2)
{
return show1.Name == show2.Name
&& show1.Publisher == show2.Publisher;
}
new public bool Artist(SimpleArtist artist1, SimpleArtist artist2)
{

View File

@ -24,6 +24,11 @@ namespace Selector {
return album1.Uri == album2.Uri
&& Enumerable.SequenceEqual(album1.Artists.Select(a => a.Uri), album2.Artists.Select(a => a.Uri));
}
public bool Show(FullShow show1, FullShow show2)
{
return show1.Uri == show2.Uri;
}
public bool Artist(FullArtist artist1, FullArtist artist2)
{
return artist1.Uri == artist2.Uri;
@ -45,6 +50,12 @@ namespace Selector {
return album1.Uri == album2.Uri
&& Enumerable.SequenceEqual(album1.Artists.Select(a => a.Uri), album2.Artists.Select(a => a.Uri));
}
public bool Show(SimpleShow show1, SimpleShow show2)
{
return show1.Uri == show2.Uri;
}
public bool Artist(SimpleArtist artist1, SimpleArtist artist2)
{
return artist1.Uri == artist2.Uri;
@ -54,6 +65,7 @@ namespace Selector {
{
return context1.Uri == context2.Uri;
}
public bool Device(Device device1, Device device2)
{
return device1.Id == device2.Id;

View File

@ -6,10 +6,11 @@ namespace Selector
{
public interface IPlayerWatcher: IWatcher
{
public event EventHandler<ListeningChangeEventArgs> TrackChange;
public event EventHandler<ListeningChangeEventArgs> ItemChange;
public event EventHandler<ListeningChangeEventArgs> AlbumChange;
public event EventHandler<ListeningChangeEventArgs> ArtistChange;
public event EventHandler<ListeningChangeEventArgs> ContextChange;
public event EventHandler<ListeningChangeEventArgs> ContentChange;
// public event EventHandler<ListeningChangeEventArgs> VolumeChange;
// public event EventHandler<ListeningChangeEventArgs> DeviceChange;

View File

@ -9,13 +9,13 @@ namespace Selector
public class PlayerWatcher: IPlayerWatcher
{
private readonly IPlayerClient spotifyClient;
private IScheduler sleepScheduler;
private IEqualityChecker equalityChecker;
public event EventHandler<ListeningChangeEventArgs> TrackChange;
public event EventHandler<ListeningChangeEventArgs> ItemChange;
public event EventHandler<ListeningChangeEventArgs> AlbumChange;
public event EventHandler<ListeningChangeEventArgs> ArtistChange;
public event EventHandler<ListeningChangeEventArgs> ContextChange;
public event EventHandler<ListeningChangeEventArgs> ContentChange;
// public event EventHandler<ListeningChangeEventArgs> VolumeChange;
// public event EventHandler<ListeningChangeEventArgs> DeviceChange;
@ -24,80 +24,157 @@ namespace Selector
private CurrentlyPlaying live { get; set; }
private List<List<CurrentlyPlaying>> lastPlays { get; set; }
public PlayerWatcher(IPlayerClient spotifyClient, IScheduler sleepScheduler, IEqualityChecker equalityChecker) {
private int _pollPeriod;
public int PollPeriod {
get => _pollPeriod;
set => _pollPeriod = Math.Max(0, value);
}
public PlayerWatcher(IPlayerClient spotifyClient,
IEqualityChecker equalityChecker,
int pollPeriod = 3000) {
this.spotifyClient = spotifyClient;
this.sleepScheduler = sleepScheduler;
this.equalityChecker = equalityChecker;
this.PollPeriod = pollPeriod;
lastPlays = new List<List<CurrentlyPlaying>>();
}
public async Task WatchOne()
{
var polledCurrent = await spotifyClient.GetCurrentlyPlaying(new PlayerCurrentlyPlayingRequest());
StoreCurrentPlaying(polledCurrent);
CurrentlyPlaying existing;
if(live is null) {
live = polledCurrent;
existing = polledCurrent;
}
else {
existing = live;
live = polledCurrent;
}
try{
var existingItem = (FullTrack) existing.Item;
var currentItem = (FullTrack) live.Item;
var polledCurrent = await spotifyClient.GetCurrentlyPlaying(new PlayerCurrentlyPlayingRequest());
if(!equalityChecker.Track(existingItem, currentItem, true)) {
OnTrackChange(new ListeningChangeEventArgs(){
Previous = existing,
Current = live
});
if (polledCurrent != null) StoreCurrentPlaying(polledCurrent);
CurrentlyPlaying previous;
if(live is null) {
live = polledCurrent;
previous = polledCurrent;
}
else {
previous = live;
live = polledCurrent;
}
if(!equalityChecker.Album(existingItem.Album, currentItem.Album)) {
OnAlbumChange(new ListeningChangeEventArgs(){
Previous = existing,
Current = live
});
// NOT PLAYING
if(previous is null && live is null)
{
// Console.WriteLine("not playing");
}
else
{
// STARTED PLAYBACK
if(previous is null && (live.Item is FullTrack || live.Item is FullEpisode))
{
// Console.WriteLine("started playing");
if(!equalityChecker.Artist(existingItem.Artists[0], currentItem.Artists[0])) {
OnArtistChange(new ListeningChangeEventArgs(){
Previous = existing,
Current = live
});
}
}
// STOPPED PLAYBACK
else if((previous.Item is FullTrack || previous.Item is FullEpisode) && live is null)
{
// Console.WriteLine("stopped playing");
if(!equalityChecker.Context(existing.Context, live.Context)) {
OnContextChange(new ListeningChangeEventArgs(){
Previous = existing,
Current = live
});
}
}
else {
if(existing.IsPlaying != live.IsPlaying) {
OnPlayingChange(new ListeningChangeEventArgs(){
Previous = existing,
Current = live
});
}
// MUSIC
if(previous.Item is FullTrack && live.Item is FullTrack)
{
var previousItem = (FullTrack) previous.Item;
var currentItem = (FullTrack) live.Item;
if(!equalityChecker.Track(previousItem, currentItem, true)) {
OnItemChange(new ListeningChangeEventArgs(){
Previous = previous,
Current = live
});
}
if(!equalityChecker.Album(previousItem.Album, currentItem.Album)) {
OnAlbumChange(new ListeningChangeEventArgs(){
Previous = previous,
Current = live
});
}
if(!equalityChecker.Artist(previousItem.Artists[0], currentItem.Artists[0])) {
OnArtistChange(new ListeningChangeEventArgs(){
Previous = previous,
Current = live
});
}
}
// CHANGED CONTENT
else if(previous.Item is FullTrack && live.Item is FullEpisode
|| previous.Item is FullEpisode && live.Item is FullTrack)
{
OnContentChange(new ListeningChangeEventArgs(){
Previous = previous,
Current = live
});
OnItemChange(new ListeningChangeEventArgs(){
Previous = previous,
Current = live
});
}
// PODCASTS
else if(previous.Item is FullEpisode && live.Item is FullEpisode)
{
var previousItem = (FullEpisode) previous.Item;
var currentItem = (FullEpisode) live.Item;
if(!equalityChecker.Episode(previousItem, currentItem)) {
OnItemChange(new ListeningChangeEventArgs(){
Previous = previous,
Current = live
});
}
}
else {
throw new NotSupportedException("Unknown item combination");
}
// CONTEXT
if(!equalityChecker.Context(previous.Context, live.Context)) {
OnContextChange(new ListeningChangeEventArgs(){
Previous = previous,
Current = live
});
}
// IS PLAYING
if(previous.IsPlaying != live.IsPlaying) {
OnPlayingChange(new ListeningChangeEventArgs(){
Previous = previous,
Current = live
});
}
}
}
}
catch(InvalidCastException)
catch(APIUnauthorizedException e)
{
var existingItem = (FullEpisode) existing.Item;
throw new NotImplementedException("Podcasts not implemented");
throw e;
}
catch(APITooManyRequestsException e)
{
throw e;
}
catch(APIException e)
{
throw e;
}
}
public Task Watch(CancellationToken cancelToken)
public async Task Watch(CancellationToken cancelToken)
{
return Task.CompletedTask;
while (!cancelToken.IsCancellationRequested)
{
await WatchOne();
await Task.Delay(PollPeriod);
}
}
public CurrentlyPlaying NowPlaying()
@ -156,9 +233,9 @@ namespace Selector
}
}
protected virtual void OnTrackChange(ListeningChangeEventArgs args)
protected virtual void OnItemChange(ListeningChangeEventArgs args)
{
TrackChange?.Invoke(this, args);
ItemChange?.Invoke(this, args);
}
protected virtual void OnAlbumChange(ListeningChangeEventArgs args)
@ -176,6 +253,10 @@ namespace Selector
ContextChange?.Invoke(this, args);
}
protected virtual void OnContentChange(ListeningChangeEventArgs args)
{
ContentChange?.Invoke(this, args);
}
// protected virtual void OnVolumeChange(ListeningChangeEventArgs args)
// {