Hooked up retention worker

This commit is contained in:
Kieran Eglin 2024-04-03 10:06:35 -07:00
parent c696576850
commit 86fa3a11dd
No known key found for this signature in database
GPG key ID: 193984967FCF432D
10 changed files with 107 additions and 16 deletions

View file

@ -50,7 +50,7 @@ config :pinchflat, Oban,
{Oban.Plugins.Pruner, max_age: 30 * 24 * 60 * 60},
{Oban.Plugins.Cron,
crontab: [
{"@midnight", Pinchflat.Downloading.MediaRetentionWorker}
{"@daily", Pinchflat.Downloading.MediaRetentionWorker}
]}
],
# TODO: consider making this an env var or something?

View file

@ -8,14 +8,30 @@ defmodule Pinchflat.Downloading.MediaRetentionWorker do
require Logger
# TODO: docs
# TODO: test
# TODO: set to a 1 min interval to make sure that cron works
alias Pinchflat.Media
# TODO: update wiki after this is merged
# TODO: remove data backfill worker
# TODO: (other PR - not this one) add way to manually trigger an index of a source AND a pending media download
# of a source
@doc """
Deletes media items that are past their retention date and prevents
them from being re-downloaded.
This worker is scheduled to run daily via the Oban Cron plugin.
Returns :ok
"""
@impl Oban.Worker
def perform(%Oban.Job{}) do
cullable_media = Media.list_cullable_media_items()
Logger.info("Culling #{length(cullable_media)} media items past their retention date")
Enum.each(cullable_media, fn media_item ->
Media.delete_media_files(media_item, %{
prevent_download: true,
culled_at: DateTime.utc_now()
})
end)
end
end

View file

@ -22,7 +22,6 @@ defmodule Pinchflat.Media do
Repo.all(MediaItem)
end
# TODO: docs
# TODO: add attr for excluding media items from this list (ie: keep forever)
@doc """
Returns a list of media_items that are cullable based on the retention period
@ -189,20 +188,19 @@ defmodule Pinchflat.Media do
Deletes the tasks and media files associated with a media_item but leaves the
media_item in the database. Does not delete anything to do with associated metadata.
## Options:
- `:prevent_download` - If `true`, the media_item will be marked to prevent being redownloaded
Optionally accepts a second argument `addl_attrs` which will be merged into the
media_item before it is updated. Useful for setting things like `prevent_download`
and `culled_at`, if wanted
Returns {:ok, %MediaItem{}} | {:error, %Ecto.Changeset{}}
"""
def delete_media_files(%MediaItem{} = media_item, opts \\ []) do
prevent_download = Keyword.get(opts, :prevent_download, false)
def delete_media_files(%MediaItem{} = media_item, addl_attrs \\ %{}) do
filepath_attrs = MediaItem.filepath_attribute_defaults()
opt_attrs = %{prevent_download: prevent_download}
Tasks.delete_tasks_for(media_item)
{:ok, _} = do_delete_media_files(media_item)
update_media_item(media_item, Map.merge(filepath_attrs, opt_attrs))
update_media_item(media_item, Map.merge(filepath_attrs, addl_attrs))
end
@doc """

View file

@ -32,7 +32,8 @@ defmodule Pinchflat.Media.MediaItem do
:metadata_filepath,
:nfo_filepath,
# These are user or system controlled fields
:prevent_download
:prevent_download,
:culled_at
]
# Pretty much all the fields captured at index are required.
@required_fields ~w(
@ -73,6 +74,7 @@ defmodule Pinchflat.Media.MediaItem do
field :subtitle_filepaths, {:array, {:array, :string}}, default: []
field :prevent_download, :boolean, default: false
field :culled_at, :utc_datetime
field :matching_search_term, :string, virtual: true

View file

@ -40,7 +40,7 @@ defmodule PinchflatWeb.MediaItems.MediaItemController do
def delete(conn, %{"id" => id} = params) do
prevent_download = Map.get(params, "prevent_download", false)
media_item = Media.get_media_item!(id)
{:ok, _} = Media.delete_media_files(media_item, prevent_download: prevent_download)
{:ok, _} = Media.delete_media_files(media_item, %{prevent_download: prevent_download})
conn
|> put_flash(:info, "Files deleted successfully.")

View file

@ -22,6 +22,13 @@
<:col :let={source} label="Should Download?">
<.icon name={if source.download_media, do: "hero-check", else: "hero-x-mark"} />
</:col>
<:col :let={source} label="Retention">
<%= if source.retention_period_days && source.retention_period_days > 0 do %>
<%= source.retention_period_days %> day(s)
<% else %>
<span class="text-lg">∞</span>
<% end %>
</:col>
<:col :let={source} label="Media Profile">
<.subtle_link href={~p"/media_profiles/#{source.media_profile_id}"}>
<%= source.media_profile.name %>

View file

@ -93,7 +93,7 @@
type="number"
label="Retention Period (days)"
min="0"
help="Days between when media is *downloaded* and when it's deleted. Set to 0 or delete to keep media indefinitely"
help="Days between when media is *downloaded* and when it's deleted. Delete to keep media forever"
/>
<section x-show="advancedMode">

View file

@ -0,0 +1,9 @@
defmodule Pinchflat.Repo.Migrations.AddCulledAtToMediaItems do
use Ecto.Migration
def change do
alter table(:media_items) do
add :culled_at, :utc_datetime
end
end
end

View file

@ -0,0 +1,58 @@
defmodule Pinchflat.Downloading.MediaRetentionWorkerTest do
use Pinchflat.DataCase
import Pinchflat.MediaFixtures
import Pinchflat.SourcesFixtures
alias Pinchflat.Downloading.MediaRetentionWorker
describe "perform/1" do
test "deletes media files that are past their retention date" do
{_source, old_media_item, new_media_item} = prepare_records()
perform_job(MediaRetentionWorker, %{})
assert File.exists?(new_media_item.media_filepath)
refute File.exists?(old_media_item.media_filepath)
assert Repo.reload!(new_media_item).media_filepath
refute Repo.reload!(old_media_item).media_filepath
end
test "sets deleted media to not re-download" do
{_source, old_media_item, new_media_item} = prepare_records()
perform_job(MediaRetentionWorker, %{})
refute Repo.reload!(new_media_item).prevent_download
assert Repo.reload!(old_media_item).prevent_download
end
test "sets culled_at timestamp on deleted media" do
{_source, old_media_item, new_media_item} = prepare_records()
perform_job(MediaRetentionWorker, %{})
refute Repo.reload!(new_media_item).culled_at
assert Repo.reload!(old_media_item).culled_at
assert DateTime.diff(now(), Repo.reload!(old_media_item).culled_at) < 1
end
end
defp prepare_records do
source = source_fixture(%{retention_period_days: 2})
old_media_item =
media_item_with_attachments(%{
source_id: source.id,
media_downloaded_at: now_minus(3, :days)
})
new_media_item =
media_item_with_attachments(%{
source_id: source.id,
media_downloaded_at: now_minus(1, :day)
})
{source, old_media_item, new_media_item}
end
end

View file

@ -699,6 +699,7 @@ defmodule Pinchflat.MediaTest do
test "deletes the media_item's files" do
media_item = media_item_with_attachments()
assert File.exists?(media_item.media_filepath)
assert {:ok, _} = Media.delete_media_files(media_item)
refute File.exists?(media_item.media_filepath)
end
@ -728,10 +729,10 @@ defmodule Pinchflat.MediaTest do
Media.delete_media_item(updated_media_item, delete_files: true)
end
test "can prevent the media item from being downloaded" do
test "can take additional attributes update media item" do
media_item = media_item_with_attachments()
assert {:ok, updated_media_item} = Media.delete_media_files(media_item, prevent_download: true)
assert {:ok, updated_media_item} = Media.delete_media_files(media_item, %{prevent_download: true})
assert updated_media_item.prevent_download
end
end