Files
wanderer/lib/wanderer_app/server/server_status_tracker.ex

222 lines
5.4 KiB
Elixir

defmodule WandererApp.Server.ServerStatusTracker do
@moduledoc false
use GenServer
require Logger
@name :server_status_tracker
defstruct [
:players,
:server_version,
:start_time,
:vip,
:retries,
:in_forced_downtime,
:downtime_notified
]
@retries_count 3
@initial_state %{
players: 0,
retries: @retries_count,
server_version: "0",
start_time: "0",
vip: true,
in_forced_downtime: false,
downtime_notified: false
}
# EVE Online daily downtime period (UTC/GMT)
@downtime_start_hour 10
@downtime_start_minute 58
@downtime_end_hour 11
@downtime_end_minute 2
@refresh_interval :timer.minutes(1)
@logger Application.compile_env(:wanderer_app, :logger)
def start_link(opts \\ []), do: GenServer.start(__MODULE__, opts, name: @name)
@impl true
def init(_opts) do
@logger.info("#{__MODULE__} started")
{:ok, @initial_state, {:continue, :start}}
end
@impl true
def terminate(_reason, _state), do: :ok
@impl true
def handle_call(:stop, _, state), do: {:stop, :normal, :ok, state}
@impl true
def handle_call(:error, _, state), do: {:stop, :error, :ok, state}
@impl true
def handle_continue(:start, state) do
Process.send_after(self(), :refresh_status, 100)
{:noreply, state}
end
@impl true
def handle_info(
:refresh_status,
%{
retries: retries,
in_forced_downtime: was_in_downtime
} = state
) do
Process.send_after(self(), :refresh_status, @refresh_interval)
in_downtime = in_forced_downtime?()
cond do
# Entering downtime period - broadcast offline status immediately
in_downtime and not was_in_downtime ->
@logger.info("#{__MODULE__} entering forced downtime period (10:58-11:02 GMT)")
downtime_status = %{
players: 0,
server_version: "downtime",
start_time: DateTime.utc_now() |> DateTime.to_iso8601(),
vip: true
}
Phoenix.PubSub.broadcast(
WandererApp.PubSub,
"server_status",
{:server_status, downtime_status}
)
{:noreply,
%{state | in_forced_downtime: true, downtime_notified: true}
|> Map.merge(downtime_status)}
# Currently in downtime - skip API call
in_downtime ->
{:noreply, state}
# Exiting downtime period - resume normal operations
not in_downtime and was_in_downtime ->
@logger.info("#{__MODULE__} exiting forced downtime period, resuming normal operations")
Task.async(fn -> get_server_status(retries) end)
{:noreply, %{state | in_forced_downtime: false, downtime_notified: false}}
# Normal operation
true ->
Task.async(fn -> get_server_status(retries) end)
{:noreply, state}
end
end
@impl true
def handle_info(
{ref, result},
%{
retries: retries
} = state
) do
Process.demonitor(ref, [:flush])
case result do
{:status, status} ->
Phoenix.PubSub.broadcast(
WandererApp.PubSub,
"server_status",
{:server_status, status}
)
{:noreply, state |> Map.merge(status)}
:retry ->
{:noreply, %{state | retries: retries - 1}}
{:error, _error} ->
{:noreply, state}
_ ->
{:noreply, state}
end
end
def handle_info(_action, state),
do: {:noreply, state}
defp get_server_status(retries) do
case WandererApp.Esi.get_server_status() do
{:ok, result} ->
{:status, extract_status(result)}
{:error, :timeout} ->
if retries > 0 do
:retry
else
Logger.warning("#{__MODULE__} failed to refresh server status: :timeout")
{:status, @initial_state}
end
{:error, error} ->
if retries > 0 do
:retry
else
Logger.warning("#{__MODULE__} failed to refresh server status: #{inspect(error)}")
{:status, @initial_state}
end
_ ->
{:error, :unknown}
end
end
defp extract_status(%{
"players" => 0,
"server_version" => server_version,
"start_time" => start_time,
"vip" => _vip
}) do
%{players: 0, server_version: server_version, start_time: start_time, vip: true}
end
defp extract_status(%{
"players" => players,
"server_version" => server_version,
"start_time" => start_time,
"vip" => vip
}) do
%{players: players, server_version: server_version, start_time: start_time, vip: vip}
end
defp extract_status(%{
"players" => players,
"server_version" => server_version,
"start_time" => start_time
}) do
%{
players: players,
server_version: server_version,
start_time: start_time,
vip: false
}
end
# Checks if the current UTC time falls within the forced downtime period (10:58-11:02 GMT).
defp in_forced_downtime? do
now = DateTime.utc_now()
current_hour = now.hour
current_minute = now.minute
# Convert times to minutes since midnight for easier comparison
current_time_minutes = current_hour * 60 + current_minute
downtime_start_minutes = @downtime_start_hour * 60 + @downtime_start_minute
downtime_end_minutes = @downtime_end_hour * 60 + @downtime_end_minute
current_time_minutes >= downtime_start_minutes and
current_time_minutes < downtime_end_minutes
end
end