[WIP] getting retention methods in place
This commit is contained in:
parent
5c909cfbfd
commit
c696576850
7 changed files with 135 additions and 2 deletions
|
|
@ -46,7 +46,13 @@ config :pinchflat, Oban,
|
||||||
engine: Oban.Engines.Lite,
|
engine: Oban.Engines.Lite,
|
||||||
repo: Pinchflat.Repo,
|
repo: Pinchflat.Repo,
|
||||||
# Keep old jobs for 30 days for display in the UI
|
# Keep old jobs for 30 days for display in the UI
|
||||||
plugins: [{Oban.Plugins.Pruner, max_age: 30 * 24 * 60 * 60}],
|
plugins: [
|
||||||
|
{Oban.Plugins.Pruner, max_age: 30 * 24 * 60 * 60},
|
||||||
|
{Oban.Plugins.Cron,
|
||||||
|
crontab: [
|
||||||
|
{"@midnight", Pinchflat.Downloading.MediaRetentionWorker}
|
||||||
|
]}
|
||||||
|
],
|
||||||
# TODO: consider making this an env var or something?
|
# TODO: consider making this an env var or something?
|
||||||
queues: [
|
queues: [
|
||||||
default: 10,
|
default: 10,
|
||||||
|
|
|
||||||
21
lib/pinchflat/downloading/media_retention_worker.ex
Normal file
21
lib/pinchflat/downloading/media_retention_worker.ex
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
defmodule Pinchflat.Downloading.MediaRetentionWorker do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
use Oban.Worker,
|
||||||
|
queue: :local_metadata,
|
||||||
|
unique: [period: :infinity, states: [:available, :scheduled, :retryable, :executing]],
|
||||||
|
tags: ["media_item", "local_metadata"]
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
# TODO: docs
|
||||||
|
# TODO: test
|
||||||
|
# TODO: set to a 1 min interval to make sure that cron works
|
||||||
|
# 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
|
||||||
|
@impl Oban.Worker
|
||||||
|
def perform(%Oban.Job{}) do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -22,6 +22,22 @@ 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)
|
||||||
|
@doc """
|
||||||
|
Returns a list of media_items that are cullable based on the retention period
|
||||||
|
of the source they belong to.
|
||||||
|
|
||||||
|
Returns [%MediaItem{}, ...]
|
||||||
|
"""
|
||||||
|
def list_cullable_media_items do
|
||||||
|
MediaQuery.new()
|
||||||
|
|> MediaQuery.join_sources()
|
||||||
|
|> MediaQuery.with_media_filepath()
|
||||||
|
|> MediaQuery.with_passed_retention_period()
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns a list of pending media_items for a given source, where
|
Returns a list of pending media_items for a given source, where
|
||||||
pending means the `media_filepath` is `nil` AND the media_item
|
pending means the `media_filepath` is `nil` AND the media_item
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ defmodule Pinchflat.Media.MediaItem do
|
||||||
source_id
|
source_id
|
||||||
upload_date
|
upload_date
|
||||||
short_form_content
|
short_form_content
|
||||||
)a
|
)a
|
||||||
|
|
||||||
schema "media_items" do
|
schema "media_items" do
|
||||||
# This is _not_ used as the primary key or internally in the database
|
# This is _not_ used as the primary key or internally in the database
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ defmodule Pinchflat.Media.MediaQuery do
|
||||||
|
|
||||||
# Prefixes:
|
# Prefixes:
|
||||||
# - for_* - belonging to a certain record
|
# - for_* - belonging to a certain record
|
||||||
|
# - join_* - for joining on a certain record
|
||||||
# - with_* - for filtering based on full, concrete attributes
|
# - with_* - for filtering based on full, concrete attributes
|
||||||
# - matching_* - for filtering based on partial attributes (e.g. LIKE, regex, full-text search)
|
# - matching_* - for filtering based on partial attributes (e.g. LIKE, regex, full-text search)
|
||||||
#
|
#
|
||||||
|
|
@ -31,6 +32,23 @@ defmodule Pinchflat.Media.MediaQuery do
|
||||||
where(query, [mi], mi.source_id == ^source.id)
|
where(query, [mi], mi.source_id == ^source.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def join_sources(query) do
|
||||||
|
from(mi in query, join: s in assoc(mi, :source), as: :sources)
|
||||||
|
end
|
||||||
|
|
||||||
|
def with_passed_retention_period(query) do
|
||||||
|
where(
|
||||||
|
query,
|
||||||
|
[mi, sources],
|
||||||
|
fragment(
|
||||||
|
"IFNULL(?, 0) > 0 AND DATETIME('now', '-' || ? || ' day') > ?",
|
||||||
|
sources.retention_period_days,
|
||||||
|
sources.retention_period_days,
|
||||||
|
mi.media_downloaded_at
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def with_id(query, id) do
|
def with_id(query, id) do
|
||||||
where(query, [mi], mi.id == ^id)
|
where(query, [mi], mi.id == ^id)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@ defmodule Pinchflat.Sources.Source do
|
||||||
|> dynamic_default(:custom_name, fn cs -> get_field(cs, :collection_name) end)
|
|> dynamic_default(:custom_name, fn cs -> get_field(cs, :collection_name) end)
|
||||||
|> dynamic_default(:uuid, fn _ -> Ecto.UUID.generate() end)
|
|> dynamic_default(:uuid, fn _ -> Ecto.UUID.generate() end)
|
||||||
|> validate_required(required_fields)
|
|> validate_required(required_fields)
|
||||||
|
|> validate_number(:retention_period_days, greater_than_or_equal_to: 0)
|
||||||
|> cast_assoc(:metadata, with: &SourceMetadata.changeset/2, required: false)
|
|> cast_assoc(:metadata, with: &SourceMetadata.changeset/2, required: false)
|
||||||
|> unique_constraint([:collection_id, :media_profile_id, :title_filter_regex], error_key: :original_url)
|
|> unique_constraint([:collection_id, :media_profile_id, :title_filter_regex], error_key: :original_url)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,77 @@ defmodule Pinchflat.MediaTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "list_cullable_media_items/0" do
|
||||||
|
test "returns media items where the source has a retention period" do
|
||||||
|
source_one = source_fixture(%{retention_period_days: 2})
|
||||||
|
source_two = source_fixture(%{retention_period_days: 0})
|
||||||
|
source_three = source_fixture(%{retention_period_days: nil})
|
||||||
|
|
||||||
|
_media_item =
|
||||||
|
media_item_fixture(%{
|
||||||
|
source_id: source_two.id,
|
||||||
|
media_filepath: "/video/#{Faker.File.file_name(:video)}",
|
||||||
|
media_downloaded_at: now_minus(3, :days)
|
||||||
|
})
|
||||||
|
|
||||||
|
_media_item =
|
||||||
|
media_item_fixture(%{
|
||||||
|
source_id: source_three.id,
|
||||||
|
media_filepath: "/video/#{Faker.File.file_name(:video)}",
|
||||||
|
media_downloaded_at: now_minus(3, :days)
|
||||||
|
})
|
||||||
|
|
||||||
|
expected_media_item =
|
||||||
|
media_item_fixture(%{
|
||||||
|
source_id: source_one.id,
|
||||||
|
media_filepath: "/video/#{Faker.File.file_name(:video)}",
|
||||||
|
media_downloaded_at: now_minus(3, :days)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert Media.list_cullable_media_items() == [expected_media_item]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns media_items with a media_filepath" do
|
||||||
|
source = source_fixture(%{retention_period_days: 2})
|
||||||
|
|
||||||
|
_media_item =
|
||||||
|
media_item_fixture(%{
|
||||||
|
source_id: source.id,
|
||||||
|
media_filepath: nil,
|
||||||
|
media_downloaded_at: now_minus(3, :days)
|
||||||
|
})
|
||||||
|
|
||||||
|
expected_media_item =
|
||||||
|
media_item_fixture(%{
|
||||||
|
source_id: source.id,
|
||||||
|
media_filepath: "/video/#{Faker.File.file_name(:video)}",
|
||||||
|
media_downloaded_at: now_minus(3, :days)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert Media.list_cullable_media_items() == [expected_media_item]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns items that have passed their retention period" do
|
||||||
|
source = source_fixture(%{retention_period_days: 2})
|
||||||
|
|
||||||
|
_media_item =
|
||||||
|
media_item_fixture(%{
|
||||||
|
source_id: source.id,
|
||||||
|
media_filepath: "/video/#{Faker.File.file_name(:video)}",
|
||||||
|
media_downloaded_at: now_minus(2, :days)
|
||||||
|
})
|
||||||
|
|
||||||
|
expected_media_item =
|
||||||
|
media_item_fixture(%{
|
||||||
|
source_id: source.id,
|
||||||
|
media_filepath: "/video/#{Faker.File.file_name(:video)}",
|
||||||
|
media_downloaded_at: now_minus(3, :days)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert Media.list_cullable_media_items() == [expected_media_item]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "list_pending_media_items_for/1" do
|
describe "list_pending_media_items_for/1" do
|
||||||
test "it returns pending without a filepath for a given source" do
|
test "it returns pending without a filepath for a given source" do
|
||||||
source = source_fixture()
|
source = source_fixture()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue