-module(server). -export([server/2, worker/2, create_workers/2, acceptor/2, listener/1, manager/1, broadcast/2, broadcast/3, handle_data/3]). server(Port, NumWorkers) -> ets:new(client_table, [named_table, public, {read_concurrency, true}]), ets:insert(client_table, {counter, 0}), {ok, ServerSock} = gen_tcp:listen(Port, [list]), spawn(?MODULE, create_workers, [NumWorkers, self()]), supervisor(ServerSock, []). create_workers(0, _) -> ok; create_workers(Num, Supervisor) -> Supervisor ! worker_new, create_workers(Num - 1, Supervisor). supervisor(ServerSock, Pids) -> receive worker_new -> {NewPid, _} = spawn_monitor(?MODULE, worker, [ServerSock, Pids]), io:format("Creating new worker ~p\n", [NewPid]), message_all(Pids, {ping, NewPid}), supervisor(ServerSock, [NewPid | Pids]); {'DOWN', _, process, From, Reason} -> io:format("Worker ~p exited due to ~p\n", [From, Reason]), self() ! create, message_all(lists:delete(From, Pids), {close, From}), supervisor(ServerSock, lists:delete(From, Pids)); {announce, Data} -> [Pid|_] = Pids, Pid ! {broadcast, "Server", ok, Data}, supervisor(ServerSock, Pids) end. message_all([Pid|Pids], Data) -> Pid ! Data, message_all(Pids, Data); message_all([], _) -> ok. worker(ServerSock, Pids) -> process_flag(trap_exit, false), ListenerPid = spawn_link(?MODULE, listener, [self()]), spawn_link(?MODULE, acceptor, [ListenerPid, ServerSock]), manager(Pids). manager(Socks) -> receive {ping, Sock} -> manager([Sock | Socks]); {close, Sock} -> manager(lists:delete(Sock, Socks)); {repeat, Data} -> broadcast(Socks, Data, norepeat), manager(Socks); {broadcast, FromIden, FromSock, Data} -> broadcast(lists:delete(FromSock, Socks), io_lib:format("~s: ~s\n", [FromIden, Data])), manager(Socks); {pm, FromIden, ToSock, Data} -> broadcast([ToSock], io_lib:format("(pm) ~s: ~s\n", [FromIden, Data])), manager(Socks); {rename, _, OldIden, NewIden } -> self() ! {broadcast, "Server", ok, io_lib:format("~s renamed to ~s", [OldIden, NewIden])}, manager(Socks) end. listener(ManagerPid) -> receive {tcp, Sock, Data} -> spawn(?MODULE, handle_data, [ManagerPid, Sock, Data]), listener(ManagerPid); {tcp_opened, Sock} -> ManagerPid ! {ping, Sock}, client_add(Sock), listener(ManagerPid); {tcp_closed, Sock} -> ManagerPid ! {close, Sock}, client_del(Sock), listener(ManagerPid) end. acceptor(ListenerPid, ServerSock) -> case gen_tcp:accept(ServerSock) of {ok, Sock} -> ok = gen_tcp:controlling_process(Sock, ListenerPid), ListenerPid ! {tcp_opened, Sock}, acceptor(ListenerPid, ServerSock) end. handle_data(ManagerPid, From, Data) -> case catch parse_data(ManagerPid, From, string:trim(Data)) of ok -> ok; _ -> ManagerPid ! {pm, "Server", From, "ERR: Transaction Failed"} end. parse_data(ManagerPid, From, Data) -> case string:tokens(Data, " ") of ["/help" | _] -> ManagerPid ! {pm, "Server", From, "..."}; ["/pm" | [ToIden | _]] -> NewData = string:slice(Data, string:length(ToIden) + 5), ManagerPid ! {pm, client_iden(From), client_sock(ToIden), NewData}; ["/pm" | _] -> ManagerPid ! {pm, "Server", From, "/pm: ..."}; ["/rename" | [NewIden | []]] -> {OldIden, _} = client_update(From, NewIden), ManagerPid ! {rename, From, OldIden, NewIden}; ["/rename" | _] -> ManagerPid ! {pm, "Server", From, "/rename: ..."}; [[$/ | _] | _] -> ManagerPid ! {pm, "Server", From, "Invalid Command, use /help for help"}; [] -> ok; _ -> ManagerPid ! {broadcast, client_iden(From), From, Data} end, ok. broadcast(Socks, Data) -> broadcast(Socks, Data, ok). broadcast([Pid|Socks], Data, norepeat) when is_pid(Pid) -> broadcast(Socks, Data, norepeat); broadcast([Pid|Socks], Data, Opt) when is_pid(Pid) -> Pid ! {repeat, Data}, broadcast(Socks, Data, Opt); broadcast([Sock|Socks], Data, Opt) -> gen_tcp:send(Sock, Data), broadcast(Socks, Data, Opt); broadcast([], _, _) -> ok. client_add(Sock) -> Count = ets:update_counter(client_table, counter, 1), Iden = "Mitko"++integer_to_list(Count), true = ets:insert_new(client_table, {Iden, Sock}), true = ets:insert_new(client_table, {Sock, Iden}), Iden. client_del(Sock) -> [{_, Iden}|_] = ets:lookup(client_table, Sock), ets:delete(client_table, Sock), ets:delete(client_table, Iden), Iden. client_update(Sock, NewIden) -> [{_, OldIden}|_] = ets:lookup(client_table, Sock), ets:delete(client_table, OldIden), [] = ets:lookup(client_table, NewIden), true = ets:update_element(client_table, Sock, {2, NewIden}), true = ets:insert_new(client_table, {NewIden, Sock}), {OldIden, NewIden}. client_iden(Sock) -> [{_, Iden}|_] = ets:lookup(client_table, Sock), Iden. client_sock(Iden) -> [{_, Sock}|_] = ets:lookup(client_table, Iden), Sock.