summaryrefslogtreecommitdiff
path: root/server.erl
blob: 2913999cee56dcc5ffe09e0441fa89c86a1012f8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
-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.