farmbot_os/farmbot_core/lib/farmbot_core/asset_supervisor.ex

121 lines
3.1 KiB
Elixir

defmodule FarmbotCore.AssetSupervisor do
@moduledoc """
Supervises all database-backed records.
"""
use Supervisor
alias FarmbotCore.{Asset.Repo, AssetWorker}
def get_state(%{} = asset) do
case whereis_child(asset) do
{_id, pid, _, _} -> :sys.get_state(pid)
_ -> :error
end
end
def get_pid(%{} = asset) do
case whereis_child(asset) do
{_id, pid, _, _} -> pid
_ -> :error
end
end
@doc "List all children for an asset"
def list_children(kind) do
name = Module.concat(__MODULE__, kind)
Supervisor.which_children(name)
end
@doc "looks up a pid for an asset"
def whereis_child(%kind{local_id: id}) do
:ok = Protocol.assert_impl!(AssetWorker, kind)
name = Module.concat(__MODULE__, kind)
Supervisor.which_children(name)
|> Enum.find(fn {sup_id, _pid, _type, _} ->
sup_id == id
end)
end
# TODO(Connor) does this really belong here?
@doc "Does a GenServer call on a server"
def cast_child(%{} = data, cast) do
case whereis_child(data) do
nil -> :error
{_, pid, _, _} -> GenServer.cast(pid, cast)
end
end
@doc "Start a process that manages an asset"
def start_child(%kind{local_id: id} = asset) when is_binary(id) do
:ok = Protocol.assert_impl!(AssetWorker, kind)
name = Module.concat(__MODULE__, kind)
spec = worker_spec(asset)
Supervisor.start_child(name, spec)
end
@doc "Removes a child if it exists"
def terminate_child(kind, id) when is_binary(id) do
:ok = Protocol.assert_impl!(AssetWorker, kind)
name = Module.concat(__MODULE__, kind)
Supervisor.terminate_child(name, id)
end
@doc "Updates a child if it exists"
def update_child(%kind{local_id: id} = asset) do
:ok = Protocol.assert_impl!(AssetWorker, kind)
# if the worker tracks changes, we don't want to
# restart it.
if AssetWorker.tracks_changes?(asset) do
cast_child(asset, {:new_data, asset})
{_, pid, _, _} = whereis_child(asset)
{:ok, pid}
else
name = Module.concat(__MODULE__, kind)
_ = terminate_child(kind, id)
_ = Supervisor.delete_child(name, id)
start_child(asset)
end
end
# Non public supervisor stuff
@doc false
def child_spec(args) do
module = Keyword.fetch!(args, :module)
id_and_name = Module.concat(__MODULE__, module)
%{
id: id_and_name,
start: {__MODULE__, :start_link, [args]}
}
end
@doc false
def worker_spec(%{local_id: id, monitor: true} = asset) do
%{
id: id,
start: {AssetWorker, :start_link, [asset]},
restart: :transient
}
end
@doc false
def start_link(args) when is_list(args) do
module = Keyword.fetch!(args, :module)
Supervisor.start_link(__MODULE__, args, name: Module.concat(__MODULE__, module))
end
@doc false
def init(args) do
module = Keyword.fetch!(args, :module)
module
|> Repo.all()
|> Enum.filter(fn %{monitor: mon} -> mon == false end)
|> Enum.map(&Repo.preload(&1, AssetWorker.preload(&1)))
|> Enum.map(&worker_spec/1)
|> Supervisor.init(strategy: :one_for_one)
end
end