From e87955fa3fc9d79db8caf8dd6659353f4c1b7f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Fri, 15 Apr 2022 21:50:45 +0200 Subject: [PATCH 01/10] Show information when a new Livebook version is available --- README.md | 4 ++ config/config.exs | 4 +- lib/livebook.ex | 11 ++++ lib/livebook/application.ex | 46 ++++++++------ lib/livebook/config.ex | 29 +++++++++ lib/livebook/settings.ex | 21 ++++++- lib/livebook/update_check.ex | 83 ++++++++++++++++++++++++++ lib/livebook_web/live/home_live.ex | 27 ++++++++- lib/livebook_web/live/settings_live.ex | 27 +++++++-- 9 files changed, 227 insertions(+), 25 deletions(-) create mode 100644 lib/livebook/update_check.ex diff --git a/README.md b/README.md index 75d9818df61..66a06adfcf9 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,10 @@ The following environment variables configure Livebook: Enabled by default unless `LIVEBOOK_PASSWORD` is set. Set it to "false" to disable it. + * LIVEBOOK_UPDATE_INSTRUCTIONS_URL - sets the URL to direct the user to for + updating Livebook when a new version becomes available. Defaults to livebook.dev. + Set it to 0 to disable the update check. + If running Livebook as a Docker image or an Elixir release, [the environment diff --git a/config/config.exs b/config/config.exs index ded1955cc7d..3aaa46c8685 100644 --- a/config/config.exs +++ b/config/config.exs @@ -26,7 +26,9 @@ config :livebook, explore_notebooks: [], plugs: [], shutdown_enabled: false, - storage: Livebook.Storage.Ets + storage: Livebook.Storage.Ets, + update_check_enabled: true, + update_instructions_url: "https://livebook.dev/#install" # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. diff --git a/lib/livebook.ex b/lib/livebook.ex index 236449d99dd..897ec205821 100644 --- a/lib/livebook.ex +++ b/lib/livebook.ex @@ -155,6 +155,17 @@ defmodule Livebook do :app_service_url, Livebook.Config.app_service_url!("LIVEBOOK_APP_SERVICE_URL") end + + case Livebook.Config.update_instructions_url!("LIVEBOOK_UPDATE_INSTRUCTIONS_URL") do + nil -> + :ok + + :disabled -> + config :livebook, :update_check_enabled, false + + url -> + config :livebook, :update_instructions_url, url + end end @doc """ diff --git a/lib/livebook/application.ex b/lib/livebook/application.ex index 63dd6e87513..0f7ef73cf90 100644 --- a/lib/livebook/application.ex +++ b/lib/livebook/application.ex @@ -17,24 +17,27 @@ defmodule Livebook.Application do # Start the Telemetry supervisor LivebookWeb.Telemetry, # Start the PubSub system - {Phoenix.PubSub, name: Livebook.PubSub}, - # Start the storage module - Livebook.Storage.current(), - # Periodid measurement of system resources - Livebook.SystemResources, - # Start the tracker server on this node - {Livebook.Tracker, pubsub_server: Livebook.PubSub}, - # Start the supervisor dynamically managing sessions - {DynamicSupervisor, name: Livebook.SessionSupervisor, strategy: :one_for_one}, - # Start a supervisor for Livebook tasks - {Task.Supervisor, name: Livebook.TaskSupervisor}, - # Start the server responsible for associating files with sessions - Livebook.Session.FileGuard, - # Start the Node Pool for managing node names - Livebook.Runtime.NodePool, - # Start the unique task dependencies - Livebook.Utils.UniqueTask + {Phoenix.PubSub, name: Livebook.PubSub} ] ++ + update_check_specs() ++ + [ + # Start the storage module + Livebook.Storage.current(), + # Periodid measurement of system resources + Livebook.SystemResources, + # Start the tracker server on this node + {Livebook.Tracker, pubsub_server: Livebook.PubSub}, + # Start the supervisor dynamically managing sessions + {DynamicSupervisor, name: Livebook.SessionSupervisor, strategy: :one_for_one}, + # Start a supervisor for Livebook tasks + {Task.Supervisor, name: Livebook.TaskSupervisor}, + # Start the server responsible for associating files with sessions + Livebook.Session.FileGuard, + # Start the Node Pool for managing node names + Livebook.Runtime.NodePool, + # Start the unique task dependencies + Livebook.Utils.UniqueTask + ] ++ iframe_server_specs() ++ [ # Start the Endpoint (http/https) @@ -186,6 +189,15 @@ defmodule Livebook.Application do defp config_env_var?("RELEASE_" <> _), do: true defp config_env_var?(_), do: false + defp update_check_specs() do + if Livebook.Config.update_check_enabled?() do + # Start the periodic version check + [Livebook.UpdateCheck] + else + [] + end + end + if Mix.target() == :app do defp app_specs, do: [LivebookApp] else diff --git a/lib/livebook/config.ex b/lib/livebook/config.ex index 49af9635455..b000b9b7f67 100644 --- a/lib/livebook/config.ex +++ b/lib/livebook/config.ex @@ -122,6 +122,22 @@ defmodule Livebook.Config do Application.fetch_env!(:livebook, :app_service_url) end + @doc """ + Returns whether the update check is enabled. + """ + @spec update_check_enabled?() :: boolean() + def update_check_enabled?() do + Application.fetch_env!(:livebook, :update_check_enabled) + end + + @doc """ + Returns the update check URL. + """ + @spec update_instructions_url() :: String.t() + def update_instructions_url() do + Application.fetch_env!(:livebook, :update_instructions_url) + end + ## Parsing @doc """ @@ -254,6 +270,19 @@ defmodule Livebook.Config do System.get_env(env) end + @doc """ + Parses update instructions url from env. + + When the env is set to "0", returns `:disabled`. + """ + def update_instructions_url!(env) do + case System.get_env(env) do + nil -> nil + "0" -> :disabled + url -> url + end + end + @doc """ Parses and validates default runtime from env. """ diff --git a/lib/livebook/settings.ex b/lib/livebook/settings.ex index 11d8afa899a..8840342c29d 100644 --- a/lib/livebook/settings.ex +++ b/lib/livebook/settings.ex @@ -13,7 +13,7 @@ defmodule Livebook.Settings do @doc """ Returns the current autosave path. """ - @spec autosave_path() :: String.t() | nil + @spec autosave_path() :: String.t() def autosave_path() do case storage().fetch_key(:settings, "global", :autosave_path) do {:ok, value} -> value @@ -98,4 +98,23 @@ defmodule Livebook.Settings do {:error, message} -> raise ArgumentError, "invalid S3 filesystem: #{message}" end end + + @doc """ + Returns the user preference for update check. + """ + @spec update_notifications_enabled?() :: boolean() + def update_notifications_enabled?() do + case storage().fetch_key(:settings, "global", :update_notifications_enabled) do + {:ok, value} -> value + :error -> true + end + end + + @doc """ + Sets the user preference for update check. + """ + @spec set_update_notifications_enabled(boolean()) :: :ok + def set_update_notifications_enabled(enabled) do + storage().insert(:settings, "global", update_notifications_enabled: enabled) + end end diff --git a/lib/livebook/update_check.ex b/lib/livebook/update_check.ex new file mode 100644 index 00000000000..3e77fc4069a --- /dev/null +++ b/lib/livebook/update_check.ex @@ -0,0 +1,83 @@ +defmodule Livebook.UpdateCheck do + @moduledoc false + + # Periodically checks for available Livebook update. + + use GenServer + + require Logger + + @version_term __MODULE__ + + @hour_in_ms 60 * 60 * 1000 + @day_in_ms 24 * @hour_in_ms + + @doc false + def start_link(_opts) do + GenServer.start_link(__MODULE__, {}) + end + + @doc """ + Returns the latest Livebook version if it's more recent than the + current one. + """ + @spec new_version() :: String.t() | nil + def new_version() do + current_version = Application.spec(:livebook, :vsn) |> List.to_string() + latest_version = get_version() + + if latest_version in [nil, current_version] do + nil + else + latest_version + end + end + + @impl true + def init({}) do + send(self(), :check) + {:ok, %{}} + end + + @impl true + def handle_info(:check, state) do + case check_release() do + {:ok, version} -> + put_version(version) + Process.send_after(self(), :check, @day_in_ms) + + {:error, error} -> + Logger.error("version check failed, #{error}") + Process.send_after(self(), :check, @hour_in_ms) + end + + {:noreply, state, :hibernate} + end + + defp check_release() do + url = "https://api.github.com/repos/livebook-dev/livebook/releases/latest" + headers = [{"accept", "application/vnd.github.v3+json"}] + + case Livebook.Utils.HTTP.request(:get, url, headers: headers) do + {:ok, status, _headers, body} -> + with 200 <- status, + {:ok, data} <- Jason.decode(body), + %{"tag_name" => "v" <> version} <- data do + {:ok, version} + else + _ -> {:error, "unexpected response"} + end + + {:error, reason} -> + {:error, "failed to make a request, reason: #{inspect(reason)}"} + end + end + + defp put_version(version) do + :persistent_term.put(@version_term, version) + end + + defp get_version() do + :persistent_term.get(@version_term, nil) + end +end diff --git a/lib/livebook_web/live/home_live.ex b/lib/livebook_web/live/home_live.ex index df129074eb1..e145c4987bd 100644 --- a/lib/livebook_web/live/home_live.ex +++ b/lib/livebook_web/live/home_live.ex @@ -16,6 +16,10 @@ defmodule LivebookWeb.HomeLive do sessions = Sessions.list_sessions() notebook_infos = Notebook.Explore.visible_notebook_infos() |> Enum.take(3) + new_version = + if Livebook.Settings.update_notifications_enabled?(), + do: Livebook.UpdateCheck.new_version() + {:ok, socket |> SidebarHelpers.shared_home_handlers() @@ -25,7 +29,9 @@ defmodule LivebookWeb.HomeLive do file_info: %{exists: true, access: :read_write}, sessions: sessions, notebook_infos: notebook_infos, - page_title: "Livebook" + page_title: "Livebook", + new_version: new_version, + update_instructions_url: Livebook.Config.update_instructions_url() )} end @@ -39,6 +45,7 @@ defmodule LivebookWeb.HomeLive do
+ <.update_notification version={@new_version} instructions_url={@update_instructions_url} />
@@ -154,6 +161,24 @@ defmodule LivebookWeb.HomeLive do end end + defp update_notification(%{version: nil} = assigns), do: ~H"" + + defp update_notification(assigns) do + ~H""" +
+ + Livebook v<%= @version %> available! Check out the + release notes 🚀 + + Update +
+ """ + end + @impl true def handle_params(%{"session_id" => session_id}, _url, socket) do session = Enum.find(socket.assigns.sessions, &(&1.id == session_id)) diff --git a/lib/livebook_web/live/settings_live.ex b/lib/livebook_web/live/settings_live.ex index 276e5788337..54e4b4f889a 100644 --- a/lib/livebook_web/live/settings_live.ex +++ b/lib/livebook_web/live/settings_live.ex @@ -7,17 +7,16 @@ defmodule LivebookWeb.SettingsLive do @impl true def mount(_params, _session, socket) do - file_systems = Livebook.Settings.file_systems() - {:ok, socket |> SidebarHelpers.shared_home_handlers() |> assign( - file_systems: file_systems, + file_systems: Livebook.Settings.file_systems(), autosave_path_state: %{ file: autosave_dir(), dialog_opened?: false }, + update_notifications_enabled: Livebook.Settings.update_notifications_enabled?(), page_title: "Livebook - Settings" )} end @@ -45,9 +44,9 @@ defmodule LivebookWeb.SettingsLive do
-

+

About -

+
<%= if app_name = Livebook.Config.app_service_name() do %> @@ -76,6 +75,18 @@ defmodule LivebookWeb.SettingsLive do <% end %>
+ +
+

+ Preferences +

+
+ <.switch_checkbox + name="update_notifications_enabled" + label="Show available Livebook updates" + checked={@update_notifications_enabled} /> +
+
@@ -253,6 +264,12 @@ defmodule LivebookWeb.SettingsLive do {:noreply, assign(socket, file_systems: file_systems)} end + def handle_event("save", %{"update_notifications_enabled" => enabled}, socket) do + enabled = enabled == "true" + Livebook.Settings.set_update_notifications_enabled(enabled) + {:noreply, assign(socket, :update_notifications_enabled, enabled)} + end + @impl true def handle_info({:file_systems_updated, file_systems}, socket) do {:noreply, assign(socket, file_systems: file_systems)} From 78d4d0b900bdc3e7c82128c8534c3a62dcad9f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Fri, 15 Apr 2022 22:03:49 +0200 Subject: [PATCH 02/10] Wording --- lib/livebook/settings.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/livebook/settings.ex b/lib/livebook/settings.ex index 8840342c29d..cbd5c60b783 100644 --- a/lib/livebook/settings.ex +++ b/lib/livebook/settings.ex @@ -100,7 +100,7 @@ defmodule Livebook.Settings do end @doc """ - Returns the user preference for update check. + Returns whether the user wants to see update notifications. """ @spec update_notifications_enabled?() :: boolean() def update_notifications_enabled?() do @@ -111,7 +111,7 @@ defmodule Livebook.Settings do end @doc """ - Sets the user preference for update check. + Sets user preference for update notifications. """ @spec set_update_notifications_enabled(boolean()) :: :ok def set_update_notifications_enabled(enabled) do From 9d2d21587a69391e436bf567d14f141889f75e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Fri, 15 Apr 2022 22:09:55 +0200 Subject: [PATCH 03/10] Use more precise version comparison --- lib/livebook/update_check.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/livebook/update_check.ex b/lib/livebook/update_check.ex index 3e77fc4069a..f3427f07d49 100644 --- a/lib/livebook/update_check.ex +++ b/lib/livebook/update_check.ex @@ -26,10 +26,10 @@ defmodule Livebook.UpdateCheck do current_version = Application.spec(:livebook, :vsn) |> List.to_string() latest_version = get_version() - if latest_version in [nil, current_version] do - nil - else + if latest_version && Version.compare(current_version, latest_version) == :lt do latest_version + else + nil end end From 441c9ec5b7d72a8dfe55e97ef92cf18a4b6ee3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Fri, 15 Apr 2022 23:35:49 +0200 Subject: [PATCH 04/10] Update lib/livebook/application.ex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Valim --- lib/livebook/application.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/livebook/application.ex b/lib/livebook/application.ex index 0f7ef73cf90..0f3f32b3eee 100644 --- a/lib/livebook/application.ex +++ b/lib/livebook/application.ex @@ -23,7 +23,7 @@ defmodule Livebook.Application do [ # Start the storage module Livebook.Storage.current(), - # Periodid measurement of system resources + # Periodic measurement of system resources Livebook.SystemResources, # Start the tracker server on this node {Livebook.Tracker, pubsub_server: Livebook.PubSub}, From 7b665518673c1d12e361d1fc8f7a99e378ed6b35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Fri, 15 Apr 2022 23:49:16 +0200 Subject: [PATCH 05/10] Up --- README.md | 3 +- config/config.exs | 4 +- lib/livebook.ex | 12 +-- lib/livebook/application.ex | 48 +++++------ lib/livebook/config.ex | 20 +---- lib/livebook/settings.ex | 16 ++-- lib/livebook/update_check.ex | 111 +++++++++++++++++++------ lib/livebook_web/live/home_live.ex | 24 ++---- lib/livebook_web/live/settings_live.ex | 12 +-- 9 files changed, 137 insertions(+), 113 deletions(-) diff --git a/README.md b/README.md index 66a06adfcf9..4a6075cddfc 100644 --- a/README.md +++ b/README.md @@ -197,8 +197,7 @@ The following environment variables configure Livebook: disable it. * LIVEBOOK_UPDATE_INSTRUCTIONS_URL - sets the URL to direct the user to for - updating Livebook when a new version becomes available. Defaults to livebook.dev. - Set it to 0 to disable the update check. + updating Livebook when a new version becomes available. diff --git a/config/config.exs b/config/config.exs index 3aaa46c8685..ded1955cc7d 100644 --- a/config/config.exs +++ b/config/config.exs @@ -26,9 +26,7 @@ config :livebook, explore_notebooks: [], plugs: [], shutdown_enabled: false, - storage: Livebook.Storage.Ets, - update_check_enabled: true, - update_instructions_url: "https://livebook.dev/#install" + storage: Livebook.Storage.Ets # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. diff --git a/lib/livebook.ex b/lib/livebook.ex index 897ec205821..bc95987a662 100644 --- a/lib/livebook.ex +++ b/lib/livebook.ex @@ -156,15 +156,9 @@ defmodule Livebook do Livebook.Config.app_service_url!("LIVEBOOK_APP_SERVICE_URL") end - case Livebook.Config.update_instructions_url!("LIVEBOOK_UPDATE_INSTRUCTIONS_URL") do - nil -> - :ok - - :disabled -> - config :livebook, :update_check_enabled, false - - url -> - config :livebook, :update_instructions_url, url + if update_instructions_url = + Livebook.Config.update_instructions_url!("LIVEBOOK_UPDATE_INSTRUCTIONS_URL") do + config :livebook, :update_instructions_url, update_instructions_url end end diff --git a/lib/livebook/application.ex b/lib/livebook/application.ex index 0f3f32b3eee..368fdedca55 100644 --- a/lib/livebook/application.ex +++ b/lib/livebook/application.ex @@ -17,27 +17,26 @@ defmodule Livebook.Application do # Start the Telemetry supervisor LivebookWeb.Telemetry, # Start the PubSub system - {Phoenix.PubSub, name: Livebook.PubSub} + {Phoenix.PubSub, name: Livebook.PubSub}, + # Start the storage module + Livebook.Storage.current(), + # Start the periodic version check + Livebook.UpdateCheck, + # Periodic measurement of system resources + Livebook.SystemResources, + # Start the tracker server on this node + {Livebook.Tracker, pubsub_server: Livebook.PubSub}, + # Start the supervisor dynamically managing sessions + {DynamicSupervisor, name: Livebook.SessionSupervisor, strategy: :one_for_one}, + # Start a supervisor for Livebook tasks + {Task.Supervisor, name: Livebook.TaskSupervisor}, + # Start the server responsible for associating files with sessions + Livebook.Session.FileGuard, + # Start the Node Pool for managing node names + Livebook.Runtime.NodePool, + # Start the unique task dependencies + Livebook.Utils.UniqueTask ] ++ - update_check_specs() ++ - [ - # Start the storage module - Livebook.Storage.current(), - # Periodic measurement of system resources - Livebook.SystemResources, - # Start the tracker server on this node - {Livebook.Tracker, pubsub_server: Livebook.PubSub}, - # Start the supervisor dynamically managing sessions - {DynamicSupervisor, name: Livebook.SessionSupervisor, strategy: :one_for_one}, - # Start a supervisor for Livebook tasks - {Task.Supervisor, name: Livebook.TaskSupervisor}, - # Start the server responsible for associating files with sessions - Livebook.Session.FileGuard, - # Start the Node Pool for managing node names - Livebook.Runtime.NodePool, - # Start the unique task dependencies - Livebook.Utils.UniqueTask - ] ++ iframe_server_specs() ++ [ # Start the Endpoint (http/https) @@ -189,15 +188,6 @@ defmodule Livebook.Application do defp config_env_var?("RELEASE_" <> _), do: true defp config_env_var?(_), do: false - defp update_check_specs() do - if Livebook.Config.update_check_enabled?() do - # Start the periodic version check - [Livebook.UpdateCheck] - else - [] - end - end - if Mix.target() == :app do defp app_specs, do: [LivebookApp] else diff --git a/lib/livebook/config.ex b/lib/livebook/config.ex index b000b9b7f67..0eaa2ce8891 100644 --- a/lib/livebook/config.ex +++ b/lib/livebook/config.ex @@ -122,20 +122,12 @@ defmodule Livebook.Config do Application.fetch_env!(:livebook, :app_service_url) end - @doc """ - Returns whether the update check is enabled. - """ - @spec update_check_enabled?() :: boolean() - def update_check_enabled?() do - Application.fetch_env!(:livebook, :update_check_enabled) - end - @doc """ Returns the update check URL. """ - @spec update_instructions_url() :: String.t() + @spec update_instructions_url() :: String.t() | nil def update_instructions_url() do - Application.fetch_env!(:livebook, :update_instructions_url) + Application.get_env(:livebook, :update_instructions_url) end ## Parsing @@ -272,15 +264,9 @@ defmodule Livebook.Config do @doc """ Parses update instructions url from env. - - When the env is set to "0", returns `:disabled`. """ def update_instructions_url!(env) do - case System.get_env(env) do - nil -> nil - "0" -> :disabled - url -> url - end + System.get_env(env) end @doc """ diff --git a/lib/livebook/settings.ex b/lib/livebook/settings.ex index cbd5c60b783..cf270cf9249 100644 --- a/lib/livebook/settings.ex +++ b/lib/livebook/settings.ex @@ -100,21 +100,21 @@ defmodule Livebook.Settings do end @doc """ - Returns whether the user wants to see update notifications. + Returns whether the update check is enabled. """ - @spec update_notifications_enabled?() :: boolean() - def update_notifications_enabled?() do - case storage().fetch_key(:settings, "global", :update_notifications_enabled) do + @spec update_check_enabled?() :: boolean() + def update_check_enabled?() do + case storage().fetch_key(:settings, "global", :update_check_enabled) do {:ok, value} -> value :error -> true end end @doc """ - Sets user preference for update notifications. + Sets whether the update check is enabled. """ - @spec set_update_notifications_enabled(boolean()) :: :ok - def set_update_notifications_enabled(enabled) do - storage().insert(:settings, "global", update_notifications_enabled: enabled) + @spec set_update_check_enabled(boolean()) :: :ok + def set_update_check_enabled(enabled) do + storage().insert(:settings, "global", update_check_enabled: enabled) end end diff --git a/lib/livebook/update_check.ex b/lib/livebook/update_check.ex index f3427f07d49..700069288d4 100644 --- a/lib/livebook/update_check.ex +++ b/lib/livebook/update_check.ex @@ -7,14 +7,15 @@ defmodule Livebook.UpdateCheck do require Logger - @version_term __MODULE__ + @name __MODULE__ + @timeout :infinity @hour_in_ms 60 * 60 * 1000 @day_in_ms 24 * @hour_in_ms @doc false def start_link(_opts) do - GenServer.start_link(__MODULE__, {}) + GenServer.start_link(__MODULE__, {}, name: @name) end @doc """ @@ -23,38 +24,96 @@ defmodule Livebook.UpdateCheck do """ @spec new_version() :: String.t() | nil def new_version() do - current_version = Application.spec(:livebook, :vsn) |> List.to_string() - latest_version = get_version() + GenServer.call(@name, :get_new_version, @timeout) + end - if latest_version && Version.compare(current_version, latest_version) == :lt do - latest_version - else - nil - end + @doc """ + Returns whether the update check is enabled. + """ + @spec enabled?() :: boolean() + def enabled?() do + GenServer.call(@name, :get_enabled, @timeout) + end + + @doc """ + Sets whether the update check is enabled. + """ + @spec set_enabled(boolean()) :: :ok + def set_enabled(enabled) do + GenServer.cast(@name, {:set_enabled, enabled}) end @impl true def init({}) do send(self(), :check) - {:ok, %{}} + + {:ok, + %{ + enabled: Livebook.Settings.update_check_enabled?(), + new_version: nil, + timer_ref: nil + }} + end + + @impl true + def handle_cast({:set_enabled, enabled}, state) do + Livebook.Settings.set_update_check_enabled(enabled) + + state = cancel_timer(state) + + if enabled do + send(self(), :check) + end + + {:noreply, %{state | enabled: enabled}} + end + + @impl true + def handle_call(:get_enabled, _from, state) do + {:reply, state.enabled, state} + end + + @impl true + def handle_call(:get_new_version, _from, state) do + new_version = if state.enabled, do: state.new_version + {:reply, new_version, state} end @impl true def handle_info(:check, state) do - case check_release() do - {:ok, version} -> - put_version(version) - Process.send_after(self(), :check, @day_in_ms) - - {:error, error} -> - Logger.error("version check failed, #{error}") - Process.send_after(self(), :check, @hour_in_ms) + state = + if state.enabled do + case fetch_latest_version() do + {:ok, version} -> + new_version = if newer?(version), do: version + timer_ref = Process.send_after(self(), :check, @day_in_ms) + %{state | new_version: new_version, timer_ref: timer_ref} + + {:error, error} -> + Logger.error("version check failed, #{error}") + timer_ref = Process.send_after(self(), :check, @hour_in_ms) + %{state | timer_ref: timer_ref} + end + else + state + end + + {:noreply, state} + end + + defp cancel_timer(%{timer_ref: nil} = state), do: state + + defp cancel_timer(state) do + if Process.cancel_timer(state.timer_ref) == false do + receive do + :check -> :ok + end end - {:noreply, state, :hibernate} + %{state | timer_ref: nil} end - defp check_release() do + defp fetch_latest_version() do url = "https://api.github.com/repos/livebook-dev/livebook/releases/latest" headers = [{"accept", "application/vnd.github.v3+json"}] @@ -73,11 +132,15 @@ defmodule Livebook.UpdateCheck do end end - defp put_version(version) do - :persistent_term.put(@version_term, version) + defp newer?(version) do + current_version = Application.spec(:livebook, :vsn) |> List.to_string() + stable?(version) and Version.compare(current_version, version) == :lt end - defp get_version() do - :persistent_term.get(@version_term, nil) + defp stable?(version) do + case Version.parse(version) do + {:ok, %{pre: []}} -> true + _ -> false + end end end diff --git a/lib/livebook_web/live/home_live.ex b/lib/livebook_web/live/home_live.ex index e145c4987bd..ea048fddfeb 100644 --- a/lib/livebook_web/live/home_live.ex +++ b/lib/livebook_web/live/home_live.ex @@ -16,10 +16,6 @@ defmodule LivebookWeb.HomeLive do sessions = Sessions.list_sessions() notebook_infos = Notebook.Explore.visible_notebook_infos() |> Enum.take(3) - new_version = - if Livebook.Settings.update_notifications_enabled?(), - do: Livebook.UpdateCheck.new_version() - {:ok, socket |> SidebarHelpers.shared_home_handlers() @@ -30,7 +26,7 @@ defmodule LivebookWeb.HomeLive do sessions: sessions, notebook_infos: notebook_infos, page_title: "Livebook", - new_version: new_version, + new_version: Livebook.UpdateCheck.new_version(), update_instructions_url: Livebook.Config.update_instructions_url() )} end @@ -165,16 +161,14 @@ defmodule LivebookWeb.HomeLive do defp update_notification(assigns) do ~H""" -
- - Livebook v<%= @version %> available! Check out the - release notes 🚀 - - Update +
+ Livebook v<%= @version %> available! Check out the news on + livebook.dev + <%= if @instructions_url do %> + and follow the + update instructions + <% end %> + 🚀
""" end diff --git a/lib/livebook_web/live/settings_live.ex b/lib/livebook_web/live/settings_live.ex index 54e4b4f889a..7bca9fd80b7 100644 --- a/lib/livebook_web/live/settings_live.ex +++ b/lib/livebook_web/live/settings_live.ex @@ -16,7 +16,7 @@ defmodule LivebookWeb.SettingsLive do file: autosave_dir(), dialog_opened?: false }, - update_notifications_enabled: Livebook.Settings.update_notifications_enabled?(), + update_check_enabled: Livebook.UpdateCheck.enabled?(), page_title: "Livebook - Settings" )} end @@ -82,9 +82,9 @@ defmodule LivebookWeb.SettingsLive do
<.switch_checkbox - name="update_notifications_enabled" + name="update_check_enabled" label="Show available Livebook updates" - checked={@update_notifications_enabled} /> + checked={@update_check_enabled} />
@@ -264,10 +264,10 @@ defmodule LivebookWeb.SettingsLive do {:noreply, assign(socket, file_systems: file_systems)} end - def handle_event("save", %{"update_notifications_enabled" => enabled}, socket) do + def handle_event("save", %{"update_check_enabled" => enabled}, socket) do enabled = enabled == "true" - Livebook.Settings.set_update_notifications_enabled(enabled) - {:noreply, assign(socket, :update_notifications_enabled, enabled)} + Livebook.UpdateCheck.set_enabled(enabled) + {:noreply, assign(socket, :update_check_enabled, enabled)} end @impl true From fbf79a1ce9eebdadcde8bbd1248399e187450cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Sat, 16 Apr 2022 02:20:28 +0200 Subject: [PATCH 06/10] Update notificaion styles --- lib/livebook_web/live/home_live.ex | 48 +++++++++++++++++------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/lib/livebook_web/live/home_live.ex b/lib/livebook_web/live/home_live.ex index b7e1d117aac..36f264c93d0 100644 --- a/lib/livebook_web/live/home_live.ex +++ b/lib/livebook_web/live/home_live.ex @@ -41,10 +41,10 @@ defmodule LivebookWeb.HomeLive do -
-
- <.update_notification version={@new_version} instructions_url={@update_instructions_url} /> - <.memory_notification memory={@memory} app_service_url={@app_service_url} /> +
+ <.update_notification version={@new_version} instructions_url={@update_instructions_url} /> + <.memory_notification memory={@memory} app_service_url={@app_service_url} /> +
@@ -164,14 +164,20 @@ defmodule LivebookWeb.HomeLive do defp update_notification(assigns) do ~H""" -
- Livebook v<%= @version %> available! Check out the news on - livebook.dev - <%= if @instructions_url do %> - and follow the - update instructions - <% end %> - 🚀 +
+ + Livebook v<%= @version %> available! Check out the news on + + livebook.dev + + <%= if @instructions_url do %> + and follow the + + update instructions + + <% end %> + 🚀 +
""" end @@ -179,15 +185,15 @@ defmodule LivebookWeb.HomeLive do defp memory_notification(assigns) do ~H""" <%= if @app_service_url && @memory.free < 30_000_000 do %> -
- - <.remix_icon icon="alarm-warning-line" class="text-xl mr-2" /> - - Less than 30 MB of memory left, consider adding more resources to - the instance - or closing running sessions. - - +
+ Less than 30 MB of memory left, consider adding more resources to + + the instance + + or closing + + running sessions + .
<% end %> """ From 706e37796edbece8f5c143adca5b714b1e0c4894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Sat, 16 Apr 2022 23:42:38 +0200 Subject: [PATCH 07/10] Update lib/livebook_web/live/home_live.ex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Valim --- lib/livebook_web/live/home_live.ex | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/livebook_web/live/home_live.ex b/lib/livebook_web/live/home_live.ex index 36f264c93d0..363317e7a2e 100644 --- a/lib/livebook_web/live/home_live.ex +++ b/lib/livebook_web/live/home_live.ex @@ -166,15 +166,21 @@ defmodule LivebookWeb.HomeLive do ~H"""
- Livebook v<%= @version %> available! Check out the news on - - livebook.dev - + Livebook v<%= @version %> available! <%= if @instructions_url do %> + Check out the news on + + livebook.dev + and follow the update instructions + <% else %> + Check out the news and installation steps on + + livebook.dev + <% end %> 🚀 From 0c5254fbf1694d5a386e16e6c3d501273f5b9c5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Sat, 16 Apr 2022 23:43:04 +0200 Subject: [PATCH 08/10] Update lib/livebook_web/live/home_live.ex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Valim --- lib/livebook_web/live/home_live.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/livebook_web/live/home_live.ex b/lib/livebook_web/live/home_live.ex index 363317e7a2e..bbf6e8304ea 100644 --- a/lib/livebook_web/live/home_live.ex +++ b/lib/livebook_web/live/home_live.ex @@ -192,9 +192,9 @@ defmodule LivebookWeb.HomeLive do ~H""" <%= if @app_service_url && @memory.free < 30_000_000 do %>
- Less than 30 MB of memory left, consider adding more resources to + Less than 30 MB of memory left, consider - the instance + adding more resources to the instance or closing From f8d41e9cc223c60753edf5cc10d51c0f6f83865f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Sat, 16 Apr 2022 23:53:05 +0200 Subject: [PATCH 09/10] Apply review comments --- lib/livebook/application.ex | 4 +- lib/livebook/update_check.ex | 70 ++++++++++++++++-------------- lib/livebook_web/live/home_live.ex | 13 ++---- 3 files changed, 44 insertions(+), 43 deletions(-) diff --git a/lib/livebook/application.ex b/lib/livebook/application.ex index 368fdedca55..ee722dc6f97 100644 --- a/lib/livebook/application.ex +++ b/lib/livebook/application.ex @@ -18,6 +18,8 @@ defmodule Livebook.Application do LivebookWeb.Telemetry, # Start the PubSub system {Phoenix.PubSub, name: Livebook.PubSub}, + # Start a supervisor for Livebook tasks + {Task.Supervisor, name: Livebook.TaskSupervisor}, # Start the storage module Livebook.Storage.current(), # Start the periodic version check @@ -28,8 +30,6 @@ defmodule Livebook.Application do {Livebook.Tracker, pubsub_server: Livebook.PubSub}, # Start the supervisor dynamically managing sessions {DynamicSupervisor, name: Livebook.SessionSupervisor, strategy: :one_for_one}, - # Start a supervisor for Livebook tasks - {Task.Supervisor, name: Livebook.TaskSupervisor}, # Start the server responsible for associating files with sessions Livebook.Session.FileGuard, # Start the Node Pool for managing node names diff --git a/lib/livebook/update_check.ex b/lib/livebook/update_check.ex index 700069288d4..2419d738cef 100644 --- a/lib/livebook/update_check.ex +++ b/lib/livebook/update_check.ex @@ -45,27 +45,21 @@ defmodule Livebook.UpdateCheck do @impl true def init({}) do - send(self(), :check) - - {:ok, - %{ - enabled: Livebook.Settings.update_check_enabled?(), - new_version: nil, - timer_ref: nil - }} + state = %{ + enabled: Livebook.Settings.update_check_enabled?(), + new_version: nil, + timer_ref: nil + } + + {:ok, schedule_check(state, 0)} end @impl true def handle_cast({:set_enabled, enabled}, state) do Livebook.Settings.set_update_check_enabled(enabled) - - state = cancel_timer(state) - - if enabled do - send(self(), :check) - end - - {:noreply, %{state | enabled: enabled}} + state = %{state | enabled: enabled} + state = state |> cancel_check() |> schedule_check(0) + {:noreply, state} end @impl true @@ -81,29 +75,41 @@ defmodule Livebook.UpdateCheck do @impl true def handle_info(:check, state) do + myself = self() + + Task.Supervisor.start_child(Livebook.TaskSupervisor, fn -> + send(myself, {:check_response, fetch_latest_version()}) + end) + + {:noreply, state} + end + + def handle_info({:check_response, response}, state) do state = - if state.enabled do - case fetch_latest_version() do - {:ok, version} -> - new_version = if newer?(version), do: version - timer_ref = Process.send_after(self(), :check, @day_in_ms) - %{state | new_version: new_version, timer_ref: timer_ref} - - {:error, error} -> - Logger.error("version check failed, #{error}") - timer_ref = Process.send_after(self(), :check, @hour_in_ms) - %{state | timer_ref: timer_ref} - end - else - state + case response do + {:ok, version} -> + new_version = if newer?(version), do: version + state = %{state | new_version: new_version} + schedule_check(state, @day_in_ms) + + {:error, error} -> + Logger.error("version check failed, #{error}") + schedule_check(state, @hour_in_ms) end {:noreply, state} end - defp cancel_timer(%{timer_ref: nil} = state), do: state + defp schedule_check(%{enabled: false} = state, _time), do: state + + defp schedule_check(state, time) do + timer_ref = Process.send_after(self(), :check, time) + %{state | timer_ref: timer_ref} + end + + defp cancel_check(%{timer_ref: nil} = state), do: state - defp cancel_timer(state) do + defp cancel_check(state) do if Process.cancel_timer(state.timer_ref) == false do receive do :check -> :ok diff --git a/lib/livebook_web/live/home_live.ex b/lib/livebook_web/live/home_live.ex index bbf6e8304ea..b8de0ba907d 100644 --- a/lib/livebook_web/live/home_live.ex +++ b/lib/livebook_web/live/home_live.ex @@ -178,9 +178,7 @@ defmodule LivebookWeb.HomeLive do <% else %> Check out the news and installation steps on - - livebook.dev - + livebook.dev <% end %> 🚀 @@ -192,14 +190,11 @@ defmodule LivebookWeb.HomeLive do ~H""" <%= if @app_service_url && @memory.free < 30_000_000 do %>
+ <.remix_icon icon="alarm-warning-line" class="align-text-bottom mr-0.5" /> Less than 30 MB of memory left, consider - - adding more resources to the instance - + adding more resources to the instance or closing - - running sessions - . + running sessions
<% end %> """ From 4d0260e53a62ad639b05233ef9e7132b7ec4e8ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Mon, 18 Apr 2022 00:46:18 +0200 Subject: [PATCH 10/10] Use async_nolink --- lib/livebook/update_check.ex | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/livebook/update_check.ex b/lib/livebook/update_check.ex index 2419d738cef..01e94e2dbea 100644 --- a/lib/livebook/update_check.ex +++ b/lib/livebook/update_check.ex @@ -48,7 +48,8 @@ defmodule Livebook.UpdateCheck do state = %{ enabled: Livebook.Settings.update_check_enabled?(), new_version: nil, - timer_ref: nil + timer_ref: nil, + request_ref: nil } {:ok, schedule_check(state, 0)} @@ -75,16 +76,13 @@ defmodule Livebook.UpdateCheck do @impl true def handle_info(:check, state) do - myself = self() - - Task.Supervisor.start_child(Livebook.TaskSupervisor, fn -> - send(myself, {:check_response, fetch_latest_version()}) - end) - - {:noreply, state} + task = Task.Supervisor.async_nolink(Livebook.TaskSupervisor, &fetch_latest_version/0) + {:noreply, %{state | request_ref: task.ref}} end - def handle_info({:check_response, response}, state) do + def handle_info({ref, response}, %{request_ref: ref} = state) do + Process.demonitor(ref, [:flush]) + state = case response do {:ok, version} -> @@ -97,9 +95,16 @@ defmodule Livebook.UpdateCheck do schedule_check(state, @hour_in_ms) end - {:noreply, state} + {:noreply, %{state | request_ref: nil}} + end + + def handle_info({:DOWN, ref, :process, _pid, reason}, %{request_ref: ref} = state) do + Logger.error("version check failed, reason: #{inspect(reason)}") + {:noreply, %{state | request_ref: nil} |> schedule_check(@hour_in_ms)} end + def handle_info(_msg, state), do: {:noreply, state} + defp schedule_check(%{enabled: false} = state, _time), do: state defp schedule_check(state, time) do