add to apple timeline with stored offset check

This commit is contained in:
Andy Pack 2025-04-02 07:48:11 +01:00
parent 7e8a9d1e29
commit bfc7a0db34
Signed by: sarsoo
GPG Key ID: A55BA3536A5E0ED7
4 changed files with 128 additions and 63 deletions

@ -26,15 +26,19 @@ public class AppleMusicApi(HttpClient client, string developerToken, string user
{ {
if (response.StatusCode == HttpStatusCode.Unauthorized) if (response.StatusCode == HttpStatusCode.Unauthorized)
{ {
throw new UnauthorisedException(); throw new UnauthorisedException { StatusCode = response.StatusCode };
} }
else if (response.StatusCode == HttpStatusCode.Forbidden) else if (response.StatusCode == HttpStatusCode.Forbidden)
{ {
throw new ForbiddenException(); throw new ForbiddenException { StatusCode = response.StatusCode };
} }
else if (response.StatusCode == HttpStatusCode.TooManyRequests) else if (response.StatusCode == HttpStatusCode.TooManyRequests)
{ {
throw new RateLimitException(); throw new RateLimitException { StatusCode = response.StatusCode };
}
else
{
throw new AppleMusicException { StatusCode = response.StatusCode };
} }
} }
} }

@ -37,50 +37,20 @@ public class AppleTimeline : Timeline<AppleMusicCurrentlyPlayingContext>
return newItems; return newItems;
} }
var stop = false; var (found, startIdx) = Loop(items, 0);
var found = 0;
var startIdx = 0; TimelineItem<AppleMusicCurrentlyPlayingContext>? popped = null;
while (!stop) 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; popped = Recent[^1];
// start from the end, minus this loops index, minus the offset Recent.RemoveAt(Recent.Count - 1);
var pulledIdx = (items.Count - 1) - i - startIdx;
if (pulledIdx < 0) startIdx = startIdxOffseted;
{
// 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;
}
} }
if (!stop) startIdx += 1;
} }
foreach (var item in items.TakeLast(startIdx)) foreach (var item in items.TakeLast(startIdx))
@ -89,6 +59,77 @@ public class AppleTimeline : Timeline<AppleMusicCurrentlyPlayingContext>
Recent.Add(TimelineItem<AppleMusicCurrentlyPlayingContext>.From(item, DateTime.UtcNow)); 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; 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;
}
} }

@ -1,5 +1,8 @@
using System.Net;
namespace Selector.AppleMusic.Exceptions; namespace Selector.AppleMusic.Exceptions;
public class AppleMusicException : Exception public class AppleMusicException : Exception
{ {
public HttpStatusCode StatusCode { get; set; }
} }

@ -7,7 +7,7 @@ namespace Selector.AppleMusic.Watcher;
public class AppleMusicPlayerWatcher : BaseWatcher, IAppleMusicPlayerWatcher public class AppleMusicPlayerWatcher : BaseWatcher, IAppleMusicPlayerWatcher
{ {
new protected readonly ILogger<AppleMusicPlayerWatcher> Logger; private new readonly ILogger<AppleMusicPlayerWatcher> Logger;
private readonly AppleMusicApi _appleMusicApi; private readonly AppleMusicApi _appleMusicApi;
public event EventHandler<AppleListeningChangeEventArgs> NetworkPoll; public event EventHandler<AppleListeningChangeEventArgs> NetworkPoll;
@ -15,12 +15,12 @@ public class AppleMusicPlayerWatcher : BaseWatcher, IAppleMusicPlayerWatcher
public event EventHandler<AppleListeningChangeEventArgs> AlbumChange; public event EventHandler<AppleListeningChangeEventArgs> AlbumChange;
public event EventHandler<AppleListeningChangeEventArgs> ArtistChange; public event EventHandler<AppleListeningChangeEventArgs> ArtistChange;
public AppleMusicCurrentlyPlayingContext Live { get; protected set; } public AppleMusicCurrentlyPlayingContext? Live { get; private set; }
protected AppleMusicCurrentlyPlayingContext Previous { get; set; } private AppleMusicCurrentlyPlayingContext? Previous { get; set; }
public AppleTimeline Past { get; set; } = new(); public AppleTimeline Past { get; private set; } = new();
public AppleMusicPlayerWatcher(AppleMusicApi appleMusicClient, public AppleMusicPlayerWatcher(AppleMusicApi appleMusicClient,
ILogger<AppleMusicPlayerWatcher> logger = null, ILogger<AppleMusicPlayerWatcher>? logger = null,
int pollPeriod = 3000 int pollPeriod = 3000
) : base(logger) ) : base(logger)
{ {
@ -35,10 +35,22 @@ public class AppleMusicPlayerWatcher : BaseWatcher, IAppleMusicPlayerWatcher
try try
{ {
using var polledLogScope = Logger.BeginScope(new Dictionary<string, object>() { { "user_id", Id } });
Logger.LogTrace("Making Apple Music call"); Logger.LogTrace("Making Apple Music call");
var polledCurrent = await _appleMusicApi.GetRecentlyPlayedTracks(); 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"); Logger.LogTrace("Received Apple Music call");
@ -65,23 +77,28 @@ public class AppleMusicPlayerWatcher : BaseWatcher, IAppleMusicPlayerWatcher
} }
catch (RateLimitException e) catch (RateLimitException e)
{ {
Logger.LogDebug("Rate Limit exception: [{message}]", e.Message); Logger.LogError(e, "Rate Limit exception");
// throw e; // throw;
} }
catch (ForbiddenException e) catch (ForbiddenException e)
{ {
Logger.LogDebug("Forbidden exception: [{message}]", e.Message); Logger.LogError(e, "Forbidden exception");
throw; // throw;
} }
catch (ServiceException e) catch (ServiceException e)
{ {
Logger.LogDebug("Apple Music internal error: [{message}]", e.Message); Logger.LogInformation("Apple Music internal error");
// throw e; // throw;
} }
catch (UnauthorisedException e) catch (UnauthorisedException e)
{ {
Logger.LogDebug("Unauthorised exception: [{message}]", e.Message); Logger.LogError(e, "Unauthorised exception");
// throw e; // 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(); 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() Live = new()
{ {
@ -116,27 +133,27 @@ public class AppleMusicPlayerWatcher : BaseWatcher, IAppleMusicPlayerWatcher
return Task.CompletedTask; return Task.CompletedTask;
} }
protected AppleListeningChangeEventArgs GetEvent() => private AppleListeningChangeEventArgs GetEvent() =>
AppleListeningChangeEventArgs.From(Previous, Live, Past, id: Id); AppleListeningChangeEventArgs.From(Previous, Live, Past, id: Id);
#region Event Firers #region Event Firers
protected virtual void OnNetworkPoll(AppleListeningChangeEventArgs args) private void OnNetworkPoll(AppleListeningChangeEventArgs args)
{ {
NetworkPoll?.Invoke(this, args); NetworkPoll?.Invoke(this, args);
} }
protected virtual void OnItemChange(AppleListeningChangeEventArgs args) private void OnItemChange(AppleListeningChangeEventArgs args)
{ {
ItemChange?.Invoke(this, args); ItemChange?.Invoke(this, args);
} }
protected virtual void OnAlbumChange(AppleListeningChangeEventArgs args) protected void OnAlbumChange(AppleListeningChangeEventArgs args)
{ {
AlbumChange?.Invoke(this, args); AlbumChange?.Invoke(this, args);
} }
protected virtual void OnArtistChange(AppleListeningChangeEventArgs args) protected void OnArtistChange(AppleListeningChangeEventArgs args)
{ {
ArtistChange?.Invoke(this, args); ArtistChange?.Invoke(this, args);
} }