Permalink
Browse files

Major refactoring to a FSM

- the main core now implements a gen_fsm behaviour i.e it's a state machine
- some stuff were extracted to a separate file ircbot_plugins.erl
- ircbot_api.erl changed to reflect the gen_fsm api and added some more
  helper functions
- ircbot_connection.erl has been cosmetically changed

I hope this will be the last major change to this code. For sure, I
introduced some new bugs with this change, but that's life - patches welcome.
  • Loading branch information...
1 parent bb3f48c commit 0bead99c7ca4348b916dcb9bf744b6fd1d29578f @gdamjan committed Sep 12, 2010
Showing with 229 additions and 132 deletions.
  1. +22 −8 src/ircbot_api.erl
  2. +26 −25 src/ircbot_connection.erl
  3. +44 −0 src/ircbot_plugins.erl
  4. +137 −99 src/ircbot_server.erl
View
@@ -4,31 +4,45 @@
-module(ircbot_api, [IrcbotRef]).
-author("gdamjan@gmail.com").
--export([connect/0, disconnect/0, reconnect/0, send_data/1]).
--export([add_plugin/2, delete_plugin/2, which_plugins/0]).
+-export([connect/0, disconnect/0, reconnect/0, send_data/1, send_message/3, privmsg/2, notice/2]).
+-export([send_event/1, add_plugin/2, delete_plugin/2, which_plugins/0]).
connect() ->
- gen_server:call(IrcbotRef, connect).
+ gen_fsm:sync_send_event(IrcbotRef, connect).
disconnect() ->
- gen_server:call(IrcbotRef, disconnect).
+ gen_fsm:sync_send_event(IrcbotRef, disconnect).
reconnect() ->
disconnect(),
connect().
+send_event(Event) ->
+ gen_fsm:send_event(IrcbotRef, Event).
+
+
add_plugin(Plugin, Args) ->
- gen_server:call(IrcbotRef, {add_plugin, Plugin, Args}).
+ gen_fsm:sync_send_all_state_event(IrcbotRef, {add_plugin, Plugin, Args}).
delete_plugin(Plugin, Args) ->
- gen_server:call(IrcbotRef, {delete_plugin, Plugin, Args}).
+ gen_fsm:sync_send_all_state_event(IrcbotRef, {delete_plugin, Plugin, Args}).
which_plugins() ->
- gen_server:call(IrcbotRef, which_plugins).
+ gen_fsm:sync_send_all_state_event(IrcbotRef, which_plugins).
+
send_data(Data) ->
- gen_server:cast(IrcbotRef, {send_data, Data}).
+ send_event({send_data, Data}).
+
+send_message(Cmd, Destination, Msg) ->
+ send_data([Cmd, " ", Destination, " :", Msg]).
+
+privmsg(Destination, Msg) ->
+ send_message("PRIVMSG", Destination, Msg).
+
+notice(Destination, Msg) ->
+ send_message("NOTICE", Destination, Msg).
% save_state(Filename) ->
% State = gen_event:call(IrcbotRef, get_state),
@@ -1,14 +1,14 @@
-module(ircbot_connection).
-author("gdamjan@gmail.com").
--include_lib("ircbot.hrl").
+-include("ircbot.hrl").
-define(CRNL, "\r\n").
--export([start_link/2, code_switch/1, connect/3]).
+-export([start_link/3, code_switch/1, connect/3]).
-start_link(Host, Port) ->
- spawn_link(?MODULE, connect, [self(), Host, Port]).
+start_link(Parent, Host, Port) ->
+ spawn_link(?MODULE, connect, [Parent, Host, Port]).
connect(Parent, Host, Port) ->
connect(Parent, Host, Port, 0).
@@ -17,12 +17,12 @@ connect(Parent, Host, Port, Backoff) when Backoff > 5 ->
connect(Parent, Host, Port, 5);
connect(Parent, Host, Port, Backoff) ->
- Opts = [ binary, {active, true}, {packet, line}, {keepalive, true},
+ Options = [ binary, {active, true}, {packet, line}, {keepalive, true},
{send_timeout, ?SEND_TIMEOUT}],
- case gen_tcp:connect(Host, Port, Opts, ?CONNECT_TIMEOUT) of
+ case gen_tcp:connect(Host, Port, Options, ?CONNECT_TIMEOUT) of
{ok, Sock} ->
- gen_server:cast(Parent, {connect_success, self()}),
- loop({Parent, Sock, ?RECV_TIMEOUT});
+ Parent:send_event({connected, self()}),
+ loop({Parent, Sock});
{error, Reason} ->
error_logger:format("Error connecting: ~s~n", [inet:format_error(Reason)]),
timer:sleep(Backoff * Backoff * 5000),
@@ -32,37 +32,38 @@ connect(Parent, Host, Port, Backoff) ->
end.
-loop({Parent, Sock, Timeout} = State) ->
+loop({Parent, Sock} = State) ->
receive
+ code_switch ->
+ ?MODULE:code_switch(State);
+
% data to send away on the socket
- {send_data, Data} ->
+ {send, Data} ->
debug(out, [Data]), % for debuging only
- gen_tcp:send(Sock, [Data, ?CRNL]),
+ ok = gen_tcp:send(Sock, [Data, ?CRNL]),
loop(State);
- % data received from the socket
+ % data received
{tcp, Sock, Data} ->
[Line|_Tail] = re:split(Data, ?CRNL), % strip the CRNL at the end
debug(in, [Line]), % for debuging only
- gen_server:cast(Parent, {received_data, Line}),
+ Parent:send_event({received, Line}),
loop(State);
- % close socket and quit
- quit ->
- gen_tcp:close(Sock);
-
- % Force the use of 'codeswitch/1' from the latest MODULE version
- code_switch ->
- ?MODULE:code_switch(State);
+ % socket closed
+ {tcp_closed, Sock} ->
+ error_logger:format("Socket ~w closed [~w]~n", [Sock, self()]);
- % handle errors on the socket
+ % socket errors
{tcp_error, Sock, Reason} ->
error_logger:format("Socket ~w error: ~w [~w]~n", [Sock, Reason, self()]);
- {tcp_closed, Sock} ->
- error_logger:format("Socket ~w closed [~w]~n", [Sock, self()])
- after Timeout ->
- error_logger:format("No activity for more than ~b microseconds. Are we stuck?~n", [Timeout]),
+ % close socket and quit
+ quit ->
+ gen_tcp:close(Sock)
+
+ after ?RECV_TIMEOUT ->
+ error_logger:format("No activity for more than ~b microseconds. Are we stuck?~n", [?RECV_TIMEOUT]),
gen_tcp:close(Sock)
end.
@@ -0,0 +1,44 @@
+-module(ircbot_plugins).
+-author("gdamjan@gmail.com").
+
+-export([start_link/1, add_handler/3, delete_handler/3, which_handlers/1, notify/2]).
+
+start_link(Settings) ->
+ {ok, Plugins} = gen_event:start_link(),
+ Channels = proplists:get_value(channels, Settings, []),
+ gen_event:add_handler(Plugins, channels_plugin, Channels),
+ gen_event:add_handler(Plugins, pong_plugin, []),
+ gen_event:add_handler(Plugins, ctcp_plugin, []),
+ lists:foreach(
+ fun ({Plugin, Args}) ->
+ gen_event:add_handler(Plugins, Plugin, Args)
+ end,
+ proplists:get_value(plugins, Settings, [])
+ ),
+ {ok, Plugins}.
+
+add_handler(GenEv, Plugin, Args)->
+ case gen_event:add_handler(GenEv, Plugin, Args) of
+ ok ->
+ ok;
+ {'EXIT', Reason} ->
+ error_logger:error_msg("Problem loading plugin ~p ~p ~n", [Plugin, Reason]);
+ Other ->
+ error_logger:error_msg("Loading ~p reports ~p ~n", [Plugin, Other])
+ end.
+
+delete_handler(GenEv, Plugin, Args)->
+ case gen_event:delete_handler(GenEv, Plugin, Args) of
+ ok ->
+ ok;
+ {'EXIT', Reason} ->
+ error_logger:error_msg("Problem deleting plugin ~p ~p ~n", [Plugin, Reason]);
+ Other ->
+ error_logger:error_msg("Deleting ~p reports ~p ~n", [Plugin, Other])
+ end.
+
+notify(GenEv, Msg) ->
+ gen_event:notify(GenEv, Msg).
+
+which_handlers(GenEv) ->
+ gen_event:which_handlers(GenEv).
Oops, something went wrong. Retry.

0 comments on commit 0bead99

Please sign in to comment.