fp-lab4/lib/exdns/zone/server.ex
2026-01-13 18:26:26 +03:00

160 lines
4.6 KiB
Elixir

defmodule Exdns.ZoneServer do
@moduledoc false
use GenServer
alias Exdns.{ZoneRules, ZoneStorage}
alias Exdns.ZoneServer.Loader
def start_link(opts \\ []) do
name = Keyword.get(opts, :name, __MODULE__)
GenServer.start_link(__MODULE__, opts, name: name)
end
def get(name, server \\ __MODULE__) do
GenServer.call(server, {:get, normalize_name(name)})
end
def put(name, records, server \\ __MODULE__) do
GenServer.call(server, {:put, normalize_name(name), records})
end
def create_zone(name, records, server \\ __MODULE__) do
GenServer.call(server, {:create, normalize_name(name), records})
end
def update_records(name, records, server \\ __MODULE__) do
GenServer.call(server, {:update, normalize_name(name), records})
end
def delete_records(name, records, server \\ __MODULE__) do
GenServer.call(server, {:delete_records, normalize_name(name), records})
end
def delete(name, server \\ __MODULE__) do
GenServer.call(server, {:delete, normalize_name(name)})
end
def list(server \\ __MODULE__) do
GenServer.call(server, :list)
end
def authority(name, server \\ __MODULE__) do
GenServer.call(server, {:authority, normalize_name(name)})
end
@impl true
def init(opts) do
zone = Keyword.get(opts, :zone, %{})
{:ok, %{zone: zone}}
end
@impl true
def handle_call({:get, name}, _from, state) do
case ZoneRules.lookup_records(state.zone, name) do
{:ok, records} ->
{:reply, {:ok, records}, state}
:error ->
case ZoneStorage.read(name) do
{:ok, records} ->
zone = Map.put(state.zone, name, records)
{:reply, {:ok, records}, %{state | zone: zone}}
{:error, _} ->
{:reply, :error, state}
end
end
end
@impl true
def handle_call({:put, name, records}, _from, state) when is_list(records) do
updated = ZoneRules.normalize_records(name, records, Map.get(state.zone, name, []))
case ZoneStorage.write(name, updated) do
:ok -> {:reply, :ok, %{state | zone: Map.put(state.zone, name, updated)}}
{:error, _} -> {:reply, :error, state}
end
end
def handle_call({:create, name, records}, _from, state) when is_list(records) do
if ZoneStorage.exists?(name) do
{:reply, {:error, :already_exists}, state}
else
updated = ZoneRules.normalize_records(name, records, [])
case ZoneStorage.write(name, updated) do
:ok -> {:reply, :ok, %{state | zone: Map.put(state.zone, name, updated)}}
{:error, _} -> {:reply, :error, state}
end
end
end
def handle_call({:update, name, records}, _from, state) when is_list(records) do
case Loader.load_zone(state, name) do
{:ok, existing, next_state} ->
updated = ZoneRules.normalize_records(name, records, existing)
case ZoneStorage.write(name, updated) do
:ok -> {:reply, :ok, %{next_state | zone: Map.put(next_state.zone, name, updated)}}
{:error, _} -> {:reply, :error, next_state}
end
:error ->
{:reply, {:error, :not_found}, state}
end
end
def handle_call({:delete_records, name, records}, _from, state) when is_list(records) do
case Loader.load_zone(state, name) do
{:ok, existing, next_state} ->
removed = ZoneRules.remove_records(existing, records)
updated = ZoneRules.normalize_records(name, removed, existing)
case ZoneStorage.write(name, updated) do
:ok -> {:reply, :ok, %{next_state | zone: Map.put(next_state.zone, name, updated)}}
{:error, _} -> {:reply, :error, next_state}
end
:error ->
{:reply, {:error, :not_found}, state}
end
end
def handle_call({:delete, name}, _from, state) do
case ZoneStorage.delete(name) do
:ok -> {:reply, :ok, %{state | zone: Map.delete(state.zone, name)}}
{:error, _} -> {:reply, :error, state}
end
end
def handle_call(:list, _from, state) do
names = ZoneStorage.list_names()
{:reply, Enum.uniq(names ++ Map.keys(state.zone)), state}
end
def handle_call({:authority, name}, _from, state) do
case ZoneRules.find_authority(state.zone, name) do
{:ok, _} = ok ->
{:reply, ok, state}
:error ->
{zone, loaded} = Loader.load_suffixes(state.zone, name)
if loaded do
{:reply, ZoneRules.find_authority(zone, name), %{state | zone: zone}}
else
{:reply, :error, state}
end
end
end
defp normalize_name(name) when is_binary(name) do
name
|> String.trim(".")
|> String.split(".", trim: true)
end
defp normalize_name(name) when is_list(name), do: name
end