diff --git a/lib/irc/puppet_connection.ex b/lib/irc/puppet_connection.ex index 8c9b218..59bce1b 100644 --- a/lib/irc/puppet_connection.ex +++ b/lib/irc/puppet_connection.ex @@ -1,179 +1,179 @@ defmodule IRC.PuppetConnection do require Logger @min_backoff :timer.seconds(5) @max_backoff :timer.seconds(2*60) - @max_idle :timer.minutes(30) + @max_idle :timer.hours(12) defmodule Supervisor do use DynamicSupervisor def start_link() do DynamicSupervisor.start_link(__MODULE__, [], name: __MODULE__) end def start_child(%IRC.Account{id: account_id}, %IRC.Connection{id: connection_id}) do spec = %{id: {account_id, connection_id}, start: {IRC.PuppetConnection, :start_link, [account_id, connection_id]}, restart: :transient} DynamicSupervisor.start_child(__MODULE__, spec) end @impl true def init(_init_arg) do DynamicSupervisor.init( strategy: :one_for_one, max_restarts: 10, max_seconds: 1 ) end end def whereis(account = %IRC.Account{id: account_id}, connection = %IRC.Connection{id: connection_id}) do {:global, name} = name(account_id, connection_id) case :global.whereis_name(name) do :undefined -> nil pid -> pid end end def send_message(account = %IRC.Account{id: account_id}, connection = %IRC.Connection{id: connection_id}, channel, text) do GenServer.cast(name(account_id, connection_id), {:send_message, channel, text}) end def start_and_send_message(account = %IRC.Account{id: account_id}, connection = %IRC.Connection{id: connection_id}, channel, text) do {:global, name} = name(account_id, connection_id) pid = whereis(account, connection) pid = if !pid do case IRC.PuppetConnection.Supervisor.start_child(account, connection) do {:ok, pid} -> pid {:error, {:already_started, pid}} -> pid end else pid end GenServer.cast(pid, {:send_message, channel, text}) end def start(account = %IRC.Account{}, connection = %IRC.Connection{}) do IRC.PuppetConnection.Supervisor.start_child(account, connection) end def start_link(account_id, connection_id) do GenServer.start_link(__MODULE__, [account_id, connection_id], name: name(account_id, connection_id)) end def name(account_id, connection_id) do {:global, {PuppetConnection, account_id, connection_id}} end def init([account_id, connection_id]) do account = %IRC.Account{} = IRC.Account.get(account_id) connection = %IRC.Connection{} = IRC.Connection.lookup(connection_id) Logger.metadata(puppet_conn: account.id <> "@" <> connection.id) backoff = :backoff.init(@min_backoff, @max_backoff) |> :backoff.type(:jitter) idle = :erlang.send_after(@max_idle, self, :idle) {:ok, %{client: nil, backoff: backoff, idle: idle, connected: false, buffer: [], channels: [], connection_id: connection_id, account_id: account_id, connected_server: nil, connected_port: nil, network: connection.network}, {:continue, :connect}} end def handle_continue(:connect, state) do conn = IRC.Connection.lookup(state.connection_id) client_opts = [] |> Keyword.put(:network, conn.network) client = if state.client && Process.alive?(state.client) do Logger.info("Reconnecting client") state.client else Logger.info("Connecting") {:ok, client} = ExIRC.Client.start_link(debug: false) ExIRC.Client.add_handler(client, self()) client end if conn.tls do ExIRC.Client.connect_ssl!(client, conn.host, conn.port, [])#[{:ifaddr, {45,150,150,33}}]) else ExIRC.Client.connect!(client, conn.host, conn.port, [])#[{:ifaddr, {45,150,150,33}}]) end {:noreply, %{state | client: client}} end def handle_continue(:connected, state) do state = Enum.reduce(Enum.reverse(state.buffer), state, fn(b, state) -> {:noreply, state} = handle_cast(b, state) state end) {:noreply, %{state | buffer: []}} end def handle_cast(cast = {:send_message, channel, text}, state = %{connected: false, buffer: buffer}) do {:noreply, %{state | buffer: [cast | buffer]}} end def handle_cast({:send_message, channel, text}, state = %{connected: true}) do channels = if !Enum.member?(state.channels, channel) do ExIRC.Client.join(state.client, channel) [channel | state.channels] else state.channels end ExIRC.Client.msg(state.client, :privmsg, channel, text) idle = if length(state.buffer) == 0 do :erlang.cancel_timer(state.idle) :erlang.send_after(@max_idle, self(), :idle) else state.idle end {:noreply, %{state | idle: idle, channels: channels}} end def handle_info(:idle, state) do ExIRC.Client.quit(state.client, "Puppet was idle for too long") ExIRC.Client.stop!(state.client) {:stop, :normal, state} end def handle_info(:disconnected, state) do {delay, backoff} = :backoff.fail(state.backoff) Logger.info("#{inspect(self())} Disconnected -- reconnecting in #{inspect delay}ms") Process.send_after(self(), :connect, delay) {:noreply, %{state | connected: false, backoff: backoff}} end def handle_info(:connect, state) do {:noreply, state, {:continue, :connect}} end # Connection successful def handle_info({:connected, server, port}, state) do Logger.info("#{inspect(self())} Connected to #{server}:#{port} #{inspect state}") {_, backoff} = :backoff.succeed(state.backoff) account = IRC.Account.get(state.account_id) user = IRC.UserTrack.find_by_account(state.network, account) base_nick = if(user, do: user.nick, else: account.name) nick = "#{base_nick}[p]" ExIRC.Client.logon(state.client, "", nick, base_nick, "#{base_nick}'s puppet") {:noreply, %{state | backoff: backoff, connected_server: server, connected_port: port}} end # Logon successful def handle_info(:logged_in, state) do Logger.info("#{inspect(self())} Logged in") {_, backoff} = :backoff.succeed(state.backoff) {:noreply, %{state | backoff: backoff}} end # ISUP def handle_info({:isup, network}, state) do {:noreply, %{state | network: network, connected: true}, {:continue, :connected}} end # Been kicked def handle_info({:kicked, _sender, chan, _reason}, state) do {:noreply, %{state | channels: state.channels -- [chan]}} end def handle_info(_info, state) do {:noreply, state} end end