* Added ability to specify overwrite behaviour when downloading media * Added helper for redownloading media items * renamed media redownload worker to disambiguate it from similarly named methods * Added new redownload option to source actions dropdown * Refactored MediaQuery to use a __using__ macro * docs
211 lines
6 KiB
Elixir
211 lines
6 KiB
Elixir
defmodule Pinchflat.Media.MediaQuery do
|
|
@moduledoc """
|
|
Query helpers for the Media context.
|
|
|
|
These methods are made to be one-ish liners used
|
|
to compose queries. Each method should strive to do
|
|
_one_ thing. These don't need to be tested as
|
|
they are just building blocks for other functionality
|
|
which, itself, will be tested.
|
|
"""
|
|
import Ecto.Query, warn: false
|
|
|
|
alias Pinchflat.Media.MediaItem
|
|
|
|
# This allows the module to be aliased and query methods to be used
|
|
# all in one go
|
|
# usage: use Pinchflat.Media.MediaQuery
|
|
defmacro __using__(_opts) do
|
|
quote do
|
|
import Ecto.Query, warn: false
|
|
|
|
alias unquote(__MODULE__)
|
|
end
|
|
end
|
|
|
|
# Prefixes:
|
|
# - for_* - belonging to a certain record
|
|
# - join_* - for joining on a certain record
|
|
# - with_*, where_* - for filtering based on full, concrete attributes
|
|
# - matching_* - for filtering based on partial attributes (e.g. LIKE, regex, full-text search)
|
|
#
|
|
# Suffixes:
|
|
# - _for - the arg passed is an association record
|
|
|
|
# NOTE: that dyanmic query approach kinda rocked - should refactor in future
|
|
|
|
def new do
|
|
MediaItem
|
|
end
|
|
|
|
def for_source(query, source_id) when is_integer(source_id) do
|
|
where(query, [mi], mi.source_id == ^source_id)
|
|
end
|
|
|
|
def for_source(query, source) do
|
|
where(query, [mi], mi.source_id == ^source.id)
|
|
end
|
|
|
|
def join_sources(query) do
|
|
from(mi in query, join: s in assoc(mi, :source), as: :sources)
|
|
end
|
|
|
|
def where_past_retention_period(query) do
|
|
query
|
|
|> require_assoc(:source)
|
|
|> where(
|
|
[mi, source],
|
|
fragment("""
|
|
IFNULL(retention_period_days, 0) > 0 AND
|
|
DATETIME('now', '-' || retention_period_days || ' day') > media_downloaded_at
|
|
""")
|
|
)
|
|
end
|
|
|
|
def where_past_redownload_delay(query) do
|
|
query
|
|
|> require_assoc(:source)
|
|
|> require_assoc(:media_profile)
|
|
|> where(
|
|
[_mi, _source, _media_profile],
|
|
# Returns media items where the upload_date is at least redownload_delay_days ago AND
|
|
# downloaded_at minus the redownload_delay_days is before the upload date
|
|
fragment("""
|
|
IFNULL(redownload_delay_days, 0) > 0 AND
|
|
DATETIME('now', '-' || redownload_delay_days || ' day') > upload_date AND
|
|
DATETIME(media_downloaded_at, '-' || redownload_delay_days || ' day') < upload_date
|
|
""")
|
|
)
|
|
end
|
|
|
|
def where_culling_not_prevented(query) do
|
|
where(query, [mi], mi.prevent_culling == false)
|
|
end
|
|
|
|
def where_not_culled(query) do
|
|
where(query, [mi], is_nil(mi.culled_at))
|
|
end
|
|
|
|
def where_media_not_redownloaded(query) do
|
|
where(query, [mi], is_nil(mi.media_redownloaded_at))
|
|
end
|
|
|
|
def with_id(query, id) do
|
|
where(query, [mi], mi.id == ^id)
|
|
end
|
|
|
|
def with_media_ids(query, media_ids) do
|
|
where(query, [mi], mi.media_id in ^media_ids)
|
|
end
|
|
|
|
def with_media_downloaded_at(query) do
|
|
where(query, [mi], not is_nil(mi.media_downloaded_at))
|
|
end
|
|
|
|
def with_media_filepath(query) do
|
|
where(query, [mi], not is_nil(mi.media_filepath))
|
|
end
|
|
|
|
def with_no_media_filepath(query) do
|
|
where(query, [mi], is_nil(mi.media_filepath))
|
|
end
|
|
|
|
def with_upload_date_after_source_cutoff(query) do
|
|
query
|
|
|> require_assoc(:source)
|
|
|> where([mi, source], is_nil(source.download_cutoff_date) or mi.upload_date >= source.download_cutoff_date)
|
|
end
|
|
|
|
def where_uploaded_on_date(query, date) do
|
|
where(query, [mi], mi.upload_date == ^date)
|
|
end
|
|
|
|
def where_download_not_prevented(query) do
|
|
where(query, [mi], mi.prevent_download == false)
|
|
end
|
|
|
|
def matching_source_title_regex(query) do
|
|
query
|
|
|> require_assoc(:source)
|
|
|> where(
|
|
[mi, source],
|
|
is_nil(source.title_filter_regex) or fragment("regexp_like(?, ?)", mi.title, source.title_filter_regex)
|
|
)
|
|
end
|
|
|
|
def matching_search_term(query, nil), do: query
|
|
|
|
def matching_search_term(query, term) do
|
|
from(mi in query,
|
|
join: mi_search_index in assoc(mi, :media_items_search_index),
|
|
where: fragment("media_items_search_index MATCH ?", ^term),
|
|
select_merge: %{
|
|
matching_search_term:
|
|
fragment("""
|
|
coalesce(snippet(media_items_search_index, 0, '[PF_HIGHLIGHT]', '[/PF_HIGHLIGHT]', '...', 20), '') ||
|
|
' ' ||
|
|
coalesce(snippet(media_items_search_index, 1, '[PF_HIGHLIGHT]', '[/PF_HIGHLIGHT]', '...', 20), '')
|
|
""")
|
|
},
|
|
order_by: [desc: fragment("rank")]
|
|
)
|
|
end
|
|
|
|
def with_format_matching_profile_preference(query) do
|
|
query
|
|
|> require_assoc(:media_profile)
|
|
|> where(
|
|
fragment("""
|
|
CASE
|
|
WHEN shorts_behaviour = 'only' AND livestream_behaviour = 'only' THEN
|
|
livestream = true OR short_form_content = true
|
|
WHEN shorts_behaviour = 'only' THEN
|
|
short_form_content = true
|
|
WHEN livestream_behaviour = 'only' THEN
|
|
livestream = true
|
|
WHEN shorts_behaviour = 'exclude' AND livestream_behaviour = 'exclude' THEN
|
|
short_form_content = false AND livestream = false
|
|
WHEN shorts_behaviour = 'exclude' THEN
|
|
short_form_content = false
|
|
WHEN livestream_behaviour = 'exclude' THEN
|
|
livestream = false
|
|
ELSE
|
|
true
|
|
END
|
|
""")
|
|
)
|
|
end
|
|
|
|
def where_pending_download(query) do
|
|
query
|
|
|> where_download_not_prevented()
|
|
|> with_no_media_filepath()
|
|
|> with_upload_date_after_source_cutoff()
|
|
|> with_format_matching_profile_preference()
|
|
|> matching_source_title_regex()
|
|
end
|
|
|
|
def where_pending_or_downloaded(query) do
|
|
query
|
|
|> where_pending_download()
|
|
|> or_where([mi], not is_nil(mi.media_downloaded_at))
|
|
end
|
|
|
|
defp require_assoc(query, identifier) do
|
|
if has_named_binding?(query, identifier) do
|
|
query
|
|
else
|
|
do_require_assoc(query, identifier)
|
|
end
|
|
end
|
|
|
|
defp do_require_assoc(query, :source) do
|
|
from(mi in query, join: s in assoc(mi, :source), as: :source)
|
|
end
|
|
|
|
defp do_require_assoc(query, :media_profile) do
|
|
query
|
|
|> require_assoc(:source)
|
|
|> join(:inner, [mi, source], mp in assoc(source, :media_profile), as: :media_profile)
|
|
end
|
|
end
|