summaryrefslogtreecommitdiff
path: root/lib/irc/connection_socket.ex
blob: 29c42c0ac7bdf4beef3e81967c89b570531107b8 (plain) (blame)
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