Mark McGranaghan 8d31ec147c move to vendor
2012-11-17 08:21:42 -08:00

170 lines
5.6 KiB
Erlang

-module(test).
-export([listen/1,
handle_client/1,
maintain_clients/1,
start/1,
stop/0,
controller/1]).
-author("jerith").
-define(TCP_OPTIONS,[list, {packet, 0}, {active, false}, {reuseaddr, true}]).
-record(player, {name=none, socket, mode}).
%% To allow incoming connections, we need to listen on a TCP port.
%% This is also the entry point for our server as a whole, so it
%% starts the client_manager process and gives it a name so the rest
%% of the code can get to it easily.
listen(Port) ->
{ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
register(client_manager, spawn(?MODULE, maintain_clients, [[]])),
do_accept(LSocket).
%% Accepting a connection gives us a connection socket with the
%% newly-connected client on the other end. Since we want to accept
%% more than one client, we spawn a new process for each and then wait
%% for another connection on our listening socket.
do_accept(LSocket) ->
case gen_tcp:accept(LSocket) of
{ok, Socket} ->
spawn(?MODULE, handle_client, [Socket]),
client_manager ! {connect, Socket};
{error, Reason} ->
io:format("Socket accept error: ~s~n", [Reason])
end,
do_accept(LSocket).
%% All the client-socket process needs to do is wait for data and
%% forward it to the client_manager process which decides what to do
%% with it. If the client disconnects, we let client_manager know and
%% then quietly go away.
handle_client(Socket) ->
case gen_tcp:recv(Socket, 0) of
{ok, Data} ->
client_manager ! {data, Socket, Data},
handle_client(Socket);
{error, closed} ->
client_manager ! {disconnect, Socket}
end.
%% This is the main loop of the client_manager process. It maintains
%% the list of "players" and calls the handler for client input.
maintain_clients(Players) ->
io:format("Players:~n", []),
lists:foreach(fun(P) -> io:format(">>> ~w~n", [P]) end, Players),
receive
{connect, Socket} ->
Player = #player{socket=Socket, mode=connect},
send_prompt(Player),
io:format("client connected: ~w~n", [Player]),
NewPlayers = [Player | Players];
{disconnect, Socket} ->
Player = find_player(Socket, Players),
io:format("client disconnected: ~w~n", [Player]),
NewPlayers = lists:delete(Player, Players);
{data, Socket, Data} ->
Player = find_player(Socket, Players),
NewPlayers = parse_data(Player, Players, Data),
NewPlayer = find_player(Socket, NewPlayers),
send_prompt(NewPlayer)
end,
maintain_clients(NewPlayers).
%% find_player is a utility function to get a player record associated
%% with a particular socket out of the player list.
find_player(Socket, Players) ->
{value, Player} = lists:keysearch(Socket, #player.socket, Players),
Player.
%% delete_player returns the player list without the given player. It
%% deletes the player from the list based on the socket rather than
%% the whole record because the list might hold a different version.
delete_player(Player, Players) ->
lists:keydelete(Player#player.socket, #player.socket, Players).
%% Sends an appropriate prompt to the player. Currently the only
%% prompt we send is the initial "Name: " when the player connects.
send_prompt(Player) ->
case Player#player.mode of
connect ->
gen_tcp:send(Player#player.socket, "Name: ");
active ->
ok
end.
%% Sends the given data to all players in active mode.
send_to_active(Prefix, Players, Data) ->
ActivePlayers = lists:filter(fun(P) -> P#player.mode == active end,
Players),
lists:foreach(fun(P) -> gen_tcp:send(P#player.socket, Prefix ++ Data) end,
ActivePlayers),
ok.
%% We don't really do much parsing, but that will probably change as
%% more features are added. Currently this handles naming the player
%% when he first connects and treats everything else as a message to
%% send.
parse_data(Player, Players, Data) ->
case Player#player.mode of
active ->
send_to_active(Player#player.name ++ ": ",
delete_player(Player, Players), Data),
Players;
connect ->
UPlayer = Player#player{name=bogostrip(Data), mode=active},
[UPlayer | delete_player(Player, Players)]
end.
%% Utility methods to clean up the name before we apply it. Called
%% bogostrip rather than strip because it returns the first continuous
%% block of non-matching characters rather stripping matching
%% characters off the front and back.
bogostrip(String) ->
bogostrip(String, "\r\n\t ").
bogostrip(String, Chars) ->
LStripped = string:substr(String, string:span(String, Chars)+1),
string:substr(LStripped, 1, string:cspan(LStripped, Chars)).
%% Here we have some extra code to test other bits of pygments' Erlang
%% lexer.
get_timestamp() ->
{{Year,Month,Day},{Hour,Min,Sec}} = erlang:universaltime(),
lists:flatten(io_lib:format(
"~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0BZ",
[Year, Month, Day, Hour, Min, Sec])).
a_binary() ->
<< 100:16/integer, 16#7f >>.
a_list_comprehension() ->
[X*2 || X <- [1,2,3]].
map(Fun, [H|T]) ->
[Fun(H) | map(Fun, T)];
map(Fun, []) ->
[].
%% pmap, just because it's cool.
pmap(F, L) ->
Parent = self(),
[receive {Pid, Result} ->
Result
end || Pid <- [spawn(fun() ->
Parent ! {self(), F(X)}
end) || X <- L]].