-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
872 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
elixir 1.17.2 | ||
erlang 27.0 | ||
erlang 27.0.1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
defmodule Mix.Tasks.Umwelt.Clone do | ||
@moduledoc "Clones phase modules and code" | ||
@shortdoc "The code puller" | ||
use Mix.Task | ||
require Logger | ||
|
||
@impl Mix.Task | ||
def run([phase_id]) do | ||
case System.get_env("UMWELT_TOKEN", "no_token") do | ||
"no_token" -> | ||
""" | ||
Token not found in env! | ||
You can get it on umwelt.dev/auth/profile and do | ||
export UMWELT_TOKEN="token" | ||
or pass it directly in | ||
mix clone phase_id "token" | ||
""" | ||
|> Logger.warning() | ||
|
||
token -> | ||
run([phase_id, token]) | ||
end | ||
end | ||
|
||
@impl Mix.Task | ||
def run([phase_id, token]) do | ||
Umwelt.Client.Application.start(nil, nil) | ||
|
||
Umwelt.Client.Supervisor | ||
|> Process.whereis() | ||
|> Process.monitor() | ||
|
||
%{phase_id: phase_id, token: token} | ||
|> assign_host() | ||
|> assign_port() | ||
|> Umwelt.Client.pull() | ||
|
||
receive do | ||
{:DOWN, _, :process, _, _} -> | ||
Logger.info("Done!") | ||
|
||
other -> | ||
Logger.warning(inspect(other)) | ||
end | ||
end | ||
|
||
defp assign_host(params) do | ||
host = | ||
case Mix.env() do | ||
:dev -> | ||
System.get_env("UMWELT_HOST", "https://umwelt.dev") | ||
|
||
:test -> | ||
"http://localhost" | ||
end | ||
|
||
Map.put(params, :api_host, host) | ||
end | ||
|
||
defp assign_port(params) do | ||
port = | ||
case Mix.env() do | ||
:dev -> | ||
case params.api_host do | ||
"http://localhost" -> 4000 | ||
"https://umwelt.dev" -> 443 | ||
end | ||
|
||
:test -> | ||
Application.get_env(:umwelt, :api_port) | ||
end | ||
|
||
Map.put(params, :port, port) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
defmodule Umwelt.Client do | ||
@moduledoc "Client for umwelt.dev" | ||
|
||
alias Umwelt.Client.Clone | ||
|
||
require Logger | ||
|
||
def pull(params) do | ||
GenServer.cast(Clone, {:pull, params}) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
defmodule Umwelt.Client.Agent do | ||
@moduledoc "Keeps pulling metadata" | ||
|
||
use Agent | ||
require Logger | ||
|
||
def start_link(_args) do | ||
Agent.start_link( | ||
fn -> | ||
%{ | ||
modules: %{}, | ||
waiting: [], | ||
fetching: [], | ||
fetched: [], | ||
writing: [], | ||
written: [], | ||
total: 0 | ||
} | ||
end, | ||
name: __MODULE__ | ||
) | ||
end | ||
|
||
def all_waiting, do: Agent.get(__MODULE__, fn state -> state.waiting end) | ||
|
||
def completed?, | ||
do: Agent.get(__MODULE__, fn state -> state.total == Enum.count(state.written) end) | ||
|
||
def state, do: Agent.get(__MODULE__, fn state -> state end) | ||
def total, do: Agent.get(__MODULE__, fn state -> state.total end) | ||
def ready, do: Agent.get(__MODULE__, fn state -> Enum.count(state.written) end) | ||
|
||
def add_modules(modules) do | ||
Agent.update(__MODULE__, fn state -> | ||
%{ | ||
state | ||
| modules: modules, | ||
waiting: Map.keys(modules), | ||
fetching: [], | ||
fetched: [], | ||
writing: [], | ||
written: [], | ||
total: map_size(modules) | ||
} | ||
end) | ||
end | ||
|
||
def next_waiting do | ||
Agent.get_and_update(__MODULE__, fn state -> | ||
case state.waiting do | ||
[mod_name | modules] -> | ||
{ | ||
%{id: state.modules[mod_name], name: mod_name}, | ||
state | ||
|> Map.put(:waiting, modules) | ||
|> Map.put(:fetching, [mod_name | state.fetching]) | ||
} | ||
|
||
[] -> | ||
{nil, state} | ||
end | ||
end) | ||
end | ||
|
||
def update_status(mod_name, :fetched) do | ||
Agent.update(__MODULE__, fn state -> | ||
state | ||
|> Map.put(:fetching, List.delete(state.fetching, mod_name)) | ||
|> Map.put(:fetched, [mod_name | state.fetched]) | ||
end) | ||
end | ||
|
||
def update_status(mod_name, :writing) do | ||
Agent.update(__MODULE__, fn state -> | ||
state | ||
|> Map.put(:writing, [mod_name | state.writing]) | ||
end) | ||
end | ||
|
||
def update_status(mod_name, :written) do | ||
Agent.update(__MODULE__, fn state -> | ||
state | ||
|> Map.put(:writing, List.delete(state.writing, mod_name)) | ||
|> Map.put(:written, [mod_name | state.written]) | ||
end) | ||
|
||
render_progress() | ||
end | ||
|
||
def render_progress() do | ||
ProgressBar.render(ready(), total(), suffix: :count) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
defmodule Umwelt.Client.Application do | ||
@moduledoc "Client app & Supervisor" | ||
|
||
use Application | ||
|
||
def start(_type, _args) do | ||
children = [ | ||
{Umwelt.Client.Agent, []}, | ||
{Umwelt.Client.Clone, []} | ||
] | ||
|
||
opts = [strategy: :one_for_one, name: Umwelt.Client.Supervisor] | ||
Supervisor.start_link(children, opts) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
defmodule Umwelt.Client.Clone do | ||
@moduledoc "Clone main process" | ||
|
||
use GenServer | ||
require Logger | ||
|
||
alias Umwelt.Client | ||
|
||
def start_link(_), | ||
do: | ||
GenServer.start_link( | ||
__MODULE__, | ||
%{phase_id: nil, port: nil}, | ||
name: __MODULE__ | ||
) | ||
|
||
def init(state), do: {:ok, state} | ||
|
||
def handle_cast({:pull, params}, _state) do | ||
send(self(), :start_pulling) | ||
|
||
{:noreply, params} | ||
end | ||
|
||
def handle_info(:start_pulling, state) do | ||
case Client.Request.fetch_modules(state) do | ||
{:ok, modules} -> | ||
Logger.info("Fetching modules: #{inspect(Map.keys(modules))}") | ||
modules |> Client.Agent.add_modules() | ||
|
||
{:error, reason} -> | ||
Logger.error("Failed to fetch modules: #{inspect(reason)}. Stopping...") | ||
Supervisor.stop(Client.Supervisor) | ||
end | ||
|
||
send(self(), :spawn_fetchers) | ||
|
||
{:noreply, state} | ||
end | ||
|
||
def handle_info(:spawn_fetchers, state) do | ||
total = Client.Agent.total() | ||
Logger.debug("Spawning fetchers: #{total}") | ||
|
||
Client.Agent.all_waiting() | ||
|> Enum.each(fn _ -> spawn_fetcher(state) end) | ||
|
||
{:noreply, state} | ||
end | ||
|
||
def handle_info({:fetched, %{name: name, code: code}}, state) do | ||
Client.Agent.update_status(name, :fetched) | ||
spawn_writer(%{name: name, code: code}) | ||
{:noreply, state} | ||
end | ||
|
||
def handle_info({:fetch_failed, module}, state) do | ||
Logger.warning("Respawning failed fetcher for module #{module.name}") | ||
Client.Fetcher.start_link(module) | ||
{:noreply, state} | ||
end | ||
|
||
def handle_info({:written, mod_name}, state) do | ||
Client.Agent.update_status(mod_name, :written) | ||
send(self(), :maybe_stop) | ||
{:noreply, state} | ||
end | ||
|
||
def handle_info(:maybe_stop, state) do | ||
if Client.Agent.completed?() do | ||
Logger.debug("All modules processed. Stopping application.") | ||
Supervisor.stop(Client.Supervisor) | ||
end | ||
|
||
{:noreply, state} | ||
end | ||
|
||
defp spawn_fetcher(state) do | ||
case Client.Agent.next_waiting() do | ||
nil -> | ||
Logger.debug("No more modules to fetch") | ||
:ok | ||
|
||
module -> | ||
Logger.debug("Spawning fetcher for module #{inspect(module.name)}") | ||
Client.Fetcher.start_link(Map.merge(module, state)) | ||
end | ||
end | ||
|
||
defp spawn_writer(%{name: name} = module) do | ||
Logger.debug("Spawning writer for module #{inspect(name)}") | ||
Client.Agent.update_status(name, :writing) | ||
Client.Writer.start_link(module) | ||
end | ||
end |
Oops, something went wrong.