diff --git a/.iex.exs b/.iex.exs
index d23dd0c..e90bc79 100644
--- a/.iex.exs
+++ b/.iex.exs
@@ -2,6 +2,7 @@ alias Pinchflat.Repo
alias Pinchflat.Tasks.Task
alias Pinchflat.Media.MediaItem
+alias Pinchflat.Tasks.SourceTasks
alias Pinchflat.Media.MediaMetadata
alias Pinchflat.MediaSource.Source
alias Pinchflat.Profiles.MediaProfile
@@ -12,3 +13,39 @@ alias Pinchflat.Profiles
alias Pinchflat.MediaSource
alias Pinchflat.MediaClient.{SourceDetails, VideoDownloader}
+
+defmodule IexHelpers do
+ def playlist_url do
+ "https://www.youtube.com/playlist?list=PLmqC3wPkeL8kSlTCcSMDD63gmSi7evcXS"
+ end
+
+ def channel_url do
+ "https://www.youtube.com/c/TheUselessTrials"
+ end
+
+ def video_url do
+ "https://www.youtube.com/watch?v=bR52O78ZIUw"
+ end
+
+ def details(type) do
+ source =
+ case type do
+ :playlist -> playlist_url()
+ :channel -> channel_url()
+ end
+
+ SourceDetails.get_source_details(source)
+ end
+
+ def ids(type) do
+ source =
+ case type do
+ :playlist -> playlist_url()
+ :channel -> channel_url()
+ end
+
+ SourceDetails.get_video_ids(source)
+ end
+end
+
+import IexHelpers
diff --git a/lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex b/lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex
index 624689b..89cb1af 100644
--- a/lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex
+++ b/lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex
@@ -4,8 +4,6 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollection do
videos (aka: a source [ie: channels, playlists]).
"""
- alias Pinchflat.MediaClient.SourceDetails
-
@doc """
Returns a list of strings representing the video ids in the collection.
@@ -28,19 +26,29 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollection do
instead we're fetching just the first video (using playlist_end: 1)
and parsing the source ID and name from _its_ metadata
- Returns {:ok, %SourceDetails{}} | {:error, any, ...}.
+ Returns {:ok, map()} | {:error, any, ...}.
"""
def get_source_details(source_url) do
- opts = [:skip_download, playlist_end: 1]
+ opts = [:simulate, :skip_download, playlist_end: 1]
+ output_template = "%(.{channel,channel_id,playlist_id,playlist_title})j"
- with {:ok, output} <- backend_runner().run(source_url, opts, "%(.{channel,channel_id})j"),
+ with {:ok, output} <- backend_runner().run(source_url, opts, output_template),
{:ok, parsed_json} <- Phoenix.json_library().decode(output) do
- {:ok, SourceDetails.new(parsed_json["channel_id"], parsed_json["channel"])}
+ {:ok, format_source_details(parsed_json)}
else
err -> err
end
end
+ defp format_source_details(response) do
+ %{
+ channel_id: response["channel_id"],
+ channel_name: response["channel"],
+ playlist_id: response["playlist_id"],
+ playlist_name: response["playlist_title"]
+ }
+ end
+
defp backend_runner do
Application.get_env(:pinchflat, :yt_dlp_runner)
end
diff --git a/lib/pinchflat/media_client/source_details.ex b/lib/pinchflat/media_client/source_details.ex
index 75c7077..0294600 100644
--- a/lib/pinchflat/media_client/source_details.ex
+++ b/lib/pinchflat/media_client/source_details.ex
@@ -5,16 +5,9 @@ defmodule Pinchflat.MediaClient.SourceDetails do
Technically hardcodes the yt-dlp backend for now, but should leave
it open-ish for future expansion (just in case).
"""
- @enforce_keys [:id, :name]
- defstruct [:id, :name]
alias Pinchflat.MediaClient.Backends.YtDlp.VideoCollection, as: YtDlpSource
- @doc false
- def new(id, name) do
- %__MODULE__{id: id, name: name}
- end
-
@doc """
Gets a source's ID and name from its URL, using the given backend.
diff --git a/lib/pinchflat/media_source.ex b/lib/pinchflat/media_source.ex
index 88dddd7..8396b8c 100644
--- a/lib/pinchflat/media_source.ex
+++ b/lib/pinchflat/media_source.ex
@@ -100,6 +100,10 @@ defmodule Pinchflat.MediaSource do
Note that this fetches source details as long as the `original_url` is present.
This means that it'll go for it even if a changeset is otherwise invalid. This
is pretty easy to change, but for MVP I'm not concerned.
+
+ IDEA: Maybe I could discern `collection_type` based on the original URL?
+ It also seems like it's a channel when the returned yt-dlp channel_id is the
+ same as the playlist_id - maybe could use that?
"""
def change_source_from_url(%Source{} = source, attrs) do
case change_source(source, attrs) do
@@ -115,14 +119,8 @@ defmodule Pinchflat.MediaSource do
%Ecto.Changeset{changes: changes} = changeset
case SourceDetails.get_source_details(changes.original_url) do
- {:ok, %SourceDetails{} = source_details} ->
- change_source(
- source,
- Map.merge(changes, %{
- name: source_details.name,
- collection_id: source_details.id
- })
- )
+ {:ok, source_details} ->
+ add_source_details_by_collection_type(source, changeset, source_details)
{:error, runner_error, _status_code} ->
Ecto.Changeset.add_error(
@@ -134,15 +132,35 @@ defmodule Pinchflat.MediaSource do
end
end
+ defp add_source_details_by_collection_type(source, changeset, source_details) do
+ %Ecto.Changeset{changes: changes} = changeset
+ collection_type = source.collection_type || changes[:collection_type]
+
+ collection_changes =
+ case collection_type do
+ :channel ->
+ %{
+ collection_id: source_details.channel_id,
+ collection_name: source_details.channel_name
+ }
+
+ :playlist ->
+ %{
+ collection_id: source_details.playlist_id,
+ collection_name: source_details.playlist_name
+ }
+
+ _ ->
+ %{}
+ end
+
+ change_source(source, Map.merge(changes, collection_changes))
+ end
+
defp commit_and_start_indexing(changeset) do
case Repo.insert_or_update(changeset) do
- {:ok, %Source{} = source} ->
- maybe_run_indexing_task(changeset, source)
-
- {:ok, source}
-
- err ->
- err
+ {:ok, %Source{} = source} -> maybe_run_indexing_task(changeset, source)
+ err -> err
end
end
@@ -159,5 +177,7 @@ defmodule Pinchflat.MediaSource do
SourceTasks.kickoff_indexing_task(source)
end
end
+
+ {:ok, source}
end
end
diff --git a/lib/pinchflat/media_source/source.ex b/lib/pinchflat/media_source/source.ex
index b633bf8..65f48c7 100644
--- a/lib/pinchflat/media_source/source.ex
+++ b/lib/pinchflat/media_source/source.ex
@@ -9,14 +9,24 @@ defmodule Pinchflat.MediaSource.Source do
alias Pinchflat.Media.MediaItem
alias Pinchflat.Profiles.MediaProfile
- @allowed_fields ~w(name collection_id collection_type index_frequency_minutes original_url media_profile_id)a
- @required_fields @allowed_fields -- ~w(index_frequency_minutes)a
+ @allowed_fields ~w(
+ collection_name
+ collection_id
+ collection_type
+ friendly_name
+ index_frequency_minutes
+ original_url
+ media_profile_id
+ )a
+
+ @required_fields @allowed_fields -- ~w(index_frequency_minutes friendly_name)a
schema "sources" do
- field :name, :string
+ field :friendly_name, :string
+ field :collection_name, :string
field :collection_id, :string
field :collection_type, Ecto.Enum, values: [:channel, :playlist]
- field :index_frequency_minutes, :integer
+ field :index_frequency_minutes, :integer, default: 60 * 24
# This should only be used for user reference going forward
# as the collection_id should be used for all API calls
field :original_url, :string
diff --git a/lib/pinchflat/tasks/source_tasks.ex b/lib/pinchflat/tasks/source_tasks.ex
index 0d5aa08..0ae18f6 100644
--- a/lib/pinchflat/tasks/source_tasks.ex
+++ b/lib/pinchflat/tasks/source_tasks.ex
@@ -3,9 +3,11 @@ defmodule Pinchflat.Tasks.SourceTasks do
This module contains methods for managing tasks (workers) related to sources.
"""
+ alias Pinchflat.Media
alias Pinchflat.Tasks
alias Pinchflat.MediaSource.Source
alias Pinchflat.Workers.MediaIndexingWorker
+ alias Pinchflat.Workers.VideoDownloadWorker
@doc """
Starts tasks for indexing a source's media.
@@ -30,4 +32,27 @@ defmodule Pinchflat.Tasks.SourceTasks do
end
end
end
+
+ @doc """
+ Starts tasks for downloading videos for any of a sources _pending_ media items.
+
+ NOTE: this starts a download for each media item that is pending,
+ not just the ones that were indexed in this job run. This should ensure
+ that any stragglers are caught if, for some reason, they weren't enqueued
+ or somehow got de-queued.
+
+ I'm not sure of a case where this would happen, but it's cheap insurance.
+
+ Returns :ok
+ """
+ def enqueue_pending_media_downloads(%Source{} = source) do
+ source
+ |> Media.list_pending_media_items_for()
+ |> Enum.each(fn media_item ->
+ media_item
+ |> Map.take([:id])
+ |> VideoDownloadWorker.new()
+ |> Tasks.create_job_with_task(media_item)
+ end)
+ end
end
diff --git a/lib/pinchflat/workers/media_indexing_worker.ex b/lib/pinchflat/workers/media_indexing_worker.ex
index 166a4bd..f46e427 100644
--- a/lib/pinchflat/workers/media_indexing_worker.ex
+++ b/lib/pinchflat/workers/media_indexing_worker.ex
@@ -7,10 +7,9 @@ defmodule Pinchflat.Workers.MediaIndexingWorker do
tags: ["media_source", "media_indexing"]
alias __MODULE__
- alias Pinchflat.Media
alias Pinchflat.Tasks
alias Pinchflat.MediaSource
- alias Pinchflat.Workers.VideoDownloadWorker
+ alias Pinchflat.Tasks.SourceTasks
@impl Oban.Worker
@doc """
@@ -49,7 +48,7 @@ defmodule Pinchflat.Workers.MediaIndexingWorker do
defp index_media_and_reschedule(source) do
MediaSource.index_media_items(source)
- enqueue_video_downloads(source)
+ SourceTasks.enqueue_pending_media_downloads(source)
source
|> Map.take([:id])
@@ -60,21 +59,4 @@ defmodule Pinchflat.Workers.MediaIndexingWorker do
{:error, :duplicate_job} -> {:ok, :job_exists}
end
end
-
- # NOTE: this starts a download for each media item that is pending,
- # not just the ones that were indexed in this job run. This should ensure
- # that any stragglers are caught if, for some reason, they weren't enqueued
- # or somehow got de-queued.
- #
- # I'm not sure of a case where this would happen, but it's cheap insurance.
- defp enqueue_video_downloads(source) do
- source
- |> Media.list_pending_media_items_for()
- |> Enum.each(fn media_item ->
- media_item
- |> Map.take([:id])
- |> VideoDownloadWorker.new()
- |> Tasks.create_job_with_task(media_item)
- end)
- end
end
diff --git a/lib/pinchflat_web/controllers/media_sources/source_controller.ex b/lib/pinchflat_web/controllers/media_sources/source_controller.ex
index b90cf1e..5061e34 100644
--- a/lib/pinchflat_web/controllers/media_sources/source_controller.ex
+++ b/lib/pinchflat_web/controllers/media_sources/source_controller.ex
@@ -1,6 +1,7 @@
defmodule PinchflatWeb.MediaSources.SourceController do
use PinchflatWeb, :controller
+ alias Pinchflat.Repo
alias Pinchflat.Profiles
alias Pinchflat.MediaSource
alias Pinchflat.MediaSource.Source
@@ -30,7 +31,10 @@ defmodule PinchflatWeb.MediaSources.SourceController do
end
def show(conn, %{"id" => id}) do
- source = MediaSource.get_source!(id)
+ source =
+ id
+ |> MediaSource.get_source!()
+ |> Repo.preload(:media_profile)
render(conn, :show, source: source)
end
diff --git a/lib/pinchflat_web/controllers/media_sources/source_html.ex b/lib/pinchflat_web/controllers/media_sources/source_html.ex
index 9531a59..7f790cd 100644
--- a/lib/pinchflat_web/controllers/media_sources/source_html.ex
+++ b/lib/pinchflat_web/controllers/media_sources/source_html.ex
@@ -24,4 +24,11 @@ defmodule PinchflatWeb.MediaSources.SourceHTML do
{"Monthly", 30 * 24 * 60}
]
end
+
+ def friendly_collection_types do
+ [
+ {"Channel", "channel"},
+ {"Playlist", "playlist"}
+ ]
+ end
end
diff --git a/lib/pinchflat_web/controllers/media_sources/source_html/index.html.heex b/lib/pinchflat_web/controllers/media_sources/source_html/index.html.heex
index 348873d..e5cede9 100644
--- a/lib/pinchflat_web/controllers/media_sources/source_html/index.html.heex
+++ b/lib/pinchflat_web/controllers/media_sources/source_html/index.html.heex
@@ -8,8 +8,8 @@
<.table id="sources" rows={@sources} row_click={&JS.navigate(~p"/media_sources/sources/#{&1}")}>
- <:col :let={source} label="Name"><%= source.name %>
- <:col :let={source} label="Source"><%= source.collection_id %>
+ <:col :let={source} label="Collection Name"><%= source.collection_name %>
+ <:col :let={source} label="Collection ID"><%= source.collection_id %>
<:action :let={source}>
<.link navigate={~p"/media_sources/sources/#{source}"}>Show
diff --git a/lib/pinchflat_web/controllers/media_sources/source_html/show.html.heex b/lib/pinchflat_web/controllers/media_sources/source_html/show.html.heex
index 5ae2176..fb07e1e 100644
--- a/lib/pinchflat_web/controllers/media_sources/source_html/show.html.heex
+++ b/lib/pinchflat_web/controllers/media_sources/source_html/show.html.heex
@@ -9,9 +9,18 @@
<.list>
- <:item title="Source Name"><%= @source.name %>
- <:item title="Source ID"><%= @source.collection_id %>
- <:item title="Original URL"><%= @source.original_url %>
+ <:item title="media_profile">
+ <.link href={~p"/media_profiles/#{@source.media_profile}"}>
+ <%= @source.media_profile.name %>
+
+
+
+ <:item
+ :for={attr <- ~w(collection_type collection_name collection_id original_url friendly_name)a}
+ title={attr}
+ >
+ <%= Map.get(@source, attr) %>
+
<.back navigate={~p"/media_sources/sources"}>Back to sources
diff --git a/lib/pinchflat_web/controllers/media_sources/source_html/source_form.html.heex b/lib/pinchflat_web/controllers/media_sources/source_html/source_form.html.heex
index f8a0a87..bada6cf 100644
--- a/lib/pinchflat_web/controllers/media_sources/source_html/source_form.html.heex
+++ b/lib/pinchflat_web/controllers/media_sources/source_html/source_form.html.heex
@@ -3,6 +3,8 @@
Oops, something went wrong! Please check the errors below.
+ <.input field={f[:friendly_name]} type="text" label="Friendly Name" />
+
<.input
field={f[:media_profile_id]}
options={Enum.map(@media_profiles, &{&1.name, &1.id})}
@@ -10,7 +12,13 @@
label="Media Profile"
/>
- <.input field={f[:collection_type]} type="text" label="Collection Type" />
+ <.input
+ field={f[:collection_type]}
+ options={friendly_collection_types()}
+ type="select"
+ label="Collection Type"
+ />
+
<.input field={f[:original_url]} type="text" label="Source URL" />
<.input
diff --git a/priv/repo/migrations/20240202190351_rename_source_name_to_collection_name.exs b/priv/repo/migrations/20240202190351_rename_source_name_to_collection_name.exs
new file mode 100644
index 0000000..3639cc3
--- /dev/null
+++ b/priv/repo/migrations/20240202190351_rename_source_name_to_collection_name.exs
@@ -0,0 +1,11 @@
+defmodule Pinchflat.Repo.Migrations.RenameSourceNameToCollectionName do
+ use Ecto.Migration
+
+ def change do
+ rename table(:sources), :name, to: :collection_name
+
+ alter table(:sources) do
+ add :friendly_name, :string
+ end
+ end
+end
diff --git a/test/pinchflat/media_client/backends/yt_dlp/video_collection_test.exs b/test/pinchflat/media_client/backends/yt_dlp/video_collection_test.exs
index 3c8bfe7..709050a 100644
--- a/test/pinchflat/media_client/backends/yt_dlp/video_collection_test.exs
+++ b/test/pinchflat/media_client/backends/yt_dlp/video_collection_test.exs
@@ -2,7 +2,6 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollectionTest do
use ExUnit.Case, async: true
import Mox
- alias Pinchflat.MediaClient.SourceDetails
alias Pinchflat.MediaClient.Backends.YtDlp.VideoCollection
@channel_url "https://www.youtube.com/c/TheUselessTrials"
@@ -45,19 +44,30 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollectionTest do
end
describe "get_source_details/1" do
- test "it returns a %SourceDetails{} with data on success" do
+ test "it returns a map with data on success" do
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot ->
- {:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"}
+ Phoenix.json_library().encode(%{
+ channel: "TheUselessTrials",
+ channel_id: "UCQH2",
+ playlist_id: "PLQH2",
+ playlist_title: "TheUselessTrials - Videos"
+ })
end)
assert {:ok, res} = VideoCollection.get_source_details(@channel_url)
- assert %SourceDetails{id: "UCQH2", name: "TheUselessTrials"} = res
+
+ assert %{
+ channel_id: "UCQH2",
+ channel_name: "TheUselessTrials",
+ playlist_id: "PLQH2",
+ playlist_name: "TheUselessTrials - Videos"
+ } = res
end
test "it passes the expected args to the backend runner" do
expect(YtDlpRunnerMock, :run, fn @channel_url, opts, ot ->
- assert opts == [:skip_download, playlist_end: 1]
- assert ot == "%(.{channel,channel_id})j"
+ assert opts == [:simulate, :skip_download, playlist_end: 1]
+ assert ot == "%(.{channel,channel_id,playlist_id,playlist_title})j"
{:ok, "{}"}
end)
diff --git a/test/pinchflat/media_client/source_details_test.exs b/test/pinchflat/media_client/source_details_test.exs
index 244e65d..5d79cc9 100644
--- a/test/pinchflat/media_client/source_details_test.exs
+++ b/test/pinchflat/media_client/source_details_test.exs
@@ -8,20 +8,13 @@ defmodule Pinchflat.MediaClient.SourceDetailsTest do
setup :verify_on_exit!
- describe "new/2" do
- test "it returns a struct with the given values" do
- assert %SourceDetails{id: "UCQH2", name: "TheUselessTrials"} =
- SourceDetails.new("UCQH2", "TheUselessTrials")
- end
- end
-
describe "get_source_details/2" do
test "it passes the expected arguments to the backend" do
expect(YtDlpRunnerMock, :run, fn @channel_url, opts, ot ->
- assert opts == [:skip_download, playlist_end: 1]
- assert ot == "%(.{channel,channel_id})j"
+ assert opts == [:simulate, :skip_download, playlist_end: 1]
+ assert ot == "%(.{channel,channel_id,playlist_id,playlist_title})j"
- {:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"}
+ {:ok, "{}"}
end)
assert {:ok, _} = SourceDetails.get_source_details(@channel_url)
@@ -29,11 +22,22 @@ defmodule Pinchflat.MediaClient.SourceDetailsTest do
test "it returns a struct composed of the returned data" do
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot ->
- {:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"}
+ Phoenix.json_library().encode(%{
+ channel: "TheUselessTrials",
+ channel_id: "UCQH2",
+ playlist_id: "PLQH2",
+ playlist_title: "TheUselessTrials - Videos"
+ })
end)
assert {:ok, res} = SourceDetails.get_source_details(@channel_url)
- assert %SourceDetails{id: "UCQH2", name: "TheUselessTrials"} = res
+
+ assert %{
+ channel_id: "UCQH2",
+ channel_name: "TheUselessTrials",
+ playlist_id: "PLQH2",
+ playlist_name: "TheUselessTrials - Videos"
+ } = res
end
end
diff --git a/test/pinchflat/media_source_test.exs b/test/pinchflat/media_source_test.exs
index e113df5..2ba30fb 100644
--- a/test/pinchflat/media_source_test.exs
+++ b/test/pinchflat/media_source_test.exs
@@ -29,7 +29,7 @@ defmodule Pinchflat.MediaSourceTest do
end
describe "create_source/1" do
- test "creates a source and adds name + ID from runner response" do
+ test "creates a source and adds name + ID from runner response for channels" do
expect(YtDlpRunnerMock, :run, &runner_function_mock/3)
valid_attrs = %{
@@ -39,20 +39,34 @@ defmodule Pinchflat.MediaSourceTest do
}
assert {:ok, %Source{} = source} = MediaSource.create_source(valid_attrs)
- assert source.name == "some name"
- assert String.starts_with?(source.collection_id, "some_source_id_")
+ assert source.collection_name == "some channel name"
+ assert String.starts_with?(source.collection_id, "some_channel_id_")
+ end
+
+ test "creates a source and adds name + ID for playlists" do
+ expect(YtDlpRunnerMock, :run, &runner_function_mock/3)
+
+ valid_attrs = %{
+ media_profile_id: media_profile_fixture().id,
+ original_url: "https://www.youtube.com/playlist?list=abc123",
+ collection_type: "playlist"
+ }
+
+ assert {:ok, %Source{} = source} = MediaSource.create_source(valid_attrs)
+ assert source.collection_name == "some playlist name"
+ assert String.starts_with?(source.collection_id, "some_playlist_id_")
end
test "creation with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = MediaSource.create_source(@invalid_source_attrs)
end
- test "creation enforces uniqueness of source_id scoped to the media_profile" do
+ test "creation enforces uniqueness of collection_id scoped to the media_profile" do
expect(YtDlpRunnerMock, :run, 2, fn _url, _opts, _ot ->
{:ok,
Phoenix.json_library().encode!(%{
- channel: "some name",
- channel_id: "some_source_id_12345678"
+ channel: "some channel name",
+ channel_id: "some_channel_id_12345678"
})}
end)
@@ -70,8 +84,8 @@ defmodule Pinchflat.MediaSourceTest do
expect(YtDlpRunnerMock, :run, 2, fn _url, _opts, _ot ->
{:ok,
Phoenix.json_library().encode!(%{
- channel: "some name",
- channel_id: "some_source_id_12345678"
+ channel: "some channel name",
+ channel_id: "some_channel_id_12345678"
})}
end)
@@ -159,21 +173,32 @@ defmodule Pinchflat.MediaSourceTest do
describe "update_source/2" do
test "updates with valid data updates the source" do
source = source_fixture()
- update_attrs = %{name: "some updated name"}
+ update_attrs = %{collection_name: "some updated name"}
assert {:ok, %Source{} = source} = MediaSource.update_source(source, update_attrs)
- assert source.name == "some updated name"
+ assert source.collection_name == "some updated name"
end
- test "updating the original_url will re-fetch the source details" do
+ test "updating the original_url will re-fetch the source details for channels" do
expect(YtDlpRunnerMock, :run, &runner_function_mock/3)
source = source_fixture()
update_attrs = %{original_url: "https://www.youtube.com/channel/abc123"}
assert {:ok, %Source{} = source} = MediaSource.update_source(source, update_attrs)
- assert source.name == "some name"
- assert String.starts_with?(source.collection_id, "some_source_id_")
+ assert source.collection_name == "some channel name"
+ assert String.starts_with?(source.collection_id, "some_channel_id_")
+ end
+
+ test "updating the original_url will re-fetch the source details for playlists" do
+ expect(YtDlpRunnerMock, :run, &runner_function_mock/3)
+
+ source = source_fixture(collection_type: "playlist")
+ update_attrs = %{original_url: "https://www.youtube.com/playlist?list=abc123"}
+
+ assert {:ok, %Source{} = source} = MediaSource.update_source(source, update_attrs)
+ assert source.collection_name == "some playlist name"
+ assert String.starts_with?(source.collection_id, "some_playlist_id_")
end
test "not updating the original_url will not re-fetch the source details" do
@@ -275,16 +300,16 @@ defmodule Pinchflat.MediaSourceTest do
media_profile_id = media_profile.id
changeset =
- MediaSource.change_source_from_url(%Source{}, %{
+ MediaSource.change_source_from_url(%Source{collection_type: :channel}, %{
original_url: "https://www.youtube.com/channel/abc123",
media_profile_id: media_profile.id
})
assert %Ecto.Changeset{} = changeset
- assert String.starts_with?(changeset.changes.collection_id, "some_source_id_")
+ assert String.starts_with?(changeset.changes.collection_id, "some_channel_id_")
assert %{
- name: "some name",
+ collection_name: "some channel name",
media_profile_id: ^media_profile_id,
original_url: "https://www.youtube.com/channel/abc123"
} = changeset.changes
@@ -309,8 +334,10 @@ defmodule Pinchflat.MediaSourceTest do
{
:ok,
Phoenix.json_library().encode!(%{
- channel: "some name",
- channel_id: "some_source_id_#{:rand.uniform(1_000_000)}"
+ channel: "some channel name",
+ channel_id: "some_channel_id_#{:rand.uniform(1_000_000)}",
+ playlist_id: "some_playlist_id_#{:rand.uniform(1_000_000)}",
+ playlist_title: "some playlist name"
})
}
end
diff --git a/test/pinchflat/tasks/source_tasks_test.exs b/test/pinchflat/tasks/source_tasks_test.exs
index 1e9c38f..33cdc03 100644
--- a/test/pinchflat/tasks/source_tasks_test.exs
+++ b/test/pinchflat/tasks/source_tasks_test.exs
@@ -2,11 +2,14 @@ defmodule Pinchflat.Tasks.SourceTasksTest do
use Pinchflat.DataCase
import Pinchflat.TasksFixtures
+ import Pinchflat.MediaFixtures
import Pinchflat.MediaSourceFixtures
+ alias Pinchflat.Tasks
alias Pinchflat.Tasks.Task
alias Pinchflat.Tasks.SourceTasks
alias Pinchflat.Workers.MediaIndexingWorker
+ alias Pinchflat.Workers.VideoDownloadWorker
describe "kickoff_indexing_task/1" do
test "it does not schedule a job if the interval is <= 0" do
@@ -42,4 +45,35 @@ defmodule Pinchflat.Tasks.SourceTasksTest do
assert_raise Ecto.NoResultsError, fn -> Repo.reload!(task) end
end
end
+
+ describe "enqueue_pending_media_downloads/1" do
+ test "it enqueues a job for each pending media item" do
+ source = source_fixture()
+ media_item = media_item_fixture(source_id: source.id, media_filepath: nil)
+
+ assert :ok = SourceTasks.enqueue_pending_media_downloads(source)
+
+ assert_enqueued(worker: VideoDownloadWorker, args: %{"id" => media_item.id})
+ end
+
+ test "it does not enqueue a job for media items with a filepath" do
+ source = source_fixture()
+ _media_item = media_item_fixture(source_id: source.id, media_filepath: "some/filepath.mp4")
+
+ assert :ok = SourceTasks.enqueue_pending_media_downloads(source)
+
+ refute_enqueued(worker: VideoDownloadWorker)
+ end
+
+ test "it attaches a task to each enqueued job" do
+ source = source_fixture()
+ media_item = media_item_fixture(source_id: source.id, media_filepath: nil)
+
+ assert [] = Tasks.list_tasks_for(:media_item_id, media_item.id)
+
+ assert :ok = SourceTasks.enqueue_pending_media_downloads(source)
+
+ assert [_] = Tasks.list_tasks_for(:media_item_id, media_item.id)
+ end
+ end
end
diff --git a/test/pinchflat_web/controllers/source_controller_test.exs b/test/pinchflat_web/controllers/source_controller_test.exs
index f973e8d..39cb9d8 100644
--- a/test/pinchflat_web/controllers/source_controller_test.exs
+++ b/test/pinchflat_web/controllers/source_controller_test.exs
@@ -104,16 +104,17 @@ defmodule PinchflatWeb.SourceControllerTest do
end
defp create_source(_) do
- source = source_fixture()
- %{source: source}
+ %{source: source_fixture()}
end
defp runner_function_mock(_url, _opts, _ot) do
{
:ok,
Phoenix.json_library().encode!(%{
- channel: "some name",
- channel_id: "some_source_id_#{:rand.uniform(1_000_000)}"
+ channel: "some channel name",
+ channel_id: "some_channel_id_#{:rand.uniform(1_000_000)}",
+ playlist_id: "some_playlist_id_#{:rand.uniform(1_000_000)}",
+ playlist_title: "some playlist name"
})
}
end
diff --git a/test/support/fixtures/media_source_fixtures.ex b/test/support/fixtures/media_source_fixtures.ex
index 178ccc9..89500ba 100644
--- a/test/support/fixtures/media_source_fixtures.ex
+++ b/test/support/fixtures/media_source_fixtures.ex
@@ -16,9 +16,10 @@ defmodule Pinchflat.MediaSourceFixtures do
%Source{}
|> Source.changeset(
Enum.into(attrs, %{
- name: "Source ##{:rand.uniform(1_000_000)}",
+ collection_name: "Source ##{:rand.uniform(1_000_000)}",
collection_id: Base.encode16(:crypto.hash(:md5, "#{:rand.uniform(1_000_000)}")),
collection_type: "channel",
+ friendly_name: "Cool and good internal name!",
original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}",
media_profile_id: ProfilesFixtures.media_profile_fixture().id,
index_frequency_minutes: 60