From 6836cf5c9ec86c97cd8d2ada638e12f7c0cc0a6d Mon Sep 17 00:00:00 2001 From: Claudio Ortolina Date: Sun, 1 Mar 2026 09:46:27 +0000 Subject: [PATCH] Integrate refresh of Last.fm data (both per artist and batch) --- config/config.exs | 2 +- lib/music_library/artists/batch.ex | 6 +++ lib/music_library/worker/fetch_artist_info.ex | 2 + .../worker/fetch_artist_last_fm_data.ex | 15 ++++--- .../live/artist_live/show.ex | 42 +++++++++++++++++++ .../live/maintenance_live/index.ex | 25 +++++++++++ priv/gettext/default.pot | 30 +++++++++++++ priv/gettext/en/LC_MESSAGES/default.po | 30 +++++++++++++ 8 files changed, 146 insertions(+), 6 deletions(-) diff --git a/config/config.exs b/config/config.exs index de10d802..8ecc89b2 100644 --- a/config/config.exs +++ b/config/config.exs @@ -93,7 +93,7 @@ config :phoenix, :json_library, JSON config :music_library, Oban, engine: Oban.Engines.Lite, - queues: [default: 10, heavy_writes: 1, music_brainz: 1, discogs: 1, wikipedia: 1], + queues: [default: 10, heavy_writes: 1, music_brainz: 1, discogs: 1, wikipedia: 1, last_fm: 1], repo: MusicLibrary.BackgroundRepo, plugins: [ {Oban.Plugins.Cron, diff --git a/lib/music_library/artists/batch.ex b/lib/music_library/artists/batch.ex index 88bba49f..edb4e417 100644 --- a/lib/music_library/artists/batch.ex +++ b/lib/music_library/artists/batch.ex @@ -22,4 +22,10 @@ defmodule MusicLibrary.Artists.Batch do Artists.refresh_wikipedia_data_async(artist_info) end) end + + def refresh_lastfm_data do + Batch.run_on_all(from(r in ArtistInfo), "artist_info", fn artist_info -> + Artists.fetch_lastfm_data_async(artist_info.id) + end) + end end diff --git a/lib/music_library/worker/fetch_artist_info.ex b/lib/music_library/worker/fetch_artist_info.ex index dbee6163..7a330d2b 100644 --- a/lib/music_library/worker/fetch_artist_info.ex +++ b/lib/music_library/worker/fetch_artist_info.ex @@ -6,6 +6,8 @@ defmodule MusicLibrary.Worker.FetchArtistInfo do with {:ok, _artist_info} <- MusicLibrary.Artists.fetch_artist_info(artist_id), {:ok, _artist_info} <- MusicLibrary.Artists.fetch_wikipedia_data(artist_id), {:ok, _artist_info} <- MusicLibrary.Artists.fetch_image(artist_id) do + # fetch_lastfm_data returns {:ok, _} even on API errors, so it won't block embeddings + MusicLibrary.Artists.fetch_lastfm_data(artist_id) regenerate_record_embeddings(artist_id) end end diff --git a/lib/music_library/worker/fetch_artist_last_fm_data.ex b/lib/music_library/worker/fetch_artist_last_fm_data.ex index cee6a155..6fc4a319 100644 --- a/lib/music_library/worker/fetch_artist_last_fm_data.ex +++ b/lib/music_library/worker/fetch_artist_last_fm_data.ex @@ -1,11 +1,16 @@ defmodule MusicLibrary.Worker.FetchArtistLastFmData do - use Oban.Worker, queue: :default, max_attempts: 3 + use Oban.Worker, queue: :last_fm, max_attempts: 3 @impl Oban.Worker def perform(%Oban.Job{args: %{"id" => artist_id}}) do - case MusicLibrary.Artists.fetch_lastfm_data(artist_id) do - {:ok, _artist_info} -> :ok - error -> error - end + result = + case MusicLibrary.Artists.fetch_lastfm_data(artist_id) do + {:ok, _artist_info} -> :ok + error -> error + end + + Process.sleep(500) + + result end end diff --git a/lib/music_library_web/live/artist_live/show.ex b/lib/music_library_web/live/artist_live/show.ex index fe58a8ed..c610da83 100644 --- a/lib/music_library_web/live/artist_live/show.ex +++ b/lib/music_library_web/live/artist_live/show.ex @@ -198,6 +198,18 @@ defmodule MusicLibraryWeb.ArtistLive.Show do /> {gettext("Refresh Wikipedia")} + <.dropdown_link + id={"actions-#{@artist.musicbrainz_id}-refresh-lastfm"} + phx-click={JS.push("refresh_lastfm_data", value: %{id: @artist.musicbrainz_id})} + > + <.icon + name="hero-arrow-path" + class="h-4 w-4 mr-1 phx-click-loading:animate-spin" + aria-hidden="true" + data-slot="icon" + /> + {gettext("Refresh Last.fm")} + @@ -444,6 +456,14 @@ defmodule MusicLibraryWeb.ArtistLive.Show do data: @artist_info.wikipedia_data, type: :json } + ), + if(@artist_info.lastfm_data != %{}, + do: %{ + name: "lastfm", + title: gettext("Last.fm"), + data: @artist_info.lastfm_data, + type: :json + } ) ], & &1 @@ -538,6 +558,28 @@ defmodule MusicLibraryWeb.ArtistLive.Show do end end + def handle_event("refresh_lastfm_data", %{"id" => id}, socket) do + case Artists.fetch_lastfm_data(id) do + {:ok, artist_info} -> + id + |> Records.get_artist_records() + |> Enum.each(&Records.generate_embedding_async/1) + + {:noreply, + socket + |> assign(:artist_info, artist_info) + |> put_toast(:info, gettext("Last.fm data refreshed successfully"))} + + {:error, reason} -> + {:noreply, + socket + |> put_toast( + :error, + gettext("Error refreshing Last.fm data") <> "," <> inspect(reason) + )} + end + end + def handle_event("refresh_artist_image", %{"id" => id}, socket) do case Artists.fetch_image(id) do {:ok, artist_info} -> diff --git a/lib/music_library_web/live/maintenance_live/index.ex b/lib/music_library_web/live/maintenance_live/index.ex index a97d6ac6..55344263 100644 --- a/lib/music_library_web/live/maintenance_live/index.ex +++ b/lib/music_library_web/live/maintenance_live/index.ex @@ -110,6 +110,19 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do <.loading :if={@refresh_artists_wikipedia_jobs > 0} class="size-4" /> {gettext("Refresh Wikipedia data")} + <.button + type="button" + phx-click="refresh_artists_lastfm_data" + disabled={@refresh_artists_lastfm_jobs > 0} + data-confirm={ + gettext( + "Are you sure you want to refresh Last.fm data for all artists? This operation can take a long time to complete." + ) + } + > + <.loading :if={@refresh_artists_lastfm_jobs > 0} class="size-4" /> + {gettext("Refresh Last.fm data")} + @@ -191,6 +204,10 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do :refresh_artists_wikipedia_jobs, count_jobs("MusicLibrary.Worker.ArtistRefreshWikipediaData") ) + |> assign( + :refresh_artists_lastfm_jobs, + count_jobs("MusicLibrary.Worker.FetchArtistLastFmData") + ) end defp count_jobs(worker) do @@ -244,6 +261,14 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do |> put_toast(:info, gettext("Operation started in the background."))} end + def handle_event("refresh_artists_lastfm_data", _params, socket) do + Artists.Batch.refresh_lastfm_data() + + {:noreply, + socket + |> put_toast(:info, gettext("Operation started in the background."))} + end + def handle_event("db_vacuum", _params, socket) do case Repo.vacuum() do {:ok, _result} -> diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index a38367c0..9d9239da 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -2028,3 +2028,33 @@ msgstr "" #, elixir-autogen, elixir-format msgid "The application has been updated." msgstr "" + +#: lib/music_library_web/live/maintenance_live/index.ex +#, elixir-autogen, elixir-format +msgid "Are you sure you want to refresh Last.fm data for all artists? This operation can take a long time to complete." +msgstr "" + +#: lib/music_library_web/live/artist_live/show.ex +#, elixir-autogen, elixir-format +msgid "Error refreshing Last.fm data" +msgstr "" + +#: lib/music_library_web/live/artist_live/show.ex +#, elixir-autogen, elixir-format +msgid "Last.fm" +msgstr "" + +#: lib/music_library_web/live/artist_live/show.ex +#, elixir-autogen, elixir-format +msgid "Last.fm data refreshed successfully" +msgstr "" + +#: lib/music_library_web/live/artist_live/show.ex +#, elixir-autogen, elixir-format +msgid "Refresh Last.fm" +msgstr "" + +#: lib/music_library_web/live/maintenance_live/index.ex +#, elixir-autogen, elixir-format +msgid "Refresh Last.fm data" +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index cd75814e..6dcb0998 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -2028,3 +2028,33 @@ msgstr "" #, elixir-autogen, elixir-format, fuzzy msgid "The application has been updated." msgstr "" + +#: lib/music_library_web/live/maintenance_live/index.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to refresh Last.fm data for all artists? This operation can take a long time to complete." +msgstr "" + +#: lib/music_library_web/live/artist_live/show.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Error refreshing Last.fm data" +msgstr "" + +#: lib/music_library_web/live/artist_live/show.ex +#, elixir-autogen, elixir-format +msgid "Last.fm" +msgstr "" + +#: lib/music_library_web/live/artist_live/show.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Last.fm data refreshed successfully" +msgstr "" + +#: lib/music_library_web/live/artist_live/show.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Refresh Last.fm" +msgstr "" + +#: lib/music_library_web/live/maintenance_live/index.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Refresh Last.fm data" +msgstr ""