109 lines
2.6 KiB
Elixir
109 lines
2.6 KiB
Elixir
defmodule Forum.Forms.Watcher do
|
|
@moduledoc """
|
|
Watches the canonical forms.html file and reloads `Forum.Forms` when it changes.
|
|
|
|
This intentionally uses a small polling loop instead of an external file watcher
|
|
dependency so it works the same way in dev, test, and releases.
|
|
"""
|
|
use GenServer
|
|
require Logger
|
|
|
|
@poll_ms 500
|
|
@debounce_ms 200
|
|
|
|
def start_link(opts) do
|
|
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
|
|
end
|
|
|
|
@impl true
|
|
def init(_opts) do
|
|
paths = paths()
|
|
state = %{paths: paths, signature: signature(paths), debounce_ref: nil}
|
|
|
|
schedule_poll()
|
|
{:ok, state}
|
|
end
|
|
|
|
@impl true
|
|
def handle_info(:poll, state) do
|
|
paths = paths()
|
|
next_signature = signature(paths)
|
|
state = %{state | paths: paths}
|
|
|
|
state =
|
|
if next_signature != state.signature do
|
|
schedule_reload(%{state | signature: next_signature})
|
|
else
|
|
state
|
|
end
|
|
|
|
schedule_poll()
|
|
{:noreply, state}
|
|
end
|
|
|
|
def handle_info({:reload, signature}, %{signature: signature} = state) do
|
|
case reload_forms() do
|
|
:ok ->
|
|
Logger.info("Forum.Forms.Watcher: reloaded #{Enum.join(state.paths, ", ")}")
|
|
broadcast_reload()
|
|
|
|
{:error, reason} ->
|
|
Logger.error("Forum.Forms.Watcher: reload failed: #{inspect(reason)}")
|
|
end
|
|
|
|
{:noreply, %{state | debounce_ref: nil}}
|
|
end
|
|
|
|
def handle_info({:reload, _stale_signature}, state) do
|
|
{:noreply, state}
|
|
end
|
|
|
|
defp schedule_reload(%{debounce_ref: ref} = state) when is_reference(ref) do
|
|
Process.cancel_timer(ref)
|
|
schedule_reload(%{state | debounce_ref: nil})
|
|
end
|
|
|
|
defp schedule_reload(state) do
|
|
ref = Process.send_after(self(), {:reload, state.signature}, @debounce_ms)
|
|
%{state | debounce_ref: ref}
|
|
end
|
|
|
|
defp schedule_poll do
|
|
Process.send_after(self(), :poll, @poll_ms)
|
|
end
|
|
|
|
defp paths do
|
|
[Forum.Forms.path() | Enum.map(Forum.Forms.network_paths(), fn {_slug, path} -> path end)]
|
|
end
|
|
|
|
defp signature(paths) do
|
|
Enum.map(paths, fn path ->
|
|
case File.read(path) do
|
|
{:ok, raw} -> {path, :ok, :erlang.phash2(raw), byte_size(raw)}
|
|
{:error, reason} -> {path, :error, reason}
|
|
end
|
|
end)
|
|
end
|
|
|
|
defp reload_forms do
|
|
try do
|
|
Forum.Forms.reload()
|
|
rescue
|
|
error -> {:error, error}
|
|
catch
|
|
kind, reason -> {:error, {kind, reason}}
|
|
else
|
|
:ok -> :ok
|
|
other -> {:error, other}
|
|
end
|
|
end
|
|
|
|
defp broadcast_reload do
|
|
html = Forum.Forms.html()
|
|
|
|
for {pid, _value} <- Registry.lookup(Forum.FormsSubscriberRegistry, :forms) do
|
|
send(pid, {:forms_reloaded, html})
|
|
end
|
|
end
|
|
end
|