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)
{
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 };
}
}
}

@ -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;
}
}

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

@ -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);
}