Author: bdonlan
Date: 2005-12-09 15:34:01 -0500 (Fri, 09 Dec 2005)
New Revision: 944

Added:
   trunk/erlang/
   trunk/erlang/server/
   trunk/erlang/server/line_encode.erl
   trunk/erlang/server/server.erl
Log:
First-pass erlang server. Still very hacky and stuff.

Added: trunk/erlang/server/line_encode.erl
===================================================================
--- trunk/erlang/server/line_encode.erl 2005-11-27 00:08:28 UTC (rev 943)
+++ trunk/erlang/server/line_encode.erl 2005-12-09 20:34:01 UTC (rev 944)
@@ -0,0 +1,29 @@
+-module(line_encode).
+-export([decode/1, encode/1]).
+
+escape([]) -> [];
+escape([$\t|T]) -> [$\e, $t | escape(T)];
+escape([$\n|T]) -> [$\e, $n | escape(T)];
+escape([$\r|T]) -> [$\e, $r | escape(T)];
+escape([$\e|T]) -> [$\e, $e | escape(T)];
+escape([H|T])    -> [H | escape(T)].
+
+unescape([]) -> [];
+unescape([$\e, $t | T]) -> [$\t | unescape(T)];
+unescape([$\e, $e | T]) -> [$\e | unescape(T)];
+unescape([$\e, $r | T]) -> [$\r | unescape(T)];
+unescape([$\e, $n | T]) -> [$\n | unescape(T)];
+unescape([H|T])           -> [H | unescape(T)].
+
+decode([]) -> [];
+decode(L)  ->
+    { Pre, Post } = lists:splitwith(fun(C) -> C =/= $\t end, L),
+    case Post of
+        [] -> [Pre];
+        [_|L2]  ->
+            ArgsAfter = decode(L2),
+            [Pre|ArgsAfter]
+    end.
+
+encode([]) -> [];
+encode([H|T]) -> lists:append(H, [$\t|encode(T)]).

Added: trunk/erlang/server/server.erl
===================================================================
--- trunk/erlang/server/server.erl      2005-11-27 00:08:28 UTC (rev 943)
+++ trunk/erlang/server/server.erl      2005-12-09 20:34:01 UTC (rev 944)
@@ -0,0 +1,386 @@
+-module(server).
+-export([cstart/1, start/1, stop/0, worker/2, haver_entry/2, setup_schema/0,
+         clear_transients/0]).
+-include_lib("mnemosyne/include/mnemosyne.hrl").
+
+-record(users, {name,
+                pid}).
+-record(channels, {name, notused}).
+-record(chanjoin, {channel, user}).
+
+
+setup_schema() ->
+    mnesia:delete_table(users),
+    mnesia:create_table(users, [
+                        {attributes, record_info(fields, users)},
+                        {type, set}
+                        ]),
+    mnesia:delete_table(chanjoin),
+    mnesia:create_table(chanjoin, [
+                        {attributes, record_info(fields, chanjoin)},
+                        {type, bag}
+                        ]),
+    mnesia:delete_table(channels),
+    mnesia:create_table(channels, [
+                        {attributes, record_info(fields, channels)},
+                        {disc_copies, [node()]},
+                        {type, set}
+                        ]).
+
+clear_transients() ->
+    mnesia:clear_table(users),
+    mnesia:clear_table(chanjoin).
+
+cstart(Port) ->
+    register(haver, self()),
+    clear_transients(),
+    {ok, LSock} = gen_tcp:listen(Port, [list, {packet, 0},
+                                              {active, true},
+                                              {reuseaddr, true}]),
+    spawn_link(?MODULE, worker, [self(), LSock]),
+    spawn_loop(LSock).
+    
+init_tables() ->
+    mnesia:wait_for_tables([users, chanjoin, channels], 5 * 1000).
+
+start(Port) ->
+    case whereis(haver) of
+        undefined -> do_start(Port);
+        Pid -> { already_running, Pid }
+    end.
+
+do_start(Port) ->
+    mnesia:start(),
+    application:start(mnemosyne),
+    case init_tables() of
+        ok -> { ok, spawn(?MODULE, cstart, [Port]) };
+        Err -> { nok, Err }
+    end.
+stop() -> haver ! shutdown.
+
+reap_child(Pid) ->
+    F = fun() ->
+        Q = query
+            [ E || E <- table(users), E.pid = Pid ]
+            end,
+        R = mnemosyne:eval(Q),
+        case R of
+            [ E ] ->
+                Name = E#users.name,
+                Q2 = query
+                     [ J || J <- table(chanjoin), J.user = Name ]
+                     end,
+                R2 = mnemosyne:eval(Q2),
+                lists:map(fun(J) -> mnesia:delete_object(J) end, R2),
+                mnesia:delete_object(E),
+                ok;
+            _ -> ok
+        end end,
+    R = mnesia:transaction(F),
+    case R of
+        { atomic, ok } -> { ok };
+        _ -> io:format("Warning: reap failed for pid ~p~n: ~p~n", [Pid,R]),
+             { nok, R }
+    end.
+        
+
+spawn_loop(LSock) ->
+    process_flag(trap_exit, true),
+    receive
+        next_worker ->
+            spawn_link(?MODULE, worker, [self(), LSock]),
+            spawn_loop(LSock);
+        shutdown -> ok;
+        { 'EXIT', Pid, Reason } ->
+            io:format("Child ~p~n died with ~p~n", [Pid, Reason]),
+            reap_child(Pid),
+            spawn_loop(LSock);
+        Other ->
+            io:format("Unknown message: ~p~n", Other)
+    end.
+
+worker(Parent, LSock) ->
+    process_flag(trap_exit, false),
+    case gen_tcp:accept(LSock) of
+        {ok, Socket} ->
+            Parent ! next_worker,
+            peer_entry(Parent, Socket),
+            reap_child(self()),
+            gen_tcp:close(Socket);
+        {error, Reason} ->
+            Parent ! next_worker,
+            io:format("Accept error ~p~n", [Reason])
+    end.
+
+peer_entry(Parent, Socket) ->
+    Pid = spawn_link(?MODULE, haver_entry, [Parent, Socket]),
+    peer(Socket, "", Pid).
+
+peer(Socket, Buf, Pid) ->
+    Buf2 = peer_proc(Buf, Pid),
+    receive
+        {tcp, Socket, Data} ->
+            io:format("incoming packet ~p~n", [Data]),
+            peer(Socket, lists:append(Buf2, Data), Pid);
+        {tcp_closed, Why} -> exit({socket_closed, Why});
+        Something ->
+            io:format("Unknown msg ~p~n", [Something]),
+            nok
+    end.
+
+peer_proc(Buf, Pid) ->
+    io:format("peer_proc ~p~n", [Buf]),
+    case lists:partition(fun(C) -> not ((C == $\r) or (C == $\n)) end, Buf) of
+        { _, [] } -> Buf;
+        { L, Rem} ->
+            Pid ! {line, line_encode:decode(L) },
+            Rem2 = lists:dropwhile(fun(C) -> ((C == $\r) or (C == $\n)) end, 
Rem),
+            Rem2
+    end.
+
+haver_sendline(Line) ->
+    Socket = get(socket),
+    gen_tcp:send(Socket, line_encode:encode(Line)),
+    gen_tcp:send(Socket, "\r\n").
+
+haver_entry(Parent, Socket) ->
+    link(Parent),
+    put(socket, Socket),
+    receive
+        { line, [ "HAVER", Vers | _ ] } ->
+            haver_sendline(["HAVER", "localhost", "Erver/0.0"]),
+            haver_login();
+        { line, _ } ->
+            gen_tcp:close(Socket),
+            exit(rude_peer);
+        Foo -> io:format("haver_entry unknown msg ~p~n", [Foo])
+
+    end.
+
+handle_ident(Uid) ->
+    F = fun() ->
+        Q = query
+            [ E || E <- table(users), E.name = Uid ]
+            end,
+        R = mnemosyne:eval(Q),
+        io:format("~p~n", [R]),
+        case R of
+            [] -> mnesia:write(#users{name = Uid, pid = self()}),
+                  ok;
+            _  -> collision
+        end
+    end,
+    R = mnesia:transaction(F),
+    case R of
+        { atomic, ok } ->
+            haver_sendline(["HELLO", Uid]),
+            put(uid, Uid),
+            haver_mainloop();
+        { atomic, collision } ->
+            haver_sendline(["FAIL", "IDENT", "exists.user", Uid]),
+            haver_login();
+        Wtf -> io:format("wtf: ~p~n", [Wtf])
+    end.
+
+haver_login() ->
+    receive
+        { line, [ "IDENT", Uid ] } -> handle_ident(Uid);
+        { line, _ } ->
+            haver_sendline(["DIE", "ihateyou"]),
+            gen_tcp:close(get(socket)),
+            exit(rudepeer);
+        M -> exit({unexpected, M})
+    end.    
+
+find_user(Uid) ->
+    T = fun() ->
+        Q = query
+            [ U.pid || U <- table(users), U.name = Uid ]
+            end,
+        mnemosyne:eval(Q)
+        end,
+    R = mnesia:transaction(T),
+    case R of
+        { atomic, [Pid] } -> Pid;
+        { atomic, []    } -> notfound;
+        { atomic, Wtf } ->
+            io:format("find_user wtf: ~p~n", [Wtf]),
+            notfound;
+        _ ->
+            io:format("find_user err: ~p~n", [R]),
+            notfound
+        end.
+
+haver_open(Channel) ->
+    T = fun() ->
+        Q = query
+            [ C || C <- table(channels), C.name = Channel ]
+            end,
+        R = mnemosyne:eval(Q),
+        case R of
+            [] -> mnesia:write(#channels{name = Channel, notused = Channel}),
+                  ok;
+            _  -> collision
+        end end,
+    R = mnesia:transaction(T),
+    case R of
+        { atomic, Result } -> Result
+    end.
+
+chan_broadcast(Channel, Message) ->
+    T = fun() ->
+        Q = query
+            [ U.pid
+            || U <- table(users),
+               J <- table(chanjoin),
+               U.name = J.user,
+               J.channel = Channel
+            ] end,
+        mnemosyne:eval(Q)
+        end,
+    R = mnesia:transaction(T),
+    case R of
+        { atomic, L } -> lists:map(fun(P) -> P ! Message end, L);
+        _ -> io:format("err, bad mnesia txn response ~p~n", [R]),
+             exit({mnesia_fail, R})
+    end.
+
+haver_join(Channel) ->
+    Uid = get(uid),
+    T = fun() ->
+        case mnesia:read({channels, Channel}) of
+            [] -> mnesia:abort(notfound);
+            _  -> ok
+        end,
+        Q = query
+            [ J || J <- table(chanjoin), J.user = Uid, J.channel = Channel ]
+            end,
+        case mnemosyne:eval(Q) of
+            [_]->
+                mnesia:abort(already_joined);
+            [] ->
+                mnesia:write(#chanjoin{channel = Channel, user = Uid}),
+                ok
+        end end,
+    R = mnesia:transaction(T),
+    case R of
+        { aborted, already_joined } -> collision;
+        { aborted, notfound } -> notfound;
+        { atomic, ok } ->
+            chan_broadcast(Channel, { sendline, [ "JOIN", Channel, Uid ] }),
+            ok
+    end.
+
+haver_part(Channel) ->
+    Uid = get(uid),
+    T = fun() ->
+        Q = query
+            [ J || J <- table(chanjoin), J.user = Uid, J.channel = Channel ]
+            end,
+        case mnemosyne:eval(Q) of
+            [] -> notfound;
+            [R]->
+                io:format("R=~p~n", [R]),
+                mnesia:delete_object(R), ok
+        end end,
+    R = mnesia:transaction(T),
+    case R of
+        { atomic, notfound } -> notfound;
+        { atomic, ok } -> 
+            chan_broadcast(Channel, { sendline, [ "PART", Channel, Uid ] }),
+            ok
+    end.
+
+haver_getlist("&lobby", "channel") -> { ok, mnesia:dirty_all_keys(channels) };
+haver_getlist("&lobby", "user")   -> { ok, mnesia:dirty_all_keys(users) };
+haver_getlist(Channel, "user")    ->
+    T = fun() ->
+        case mnesia:read({channels, Channel}) of
+            [] -> mnesia:abort(notfound);
+            _  -> ok
+        end,
+        Records = mnesia:read({chanjoin, Channel}),
+        Uids = lists:map(fun({chanjoin, _, Uid}) -> Uid end, Records),
+        { ok, Uids }
+    end,
+    case mnesia:transaction(T) of
+        { aborted, notfound } -> notfound;
+        { atomic, {ok, Uids} } -> { ok, Uids };
+        Other ->
+            io:format("getlist(~p~n, \"user\") unexpected: ~p~n", [Channel, 
Other]),
+            exit(Other)    
+    end;
+haver_getlist(_, "channel") -> { ok, [] };
+haver_getlist(_, Type) -> {badtype, Type}.
+
+haver_list(C, T) ->
+    R = haver_getlist(C, T),
+    case R of
+        { ok, Members } -> haver_sendline(["LIST", C, T | Members ]);
+        notfound -> haver_sendline(["FAIL", "LIST", "unknown.channel", C]);
+        badtype  -> haver_sendline(["FAIL", "LIST", "unknown.type", C])
+    end.
+
+haver_sendchan(Channel, Message) ->
+    Uid = get(uid),
+    T = fun() ->    
+        Q = query
+            [ J || J <- table(chanjoin),
+                   J.user = Uid,
+                   J.channel = Channel
+            ] end,
+        case mnemosyne:eval(Q) of
+            [] -> mnesis:abort(notjoined);
+            _  -> ok
+        end end,
+    R = mnesia:transaction(T),
+    case R of
+        { aborted, notjoined } ->
+            haver_sendline(["FAIL", "IN", "not.joined", Channel]);
+        { atomic, ok } ->
+            chan_broadcast(Channel, { sendline, ["IN", Channel, Uid | Message] 
})
+    end.
+
+haver_mainloop() -> 
+    V = receive
+        { line, [ "OPEN", Channel ] } ->
+            case haver_open(Channel) of
+                ok -> haver_sendline(["OPEN", Channel]);
+                collision -> haver_sendline(["FAIL", "OPEN", "exists.channel", 
Channel])
+            end, ok;
+        { line, [ "JOIN", Channel ] } ->
+            case haver_join(Channel) of
+                ok -> ok;
+                notfound -> haver_sendline(["FAIL", "JOIN", "unknown.channel", 
Channel]);
+                collision -> haver_sendline(["FAIL", "JOIN", "already.joined", 
Channel])
+            end, ok;
+        { line, [ "PART", Channel ] } ->
+            case haver_part(Channel) of
+                ok -> haver_sendline(["PART", Channel, get(uid)]);
+                notfound -> haver_sendline(["FAIL", "PART", "not.joined", 
Channel])
+            end, ok;
+        { line, [ "TO", Uid | Message ] } ->
+            Pid = find_user(Uid),
+            case Pid of
+                notfound -> haver_sendline(["FAIL", "TO", "unknown.user", 
Uid]);
+                _        -> Pid ! { privmsg, get(uid), Message }
+            end, ok;
+        { line, [ "IN", Channel | Message ] } ->
+            haver_sendchan(Channel, Message);
+        { line, [ "LIST", Channel, Type ] } -> haver_list(Channel, Type), ok;
+        { line, [ "POKE" | L ] } -> haver_sendline(["OUCH" | L]);
+        { line, ["DIE"]} ->
+            [] = [1]; % XXX
+        { line, [ L | _ ] } ->
+            io:format("unmatched line: ~p~n", L),
+            haver_sendline(["FAIL", L, "unknown.cmd"]),
+            ok;
+        { line, L } ->
+            exit({err_line, L});
+        { sendline, L } -> haver_sendline(L), ok;
+        { privmsg, From, Message } ->
+            haver_sendline(["FROM", From | Message]),
+            ok;
+        Other -> io:format("unknown msg: ~p~n", Other), ok
+        end,
+    haver_mainloop().


Reply via email to