From 47f8eddff1a0fbd1c8809d358709494be5d49af3 Mon Sep 17 00:00:00 2001 From: Claudio Ortolina Date: Sat, 28 Mar 2026 08:05:02 +0000 Subject: [PATCH] Move BackfillScrobbledTracks worker under MusicLibrary namespace --- docs/architecture.md | 2 +- lib/last_fm.ex | 17 +---------------- lib/music_library/listening_stats.ex | 18 +++++++++++++++++- .../worker/backfill_scrobbled_tracks.ex | 16 ++++++++++------ 4 files changed, 29 insertions(+), 24 deletions(-) rename lib/{last_fm => music_library}/worker/backfill_scrobbled_tracks.ex (54%) diff --git a/docs/architecture.md b/docs/architecture.md index 665db4f6..d756aa07 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -198,7 +198,7 @@ stubbed via `Req.Test` (configured in `config/test.exs`). | `ArtistRefreshAllMusicBrainzData` | music_brainz | Manual / cron (bulk refresh via Artists.Batch) | | `ArtistRefreshAllDiscogsData` | discogs | Manual / cron (bulk refresh via Artists.Batch) | | `ArtistRefreshAllWikipediaData` | wikipedia | Manual / cron (bulk refresh via Artists.Batch) | -| `LastFm.Worker.BackfillScrobbledTracks` | heavy_writes | Manual (self-chaining batch import) | +| `BackfillScrobbledTracks` | heavy_writes | Manual (self-chaining batch import) | | `SendRecordsOnThisDayEmail` | default | Cron (daily "records on this day" email) | ### Cron Workers diff --git a/lib/last_fm.ex b/lib/last_fm.ex index b4d8f01c..964d1927 100644 --- a/lib/last_fm.ex +++ b/lib/last_fm.ex @@ -3,8 +3,7 @@ defmodule LastFm do Last.fm API facade for scrobbling and listening history. """ - alias LastFm.{API, Scrobble, Track, Worker} - alias MusicLibrary.{BackgroundRepo, Repo} + alias LastFm.{API, Scrobble, Track} @spec get_tracks(keyword()) :: {:ok, [Track.t()]} | {:error, term()} def get_tracks(opts) do @@ -12,20 +11,6 @@ defmodule LastFm do API.get_recent_tracks(opts, last_fm_config) end - @spec lowest_scrobbled_at_uts() :: integer() | nil - def lowest_scrobbled_at_uts do - Repo.aggregate(Track, :min, :scrobbled_at_uts) - end - - @spec backfill_scrobbled_tracks() :: {:ok, Oban.Job.t()} | {:error, Ecto.Changeset.t()} - def backfill_scrobbled_tracks do - to_uts = lowest_scrobbled_at_uts() - - %{"to_uts" => to_uts} - |> Worker.BackfillScrobbledTracks.new() - |> BackgroundRepo.insert() - end - @spec get_artist_info(String.t(), String.t()) :: {:ok, LastFm.Artist.t()} | {:error, term()} def get_artist_info(musicbrainz_id, name) do last_fm_config = last_fm_config() diff --git a/lib/music_library/listening_stats.ex b/lib/music_library/listening_stats.ex index f1210859..c730e186 100644 --- a/lib/music_library/listening_stats.ex +++ b/lib/music_library/listening_stats.ex @@ -13,12 +13,14 @@ defmodule MusicLibrary.ListeningStats do alias MusicLibrary.{ Artists, + BackgroundRepo, Collection, ListeningStats.Refresh, Records.ArtistRecord, Records.Record, Repo, - Wishlist + Wishlist, + Worker } @pagination Application.compile_env!(:music_library, :pagination) @@ -79,6 +81,20 @@ defmodule MusicLibrary.ListeningStats do Refresh.refresh() end + @spec lowest_scrobbled_at_uts() :: integer() | nil + def lowest_scrobbled_at_uts do + Repo.aggregate(Track, :min, :scrobbled_at_uts) + end + + @spec backfill_scrobbled_tracks() :: {:ok, Oban.Job.t()} | {:error, Ecto.Changeset.t()} + def backfill_scrobbled_tracks do + to_uts = lowest_scrobbled_at_uts() + + %{"to_uts" => to_uts} + |> Worker.BackfillScrobbledTracks.new() + |> BackgroundRepo.insert() + end + @spec artist_play_count(String.t()) :: non_neg_integer() def artist_play_count(artist_musicbrainz_id) do from(t in Track, diff --git a/lib/last_fm/worker/backfill_scrobbled_tracks.ex b/lib/music_library/worker/backfill_scrobbled_tracks.ex similarity index 54% rename from lib/last_fm/worker/backfill_scrobbled_tracks.ex rename to lib/music_library/worker/backfill_scrobbled_tracks.ex index ff38ba5f..392318d8 100644 --- a/lib/last_fm/worker/backfill_scrobbled_tracks.ex +++ b/lib/music_library/worker/backfill_scrobbled_tracks.ex @@ -1,19 +1,23 @@ -defmodule LastFm.Worker.BackfillScrobbledTracks do +defmodule MusicLibrary.Worker.BackfillScrobbledTracks do + @moduledoc """ + Oban worker that backfills scrobbled tracks from Last.fm in batches. + + Self-chaining: after importing a full batch, enqueues itself with the + next `to_uts` timestamp to continue backfilling. + """ + use Oban.Worker, queue: :heavy_writes, max_attempts: 3 - alias MusicLibrary.BackgroundRepo + alias MusicLibrary.{BackgroundRepo, ListeningStats} @backfill_delay 1 @batch_size 200 @impl Oban.Worker def perform(%{args: %{"to_uts" => to_uts}}) do - # importing is an all or nothing operation, which means that we can - # use the returning count to determine if we reached the end of the backfilling - # process. case LastFm.Import.batch(to_uts: to_uts, limit: @batch_size) do {:ok, @batch_size} -> - next_to_uts = LastFm.lowest_scrobbled_at_uts() + next_to_uts = ListeningStats.lowest_scrobbled_at_uts() %{"to_uts" => next_to_uts} |> new(schedule_in: @backfill_delay)