From 09f61b6039d5d27f14699029913d3d82b069e22b Mon Sep 17 00:00:00 2001 From: Claudio Ortolina Date: Wed, 25 Mar 2026 11:55:28 +0000 Subject: [PATCH] Move asset pruning logic to Assets context Closes #130 --- lib/music_library/assets.ex | 24 +++++++++++++++++++ lib/music_library/worker/prune_assets.ex | 30 +++++------------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/lib/music_library/assets.ex b/lib/music_library/assets.ex index 617a9508..30f8b2f0 100644 --- a/lib/music_library/assets.ex +++ b/lib/music_library/assets.ex @@ -56,6 +56,30 @@ defmodule MusicLibrary.Assets do ) end + @doc """ + Deletes all assets not referenced by any record cover or artist info image. + Returns the number of pruned assets. + """ + @spec prune_unreferenced() :: non_neg_integer() + def prune_unreferenced do + # Note that SQLite doesn't support left joins on delete, so we do it in two steps. + asset_hashes = + from a in Asset, + left_join: r in MusicLibrary.Records.Record, + on: r.cover_hash == a.hash, + left_join: ai in MusicLibrary.Artists.ArtistInfo, + on: ai.image_data_hash == a.hash, + where: is_nil(r.id) and is_nil(ai.id), + select: a.hash + + q = + from a in Asset, + where: a.hash in subquery(asset_hashes) + + {count, nil} = Repo.delete_all(q) + count + end + @spec track_total_cache_size() :: :ok def track_total_cache_size do :telemetry.execute( diff --git a/lib/music_library/worker/prune_assets.ex b/lib/music_library/worker/prune_assets.ex index 289f79b9..3ffe3c84 100644 --- a/lib/music_library/worker/prune_assets.ex +++ b/lib/music_library/worker/prune_assets.ex @@ -1,35 +1,17 @@ defmodule MusicLibrary.Worker.PruneAssets do - use Oban.Worker, queue: :default, max_attempts: 3 + @moduledoc """ + Prunes unreferenced assets from the database. + """ - import Ecto.Query + use Oban.Worker, queue: :default, max_attempts: 3 require Logger - alias MusicLibrary.{ - Artists.ArtistInfo, - Assets.Asset, - Records.Record, - Repo - } + alias MusicLibrary.Assets @impl Oban.Worker def perform(_) do - # Find all assets that are not referenced by any records or artist info - # Note that SQLite doesn't support left joins on delete, so we do it in two steps. - asset_hashes = - from a in Asset, - left_join: r in Record, - on: r.cover_hash == a.hash, - left_join: ai in ArtistInfo, - on: ai.image_data_hash == a.hash, - where: is_nil(r.id) and is_nil(ai.id), - select: a.hash - - q = - from a in Asset, - where: a.hash in subquery(asset_hashes) - - {count, nil} = Repo.delete_all(q) + count = Assets.prune_unreferenced() Logger.info(fn -> "Pruned #{count} unreferenced assets." end) end