diff --git a/lib/pinchflat/metadata/nfo_builder.ex b/lib/pinchflat/metadata/nfo_builder.ex
index d14fb0b..fb5ba7b 100644
--- a/lib/pinchflat/metadata/nfo_builder.ex
+++ b/lib/pinchflat/metadata/nfo_builder.ex
@@ -21,6 +21,20 @@ defmodule Pinchflat.Metadata.NfoBuilder do
filepath
end
+ @doc """
+ Builds an NFO file for a souce and stores it at the specified location.
+ Technically works for playlists, but it's really made for channels.
+
+ Returns the filepath of the NFO file.
+ """
+ def build_and_store_for_source(filepath, metadata) do
+ nfo = build_for_source(metadata)
+
+ FilesystemHelpers.write_p!(filepath, nfo)
+
+ filepath
+ end
+
defp build_for_media_item(metadata) do
upload_date = MetadataFileHelpers.parse_upload_date(metadata["upload_date"])
# Cribbed from a combination of the Kodi wiki, ytdl-nfo, and ytdl-sub.
@@ -39,4 +53,16 @@ defmodule Pinchflat.Metadata.NfoBuilder do
"""
end
+
+ defp build_for_source(metadata) do
+ """
+
+
+ #{metadata["title"]}
+ #{metadata["description"]}
+ #{metadata["id"]}
+ YouTube
+
+ """
+ end
end
diff --git a/lib/pinchflat/metadata/source_metadata_storage_worker.ex b/lib/pinchflat/metadata/source_metadata_storage_worker.ex
index c430213..257b0f0 100644
--- a/lib/pinchflat/metadata/source_metadata_storage_worker.ex
+++ b/lib/pinchflat/metadata/source_metadata_storage_worker.ex
@@ -4,7 +4,7 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
use Oban.Worker,
queue: :remote_metadata,
tags: ["media_source", "source_metadata", "remote_metadata"],
- max_attempts: 1,
+ max_attempts: 3,
# This is the only thing stopping this job from calling itself
# in an infinite loop. Time is in seconds
unique: [period: 120]
@@ -15,6 +15,7 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
alias Pinchflat.Repo
alias Pinchflat.Tasks
alias Pinchflat.Sources
+ alias Pinchflat.Metadata.NfoBuilder
alias Pinchflat.YtDlp.MediaCollection
alias Pinchflat.Metadata.MetadataFileHelpers
alias Pinchflat.Downloading.DownloadOptionBuilder
@@ -41,6 +42,7 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
@impl Oban.Worker
def perform(%Oban.Job{args: %{"id" => source_id}}) do
source = Repo.preload(Sources.get_source!(source_id), [:metadata, :media_profile])
+ source_metadata = fetch_source_metadata(source)
series_directory = determine_series_directory(source)
# Since updating a source kicks this job off again, we enforce job uniqueness (above)
@@ -48,8 +50,9 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
# in an infinite loop.
Sources.update_source(source, %{
series_directory: series_directory,
+ nfo_filepath: store_source_nfo(source, series_directory, source_metadata),
metadata: %{
- metadata_filepath: store_source_metadata(source)
+ metadata_filepath: store_source_metadata(source, source_metadata)
}
})
@@ -59,9 +62,13 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
Ecto.StaleEntryError -> Logger.info("#{__MODULE__} discarded: source #{source_id} stale")
end
- defp store_source_metadata(source) do
+ defp fetch_source_metadata(source) do
{:ok, metadata} = MediaCollection.get_source_metadata(source.original_url)
+ metadata
+ end
+
+ defp store_source_metadata(source, metadata) do
MetadataFileHelpers.compress_and_store_metadata_for(source, metadata)
end
@@ -74,4 +81,12 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
{:error, _} -> nil
end
end
+
+ defp store_source_nfo(source, series_directory, metadata) do
+ if source.download_nfo && series_directory do
+ nfo_filepath = Path.join(series_directory, "tvshow.nfo")
+
+ NfoBuilder.build_and_store_for_source(nfo_filepath, metadata)
+ end
+ end
end
diff --git a/lib/pinchflat/sources/source.ex b/lib/pinchflat/sources/source.ex
index 1d393c2..baab932 100644
--- a/lib/pinchflat/sources/source.ex
+++ b/lib/pinchflat/sources/source.ex
@@ -106,4 +106,9 @@ defmodule Pinchflat.Sources.Source do
# minutes
15
end
+
+ @doc false
+ def filepath_attributes do
+ ~w(nfo_filepath)a
+ end
end
diff --git a/lib/pinchflat/sources/sources.ex b/lib/pinchflat/sources/sources.ex
index 64f1e61..8f9ca71 100644
--- a/lib/pinchflat/sources/sources.ex
+++ b/lib/pinchflat/sources/sources.ex
@@ -102,7 +102,6 @@ defmodule Pinchflat.Sources do
"""
def delete_source(%Source{} = source, opts \\ []) do
delete_files = Keyword.get(opts, :delete_files, false)
-
Tasks.delete_tasks_for(source)
source
@@ -111,7 +110,11 @@ defmodule Pinchflat.Sources do
Media.delete_media_item(media_item, delete_files: delete_files)
end)
- delete_source_metadata_files(source)
+ if delete_files do
+ delete_source_files(source)
+ end
+
+ delete_internal_metadata_files(source)
Repo.delete(source)
end
@@ -134,16 +137,23 @@ defmodule Pinchflat.Sources do
end
end
- defp delete_source_metadata_files(source) do
+ defp delete_source_files(source) do
+ mapped_struct = Map.from_struct(source)
+
+ Source.filepath_attributes()
+ |> Enum.map(fn field -> mapped_struct[field] end)
+ |> Enum.filter(&is_binary/1)
+ |> Enum.each(&FilesystemHelpers.delete_file_and_remove_empty_directories/1)
+ end
+
+ defp delete_internal_metadata_files(source) do
metadata = Repo.preload(source, :metadata).metadata || %SourceMetadata{}
mapped_struct = Map.from_struct(metadata)
- filepaths =
- SourceMetadata.filepath_attributes()
- |> Enum.map(fn field -> mapped_struct[field] end)
- |> Enum.filter(&is_binary/1)
-
- Enum.each(filepaths, &FilesystemHelpers.delete_file_and_remove_empty_directories/1)
+ SourceMetadata.filepath_attributes()
+ |> Enum.map(fn field -> mapped_struct[field] end)
+ |> Enum.filter(&is_binary/1)
+ |> Enum.each(&FilesystemHelpers.delete_file_and_remove_empty_directories/1)
end
defp add_source_details_to_changeset(source, changeset) do
diff --git a/test/pinchflat/metadata/nfo_builder_test.exs b/test/pinchflat/metadata/nfo_builder_test.exs
index 0985170..c6119a4 100644
--- a/test/pinchflat/metadata/nfo_builder_test.exs
+++ b/test/pinchflat/metadata/nfo_builder_test.exs
@@ -31,4 +31,20 @@ defmodule Pinchflat.Metadata.NfoBuilderTest do
assert String.contains?(nfo, "
#{metadata["title"]}")
end
end
+
+ describe "build_and_store_for_source/2" do
+ test "returns the filepath", %{metadata: metadata, filepath: filepath} do
+ result = NfoBuilder.build_and_store_for_source(filepath, metadata)
+
+ assert File.exists?(result)
+ end
+
+ test "builds an NFO file", %{metadata: metadata, filepath: filepath} do
+ result = NfoBuilder.build_and_store_for_source(filepath, metadata)
+ nfo = File.read!(result)
+
+ assert String.contains?(nfo, ~S())
+ assert String.contains?(nfo, "#{metadata["title"]}")
+ end
+ end
end
diff --git a/test/pinchflat/metadata/source_metadata_storage_worker_test.exs b/test/pinchflat/metadata/source_metadata_storage_worker_test.exs
index 0a24e49..2981f0c 100644
--- a/test/pinchflat/metadata/source_metadata_storage_worker_test.exs
+++ b/test/pinchflat/metadata/source_metadata_storage_worker_test.exs
@@ -138,4 +138,64 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorkerTest do
refute source.series_directory
end
end
+
+ describe "perform/1 when storing the series NFO" do
+ test "stores the NFO if specified" do
+ stub(YtDlpRunnerMock, :run, fn
+ _url, _opts, ot when ot == @source_details_ot ->
+ filename = Path.join([Application.get_env(:pinchflat, :media_directory), "Season 1", "bar.mp4"])
+
+ {:ok, source_details_return_fixture(%{filename: filename})}
+
+ _url, _opts, ot when ot == @metadata_ot ->
+ {:ok, "{}"}
+ end)
+
+ source = source_fixture(%{download_nfo: true, nfo_filepath: nil})
+ perform_job(SourceMetadataStorageWorker, %{id: source.id})
+ source = Repo.reload(source)
+
+ assert source.nfo_filepath
+ assert source.nfo_filepath == Path.join([source.series_directory, "tvshow.nfo"])
+ assert File.exists?(source.nfo_filepath)
+
+ File.rm!(source.nfo_filepath)
+ end
+
+ test "does not store the NFO if not specified" do
+ stub(YtDlpRunnerMock, :run, fn
+ _url, _opts, ot when ot == @source_details_ot ->
+ filename = Path.join([Application.get_env(:pinchflat, :media_directory), "Season 1", "bar.mp4"])
+
+ {:ok, source_details_return_fixture(%{filename: filename})}
+
+ _url, _opts, ot when ot == @metadata_ot ->
+ {:ok, "{}"}
+ end)
+
+ source = source_fixture(%{download_nfo: false, nfo_filepath: nil})
+ perform_job(SourceMetadataStorageWorker, %{id: source.id})
+ source = Repo.reload(source)
+
+ refute source.nfo_filepath
+ end
+
+ test "does not store the NFO if the series directory cannot be determined" do
+ stub(YtDlpRunnerMock, :run, fn
+ _url, _opts, ot when ot == @source_details_ot ->
+ filename = Path.join([Application.get_env(:pinchflat, :media_directory), "foo", "bar.mp4"])
+
+ {:ok, source_details_return_fixture(%{filename: filename})}
+
+ _url, _opts, ot when ot == @metadata_ot ->
+ {:ok, "{}"}
+ end)
+
+ source = source_fixture(%{download_nfo: true, nfo_filepath: nil})
+ perform_job(SourceMetadataStorageWorker, %{id: source.id})
+ source = Repo.reload(source)
+
+ refute source.nfo_filepath
+ end
+ end
end
diff --git a/test/pinchflat/sources_test.exs b/test/pinchflat/sources_test.exs
index 103a33f..788fd7c 100644
--- a/test/pinchflat/sources_test.exs
+++ b/test/pinchflat/sources_test.exs
@@ -8,6 +8,7 @@ defmodule Pinchflat.SourcesTest do
alias Pinchflat.Sources
alias Pinchflat.Sources.Source
+ alias Pinchflat.Filesystem.FilesystemHelpers
alias Pinchflat.Metadata.MetadataFileHelpers
alias Pinchflat.Downloading.DownloadingHelpers
alias Pinchflat.FastIndexing.FastIndexingWorker
@@ -474,9 +475,19 @@ defmodule Pinchflat.SourcesTest do
{:ok, updated_source} = Sources.update_source(source, update_attrs)
- assert {:ok, _} = Sources.delete_source(updated_source, delete_files: true)
+ assert {:ok, _} = Sources.delete_source(updated_source)
refute File.exists?(updated_source.metadata.metadata_filepath)
end
+
+ test "does not delete the source's non-metadata files" do
+ filepath = FilesystemHelpers.generate_metadata_tmpfile(:nfo)
+ source = source_fixture(%{nfo_filepath: filepath})
+
+ assert {:ok, _} = Sources.delete_source(source)
+ assert File.exists?(filepath)
+
+ File.rm!(filepath)
+ end
end
describe "delete_source/2 when deleting files" do
@@ -498,6 +509,15 @@ defmodule Pinchflat.SourcesTest do
refute File.exists?(media_item.media_filepath)
end
+
+ test "deletes the source's non-metadata files" do
+ filepath = FilesystemHelpers.generate_metadata_tmpfile(:nfo)
+ source = source_fixture(%{nfo_filepath: filepath})
+
+ assert {:ok, _} = Sources.delete_source(source, delete_files: true)
+
+ refute File.exists?(filepath)
+ end
end
describe "change_source/3" do