feat: initial fluxer codebase import

This commit is contained in:
root
2026-03-13 09:47:47 +01:00
parent 5ceda343b8
commit 570a3f3051
8134 changed files with 1409671 additions and 0 deletions

View File

@@ -0,0 +1,227 @@
%% Copyright (C) 2026 Fluxer Contributors
%%
%% This file is part of Fluxer.
%%
%% Fluxer is free software: you can redistribute it and/or modify
%% it under the terms of the GNU Affero General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%%
%% Fluxer is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU Affero General Public License for more details.
%%
%% You should have received a copy of the GNU Affero General Public License
%% along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
-module(presence_manager_shard).
-behaviour(gen_server).
-include_lib("fluxer_gateway/include/timeout_config.hrl").
-export([start_link/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-type user_id() :: integer().
-type presence_ref() :: {pid(), reference()}.
-type status() :: online | offline | idle | dnd.
-type event_type() :: atom() | binary().
-type start_or_lookup_request() :: #{
user_id := user_id(),
user_data := map(),
guild_ids := [integer()],
status := status(),
friend_ids := [user_id()],
group_dm_recipients := map()
}.
-type state() :: #{presences := #{user_id() => presence_ref()}}.
-spec start_link(non_neg_integer()) -> {ok, pid()} | {error, term()}.
start_link(ShardIndex) ->
gen_server:start_link(?MODULE, #{shard_index => ShardIndex}, []).
-spec init(map()) -> {ok, state()}.
init(_Args) ->
process_flag(trap_exit, true),
{ok, #{presences => #{}}}.
-spec handle_call(Request, From, State) -> Result when
Request ::
{lookup, user_id()}
| {start_or_lookup, start_or_lookup_request()}
| {dispatch, user_id(), event_type(), term()}
| get_local_count
| get_global_count
| term(),
From :: gen_server:from(),
State :: state(),
Result :: {reply, Reply, state()},
Reply ::
{ok, pid()}
| {error, not_found}
| {error, registration_failed}
| {error, process_disappeared}
| {error, term()}
| {ok, non_neg_integer()}
| ok.
handle_call({lookup, UserId}, _From, State) ->
do_lookup(UserId, State);
handle_call({dispatch, UserId, Event, Data}, _From, State) ->
case lookup_presence(UserId, State) of
{ok, PresencePid, NewState} ->
gen_server:cast(PresencePid, {dispatch, Event, Data}),
{reply, ok, NewState};
{error, not_found, NewState} ->
{reply, {error, not_found}, NewState}
end;
handle_call({start_or_lookup, Request}, _From, State) ->
do_start_or_lookup(Request, State);
handle_call({terminate_all_sessions, UserId}, _From, State) ->
case terminate_sessions_for_user(UserId, State) of
{Result, NewState} ->
{reply, Result, NewState}
end;
handle_call(get_local_count, _From, State) ->
Presences = maps:get(presences, State),
Count = process_registry:get_count(Presences),
{reply, {ok, Count}, State};
handle_call(get_global_count, _From, State) ->
Presences = maps:get(presences, State),
Count = process_registry:get_count(Presences),
{reply, {ok, Count}, State};
handle_call(_Unknown, _From, State) ->
{reply, ok, State}.
-spec handle_cast(term(), state()) -> {noreply, state()}.
handle_cast(_Unknown, State) ->
{noreply, State}.
-spec handle_info(Info, State) -> {noreply, state()} when
Info :: {'DOWN', reference(), process, pid(), term()} | term(),
State :: state().
handle_info({'DOWN', _Ref, process, Pid, _Reason}, State) ->
Presences = maps:get(presences, State),
NewPresences = process_registry:cleanup_on_down(Pid, Presences),
{noreply, State#{presences := NewPresences}};
handle_info(_Unknown, State) ->
{noreply, State}.
-spec terminate(Reason, State) -> ok when
Reason :: term(),
State :: state().
terminate(_Reason, _State) ->
ok.
-spec code_change(term(), term(), term()) -> {ok, state()}.
code_change(_OldVsn, State, _Extra) when is_map(State) ->
{ok, State};
code_change(_OldVsn, {state, Presences}, _Extra) ->
{ok, #{presences => Presences}}.
-spec do_lookup(user_id(), state()) -> {reply, {ok, pid()} | {error, not_found}, state()}.
do_lookup(UserId, State) ->
case lookup_presence(UserId, State) of
{ok, Pid, NewState} ->
{reply, {ok, Pid}, NewState};
{error, not_found, NewState} ->
{reply, {error, not_found}, NewState}
end.
-spec do_start_or_lookup(start_or_lookup_request(), state()) ->
{reply, {ok, pid()} | {error, registration_failed | process_disappeared | term()}, state()}.
do_start_or_lookup(Request, State) ->
Presences = maps:get(presences, State),
#{
user_id := UserId,
user_data := UserData,
guild_ids := GuildIds,
status := Status
} = Request,
case maps:get(UserId, Presences, undefined) of
{Pid, _Ref} ->
{reply, {ok, Pid}, State};
undefined ->
PresenceName = process_registry:build_process_name(presence, UserId),
case whereis(PresenceName) of
undefined ->
FriendIds = maps:get(friend_ids, Request, []),
GroupDmRecipients = maps:get(group_dm_recipients, Request, #{}),
PresenceData = #{
user_id => UserId,
user_data => UserData,
guild_ids => GuildIds,
status => Status,
friend_ids => FriendIds,
group_dm_recipients => GroupDmRecipients,
custom_status => maps:get(custom_status, Request, null)
},
case presence:start_link(PresenceData) of
{ok, Pid} ->
case
process_registry:register_and_monitor(PresenceName, Pid, Presences)
of
{ok, RegisteredPid, Ref, NewPresences0} ->
CleanPresences = maps:remove(PresenceName, NewPresences0),
NewPresences = maps:put(
UserId, {RegisteredPid, Ref}, CleanPresences
),
{reply, {ok, RegisteredPid}, State#{
presences := NewPresences
}};
{error, registration_race_condition} ->
{reply, {error, registration_failed}, State};
{error, _Reason} = Error ->
{reply, Error, State}
end;
Error ->
{reply, Error, State}
end;
_ExistingPid ->
case process_registry:lookup_or_monitor(PresenceName, UserId, Presences) of
{ok, Pid, _Ref, NewPresences} ->
{reply, {ok, Pid}, State#{presences := NewPresences}};
{error, not_found} ->
{reply, {error, process_disappeared}, State}
end
end
end.
-spec lookup_presence(user_id(), state()) -> {ok, pid(), state()} | {error, not_found, state()}.
lookup_presence(UserId, State) ->
Presences = maps:get(presences, State),
case maps:get(UserId, Presences, undefined) of
{Pid, _Ref} ->
{ok, Pid, State};
undefined ->
PresenceName = process_registry:build_process_name(presence, UserId),
case process_registry:lookup_or_monitor(PresenceName, UserId, Presences) of
{ok, Pid, Ref, NewPresences0} ->
CleanPresences = maps:remove(PresenceName, NewPresences0),
FinalPresences = maps:put(UserId, {Pid, Ref}, CleanPresences),
{ok, Pid, State#{presences := FinalPresences}};
{error, not_found} ->
{error, not_found, State}
end
end.
terminate_sessions_for_user(UserId, State) ->
Presences = maps:get(presences, State),
case maps:get(UserId, Presences, undefined) of
{Pid, _Ref} ->
gen_server:cast(Pid, {terminate_all_sessions}),
{ok, State};
undefined ->
PresenceName = process_registry:build_process_name(presence, UserId),
case process_registry:lookup_or_monitor(PresenceName, UserId, Presences) of
{ok, Pid, Ref, NewPresences0} ->
CleanPresences = maps:remove(PresenceName, NewPresences0),
FinalPresences = maps:put(UserId, {Pid, Ref}, CleanPresences),
gen_server:cast(Pid, {terminate_all_sessions}),
{ok, State#{presences := FinalPresences}};
{error, not_found} ->
{ok, State}
end
end.