diff --git a/lib/music_library/records/cover.ex b/lib/music_library/records/cover.ex index b79e4d38..aa715f0f 100644 --- a/lib/music_library/records/cover.ex +++ b/lib/music_library/records/cover.ex @@ -1,8 +1,8 @@ defmodule MusicLibrary.Records.Cover do @size 600 - def resize(cover_data) do - {:ok, thumb} = Vix.Vips.Operation.thumbnail_buffer(cover_data, @size) + def resize(cover_data, size \\ @size) do + {:ok, thumb} = Vix.Vips.Operation.thumbnail_buffer(cover_data, size) Vix.Vips.Image.write_to_buffer(thumb, ".jpg") end diff --git a/lib/music_library_web/controllers/cover_controller.ex b/lib/music_library_web/controllers/cover_controller.ex index 1b6c3a82..df8c4eef 100644 --- a/lib/music_library_web/controllers/cover_controller.ex +++ b/lib/music_library_web/controllers/cover_controller.ex @@ -2,6 +2,36 @@ defmodule MusicLibraryWeb.CoverController do use MusicLibraryWeb, :controller alias MusicLibrary.Records + alias MusicLibrary.Records.Cover + + def show(conn, %{"record_id" => record_id, "size" => size}) do + case Records.get_cover(record_id) do + nil -> + conn + |> put_status(:not_found) + |> text("Not found") + + %{cover_data: cover_data} -> + {:ok, thumb_data} = Cover.resize(cover_data, String.to_integer(size)) + hash = :crypto.hash(:sha256, thumb_data) |> Base.encode16() + + case get_req_header(conn, "if-none-match") do + [^hash] -> + conn + # 24 hours + |> put_resp_header("cache-control", "public, max-age=86400") + |> send_resp(304, "") + + _ -> + conn + |> put_resp_content_type("image/jpeg", "utf-8") + # 24 hours + |> put_resp_header("cache-control", "public, max-age=86400") + |> put_resp_header("etag", hash) + |> send_resp(200, thumb_data) + end + end + end def show(conn, %{"record_id" => record_id}) do case Records.get_cover(record_id) do diff --git a/test/music_library_web/controllers/cover_controller_test.exs b/test/music_library_web/controllers/cover_controller_test.exs index 52126195..16e99de4 100644 --- a/test/music_library_web/controllers/cover_controller_test.exs +++ b/test/music_library_web/controllers/cover_controller_test.exs @@ -2,6 +2,7 @@ defmodule MusicLibraryWeb.CoverControllerTest do use MusicLibraryWeb.ConnCase import MusicLibrary.RecordsFixtures + alias MusicLibrary.Records.Cover defp create_record(_) do %{record: record_fixture()} @@ -55,5 +56,19 @@ defmodule MusicLibraryWeb.CoverControllerTest do assert conn.resp_body == <<>> end + + test "accepts a size attribute for resizing", %{conn: conn, record: record} do + conn = get(conn, ~p"/covers/#{record.id}?size=480") + + thumb = File.read!(marbles_thumb_fixture()) + hash = Cover.hash(thumb) + + assert conn.status == 200 + assert get_resp_header(conn, "content-type") == ["image/jpeg; charset=utf-8"] + assert get_resp_header(conn, "cache-control") == ["public, max-age=86400"] + assert get_resp_header(conn, "etag") == [hash] + + assert conn.resp_body == thumb + end end end diff --git a/test/support/fixtures/marillion-marbles-thumb.jpg b/test/support/fixtures/marillion-marbles-thumb.jpg new file mode 100644 index 00000000..be85b919 Binary files /dev/null and b/test/support/fixtures/marillion-marbles-thumb.jpg differ diff --git a/test/support/fixtures/records_fixtures.ex b/test/support/fixtures/records_fixtures.ex index fcfb7606..48959edd 100644 --- a/test/support/fixtures/records_fixtures.ex +++ b/test/support/fixtures/records_fixtures.ex @@ -43,9 +43,11 @@ defmodule MusicLibrary.RecordsFixtures do ] # While it would be great to have this random, it's ok to use one single image @marbles_cover_data_path "#{__DIR__}/marillion-marbles.jpg" + @marbles_thumb_data_path "#{__DIR__}/marillion-marbles-thumb.jpg" @raven_cover_data_path "#{__DIR__}/steven-wilson-raven.jpg" def marbles_cover_fixture, do: @marbles_cover_data_path + def marbles_thumb_fixture, do: @marbles_thumb_data_path def raven_cover_fixture, do: @raven_cover_data_path def record_fixture(attrs \\ %{}) do