Connection completely rewritten to use GenServer pattern

Buffer now calls Connection through :gen_server. Connection buffers all
messages leading to a chicken and egg situation, but alas. Maybe I'll figure
out a better solution some day.
This commit is contained in:
Mikko Ahlroth 2013-07-10 01:29:29 +03:00
parent 21048ad9b9
commit bbc2a6520b
3 changed files with 86 additions and 39 deletions

View file

@ -1,13 +1,23 @@
defmodule Nulform do
def run() do
connection = spawn Nulform.Connection, :run, []
connection <- {:nulform, :connect, 'irc.quakenet.org', 6667, "Nulform",
nil, "nulform", "␀form"}
{:ok, buffer} = :gen_server.start_link Nulform.Buffer, nil, []
IO.puts("Sent message")
{:ok, connection} = :gen_server.start_link Nulform.Connection,
[
self(), buffer, "irc.quakenet.org", 6667,
"Nulform", nil, "nulform", "␀form"
], []
buffer <- {:nulform, :buffer, :reset_connection, connection}
:gen_server.cast connection, :connect
listen_loop
end
def listen_loop() do
receive do
end
listen_loop
end
end

View file

@ -1,18 +1,18 @@
defmodule Nulform.Buffer do
use GenServer.Behaviour
@moduledoc """
A buffer takes outgoing messages from the bot and sends them to the
given connection process with a delay to prevent the bot from flooding
itself out of the network.
A buffer takes outgoing messages from the bot and sends them to the
given connection process with a delay to prevent the bot from flooding
itself out of the network.
The algorithm is as follows (reasonable values are t = 2, m = 10):
1. Read messages from input
2. Insert messages into queue
3. Set timer to be current time, if lower
4. For each message
4.1. Raise timer value by t
4.2. If timer value is higher than current time + m, next send is
at current time + t, otherwise send message immediately
The algorithm is as follows (reasonable values are t = 2, m = 10):
1. Read messages from input
2. Insert messages into queue
3. Set timer to be current time, if lower
4. For each message
4.1. Raise timer value by t
4.2. If timer value is higher than current time + m, next send is
at current time + t, otherwise send message immediately
"""
@t 2
@ -41,6 +41,9 @@ defmodule Nulform.Buffer do
{:send_message} ->
data = send_message data
data = handle_buffer data
{:nulform, :buffer, :reset_connection, pid} ->
data = data.connection pid
end
{:noreply, data}
end
@ -73,7 +76,7 @@ defmodule Nulform.Buffer do
if not Enum.empty? data.buffer do
[msg | tail] = data.buffer
data = data.buffer tail
data.connection <- {:nulform, :msg_out, msg}
:gen_server.cast data.connection, msg
data = data.timer data.timer + @t
else
data

View file

@ -6,30 +6,55 @@ defmodule Nulform.Connection do
Connection automatically handles using an altnick (but only one) and
responding to pings.
"""
defrecord Data,
handler: nil, # Pid
buffer: nil, # Pid
host: "" :: binary,
port: 0 :: number,
nick: "" :: binary,
altnick: "" :: binary,
username: "" :: binary,
realname: "" :: binary,
sock: nil
def run() do
run nil, nil, nil, nil, nil, nil, nil, nil
def init([handler, buffer, host, port, nick, altnick, username, realname]) do
data = Data.new handler: handler, buffer: buffer, host: host,
port: port, nick: nick, altnick: altnick,
username: username, realname: realname
{:ok, data}
end
def run(sock, handler, host, port, nick, altnick, username, realname) do
receive do
{:nulform, :connect, host, port, nick, altnick, username, realname} ->
{:ok, sock} = connect host, port
def handle_cast(:connect, data) do
{:ok, sock} = connect binary_to_list(data.host), data.port
data = data.sock sock
# TODO: Do we need to delay here on some networks?
send sock, "NICK " <> nick
send sock, "USER " <> username <> " 8 * :" <> realname
send_connect_info data
{:noreply, data}
end
{:nulform, :set_parent, handler} ->
nil
def handle_cast({:connect, sock}, data) do
data = data.sock sock
send_connect_info data
{:nulform, :msg_out, msg} ->
send sock, msg
{:noreply, data}
end
{:tcp, sock, msg} ->
def handle_cast(msg, data) when is_binary msg do
send_raw data.sock, msg
{:noreply, data}
end
def handle_info(msg, data) do
case msg do
{:nulform, :connection, :reset_buffer, pid} ->
data = data.buffer pid
{:tcp, _, msg} ->
stripped = String.rstrip msg
IO.puts stripped
tell_handler handler, stripped
tell_handler data.handler, stripped
# Handle PING responses
case String.split stripped, ":" do
@ -43,7 +68,7 @@ defmodule Nulform.Connection do
last
end
send sock, "PONG :" <> concat_list.(concat_list, rest, ":")
send data.buffer, "PONG :" <> concat_list.(concat_list, rest, ":")
_ ->
end
@ -54,16 +79,14 @@ defmodule Nulform.Connection do
# TODO: New altnick is predictable, no seeding
case String.split stripped do
[_, "433" | _] ->
send sock, "NICK " <> altnick
send data.buffer, "NICK " <> data.altnick
uniqid = to_binary :random.uniform(9999)
altnick = String.slice(altnick, 0, 10) <> "-" <> uniqid
data = data.altnick String.slice(data.altnick, 0, 10) <> "-" <> uniqid
_ ->
end
tuple when is_tuple(tuple) and elem(tuple, 0) == :nulform -> Nulform.Utils.hn tuple
end
run sock, handler, host, port, nick, altnick, username, realname
{:noreply, data}
end
defp connect(host, port) do
@ -76,7 +99,18 @@ defmodule Nulform.Connection do
:gen_tcp.close sock
end
defp send(sock, msg) do
defp send_connect_info(data) do
# TODO: Do we need to delay here on some networks?
send data.buffer, "NICK " <> data.nick
send data.buffer, "USER " <> data.username <> " 8 * :" <> data.realname
end
# Send message through buffer to avoid flooding
defp send(buffer, msg) do
:gen_server.cast buffer, msg
end
defp send_raw(sock, msg) do
:ok = :gen_tcp.send sock, msg <> "\r\n"
IO.puts("<- " <> msg)
end
@ -85,7 +119,7 @@ defmodule Nulform.Connection do
handler <- {:nulform, :msg_in, msg}
end
defp tell_handler(_, msg) do
defp tell_handler(_, _) do
nil
end
end