% Copyright (c) Camden Dixie O'Brien % SPDX-License-Identifier: AGPL-3.0-only -module(session_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(Socket) -> gen_server:start_link(?MODULE, Socket, []). init(Socket) -> ok = ssl:setopts(Socket, [{active, true}]), process_flag(trap_exit, true), {ok, #{socket => Socket, transactions => #{}}}. handle_call(_Request, _From, State) -> {reply, ok, State}. handle_cast(_Msg, State) -> {noreply, State}. handle_info({ssl, Socket, Data}, State = #{transactions := Transactions}) -> case 'StudySystemProtocol':decode('Request', Data) of {ok, {'Request', TransactionId, RequestBody}} -> Pid = spawn_link(fun() -> handle_request(RequestBody) end), NewTransactions = maps:put(Pid, TransactionId, Transactions), {noreply, State#{transactions := NewTransactions}}; {error, {asn1, _Reason}} -> send(Socket, -1, {error, invalidRequest}), {noreply, State} end; handle_info({'EXIT', Pid, Reason}, State = #{socket := Socket, transactions := Transactions}) when is_map_key(Pid, Transactions) -> TransactionId = maps:get(Pid, Transactions), Response = case Reason of {response, Value} -> Value; _ -> io:format("Error handling request: ~p~n", [Reason]), {error, serverError} end, send(Socket, TransactionId, Response), {noreply, State#{transactions := maps:remove(Pid, Transactions)}}; handle_info({ssl_closed, _Socket}, State) -> {stop, normal, State}; handle_info({ssl_error, _Socket, _Reason}, State) -> {stop, normal, State}; handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, #{socket := Socket}) -> ssl:close(Socket). code_change(_OldVsn, State, _Extra) -> {ok, State}. handle_request(Request) -> timer:kill_after(500), exit(map_request(Request)). map_request({ping, 'NULL'}) -> {response, {ack, 'NULL'}}; map_request({listActivities, 'NULL'}) -> {activities, Activities} = subject_router:get_activities(), SortedActivities = lists:reverse(lists:keysort(3, Activities)), {response, {activities, [{'Activity', Subject, Type, round(Priority * 100)} || {Subject, Type, Priority} <- SortedActivities]}}; 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, _Error} -> {response, {error, invalidArguments}} end; map_request(_) -> {response, {error, invalidArguments}}. send(Socket, TransactionId, Response) -> {ok, Encoded} = 'StudySystemProtocol':encode( 'Response', {'Response', TransactionId, Response}), ok = ssl:send(Socket, Encoded).