Hooked up retention worker
This commit is contained in:
parent
c696576850
commit
86fa3a11dd
10 changed files with 107 additions and 16 deletions
|
|
@ -50,7 +50,7 @@ config :pinchflat, Oban,
|
||||||
{Oban.Plugins.Pruner, max_age: 30 * 24 * 60 * 60},
|
{Oban.Plugins.Pruner, max_age: 30 * 24 * 60 * 60},
|
||||||
{Oban.Plugins.Cron,
|
{Oban.Plugins.Cron,
|
||||||
crontab: [
|
crontab: [
|
||||||
{"@midnight", Pinchflat.Downloading.MediaRetentionWorker}
|
{"@daily", Pinchflat.Downloading.MediaRetentionWorker}
|
||||||
]}
|
]}
|
||||||
],
|
],
|
||||||
# TODO: consider making this an env var or something?
|
# TODO: consider making this an env var or something?
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,30 @@ defmodule Pinchflat.Downloading.MediaRetentionWorker do
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
# TODO: docs
|
alias Pinchflat.Media
|
||||||
# TODO: test
|
|
||||||
# TODO: set to a 1 min interval to make sure that cron works
|
|
||||||
# TODO: update wiki after this is merged
|
# TODO: update wiki after this is merged
|
||||||
# TODO: remove data backfill worker
|
# 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
|
# TODO: (other PR - not this one) add way to manually trigger an index of a source AND a pending media download
|
||||||
# of a source
|
# 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
|
@impl Oban.Worker
|
||||||
def perform(%Oban.Job{}) do
|
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
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ defmodule Pinchflat.Media do
|
||||||
Repo.all(MediaItem)
|
Repo.all(MediaItem)
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: docs
|
|
||||||
# TODO: add attr for excluding media items from this list (ie: keep forever)
|
# TODO: add attr for excluding media items from this list (ie: keep forever)
|
||||||
@doc """
|
@doc """
|
||||||
Returns a list of media_items that are cullable based on the retention period
|
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
|
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.
|
media_item in the database. Does not delete anything to do with associated metadata.
|
||||||
|
|
||||||
## Options:
|
Optionally accepts a second argument `addl_attrs` which will be merged into the
|
||||||
- `:prevent_download` - If `true`, the media_item will be marked to prevent being redownloaded
|
media_item before it is updated. Useful for setting things like `prevent_download`
|
||||||
|
and `culled_at`, if wanted
|
||||||
|
|
||||||
Returns {:ok, %MediaItem{}} | {:error, %Ecto.Changeset{}}
|
Returns {:ok, %MediaItem{}} | {:error, %Ecto.Changeset{}}
|
||||||
"""
|
"""
|
||||||
def delete_media_files(%MediaItem{} = media_item, opts \\ []) do
|
def delete_media_files(%MediaItem{} = media_item, addl_attrs \\ %{}) do
|
||||||
prevent_download = Keyword.get(opts, :prevent_download, false)
|
|
||||||
filepath_attrs = MediaItem.filepath_attribute_defaults()
|
filepath_attrs = MediaItem.filepath_attribute_defaults()
|
||||||
opt_attrs = %{prevent_download: prevent_download}
|
|
||||||
|
|
||||||
Tasks.delete_tasks_for(media_item)
|
Tasks.delete_tasks_for(media_item)
|
||||||
{:ok, _} = do_delete_media_files(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
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,8 @@ defmodule Pinchflat.Media.MediaItem do
|
||||||
:metadata_filepath,
|
:metadata_filepath,
|
||||||
:nfo_filepath,
|
:nfo_filepath,
|
||||||
# These are user or system controlled fields
|
# These are user or system controlled fields
|
||||||
:prevent_download
|
:prevent_download,
|
||||||
|
:culled_at
|
||||||
]
|
]
|
||||||
# Pretty much all the fields captured at index are required.
|
# Pretty much all the fields captured at index are required.
|
||||||
@required_fields ~w(
|
@required_fields ~w(
|
||||||
|
|
@ -73,6 +74,7 @@ defmodule Pinchflat.Media.MediaItem do
|
||||||
field :subtitle_filepaths, {:array, {:array, :string}}, default: []
|
field :subtitle_filepaths, {:array, {:array, :string}}, default: []
|
||||||
|
|
||||||
field :prevent_download, :boolean, default: false
|
field :prevent_download, :boolean, default: false
|
||||||
|
field :culled_at, :utc_datetime
|
||||||
|
|
||||||
field :matching_search_term, :string, virtual: true
|
field :matching_search_term, :string, virtual: true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ defmodule PinchflatWeb.MediaItems.MediaItemController do
|
||||||
def delete(conn, %{"id" => id} = params) do
|
def delete(conn, %{"id" => id} = params) do
|
||||||
prevent_download = Map.get(params, "prevent_download", false)
|
prevent_download = Map.get(params, "prevent_download", false)
|
||||||
media_item = Media.get_media_item!(id)
|
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
|
conn
|
||||||
|> put_flash(:info, "Files deleted successfully.")
|
|> put_flash(:info, "Files deleted successfully.")
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,13 @@
|
||||||
<:col :let={source} label="Should Download?">
|
<:col :let={source} label="Should Download?">
|
||||||
<.icon name={if source.download_media, do: "hero-check", else: "hero-x-mark"} />
|
<.icon name={if source.download_media, do: "hero-check", else: "hero-x-mark"} />
|
||||||
</:col>
|
</: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">
|
<:col :let={source} label="Media Profile">
|
||||||
<.subtle_link href={~p"/media_profiles/#{source.media_profile_id}"}>
|
<.subtle_link href={~p"/media_profiles/#{source.media_profile_id}"}>
|
||||||
<%= source.media_profile.name %>
|
<%= source.media_profile.name %>
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@
|
||||||
type="number"
|
type="number"
|
||||||
label="Retention Period (days)"
|
label="Retention Period (days)"
|
||||||
min="0"
|
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">
|
<section x-show="advancedMode">
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
58
test/pinchflat/downloading/media_retention_worker_test.exs
Normal file
58
test/pinchflat/downloading/media_retention_worker_test.exs
Normal 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
|
||||||
|
|
@ -699,6 +699,7 @@ defmodule Pinchflat.MediaTest do
|
||||||
test "deletes the media_item's files" do
|
test "deletes the media_item's files" do
|
||||||
media_item = media_item_with_attachments()
|
media_item = media_item_with_attachments()
|
||||||
|
|
||||||
|
assert File.exists?(media_item.media_filepath)
|
||||||
assert {:ok, _} = Media.delete_media_files(media_item)
|
assert {:ok, _} = Media.delete_media_files(media_item)
|
||||||
refute File.exists?(media_item.media_filepath)
|
refute File.exists?(media_item.media_filepath)
|
||||||
end
|
end
|
||||||
|
|
@ -728,10 +729,10 @@ defmodule Pinchflat.MediaTest do
|
||||||
Media.delete_media_item(updated_media_item, delete_files: true)
|
Media.delete_media_item(updated_media_item, delete_files: true)
|
||||||
end
|
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()
|
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
|
assert updated_media_item.prevent_download
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue