mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-03-11 14:25:46 +08:00
Support file scheme when importing from URL (#706)
* Add test * Support file scheme when importing from URL
This commit is contained in:
parent
d78a3cf865
commit
4d92aeba2e
6 changed files with 80 additions and 30 deletions
|
@ -3,6 +3,11 @@ defmodule Livebook.ContentLoader do
|
|||
|
||||
alias Livebook.Utils.HTTP
|
||||
|
||||
@typedoc """
|
||||
A location from where content gets loaded.
|
||||
"""
|
||||
@type location :: {:file, FileSystem.File.t()} | {:url, String.t()}
|
||||
|
||||
@doc """
|
||||
Rewrite known URLs, so that they point to plain text file rather than HTML.
|
||||
|
||||
|
@ -81,4 +86,51 @@ defmodule Livebook.ContentLoader do
|
|||
{:error, "failed to download notebook from the given URL"}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Loads a notebook content from the given location.
|
||||
"""
|
||||
@spec fetch_content_from_location(location()) :: {:ok, String.t()} | {:error, String.t()}
|
||||
def fetch_content_from_location(location)
|
||||
|
||||
def fetch_content_from_location({:file, file}) do
|
||||
case Livebook.FileSystem.File.read(file) do
|
||||
{:ok, content} -> {:ok, content}
|
||||
{:error, message} -> {:error, "failed to read #{file.path}, reason: #{message}"}
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_content_from_location({:url, url}) do
|
||||
url
|
||||
|> rewrite_url()
|
||||
|> fetch_content()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Normalizes the given URL into a location.
|
||||
"""
|
||||
@spec url_to_location(String.t()) :: location()
|
||||
def url_to_location(url)
|
||||
|
||||
def url_to_location("file://" <> path) do
|
||||
path = Path.expand(path)
|
||||
file = Livebook.FileSystem.File.local(path)
|
||||
{:file, file}
|
||||
end
|
||||
|
||||
def url_to_location(url), do: {:url, url}
|
||||
|
||||
@doc """
|
||||
Resolves the given relative path with regard to the given location.
|
||||
"""
|
||||
@spec resolve_location(location(), String.t()) :: location()
|
||||
def resolve_location(location, relative_path)
|
||||
|
||||
def resolve_location({:url, url}, relative_path) do
|
||||
{:url, Livebook.Utils.expand_url(url, relative_path)}
|
||||
end
|
||||
|
||||
def resolve_location({:file, file}, relative_path) do
|
||||
{:file, Livebook.FileSystem.File.resolve(file, relative_path)}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -58,7 +58,7 @@ defmodule Livebook.Session do
|
|||
@type t :: %__MODULE__{
|
||||
id: id(),
|
||||
pid: pid(),
|
||||
origin: {:file, FileSystem.File.t()} | {:url, String.t()} | nil,
|
||||
origin: Livebook.ContentLoader.location() | nil,
|
||||
notebook_name: String.t(),
|
||||
file: FileSystem.File.t() | nil,
|
||||
images_dir: FileSystem.File.t(),
|
||||
|
|
|
@ -159,12 +159,13 @@ defmodule LivebookWeb.HomeLive do
|
|||
end
|
||||
|
||||
def handle_params(%{"url" => url}, _url, %{assigns: %{live_action: :public_import}} = socket) do
|
||||
url
|
||||
|> Livebook.ContentLoader.rewrite_url()
|
||||
|> Livebook.ContentLoader.fetch_content()
|
||||
origin = Livebook.ContentLoader.url_to_location(url)
|
||||
|
||||
origin
|
||||
|> Livebook.ContentLoader.fetch_content_from_location()
|
||||
|> case do
|
||||
{:ok, content} ->
|
||||
socket = import_content(socket, content, origin: {:url, url})
|
||||
socket = import_content(socket, content, origin: origin)
|
||||
{:noreply, socket}
|
||||
|
||||
{:error, _message} ->
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule LivebookWeb.HomeLive.ImportUrlComponent do
|
||||
use LivebookWeb, :live_component
|
||||
|
||||
alias Livebook.{ContentLoader, Utils}
|
||||
alias Livebook.Utils
|
||||
|
||||
@impl true
|
||||
def mount(socket) do
|
||||
|
@ -58,12 +58,13 @@ defmodule LivebookWeb.HomeLive.ImportUrlComponent do
|
|||
end
|
||||
|
||||
defp do_import(socket, url) do
|
||||
url
|
||||
|> ContentLoader.rewrite_url()
|
||||
|> ContentLoader.fetch_content()
|
||||
origin = Livebook.ContentLoader.url_to_location(url)
|
||||
|
||||
origin
|
||||
|> Livebook.ContentLoader.fetch_content_from_location()
|
||||
|> case do
|
||||
{:ok, content} ->
|
||||
send(self(), {:import_content, content, [origin: {:url, url}]})
|
||||
send(self(), {:import_content, content, [origin: origin]})
|
||||
socket
|
||||
|
||||
{:error, message} ->
|
||||
|
|
|
@ -6,7 +6,7 @@ defmodule LivebookWeb.SessionLive do
|
|||
import Livebook.Utils, only: [access_by_id: 1]
|
||||
|
||||
alias LivebookWeb.SidebarHelpers
|
||||
alias Livebook.{Sessions, Session, Delta, Notebook, Runtime, LiveMarkdown, FileSystem}
|
||||
alias Livebook.{Sessions, Session, Delta, Notebook, Runtime, LiveMarkdown}
|
||||
alias Livebook.Notebook.Cell
|
||||
alias Livebook.JSInterop
|
||||
|
||||
|
@ -961,11 +961,7 @@ defmodule LivebookWeb.SessionLive do
|
|||
|> redirect_to_self()
|
||||
|
||||
resolution_location ->
|
||||
origin =
|
||||
case resolution_location do
|
||||
{:url, url} -> {:url, Livebook.Utils.expand_url(url, relative_path)}
|
||||
{:file, file} -> {:file, FileSystem.File.resolve(file, relative_path)}
|
||||
end
|
||||
origin = Livebook.ContentLoader.resolve_location(resolution_location, relative_path)
|
||||
|
||||
case session_id_by_location(origin) do
|
||||
{:ok, session_id} ->
|
||||
|
@ -996,7 +992,7 @@ defmodule LivebookWeb.SessionLive do
|
|||
defp location(%{origin: origin}), do: origin
|
||||
|
||||
defp open_notebook(socket, origin) do
|
||||
case load_content(origin) do
|
||||
case Livebook.ContentLoader.fetch_content_from_location(origin) do
|
||||
{:ok, content} ->
|
||||
{notebook, messages} = Livebook.LiveMarkdown.Import.notebook_from_markdown(content)
|
||||
|
||||
|
@ -1015,19 +1011,6 @@ defmodule LivebookWeb.SessionLive do
|
|||
end
|
||||
end
|
||||
|
||||
defp load_content({:file, file}) do
|
||||
case FileSystem.File.read(file) do
|
||||
{:ok, content} -> {:ok, content}
|
||||
{:error, message} -> {:error, "failed to read #{file.path}, reason: #{message}"}
|
||||
end
|
||||
end
|
||||
|
||||
defp load_content({:url, url}) do
|
||||
url
|
||||
|> Livebook.ContentLoader.rewrite_url()
|
||||
|> Livebook.ContentLoader.fetch_content()
|
||||
end
|
||||
|
||||
defp file_and_notebook(fork?, origin, notebook)
|
||||
defp file_and_notebook(false, {:file, file}, notebook), do: {file, notebook}
|
||||
defp file_and_notebook(true, {:file, _file}, notebook), do: {nil, Notebook.forked(notebook)}
|
||||
|
|
|
@ -264,6 +264,19 @@ defmodule LivebookWeb.HomeLiveTest do
|
|||
assert render(view) =~ "My notebook"
|
||||
end
|
||||
|
||||
@tag :tmp_dir
|
||||
test "imports notebook from local file URL", %{conn: conn, tmp_dir: tmp_dir} do
|
||||
notebook_path = Path.join(tmp_dir, "notebook.livemd")
|
||||
File.write!(notebook_path, "# My notebook")
|
||||
notebook_url = "file://" <> notebook_path
|
||||
|
||||
assert {:error, {:live_redirect, %{to: to}}} =
|
||||
live(conn, "/import?url=#{URI.encode_www_form(notebook_url)}")
|
||||
|
||||
{:ok, view, _} = live(conn, to)
|
||||
assert render(view) =~ "My notebook"
|
||||
end
|
||||
|
||||
test "redirects to the import form on error", %{conn: conn} do
|
||||
bypass = Bypass.open()
|
||||
|
||||
|
|
Loading…
Reference in a new issue