Current section
Files
Jump to
Current section
Files
lib/serum/dev_server/service/gen_server.ex
defmodule Serum.DevServer.Service.GenServer do
@moduledoc false
_moduledocp = """
A GenServer-based implementation of `Serum.DevServer.Service` behaviour.
"""
use GenServer
import Serum.IOProxy, only: [put_err: 2, put_msg: 2]
alias Serum.Build
alias Serum.DevServer.Service
alias Serum.Project
alias Serum.Project.Loader, as: ProjectLoader
alias Serum.Result
@behaviour Service
@type start_arg() :: {binary(), binary(), pos_integer()}
#
# GenServer Implementation - Client
#
@doc "Starts the GenServer process."
@spec start_link(start_arg()) :: {:ok, pid()} | {:error, atom()}
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: __MODULE__, timeout: :infinity)
end
@impl Service
@spec rebuild() :: :ok
def rebuild, do: GenServer.call(__MODULE__, :rebuild)
@impl Service
@spec source_dir() :: binary
def source_dir, do: GenServer.call(__MODULE__, :source_dir)
@impl Service
@spec site_dir() :: binary
def site_dir, do: GenServer.call(__MODULE__, :site_dir)
@impl Service
@spec port() :: pos_integer
def port, do: GenServer.call(__MODULE__, :port)
@impl Service
@spec dirty?() :: boolean
def dirty?, do: GenServer.call(__MODULE__, :is_dirty)
@impl Service
@spec subscribe() :: :ok
def subscribe, do: GenServer.call(__MODULE__, :subscribe)
#
# GenServer Implementation - Server
#
@impl GenServer
def init({dir, site, portnum}) do
false = Process.flag(:trap_exit, true)
watcher =
case FileSystem.start_link(dirs: [Path.absname(dir)]) do
{:ok, pid} -> pid
:ignore -> nil
end
state = %{
watcher: watcher,
dir: dir,
site: site,
portnum: portnum,
is_dirty: false,
subscribers: %{}
}
do_rebuild(dir, site)
unless(is_nil(watcher), do: FileSystem.subscribe(watcher))
{:ok, state}
end
@impl GenServer
def handle_call(msg, from, state)
def handle_call(:rebuild, _from, state) do
do_rebuild(state.dir, state.site)
{:reply, :ok, state}
end
def handle_call(:source_dir, _from, state), do: {:reply, state.dir, state}
def handle_call(:site_dir, _from, state), do: {:reply, state.site, state}
def handle_call(:port, _from, state), do: {:reply, state.portnum, state}
def handle_call(:is_dirty, _from, state),
do: {:reply, state.is_dirty, %{state | is_dirty: false}}
def handle_call(:subscribe, {caller, _}, state) do
ref = Process.monitor(caller)
state2 = %{state | subscribers: Map.put(state.subscribers, ref, caller)}
{:reply, :ok, state2}
end
@impl GenServer
def handle_info(msg, state)
def handle_info({:file_event, _, _}, %{is_dirty: true} = state) do
{:noreply, state}
end
def handle_info({:file_event, pid, :stop}, %{watcher: pid} = state) do
{:noreply, state}
end
def handle_info({:file_event, pid, {path, _}}, %{watcher: pid} = state) do
ignore? =
path
|> Path.relative_to(state.dir)
|> Path.split()
|> Enum.any?(&dotfile?/1)
if ignore? do
{:noreply, state}
else
server = self()
spawn_link(fn ->
receive do
after
200 ->
send(server, :tick)
end
end)
{:noreply, %{state | is_dirty: true}}
end
end
def handle_info(:tick, state) do
do_rebuild(state.dir, state.site)
Enum.each(state.subscribers, fn {_, pid} -> send(pid, :send_reload) end)
{:noreply, %{state | is_dirty: false}}
end
def handle_info({:DOWN, ref, :process, _, _}, state) do
{:noreply, %{state | subscribers: Map.delete(state.subscribers, ref)}}
end
def handle_info({:EXIT, _pid, :normal}, state), do: {:noreply, state}
@impl GenServer
def terminate(_reason, %{site: site, watcher: watcher}) do
put_msg(:info, "Removing temporary directory \"#{site}\"...")
File.rm_rf!(site)
put_msg(:info, "Shutting down...")
unless(is_nil(watcher), do: Process.exit(watcher, :shutdown))
end
@spec do_rebuild(binary(), binary()) :: :ok
defp do_rebuild(src, dest) do
with {:ok, %Project{} = proj} <- ProjectLoader.load(src, dest),
{:ok, ^dest} <- Build.build(proj) do
:ok
else
{:error, _} = error -> build_failed(error)
end
end
@spec build_failed(Result.t()) :: :ok
defp build_failed(error) do
Result.show(error)
put_err(:warn, "Error occurred while building the website.")
put_err(:warn, "The website may not be displayed correctly.")
end
@spec dotfile?(binary()) :: boolean()
defp dotfile?(item)
defp dotfile?(<<?.::8, _::binary>>), do: true
defp dotfile?(_), do: false
end