[Enhancement] Allow manual indexing/downloading (#162)
* Added controller actions and UI for forcing index and download actions * Added forcing of downloads for media items
This commit is contained in:
@@ -6,6 +6,7 @@ defmodule PinchflatWeb.MediaItems.MediaItemController do
|
||||
alias Pinchflat.Repo
|
||||
alias Pinchflat.Media
|
||||
alias Pinchflat.Media.MediaItem
|
||||
alias Pinchflat.Downloading.MediaDownloadWorker
|
||||
|
||||
def show(conn, %{"id" => id}) do
|
||||
media_item =
|
||||
@@ -47,6 +48,15 @@ defmodule PinchflatWeb.MediaItems.MediaItemController do
|
||||
|> redirect(to: ~p"/sources/#{media_item.source_id}")
|
||||
end
|
||||
|
||||
def force_download(conn, %{"media_item_id" => id}) do
|
||||
media_item = Media.get_media_item!(id)
|
||||
{:ok, _} = MediaDownloadWorker.kickoff_with_task(media_item, %{force: true})
|
||||
|
||||
conn
|
||||
|> put_flash(:info, "Download task enqueued.")
|
||||
|> redirect(to: ~p"/sources/#{media_item.source_id}/media/#{media_item}")
|
||||
end
|
||||
|
||||
# See here for details on streaming files and range requests:
|
||||
# https://www.zeng.dev/post/2023-http-range-and-play-mp4-in-browser/
|
||||
#
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<.button_dropdown text="Actions" class="justify-center w-full sm:w-50">
|
||||
<:option>
|
||||
<.link
|
||||
href={~p"/sources/#{@media_item.source_id}/media/#{@media_item}/force_download"}
|
||||
method="post"
|
||||
data-confirm="Are you sure you force a download of this media?"
|
||||
>
|
||||
Force Download
|
||||
</.link>
|
||||
</:option>
|
||||
<:option>
|
||||
<div class="h-px w-full bg-bodydark2"></div>
|
||||
</:option>
|
||||
<:option>
|
||||
<.link
|
||||
href={~p"/sources/#{@media_item.source_id}/media/#{@media_item}"}
|
||||
method="delete"
|
||||
data-confirm="Are you sure you want to delete all files for this media item? This cannot be undone."
|
||||
>
|
||||
Delete Files
|
||||
</.link>
|
||||
</:option>
|
||||
<:option>
|
||||
<.link
|
||||
href={~p"/sources/#{@media_item.source_id}/media/#{@media_item}?prevent_download=true"}
|
||||
method="delete"
|
||||
data-confirm="Are you sure you want to delete all files for this media item and prevent it from re-downloading in the future? This cannot be undone."
|
||||
>
|
||||
Delete and Ignore
|
||||
</.link>
|
||||
</:option>
|
||||
</.button_dropdown>
|
||||
@@ -17,7 +17,7 @@
|
||||
field={f[:prevent_download]}
|
||||
type="toggle"
|
||||
label="Prevent Download"
|
||||
help="Checking excludes this media item from being downloaded"
|
||||
help="Checking excludes this media item from automatic download. Download can still be manually forced"
|
||||
/>
|
||||
|
||||
<.input
|
||||
|
||||
@@ -20,26 +20,7 @@
|
||||
<div class="max-w-full overflow-x-auto">
|
||||
<.tabbed_layout>
|
||||
<:tab_append>
|
||||
<.button_dropdown text="Actions" class="justify-center w-full sm:w-50">
|
||||
<:option>
|
||||
<.link
|
||||
href={~p"/sources/#{@media_item.source_id}/media/#{@media_item}"}
|
||||
method="delete"
|
||||
data-confirm="Are you sure you want to delete all files for this media item? This cannot be undone."
|
||||
>
|
||||
Delete Files
|
||||
</.link>
|
||||
</:option>
|
||||
<:option>
|
||||
<.link
|
||||
href={~p"/sources/#{@media_item.source_id}/media/#{@media_item}?prevent_download=true"}
|
||||
method="delete"
|
||||
data-confirm="Are you sure you want to delete all files for this media item and prevent it from re-downloading in the future? This cannot be undone."
|
||||
>
|
||||
Delete and Ignore
|
||||
</.link>
|
||||
</:option>
|
||||
</.button_dropdown>
|
||||
<.actions_dropdown media_item={@media_item} />
|
||||
</:tab_append>
|
||||
|
||||
<:tab title="Attributes">
|
||||
@@ -53,9 +34,9 @@
|
||||
<h3 class="font-bold text-xl">Attributes</h3>
|
||||
<section>
|
||||
<strong>Source:</strong>
|
||||
<.inline_link href={~p"/sources/#{@media_item.source_id}"}>
|
||||
<.subtle_link href={~p"/sources/#{@media_item.source_id}"}>
|
||||
<%= @media_item.source.custom_name %>
|
||||
</.inline_link>
|
||||
</.subtle_link>
|
||||
</section>
|
||||
|
||||
<.list_items_from_map map={Map.from_struct(@media_item)} />
|
||||
|
||||
@@ -10,6 +10,8 @@ defmodule PinchflatWeb.Sources.SourceController do
|
||||
alias Pinchflat.Profiles
|
||||
alias Pinchflat.Sources.Source
|
||||
alias Pinchflat.Media.MediaQuery
|
||||
alias Pinchflat.Downloading.DownloadingHelpers
|
||||
alias Pinchflat.SlowIndexing.SlowIndexingHelpers
|
||||
|
||||
def index(conn, _params) do
|
||||
sources = Repo.preload(Sources.list_sources(), :media_profile)
|
||||
@@ -49,7 +51,11 @@ defmodule PinchflatWeb.Sources.SourceController do
|
||||
def show(conn, %{"id" => id}) do
|
||||
source = Repo.preload(Sources.get_source!(id), :media_profile)
|
||||
|
||||
pending_tasks = Repo.preload(Tasks.list_pending_tasks_for(source), :job)
|
||||
pending_tasks =
|
||||
source
|
||||
|> Tasks.list_tasks_for(nil, [:executing, :available, :scheduled, :retryable])
|
||||
|> Repo.preload(:job)
|
||||
|
||||
pending_media = Media.list_pending_media_items_for(source, limit: 100)
|
||||
downloaded_media = Media.list_downloaded_media_items_for(source, limit: 100)
|
||||
|
||||
@@ -104,12 +110,28 @@ defmodule PinchflatWeb.Sources.SourceController do
|
||||
|> redirect(to: ~p"/sources")
|
||||
end
|
||||
|
||||
def force_download(conn, %{"source_id" => id}) do
|
||||
source = Sources.get_source!(id)
|
||||
DownloadingHelpers.enqueue_pending_download_tasks(source)
|
||||
|
||||
conn
|
||||
|> put_flash(:info, "Forced download of pending media items.")
|
||||
|> redirect(to: ~p"/sources/#{source}")
|
||||
end
|
||||
|
||||
def force_index(conn, %{"source_id" => id}) do
|
||||
source = Sources.get_source!(id)
|
||||
SlowIndexingHelpers.kickoff_indexing_task(source, %{force: true})
|
||||
|
||||
conn
|
||||
|> put_flash(:info, "Index enqueued.")
|
||||
|> redirect(to: ~p"/sources/#{source}")
|
||||
end
|
||||
|
||||
defp media_profiles do
|
||||
Profiles.list_media_profiles()
|
||||
end
|
||||
|
||||
# NOTE: should move this out of the controller
|
||||
# once I finally add some query fragment layer
|
||||
defp total_downloaded_for(source) do
|
||||
MediaQuery.new()
|
||||
|> MediaQuery.for_source(source)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<.button_dropdown text="Actions" class="justify-center w-full sm:w-50">
|
||||
<:option>
|
||||
<span
|
||||
x-data="{ copied: false }"
|
||||
x-on:click={"
|
||||
window.copyTextToClipboard('#{rss_feed_url(@conn, @source)}')
|
||||
copied = true
|
||||
setTimeout(() => copied = false, 4000)
|
||||
"}
|
||||
>
|
||||
Copy RSS Feed
|
||||
<span x-show="copied" x-transition.duration.150ms><.icon name="hero-check" class="ml-2 h-4 w-4" /></span>
|
||||
</span>
|
||||
</:option>
|
||||
<:option :if={@source.download_media}>
|
||||
<.link
|
||||
href={~p"/sources/#{@source}/force_download"}
|
||||
method="post"
|
||||
data-confirm="Are you sure you want to force a download of all *pending* media items? This isn't normally needed."
|
||||
>
|
||||
Force Download
|
||||
</.link>
|
||||
</:option>
|
||||
<:option>
|
||||
<.link
|
||||
href={~p"/sources/#{@source}/force_index"}
|
||||
method="post"
|
||||
data-confirm="Are you sure you want to force an index of this source? This isn't normally needed."
|
||||
>
|
||||
Force Index
|
||||
</.link>
|
||||
</:option>
|
||||
<:option>
|
||||
<div class="h-px w-full bg-bodydark2"></div>
|
||||
</:option>
|
||||
<:option>
|
||||
<.link
|
||||
href={~p"/sources/#{@source}"}
|
||||
method="delete"
|
||||
data-confirm="Are you sure you want to delete this source (leaving files in place)? This cannot be undone."
|
||||
>
|
||||
Delete Source
|
||||
</.link>
|
||||
</:option>
|
||||
<:option>
|
||||
<.link
|
||||
href={~p"/sources/#{@source}?delete_files=true"}
|
||||
method="delete"
|
||||
data-confirm="Are you sure you want to delete this source and it's files on disk? This cannot be undone."
|
||||
class="mt-5 md:mt-0"
|
||||
>
|
||||
Delete Source + Files
|
||||
</.link>
|
||||
</:option>
|
||||
</.button_dropdown>
|
||||
@@ -20,43 +20,7 @@
|
||||
<div class="max-w-full overflow-x-auto">
|
||||
<.tabbed_layout>
|
||||
<:tab_append>
|
||||
<.button_dropdown text="Actions" class="justify-center w-full sm:w-50">
|
||||
<:option>
|
||||
<span
|
||||
x-data="{ copied: false }"
|
||||
x-on:click={"
|
||||
window.copyTextToClipboard('#{rss_feed_url(@conn, @source)}')
|
||||
copied = true
|
||||
setTimeout(() => copied = false, 4000)
|
||||
"}
|
||||
>
|
||||
Copy RSS Feed
|
||||
<span x-show="copied" x-transition.duration.150ms><.icon name="hero-check" class="ml-2 h-4 w-4" /></span>
|
||||
</span>
|
||||
</:option>
|
||||
<:option>
|
||||
<div class="h-px w-full bg-bodydark2"></div>
|
||||
</:option>
|
||||
<:option>
|
||||
<.link
|
||||
href={~p"/sources/#{@source}"}
|
||||
method="delete"
|
||||
data-confirm="Are you sure you want to delete this source (leaving files in place)? This cannot be undone."
|
||||
>
|
||||
Delete Source
|
||||
</.link>
|
||||
</:option>
|
||||
<:option>
|
||||
<.link
|
||||
href={~p"/sources/#{@source}?delete_files=true"}
|
||||
method="delete"
|
||||
data-confirm="Are you sure you want to delete this source and it's files on disk? This cannot be undone."
|
||||
class="mt-5 md:mt-0"
|
||||
>
|
||||
Delete Source + Files
|
||||
</.link>
|
||||
</:option>
|
||||
</.button_dropdown>
|
||||
<.actions_dropdown source={@source} conn={@conn} />
|
||||
</:tab_append>
|
||||
|
||||
<:tab title="Attributes">
|
||||
@@ -64,9 +28,9 @@
|
||||
<h3 class="font-bold text-lg">Attributes</h3>
|
||||
<section>
|
||||
<strong>Media Profile:</strong>
|
||||
<.inline_link href={~p"/media_profiles/#{@source.media_profile_id}"}>
|
||||
<.subtle_link href={~p"/media_profiles/#{@source.media_profile_id}"}>
|
||||
<%= @source.media_profile.name %>
|
||||
</.inline_link>
|
||||
</.subtle_link>
|
||||
</section>
|
||||
|
||||
<.list_items_from_map map={Map.from_struct(@source)} />
|
||||
|
||||
Reference in New Issue
Block a user