changed player to gen_server

This commit is contained in:
Jeena Paradies 2011-04-11 16:48:40 +02:00
parent da67b0a977
commit 135caff0e9
7 changed files with 1673 additions and 1445 deletions

View file

@ -1,121 +1,62 @@
%%% @doc This module handles TCP incomming and outcommint.
%% Parse a string formatted with the GGS protocol using
%% an FSM. Each char is put into the FSM, which incrementally
%% builds a list of strings which represent the complete
%% message.
-module(ggs_protocol).
-export([start_link/2,stop/1]).
-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
%% Old
-export([parse/1, getToken/1, create_message/4, send_command/2]).
-export([parse/2, getToken/1, create_message/4,
expect_headers/2, expect_data_section/2,
expect_headers/3, expect_data_section/3]).
-vsn(1.0).
%% tests
-export([to_dictionary/2]).
-record(state, {
player,
socket,
header_string,
header_list,
body,
content_length}).
start_link(Socket, Player) ->
gen_server:start_link(?MODULE, {Socket, Player}, []).
stop(Protocol) ->
gen_server:cast(Protocol, stop).
send_command(Protocol, {Command, Data}) ->
gen_server:cast(Protocol, {send, Command, Data}).
init({Socket, Player}) ->
erlang:port_connect(Socket, self()),
State = #state{
socket = Socket,
player = Player,
header_list = [],
header_string = "",
body = "",
content_length = -1
},
{ok, State}.
handle_cast({tcp, _Socket, Data}, State) ->
case State#state.content_length of
-1 -> % its a header
TmpHeader = State#state.header_string ++ Data,
case string:str(TmpHeader, "\n\n") of
0 -> % still in header
{reply, ok, State # state {header_string = TmpHeader}};
_ -> % we left the header
{Header, Body} = parse(TmpHeader),
{_, ContentLengthString} = lists:keyfind(content_len, 1, Header), % find Content-Length
{ContentLength, []} = string:to_integer(ContentLengthString),
{reply, ok, State#state{
header_list = Header,
header_string = "",
body = Body,
content_length = ContentLength}}
end;
Length -> % its a body
LBody = string:len(State#state.body),
LData = string:len(Data),
NewLength = LBody + LData,
if
NewLength < Length -> % not enough data
Body = State#state.body ++ Data,
{reply, ok, State#state {body = Body}};
NewLength > Length -> % too much data
EndOfMessagePos = LBody + LData - Length,
Body = State#state.body ++ string:substr(Data, 0, EndOfMessagePos),
NextHeader = string:substr(Data, EndOfMessagePos, LData),
Message = prettify(State#state.header_list, Body),
gen_player:notify_game(State#state.player, Message),
{reply, ok, State#state {
header_string = NextHeader,
header_list = [],
body = "",
content_length = -1}};
NewLength == Length -> % end of message
Message = prettify(State#state.header_list, State#state.body ++ Data),
gen_player:notify_game(State#state.player, Message),
{reply, ok, State#state {
header_string = "",
header_list = [],
body = "",
content_length = -1}}
end
end;
handle_cast({send, Command, Data}, State) ->
Message = create_message(Command, "text", "text", Data),
gen_tcp:send(State#state.socket, Message),
{noreply, State};
handle_cast(_Request, St) -> {stop, unimplemented, St}.
handle_call(_Request, _From, St) -> {stop, unimplemented, St}.
handle_info(_Info, St) -> {stop, unimplemented, St}.
% gen_fsm callbacks..
-export([init/1, handle_info/2, terminate/2, code_change/3, start_link/0]).
terminate(_Reason, _St) -> ok.
code_change(_OldVsn, St, _Extra) -> {ok, St}.
-define(SERVER, ?MODULE).
%% JONTES TESTS
%test() ->
% start_link(),
% parse("Token: %s\nServer-Command: define\nContent-Type: text\nContent-Length: 9\n\nHELLOWORLDToken: %s\nServer-Command: define\nContent-Type: text\nContent-Length: 9\n\nHELLOWORLDToken: %s\nServer-Command: define\n"),
% to_dictionary(["Hello: world", "Hi: there!"], []).
%% END TESTS
%% API Functions
parse(Data) ->
do_parse(Data, []).
parse(Protocol, Data) ->
lists:foreach(fun(X) ->
gen_fsm:sync_send_event(Protocol, {char, X})
end, Data).
start_link() ->
gen_fsm:start_link({local, ?SERVER}, ?MODULE, [], []).
% Start state: {[""],0}, meaning:
% - Start with no strings parsed
% - Start with a data-section-lengths of 0
init([]) ->
{ok,expect_headers,{[""], 0}}.
getToken(Parsed) ->
case lists:keyfind(token, 1, Parsed) of
{_, Value} ->
Value;
false ->
false
end.
create_message({Command, Data}) ->
create_message(Command, "text", "text", Data).
%% Assemble a message which can b
%e used as a reply to a client
create_message(Cmd, Enc, Acc, Data) ->
Length = integer_to_list(string:len(Data)),
Msg = "Client-Command: " ++ Cmd ++ "\n" ++
@ -127,41 +68,64 @@ create_message(Cmd, Enc, Acc, Data) ->
Data,
Msg.
%% Internal helpers
do_parse(Data, Headers) ->
NewLinePos = string:chr(Data, $\n),
Line = string:substr(Data, 1, NewLinePos-1),
Tokens = re:split(Line, ": ", [{return, list}]),
case handle(Tokens) of
{Command, more} ->
do_parse(string:substr(Data, NewLinePos+1), Headers ++ [Command]);
{separator, data_next} ->
{Headers, Data}
%%% Transitions
expect_headers(_Event, State) ->
{next_state, expect_headers, State}.
expect_data_section(_Event, State) ->
{next_state, expect_data_section, State}.
%%% End transitions
expect_headers({char, $\n}, _From, {Strings, Remains}) ->
[LastMessage|_] = Strings,
case LastMessage of
"" -> % We have a data section.. Last line should thus be the content length.
[LastMessage, SecondLast | _] = Strings,
case re:split(SecondLast, ": ", [{return, list}]) of
["Content-Length", X] ->
{Int,_} = string:to_integer(X),
{reply, ok, expect_data_section, {[""|Strings], Int}};
Other -> ok
end;
Other ->
{reply,ok,expect_headers, {[""|Strings], Remains}}
end;
expect_headers({char, Char}, _From, {[Current|Rest], Remains}) ->
NewCurrent = Current ++ [Char],
{reply,ok, expect_headers, {[NewCurrent|Rest], Remains}}.
expect_data_section({char, Char}, From, {Strings, Remains}) ->
case Remains of
0 ->
[LastMsg,_|Rest] = Strings,
NewMsg = LastMsg ++ [Char],
Result = [NewMsg|Rest],
{Pid,_} = From,
erlang:display(From),
Pid ! (prettify(to_dictionary(Rest, []), NewMsg)),
{reply, ok, expect_headers, {[""], 0}};
Other -> [LastMsg|Rest] = Strings,
NewMsg = LastMsg ++ [Char],
{reply, ok, expect_data_section, {[NewMsg|Rest], Remains-1}}
end.
handle([[]]) ->
{separator, data_next};
handle(["Server-Command", Param]) ->
{{srv_cmd, Param}, more};
handle(["Game-Command", Param]) ->
{{game_cmd, Param}, more};
handle(["Content-Length", Param]) ->
{{content_len, Param}, more};
handle(["Token", Param]) ->
{{token, Param}, more};
handle(["Content-Type", Param]) ->
{{content_type, Param}, more}.
%handle_data(Data, Length) ->
% {data, string:substr(Data,1,Length)}.
handle_call(_Msg, _From, State) ->
{noreply, State}.
handle_info(_Msg, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
prettify(Args, Data) ->
case lists:keyfind(srv_cmd, 1, Args) of
case lists:keyfind("Server-Command", 1, Args) of
{_, Value} ->
{srv_cmd, Value, Args, Data};
_Other ->
case lists:keyfind(game_cmd, 1, Args) of
case lists:keyfind("Game-Command", 1, Args) of
{_, Value} ->
{game_cmd, Value, Args, Data};
_ ->
@ -169,3 +133,9 @@ prettify(Args, Data) ->
end
end.
to_dictionary([], Dict) ->
Dict;
to_dictionary([S|Strings], Dict) ->
[First, Snd] = re:split(S, ": ", [{return, list}]),
to_dictionary(Strings, [{First, Snd}|Dict]).