begin to add chat function

This commit is contained in:
Peter Harpending 2025-10-21 21:06:27 -07:00
parent 079c47962a
commit 027c020a34
2 changed files with 236 additions and 1 deletions

229
src/fd_chat.erl Normal file
View File

@ -0,0 +1,229 @@
% @doc
% controller for chat
-module(fd_chat).
-vsn("0.1.0").
-behavior(gen_server).
-author("Peter Harpending <peterharpending@qpq.swiss>").
-copyright("Peter Harpending <peterharpending@qpq.swiss>").
-license("BSD-2-Clause-FreeBSD").
-export([
join/1,
nick_available/1
]).
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
code_change/3, terminate/2]).
-record(o, {pid :: pid(),
nick :: string()}).
-type orator() :: #o{}.
-record(s, {orators = [] :: [orator()]}).
-type state() :: #s{}.
%%% Service Interface
-spec join(Nick) -> Result
when Nick :: string(),
Result :: ok
| {error, Reason :: any()}.
join(Nick) ->
gen_server:call(?MODULE, {join, Nick}).
-spec nick_available(Nick) -> Result
when Nick :: string(),
Result :: boolean().
nick_available(Nick) ->
gen_server:call(?MODULE, {nick_available, Nick}).
%%% Startup Functions
-spec start_link() -> Result
when Result :: {ok, pid()}
| {error, Reason :: term()}.
%% @private
%% This should only ever be called by fd_chat_orators (the service-level supervisor).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, none, []).
-spec init(none) -> {ok, state()}.
%% @private
%% Called by the supervisor process to give the process a chance to perform any
%% preparatory work necessary for proper function.
init(none) ->
ok = io:format("Starting.~n"),
State = #s{},
{ok, State}.
%%% gen_server Message Handling Callbacks
-spec handle_call(Message, From, State) -> Result
when Message :: term(),
From :: {pid(), reference()},
State :: state(),
Result :: {reply, Response, NewState}
| {noreply, State},
Response :: term(),
NewState :: state().
%% @private
%% The gen_server:handle_call/3 callback.
%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_call-3
handle_call({join, Nick}, {Pid, _}, State) ->
{Reply, NewState} = do_join(Pid, Nick, State),
{reply, Reply, NewState}.
handle_call({nick_available, Nick}, _, State = #s{orators = Orators}) ->
Reply = is_nick_available(Nick, Orators),
{reply, Reply, State}.
handle_call(Unexpected, From, State) ->
ok = io:format("~p Unexpected call from ~tp: ~tp~n", [self(), From, Unexpected]),
{noreply, State}.
-spec handle_cast(Message, State) -> {noreply, NewState}
when Message :: term(),
State :: state(),
NewState :: state().
%% @private
%% The gen_server:handle_cast/2 callback.
%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_cast-2
handle_cast(Unexpected, State) ->
ok = io:format("~p Unexpected cast: ~tp~n", [self(), Unexpected]),
{noreply, State}.
-spec handle_info(Message, State) -> {noreply, NewState}
when Message :: term(),
State :: state(),
NewState :: state().
%% @private
%% The gen_server:handle_info/2 callback.
%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_info-2
handle_info(Msg = {'DOWN', Mon, process, Pid, Reason}, State) ->
NewState = handle_down(Msg, State),
{noreply, NewState};
handle_info(Unexpected, State) ->
ok = io:format("~p Unexpected info: ~tp~n", [self(), Unexpected]),
{noreply, State}.
%%% OTP Service Functions
-spec code_change(OldVersion, State, Extra) -> Result
when OldVersion :: {down, Version} | Version,
Version :: term(),
State :: state(),
Extra :: term(),
Result :: {ok, NewState}
| {error, Reason :: term()},
NewState :: state().
%% @private
%% The gen_server:code_change/3 callback.
%% See: http://erlang.org/doc/man/gen_server.html#Module:code_change-3
code_change(_, State, _) ->
{ok, State}.
-spec terminate(Reason, State) -> no_return()
when Reason :: normal
| shutdown
| {shutdown, term()}
| term(),
State :: state().
%% @private
%% The gen_server:terminate/2 callback.
%% See: http://erlang.org/doc/man/gen_server.html#Module:terminate-2
terminate(_, _) ->
ok.
%%% internals
-spec do_join(Pid, Nick, State) -> {Reply, NewState}
when Pid :: pid(),
Nick :: string(),
Reply :: ok | {error, Reason :: any()},
NewState :: State.
do_join(Pid, Nick, State = #s{orators = Orators}) ->
case ensure_can_join(Pid, Nick, Orators) of
ok -> do_join2(Pid, Nick, State);
Error -> {Error, State}
end.
do_join2(Pid, Nick, State = #s{orators = Orators}) ->
_Monitor = erlang:monitor(process, Pid),
NewOrator = #o{pid = Pid, nick = Nick},
NewOrators = [NewOrator | NewOrators],
NewState = State#s{orators = NewOrators},
{ok, NewState}.
-spec ensure_can_join(Pid, Nick, Orators) -> Result.
when Pid :: pid(),
Nick :: string(),
Orators :: [orator()]
Result :: ok
| {error, Reason},
Reason :: any().
% @private
% ensures both Pid and Nick are unique
ensure_can_join(Pid, _ , [#o{pid = Pid} | _ ]) -> {error, already_joined};
ensure_can_join(_ , Nick, [#o{nick = Nick} | _ ]) -> {error, {nick_taken, Nick}};
ensure_can_join(Pid, Nick, [_ | Rest]) -> ensure_can_join(Pid, Nick, Rest);
ensure_can_join(Pid, _, _, [] ) -> ok.
-spec is_nick_available(Nick, Orators) -> boolean()
when Nick :: string(),
Orators :: [orator()].
is_nick_available(Nick, [#o{nick = Nick} | _ ]) -> false;
is_nick_available(Nick, [_ | Rest]) -> is_nick_available(Nick, Rest);
is_nick_available(Nick, [] ) -> true.
-spec handle_down(Msg, State) -> NewState
when Msg :: {'DOWN', Mon, process, Pid, Reason},
Mon :: monitor(),
Pid :: pid(),
Reason :: any(),
State :: state(),
NewState :: State.
handle_down(Msg = {'DOWN', _, process, Pid, _}, State = #s{orators = Orators}) ->
NewOrators = hdn(Msg, Pid, Orators, []),
NewState = State#s{orators = NewOrators},
NewState.
% encountered item, removing
hdn(_, Pid, [#o{pid = Pid} | Rest], Acc) -> Rest ++ Acc;
hdn(Msg, Pid, [Skip | Rest], Acc) -> hdn(Msg, Pid, Rest, [Skip | Acc]);
hdn(Msg, Pid, [] , Acc) ->
log("~tp: Unexpected message: ~tp", [?MODULE, Msg]),
Acc.

View File

@ -42,11 +42,17 @@ init([]) ->
5000, 5000,
supervisor, supervisor,
[fd_clients]}, [fd_clients]},
Chat = {fd_chat,
{fd_chat, start_link, []},
permanent,
5000,
worker,
[fd_chat]},
Cache = {fd_cache, Cache = {fd_cache,
{fd_cache, start_link, []}, {fd_cache, start_link, []},
permanent, permanent,
5000, 5000,
worker, worker,
[fd_cache]}, [fd_cache]},
Children = [Clients, Cache], Children = [Clients, Chat, Cache],
{ok, {RestartStrategy, Children}}. {ok, {RestartStrategy, Children}}.