diff --git a/lib/music_library/listening_stats.ex b/lib/music_library/listening_stats.ex index 364d35f0..1f38e5f8 100644 --- a/lib/music_library/listening_stats.ex +++ b/lib/music_library/listening_stats.ex @@ -10,7 +10,7 @@ defmodule MusicLibrary.ListeningStats do import Ecto.Query alias LastFm.Track - alias MusicLibrary.{Artists, Collection, Records.ArtistRecord, Repo, Wishlist} + alias MusicLibrary.{Artists, Collection, Records.ArtistRecord, Records.Record, Repo, Wishlist} @pagination Application.compile_env!(:music_library, :pagination) @@ -247,6 +247,42 @@ defmodule MusicLibrary.ListeningStats do } end + @spec get_last_listened_track(Record.t()) :: Track.t() | nil + def get_last_listened_track(record) do + q = + from t in scrobbles_for_record_query(record), + order_by: [desc: t.scrobbled_at_uts], + limit: 1 + + Repo.one(q) + end + + @spec play_count(Record.t()) :: non_neg_integer() + def play_count(record) do + record + |> scrobbles_for_record_query() + |> Repo.aggregate(:count) + end + + defp scrobbles_for_record_query(record) do + record_id = record.id + + q = + from r in fragment("records, json_each(records.release_ids)"), + where: fragment("records.id = ?", ^record_id), + select: r.value + + release_ids = Repo.all(q) + main_artist_name = Record.main_artist(record).name + record_title = record.title + + from t in Track, + where: fragment("? ->> '$.musicbrainz_id'", t.album) in ^release_ids, + or_where: + fragment("? ->> '$.title'", t.album) == ^record_title and + fragment("? ->> '$.name'", t.artist) == ^main_artist_name + end + defp polyfill_artist(artist, musicbrainz_id) do if is_nil(artist.musicbrainz_id) or artist.musicbrainz_id == "" do %{artist | musicbrainz_id: musicbrainz_id} diff --git a/lib/music_library/records.ex b/lib/music_library/records.ex index 7df4cf44..1eb105b6 100644 --- a/lib/music_library/records.ex +++ b/lib/music_library/records.ex @@ -278,42 +278,6 @@ defmodule MusicLibrary.Records do end end - @spec get_last_listened_track(Record.t()) :: LastFm.Track.t() | nil - def get_last_listened_track(record) do - q = - from t in scrobbles_for_record_query(record), - order_by: [desc: t.scrobbled_at_uts], - limit: 1 - - Repo.one(q) - end - - @spec play_count(Record.t()) :: non_neg_integer() - def play_count(record) do - record - |> scrobbles_for_record_query() - |> Repo.aggregate(:count) - end - - defp scrobbles_for_record_query(record) do - record_id = record.id - - q = - from r in fragment("records, json_each(records.release_ids)"), - where: fragment("records.id = ?", ^record_id), - select: r.value - - release_ids = Repo.all(q) - main_artist_name = Record.main_artist(record).name - record_title = record.title - - from t in LastFm.Track, - where: fragment("? ->> '$.musicbrainz_id'", t.album) in ^release_ids, - or_where: - fragment("? ->> '$.title'", t.album) == ^record_title and - fragment("? ->> '$.name'", t.artist) == ^main_artist_name - end - @spec refresh_cover(Record.t()) :: {:ok, Record.t()} | {:error, term()} def refresh_cover(record) do with {:ok, cover_data} <- MusicBrainz.get_cover_art({:url, record.cover_url}), diff --git a/lib/music_library_web/live/collection_live/show.ex b/lib/music_library_web/live/collection_live/show.ex index ee73971e..2a44d203 100644 --- a/lib/music_library_web/live/collection_live/show.ex +++ b/lib/music_library_web/live/collection_live/show.ex @@ -21,7 +21,7 @@ defmodule MusicLibraryWeb.CollectionLive.Show do similar_records: 1 ] - alias MusicLibrary.{Records, RecordSets, ScrobbleActivity} + alias MusicLibrary.{ListeningStats, Records, RecordSets, ScrobbleActivity} alias MusicLibrary.Records.Similarity alias MusicLibraryWeb.ErrorMessages alias Phoenix.LiveView.JS @@ -346,8 +346,8 @@ defmodule MusicLibraryWeb.CollectionLive.Show do @impl true def handle_params(%{"id" => id}, _, socket) do record = Records.get_record!(id) - last_listened_track = Records.get_last_listened_track(record) - play_count = Records.play_count(record) || 0 + last_listened_track = ListeningStats.get_last_listened_track(record) + play_count = ListeningStats.play_count(record) || 0 socket = if record.selected_release_id do diff --git a/test/music_library/listening_stats_test.exs b/test/music_library/listening_stats_test.exs index 04fffeba..af27bcf0 100644 --- a/test/music_library/listening_stats_test.exs +++ b/test/music_library/listening_stats_test.exs @@ -4,7 +4,9 @@ defmodule MusicLibrary.ListeningStatsTest do import MusicLibrary.ArtistInfoFixtures import MusicLibrary.ScrobbledTracksFixtures + alias MusicLibrary.Fixtures.Records, as: RecordsFixtures alias MusicLibrary.ListeningStats + alias MusicLibrary.Records.Record describe "scrobble_count/0" do test "returns correct count" do @@ -94,6 +96,65 @@ defmodule MusicLibrary.ListeningStatsTest do end end + describe "get_last_listened_track/1" do + test "returns the most recent scrobbled track for a record" do + record = RecordsFixtures.record() + main_artist = Record.main_artist(record) + now = System.system_time(:second) + + _older = + track_fixture(%{ + artist_name: main_artist.name, + album_title: record.title, + title: "Older Track", + scrobbled_at_uts: now - 200 + }) + + track_fixture(%{ + artist_name: main_artist.name, + album_title: record.title, + title: "Newer Track", + scrobbled_at_uts: now - 100 + }) + + result = ListeningStats.get_last_listened_track(record) + + assert result.title == "Newer Track" + assert result.scrobbled_at_uts == now - 100 + end + + test "returns nil when no scrobbles exist for the record" do + record = RecordsFixtures.record() + + assert ListeningStats.get_last_listened_track(record) == nil + end + end + + describe "play_count/1" do + test "returns the correct scrobble count for a record" do + record = RecordsFixtures.record() + main_artist = Record.main_artist(record) + now = System.system_time(:second) + + for i <- 1..3 do + track_fixture(%{ + artist_name: main_artist.name, + album_title: record.title, + title: "Track #{i}", + scrobbled_at_uts: now - i * 100 + }) + end + + assert ListeningStats.play_count(record) == 3 + end + + test "returns 0 when no scrobbles exist for the record" do + record = RecordsFixtures.record() + + assert ListeningStats.play_count(record) == 0 + end + end + describe "get_top_artists_by_days/2" do test "counts tracks with missing artist_infos records within date range" do artist_info =