Current section
Files
Jump to
Current section
Files
lib/nostrum/cache/guild/guild_server.ex
defmodule Nostrum.Cache.Guild.GuildServer do
@moduledoc false
use GenServer, restart: :transient
alias Nostrum.Cache.Guild.GuildRegister
alias Nostrum.Struct.{Channel, Guild}
alias Nostrum.Struct.Guild.{Member, Role}
alias Nostrum.Util
require Logger
@doc false
# REVIEW: If a guild server crashes, it will be restarted with its initial state.
def start_link(id, guild) do
GenServer.start_link(__MODULE__, [id, guild])
end
@doc false
def init([id, guild]) do
case Registry.register(GuildRegistry, id, self()) do
{:ok, _pid} ->
{:ok, guild}
{:error, error} ->
# Causes start_link to return {:error, reason}
{:stop, error}
end
end
@doc false
def child_spec do
Supervisor.child_spec(__MODULE__, start: {__MODULE__, :start_link, []})
end
@doc false
def call(id, request) do
with {:ok, pid} <- GuildRegister.lookup(id), do: GenServer.call(pid, request)
end
def cast(id, request) do
with {:ok, pid} <- GuildRegister.lookup(id), do: GenServer.cast(pid, request)
end
@doc false
@spec create(Guild.t()) :: {:ok, Guild.t()} | {:error, term}
def create(guild) do
GuildRegister.create_guild_process(guild.id, guild)
end
@doc false
def update(guild) do
call(guild.id, {:update, guild})
end
# Uses a selector function to select certain fields from the guild state.
@doc false
@spec select(Guild.id(), (Guild.t() -> any)) :: any
def select(guild_id, fun) do
call(guild_id, {:select, fun})
end
@doc false
def delete(guild_id) do
call(guild_id, {:delete})
end
@doc false
def member_add(guild_id, member) do
call(guild_id, {:create, :member, guild_id, member})
end
@doc false
def member_add_from_presence(guild_id, member) do
cast(guild_id, {:create, :member, member})
end
@doc false
def member_update(guild_id, member) do
call(guild_id, {:update, :member, guild_id, member})
end
@doc false
def member_remove(guild_id, user) do
call(guild_id, {:delete, :member, guild_id, user})
end
def member_chunk(guild_id, user) do
cast(guild_id, {:chunk, :member, user})
end
@doc false
def channel_create(guild_id, channel) do
call(guild_id, {:create, :channel, channel})
end
@doc false
def channel_update(guild_id, channel) do
call(guild_id, {:update, :channel, channel})
end
@doc false
def channel_delete(guild_id, channel_id) do
call(guild_id, {:delete, :channel, channel_id})
end
@doc false
def role_create(guild_id, role) do
call(guild_id, {:create, :role, guild_id, role})
end
@doc false
def role_update(guild_id, role) do
call(guild_id, {:update, :role, guild_id, role})
end
@doc false
def role_delete(guild_id, role_id) do
call(guild_id, {:delete, :role, guild_id, role_id})
end
@doc false
def emoji_update(guild_id, emojis) do
call(guild_id, {:update, :emoji, guild_id, emojis})
end
def handle_call({:select, fun}, _from, state) do
{:reply, fun.(state), state, :hibernate}
end
def handle_call({:update, guild}, _from, state) do
new_guild =
state
|> Map.from_struct()
|> Map.merge(guild)
|> Util.cast({:struct, Guild})
{:reply, {state, new_guild}, new_guild, :hibernate}
end
def handle_call({:delete}, _from, state) do
{:stop, :normal, state, %{}}
end
def handle_call(
{:create, :member, guild_id, %{user: %{id: id}} = member},
_from,
%{members: members} = state
) do
{_old, new, new_map} = upsert(members, id, member, Member)
{:reply, {guild_id, new}, %{state | members: new_map, member_count: state.member_count + 1},
:hibernate}
end
def handle_call(
{:update, :member, guild_id, %{user: %{id: id}} = partial_member},
_from,
%{members: members} = state
) do
{old, new, new_map} = upsert(members, id, partial_member, Member)
{:reply, {guild_id, old, new}, %{state | members: new_map}, :hibernate}
end
def handle_call({:delete, :member, guild_id, %{id: id}}, _from, %{members: members} = state) do
{popped, new} = Map.pop(members, id)
ret = if popped, do: {guild_id, popped}, else: :noop
{:reply, ret, %{state | members: new, member_count: state.member_count - 1}, :hibernate}
end
def handle_call({:create, :channel, %{id: id} = channel}, _from, %{channels: channels} = state) do
{_old, new, new_map} = upsert(channels, id, channel, Channel)
{:reply, new, %{state | channels: new_map}, :hibernate}
end
def handle_call({:update, :channel, %{id: id} = channel}, _from, %{channels: channels} = state) do
{old, new, new_map} = upsert(channels, id, channel, Channel)
{:reply, {old, new}, %{state | channels: new_map}, :hibernate}
end
def handle_call({:delete, :channel, channel_id}, _from, %{channels: channels} = state) do
{popped, new} = Map.pop(channels, channel_id)
ret = if popped, do: popped, else: :noop
{:reply, ret, %{state | channels: new}, :hibernate}
end
def handle_call({:create, :role, guild_id, %{id: id} = role}, _from, %{roles: roles} = state) do
{_old, new, new_map} = upsert(roles, id, role, Role)
{:reply, {guild_id, new}, %{state | roles: new_map}, :hibernate}
end
def handle_call({:update, :role, guild_id, %{id: id} = role}, _from, %{roles: roles} = state) do
{old, new, new_map} = upsert(roles, id, role, Role)
{:reply, {guild_id, old, new}, %{state | roles: new_map}, :hibernate}
end
def handle_call({:delete, :role, guild_id, role_id}, _from, %{roles: roles} = state) do
{popped, new} = Map.pop(roles, role_id)
ret = if popped, do: {guild_id, popped}, else: :noop
{:reply, ret, %{state | roles: new}, :hibernate}
end
def handle_call({:update, :emoji, guild_id, emojis}, _from, state) do
old_emojis = state.emojis
{:reply, {guild_id, old_emojis, emojis}, %{state | emojis: emojis}, :hibernate}
end
def handle_cast({:chunk, :member, new_members}, %{members: members} = state) do
new_members =
Enum.reduce(new_members, members, fn m, acc ->
Map.put(acc, m.user.id, Util.cast(m, {:struct, Member}))
end)
{:noreply, %{state | members: new_members}, :hibernate}
end
@spec upsert(%{required(Snowflake.t()) => struct}, Snowflake.t(), map, atom) ::
{struct | nil, struct, %{required(Snowflake.t()) => struct}}
defp upsert(map, key, new, struct) do
if Map.has_key?(map, key) do
old = Map.get(map, key)
new =
old
|> Map.from_struct()
|> Map.merge(new)
|> Util.cast({:struct, struct})
new_map = Map.put(map, key, new)
{old, new, new_map}
else
new = Util.cast(new, {:struct, struct})
{nil, new, Map.put(map, key, new)}
end
end
end