diff --git a/lib/app.ex b/lib/app.ex new file mode 100644 index 0000000..a02b70a --- /dev/null +++ b/lib/app.ex @@ -0,0 +1,10 @@ +defmodule ExIrc.App do + @moduledoc """ + Entry point for the ExIrc application. + """ + use Application.Behaviour + + def start(_type, _args) do + ExIrc.start! + end +end \ No newline at end of file diff --git a/lib/exirc.ex b/lib/exirc.ex deleted file mode 100644 index 42a291b..0000000 --- a/lib/exirc.ex +++ /dev/null @@ -1,2 +0,0 @@ -defmodule ExIrc do -end diff --git a/lib/exirc/.gitignore b/lib/exirc/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/lib/exirc/exirc.ex b/lib/exirc/exirc.ex new file mode 100644 index 0000000..a94c942 --- /dev/null +++ b/lib/exirc/exirc.ex @@ -0,0 +1,43 @@ +defmodule ExIrc do + @moduledoc """ + Supervises IRC client processes + + Usage: + # Start the supervisor (started automatically when ExIrc is run as an application) + ExIrc.start_link + # Start a new IRC client + {:ok, client} = ExIrc.start_client! + + """ + use Supervisor.Behaviour + + ############## + # Public API + ############## + + @doc """ + Start the ExIrc supervisor. + """ + @spec start! :: {:ok, pid} | {:error, term} + def start! do + :supervisor.start_link({:local, :ircsuper}, __MODULE__, []) + end + + @doc """ + Start a new ExIrc client + """ + @spec start_client! :: {:ok, pid} | {:error, term} + def start_client! do + # Start the client worker + :supervisor.start_child(:ircsuper, worker(ExIrc.Client, [])) + end + + ############## + # Supervisor API + ############## + + def init(_) do + supervise [], strategy: :one_for_one + end + +end diff --git a/lib/exirc/exirc_client.ex b/lib/exirc/exirc_client.ex new file mode 100644 index 0000000..0612602 --- /dev/null +++ b/lib/exirc/exirc_client.ex @@ -0,0 +1,94 @@ +defmodule ExIrc.Client do + use GenServer.Behaviour + + # Maintains client state + defrecord ClientState, events: nil, socket: nil + # Defines the connection to an IRC server + defrecord IrcConnection, host: 'localhost', port: 6667, password: '' + + alias IO.ANSI, as: ANSI + + ##################### + # Public API + ##################### + + @doc """ + Add a new event handler (i.e bot) to a client + """ + def add_handler(client, handler, args // []) do + :gen_server.cast(client, {:add_handler, handler, args}) + end + + @doc """ + Connect a client to the provided IRC server + """ + def connect!(client, connection) do + :gen_server.cast client, {:connect, connection} + end + + @doc """ + Disconnect a client + """ + def disconnect!(client) do + :gen_server.cast client, :disconnect + end + + @doc """ + Send an event to a client's event handlers + """ + def notify!(pid, event) do + :gen_server.cast pid, {:notify, event} + end + + ##################### + # GenServer API + ##################### + + def start_link() do + :gen_server.start_link(__MODULE__, nil, []) + end + + def init(_) do + # Start the event handler + {:ok, events} = :gen_event.start_link() + {:ok, ClientState.new([events: events])} + end + + @doc """ + Handles connecting the client to the provided IRC server + """ + def handle_cast({:connect, connection}, state) do + {:noreply, state} + end + + @doc """ + Handles adding a new event handler (i.e bot) to the client + """ + def handle_cast({:add_handler, handler, args}, state) do + :gen_event.add_sup_handler(state.events, handler, args) + {:noreply, state} + end + + @doc """ + Handles event notifications + """ + def handle_cast({:notify, event}, state) do + :gen_event.notify(state.events, event) + {:noreply, state} + end + + @doc """ + Handles event handler termination. Specifically, it restarts handlers which have crashed. + """ + def handle_info({:gen_event_EXIT, handler, reason}, state) do + case reason do + :normal -> {:noreply, state.events} + :shutdown -> {:noreply, state.events} + {:swapped, _, _} -> {:noreply, state.events} + _ -> + :gen_server.cast(self, {:add_handler, handler, []}) + IO.puts(ANSI.magenta() <> "Handler #{atom_to_binary(handler)} crashed. Restarting..." <> ANSI.reset()) + {:noreply, state} + end + end +end \ No newline at end of file diff --git a/lib/exirc/exirc_example_handler.ex b/lib/exirc/exirc_example_handler.ex new file mode 100644 index 0000000..52e6b5b --- /dev/null +++ b/lib/exirc/exirc_example_handler.ex @@ -0,0 +1,21 @@ +defmodule ExIrc.ExampleHandler do + use GenEvent.Behaviour + + ################ + # GenEvent API + ################ + + def init(args) do + {:ok, args} + end + + def handle_event(:connected, state) do + IO.puts "Received event :connected" + {:ok, state} + end + def handle_event(:login, state) do + IO.puts "Received event :login" + {:ok, state} + end + +end diff --git a/mix.exs b/mix.exs index 830a13a..88f71c3 100644 --- a/mix.exs +++ b/mix.exs @@ -1,24 +1,24 @@ defmodule ExIrc.Mixfile do use Mix.Project def project do [ app: :exirc, version: "0.0.1", elixir: "~> 0.11.2", deps: deps ] end # Configuration for the OTP application def application do - [] + [mod: {ExIrc.App, []}] end # Returns the list of dependencies in the format: # { :foobar, git: "https://github.com/elixir-lang/foobar.git", tag: "0.1" } # # To specify particular versions, regardless of the tag, do: # { :barbat, "~> 0.1", github: "elixir-lang/barbat.git" } defp deps do [] end end