1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
defmodule Irc.ConnectionSocket do
use GenServer
require Logger
alias Irc.Parser.Line
import Kernel, except: [send: 2]
@moduledoc "Underlying wrapper that just parses IRC lines"
@type error :: :todo
@type t :: {ConnectionSocket, pid(), reference()}
@spec connect(tls :: boolean, host :: String.t, port :: Integer.t, options :: Keyword.t) :: {:ok, t} | {:error, error}
def connect(tls, host, port, options) do
caller = self()
case GenServer.start(__MODULE__, [caller, tls, host, port, options]) do
{:ok, pid} ->
mon = Process.monitor(pid)
{:ok, {__MODULE__, pid, mon}}
error -> error
end
end
@spec close(t) :: :ok
def close({__MODULE__, pid, mon}) do
case GenServer.call(pid, :close) do
:ok ->
Process.demonitor(mon, [:flush])
error ->
error
end
end
@spec send(t, iolist) :: :ok
def send({__MODULE__, pid, _}, line) do
GenServer.call(pid, {:send, line})
end
@doc false
def init([caller, tls, host, port, options]) do
Process.monitor(caller)
module = if tls, do: :ssl, else: :gen_tcp
state = %{caller: caller, module: module, host: host, port: port, options: options, socket: nil}
case module.connect(String.to_charlist(host), port, options) do
{:ok, socket} ->
{:ok, Map.put(state, :socket, socket)}
error ->
{:error, error}
end
end
@doc false
def handle_call({:send, data}, {caller, _}, state = %{caller: caller, module: module, socket: socket}) do
{:reply, module.send(socket, data), state}
end
def handle_call(:close, {caller, _}, state = %{caller: caller, module: module, socket: socket}) do
{:stop, :normal, module.close(socket), state = %{state | module: nil, socket: nil}}
end
def handle_call(_, {caller, _}, state = %{caller: caller}) do
{:reply, {:error, :invalid_call}, state}
end
def handle_call(_, _, state) do
{:noreply, state}
end
@doc false
# Received a line from socket
def handle_info({module, socket, line}, state = %{caller: caller, module: module, socket: socket}) do
Kernel.send(caller, {__MODULE__, self(), Line.parse(line)})
{:noreply, state}
end
# Socket is closed
def handle_info({closed, socket}, state = %{socket: socket, caller: caller}) when closed in [:tcp_closed, :ssl_closed] do
Kernel.send(caller, {__MODULE__, self(), :closed})
{:stop, :normal, state}
end
# Socket error
# TODO: SSL errors
def handle_info({:tcp_error, socket, reason}, state = %{socket: socket, caller: caller}) do
Kernel.send(caller, {__MODULE__, self(), {:error, reason}})
{:stop, :normal, state}
end
# Caller is down
def handle_info({:DOWN, _, :process, caller, reason}, state = %{caller: caller, module: module, socket: socket}) do
module.close(socket)
{:stop, :normal, state}
end
def terminate(reason, state) do
state
end
end
|