diff --git a/assets/js/tabs.js b/assets/js/tabs.js
index 964223d0..0d1cfa82 100644
--- a/assets/js/tabs.js
+++ b/assets/js/tabs.js
@@ -1,20 +1,20 @@
-window.setTabIndex = (index) => {
- window.location.hash = `tab-${index}`
+window.setTabByName = (tabName) => {
+ window.location.hash = `tab-${tabName}`
- return index
+ return tabName
}
// The conditionals and currIndex stuff ensures that
// the tab index is always set to 0 if the hash is empty
// AND other hash values are ignored
-window.getTabIndex = (currIndex) => {
+window.getTabFromHash = (currentTabName, defaultTabName) => {
if (window.location.hash === '' || window.location.hash === '#') {
- return 0
+ return defaultTabName
}
if (window.location.hash.startsWith('#tab-')) {
- return parseInt(window.location.hash.replace('#tab-', ''))
+ return window.location.hash.replace('#tab-', '')
}
- return currIndex
+ return currentTabName
}
diff --git a/lib/pinchflat/downloading/downloading_helpers.ex b/lib/pinchflat/downloading/downloading_helpers.ex
index d6f8966d..cb679a82 100644
--- a/lib/pinchflat/downloading/downloading_helpers.ex
+++ b/lib/pinchflat/downloading/downloading_helpers.ex
@@ -85,10 +85,16 @@ defmodule Pinchflat.Downloading.DownloadingHelpers do
"""
def kickoff_redownload_for_existing_media(%Source{} = source) do
MediaQuery.new()
- |> MediaQuery.for_source(source)
- |> MediaQuery.with_media_downloaded_at()
- |> MediaQuery.where_download_not_prevented()
- |> MediaQuery.where_not_culled()
+ |> MediaQuery.require_assoc(:media_profile)
+ |> where(
+ ^dynamic(
+ [m, s, mp],
+ ^MediaQuery.for_source(source) and
+ ^MediaQuery.downloaded() and
+ not (^MediaQuery.download_prevented()) and
+ not (^MediaQuery.culled())
+ )
+ )
|> Repo.all()
|> Enum.map(&MediaDownloadWorker.kickoff_with_task/1)
end
diff --git a/lib/pinchflat/fast_indexing/fast_indexing_helpers.ex b/lib/pinchflat/fast_indexing/fast_indexing_helpers.ex
index 366357fb..684ef49c 100644
--- a/lib/pinchflat/fast_indexing/fast_indexing_helpers.ex
+++ b/lib/pinchflat/fast_indexing/fast_indexing_helpers.ex
@@ -49,8 +49,7 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpers do
defp list_media_items_by_media_id_for(source, media_ids) do
MediaQuery.new()
- |> MediaQuery.for_source(source)
- |> MediaQuery.with_media_ids(media_ids)
+ |> where(^dynamic([mi], ^MediaQuery.for_source(source) and mi.media_id in ^media_ids))
|> Repo.all()
end
diff --git a/lib/pinchflat/lifecycle/notifications/source_notifications.ex b/lib/pinchflat/lifecycle/notifications/source_notifications.ex
index 29a13967..e6e4c048 100644
--- a/lib/pinchflat/lifecycle/notifications/source_notifications.ex
+++ b/lib/pinchflat/lifecycle/notifications/source_notifications.ex
@@ -63,15 +63,14 @@ defmodule Pinchflat.Lifecycle.Notifications.SourceNotifications do
defp pending_media_item_count(source) do
MediaQuery.new()
- |> MediaQuery.for_source(source)
- |> MediaQuery.where_pending_download()
+ |> MediaQuery.require_assoc(:media_profile)
+ |> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.pending()))
|> Repo.aggregate(:count)
end
defp downloaded_media_item_count(source) do
MediaQuery.new()
- |> MediaQuery.for_source(source)
- |> MediaQuery.with_media_filepath()
+ |> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.downloaded()))
|> Repo.aggregate(:count)
end
diff --git a/lib/pinchflat/media/media.ex b/lib/pinchflat/media/media.ex
index c5e9a381..b8a35fa9 100644
--- a/lib/pinchflat/media/media.ex
+++ b/lib/pinchflat/media/media.ex
@@ -32,9 +32,8 @@ defmodule Pinchflat.Media do
"""
def list_cullable_media_items do
MediaQuery.new()
- |> MediaQuery.with_media_filepath()
- |> MediaQuery.where_past_retention_period()
- |> MediaQuery.where_culling_not_prevented()
+ |> MediaQuery.require_assoc(:source)
+ |> where(^MediaQuery.cullable())
|> Repo.all()
end
@@ -54,18 +53,14 @@ defmodule Pinchflat.Media do
"""
def list_redownloadable_media_items do
MediaQuery.new()
- |> MediaQuery.with_media_downloaded_at()
- |> MediaQuery.where_download_not_prevented()
- |> MediaQuery.where_not_culled()
- |> MediaQuery.where_media_not_redownloaded()
- |> MediaQuery.where_past_redownload_delay()
+ |> MediaQuery.require_assoc(:media_profile)
+ |> where(^MediaQuery.redownloadable())
|> Repo.all()
end
@doc """
Returns a list of pending media_items for a given source, where
- pending means the `media_filepath` is `nil` AND the media_item
- matches satisfies `MediaQuery.where_pending_download`. You
+ pending means the media_item satisfies `MediaQuery.pending`. You
should really check out that function if you need to know more
because it has a lot going on.
@@ -73,15 +68,14 @@ defmodule Pinchflat.Media do
"""
def list_pending_media_items_for(%Source{} = source) do
MediaQuery.new()
- |> MediaQuery.for_source(source)
- |> MediaQuery.where_pending_download()
+ |> MediaQuery.require_assoc(:media_profile)
+ |> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.pending()))
|> Repo.all()
end
@doc """
For a given media_item, tells you if it is pending download. This is defined as
- the media_item having a `media_filepath` of `nil` and matching the format selection
- rules of the parent media_profile.
+ the media_item satisfying `MediaQuery.pending` which you should really check out.
Intentionally does not take the `download_media` setting of the source into account.
@@ -91,8 +85,8 @@ defmodule Pinchflat.Media do
media_item = Repo.preload(media_item, source: :media_profile)
MediaQuery.new()
- |> MediaQuery.with_id(media_item.id)
- |> MediaQuery.where_pending_download()
+ |> MediaQuery.require_assoc(:media_profile)
+ |> where(^dynamic([m, s, mp], m.id == ^media_item.id and ^MediaQuery.pending()))
|> Repo.exists?()
end
diff --git a/lib/pinchflat/media/media_item.ex b/lib/pinchflat/media/media_item.ex
index 7018e39b..eab0ef5a 100644
--- a/lib/pinchflat/media/media_item.ex
+++ b/lib/pinchflat/media/media_item.ex
@@ -142,8 +142,7 @@ defmodule Pinchflat.Media.MediaItem do
current_max =
MediaQuery.new()
- |> MediaQuery.for_source(source_id)
- |> MediaQuery.where_uploaded_on_date(changes.upload_date)
+ |> where(^dynamic([mi], mi.upload_date == ^changes.upload_date and ^MediaQuery.for_source(source)))
|> Repo.aggregate(aggregator, :upload_date_index)
case current_max do
diff --git a/lib/pinchflat/media/media_query.ex b/lib/pinchflat/media/media_query.ex
index 0ae4777b..c3a1a652 100644
--- a/lib/pinchflat/media/media_query.ex
+++ b/lib/pinchflat/media/media_query.ex
@@ -23,51 +23,67 @@ defmodule Pinchflat.Media.MediaQuery do
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)
+ def for_source(source_id) when is_integer(source_id), do: dynamic([mi], mi.source_id == ^source_id)
+ def for_source(source), do: dynamic([mi], mi.source_id == ^source.id)
+
+ def downloaded, do: dynamic([mi], not is_nil(mi.media_filepath))
+ def download_prevented, do: dynamic([mi], mi.prevent_download == true)
+ def culling_prevented, do: dynamic([mi], mi.prevent_culling == true)
+ def culled, do: dynamic([mi], not is_nil(mi.culled_at))
+ def redownloaded, do: dynamic([mi], not is_nil(mi.media_redownloaded_at))
+
+ def upload_date_after_source_cutoff do
+ dynamic([mi, source], is_nil(source.download_cutoff_date) or mi.upload_date >= source.download_cutoff_date)
end
- def for_source(query, source) do
- where(query, [mi], mi.source_id == ^source.id)
+ def format_matching_profile_preference do
+ dynamic(
+ [mi, source, media_profile],
+ 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 join_sources(query) do
- from(mi in query, join: s in assoc(mi, :source), as: :sources)
+ def matches_source_title_regex do
+ dynamic(
+ [mi, source],
+ is_nil(source.title_filter_regex) or fragment("regexp_like(?, ?)", mi.title, source.title_filter_regex)
+ )
end
- def where_past_retention_period(query) do
- query
- |> require_assoc(:source)
- |> where(
+ def past_retention_period do
+ dynamic(
[mi, source],
fragment("""
- IFNULL(retention_period_days, 0) > 0 AND
- DATETIME('now', '-' || retention_period_days || ' day') > media_downloaded_at
+ 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],
+ def past_redownload_delay do
+ dynamic(
+ [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("""
@@ -78,61 +94,57 @@ defmodule Pinchflat.Media.MediaQuery do
)
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))
+ def cullable do
+ dynamic(
+ [mi, source],
+ ^downloaded() and
+ ^past_retention_period() and
+ not (^culling_prevented())
+ )
end
- def with_no_media_filepath(query) do
- where(query, [mi], is_nil(mi.media_filepath))
+ def pending do
+ dynamic(
+ [mi],
+ not (^downloaded()) and
+ not (^download_prevented()) and
+ ^upload_date_after_source_cutoff() and
+ ^format_matching_profile_preference() and
+ ^matches_source_title_regex()
+ )
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)
+ def redownloadable do
+ dynamic(
+ [mi, source],
+ ^downloaded() and
+ not (^download_prevented()) and
+ not (^culled()) and
+ not (^redownloaded()) and
+ ^past_redownload_delay()
+ )
end
- def where_uploaded_on_date(query, date) do
- where(query, [mi], mi.upload_date == ^date)
+ def require_assoc(query, identifier) do
+ if has_named_binding?(query, identifier) do
+ query
+ else
+ do_require_assoc(query, identifier)
+ end
end
- def where_download_not_prevented(query) do
- where(query, [mi], mi.prevent_download == false)
+ defp do_require_assoc(query, :source) do
+ from(mi in query, join: s in assoc(mi, :source), as: :source)
end
- def matching_source_title_regex(query) do
+ defp do_require_assoc(query, :media_profile) do
query
|> require_assoc(:source)
- |> where(
- [mi, source],
- is_nil(source.title_filter_regex) or fragment("regexp_like(?, ?)", mi.title, source.title_filter_regex)
- )
+ |> join(:inner, [mi, source], mp in assoc(source, :media_profile), as: :media_profile)
end
+ # This needs to be a non-dynamic query because it alone should control things like
+ # ordering and `snippets` for full-text search
def matching_search_term(query, nil), do: query
def matching_search_term(query, term) do
@@ -150,62 +162,4 @@ defmodule Pinchflat.Media.MediaQuery do
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
diff --git a/lib/pinchflat/podcasts/podcast_helpers.ex b/lib/pinchflat/podcasts/podcast_helpers.ex
index 013ee840..a40e63bf 100644
--- a/lib/pinchflat/podcasts/podcast_helpers.ex
+++ b/lib/pinchflat/podcasts/podcast_helpers.ex
@@ -27,8 +27,7 @@ defmodule Pinchflat.Podcasts.PodcastHelpers do
limit = Keyword.get(opts, :limit, 1_000)
MediaQuery.new()
- |> MediaQuery.for_source(source)
- |> MediaQuery.with_media_filepath()
+ |> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.downloaded()))
|> order_by(desc: :upload_date)
|> Repo.maybe_limit(limit)
|> Repo.all()
diff --git a/lib/pinchflat/sources/sources.ex b/lib/pinchflat/sources/sources.ex
index 9fd66377..6cd45c1d 100644
--- a/lib/pinchflat/sources/sources.ex
+++ b/lib/pinchflat/sources/sources.ex
@@ -127,7 +127,7 @@ defmodule Pinchflat.Sources do
Tasks.delete_tasks_for(source)
MediaQuery.new()
- |> MediaQuery.for_source(source)
+ |> where(^MediaQuery.for_source(source))
|> Repo.all()
|> Enum.each(fn media_item ->
Media.delete_media_item(media_item, delete_files: delete_files)
diff --git a/lib/pinchflat/sources/sources_query.ex b/lib/pinchflat/sources/sources_query.ex
index 81bd4498..850eef63 100644
--- a/lib/pinchflat/sources/sources_query.ex
+++ b/lib/pinchflat/sources/sources_query.ex
@@ -12,20 +12,26 @@ defmodule Pinchflat.Sources.SourcesQuery do
alias Pinchflat.Sources.Source
- # Prefixes:
- # - for_* - belonging to a certain record
- # - join_* - for joining on a certain record
- # - with_* - 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
+ # This allows the module to be aliased and query methods to be used
+ # all in one go
+ # usage: use Pinchflat.Sources.SourcesQuery
+ defmacro __using__(_opts) do
+ quote do
+ import Ecto.Query, warn: false
+
+ alias unquote(__MODULE__)
+ end
+ end
def new do
Source
end
- def for_media_profile(query, media_profile) do
- where(query, [s], s.media_profile_id == ^media_profile.id)
+ def for_media_profile(media_profile_id) when is_integer(media_profile_id) do
+ dynamic([s], s.media_profile_id == ^media_profile_id)
+ end
+
+ def for_media_profile(media_profile) do
+ dynamic([s], s.media_profile_id == ^media_profile.id)
end
end
diff --git a/lib/pinchflat_web/components/custom_components/tab_components.ex b/lib/pinchflat_web/components/custom_components/tab_components.ex
index fda08bd8..56cfe2a1 100644
--- a/lib/pinchflat_web/components/custom_components/tab_components.ex
+++ b/lib/pinchflat_web/components/custom_components/tab_components.ex
@@ -6,25 +6,32 @@ defmodule PinchflatWeb.CustomComponents.TabComponents do
Takes a list of tabs and renders them in a tabbed layout.
"""
slot :tab, required: true do
+ attr :id, :string, required: true
attr :title, :string, required: true
end
slot :tab_append, required: false
def tabbed_layout(assigns) do
+ assigns = Map.put(assigns, :first_tab_id, hd(assigns.tab).id)
+
~H"""
-
+
<%= render_slot(tab) %>
diff --git a/lib/pinchflat_web/components/custom_components/table_components.ex b/lib/pinchflat_web/components/custom_components/table_components.ex
index fa283d38..166a3136 100644
--- a/lib/pinchflat_web/components/custom_components/table_components.ex
+++ b/lib/pinchflat_web/components/custom_components/table_components.ex
@@ -36,7 +36,7 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
-
+
|
- <:tab title="Media">
+ <:tab title="Media" id="media">
<%= if media_file_exists?(@media_item) do %>
- <:tab title="Tasks">
+ <:tab title="Tasks" id="tasks">
<%= if match?([_|_], @media_item.tasks) do %>
<.table rows={@media_item.tasks} table_class="text-black dark:text-white">
<:col :let={task} label="Worker">
diff --git a/lib/pinchflat_web/controllers/media_profiles/media_profile_controller.ex b/lib/pinchflat_web/controllers/media_profiles/media_profile_controller.ex
index cc638ec6..22a49160 100644
--- a/lib/pinchflat_web/controllers/media_profiles/media_profile_controller.ex
+++ b/lib/pinchflat_web/controllers/media_profiles/media_profile_controller.ex
@@ -1,11 +1,9 @@
defmodule PinchflatWeb.MediaProfiles.MediaProfileController do
use PinchflatWeb, :controller
-
- import Ecto.Query, warn: false
+ use Pinchflat.Sources.SourcesQuery
alias Pinchflat.Repo
alias Pinchflat.Profiles
- alias Pinchflat.Sources.SourcesQuery
alias Pinchflat.Profiles.MediaProfile
def index(conn, _params) do
@@ -43,7 +41,7 @@ defmodule PinchflatWeb.MediaProfiles.MediaProfileController do
sources =
SourcesQuery.new()
- |> SourcesQuery.for_media_profile(media_profile)
+ |> where(^SourcesQuery.for_media_profile(media_profile))
|> order_by(asc: :custom_name)
|> Repo.all()
diff --git a/lib/pinchflat_web/controllers/media_profiles/media_profile_html/index.html.heex b/lib/pinchflat_web/controllers/media_profiles/media_profile_html/index.html.heex
index f5e1ae64..e3a2deea 100644
--- a/lib/pinchflat_web/controllers/media_profiles/media_profile_html/index.html.heex
+++ b/lib/pinchflat_web/controllers/media_profiles/media_profile_html/index.html.heex
@@ -23,9 +23,8 @@
<:col :let={media_profile} label="Preferred Resolution">
<%= media_profile.preferred_resolution %>
- <:col :let={media_profile} label="" class="flex place-content-evenly">
- <.icon_link href={~p"/media_profiles/#{media_profile.id}"} icon="hero-eye" class="mx-1" />
- <.icon_link href={~p"/media_profiles/#{media_profile.id}/edit"} icon="hero-pencil-square" class="mx-1" />
+ <:col :let={media_profile} label="" class="flex justify-end">
+ <.icon_link href={~p"/media_profiles/#{media_profile.id}/edit"} icon="hero-pencil-square" class="mr-4" />
diff --git a/lib/pinchflat_web/controllers/media_profiles/media_profile_html/show.html.heex b/lib/pinchflat_web/controllers/media_profiles/media_profile_html/show.html.heex
index 7a33fe88..ef0629f2 100644
--- a/lib/pinchflat_web/controllers/media_profiles/media_profile_html/show.html.heex
+++ b/lib/pinchflat_web/controllers/media_profiles/media_profile_html/show.html.heex
@@ -23,13 +23,13 @@
<.actions_dropdown media_profile={@media_profile} />
- <:tab title="Media Profile">
+ <:tab title="Media Profile" id="media-profile">
Raw Attributes
<.list_items_from_map map={Map.from_struct(@media_profile)} />
- <:tab title="Sources">
+ <:tab title="Sources" id="sources">
<.table rows={@sources} table_class="text-black dark:text-white">
<:col :let={source} label="Name">
<.subtle_link href={~p"/sources/#{source.id}"}>
@@ -40,9 +40,8 @@
<:col :let={source} label="Should Download?">
<.icon name={if source.download_media, do: "hero-check", else: "hero-x-mark"} />
- <:col :let={source} label="" class="flex place-content-evenly">
- <.icon_link href={~p"/sources/#{source.id}"} icon="hero-eye" class="mx-1" />
- <.icon_link href={~p"/sources/#{source.id}/edit"} icon="hero-pencil-square" class="mx-1" />
+ <:col :let={source} label="" class="flex justify-end">
+ <.icon_link href={~p"/sources/#{source.id}/edit"} icon="hero-pencil-square" class="mr-4" />
diff --git a/lib/pinchflat_web/controllers/pages/page_controller.ex b/lib/pinchflat_web/controllers/pages/page_controller.ex
index a6e36dad..60dd2d85 100644
--- a/lib/pinchflat_web/controllers/pages/page_controller.ex
+++ b/lib/pinchflat_web/controllers/pages/page_controller.ex
@@ -26,7 +26,7 @@ defmodule PinchflatWeb.Pages.PageController do
source_count: Repo.aggregate(Source, :count, :id),
media_item_count:
MediaQuery.new()
- |> MediaQuery.with_media_downloaded_at()
+ |> where(^MediaQuery.downloaded())
|> Repo.aggregate(:count, :id)
)
end
diff --git a/lib/pinchflat_web/controllers/pages/page_html/history_table_live.ex b/lib/pinchflat_web/controllers/pages/page_html/history_table_live.ex
index 018bcef4..6ec85aea 100644
--- a/lib/pinchflat_web/controllers/pages/page_html/history_table_live.ex
+++ b/lib/pinchflat_web/controllers/pages/page_html/history_table_live.ex
@@ -97,7 +97,8 @@ defmodule Pinchflat.Pages.HistoryTableLive do
defp generate_base_query do
MediaQuery.new()
- |> MediaQuery.where_pending_or_downloaded()
+ |> MediaQuery.require_assoc(:media_profile)
+ |> where(^dynamic(^MediaQuery.downloaded() or ^MediaQuery.pending()))
|> order_by(desc: :id)
end
diff --git a/lib/pinchflat_web/controllers/podcasts/podcast_controller.ex b/lib/pinchflat_web/controllers/podcasts/podcast_controller.ex
index cf7c7fce..d69e4f6d 100644
--- a/lib/pinchflat_web/controllers/podcasts/podcast_controller.ex
+++ b/lib/pinchflat_web/controllers/podcasts/podcast_controller.ex
@@ -26,8 +26,7 @@ defmodule PinchflatWeb.Podcasts.PodcastController do
# if the source doesn't have any usable images
media_items =
MediaQuery.new()
- |> MediaQuery.for_source(source)
- |> MediaQuery.with_media_filepath()
+ |> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.downloaded()))
|> Repo.maybe_limit(1)
|> Repo.all()
diff --git a/lib/pinchflat_web/controllers/sources/source_controller.ex b/lib/pinchflat_web/controllers/sources/source_controller.ex
index 3b9c5b2a..2df192f2 100644
--- a/lib/pinchflat_web/controllers/sources/source_controller.ex
+++ b/lib/pinchflat_web/controllers/sources/source_controller.ex
@@ -1,25 +1,44 @@
defmodule PinchflatWeb.Sources.SourceController do
use PinchflatWeb, :controller
-
- import Ecto.Query, warn: false
+ use Pinchflat.Media.MediaQuery
alias Pinchflat.Repo
alias Pinchflat.Tasks
alias Pinchflat.Sources
alias Pinchflat.Sources.Source
+ alias Pinchflat.Media.MediaItem
alias Pinchflat.Profiles.MediaProfile
alias Pinchflat.Downloading.DownloadingHelpers
alias Pinchflat.SlowIndexing.SlowIndexingHelpers
alias Pinchflat.Metadata.SourceMetadataStorageWorker
def index(conn, _params) do
- sources =
- Source
- |> order_by(asc: :custom_name)
- |> Repo.all()
- |> Repo.preload(:media_profile)
-
- render(conn, :index, sources: sources)
+ source_query =
+ from s in Source,
+ as: :source,
+ inner_join: mp in assoc(s, :media_profile),
+ preload: [media_profile: mp],
+ order_by: [asc: s.custom_name],
+ select: map(s, ^Source.__schema__(:fields)),
+ select_merge: %{
+ downloaded_count:
+ subquery(
+ from m in MediaItem,
+ where: m.source_id == parent_as(:source).id,
+ where: ^MediaQuery.downloaded(),
+ select: count(m.id)
+ ),
+ pending_count:
+ subquery(
+ from m in MediaItem,
+ join: s in assoc(m, :source),
+ where: m.source_id == parent_as(:source).id,
+ where: ^MediaQuery.pending(),
+ select: count(m.id)
+ )
+ }
+
+ render(conn, :index, sources: Repo.all(source_query))
end
def new(conn, _params) do
diff --git a/lib/pinchflat_web/controllers/sources/source_html/index.html.heex b/lib/pinchflat_web/controllers/sources/source_html/index.html.heex
index c912cfb8..a064d535 100644
--- a/lib/pinchflat_web/controllers/sources/source_html/index.html.heex
+++ b/lib/pinchflat_web/controllers/sources/source_html/index.html.heex
@@ -15,13 +15,12 @@
<.table rows={@sources} table_class="text-black dark:text-white">
<:col :let={source} label="Name">
<.subtle_link href={~p"/sources/#{source.id}"}>
- <%= source.custom_name || source.collection_name %>
+ <%= StringUtils.truncate(source.custom_name || source.collection_name, 35) %>
<:col :let={source} label="Type"><%= source.collection_type %>
- <:col :let={source} label="Should Download?">
- <.icon name={if source.download_media, do: "hero-check", else: "hero-x-mark"} />
-
+ <:col :let={source} label="Pending"><%= source.pending_count %>
+ <:col :let={source} label="Downloaded"><%= source.downloaded_count %>
<:col :let={source} label="Retention">
<%= if source.retention_period_days && source.retention_period_days > 0 do %>
<%= source.retention_period_days %> day(s)
@@ -35,7 +34,6 @@
<:col :let={source} label="" class="flex place-content-evenly">
- <.icon_link href={~p"/sources/#{source.id}"} icon="hero-eye" class="mx-1" />
<.icon_link href={~p"/sources/#{source.id}/edit"} icon="hero-pencil-square" class="mx-1" />
diff --git a/lib/pinchflat_web/controllers/sources/source_html/media_item_table_live.ex b/lib/pinchflat_web/controllers/sources/source_html/media_item_table_live.ex
index 0b986874..7ffac3c3 100644
--- a/lib/pinchflat_web/controllers/sources/source_html/media_item_table_live.ex
+++ b/lib/pinchflat_web/controllers/sources/source_html/media_item_table_live.ex
@@ -30,9 +30,8 @@ defmodule Pinchflat.Sources.MediaItemTableLive do
<%= StringUtils.truncate(media_item.title, 50) %>
- <:col :let={media_item} label="" class="flex place-content-evenly">
- <.icon_link href={~p"/sources/#{@source.id}/media/#{media_item.id}"} icon="hero-eye" class="mx-1" />
- <.icon_link href={~p"/sources/#{@source.id}/media/#{media_item.id}/edit"} icon="hero-pencil-square" class="mx-1" />
+ <:col :let={media_item} label="" class="flex justify-end">
+ <.icon_link href={~p"/sources/#{@source.id}/media/#{media_item.id}/edit"} icon="hero-pencil-square" class="mr-4" />
@@ -86,15 +85,14 @@ defmodule Pinchflat.Sources.MediaItemTableLive do
defp generate_base_query(source, "pending") do
MediaQuery.new()
- |> MediaQuery.for_source(source)
- |> MediaQuery.where_pending_download()
+ |> MediaQuery.require_assoc(:media_profile)
+ |> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.pending()))
|> order_by(desc: :id)
end
defp generate_base_query(source, "downloaded") do
MediaQuery.new()
- |> MediaQuery.for_source(source)
- |> MediaQuery.with_media_filepath()
+ |> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.downloaded()))
|> order_by(desc: :id)
end
end
diff --git a/lib/pinchflat_web/controllers/sources/source_html/show.html.heex b/lib/pinchflat_web/controllers/sources/source_html/show.html.heex
index 92104c8b..00ad375b 100644
--- a/lib/pinchflat_web/controllers/sources/source_html/show.html.heex
+++ b/lib/pinchflat_web/controllers/sources/source_html/show.html.heex
@@ -23,7 +23,7 @@
<.actions_dropdown source={@source} conn={@conn} />
- <:tab title="Source">
+ <:tab title="Source" id="source">
Raw Attributes
@@ -36,21 +36,21 @@
<.list_items_from_map map={Map.from_struct(@source)} />
- <:tab title="Pending Media">
+ <:tab title="Pending Media" id="pending">
<%= live_render(
@conn,
Pinchflat.Sources.MediaItemTableLive,
session: %{"source_id" => @source.id, "media_state" => "pending"}
) %>
- <:tab title="Downloaded Media">
+ <:tab title="Downloaded Media" id="downloaded">
<%= live_render(
@conn,
Pinchflat.Sources.MediaItemTableLive,
session: %{"source_id" => @source.id, "media_state" => "downloaded"}
) %>
- <:tab title="Pending Tasks">
+ <:tab title="Pending Tasks" id="tasks">
<%= if match?([_|_], @pending_tasks) do %>
<.table rows={@pending_tasks} table_class="text-black dark:text-white">
<:col :let={task} label="Worker">
diff --git a/mix.exs b/mix.exs
index a1b60282..e74e6b16 100644
--- a/mix.exs
+++ b/mix.exs
@@ -48,6 +48,7 @@ defmodule Pinchflat.MixProject do
[
{:phoenix, "~> 1.7.10"},
{:phoenix_ecto, "~> 4.4"},
+ {:ecto, "~> 3.11.2"},
{:ecto_sql, "~> 3.10"},
{:ecto_sqlite3, ">= 0.0.0"},
{:ecto_sqlite3_extras, "~> 1.2.0"},
diff --git a/mix.lock b/mix.lock
index 7fe77ff7..e986d1bd 100644
--- a/mix.lock
+++ b/mix.lock
@@ -12,7 +12,7 @@
"db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"dns_cluster": {:hex, :dns_cluster, "0.1.2", "3eb5be824c7888dadf9781018e1a5f1d3d1113b333c50bce90fb1b83df1015f2", [:mix], [], "hexpm", "7494272040f847637bbdb01bcdf4b871e82daf09b813e7d3cb3b84f112c6f2f8"},
- "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"},
+ "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"},
"ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"},
"ecto_sqlite3": {:hex, :ecto_sqlite3, "0.15.1", "40f2fbd9e246455f8c42e7e0a77009ef806caa1b3ce6f717b2a0a80e8432fcfd", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.11", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.19", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "28b16e177123c688948357176662bf9ff9084daddf950ef5b6baf3ee93707064"},
"ecto_sqlite3_extras": {:hex, :ecto_sqlite3_extras, "1.2.2", "36e60b561a11441d15f26c791817999269fb578b985162207ebb08b04ca71e40", [:mix], [{:exqlite, ">= 0.13.2", [hex: :exqlite, repo: "hexpm", optional: false]}, {:table_rex, "~> 4.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "2b66ba7246bb4f7e39e2578acd4a0e4e4be54f60ff52d450a01be95eeb78ff1e"},
diff --git a/test/pinchflat/downloading/downloading_helpers_test.exs b/test/pinchflat/downloading/downloading_helpers_test.exs
index 23c493d6..01b0518c 100644
--- a/test/pinchflat/downloading/downloading_helpers_test.exs
+++ b/test/pinchflat/downloading/downloading_helpers_test.exs
@@ -114,7 +114,7 @@ defmodule Pinchflat.Downloading.DownloadingHelpersTest do
describe "kickoff_redownload_for_existing_media/1" do
test "enqueues a download job for each downloaded media item" do
source = source_fixture()
- media_item = media_item_fixture(source_id: source.id, media_downloaded_at: now())
+ media_item = media_item_fixture(source_id: source.id, media_filepath: "some/filepath.mp4")
assert [{:ok, _}] = DownloadingHelpers.kickoff_redownload_for_existing_media(source)
@@ -124,14 +124,14 @@ defmodule Pinchflat.Downloading.DownloadingHelpersTest do
test "doesn't enqueue jobs for media that should be ignored" do
source = source_fixture()
other_source = source_fixture()
- _not_downloaded = media_item_fixture(source_id: source.id, media_downloaded_at: nil)
- _other_source = media_item_fixture(source_id: other_source.id, media_downloaded_at: now())
+ _not_downloaded = media_item_fixture(source_id: source.id, media_filepath: nil)
+ _other_source = media_item_fixture(source_id: other_source.id, media_filepath: "some/filepath.mp4")
_download_prevented =
- media_item_fixture(source_id: source.id, media_downloaded_at: now(), prevent_download: true)
+ media_item_fixture(source_id: source.id, media_filepath: "some/filepath.mp4", prevent_download: true)
_culled =
- media_item_fixture(source_id: source.id, media_downloaded_at: now(), culled_at: now())
+ media_item_fixture(source_id: source.id, media_filepath: "some/filepath.mp4", culled_at: now())
assert [] = DownloadingHelpers.kickoff_redownload_for_existing_media(source)
diff --git a/test/pinchflat/media_test.exs b/test/pinchflat/media_test.exs
index 412b9e31..f98afb16 100644
--- a/test/pinchflat/media_test.exs
+++ b/test/pinchflat/media_test.exs
@@ -405,7 +405,9 @@ defmodule Pinchflat.MediaTest do
describe "list_pending_media_items_for/1" do
test "it returns pending without a filepath for a given source" do
source = source_fixture()
+ other_source = source_fixture()
media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil})
+ _other_media_item = media_item_fixture(%{source_id: other_source.id, media_filepath: nil})
assert Media.list_pending_media_items_for(source) == [media_item]
end
diff --git a/test/support/fixtures/media_fixtures.ex b/test/support/fixtures/media_fixtures.ex
index 54dedcaa..53971a07 100644
--- a/test/support/fixtures/media_fixtures.ex
+++ b/test/support/fixtures/media_fixtures.ex
@@ -17,7 +17,7 @@ defmodule Pinchflat.MediaFixtures do
attrs
|> Enum.into(%{
media_id: media_id,
- title: Faker.Commerce.product_name(),
+ title: Faker.Commerce.product_name() <> " #{media_id}",
original_url: "https://www.youtube.com/watch?v=#{media_id}",
livestream: false,
short_form_content: false,