Remove LastFm.Feed, LastFm.Refresh, and LastFm.Supervisor
This commit is contained in:
@@ -1,48 +0,0 @@
|
||||
defmodule LastFm.Feed do
|
||||
@moduledoc """
|
||||
Persists scrobbled tracks in `MusicLibrary.Repo` and publishes feed updates.
|
||||
|
||||
Tracks are inserted into the `scrobbled_tracks` table with conflict handling on
|
||||
`[:scrobbled_at_uts, :title]` to avoid duplicates, then scrobble rules are applied
|
||||
to newly inserted rows.
|
||||
"""
|
||||
|
||||
@insertable_fields [
|
||||
:musicbrainz_id,
|
||||
:title,
|
||||
:artist,
|
||||
:album,
|
||||
:cover_url,
|
||||
:scrobbled_at_uts,
|
||||
:scrobbled_at_label,
|
||||
:last_fm_data
|
||||
]
|
||||
|
||||
@spec update([LastFm.Track.t()]) :: {:ok, non_neg_integer()} | no_return
|
||||
def update(tracks) do
|
||||
track_params =
|
||||
tracks
|
||||
|> Enum.map(fn t -> Map.take(t, @insertable_fields) end)
|
||||
|> Enum.map(&Map.to_list/1)
|
||||
|
||||
{count, tracks} =
|
||||
MusicLibrary.Repo.insert_all(LastFm.Track, track_params,
|
||||
on_conflict: :nothing,
|
||||
conflict_target: [:scrobbled_at_uts, :title],
|
||||
returning: true
|
||||
)
|
||||
|
||||
tracks
|
||||
|> MusicLibrary.ScrobbleRules.apply_all_rules()
|
||||
|> MusicLibrary.ScrobbleRules.log_apply_results()
|
||||
|
||||
Phoenix.PubSub.broadcast(LastFm.PubSub, "feed:update", %{track_count: count})
|
||||
|
||||
{:ok, count}
|
||||
end
|
||||
|
||||
@spec subscribe() :: :ok
|
||||
def subscribe do
|
||||
Phoenix.PubSub.subscribe(LastFm.PubSub, "feed:update")
|
||||
end
|
||||
end
|
||||
@@ -1,100 +0,0 @@
|
||||
defmodule LastFm.Refresh do
|
||||
@moduledoc """
|
||||
A GenServer that manages periodic refreshing of Last.fm scrobbled tracks.
|
||||
|
||||
This module is responsible for:
|
||||
- Fetching recent tracks from Last.fm at configurable intervals
|
||||
- Updating an in-memory feed with the latest tracks
|
||||
- Supporting both automatic and manual refresh modes
|
||||
|
||||
## Configuration
|
||||
|
||||
The server accepts a `LastFm.Config` struct with the following options:
|
||||
- `auto_refresh`: When true, automatically starts refreshing on init
|
||||
- `refresh_interval`: Time in milliseconds between refresh attempts
|
||||
|
||||
## Operation Modes
|
||||
|
||||
1. Automatic Mode (`auto_refresh: true`):
|
||||
- Starts refreshing immediately on initialization
|
||||
- Continues to refresh at the configured interval
|
||||
- Handles failures gracefully by continuing to retry
|
||||
|
||||
2. Manual Mode (`auto_refresh: false`):
|
||||
- Server remains dormant on initialization
|
||||
- Refreshes only occur via explicit `refresh/0` calls
|
||||
- Useful for testing or controlled refresh scenarios
|
||||
|
||||
## Usage
|
||||
|
||||
# Manual refresh
|
||||
LastFm.Refresh.refresh()
|
||||
|
||||
The module uses `LastFm.Feed` to store and broadcast track updates to subscribers.
|
||||
"""
|
||||
|
||||
use GenServer
|
||||
|
||||
alias LastFm.{API, Config, Feed}
|
||||
|
||||
@type config :: Config.t()
|
||||
|
||||
@spec start_link(config) :: GenServer.on_start()
|
||||
def start_link(config) do
|
||||
GenServer.start_link(__MODULE__, config, name: __MODULE__)
|
||||
end
|
||||
|
||||
@spec refresh() :: :ok
|
||||
def refresh do
|
||||
GenServer.call(__MODULE__, :refresh, 10_000)
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec init(config) :: {:ok, config, {:continue, :refresh}} | :ignore
|
||||
def init(config) do
|
||||
if config.auto_refresh do
|
||||
{:ok, config, {:continue, :refresh}}
|
||||
else
|
||||
:ignore
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec handle_call(:refresh, GenServer.from(), config) ::
|
||||
{:reply, :ok | {:error, term()}, config, pos_integer()}
|
||||
def handle_call(:refresh, _from, config) do
|
||||
case API.get_recent_tracks(config) do
|
||||
{:ok, tracks} ->
|
||||
Feed.update(tracks)
|
||||
{:reply, :ok, config, config.refresh_interval}
|
||||
|
||||
error ->
|
||||
{:reply, error, config, config.refresh_interval}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec handle_continue(atom(), config) :: {:noreply, config, pos_integer()}
|
||||
def handle_continue(:refresh, config), do: refresh(config)
|
||||
|
||||
@impl true
|
||||
@spec handle_info(atom(), config) :: {:noreply, config, pos_integer()}
|
||||
def handle_info(:refresh, config), do: refresh(config)
|
||||
|
||||
def handle_info(:timeout, config), do: refresh(config)
|
||||
|
||||
defp refresh(config) do
|
||||
case API.get_recent_tracks(config) do
|
||||
{:ok, tracks} ->
|
||||
Feed.update(tracks)
|
||||
{:noreply, config, config.refresh_interval}
|
||||
|
||||
{:error, error} ->
|
||||
if API.ErrorResponse.retryable_error?(error) do
|
||||
{:noreply, config, API.ErrorResponse.retry_delay(error)}
|
||||
else
|
||||
{:stop, error, config}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,19 +0,0 @@
|
||||
defmodule LastFm.Supervisor do
|
||||
@moduledoc false
|
||||
|
||||
use Supervisor
|
||||
|
||||
def start_link(config) do
|
||||
Supervisor.start_link(__MODULE__, config, name: __MODULE__)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(config) do
|
||||
children = [
|
||||
{Phoenix.PubSub, name: LastFm.PubSub},
|
||||
{LastFm.Refresh, config}
|
||||
]
|
||||
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
@@ -1,52 +0,0 @@
|
||||
defmodule LastFm.FeedTest do
|
||||
use MusicLibrary.DataCase
|
||||
|
||||
alias LastFm.{Album, Artist, Track}
|
||||
alias MusicLibrary.ListeningStats
|
||||
|
||||
@track_one %Track{
|
||||
musicbrainz_id: "5689211e-9afa-3c3e-8e34-63dc0de45ef1",
|
||||
title: "The Flow",
|
||||
artist: %Artist{
|
||||
musicbrainz_id: "0cf0af1f-20ca-4863-9b24-5f52772f7715",
|
||||
name: "Anekdoten"
|
||||
},
|
||||
album: %Album{
|
||||
musicbrainz_id: "08237599-8fdf-4e2b-a7c9-eb5336f60346",
|
||||
title: "Vemod"
|
||||
},
|
||||
cover_url: "https://lastfm.freetls.fastly.net/i/u/64s/9741e297b9884a4294624f0f90e14749.jpg",
|
||||
scrobbled_at_uts: 1_731_318_211,
|
||||
scrobbled_at_label: "11 Nov 2024, 09:43",
|
||||
last_fm_data: %{}
|
||||
}
|
||||
@track_two %Track{
|
||||
musicbrainz_id: "619cb295-b155-3e35-b65a-396a7cd1fc47",
|
||||
title: "Wheel",
|
||||
artist: %Artist{
|
||||
musicbrainz_id: "0cf0af1f-20ca-4863-9b24-5f52772f7715",
|
||||
name: "Anekdoten"
|
||||
},
|
||||
album: %Album{
|
||||
musicbrainz_id: "08237599-8fdf-4e2b-a7c9-eb5336f60346",
|
||||
title: "Vemod"
|
||||
},
|
||||
cover_url: "https://lastfm.freetls.fastly.net/i/u/64s/9741e297b9884a4294624f0f90e14749.jpg",
|
||||
scrobbled_at_uts: 1_731_318_945,
|
||||
scrobbled_at_label: "11 Nov 2024, 09:55",
|
||||
last_fm_data: %{}
|
||||
}
|
||||
|
||||
describe "update and broadcast" do
|
||||
test "stores the track and broadcasts the updated track count" do
|
||||
:ok = ListeningStats.subscribe()
|
||||
|
||||
assert {:ok, 2} == ListeningStats.update([@track_two, @track_one])
|
||||
assert_receive %{track_count: 2}
|
||||
|
||||
# Tracks have already been inserted, count of new tracks is 0
|
||||
assert {:ok, 0} == ListeningStats.update([@track_two, @track_one])
|
||||
assert_receive %{track_count: 0}
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user