From bfc7a0db341ea25da30fb3517c83db5102dde2ed Mon Sep 17 00:00:00 2001 From: Andy Pack <andy@sarsoo.xyz> Date: Wed, 2 Apr 2025 07:48:11 +0100 Subject: [PATCH] add to apple timeline with stored offset check --- Selector.AppleMusic/AppleMusicApi.cs | 10 +- Selector.AppleMusic/AppleTimeline.cs | 121 ++++++++++++------ .../Exceptions/AppleMusicException.cs | 3 + Selector.AppleMusic/Watcher/PlayerWatcher.cs | 57 ++++++--- 4 files changed, 128 insertions(+), 63 deletions(-) diff --git a/Selector.AppleMusic/AppleMusicApi.cs b/Selector.AppleMusic/AppleMusicApi.cs index cd69df2..1f3300f 100644 --- a/Selector.AppleMusic/AppleMusicApi.cs +++ b/Selector.AppleMusic/AppleMusicApi.cs @@ -26,15 +26,19 @@ public class AppleMusicApi(HttpClient client, string developerToken, string user { if (response.StatusCode == HttpStatusCode.Unauthorized) { - throw new UnauthorisedException(); + throw new UnauthorisedException { StatusCode = response.StatusCode }; } else if (response.StatusCode == HttpStatusCode.Forbidden) { - throw new ForbiddenException(); + throw new ForbiddenException { StatusCode = response.StatusCode }; } else if (response.StatusCode == HttpStatusCode.TooManyRequests) { - throw new RateLimitException(); + throw new RateLimitException { StatusCode = response.StatusCode }; + } + else + { + throw new AppleMusicException { StatusCode = response.StatusCode }; } } } diff --git a/Selector.AppleMusic/AppleTimeline.cs b/Selector.AppleMusic/AppleTimeline.cs index fec99f5..28da72c 100644 --- a/Selector.AppleMusic/AppleTimeline.cs +++ b/Selector.AppleMusic/AppleTimeline.cs @@ -37,50 +37,20 @@ public class AppleTimeline : Timeline<AppleMusicCurrentlyPlayingContext> return newItems; } - var stop = false; - var found = 0; - var startIdx = 0; - while (!stop) + var (found, startIdx) = Loop(items, 0); + + TimelineItem<AppleMusicCurrentlyPlayingContext>? popped = null; + if (found == 0) { - for (var i = 0; i < items.Count; i++) + var (foundOffseted, startIdxOffseted) = Loop(items, 1); + + if (foundOffseted > found) { - var storedIdx = (Recent.Count - 1) - i; - // start from the end, minus this loops index, minus the offset - var pulledIdx = (items.Count - 1) - i - startIdx; + popped = Recent[^1]; + Recent.RemoveAt(Recent.Count - 1); - if (pulledIdx < 0) - { - // ran to the end of new items and none matched the end, add all the new ones - stop = true; - break; - } - - if (storedIdx < 0) - { - // all the new stuff matches, we're done and there's nothing new to add - stop = true; - break; - } - - if (Recent[storedIdx].Item.Track.Id == items[pulledIdx].Track.Id) - { - // good, keep going - found++; - if (found >= 3) - { - stop = true; - break; - } - } - else - { - // bad, doesn't match, break and bump stored - found = 0; - break; - } + startIdx = startIdxOffseted; } - - if (!stop) startIdx += 1; } foreach (var item in items.TakeLast(startIdx)) @@ -89,6 +59,77 @@ public class AppleTimeline : Timeline<AppleMusicCurrentlyPlayingContext> Recent.Add(TimelineItem<AppleMusicCurrentlyPlayingContext>.From(item, DateTime.UtcNow)); } + if (popped is not null) + { + var idx = Recent.FindIndex(x => x.Item.Track.Id == popped.Item.Track.Id); + if (idx >= 0) + { + newItems.RemoveAt(idx); + } + } + + CheckSize(); + return newItems; } + + private (int, int) Loop(List<AppleMusicCurrentlyPlayingContext> items, int storedOffset) + { + var stop = false; + var found = 0; + var startIdx = 0; + while (!stop) + { + found = Loop(items, storedOffset, ref startIdx, ref stop); + + if (!stop) startIdx += 1; + } + + return (found, startIdx); + } + + private int Loop(List<AppleMusicCurrentlyPlayingContext> items, int storedOffset, ref int startIdx, ref bool stop) + { + var found = 0; + + for (var i = 0; i < items.Count; i++) + { + var storedIdx = (Recent.Count - 1) - i - storedOffset; + // start from the end, minus this loops index, minus the offset + var pulledIdx = (items.Count - 1) - i - startIdx; + + if (pulledIdx < 0) + { + // ran to the end of new items and none matched the end, add all the new ones + stop = true; + break; + } + + if (storedIdx < 0) + { + // all the new stuff matches, we're done and there's nothing new to add + stop = true; + break; + } + + if (Recent[storedIdx].Item.Track.Id == items[pulledIdx].Track.Id) + { + // good, keep going + found++; + if (found >= 3) + { + stop = true; + break; + } + } + else + { + // bad, doesn't match, break and bump stored + found = 0; + break; + } + } + + return found; + } } \ No newline at end of file diff --git a/Selector.AppleMusic/Exceptions/AppleMusicException.cs b/Selector.AppleMusic/Exceptions/AppleMusicException.cs index ce36f73..5ca47e0 100644 --- a/Selector.AppleMusic/Exceptions/AppleMusicException.cs +++ b/Selector.AppleMusic/Exceptions/AppleMusicException.cs @@ -1,5 +1,8 @@ +using System.Net; + namespace Selector.AppleMusic.Exceptions; public class AppleMusicException : Exception { + public HttpStatusCode StatusCode { get; set; } } \ No newline at end of file diff --git a/Selector.AppleMusic/Watcher/PlayerWatcher.cs b/Selector.AppleMusic/Watcher/PlayerWatcher.cs index cbbc62a..2f5f76a 100644 --- a/Selector.AppleMusic/Watcher/PlayerWatcher.cs +++ b/Selector.AppleMusic/Watcher/PlayerWatcher.cs @@ -7,7 +7,7 @@ namespace Selector.AppleMusic.Watcher; public class AppleMusicPlayerWatcher : BaseWatcher, IAppleMusicPlayerWatcher { - new protected readonly ILogger<AppleMusicPlayerWatcher> Logger; + private new readonly ILogger<AppleMusicPlayerWatcher> Logger; private readonly AppleMusicApi _appleMusicApi; public event EventHandler<AppleListeningChangeEventArgs> NetworkPoll; @@ -15,12 +15,12 @@ public class AppleMusicPlayerWatcher : BaseWatcher, IAppleMusicPlayerWatcher public event EventHandler<AppleListeningChangeEventArgs> AlbumChange; public event EventHandler<AppleListeningChangeEventArgs> ArtistChange; - public AppleMusicCurrentlyPlayingContext Live { get; protected set; } - protected AppleMusicCurrentlyPlayingContext Previous { get; set; } - public AppleTimeline Past { get; set; } = new(); + public AppleMusicCurrentlyPlayingContext? Live { get; private set; } + private AppleMusicCurrentlyPlayingContext? Previous { get; set; } + public AppleTimeline Past { get; private set; } = new(); public AppleMusicPlayerWatcher(AppleMusicApi appleMusicClient, - ILogger<AppleMusicPlayerWatcher> logger = null, + ILogger<AppleMusicPlayerWatcher>? logger = null, int pollPeriod = 3000 ) : base(logger) { @@ -35,10 +35,22 @@ public class AppleMusicPlayerWatcher : BaseWatcher, IAppleMusicPlayerWatcher try { + using var polledLogScope = Logger.BeginScope(new Dictionary<string, object>() { { "user_id", Id } }); + Logger.LogTrace("Making Apple Music call"); var polledCurrent = await _appleMusicApi.GetRecentlyPlayedTracks(); - // using var polledLogScope = Logger.BeginScope(new Dictionary<string, object>() { { "context", polledCurrent?.DisplayString() } }); + if (polledCurrent is null) + { + Logger.LogInformation("Null response when calling Apple Music API"); + return; + } + + if (polledCurrent.Data is null) + { + Logger.LogInformation("Null track list when calling Apple Music API"); + return; + } Logger.LogTrace("Received Apple Music call"); @@ -65,23 +77,28 @@ public class AppleMusicPlayerWatcher : BaseWatcher, IAppleMusicPlayerWatcher } catch (RateLimitException e) { - Logger.LogDebug("Rate Limit exception: [{message}]", e.Message); - // throw e; + Logger.LogError(e, "Rate Limit exception"); + // throw; } catch (ForbiddenException e) { - Logger.LogDebug("Forbidden exception: [{message}]", e.Message); - throw; + Logger.LogError(e, "Forbidden exception"); + // throw; } catch (ServiceException e) { - Logger.LogDebug("Apple Music internal error: [{message}]", e.Message); - // throw e; + Logger.LogInformation("Apple Music internal error"); + // throw; } catch (UnauthorisedException e) { - Logger.LogDebug("Unauthorised exception: [{message}]", e.Message); - // throw e; + Logger.LogError(e, "Unauthorised exception"); + // throw; + } + catch (AppleMusicException e) + { + Logger.LogInformation("Apple Music exception ({})", e.StatusCode); + // throw; } } @@ -89,7 +106,7 @@ public class AppleMusicPlayerWatcher : BaseWatcher, IAppleMusicPlayerWatcher { var lastTrack = recentlyPlayedTracks.Data?.FirstOrDefault(); - if (Live != null && Live.Track != null && Live.Track.Id == lastTrack?.Id) + if (Live is { Track: not null } && Live.Track.Id == lastTrack?.Id) { Live = new() { @@ -116,27 +133,27 @@ public class AppleMusicPlayerWatcher : BaseWatcher, IAppleMusicPlayerWatcher return Task.CompletedTask; } - protected AppleListeningChangeEventArgs GetEvent() => + private AppleListeningChangeEventArgs GetEvent() => AppleListeningChangeEventArgs.From(Previous, Live, Past, id: Id); #region Event Firers - protected virtual void OnNetworkPoll(AppleListeningChangeEventArgs args) + private void OnNetworkPoll(AppleListeningChangeEventArgs args) { NetworkPoll?.Invoke(this, args); } - protected virtual void OnItemChange(AppleListeningChangeEventArgs args) + private void OnItemChange(AppleListeningChangeEventArgs args) { ItemChange?.Invoke(this, args); } - protected virtual void OnAlbumChange(AppleListeningChangeEventArgs args) + protected void OnAlbumChange(AppleListeningChangeEventArgs args) { AlbumChange?.Invoke(this, args); } - protected virtual void OnArtistChange(AppleListeningChangeEventArgs args) + protected void OnArtistChange(AppleListeningChangeEventArgs args) { ArtistChange?.Invoke(this, args); }