Skip to content

Commit c0845f6

Browse files
authored
Merge pull request #38 from charlesharries/fetch-since-timestamp
fix: Fetch all Last.fm scrobbles since last fetch
2 parents b75d800 + 50034ed commit c0845f6

1 file changed

Lines changed: 31 additions & 5 deletions

File tree

service/lastfm/lastfm.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323

2424
const (
2525
lastfmAPIBaseURL = "https://ws.audioscrobbler.com/2.0/"
26+
downloadLimit = 200
27+
noTimestampLimit = 5
2628
)
2729

2830
type Service struct {
@@ -97,17 +99,42 @@ func (l *Service) loadUsernames() error {
9799
}
98100

99101
// getRecentTracks fetches the most recent tracks for a given Last.fm user.
100-
func (l *Service) getRecentTracks(ctx context.Context, username string, limit int) (*RecentTracksResponse, error) {
102+
func (l *Service) getRecentTracks(ctx context.Context, username string) (*RecentTracksResponse, error) {
101103
if username == "" {
102104
return nil, fmt.Errorf("username cannot be empty")
103105
}
104106

107+
if l.db == nil {
108+
return nil, fmt.Errorf("database connection is nil")
109+
}
110+
111+
user, err := l.db.GetUserByLastFM(username)
112+
if err != nil {
113+
return nil, fmt.Errorf("failed to get user ID for %s: %w", username, err)
114+
}
115+
116+
lastKnownTimestamp, err := l.db.GetLastKnownTimestamp(user.ID)
117+
if err != nil {
118+
return nil, fmt.Errorf("failed to get last scrobble timestamp for %s: %w", username, err)
119+
}
120+
105121
params := url.Values{}
106122
params.Set("method", "user.getrecenttracks")
107123
params.Set("user", username)
108124
params.Set("api_key", l.apiKey)
109125
params.Set("format", "json")
110-
params.Set("limit", strconv.Itoa(limit)) // Fetch a few more to handle duplicates/now playing
126+
127+
if lastKnownTimestamp == nil {
128+
// If no timestamp, then just get the {noTimestampLimit} most recent tracks
129+
l.logger.Printf("no previous scrobble timestamp found for user %s. retrieving last 5 tracks", username)
130+
params.Set("limit", strconv.Itoa(noTimestampLimit))
131+
} else {
132+
// As last.fm returns tracks >= from, this will always return at leastthe user's most recent track, but should
133+
// cover the (extremely remote) edge case where a user scrobbles multiple tracks within a single second
134+
l.logger.Printf("retrieving all tracks for %s since last timestamp %s", username, lastKnownTimestamp.Format(time.RFC3339))
135+
params.Set("limit", strconv.Itoa(downloadLimit))
136+
params.Set("from", strconv.FormatInt(lastKnownTimestamp.Unix(), 10))
137+
}
111138

112139
apiURL := lastfmAPIBaseURL + "?" + params.Encode()
113140

@@ -232,8 +259,7 @@ func (l *Service) fetchAllUserTracks(ctx context.Context) {
232259

233260
// Fetch slightly more than 1 track to better handle edge cases
234261
// where the latest is 'now playing' or duplicates exist.
235-
const fetchLimit = 5
236-
recentTracks, err := l.getRecentTracks(ctx, uname, fetchLimit)
262+
recentTracks, err := l.getRecentTracks(ctx, uname)
237263
if err != nil {
238264
l.logger.Printf("Error fetching tracks for %s: %v", uname, err)
239265
fetchErrors <- fmt.Errorf("fetch failed for %s: %w", uname, err) // Report error
@@ -332,7 +358,7 @@ func (l *Service) processTracks(ctx context.Context, username string, tracks []T
332358

333359
// find last non-now-playing track
334360
var lastNonNowPlaying *Track
335-
for i := len(tracks) - 1; i >= 0; i-- {
361+
for i := range tracks {
336362
if tracks[i].Attr == nil || tracks[i].Attr.NowPlaying != "true" {
337363
lastNonNowPlaying = &tracks[i]
338364
break

0 commit comments

Comments
 (0)