defmodule Wideact do @moduledoc """ This is Wideact: an inter-lingual actor system. """ use Application @doc """ Starts the application with arguments being an array containing only the port number (default: 40444). """ def start(_type, args) do import Supervisor.Spec Process.register(spawn(StaleMail, :start, []), :stalemail) children = [ supervisor(Task.Supervisor, [[ name: Wideact.TaskSupervisor ]]), worker(Task, [ Wideact, :accept, args ]) ] opts = [ strategy: :one_for_one, name: Wideact.Supervisor ] Supervisor.start_link(children, opts) end @doc """ Opens a listener port and starts accepting connections. """ def accept(port) do { :ok, socket } = :gen_tcp.listen(port, [ :binary, packet: :line, active: false, reuseaddr: true, send_timeout: 40 ]) loop_acceptor(socket) end @doc """ Loops and accepts clients, setting up the consumer connections in the process. """ defp loop_acceptor(socket) do { :ok, client } = :gen_tcp.accept(socket) { :ok, pid } = Task.Supervisor.start_child(Wideact.TaskSupervisor, fn -> setup(client) end) :gen_tcp.controlling_process(client, pid) loop_acceptor(socket) end @doc """ A receiving loop, checking the stale mail for messages. """ defp recvloop(client, name) do receive do { :received_message, sender, message } -> case :gen_tcp.send(client, "MESSAGE #{sender} #{message}\n") do :ok -> send :stalemail, { :got_mail, name } { :error, _reason } -> exit(:normal) end _ -> exit(:normal) after 10 -> send :stalemail, { :new_mail, name, self() } end recvloop(client, name) end @doc """ Sets the connection up. If the first message isn't a [ CONNECT name ] message, stop the client, because of protocol breach. """ defp setup(socket) do case :gen_tcp.recv(socket, 0) do { :ok, data } -> case data |> String.split(" ", parts: 2) do [ "CONNECT", name ] -> send :stalemail, { :new_mailbox, name, self() } spawn(fn -> recvloop(socket, name) end) serve(socket, name) _ -> :gen_tcp.close(socket) exit(:normal) end _ -> :gen_tcp.close(socket) exit(:normal) end end @doc """ Serve the content to the socket under a given name. """ defp serve(socket, name) do case :gen_tcp.recv(socket, 0) do { :ok, data } -> case data |> String.split(" ", parts: 3) do [ "MESSAGE", receiver, message ] -> send :stalemail, { :pass_message, receiver, message, name } end { :error, _reason } -> nil end serve(socket, name) end end