diff options
Diffstat (limited to 'lib/irc/connection_socket.ex')
-rw-r--r-- | lib/irc/connection_socket.ex | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/lib/irc/connection_socket.ex b/lib/irc/connection_socket.ex new file mode 100644 index 0000000..29c42c0 --- /dev/null +++ b/lib/irc/connection_socket.ex @@ -0,0 +1,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 |