livebook/lib/livebook_web/controllers/session_controller.ex
Jonatan Kłosko 8e6558a83a
Introduce file system abstraction and an S3 implementation (#492)
* Introduce file system abstraction and an S3 implementation

* Support arbitrary absolute paths and delegate resolution to file system

* Remove accidental notebook file

* Apply suggestions from code review

Co-authored-by: José Valim <jose.valim@dashbit.co>

* Apply review comments

* Add missing path assertions

* Apply review comments

* Fix test saving notebook in project root

Co-authored-by: José Valim <jose.valim@dashbit.co>
2021-08-13 21:17:43 +02:00

88 lines
2.5 KiB
Elixir

defmodule LivebookWeb.SessionController do
use LivebookWeb, :controller
alias Livebook.{SessionSupervisor, Session, FileSystem}
def show_image(conn, %{"id" => id, "image" => image}) do
if SessionSupervisor.session_exists?(id) do
%{images_dir: images_dir} = Session.get_summary(id)
file = FileSystem.File.resolve(images_dir, image)
serve_static(conn, file)
else
send_resp(conn, 404, "Not found")
end
end
def download_source(conn, %{"id" => id, "format" => format}) do
if SessionSupervisor.session_exists?(id) do
notebook = Session.get_notebook(id)
send_notebook_source(conn, notebook, format)
else
send_resp(conn, 404, "Not found")
end
end
defp send_notebook_source(conn, notebook, "livemd") do
opts = [include_outputs: conn.params["include_outputs"] == "true"]
source = Livebook.LiveMarkdown.Export.notebook_to_markdown(notebook, opts)
send_download(conn, {:binary, source},
filename: "notebook.livemd",
content_type: "text/plain"
)
end
defp send_notebook_source(conn, notebook, "exs") do
source = Livebook.Notebook.Export.Elixir.notebook_to_elixir(notebook)
send_download(conn, {:binary, source},
filename: "notebook.exs",
content_type: "text/plain"
)
end
defp send_notebook_source(conn, _notebook, _format) do
send_resp(conn, 400, "Invalid format, supported formats: livemd, exs")
end
defp serve_static(conn, file) do
with {:ok, cache_state, conn} <- put_cache_header(conn, file),
{:ok, conn} <- serve_with_cache(conn, file, cache_state) do
conn
else
{:error, message} -> send_resp(conn, 404, Livebook.Utils.upcase_first(message))
end
end
defp put_cache_header(conn, file) do
with {:ok, etag} <- FileSystem.File.etag_for(file) do
conn =
conn
|> put_resp_header("cache-control", "public")
|> put_resp_header("etag", etag)
if etag in get_req_header(conn, "if-none-match") do
{:ok, :fresh, conn}
else
{:ok, :stale, conn}
end
end
end
defp serve_with_cache(conn, file, :stale) do
filename = FileSystem.File.name(file)
content_type = MIME.from_path(filename)
with {:ok, content} <- FileSystem.File.read(file) do
conn
|> put_resp_header("content-type", content_type)
|> send_resp(200, content)
|> then(&{:ok, &1})
end
end
defp serve_with_cache(conn, _file, :fresh) do
{:ok, send_resp(conn, 304, "")}
end
end