diff --git a/server/src/session_server.erl b/server/src/session_server.erl index 4e44c7e..dd078da 100644 --- a/server/src/session_server.erl +++ b/server/src/session_server.erl @@ -39,7 +39,7 @@ handle_info({'EXIT', Pid, Reason}, Response = case Reason of {response, Value} -> Value; _ -> - io:format("Request handling error: ~p~n", [Reason]), + io:format("Error handling request: ~p~n", [Reason]), {error, serverError} end, send(Socket, TransactionId, Response), @@ -63,6 +63,13 @@ handle_request(Request) -> map_request({ping, 'NULL'}) -> {response, {ack, 'NULL'}}; +map_request({logSession, {'Session', Subject, Type, Timestamp, Minutes}}) -> + Session = {unicode:characters_to_list(Subject), + Type, Timestamp, Minutes}, + case subject_router:log_session(Session) of + ok -> {response, {ack, 'NULL'}}; + {error, invalid_subject} -> {response, {error, invalidArguments}} + end; map_request(_) -> {response, {error, invalidArguments}}. diff --git a/server/src/study_system_server_app.erl b/server/src/study_system_server_app.erl index 5f0e104..1785e57 100644 --- a/server/src/study_system_server_app.erl +++ b/server/src/study_system_server_app.erl @@ -9,7 +9,11 @@ start(_StartType, _StartArgs) -> Port = application:get_env(study_system_server, port, 12888), {ok, CertDir} = application:get_env(study_system_server, cert_dir), - proto_sup:start_link(Port, CertDir). + {ok, _Pid} = pg:start(study_system_server), + study_system_server_sup:start_link(Port, CertDir), + subject_sup:start_subject("Cybernetics"), + subject_sup:start_subject("Physics"), + subject_sup:start_subject("Linguistics"). stop(_State) -> ok. diff --git a/server/src/study_system_server_sup.erl b/server/src/study_system_server_sup.erl new file mode 100644 index 0000000..4295476 --- /dev/null +++ b/server/src/study_system_server_sup.erl @@ -0,0 +1,35 @@ +% Copyright (c) Camden Dixie O'Brien +% SPDX-License-Identifier: AGPL-3.0-only + +-module(study_system_server_sup). +-behaviour(supervisor). + +-export([start_link/2]). +-export([init/1]). + +start_link(Port, CertDir) -> + supervisor:start_link({local, ?MODULE}, ?MODULE, [Port, CertDir]). + +init([Port, CertDir]) -> + SupFlags = #{strategy => one_for_one, + intensity => 5, + period => 10}, + ChildSpecs = [#{id => subject_router, + start => {subject_router, start_link, []}, + restart => permanent, + shutdown => 5000, + type => worker, + modules => [subject_router]}, + #{id => subject_sup, + start => {subject_sup, start_link, []}, + restart => permanent, + shutdown => 5000, + type => supervisor, + modules => [subject_sup]}, + #{id => proto_sup, + start => {proto_sup, start_link, [Port, CertDir]}, + restart => permanent, + shutdown => 5000, + type => supervisor, + modules => [proto_sup]}], + {ok, {SupFlags, ChildSpecs}}. diff --git a/server/src/subject_router.erl b/server/src/subject_router.erl new file mode 100644 index 0000000..c561bd8 --- /dev/null +++ b/server/src/subject_router.erl @@ -0,0 +1,75 @@ +% Copyright (c) Camden Dixie O'Brien +% SPDX-License-Identifier: AGPL-3.0-only + +-module(subject_router). +-behaviour(gen_server). + +-export([start_link/0, log_session/1]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +log_session(Session) -> + gen_server:call(?MODULE, {log_session, Session}). + +init([]) -> + {MonitorRef, Pids} = pg:monitor(study_system_server, subject_servers), + SubjectTable = ets:new(subject_table, [private]), + register_servers(SubjectTable, Pids), + {ok, #{monitor_ref => MonitorRef, + subject_table => SubjectTable}}. + +handle_call({log_session, {Subject, Type, Timestamp, Minutes}}, + _From, State = #{subject_table := SubjectTable}) -> + case ets:lookup(SubjectTable, Subject) of + [{Subject, Pid}] -> + Pid ! {new_session, Type, Timestamp, Minutes}, + {reply, ok, State}; + [] -> + {reply, {error, invalid_subject}, State} + end; +handle_call(_Request, _From, State) -> + {reply, ok, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({Ref, join, subject_servers, Pids}, + State = #{monitor_ref := MonitorRef, + subject_table := SubjectTable}) + when Ref =:= MonitorRef -> + register_servers(SubjectTable, Pids), + {noreply, State}; +handle_info({Ref, leave, subject_servers, Pids}, + State = #{monitor_ref := MonitorRef, + subject_table := SubjectTable}) + when Ref =:= MonitorRef -> + deregister_servers(SubjectTable, Pids), + {noreply, State}; +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +register_servers(SubjectTable, Pids) -> + [register_server(SubjectTable, Pid) || Pid <- Pids]. + +register_server(SubjectTable, Pid) -> + try + {subject, Subject} = gen_server:call(Pid, get_subject), + ets:insert(SubjectTable, {Subject, Pid}) + catch + _:_ -> ok + end. + +deregister_servers(SubjectTable, Pids) -> + [deregister_server(SubjectTable, Pid) || Pid <- Pids]. + +deregister_server(SubjectTable, Pid) -> + ets:match_delete(SubjectTable, {'_', Pid}). diff --git a/server/src/subject_server.erl b/server/src/subject_server.erl new file mode 100644 index 0000000..78a3a61 --- /dev/null +++ b/server/src/subject_server.erl @@ -0,0 +1,39 @@ +% Copyright (c) Camden Dixie O'Brien +% SPDX-License-Identifier: AGPL-3.0-only + +-module(subject_server). +-behaviour(gen_server). + +-export([start_link/1]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +start_link(Subject) -> + gen_server:start_link(?MODULE, Subject, []). + +init(Subject) -> + pg:join(study_system_server, subject_servers, self()), + {ok, #{subject => Subject}}. + +handle_call(get_subject, _From, State = #{subject := Subject}) -> + {reply, {subject, Subject}, State}; +handle_call(_Request, _From, State) -> + {reply, ok, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({new_session, Type, Timestamp, Minutes}, + State = #{subject := Subject}) -> + io:format( + "Received new ~p session: type ~p, timestamp ~p, minutes ~p~n", + [Subject, Type, Timestamp, Minutes]), + {noreply, State}; +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/server/src/subject_sup.erl b/server/src/subject_sup.erl new file mode 100644 index 0000000..ac5bcfb --- /dev/null +++ b/server/src/subject_sup.erl @@ -0,0 +1,25 @@ +% Copyright (c) Camden Dixie O'Brien +% SPDX-License-Identifier: AGPL-3.0-only + +-module(subject_sup). +-behaviour(supervisor). + +-export([start_link/0, init/1, start_subject/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +init([]) -> + SupFlags = #{strategy => simple_one_for_one, + intensity => 5, + period => 10}, + ChildSpec = #{id => subject_server, + start => {subject_server, start_link, []}, + restart => permanent, + shutdown => 5000, + type => worker, + modules => [subject_server]}, + {ok, {SupFlags, [ChildSpec]}}. + +start_subject(Subject) -> + supervisor:start_child(?MODULE, [Subject]).