More renaming; renamed channel details module
This commit is contained in:
parent
d822aaa899
commit
2250695a3e
9 changed files with 64 additions and 64 deletions
2
.iex.exs
2
.iex.exs
|
|
@ -11,4 +11,4 @@ alias Pinchflat.Media
|
||||||
alias Pinchflat.Profiles
|
alias Pinchflat.Profiles
|
||||||
alias Pinchflat.MediaSource
|
alias Pinchflat.MediaSource
|
||||||
|
|
||||||
alias Pinchflat.MediaClient.{ChannelDetails, VideoDownloader}
|
alias Pinchflat.MediaClient.{SourceDetails, VideoDownloader}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ defmodule Pinchflat.Media do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns a list of pending media_items for a given channel, where
|
Returns a list of pending media_items for a given source, where
|
||||||
pending means the `media_filepath` is `nil`.
|
pending means the `media_filepath` is `nil`.
|
||||||
|
|
||||||
Returns [%MediaItem{}, ...].
|
Returns [%MediaItem{}, ...].
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.Channel do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Pinchflat.MediaClient.Backends.YtDlp.VideoCollection
|
use Pinchflat.MediaClient.Backends.YtDlp.VideoCollection
|
||||||
alias Pinchflat.MediaClient.ChannelDetails
|
alias Pinchflat.MediaClient.SourceDetails
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a channel's ID and name from its URL.
|
Gets a channel's ID and name from its URL.
|
||||||
|
|
@ -13,14 +13,14 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.Channel do
|
||||||
instead we're fetching just the first video (using playlist_end: 1)
|
instead we're fetching just the first video (using playlist_end: 1)
|
||||||
and parsing the channel ID and name from _its_ metadata
|
and parsing the channel ID and name from _its_ metadata
|
||||||
|
|
||||||
Returns {:ok, %ChannelDetails{}} | {:error, any, ...}.
|
Returns {:ok, %SourceDetails{}} | {:error, any, ...}.
|
||||||
"""
|
"""
|
||||||
def get_source_details(channel_url) do
|
def get_source_details(channel_url) do
|
||||||
opts = [:skip_download, playlist_end: 1]
|
opts = [:skip_download, playlist_end: 1]
|
||||||
|
|
||||||
with {:ok, output} <- backend_runner().run(channel_url, opts, "%(.{channel,channel_id})j"),
|
with {:ok, output} <- backend_runner().run(channel_url, opts, "%(.{channel,channel_id})j"),
|
||||||
{:ok, parsed_json} <- Phoenix.json_library().decode(output) do
|
{:ok, parsed_json} <- Phoenix.json_library().decode(output) do
|
||||||
{:ok, ChannelDetails.new(parsed_json["channel_id"], parsed_json["channel"])}
|
{:ok, SourceDetails.new(parsed_json["channel_id"], parsed_json["channel"])}
|
||||||
else
|
else
|
||||||
err -> err
|
err -> err
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
defmodule Pinchflat.MediaClient.ChannelDetails do
|
defmodule Pinchflat.MediaClient.SourceDetails do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
This is the integration layer for actually working with channels.
|
This is the integration layer for actually working with sources.
|
||||||
|
|
||||||
Technically hardcodes the yt-dlp backend for now, but should leave
|
Technically hardcodes the yt-dlp backend for now, but should leave
|
||||||
it open-ish for future expansion (just in case).
|
it open-ish for future expansion (just in case).
|
||||||
|
|
@ -16,24 +16,24 @@ defmodule Pinchflat.MediaClient.ChannelDetails do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a channel's ID and name from its URL, using the given backend.
|
Gets a source's ID and name from its URL, using the given backend.
|
||||||
|
|
||||||
Returns {:ok, map()} | {:error, any, ...}.
|
Returns {:ok, map()} | {:error, any, ...}.
|
||||||
"""
|
"""
|
||||||
def get_source_details(channel_url, backend \\ :yt_dlp) do
|
def get_source_details(source_url, backend \\ :yt_dlp) do
|
||||||
channel_module(backend).get_source_details(channel_url)
|
source_module(backend).get_source_details(source_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns a list of video IDs for the given channel URL, using the given backend.
|
Returns a list of video IDs for the given source URL, using the given backend.
|
||||||
|
|
||||||
Returns {:ok, list(binary())} | {:error, any, ...}.
|
Returns {:ok, list(binary())} | {:error, any, ...}.
|
||||||
"""
|
"""
|
||||||
def get_video_ids(channel_url, backend \\ :yt_dlp) do
|
def get_video_ids(source_url, backend \\ :yt_dlp) do
|
||||||
channel_module(backend).get_video_ids(channel_url)
|
source_module(backend).get_video_ids(source_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp channel_module(backend) do
|
defp source_module(backend) do
|
||||||
case backend do
|
case backend do
|
||||||
:yt_dlp -> YtDlpChannel
|
:yt_dlp -> YtDlpChannel
|
||||||
end
|
end
|
||||||
|
|
@ -10,25 +10,25 @@ defmodule Pinchflat.MediaSource do
|
||||||
alias Pinchflat.Media
|
alias Pinchflat.Media
|
||||||
alias Pinchflat.Tasks.SourceTasks
|
alias Pinchflat.Tasks.SourceTasks
|
||||||
alias Pinchflat.MediaSource.Source
|
alias Pinchflat.MediaSource.Source
|
||||||
alias Pinchflat.MediaClient.ChannelDetails
|
alias Pinchflat.MediaClient.SourceDetails
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns the list of channels. Returns [%Source{}, ...]
|
Returns the list of sources. Returns [%Source{}, ...]
|
||||||
"""
|
"""
|
||||||
def list_sources do
|
def list_sources do
|
||||||
Repo.all(Source)
|
Repo.all(Source)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single channel.
|
Gets a single source.
|
||||||
|
|
||||||
Returns %Source{}. Raises `Ecto.NoResultsError` if the Channel does not exist.
|
Returns %Source{}. Raises `Ecto.NoResultsError` if the Channel does not exist.
|
||||||
"""
|
"""
|
||||||
def get_source!(id), do: Repo.get!(Source, id)
|
def get_source!(id), do: Repo.get!(Source, id)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates a channel. May attempt to pull additional channel details from the
|
Creates a source. May attempt to pull additional source details from the
|
||||||
original_url (if provided). Will attempt to start indexing the channel's
|
original_url (if provided). Will attempt to start indexing the source's
|
||||||
media if successfully inserted.
|
media if successfully inserted.
|
||||||
|
|
||||||
Returns {:ok, %Source{}} | {:error, %Ecto.Changeset{}}
|
Returns {:ok, %Source{}} | {:error, %Ecto.Changeset{}}
|
||||||
|
|
@ -46,7 +46,7 @@ defmodule Pinchflat.MediaSource do
|
||||||
Returns [%MediaItem{}, ...] | [%Ecto.Changeset{}, ...]
|
Returns [%MediaItem{}, ...] | [%Ecto.Changeset{}, ...]
|
||||||
"""
|
"""
|
||||||
def index_media_items(%Source{} = source) do
|
def index_media_items(%Source{} = source) do
|
||||||
{:ok, media_ids} = ChannelDetails.get_video_ids(source.original_url)
|
{:ok, media_ids} = SourceDetails.get_video_ids(source.original_url)
|
||||||
|
|
||||||
media_ids
|
media_ids
|
||||||
|> Enum.map(fn media_id ->
|
|> Enum.map(fn media_id ->
|
||||||
|
|
@ -60,8 +60,8 @@ defmodule Pinchflat.MediaSource do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Updates a channel. May attempt to pull additional channel details from the
|
Updates a source. May attempt to pull additional source details from the
|
||||||
original_url (if changed). May attempt to start indexing the channel's
|
original_url (if changed). May attempt to start indexing the source's
|
||||||
media if the indexing frequency has been changed.
|
media if the indexing frequency has been changed.
|
||||||
|
|
||||||
Existing indexing tasks will be cancelled if the indexing frequency has been
|
Existing indexing tasks will be cancelled if the indexing frequency has been
|
||||||
|
|
@ -69,58 +69,58 @@ defmodule Pinchflat.MediaSource do
|
||||||
|
|
||||||
Returns {:ok, %Source{}} | {:error, %Ecto.Changeset{}}
|
Returns {:ok, %Source{}} | {:error, %Ecto.Changeset{}}
|
||||||
"""
|
"""
|
||||||
def update_source(%Source{} = channel, attrs) do
|
def update_source(%Source{} = source, attrs) do
|
||||||
channel
|
source
|
||||||
|> change_source_from_url(attrs)
|
|> change_source_from_url(attrs)
|
||||||
|> commit_and_start_indexing()
|
|> commit_and_start_indexing()
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Deletes a channel and it's associated tasks (of any state).
|
Deletes a source and it's associated tasks (of any state).
|
||||||
|
|
||||||
Returns {:ok, %Source{}} | {:error, %Ecto.Changeset{}}
|
Returns {:ok, %Source{}} | {:error, %Ecto.Changeset{}}
|
||||||
"""
|
"""
|
||||||
def delete_source(%Source{} = channel) do
|
def delete_source(%Source{} = source) do
|
||||||
Tasks.delete_tasks_for(channel)
|
Tasks.delete_tasks_for(source)
|
||||||
Repo.delete(channel)
|
Repo.delete(source)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns an `%Ecto.Changeset{}` for tracking channel changes.
|
Returns an `%Ecto.Changeset{}` for tracking source changes.
|
||||||
"""
|
"""
|
||||||
def change_source(%Source{} = channel, attrs \\ %{}) do
|
def change_source(%Source{} = source, attrs \\ %{}) do
|
||||||
Source.changeset(channel, attrs)
|
Source.changeset(source, attrs)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns an `%Ecto.Changeset{}` for tracking channel changes and additionally
|
Returns an `%Ecto.Changeset{}` for tracking source changes and additionally
|
||||||
fetches channel details from the original_url (if provided). If the channel
|
fetches source details from the original_url (if provided). If the source
|
||||||
details cannot be fetched, an error is added to the changeset.
|
details cannot be fetched, an error is added to the changeset.
|
||||||
|
|
||||||
Note that this fetches channel details as long as the `original_url` is present.
|
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
|
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.
|
is pretty easy to change, but for MVP I'm not concerned.
|
||||||
"""
|
"""
|
||||||
def change_source_from_url(%Source{} = channel, attrs) do
|
def change_source_from_url(%Source{} = source, attrs) do
|
||||||
case change_source(channel, attrs) do
|
case change_source(source, attrs) do
|
||||||
%Ecto.Changeset{changes: %{original_url: _}} = changeset ->
|
%Ecto.Changeset{changes: %{original_url: _}} = changeset ->
|
||||||
add_source_details_to_changeset(channel, changeset)
|
add_source_details_to_changeset(source, changeset)
|
||||||
|
|
||||||
changeset ->
|
changeset ->
|
||||||
changeset
|
changeset
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp add_source_details_to_changeset(channel, changeset) do
|
defp add_source_details_to_changeset(source, changeset) do
|
||||||
%Ecto.Changeset{changes: changes} = changeset
|
%Ecto.Changeset{changes: changes} = changeset
|
||||||
|
|
||||||
case ChannelDetails.get_source_details(changes.original_url) do
|
case SourceDetails.get_source_details(changes.original_url) do
|
||||||
{:ok, %ChannelDetails{} = channel_details} ->
|
{:ok, %SourceDetails{} = source_details} ->
|
||||||
change_source(
|
change_source(
|
||||||
channel,
|
source,
|
||||||
Map.merge(changes, %{
|
Map.merge(changes, %{
|
||||||
name: channel_details.name,
|
name: source_details.name,
|
||||||
collection_id: channel_details.id
|
collection_id: source_details.id
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -128,7 +128,7 @@ defmodule Pinchflat.MediaSource do
|
||||||
Ecto.Changeset.add_error(
|
Ecto.Changeset.add_error(
|
||||||
changeset,
|
changeset,
|
||||||
:original_url,
|
:original_url,
|
||||||
"could not fetch channel details from URL",
|
"could not fetch source details from URL",
|
||||||
error: runner_error
|
error: runner_error
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
@ -136,27 +136,27 @@ defmodule Pinchflat.MediaSource do
|
||||||
|
|
||||||
defp commit_and_start_indexing(changeset) do
|
defp commit_and_start_indexing(changeset) do
|
||||||
case Repo.insert_or_update(changeset) do
|
case Repo.insert_or_update(changeset) do
|
||||||
{:ok, %Source{} = channel} ->
|
{:ok, %Source{} = source} ->
|
||||||
maybe_run_indexing_task(changeset, channel)
|
maybe_run_indexing_task(changeset, source)
|
||||||
|
|
||||||
{:ok, channel}
|
{:ok, source}
|
||||||
|
|
||||||
err ->
|
err ->
|
||||||
err
|
err
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_run_indexing_task(changeset, channel) do
|
defp maybe_run_indexing_task(changeset, source) do
|
||||||
case changeset.data do
|
case changeset.data do
|
||||||
# If the changeset is new (not persisted), attempt indexing no matter what
|
# If the changeset is new (not persisted), attempt indexing no matter what
|
||||||
%{__meta__: %{state: :built}} ->
|
%{__meta__: %{state: :built}} ->
|
||||||
SourceTasks.kickoff_indexing_task(channel)
|
SourceTasks.kickoff_indexing_task(source)
|
||||||
|
|
||||||
# If the record has been persisted, only attempt indexing if the
|
# If the record has been persisted, only attempt indexing if the
|
||||||
# indexing frequency has been changed
|
# indexing frequency has been changed
|
||||||
%{__meta__: %{state: :loaded}} ->
|
%{__meta__: %{state: :loaded}} ->
|
||||||
if Map.has_key?(changeset.changes, :index_frequency_minutes) do
|
if Map.has_key?(changeset.changes, :index_frequency_minutes) do
|
||||||
SourceTasks.kickoff_indexing_task(channel)
|
SourceTasks.kickoff_indexing_task(source)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ defmodule Pinchflat.Tasks do
|
||||||
def create_task(%Oban.Job{} = job, attached_record) do
|
def create_task(%Oban.Job{} = job, attached_record) do
|
||||||
attached_record_attr =
|
attached_record_attr =
|
||||||
case attached_record do
|
case attached_record do
|
||||||
%Source{} = channel -> %{source_id: channel.id}
|
%Source{} = source -> %{source_id: source.id}
|
||||||
%MediaItem{} = media_item -> %{media_item_id: media_item.id}
|
%MediaItem{} = media_item -> %{media_item_id: media_item.id}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.ChannelTest do
|
||||||
use ExUnit.Case, async: true
|
use ExUnit.Case, async: true
|
||||||
import Mox
|
import Mox
|
||||||
|
|
||||||
alias Pinchflat.MediaClient.ChannelDetails
|
alias Pinchflat.MediaClient.SourceDetails
|
||||||
alias Pinchflat.MediaClient.Backends.YtDlp.Channel
|
alias Pinchflat.MediaClient.Backends.YtDlp.Channel
|
||||||
|
|
||||||
@channel_url "https://www.youtube.com/c/TheUselessTrials"
|
@channel_url "https://www.youtube.com/c/TheUselessTrials"
|
||||||
|
|
@ -10,13 +10,13 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.ChannelTest do
|
||||||
setup :verify_on_exit!
|
setup :verify_on_exit!
|
||||||
|
|
||||||
describe "get_source_details/1" do
|
describe "get_source_details/1" do
|
||||||
test "it returns a %ChannelDetails{} with data on success" do
|
test "it returns a %SourceDetails{} with data on success" do
|
||||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot ->
|
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot ->
|
||||||
{:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"}
|
{:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert {:ok, res} = Channel.get_source_details(@channel_url)
|
assert {:ok, res} = Channel.get_source_details(@channel_url)
|
||||||
assert %ChannelDetails{id: "UCQH2", name: "TheUselessTrials"} = res
|
assert %SourceDetails{id: "UCQH2", name: "TheUselessTrials"} = res
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it passes the expected args to the backend runner" do
|
test "it passes the expected args to the backend runner" do
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
defmodule Pinchflat.MediaClient.ChannelDetailsTest do
|
defmodule Pinchflat.MediaClient.SourceDetailsTest do
|
||||||
use ExUnit.Case, async: true
|
use ExUnit.Case, async: true
|
||||||
import Mox
|
import Mox
|
||||||
|
|
||||||
alias Pinchflat.MediaClient.ChannelDetails
|
alias Pinchflat.MediaClient.SourceDetails
|
||||||
|
|
||||||
@channel_url "https://www.youtube.com/c/TheUselessTrials"
|
@channel_url "https://www.youtube.com/c/TheUselessTrials"
|
||||||
|
|
||||||
|
|
@ -10,8 +10,8 @@ defmodule Pinchflat.MediaClient.ChannelDetailsTest do
|
||||||
|
|
||||||
describe "new/2" do
|
describe "new/2" do
|
||||||
test "it returns a struct with the given values" do
|
test "it returns a struct with the given values" do
|
||||||
assert %ChannelDetails{id: "UCQH2", name: "TheUselessTrials"} =
|
assert %SourceDetails{id: "UCQH2", name: "TheUselessTrials"} =
|
||||||
ChannelDetails.new("UCQH2", "TheUselessTrials")
|
SourceDetails.new("UCQH2", "TheUselessTrials")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ defmodule Pinchflat.MediaClient.ChannelDetailsTest do
|
||||||
{:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"}
|
{:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert {:ok, _} = ChannelDetails.get_source_details(@channel_url)
|
assert {:ok, _} = SourceDetails.get_source_details(@channel_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns a struct composed of the returned data" do
|
test "it returns a struct composed of the returned data" do
|
||||||
|
|
@ -32,8 +32,8 @@ defmodule Pinchflat.MediaClient.ChannelDetailsTest do
|
||||||
{:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"}
|
{:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert {:ok, res} = ChannelDetails.get_source_details(@channel_url)
|
assert {:ok, res} = SourceDetails.get_source_details(@channel_url)
|
||||||
assert %ChannelDetails{id: "UCQH2", name: "TheUselessTrials"} = res
|
assert %SourceDetails{id: "UCQH2", name: "TheUselessTrials"} = res
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -46,7 +46,7 @@ defmodule Pinchflat.MediaClient.ChannelDetailsTest do
|
||||||
{:ok, ""}
|
{:ok, ""}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert {:ok, _} = ChannelDetails.get_video_ids(@channel_url)
|
assert {:ok, _} = SourceDetails.get_video_ids(@channel_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns a list of strings" do
|
test "it returns a list of strings" do
|
||||||
|
|
@ -54,7 +54,7 @@ defmodule Pinchflat.MediaClient.ChannelDetailsTest do
|
||||||
{:ok, "video1\nvideo2\nvideo3"}
|
{:ok, "video1\nvideo2\nvideo3"}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert {:ok, ["video1", "video2", "video3"]} = ChannelDetails.get_video_ids(@channel_url)
|
assert {:ok, ["video1", "video2", "video3"]} = SourceDetails.get_video_ids(@channel_url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -301,7 +301,7 @@ defmodule Pinchflat.MediaSourceTest do
|
||||||
})
|
})
|
||||||
|
|
||||||
assert %Ecto.Changeset{} = changeset
|
assert %Ecto.Changeset{} = changeset
|
||||||
assert errors_on(changeset).original_url == ["could not fetch channel details from URL"]
|
assert errors_on(changeset).original_url == ["could not fetch source details from URL"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue