Compare commits

...

5 Commits

Author SHA1 Message Date
Kieran Eglin 0fbf810cb6 bumped version 2025-03-06 14:41:36 -08:00
Kieran a97bb248e2 [Enhancement] Retry a download using cookies if doing so might help (#641)
* Sources that use cookies when_needed now retry downloads if we think it'd help

* tweaked error message we're checking on to match media_download_worker
2025-03-05 16:41:07 -08:00
Kieran ac895944a8 [Enhancement] Add option for a source to only use cookies when needed (#640)
* Updated model with new attribute

* Update app logic to use new cookie logic

* lots of tests

* Updated UI and renamed attribute

* Updated tests
2025-03-05 15:32:15 -08:00
Kieran Eglin 59f8aa69cd updating yt-dlp permissions, again 2025-03-04 11:08:02 -08:00
Kieran b790e05133 Testing yt-dlp binary permissions (#634) 2025-03-04 10:53:40 -08:00
19 changed files with 367 additions and 73 deletions
+1 -1
View File
@@ -118,7 +118,7 @@ WORKDIR "/app"
# Set up data volumes # Set up data volumes
RUN mkdir -p /config /downloads /etc/elixir_tzdata_data /etc/yt-dlp/plugins && \ RUN mkdir -p /config /downloads /etc/elixir_tzdata_data /etc/yt-dlp/plugins && \
chmod ugo+rw /etc/elixir_tzdata_data /etc/yt-dlp /etc/yt-dlp/plugins chmod ugo+rw /etc/elixir_tzdata_data /etc/yt-dlp /etc/yt-dlp/plugins /usr/local/bin /usr/local/bin/yt-dlp
# set runner ENV # set runner ENV
ENV MIX_ENV="prod" ENV MIX_ENV="prod"
+53 -6
View File
@@ -9,6 +9,7 @@ defmodule Pinchflat.Downloading.MediaDownloader do
alias Pinchflat.Repo alias Pinchflat.Repo
alias Pinchflat.Media alias Pinchflat.Media
alias Pinchflat.Sources
alias Pinchflat.Media.MediaItem alias Pinchflat.Media.MediaItem
alias Pinchflat.Utils.StringUtils alias Pinchflat.Utils.StringUtils
alias Pinchflat.Metadata.NfoBuilder alias Pinchflat.Metadata.NfoBuilder
@@ -51,6 +52,8 @@ defmodule Pinchflat.Downloading.MediaDownloader do
# Looks complicated, but here's the key points: # Looks complicated, but here's the key points:
# - download_with_options runs a pre-check to see if the media item is suitable for download. # - download_with_options runs a pre-check to see if the media item is suitable for download.
# - If the media item fails the precheck, it returns {:error, :unsuitable_for_download, message} # - If the media item fails the precheck, it returns {:error, :unsuitable_for_download, message}
# - However, if the precheck fails in a way that we think can be fixed by using cookies, we retry with cookies
# and return the result of that
# - If the precheck passes but the download fails, it normally returns {:error, :download_failed, message} # - If the precheck passes but the download fails, it normally returns {:error, :download_failed, message}
# - However, there are some errors we can recover from (eg: failure to communicate with SponsorBlock). # - However, there are some errors we can recover from (eg: failure to communicate with SponsorBlock).
# In this case, we attempt the download anyway and update the media item with what details we do have. # In this case, we attempt the download anyway and update the media item with what details we do have.
@@ -67,6 +70,8 @@ defmodule Pinchflat.Downloading.MediaDownloader do
# - `:unrecoverable` if there was an initial failure and the recovery attempt failed # - `:unrecoverable` if there was an initial failure and the recovery attempt failed
# - `:download_failed` for all other yt-dlp-related downloading errors # - `:download_failed` for all other yt-dlp-related downloading errors
# - `:unknown` for any other errors, including those not related to yt-dlp # - `:unknown` for any other errors, including those not related to yt-dlp
# - If we retry using cookies, all of the above return values apply. The cookie retry
# logic is handled transparently as far as the caller is concerned
defp attempt_download_and_update_for_media_item(media_item, override_opts) do defp attempt_download_and_update_for_media_item(media_item, override_opts) do
output_filepath = FilesystemUtils.generate_metadata_tmpfile(:json) output_filepath = FilesystemUtils.generate_metadata_tmpfile(:json)
media_with_preloads = Repo.preload(media_item, [:metadata, source: :media_profile]) media_with_preloads = Repo.preload(media_item, [:metadata, source: :media_profile])
@@ -151,13 +156,48 @@ defmodule Pinchflat.Downloading.MediaDownloader do
defp download_with_options(url, item_with_preloads, output_filepath, override_opts) do defp download_with_options(url, item_with_preloads, output_filepath, override_opts) do
{:ok, options} = DownloadOptionBuilder.build(item_with_preloads, override_opts) {:ok, options} = DownloadOptionBuilder.build(item_with_preloads, override_opts)
use_cookies = item_with_preloads.source.use_cookies force_use_cookies = Keyword.get(override_opts, :force_use_cookies, false)
runner_opts = [output_filepath: output_filepath, use_cookies: use_cookies] source_uses_cookies = Sources.use_cookies?(item_with_preloads.source, :downloading)
should_use_cookies = force_use_cookies || source_uses_cookies
case YtDlpMedia.get_downloadable_status(url, use_cookies: use_cookies) do runner_opts = [output_filepath: output_filepath, use_cookies: should_use_cookies]
{:ok, :downloadable} -> YtDlpMedia.download(url, options, runner_opts)
{:ok, :ignorable} -> {:error, :unsuitable_for_download} case {YtDlpMedia.get_downloadable_status(url, use_cookies: should_use_cookies), should_use_cookies} do
err -> err {{:ok, :downloadable}, _} ->
YtDlpMedia.download(url, options, runner_opts)
{{:ok, :ignorable}, _} ->
{:error, :unsuitable_for_download}
{{:error, _message, _exit_code} = err, false} ->
# If there was an error and we don't have cookies, this method will retry with cookies
# if doing so would help AND the source allows. Otherwise, it will return the error as-is
maybe_retry_with_cookies(url, item_with_preloads, output_filepath, override_opts, err)
# This gets hit if cookies are enabled which, importantly, also covers the case where we
# retry a download with cookies and it fails again
{{:error, message, exit_code}, true} ->
{:error, message, exit_code}
{err, _} ->
err
end
end
defp maybe_retry_with_cookies(url, item_with_preloads, output_filepath, override_opts, err) do
{:error, message, _} = err
source = item_with_preloads.source
message_contains_cookie_error = String.contains?(to_string(message), recoverable_cookie_errors())
if Sources.use_cookies?(source, :error_recovery) && message_contains_cookie_error do
download_with_options(
url,
item_with_preloads,
output_filepath,
Keyword.put(override_opts, :force_use_cookies, true)
)
else
err
end end
end end
@@ -166,4 +206,11 @@ defmodule Pinchflat.Downloading.MediaDownloader do
"Unable to communicate with SponsorBlock" "Unable to communicate with SponsorBlock"
] ]
end end
defp recoverable_cookie_errors do
[
"Sign in to confirm",
"This video is available to this channel's members"
]
end
end end
@@ -12,6 +12,7 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpers do
alias Pinchflat.Repo alias Pinchflat.Repo
alias Pinchflat.Media alias Pinchflat.Media
alias Pinchflat.Tasks alias Pinchflat.Tasks
alias Pinchflat.Sources
alias Pinchflat.Sources.Source alias Pinchflat.Sources.Source
alias Pinchflat.FastIndexing.YoutubeRss alias Pinchflat.FastIndexing.YoutubeRss
alias Pinchflat.FastIndexing.YoutubeApi alias Pinchflat.FastIndexing.YoutubeApi
@@ -88,12 +89,16 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpers do
defp create_media_item_from_media_id(source, media_id) do defp create_media_item_from_media_id(source, media_id) do
url = "https://www.youtube.com/watch?v=#{media_id}" url = "https://www.youtube.com/watch?v=#{media_id}"
# This is set to :metadata instead of :indexing since this happens _after_ the
# actual indexing process. In reality, slow indexing is the only thing that
# should be using :indexing.
should_use_cookies = Sources.use_cookies?(source, :metadata)
command_opts = command_opts =
[output: DownloadOptionBuilder.build_output_path_for(source)] ++ [output: DownloadOptionBuilder.build_output_path_for(source)] ++
DownloadOptionBuilder.build_quality_options_for(source) DownloadOptionBuilder.build_quality_options_for(source)
case YtDlpMedia.get_media_attributes(url, command_opts, use_cookies: source.use_cookies) do case YtDlpMedia.get_media_attributes(url, command_opts, use_cookies: should_use_cookies) do
{:ok, media_attrs} -> {:ok, media_attrs} ->
Media.create_media_item_from_backend_attrs(source, media_attrs) Media.create_media_item_from_backend_attrs(source, media_attrs)
@@ -9,6 +9,7 @@ defmodule Pinchflat.Metadata.MetadataFileHelpers do
needed needed
""" """
alias Pinchflat.Sources
alias Pinchflat.Utils.FilesystemUtils alias Pinchflat.Utils.FilesystemUtils
alias Pinchflat.YtDlp.Media, as: YtDlpMedia alias Pinchflat.YtDlp.Media, as: YtDlpMedia
@@ -66,7 +67,7 @@ defmodule Pinchflat.Metadata.MetadataFileHelpers do
yt_dlp_filepath = generate_filepath_for(media_item_with_preloads, "thumbnail.%(ext)s") yt_dlp_filepath = generate_filepath_for(media_item_with_preloads, "thumbnail.%(ext)s")
real_filepath = generate_filepath_for(media_item_with_preloads, "thumbnail.jpg") real_filepath = generate_filepath_for(media_item_with_preloads, "thumbnail.jpg")
command_opts = [output: yt_dlp_filepath] command_opts = [output: yt_dlp_filepath]
addl_opts = [use_cookies: media_item_with_preloads.source.use_cookies] addl_opts = [use_cookies: Sources.use_cookies?(media_item_with_preloads.source, :metadata)]
case YtDlpMedia.download_thumbnail(media_item_with_preloads.original_url, command_opts, addl_opts) do case YtDlpMedia.download_thumbnail(media_item_with_preloads.original_url, command_opts, addl_opts) do
{:ok, _} -> real_filepath {:ok, _} -> real_filepath
@@ -93,7 +93,7 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
defp determine_series_directory(source) do defp determine_series_directory(source) do
output_path = DownloadOptionBuilder.build_output_path_for(source) output_path = DownloadOptionBuilder.build_output_path_for(source)
runner_opts = [output: output_path] runner_opts = [output: output_path]
addl_opts = [use_cookies: source.use_cookies] addl_opts = [use_cookies: Sources.use_cookies?(source, :metadata)]
{:ok, %{filepath: filepath}} = MediaCollection.get_source_details(source.original_url, runner_opts, addl_opts) {:ok, %{filepath: filepath}} = MediaCollection.get_source_details(source.original_url, runner_opts, addl_opts)
case MetadataFileHelpers.series_directory_from_media_filepath(filepath) do case MetadataFileHelpers.series_directory_from_media_filepath(filepath) do
@@ -113,6 +113,7 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
defp fetch_metadata_for_source(source) do defp fetch_metadata_for_source(source) do
tmp_output_path = "#{tmp_directory()}/#{StringUtils.random_string(16)}/source_image.%(ext)S" tmp_output_path = "#{tmp_directory()}/#{StringUtils.random_string(16)}/source_image.%(ext)S"
base_opts = [convert_thumbnails: "jpg", output: tmp_output_path] base_opts = [convert_thumbnails: "jpg", output: tmp_output_path]
should_use_cookies = Sources.use_cookies?(source, :metadata)
opts = opts =
if source.collection_type == :channel do if source.collection_type == :channel do
@@ -121,7 +122,7 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
base_opts ++ [:write_thumbnail, playlist_items: 1] base_opts ++ [:write_thumbnail, playlist_items: 1]
end end
MediaCollection.get_source_metadata(source.original_url, opts, use_cookies: source.use_cookies) MediaCollection.get_source_metadata(source.original_url, opts, use_cookies: should_use_cookies)
end end
defp tmp_directory do defp tmp_directory do
@@ -132,13 +132,14 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpers do
{:ok, pid} = FileFollowerServer.start_link() {:ok, pid} = FileFollowerServer.start_link()
handler = fn filepath -> setup_file_follower_watcher(pid, filepath, source) end handler = fn filepath -> setup_file_follower_watcher(pid, filepath, source) end
should_use_cookies = Sources.use_cookies?(source, :indexing)
command_opts = command_opts =
[output: DownloadOptionBuilder.build_output_path_for(source)] ++ [output: DownloadOptionBuilder.build_output_path_for(source)] ++
DownloadOptionBuilder.build_quality_options_for(source) ++ DownloadOptionBuilder.build_quality_options_for(source) ++
build_download_archive_options(source, was_forced) build_download_archive_options(source, was_forced)
runner_opts = [file_listener_handler: handler, use_cookies: source.use_cookies] runner_opts = [file_listener_handler: handler, use_cookies: should_use_cookies]
result = MediaCollection.get_media_attributes_for_collection(source.original_url, command_opts, runner_opts) result = MediaCollection.get_media_attributes_for_collection(source.original_url, command_opts, runner_opts)
FileFollowerServer.stop(pid) FileFollowerServer.stop(pid)
+2 -2
View File
@@ -28,7 +28,7 @@ defmodule Pinchflat.Sources.Source do
series_directory series_directory
index_frequency_minutes index_frequency_minutes
fast_index fast_index
use_cookies cookie_behaviour
download_media download_media
last_indexed_at last_indexed_at
original_url original_url
@@ -78,7 +78,7 @@ defmodule Pinchflat.Sources.Source do
field :collection_type, Ecto.Enum, values: [:channel, :playlist] field :collection_type, Ecto.Enum, values: [:channel, :playlist]
field :index_frequency_minutes, :integer, default: 60 * 24 field :index_frequency_minutes, :integer, default: 60 * 24
field :fast_index, :boolean, default: false field :fast_index, :boolean, default: false
field :use_cookies, :boolean, default: false field :cookie_behaviour, Ecto.Enum, values: [:disabled, :when_needed, :all_operations], default: :disabled
field :download_media, :boolean, default: true field :download_media, :boolean, default: true
field :last_indexed_at, :utc_datetime field :last_indexed_at, :utc_datetime
# Only download media items that were published after this date # Only download media items that were published after this date
+15 -2
View File
@@ -32,6 +32,19 @@ defmodule Pinchflat.Sources do
source.output_path_template_override || media_profile.output_path_template source.output_path_template_override || media_profile.output_path_template
end end
@doc """
Returns a boolean indicating whether or not cookies should be used for a given operation.
Returns boolean()
"""
def use_cookies?(source, operation) when operation in [:indexing, :downloading, :metadata, :error_recovery] do
case source.cookie_behaviour do
:disabled -> false
:all_operations -> true
:when_needed -> operation in [:indexing, :error_recovery]
end
end
@doc """ @doc """
Returns the list of sources. Returns [%Source{}, ...] Returns the list of sources. Returns [%Source{}, ...]
""" """
@@ -181,9 +194,9 @@ defmodule Pinchflat.Sources do
defp add_source_details_to_changeset(source, changeset) do defp add_source_details_to_changeset(source, changeset) do
original_url = changeset.changes.original_url original_url = changeset.changes.original_url
use_cookies = Ecto.Changeset.get_field(changeset, :use_cookies) should_use_cookies = Ecto.Changeset.get_field(changeset, :cookie_behaviour) == :all_operations
# Skipping sleep interval since this is UI blocking and we want to keep this as fast as possible # Skipping sleep interval since this is UI blocking and we want to keep this as fast as possible
addl_opts = [use_cookies: use_cookies, skip_sleep_interval: true] addl_opts = [use_cookies: should_use_cookies, skip_sleep_interval: true]
case MediaCollection.get_source_details(original_url, [], addl_opts) do case MediaCollection.get_source_details(original_url, [], addl_opts) do
{:ok, source_details} -> {:ok, source_details} ->
@@ -27,6 +27,14 @@ defmodule PinchflatWeb.Sources.SourceHTML do
] ]
end end
def friendly_cookie_behaviours do
[
{"Disabled", :disabled},
{"When Needed", :when_needed},
{"All Operations", :all_operations}
]
end
def cutoff_date_presets do def cutoff_date_presets do
[ [
{"7 days", compute_date_offset(7)}, {"7 days", compute_date_offset(7)},
@@ -87,10 +87,11 @@
/> />
<.input <.input
field={f[:use_cookies]} field={f[:cookie_behaviour]}
type="toggle" options={friendly_cookie_behaviours()}
label="Use Cookies for Downloading" type="select"
help="Uses your YouTube cookies for this source (if configured). Used for downloading private playlists and videos. See docs for important details" label="Cookie Behaviour"
help="Uses your YouTube cookies for this source (if configured). 'When Needed' tries to minimize cookie usage except for certain indexing and downloading tasks. See docs"
/> />
<section x-show="advancedMode"> <section x-show="advancedMode">
+1 -1
View File
@@ -4,7 +4,7 @@ defmodule Pinchflat.MixProject do
def project do def project do
[ [
app: :pinchflat, app: :pinchflat,
version: "2025.2.20", version: "2025.3.6",
elixir: "~> 1.17", elixir: "~> 1.17",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod, start_permanent: Mix.env() == :prod,
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 497 KiB

After

Width:  |  Height:  |  Size: 497 KiB

@@ -0,0 +1,18 @@
defmodule Pinchflat.Repo.Migrations.AddCookieBehaviourToSources do
use Ecto.Migration
def change do
alter table(:sources) do
add :cookie_behaviour, :string, null: false, default: "disabled"
end
execute(
"UPDATE sources SET cookie_behaviour = 'all_operations' WHERE use_cookies = TRUE",
"UPDATE sources SET use_cookies = TRUE WHERE cookie_behaviour = 'all_operations'"
)
alter table(:sources) do
remove :use_cookies, :boolean, null: false, default: false
end
end
end
@@ -157,15 +157,16 @@ defmodule Pinchflat.Downloading.MediaDownloaderTest do
{:ok, ""} {:ok, ""}
end) end)
source = source_fixture(%{use_cookies: true}) source = source_fixture(%{cookie_behaviour: :all_operations})
media_item = media_item_fixture(%{source_id: source.id}) media_item = media_item_fixture(%{source_id: source.id})
assert {:ok, _} = MediaDownloader.download_for_media_item(media_item) assert {:ok, _} = MediaDownloader.download_for_media_item(media_item)
end end
test "does not set use_cookies if the source does not use cookies" do test "does not set use_cookies if the source uses cookies when needed" do
expect(YtDlpRunnerMock, :run, 3, fn expect(YtDlpRunnerMock, :run, 3, fn
_url, :get_downloadable_status, _opts, _ot, _addl -> _url, :get_downloadable_status, _opts, _ot, addl ->
assert {:use_cookies, false} in addl
{:ok, "{}"} {:ok, "{}"}
_url, :download, _opts, _ot, addl -> _url, :download, _opts, _ot, addl ->
@@ -176,14 +177,34 @@ defmodule Pinchflat.Downloading.MediaDownloaderTest do
{:ok, ""} {:ok, ""}
end) end)
source = source_fixture(%{use_cookies: false}) source = source_fixture(%{cookie_behaviour: :when_needed})
media_item = media_item_fixture(%{source_id: source.id})
assert {:ok, _} = MediaDownloader.download_for_media_item(media_item)
end
test "does not set use_cookies if the source does not use cookies" do
expect(YtDlpRunnerMock, :run, 3, fn
_url, :get_downloadable_status, _opts, _ot, addl ->
assert {:use_cookies, false} in addl
{:ok, "{}"}
_url, :download, _opts, _ot, addl ->
assert {:use_cookies, false} in addl
{:ok, render_metadata(:media_metadata)}
_url, :download_thumbnail, _opts, _ot, _addl ->
{:ok, ""}
end)
source = source_fixture(%{cookie_behaviour: :disabled})
media_item = media_item_fixture(%{source_id: source.id}) media_item = media_item_fixture(%{source_id: source.id})
assert {:ok, _} = MediaDownloader.download_for_media_item(media_item) assert {:ok, _} = MediaDownloader.download_for_media_item(media_item)
end end
end end
describe "download_for_media_item/3 when testing retries" do describe "download_for_media_item/3 when testing non-cookie retries" do
test "returns a recovered tuple on recoverable errors", %{media_item: media_item} do test "returns a recovered tuple on recoverable errors", %{media_item: media_item} do
message = "Unable to communicate with SponsorBlock" message = "Unable to communicate with SponsorBlock"
@@ -277,6 +298,68 @@ defmodule Pinchflat.Downloading.MediaDownloaderTest do
end end
end end
describe "download_for_media_item/3 when testing cookie retries" do
test "retries with cookies if we think it would help and the source allows" do
expect(YtDlpRunnerMock, :run, 4, fn
_url, :get_downloadable_status, _opts, _ot, [use_cookies: false] ->
{:error, "Sign in to confirm your age", 1}
_url, :get_downloadable_status, _opts, _ot, [use_cookies: true] ->
{:ok, "{}"}
_url, :download, _opts, _ot, addl ->
assert {:use_cookies, true} in addl
{:ok, render_metadata(:media_metadata)}
_url, :download_thumbnail, _opts, _ot, _addl ->
{:ok, ""}
end)
source = source_fixture(%{cookie_behaviour: :when_needed})
media_item = media_item_fixture(%{source_id: source.id})
assert {:ok, _} = MediaDownloader.download_for_media_item(media_item)
end
test "does not retry with cookies if we don't think it would help even the source allows" do
expect(YtDlpRunnerMock, :run, 1, fn
_url, :get_downloadable_status, _opts, _ot, [use_cookies: false] ->
{:error, "Some other error", 1}
end)
source = source_fixture(%{cookie_behaviour: :when_needed})
media_item = media_item_fixture(%{source_id: source.id})
assert {:error, :download_failed, "Some other error"} = MediaDownloader.download_for_media_item(media_item)
end
test "does not retry with cookies even if we think it would help but source doesn't allow" do
expect(YtDlpRunnerMock, :run, 1, fn
_url, :get_downloadable_status, _opts, _ot, [use_cookies: false] ->
{:error, "Sign in to confirm your age", 1}
end)
source = source_fixture(%{cookie_behaviour: :disabled})
media_item = media_item_fixture(%{source_id: source.id})
assert {:error, :download_failed, "Sign in to confirm your age"} =
MediaDownloader.download_for_media_item(media_item)
end
test "does not retry with cookies if cookies were already used" do
expect(YtDlpRunnerMock, :run, 1, fn
_url, :get_downloadable_status, _opts, _ot, [use_cookies: true] ->
{:error, "This video is available to this channel's members", 1}
end)
source = source_fixture(%{cookie_behaviour: :all_operations})
media_item = media_item_fixture(%{source_id: source.id})
assert {:error, :download_failed, "This video is available to this channel's members"} =
MediaDownloader.download_for_media_item(media_item)
end
end
describe "download_for_media_item/3 when testing media_item attributes" do describe "download_for_media_item/3 when testing media_item attributes" do
setup do setup do
stub(YtDlpRunnerMock, :run, fn stub(YtDlpRunnerMock, :run, fn
@@ -104,34 +104,6 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpersTest do
FastIndexingHelpers.index_and_kickoff_downloads(source) FastIndexingHelpers.index_and_kickoff_downloads(source)
end end
test "sets use_cookies if the source uses cookies" do
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
stub(YtDlpRunnerMock, :run, fn _url, :get_media_attributes, _opts, _ot, addl ->
assert {:use_cookies, true} in addl
{:ok, media_attributes_return_fixture()}
end)
source = source_fixture(%{use_cookies: true})
assert [%MediaItem{}] = FastIndexingHelpers.index_and_kickoff_downloads(source)
end
test "does not set use_cookies if the source does not use cookies" do
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
stub(YtDlpRunnerMock, :run, fn _url, :get_media_attributes, _opts, _ot, addl ->
assert {:use_cookies, false} in addl
{:ok, media_attributes_return_fixture()}
end)
source = source_fixture(%{use_cookies: false})
assert [%MediaItem{}] = FastIndexingHelpers.index_and_kickoff_downloads(source)
end
test "does not enqueue a download job if the media item does not match the format rules" do test "does not enqueue a download job if the media item does not match the format rules" do
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end) expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
@@ -180,6 +152,50 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpersTest do
end end
end end
describe "index_and_kickoff_downloads/1 when testing cookies" do
test "sets use_cookies if the source uses cookies" do
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
stub(YtDlpRunnerMock, :run, fn _url, :get_media_attributes, _opts, _ot, addl ->
assert {:use_cookies, true} in addl
{:ok, media_attributes_return_fixture()}
end)
source = source_fixture(%{cookie_behaviour: :all_operations})
assert [%MediaItem{}] = FastIndexingHelpers.index_and_kickoff_downloads(source)
end
test "does not set use_cookies if the source uses cookies when needed" do
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
stub(YtDlpRunnerMock, :run, fn _url, :get_media_attributes, _opts, _ot, addl ->
assert {:use_cookies, false} in addl
{:ok, media_attributes_return_fixture()}
end)
source = source_fixture(%{cookie_behaviour: :when_needed})
assert [%MediaItem{}] = FastIndexingHelpers.index_and_kickoff_downloads(source)
end
test "does not set use_cookies if the source does not use cookies" do
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
stub(YtDlpRunnerMock, :run, fn _url, :get_media_attributes, _opts, _ot, addl ->
assert {:use_cookies, false} in addl
{:ok, media_attributes_return_fixture()}
end)
source = source_fixture(%{cookie_behaviour: :disabled})
assert [%MediaItem{}] = FastIndexingHelpers.index_and_kickoff_downloads(source)
end
end
describe "index_and_kickoff_downloads/1 when testing backends" do describe "index_and_kickoff_downloads/1 when testing backends" do
test "uses the YouTube API if it is enabled", %{source: source} do test "uses the YouTube API if it is enabled", %{source: source} do
expect(HTTPClientMock, :get, fn url, _headers -> expect(HTTPClientMock, :get, fn url, _headers ->
@@ -88,13 +88,35 @@ defmodule Pinchflat.Metadata.MetadataFileHelpersTest do
Helpers.download_and_store_thumbnail_for(media_item) Helpers.download_and_store_thumbnail_for(media_item)
end end
test "returns nil if yt-dlp fails", %{media_item: media_item} do
stub(YtDlpRunnerMock, :run, fn _url, :download_thumbnail, _opts, _ot, _addl -> {:error, "error"} end)
filepath = Helpers.download_and_store_thumbnail_for(media_item)
assert filepath == nil
end
end
describe "download_and_store_thumbnail_for/2 when testing cookie usage" do
test "sets use_cookies if the source uses cookies" do test "sets use_cookies if the source uses cookies" do
expect(YtDlpRunnerMock, :run, fn _url, :download_thumbnail, _opts, _ot, addl -> expect(YtDlpRunnerMock, :run, fn _url, :download_thumbnail, _opts, _ot, addl ->
assert {:use_cookies, true} in addl assert {:use_cookies, true} in addl
{:ok, ""} {:ok, ""}
end) end)
source = source_fixture(%{use_cookies: true}) source = source_fixture(%{cookie_behaviour: :all_operations})
media_item = Repo.preload(media_item_fixture(%{source_id: source.id}), :source)
Helpers.download_and_store_thumbnail_for(media_item)
end
test "does not set use_cookies if the source uses cookies when needed" do
expect(YtDlpRunnerMock, :run, fn _url, :download_thumbnail, _opts, _ot, addl ->
assert {:use_cookies, false} in addl
{:ok, ""}
end)
source = source_fixture(%{cookie_behaviour: :when_needed})
media_item = Repo.preload(media_item_fixture(%{source_id: source.id}), :source) media_item = Repo.preload(media_item_fixture(%{source_id: source.id}), :source)
Helpers.download_and_store_thumbnail_for(media_item) Helpers.download_and_store_thumbnail_for(media_item)
@@ -106,19 +128,11 @@ defmodule Pinchflat.Metadata.MetadataFileHelpersTest do
{:ok, ""} {:ok, ""}
end) end)
source = source_fixture(%{use_cookies: false}) source = source_fixture(%{cookie_behaviour: :disabled})
media_item = Repo.preload(media_item_fixture(%{source_id: source.id}), :source) media_item = Repo.preload(media_item_fixture(%{source_id: source.id}), :source)
Helpers.download_and_store_thumbnail_for(media_item) Helpers.download_and_store_thumbnail_for(media_item)
end end
test "returns nil if yt-dlp fails", %{media_item: media_item} do
stub(YtDlpRunnerMock, :run, fn _url, :download_thumbnail, _opts, _ot, _addl -> {:error, "error"} end)
filepath = Helpers.download_and_store_thumbnail_for(media_item)
assert filepath == nil
end
end end
describe "parse_upload_date/1" do describe "parse_upload_date/1" do
@@ -254,7 +254,23 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorkerTest do
end) end)
profile = media_profile_fixture(%{download_source_images: true}) profile = media_profile_fixture(%{download_source_images: true})
source = source_fixture(media_profile_id: profile.id, use_cookies: true) source = source_fixture(media_profile_id: profile.id, cookie_behaviour: :all_operations)
perform_job(SourceMetadataStorageWorker, %{id: source.id})
end
test "does not set use_cookies if the source uses cookies when needed" do
expect(YtDlpRunnerMock, :run, 2, fn
_url, :get_source_details, _opts, _ot, _addl ->
{:ok, source_details_return_fixture()}
_url, :get_source_metadata, _opts, _ot, addl ->
assert {:use_cookies, false} in addl
{:ok, render_metadata(:channel_source_metadata)}
end)
profile = media_profile_fixture(%{download_source_images: true})
source = source_fixture(media_profile_id: profile.id, cookie_behaviour: :when_needed)
perform_job(SourceMetadataStorageWorker, %{id: source.id}) perform_job(SourceMetadataStorageWorker, %{id: source.id})
end end
@@ -270,7 +286,7 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorkerTest do
end) end)
profile = media_profile_fixture(%{download_source_images: true}) profile = media_profile_fixture(%{download_source_images: true})
source = source_fixture(media_profile_id: profile.id, use_cookies: false) source = source_fixture(media_profile_id: profile.id, cookie_behaviour: :disabled)
perform_job(SourceMetadataStorageWorker, %{id: source.id}) perform_job(SourceMetadataStorageWorker, %{id: source.id})
end end
@@ -323,7 +339,21 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorkerTest do
{:ok, "{}"} {:ok, "{}"}
end) end)
source = source_fixture(%{series_directory: nil, use_cookies: true}) source = source_fixture(%{series_directory: nil, cookie_behaviour: :all_operations})
perform_job(SourceMetadataStorageWorker, %{id: source.id})
end
test "does not set use_cookies if the source uses cookies when needed" do
expect(YtDlpRunnerMock, :run, 2, fn
_url, :get_source_details, _opts, _ot, addl ->
assert {:use_cookies, false} in addl
{:ok, source_details_return_fixture()}
_url, :get_source_metadata, _opts, _ot, _addl ->
{:ok, "{}"}
end)
source = source_fixture(%{series_directory: nil, cookie_behaviour: :when_needed})
perform_job(SourceMetadataStorageWorker, %{id: source.id}) perform_job(SourceMetadataStorageWorker, %{id: source.id})
end end
@@ -337,7 +367,7 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorkerTest do
{:ok, "{}"} {:ok, "{}"}
end) end)
source = source_fixture(%{series_directory: nil, use_cookies: false}) source = source_fixture(%{series_directory: nil, cookie_behaviour: :disabled})
perform_job(SourceMetadataStorageWorker, %{id: source.id}) perform_job(SourceMetadataStorageWorker, %{id: source.id})
end end
end end
@@ -302,14 +302,27 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpersTest do
SlowIndexingHelpers.index_and_enqueue_download_for_media_items(source) SlowIndexingHelpers.index_and_enqueue_download_for_media_items(source)
end end
end
describe "index_and_enqueue_download_for_media_items/2 when testing cookies" do
test "sets use_cookies if the source uses cookies" do test "sets use_cookies if the source uses cookies" do
expect(YtDlpRunnerMock, :run, fn _url, :get_media_attributes_for_collection, _opts, _ot, addl_opts -> expect(YtDlpRunnerMock, :run, fn _url, :get_media_attributes_for_collection, _opts, _ot, addl_opts ->
assert {:use_cookies, true} in addl_opts assert {:use_cookies, true} in addl_opts
{:ok, source_attributes_return_fixture()} {:ok, source_attributes_return_fixture()}
end) end)
source = source_fixture(%{use_cookies: true}) source = source_fixture(%{cookie_behaviour: :all_operations})
SlowIndexingHelpers.index_and_enqueue_download_for_media_items(source)
end
test "sets use_cookies if the source uses cookies when needed" do
expect(YtDlpRunnerMock, :run, fn _url, :get_media_attributes_for_collection, _opts, _ot, addl_opts ->
assert {:use_cookies, true} in addl_opts
{:ok, source_attributes_return_fixture()}
end)
source = source_fixture(%{cookie_behaviour: :when_needed})
SlowIndexingHelpers.index_and_enqueue_download_for_media_items(source) SlowIndexingHelpers.index_and_enqueue_download_for_media_items(source)
end end
@@ -320,7 +333,7 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpersTest do
{:ok, source_attributes_return_fixture()} {:ok, source_attributes_return_fixture()}
end) end)
source = source_fixture(%{use_cookies: false}) source = source_fixture(%{cookie_behaviour: :disabled})
SlowIndexingHelpers.index_and_enqueue_download_for_media_items(source) SlowIndexingHelpers.index_and_enqueue_download_for_media_items(source)
end end
+46 -3
View File
@@ -60,6 +60,33 @@ defmodule Pinchflat.SourcesTest do
end end
end end
describe "use_cookies?/2" do
test "returns true if the source has been set to use cookies" do
source = source_fixture(%{cookie_behaviour: :all_operations})
assert Sources.use_cookies?(source, :downloading)
end
test "returns false if the source has not been set to use cookies" do
source = source_fixture(%{cookie_behaviour: :disabled})
refute Sources.use_cookies?(source, :downloading)
end
test "returns true if the action is indexing and the source is set to :when_needed" do
source = source_fixture(%{cookie_behaviour: :when_needed})
assert Sources.use_cookies?(source, :indexing)
end
test "returns false if the action is downloading and the source is set to :when_needed" do
source = source_fixture(%{cookie_behaviour: :when_needed})
refute Sources.use_cookies?(source, :downloading)
end
test "returns true if the action is error_recovery and the source is set to :when_needed" do
source = source_fixture(%{cookie_behaviour: :when_needed})
assert Sources.use_cookies?(source, :error_recovery)
end
end
describe "list_sources/0" do describe "list_sources/0" do
test "it returns all sources" do test "it returns all sources" do
source = source_fixture() source = source_fixture()
@@ -393,13 +420,13 @@ defmodule Pinchflat.SourcesTest do
valid_attrs = %{ valid_attrs = %{
media_profile_id: media_profile_fixture().id, media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123", original_url: "https://www.youtube.com/channel/abc123",
use_cookies: true cookie_behaviour: :all_operations
} }
assert {:ok, %Source{}} = Sources.create_source(valid_attrs) assert {:ok, %Source{}} = Sources.create_source(valid_attrs)
end end
test "does not set use_cookies to false if the source has not been set to use cookies" do test "does not set use_cookies if the source uses cookies when needed" do
expect(YtDlpRunnerMock, :run, fn _url, :get_source_details, _opts, _ot, addl -> expect(YtDlpRunnerMock, :run, fn _url, :get_source_details, _opts, _ot, addl ->
refute Keyword.get(addl, :use_cookies) refute Keyword.get(addl, :use_cookies)
@@ -409,7 +436,23 @@ defmodule Pinchflat.SourcesTest do
valid_attrs = %{ valid_attrs = %{
media_profile_id: media_profile_fixture().id, media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123", original_url: "https://www.youtube.com/channel/abc123",
use_cookies: false cookie_behaviour: :when_needed
}
assert {:ok, %Source{}} = Sources.create_source(valid_attrs)
end
test "does not set use_cookies if the source has not been set to use cookies" do
expect(YtDlpRunnerMock, :run, fn _url, :get_source_details, _opts, _ot, addl ->
refute Keyword.get(addl, :use_cookies)
{:ok, playlist_return()}
end)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123",
cookie_behaviour: :disabled
} }
assert {:ok, %Source{}} = Sources.create_source(valid_attrs) assert {:ok, %Source{}} = Sources.create_source(valid_attrs)