Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f6d8e15670 | |||
| 2906ff1b30 | |||
| 0582a7bfd5 | |||
| 3db2e8190f |
@@ -1,4 +1,20 @@
|
|||||||
# Pinchflat (Alpha)
|
<p align="center">
|
||||||
|
<img
|
||||||
|
src="priv/static/images/originals/logo-white-wordmark-with-background.png"
|
||||||
|
alt="Pinchflat Logo by @hernandito"
|
||||||
|
width="700"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<sup>
|
||||||
|
<em>logo by <a href="https://github.com/hernandito" target="_blank">@hernandito</a></em>
|
||||||
|
</sup>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# Your next YouTube media manager
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
This is alpha software and anything can break at any time. I make not guarantees about the stability of this software, forward-compatibility of updates, or integrity (both related to and independent of Pinchflat). Essentially, use at your own risk and expect there will be rough edges.
|
This is alpha software and anything can break at any time. I make not guarantees about the stability of this software, forward-compatibility of updates, or integrity (both related to and independent of Pinchflat). Essentially, use at your own risk and expect there will be rough edges.
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,21 @@ config :pinchflat,
|
|||||||
basic_auth_username: System.get_env("BASIC_AUTH_USERNAME"),
|
basic_auth_username: System.get_env("BASIC_AUTH_USERNAME"),
|
||||||
basic_auth_password: System.get_env("BASIC_AUTH_PASSWORD")
|
basic_auth_password: System.get_env("BASIC_AUTH_PASSWORD")
|
||||||
|
|
||||||
|
arch_string = to_string(:erlang.system_info(:system_architecture))
|
||||||
|
|
||||||
|
system_arch =
|
||||||
|
cond do
|
||||||
|
String.contains?(arch_string, "arm") -> "arm"
|
||||||
|
String.contains?(arch_string, "aarch") -> "arm"
|
||||||
|
String.contains?(arch_string, "x86") -> "x86"
|
||||||
|
true -> "unknown"
|
||||||
|
end
|
||||||
|
|
||||||
|
config :pinchflat, Pinchflat.Repo,
|
||||||
|
load_extensions: [
|
||||||
|
Path.join([:code.priv_dir(:pinchflat), "repo", "extensions", "sqlean-linux-#{system_arch}", "sqlean"])
|
||||||
|
]
|
||||||
|
|
||||||
if config_env() == :prod do
|
if config_env() == :prod do
|
||||||
config_path =
|
config_path =
|
||||||
System.get_env("CONFIG_PATH") ||
|
System.get_env("CONFIG_PATH") ||
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ defmodule Pinchflat.Media do
|
|||||||
|> where([mi], mi.source_id == ^source.id and is_nil(mi.media_filepath))
|
|> where([mi], mi.source_id == ^source.id and is_nil(mi.media_filepath))
|
||||||
|> where(^build_format_clauses(media_profile))
|
|> where(^build_format_clauses(media_profile))
|
||||||
|> where(^maybe_apply_cutoff_date(source))
|
|> where(^maybe_apply_cutoff_date(source))
|
||||||
|
|> where(^maybe_apply_title_regex(source))
|
||||||
|> Repo.maybe_limit(limit)
|
|> Repo.maybe_limit(limit)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
@@ -247,6 +248,14 @@ defmodule Pinchflat.Media do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_apply_title_regex(source) do
|
||||||
|
if source.title_filter_regex do
|
||||||
|
dynamic([mi], fragment("regexp_like(?, ?)", mi.title, ^source.title_filter_regex))
|
||||||
|
else
|
||||||
|
dynamic(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp build_format_clauses(media_profile) do
|
defp build_format_clauses(media_profile) do
|
||||||
mapped_struct = Map.from_struct(media_profile)
|
mapped_struct = Map.from_struct(media_profile)
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ defmodule Pinchflat.SlowIndexing.FileFollowerServer do
|
|||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@poll_interval_ms Application.compile_env(:pinchflat, :file_watcher_poll_interval)
|
@poll_interval_ms Application.compile_env(:pinchflat, :file_watcher_poll_interval)
|
||||||
@activity_timeout_ms 60_000
|
@activity_timeout_ms 600_000
|
||||||
|
|
||||||
# Client API
|
# Client API
|
||||||
@doc """
|
@doc """
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpers do
|
|||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
alias Pinchflat.Repo
|
||||||
alias Pinchflat.Media
|
alias Pinchflat.Media
|
||||||
alias Pinchflat.Tasks
|
alias Pinchflat.Tasks
|
||||||
alias Pinchflat.Sources
|
alias Pinchflat.Sources
|
||||||
@@ -60,6 +61,9 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpers do
|
|||||||
# See the method definition below for more info on how file watchers work
|
# See the method definition below for more info on how file watchers work
|
||||||
# (important reading if you're not familiar with it)
|
# (important reading if you're not familiar with it)
|
||||||
{:ok, media_attributes} = get_media_attributes_for_collection_and_setup_file_watcher(source)
|
{:ok, media_attributes} = get_media_attributes_for_collection_and_setup_file_watcher(source)
|
||||||
|
# Reload because the source may have been updated during the (long-running) indexing process
|
||||||
|
# and important settings like `download_media` may have changed.
|
||||||
|
source = Repo.reload!(source)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
Enum.map(media_attributes, fn media_attrs ->
|
Enum.map(media_attributes, fn media_attrs ->
|
||||||
@@ -117,6 +121,10 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpers do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp create_media_item_and_enqueue_download(source, media_attrs) do
|
defp create_media_item_and_enqueue_download(source, media_attrs) do
|
||||||
|
# Reload because the source may have been updated during the (long-running) indexing process
|
||||||
|
# and important settings like `download_media` may have changed.
|
||||||
|
source = Repo.reload!(source)
|
||||||
|
|
||||||
case Media.create_media_item_from_backend_attrs(source, media_attrs) do
|
case Media.create_media_item_from_backend_attrs(source, media_attrs) do
|
||||||
{:ok, %MediaItem{} = media_item} ->
|
{:ok, %MediaItem{} = media_item} ->
|
||||||
if source.download_media && Media.pending_download?(media_item) do
|
if source.download_media && Media.pending_download?(media_item) do
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ defmodule Pinchflat.Sources.Source do
|
|||||||
last_indexed_at
|
last_indexed_at
|
||||||
original_url
|
original_url
|
||||||
download_cutoff_date
|
download_cutoff_date
|
||||||
|
title_filter_regex
|
||||||
media_profile_id
|
media_profile_id
|
||||||
)a
|
)a
|
||||||
|
|
||||||
@@ -57,11 +58,6 @@ defmodule Pinchflat.Sources.Source do
|
|||||||
field :collection_name, :string
|
field :collection_name, :string
|
||||||
field :collection_id, :string
|
field :collection_id, :string
|
||||||
field :collection_type, Ecto.Enum, values: [:channel, :playlist]
|
field :collection_type, Ecto.Enum, values: [:channel, :playlist]
|
||||||
field :nfo_filepath, :string
|
|
||||||
field :poster_filepath, :string
|
|
||||||
field :fanart_filepath, :string
|
|
||||||
field :banner_filepath, :string
|
|
||||||
field :series_directory, :string
|
|
||||||
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 :download_media, :boolean, default: true
|
field :download_media, :boolean, default: true
|
||||||
@@ -69,6 +65,13 @@ defmodule Pinchflat.Sources.Source do
|
|||||||
# Only download media items that were published after this date
|
# Only download media items that were published after this date
|
||||||
field :download_cutoff_date, :date
|
field :download_cutoff_date, :date
|
||||||
field :original_url, :string
|
field :original_url, :string
|
||||||
|
field :title_filter_regex, :string
|
||||||
|
|
||||||
|
field :series_directory, :string
|
||||||
|
field :nfo_filepath, :string
|
||||||
|
field :poster_filepath, :string
|
||||||
|
field :fanart_filepath, :string
|
||||||
|
field :banner_filepath, :string
|
||||||
|
|
||||||
belongs_to :media_profile, MediaProfile
|
belongs_to :media_profile, MediaProfile
|
||||||
|
|
||||||
@@ -95,7 +98,6 @@ defmodule Pinchflat.Sources.Source do
|
|||||||
|> dynamic_default(:custom_name, fn cs -> get_field(cs, :collection_name) end)
|
|> dynamic_default(:custom_name, fn cs -> get_field(cs, :collection_name) end)
|
||||||
|> validate_required(required_fields)
|
|> validate_required(required_fields)
|
||||||
|> cast_assoc(:metadata, with: &SourceMetadata.changeset/2, required: false)
|
|> cast_assoc(:metadata, with: &SourceMetadata.changeset/2, required: false)
|
||||||
|> unique_constraint([:collection_id, :media_profile_id])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
<div class="flex h-screen overflow-hidden">
|
<div class="flex h-screen overflow-hidden">
|
||||||
<div class="relative flex flex-1 flex-col overflow-y-auto overflow-x-hidden">
|
<div class="relative flex flex-1 flex-col overflow-y-auto overflow-x-hidden">
|
||||||
<header class="sticky top-0 z-999 flex w-full bg-white drop-shadow-1 dark:bg-boxdark dark:drop-shadow-none">
|
<header class="sticky top-0 z-999 flex w-full bg-white drop-shadow-1 dark:bg-boxdark dark:drop-shadow-none">
|
||||||
<div class="flex flex-grow items-center px-4 py-4 shadow-2 md:px-6 2xl:px-11">
|
<div class="w-65 px-4 py-2 shadow-2 md:px-6">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center gap-2 py-2">
|
||||||
<img src={~p"/images/logo.png?cachebust=2024-02-29"} alt="Pinchflat" class="w-9 h-9" />
|
<img src={~p"/images/logo.png?cachebust=2024-03-20"} alt="Pinchflat" class="w-auto" />
|
||||||
<h2 class="text-xl font-bold text-white pl-2">Pinchflat</h2>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
<header class="sticky top-0 z-999 flex w-full bg-white drop-shadow-1 dark:bg-boxdark dark:drop-shadow-none">
|
<header class="sticky top-0 z-999 flex h-20 w-full bg-white drop-shadow-1 dark:bg-boxdark dark:drop-shadow-none">
|
||||||
<div class="flex flex-grow items-center justify-between lg:justify-end px-4 py-4 shadow-2 md:px-6 2xl:px-11">
|
<div class="flex flex-grow items-center justify-between lg:justify-end px-4 py-4 shadow-2 md:px-6 2xl:px-11">
|
||||||
<div class="flex items-center gap-2 sm:gap-4 lg:hidden">
|
<div class="flex items-center gap-2 sm:gap-4 lg:hidden w-2/6">
|
||||||
|
<section class="pr-1">
|
||||||
|
<img src={~p"/images/icon.png?cachebust=2024-03-20"} alt="Pinchflat" class="w-10" />
|
||||||
|
</section>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="z-99999 block rounded-sm border border-stroke bg-white p-1.5 shadow-sm dark:border-strokedark dark:bg-boxdark lg:hidden"
|
class="z-99999 block mx-2 rounded-sm border border-stroke bg-white p-1.5 shadow-sm dark:border-strokedark dark:bg-boxdark lg:hidden"
|
||||||
@click.stop="sidebarVisible = !sidebarVisible"
|
@click.stop="sidebarVisible = !sidebarVisible"
|
||||||
>
|
>
|
||||||
<.icon name="hero-bars-3" />
|
<.icon name="hero-bars-3" />
|
||||||
</button>
|
</button>
|
||||||
<a class="hidden sm:flex items-center lg:hidden" href="/">
|
|
||||||
<img src={~p"/images/logo.png?cachebust=2024-02-29"} alt="Pinchflat" class="w-9 h-9" />
|
|
||||||
<h2 class="text-xl font-bold text-white pl-2">Pinchflat</h2>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-meta-4 rounded-md">
|
<div class="bg-meta-4 rounded-md w-4/6 lg:w-3/6 xl:w-2/6">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<span class="absolute left-2 top-1/2 -translate-y-1/2 flex">
|
<span class="absolute left-2 top-1/2 -translate-y-1/2 flex">
|
||||||
<.icon name="hero-magnifying-glass" />
|
<.icon name="hero-magnifying-glass" />
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
name="q"
|
name="q"
|
||||||
value={@params["q"]}
|
value={@params["q"]}
|
||||||
placeholder="Type to search..."
|
placeholder="Type to search..."
|
||||||
class="w-full bg-transparent pl-9 pr-4 border-0 focus:ring-0 focus:outline-none lg:w-125"
|
class="w-full bg-transparent pl-9 pr-4 border-0 focus:ring-0 focus:outline-none"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
<aside
|
<aside
|
||||||
x-bind:class="sidebarVisible ? 'translate-x-0' : '-translate-x-full'"
|
x-bind:class="sidebarVisible ? 'translate-x-0' : '-translate-x-full'"
|
||||||
class={[
|
class={[
|
||||||
"-translate-x-full absolute left-0 top-0 z-9999 flex h-screen w-60 flex-col overflow-y-hidden justify-between",
|
"-translate-x-full absolute left-0 top-0 z-9999 flex h-screen w-65 flex-col overflow-y-hidden justify-between",
|
||||||
"bg-black duration-300 ease-linear shadow-lg sm:shadow-none dark:bg-boxdark lg:static lg:translate-x-0"
|
"bg-black duration-300 ease-linear shadow-lg sm:shadow-none dark:bg-boxdark lg:static lg:translate-x-0"
|
||||||
]}
|
]}
|
||||||
@click.outside="sidebarVisible = false"
|
@click.outside="sidebarVisible = false"
|
||||||
>
|
>
|
||||||
<section>
|
<section>
|
||||||
<div class="flex items-center justify-between gap-2 px-6 py-5.5 lg:py-6.5">
|
<div class="flex items-center justify-between gap-2 px-6 py-4">
|
||||||
<a href="/" class="flex items-center">
|
<a href="/" class="flex items-center">
|
||||||
<img src={~p"/images/logo.png?cachebust=2024-02-29"} alt="Pinchflat" class="w-9 h-9" />
|
<img src={~p"/images/logo.png?cachebust=2024-03-20"} alt="Pinchflat" class="w-auto" />
|
||||||
<h2 class="text-xl font-bold text-white pl-2">Pinchflat</h2>
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<button class="block lg:hidden" @click.stop="sidebarVisible = !sidebarVisible">
|
<button class="block mt-3 lg:hidden" @click.stop="sidebarVisible = !sidebarVisible">
|
||||||
<.icon name="hero-arrow-left" class="fill-current" />
|
<.icon name="hero-arrow-left" class="fill-current" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="no-scrollbar flex flex-col overflow-y-auto duration-300 ease-linear">
|
<div class="no-scrollbar flex flex-col overflow-y-auto duration-300 ease-linear">
|
||||||
<nav class="mt-5 px-4 py-4 lg:mt-9 lg:px-6">
|
<nav class="mt-3 px-4 py-4 lg:px-6">
|
||||||
<h3 class="mb-4 ml-4 text-sm font-medium text-bodydark2">MENU</h3>
|
<h3 class="mb-4 ml-4 text-sm font-medium text-bodydark2">MENU</h3>
|
||||||
<div class="flex flex-col justify-between">
|
<div class="flex flex-col justify-between">
|
||||||
<ul class="mb-6 flex flex-col gap-1.5">
|
<ul class="mb-6 flex flex-col gap-1.5">
|
||||||
@@ -31,7 +30,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<nav class="mt-5 px-4 py-4 lg:mt-9 lg:px-6">
|
<nav class="px-4 py-4 lg:px-6">
|
||||||
<ul class="mb-6 flex flex-col gap-1.5">
|
<ul class="mb-6 flex flex-col gap-1.5">
|
||||||
<.sidebar_item
|
<.sidebar_item
|
||||||
icon="hero-code-bracket"
|
icon="hero-code-bracket"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<%= assigns[:page_title] || "Pinchflat" %>
|
<%= assigns[:page_title] || "Pinchflat" %>
|
||||||
</.live_title>
|
</.live_title>
|
||||||
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
||||||
<link rel="icon" type="image/x-icon" href={~p"/favicon.ico?cachebust=2024-02-29"} />
|
<link rel="icon" type="image/x-icon" href={~p"/favicon.ico?cachebust=2024-03-20"} />
|
||||||
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -238,7 +238,7 @@
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<.button class="my-10 sm:mb-7.5 w-full sm:w-auto">Save Media profile</.button>
|
<.button class="my-10 sm:mb-7.5 w-full sm:w-auto" rounding="rounded-lg">Save Media profile</.button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="rounded-sm dark:bg-meta-4 p-4 md:p-6 mb-5">
|
<div class="rounded-sm dark:bg-meta-4 p-4 md:p-6 mb-5">
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
<.simple_form :let={f} for={@changeset} action={@action}>
|
<.simple_form
|
||||||
|
:let={f}
|
||||||
|
for={@changeset}
|
||||||
|
action={@action}
|
||||||
|
x-data="{ advancedMode: !!JSON.parse(localStorage.getItem('advancedMode')) }"
|
||||||
|
x-init="$watch('advancedMode', value => localStorage.setItem('advancedMode', JSON.stringify(value)))"
|
||||||
|
>
|
||||||
<.error :if={@changeset.action}>
|
<.error :if={@changeset.action}>
|
||||||
Oops, something went wrong! Please check the errors below.
|
Oops, something went wrong! Please check the errors below.
|
||||||
</.error>
|
</.error>
|
||||||
|
|
||||||
<h3 class="mt-8 text-2xl text-black dark:text-white">
|
<section class="flex justify-between items-center mt-8">
|
||||||
General Options
|
<h3 class=" text-2xl text-black dark:text-white">
|
||||||
</h3>
|
General Options
|
||||||
|
</h3>
|
||||||
|
<span class="cursor-pointer hover:underline" x-on:click="advancedMode = !advancedMode">
|
||||||
|
Editing Mode: <span x-text="advancedMode ? 'Advanced' : 'Basic'"></span>
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
|
||||||
<.input
|
<.input
|
||||||
field={f[:custom_name]}
|
field={f[:custom_name]}
|
||||||
@@ -69,7 +80,24 @@
|
|||||||
help="Only download media uploaded after this date. Leave blank to download all media. Must be in YYYY-MM-DD format"
|
help="Only download media uploaded after this date. Leave blank to download all media. Must be in YYYY-MM-DD format"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<.button class="my-10 sm:mb-7.5 w-full sm:w-auto">Save Source</.button>
|
<section x-show="advancedMode">
|
||||||
|
<h3 class="mt-8 text-2xl text-black dark:text-white">
|
||||||
|
Advanced Options
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm mt-2">
|
||||||
|
Tread carefully
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<.input
|
||||||
|
field={f[:title_filter_regex]}
|
||||||
|
type="text"
|
||||||
|
label="Title Filter Regex"
|
||||||
|
placeholder="(?i)^How to Bike$"
|
||||||
|
help="A PCRE-compatible regex. Only media with titles that match this regex will be downloaded. Look up 'SQLean Regex docs' for more"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<.button class="my-10 sm:mb-7.5 w-full sm:w-auto" rounding="rounded-lg">Save Source</.button>
|
||||||
|
|
||||||
<div class="rounded-sm dark:bg-meta-4 p-4 md:p-6 mb-5">
|
<div class="rounded-sm dark:bg-meta-4 p-4 md:p-6 mb-5">
|
||||||
<.fast_indexing_help />
|
<.fast_indexing_help />
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ defmodule Pinchflat.MixProject do
|
|||||||
def project do
|
def project do
|
||||||
[
|
[
|
||||||
app: :pinchflat,
|
app: :pinchflat,
|
||||||
version: "0.1.0-alpha.6",
|
version: "0.1.0-alpha.7",
|
||||||
elixir: "~> 1.16",
|
elixir: "~> 1.16",
|
||||||
elixirc_paths: elixirc_paths(Mix.env()),
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
start_permanent: Mix.env() == :prod,
|
start_permanent: Mix.env() == :prod,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
defmodule Pinchflat.Repo.Migrations.AddTitleRegexToSource do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:sources) do
|
||||||
|
add :title_filter_regex, :string
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
defmodule Pinchflat.Repo.Migrations.RemoveUniqueIndexFromSources do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
drop unique_index(:sources, [:collection_id, :media_profile_id])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 164 KiB |
@@ -239,6 +239,26 @@ defmodule Pinchflat.MediaTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "list_pending_media_items_for/1 when testing title regex" do
|
||||||
|
test "returns only media items that match the title regex" do
|
||||||
|
source = source_fixture(%{title_filter_regex: "(?i)^FOO$"})
|
||||||
|
|
||||||
|
matching_media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, title: "foo"})
|
||||||
|
_non_matching_media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, title: "bar"})
|
||||||
|
|
||||||
|
assert Media.list_pending_media_items_for(source) == [matching_media_item]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not apply a regex if none is specified" do
|
||||||
|
source = source_fixture(%{title_filter_regex: nil})
|
||||||
|
|
||||||
|
media_item_one = media_item_fixture(%{source_id: source.id, media_filepath: nil, title: "foo"})
|
||||||
|
media_item_two = media_item_fixture(%{source_id: source.id, media_filepath: nil, title: "bar"})
|
||||||
|
|
||||||
|
assert Media.list_pending_media_items_for(source) == [media_item_one, media_item_two]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "list_downloaded_media_items_for/1" do
|
describe "list_downloaded_media_items_for/1" do
|
||||||
test "returns only media items with a media_filepath" do
|
test "returns only media items with a media_filepath" do
|
||||||
source = source_fixture()
|
source = source_fixture()
|
||||||
|
|||||||
@@ -138,49 +138,6 @@ defmodule Pinchflat.SourcesTest do
|
|||||||
assert {:error, %Ecto.Changeset{}} = Sources.create_source(@invalid_source_attrs)
|
assert {:error, %Ecto.Changeset{}} = Sources.create_source(@invalid_source_attrs)
|
||||||
end
|
end
|
||||||
|
|
||||||
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 channel name",
|
|
||||||
channel_id: "some_channel_id_12345678",
|
|
||||||
playlist_id: "some_channel_id_12345678",
|
|
||||||
playlist_title: "some channel name - videos"
|
|
||||||
})}
|
|
||||||
end)
|
|
||||||
|
|
||||||
valid_once_attrs = %{
|
|
||||||
media_profile_id: media_profile_fixture().id,
|
|
||||||
original_url: "https://www.youtube.com/channel/abc123"
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {:ok, %Source{}} = Sources.create_source(valid_once_attrs)
|
|
||||||
assert {:error, %Ecto.Changeset{}} = Sources.create_source(valid_once_attrs)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "creation lets you duplicate collection_ids as long as the media profile is different" do
|
|
||||||
expect(YtDlpRunnerMock, :run, 2, fn _url, _opts, _ot ->
|
|
||||||
{:ok,
|
|
||||||
Phoenix.json_library().encode!(%{
|
|
||||||
channel: "some channel name",
|
|
||||||
channel_id: "some_channel_id_12345678",
|
|
||||||
playlist_id: "some_channel_id_12345678",
|
|
||||||
playlist_title: "some channel name - videos"
|
|
||||||
})}
|
|
||||||
end)
|
|
||||||
|
|
||||||
valid_attrs = %{
|
|
||||||
name: "some name",
|
|
||||||
original_url: "https://www.youtube.com/channel/abc123"
|
|
||||||
}
|
|
||||||
|
|
||||||
source_1_attrs = Map.merge(valid_attrs, %{media_profile_id: media_profile_fixture().id})
|
|
||||||
source_2_attrs = Map.merge(valid_attrs, %{media_profile_id: media_profile_fixture().id})
|
|
||||||
|
|
||||||
assert {:ok, %Source{}} = Sources.create_source(source_1_attrs)
|
|
||||||
assert {:ok, %Source{}} = Sources.create_source(source_2_attrs)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "creation will schedule the indexing task" do
|
test "creation will schedule the indexing task" do
|
||||||
expect(YtDlpRunnerMock, :run, &channel_mock/3)
|
expect(YtDlpRunnerMock, :run, &channel_mock/3)
|
||||||
|
|
||||||
|
|||||||