diff --git a/lib/pinchflat/podcasts/podcast_helpers.ex b/lib/pinchflat/podcasts/podcast_helpers.ex new file mode 100644 index 0000000..d67482a --- /dev/null +++ b/lib/pinchflat/podcasts/podcast_helpers.ex @@ -0,0 +1,44 @@ +defmodule Pinchflat.Podcasts.PostcastHelpers do + alias Pinchflat.Repo + alias Pinchflat.Media + + # TODO: test + def persisted_media_items_for(source, opts \\ []) do + limit = Keyword.get(opts, :limit, 500) + + source + |> Media.list_downloaded_media_items_for(limit: limit) + |> Enum.filter(fn media_item -> File.exists?(media_item.media_filepath) end) + end + + # TODO: test + # Returns string or nil + def select_cover_image(source) do + source_with_preloads = Repo.preload(source, :metadata) + # Since we're looking for the metadata image, _any_ downloaded media + # items should be fine + media_items = Media.list_downloaded_media_items_for(source, limit: 1) + + source_with_preloads + |> get_images_by_preference(media_items) + |> Enum.reject(&is_nil(&1)) + |> Enum.find(&File.exists?/1) + end + + def get_images_by_preference(source_with_preloads, []) do + source_metadata = source_with_preloads.metadata + + [ + source_metadata.poster_filepath, + source_metadata.banner_filepath + ] + end + + def get_images_by_preference(source_with_preloads, [media_item | _]) do + media_item_with_preloads = Repo.preload(media_item, :metadata) + media_item_metadata = media_item_with_preloads.metadata + source_images = get_images_by_preference(source_with_preloads, []) + + source_images ++ [media_item_metadata.thumbnail_filepath] + end +end diff --git a/lib/pinchflat/podcasts/rss_feed_builder.ex b/lib/pinchflat/podcasts/rss_feed_builder.ex index 1c921ed..0e6b260 100644 --- a/lib/pinchflat/podcasts/rss_feed_builder.ex +++ b/lib/pinchflat/podcasts/rss_feed_builder.ex @@ -1,6 +1,9 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do @datetime_format "%a, %d %b %Y %H:%M:%S %z" + alias Pinchflat.Podcasts.PostcastHelpers + alias PinchflatWeb.Router.Helpers, as: Routes + # TODO: test # TODO: only MIs that are confirmed to exist on-disk should be provided def build(source, media_items) do @@ -36,14 +39,14 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do yes #{source.uuid} - #{generate_source_image_path(source)} + #{feed_image_path(source)} #{source.custom_name} #{source.original_url} #{source.custom_name} #{source.custom_name} yes - + false @@ -63,7 +66,7 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do #{media_item.description} #{generate_upload_date(media_item)} @@ -76,18 +79,26 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do end defp generate_self_link(source) do - "#{url_base()}/sources/#{source.uuid}/feed.xml" + Path.join(url_base(), "#{podcast_route(:rss_feed, source.uuid)}.xml") end - defp generate_media_stream_path(media_item) do + defp media_stream_path(media_item) do extension = Path.extname(media_item.media_filepath) - "#{url_base()}/media/#{media_item.uuid}/stream#{extension}" + Path.join(url_base(), "#{media_route(:stream, media_item.uuid)}#{extension}") end - # TODO: add extension maybe. maybe refactor controller to handle this - defp generate_source_image_path(source) do - "#{url_base()}/sources/#{source.uuid}/feed_image" + defp feed_image_path(source) do + image_path_on_disk = PostcastHelpers.select_cover_image(source) + + case image_path_on_disk do + nil -> + "" + + _ -> + extension = Path.extname(image_path_on_disk) + Path.join(url_base(), "#{podcast_route(:feed_image, source.uuid)}#{extension}") + end end defp generate_upload_date(media_item) do @@ -102,6 +113,14 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do MIME.from_path(media_item.media_filepath) end + defp podcast_route(action, params) do + Routes.podcast_path(PinchflatWeb.Endpoint, action, params) + end + + defp media_route(action, params) do + Routes.media_item_path(PinchflatWeb.Endpoint, action, params) + end + defp url_base do Application.get_env(:pinchflat, :url_base) end diff --git a/lib/pinchflat_web.ex b/lib/pinchflat_web.ex index e7fbc32..ef58d00 100644 --- a/lib/pinchflat_web.ex +++ b/lib/pinchflat_web.ex @@ -21,7 +21,7 @@ defmodule PinchflatWeb do def router do quote do - use Phoenix.Router, helpers: false + use Phoenix.Router, helpers: true # Import common connection and controller functions to use in pipelines import Plug.Conn diff --git a/lib/pinchflat_web/controllers/media_items/media_item_controller.ex b/lib/pinchflat_web/controllers/media_items/media_item_controller.ex index bf44c77..5efc308 100644 --- a/lib/pinchflat_web/controllers/media_items/media_item_controller.ex +++ b/lib/pinchflat_web/controllers/media_items/media_item_controller.ex @@ -36,7 +36,7 @@ defmodule PinchflatWeb.MediaItems.MediaItemController do # # Uses the UUID instead of the ID to avoid enumeration attacks # since streaming is a public endpoint (ie: no auth required) - def stream(conn, %{"id" => uuid}) do + def stream(conn, %{"uuid" => uuid}) do media_item = Repo.get_by!(MediaItem, uuid: uuid) if File.exists?(media_item.media_filepath) do diff --git a/lib/pinchflat_web/controllers/podcasts/podcast_controller.ex b/lib/pinchflat_web/controllers/podcasts/podcast_controller.ex new file mode 100644 index 0000000..b5e2c36 --- /dev/null +++ b/lib/pinchflat_web/controllers/podcasts/podcast_controller.ex @@ -0,0 +1,34 @@ +defmodule PinchflatWeb.Podcasts.PodcastController do + use PinchflatWeb, :controller + + alias Pinchflat.Repo + alias Pinchflat.Podcasts.RssFeedBuilder + alias Pinchflat.Podcasts.PostcastHelpers + + # TODO: test + def rss_feed(conn, %{"uuid" => uuid}) do + # TODO: change this to UUID + source = Repo.get_by!(Source, id: uuid) + media_items = PostcastHelpers.persisted_media_items_for(source) + xml = RssFeedBuilder.build(source, media_items) + + conn + |> put_resp_content_type("application/rss+xml") + |> put_resp_header("content-disposition", "inline") + |> send_resp(200, xml) + end + + # TODO: test + def feed_image(conn, %{"uuid" => uuid}) do + source = Repo.get_by!(Source, uuid: uuid) + filepath = PostcastHelpers.select_cover_image(source) + + if filepath && File.exists?(filepath) do + conn + |> put_resp_content_type(MIME.from_path(filepath)) + |> send_file(200, filepath) + else + send_resp(conn, 404, "File not found") + end + end +end diff --git a/lib/pinchflat_web/controllers/sources/source_controller.ex b/lib/pinchflat_web/controllers/sources/source_controller.ex index b67846f..3211ccc 100644 --- a/lib/pinchflat_web/controllers/sources/source_controller.ex +++ b/lib/pinchflat_web/controllers/sources/source_controller.ex @@ -7,7 +7,6 @@ defmodule PinchflatWeb.Sources.SourceController do alias Pinchflat.Sources alias Pinchflat.Profiles alias Pinchflat.Sources.Source - alias Pinchflat.Podcasts.RssFeedBuilder def index(conn, _params) do sources = Repo.preload(Sources.list_sources(), :media_profile) @@ -59,47 +58,6 @@ defmodule PinchflatWeb.Sources.SourceController do ) end - # TODO: test - # TODO: maybe move both of these to a separate controller - def feed(conn, %{"id" => uuid}) do - # TODO: change this to UUID - source = Repo.get_by!(Source, id: uuid) - media_items = Media.list_downloaded_media_items_for(source, limit: 100) - - xml = RssFeedBuilder.build(source, media_items) - - conn - |> put_resp_content_type("application/rss+xml") - |> put_resp_header("content-disposition", "inline") - |> send_resp(200, xml) - end - - # TODO: test - # TODO: look into media_items having a thumbnail path when the image is embedded but not on disk - # TODO: pull these images from the internal metadata instead. this implies I'll have to hook up - # metadata images for sources - def feed_image(conn, %{"id" => uuid}) do - source = Repo.get_by!(Source, uuid: uuid) - media_item = Media.list_downloaded_media_items_for(source, limit: 1) - - filepath = - case {source.poster_filepath, media_item} do - {poster, _} when poster != nil -> poster - {nil, [media_item]} -> media_item.thumbnail_filepath - _ -> nil - end - - IO.inspect(filepath) - - if filepath && File.exists?(filepath) do - conn - |> put_resp_content_type(MIME.from_path(filepath)) - |> send_file(200, filepath) - else - send_resp(conn, 404, "File not found") - end - end - def edit(conn, %{"id" => id}) do source = Sources.get_source!(id) changeset = Sources.change_source(source) diff --git a/lib/pinchflat_web/router.ex b/lib/pinchflat_web/router.ex index 0a1071c..3c8cc67 100644 --- a/lib/pinchflat_web/router.ex +++ b/lib/pinchflat_web/router.ex @@ -36,10 +36,10 @@ defmodule PinchflatWeb.Router do # TODO: consider putting the basic auth here behind a config flag # so people that want RSS feeds to work can enable it. scope "/", PinchflatWeb do - get "/sources/:id/feed", Sources.SourceController, :feed - get "/sources/:id/feed_image", Sources.SourceController, :feed_image + get "/sources/:uuid/rss_feed", Podcasts.PodcastController, :rss_feed + get "/sources/:uuid/feed_image", Podcasts.PodcastController, :feed_image - get "/media/:id/stream", MediaItems.MediaItemController, :stream + get "/media/:uuid/stream", MediaItems.MediaItemController, :stream end # Enable LiveDashboard and Swoosh mailbox preview in development