diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..ed7d163 --- /dev/null +++ b/server/.gitignore @@ -0,0 +1,2 @@ +_build/* +*.beam diff --git a/server/rebar.config b/server/rebar.config new file mode 100644 index 0000000..2656fd5 --- /dev/null +++ b/server/rebar.config @@ -0,0 +1,2 @@ +{erl_opts, [debug_info]}. +{deps, []}. diff --git a/server/rebar.lock b/server/rebar.lock new file mode 100644 index 0000000..57afcca --- /dev/null +++ b/server/rebar.lock @@ -0,0 +1 @@ +[]. diff --git a/server/src/proto_sup.erl b/server/src/proto_sup.erl new file mode 100644 index 0000000..c7d5db6 --- /dev/null +++ b/server/src/proto_sup.erl @@ -0,0 +1,18 @@ +% Copyright (c) Camden Dixie O'Brien +% SPDX-License-Identifier: AGPL-3.0-only + +-module(proto_sup). +-behaviour(supervisor). + +-export([start_link/1]). +-export([init/1]). + +start_link(Port) -> + supervisor:start_link({local, ?MODULE}, ?MODULE, [Port]). + +init([Port]) -> + SupFlags = #{stragegy => one_for_all, + intensity => 1, + period => 5}, + ChildSpecs = [tcp_server:child_spec(Port)], + {ok, {SupFlags, ChildSpecs}}. diff --git a/server/src/study_system_server.app.src b/server/src/study_system_server.app.src new file mode 100644 index 0000000..73c2d2f --- /dev/null +++ b/server/src/study_system_server.app.src @@ -0,0 +1,10 @@ +{application, study_system_server, + [{description, "Study System Server"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {study_system_server_app, []}}, + {applications, [kernel, stdlib]}, + {env, []}, + {modules, []}, + {licenses, ["AGPL-3.0-only"]}, + {links, []}]}. diff --git a/server/src/study_system_server_app.erl b/server/src/study_system_server_app.erl new file mode 100644 index 0000000..095435a --- /dev/null +++ b/server/src/study_system_server_app.erl @@ -0,0 +1,14 @@ +% Copyright (c) Camden Dixie O'Brien +% SPDX-License-Identifier: AGPL-3.0-only + +-module(study_system_server_app). +-behaviour(application). + +-export([start/2, stop/1]). + +start(_StartType, _StartArgs) -> + Port = application:get_env(study_system_server, port, 12888), + proto_sup:start_link(Port). + +stop(_State) -> + ok. diff --git a/server/src/tcp_server.erl b/server/src/tcp_server.erl new file mode 100644 index 0000000..588f212 --- /dev/null +++ b/server/src/tcp_server.erl @@ -0,0 +1,65 @@ +% Copyright (c) Camden Dixie O'Brien +% SPDX-License-Identifier: AGPL-3.0-only + +-module(tcp_server). +-behaviour(gen_server). + +-export([start_link/1, child_spec/1]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(state, {socket, acceptor}). + +start_link(Port) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [Port], []). + +child_spec(Port) -> + #{id => ?MODULE, + start => {?MODULE, start_link, [Port]}, + restart => permanent, + shutdown => 5000, + type => worker, + modules => [?MODULE]}. + +init([Port]) -> + {ok, Socket} = gen_tcp:listen(Port, [binary, {active, true}]), + Pid = spawn_link(fun() -> acceptor_loop(Socket) end), + {ok, #state{socket = Socket, acceptor = Pid}}. + +handle_call(_Request, _From, State) -> + {reply, ok, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({'EXIT', Pid, Reason}, State = #state{acceptor = Pid}) -> + {stop, {acceptor_died, Reason}, State}; +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, #state{socket = Socket}) -> + gen_tcp:close(Socket), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +acceptor_loop(Socket) -> + case gen_tcp:accept(Socket) of + {ok, ClientSocket} -> + gen_tcp:controlling_process( + ClientSocket, + spawn(fun() -> handle_connection(ClientSocket) end)), + acceptor_loop(Socket); + {error, closed} -> + ok + end. + +handle_connection(Socket) -> + receive + {tcp, Socket, Data} -> + gen_tcp:send(Socket, Data), + handle_connection(Socket); + {tcp_closed, Socket} -> + ok + end.