Packages
livebook
0.14.3
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook_web/helpers/session_helpers.ex
defmodule LivebookWeb.SessionHelpers do
import Phoenix.LiveView
use LivebookWeb, :html
alias Phoenix.LiveView.Socket
alias Livebook.Session
alias Livebook.FileSystem
@doc """
Determines user platform based on the given *User-Agent* header.
"""
@spec platform_from_user_agent(String.t()) :: :linux | :mac | :windows | :other
def platform_from_user_agent(user_agent) when is_binary(user_agent) do
cond do
linux?(user_agent) -> :linux
mac?(user_agent) -> :mac
windows?(user_agent) -> :windows
true -> :other
end
end
defp linux?(user_agent), do: String.match?(user_agent, ~r/Linux/)
defp mac?(user_agent), do: String.match?(user_agent, ~r/Mac OS X/)
defp windows?(user_agent), do: String.match?(user_agent, ~r/Windows/)
@doc """
Creates a new session, redirects on success,
puts an error flash message on failure.
## Options
* `:queue_setup` - whether to queue the setup cell right after
the session is started. Defaults to `false`
Accepts the same options as `Livebook.Sessions.create_session/1`.
"""
@spec create_session(Socket.t(), keyword()) :: Socket.t()
def create_session(socket, opts \\ []) do
{queue_setup, opts} = Keyword.pop(opts, :queue_setup, false)
# Revert persistence options to default values if there is
# no file attached to the new session
opts =
if opts[:notebook] != nil and opts[:file] == nil do
Keyword.update!(opts, :notebook, &Livebook.Notebook.reset_persistence_options/1)
else
opts
end
case Livebook.Sessions.create_session(opts) do
{:ok, session} ->
if queue_setup do
Session.queue_cell_evaluation(session.pid, Livebook.Notebook.Cell.setup_cell_id())
end
redirect_path = session_path(session.id, opts)
push_navigate(socket, to: redirect_path)
{:error, reason} ->
put_flash(socket, :error, "Failed to create session: #{reason}")
end
end
@doc """
Generate the session path based on the provided options.
"""
@spec session_path(Session.id(), keyword()) :: String.t()
def session_path(session_id, opts \\ []) do
maybe_add_url_hash(~p"/sessions/#{session_id}", opts)
end
defp maybe_add_url_hash(redirect_path, opts) do
case opts[:url_hash] do
nil -> redirect_path
url_hash -> "#{redirect_path}##{url_hash}"
end
end
@doc """
Formats the given list of notebook import messages and puts
into the warning flash.
"""
@spec put_import_warnings(Socket.t(), list(String.t())) :: Socket.t()
def put_import_warnings(socket, messages)
def put_import_warnings(socket, []), do: socket
def put_import_warnings(socket, messages) do
list =
messages
|> Enum.map(fn message -> ["- ", message] end)
|> Enum.intersperse("\n")
flash =
IO.iodata_to_binary(["We found problems while importing the file:\n" | list])
put_flash(socket, :warning, flash)
end
def uses_memory?(%{runtime: %{total: total}}) when total > 0, do: true
def uses_memory?(_), do: false
@doc """
Updates a list of sessions based on the given `Sessions` event.
"""
@spec update_session_list(
list(Session.t()),
{:session_created | :session_updated | :session_closed, Session.t()}
) :: list(Session.t())
def update_session_list(sessions, {:session_created, session}) do
if session in sessions do
sessions
else
[session | sessions]
end
end
def update_session_list(sessions, {:session_updated, session}) do
Enum.map(sessions, fn other ->
if other.id == session.id, do: session, else: other
end)
end
def update_session_list(sessions, {:session_closed, session}) do
Enum.reject(sessions, &(&1.id == session.id))
end
@doc """
Creates a new session by forking the given notebook file.
"""
@spec fork_notebook(Socket.t(), FileSystem.File.t()) :: Socket.t()
def fork_notebook(socket, file) do
case import_notebook(file) do
{:ok, {notebook, %{warnings: messages}}} ->
notebook = Livebook.Notebook.forked(notebook)
files_dir = Session.files_dir_for_notebook(file)
socket
|> put_import_warnings(messages)
|> create_session(
notebook: notebook,
files_source: {:dir, files_dir},
origin: {:file, file}
)
{:error, error} ->
put_flash(socket, :error, Livebook.Utils.upcase_first(error))
end
end
@doc """
Creates a new session by opening the given notebook file.
"""
@spec open_notebook(Socket.t(), FileSystem.File.t()) :: Socket.t()
def open_notebook(socket, file) do
case import_notebook(file) do
{:ok, {notebook, %{warnings: messages}}} ->
socket
|> put_import_warnings(messages)
|> create_session(notebook: notebook, file: file, origin: {:file, file})
{:error, error} ->
put_flash(socket, :error, Livebook.Utils.upcase_first(error))
end
end
defp import_notebook(file) do
with {:ok, content} <- FileSystem.File.read(file) do
{:ok, Livebook.LiveMarkdown.notebook_from_livemd(content)}
end
end
@doc """
Shows a confirmation modal to delete the given session.
## Options
* `:redirect_to` - a URL to redirect to after closing the session
"""
def confirm_close_session(socket, session, opts \\ []) do
redirect_to = opts[:redirect_to]
on_confirm = fn socket ->
Livebook.Session.close(session.pid)
if redirect_to do
push_navigate(socket, to: redirect_to)
else
socket
end
end
assigns = %{notebook_name: session.notebook_name, file: session.file}
description = ~H"""
Are you sure you want to close this session - <span class="font-semibold">“<%= @notebook_name %>”</span>?
<br />
<%= if @file do %>
This won't delete any persisted files.
<% else %>
The notebook is not persisted and content may be lost.
<% end %>
"""
confirm(socket, on_confirm,
title: "Close session",
description: description,
confirm_text: "Close session",
confirm_icon: "close-circle-line"
)
end
@doc """
Converts the given arbitrary name to a file entry name.
The returned name is either valid or empty.
"""
@spec sanitize_file_entry_name(String.t()) :: String.t() | nil
def sanitize_file_entry_name(client_name) do
client_name
|> String.replace(~r/[^\s\w-.]/u, "")
|> String.trim()
|> String.replace(~r/\s+/u, "_")
|> case do
"" ->
""
name ->
if String.contains?(name, ".") do
name
else
name <> ".bin"
end
end
end
@doc """
Generates a token for the given input.
"""
@spec generate_input_token(pid(), String.t()) :: String.t()
def generate_input_token(live_view_pid, input_id) do
Phoenix.Token.sign(LivebookWeb.Endpoint, "session-input", %{
live_view_pid: live_view_pid,
input_id: input_id
})
end
@doc """
Verifies token from `generate_input_token/2` and extracts the encoded
data.
"""
@spec verify_input_token!(String.t()) :: {pid(), String.t()}
def verify_input_token!(token) do
{:ok, %{live_view_pid: live_view_pid, input_id: input_id}} =
Phoenix.Token.verify(LivebookWeb.Endpoint, "session-input", token)
{live_view_pid, input_id}
end
@doc """
Registers an uploaded input file in session.
"""
@spec register_input_file(pid(), String.t(), String.t(), boolean(), String.t()) ::
{:ok, Livebook.Runtime.file_ref()}
def register_input_file(session_pid, path, input_id, local, client_id) do
if local do
key = "#{input_id}-#{client_id}"
Livebook.Session.register_file(session_pid, path, key, linked_client_id: client_id)
else
key = "#{input_id}-global"
Livebook.Session.register_file(session_pid, path, key)
end
end
end