[WIP] Working on refactoring feed
This commit is contained in:
parent
d199f02a57
commit
ab47bdbdf5
7 changed files with 111 additions and 56 deletions
44
lib/pinchflat/podcasts/podcast_helpers.ex
Normal file
44
lib/pinchflat/podcasts/podcast_helpers.ex
Normal file
|
|
@ -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
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
defmodule Pinchflat.Podcasts.RssFeedBuilder do
|
defmodule Pinchflat.Podcasts.RssFeedBuilder do
|
||||||
@datetime_format "%a, %d %b %Y %H:%M:%S %z"
|
@datetime_format "%a, %d %b %Y %H:%M:%S %z"
|
||||||
|
|
||||||
|
alias Pinchflat.Podcasts.PostcastHelpers
|
||||||
|
alias PinchflatWeb.Router.Helpers, as: Routes
|
||||||
|
|
||||||
# TODO: test
|
# TODO: test
|
||||||
# TODO: only MIs that are confirmed to exist on-disk should be provided
|
# TODO: only MIs that are confirmed to exist on-disk should be provided
|
||||||
def build(source, media_items) do
|
def build(source, media_items) do
|
||||||
|
|
@ -36,14 +39,14 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do
|
||||||
<podcast:locked>yes</podcast:locked>
|
<podcast:locked>yes</podcast:locked>
|
||||||
<podcast:guid>#{source.uuid}</podcast:guid>
|
<podcast:guid>#{source.uuid}</podcast:guid>
|
||||||
<image>
|
<image>
|
||||||
<url>#{generate_source_image_path(source)}</url>
|
<url>#{feed_image_path(source)}</url>
|
||||||
<title>#{source.custom_name}</title>
|
<title>#{source.custom_name}</title>
|
||||||
<link>#{source.original_url}</link>
|
<link>#{source.original_url}</link>
|
||||||
</image>
|
</image>
|
||||||
<itunes:author>#{source.custom_name}</itunes:author>
|
<itunes:author>#{source.custom_name}</itunes:author>
|
||||||
<itunes:subtitle>#{source.custom_name}</itunes:subtitle>
|
<itunes:subtitle>#{source.custom_name}</itunes:subtitle>
|
||||||
<itunes:block>yes</itunes:block>
|
<itunes:block>yes</itunes:block>
|
||||||
<itunes:image href="#{generate_source_image_path(source)}"></itunes:image>
|
<itunes:image href="#{feed_image_path(source)}"></itunes:image>
|
||||||
<itunes:explicit>false</itunes:explicit>
|
<itunes:explicit>false</itunes:explicit>
|
||||||
<itunes:category text="TV & Film"></itunes:category>
|
<itunes:category text="TV & Film"></itunes:category>
|
||||||
|
|
||||||
|
|
@ -63,7 +66,7 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do
|
||||||
<description>#{media_item.description}</description>
|
<description>#{media_item.description}</description>
|
||||||
<pubDate>#{generate_upload_date(media_item)}</pubDate>
|
<pubDate>#{generate_upload_date(media_item)}</pubDate>
|
||||||
<enclosure
|
<enclosure
|
||||||
url="#{generate_media_stream_path(media_item)}"
|
url="#{media_stream_path(media_item)}"
|
||||||
length="#{media_item.media_size_bytes}"
|
length="#{media_item.media_size_bytes}"
|
||||||
type="#{determine_content_type(media_item)}">
|
type="#{determine_content_type(media_item)}">
|
||||||
</enclosure>
|
</enclosure>
|
||||||
|
|
@ -76,18 +79,26 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp generate_self_link(source) do
|
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
|
end
|
||||||
|
|
||||||
defp generate_media_stream_path(media_item) do
|
defp media_stream_path(media_item) do
|
||||||
extension = Path.extname(media_item.media_filepath)
|
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
|
end
|
||||||
|
|
||||||
# TODO: add extension maybe. maybe refactor controller to handle this
|
defp feed_image_path(source) do
|
||||||
defp generate_source_image_path(source) do
|
image_path_on_disk = PostcastHelpers.select_cover_image(source)
|
||||||
"#{url_base()}/sources/#{source.uuid}/feed_image"
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
defp generate_upload_date(media_item) do
|
defp generate_upload_date(media_item) do
|
||||||
|
|
@ -102,6 +113,14 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do
|
||||||
MIME.from_path(media_item.media_filepath)
|
MIME.from_path(media_item.media_filepath)
|
||||||
end
|
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
|
defp url_base do
|
||||||
Application.get_env(:pinchflat, :url_base)
|
Application.get_env(:pinchflat, :url_base)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ defmodule PinchflatWeb do
|
||||||
|
|
||||||
def router do
|
def router do
|
||||||
quote do
|
quote do
|
||||||
use Phoenix.Router, helpers: false
|
use Phoenix.Router, helpers: true
|
||||||
|
|
||||||
# Import common connection and controller functions to use in pipelines
|
# Import common connection and controller functions to use in pipelines
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ defmodule PinchflatWeb.MediaItems.MediaItemController do
|
||||||
#
|
#
|
||||||
# Uses the UUID instead of the ID to avoid enumeration attacks
|
# Uses the UUID instead of the ID to avoid enumeration attacks
|
||||||
# since streaming is a public endpoint (ie: no auth required)
|
# 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)
|
media_item = Repo.get_by!(MediaItem, uuid: uuid)
|
||||||
|
|
||||||
if File.exists?(media_item.media_filepath) do
|
if File.exists?(media_item.media_filepath) do
|
||||||
|
|
|
||||||
34
lib/pinchflat_web/controllers/podcasts/podcast_controller.ex
Normal file
34
lib/pinchflat_web/controllers/podcasts/podcast_controller.ex
Normal file
|
|
@ -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
|
||||||
|
|
@ -7,7 +7,6 @@ defmodule PinchflatWeb.Sources.SourceController do
|
||||||
alias Pinchflat.Sources
|
alias Pinchflat.Sources
|
||||||
alias Pinchflat.Profiles
|
alias Pinchflat.Profiles
|
||||||
alias Pinchflat.Sources.Source
|
alias Pinchflat.Sources.Source
|
||||||
alias Pinchflat.Podcasts.RssFeedBuilder
|
|
||||||
|
|
||||||
def index(conn, _params) do
|
def index(conn, _params) do
|
||||||
sources = Repo.preload(Sources.list_sources(), :media_profile)
|
sources = Repo.preload(Sources.list_sources(), :media_profile)
|
||||||
|
|
@ -59,47 +58,6 @@ defmodule PinchflatWeb.Sources.SourceController do
|
||||||
)
|
)
|
||||||
end
|
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
|
def edit(conn, %{"id" => id}) do
|
||||||
source = Sources.get_source!(id)
|
source = Sources.get_source!(id)
|
||||||
changeset = Sources.change_source(source)
|
changeset = Sources.change_source(source)
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,10 @@ defmodule PinchflatWeb.Router do
|
||||||
# TODO: consider putting the basic auth here behind a config flag
|
# TODO: consider putting the basic auth here behind a config flag
|
||||||
# so people that want RSS feeds to work can enable it.
|
# so people that want RSS feeds to work can enable it.
|
||||||
scope "/", PinchflatWeb do
|
scope "/", PinchflatWeb do
|
||||||
get "/sources/:id/feed", Sources.SourceController, :feed
|
get "/sources/:uuid/rss_feed", Podcasts.PodcastController, :rss_feed
|
||||||
get "/sources/:id/feed_image", Sources.SourceController, :feed_image
|
get "/sources/:uuid/feed_image", Podcasts.PodcastController, :feed_image
|
||||||
|
|
||||||
get "/media/:id/stream", MediaItems.MediaItemController, :stream
|
get "/media/:uuid/stream", MediaItems.MediaItemController, :stream
|
||||||
end
|
end
|
||||||
|
|
||||||
# Enable LiveDashboard and Swoosh mailbox preview in development
|
# Enable LiveDashboard and Swoosh mailbox preview in development
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue