diff options
Diffstat (limited to 'lib/app/chat.ex')
-rw-r--r-- | lib/app/chat.ex | 62 |
1 files changed, 57 insertions, 5 deletions
diff --git a/lib/app/chat.ex b/lib/app/chat.ex index f165514..e86f739 100644 --- a/lib/app/chat.ex +++ b/lib/app/chat.ex @@ -1,14 +1,37 @@ defmodule SApp.Chat do + @moduledoc """ + Shard application for a replicated chat room with full history. + + Chat rooms are globally identified by their channel name. + A chat room manifest is of the form: + + {:chat, channel_name} + + Future improvements: + - message signing + - storage of the chatroom messages to disk + - storage of the known peers that have this channel to disk + - use a DHT to find peers that are interested in this channel + - epidemic broadcast (carefull not to be too costly, + maybe by limiting the number of peers we talk to) + """ + use GenServer + @doc """ + Start a process that connects to a given channel + """ def start_link(channel) do GenServer.start_link(__MODULE__, channel) end + @doc """ + Initialize channel process. + """ def init(channel) do {:ok, store} = SData.MerkleList.start_link(&msg_cmp/2) manifest = {:chat, channel} - id = :crypto.hash(:sha256, :erlang.term_to_binary(manifest)) + id = SData.term_hash manifest GenServer.cast(Shard.Manager, {:register, id, self()}) GenServer.cast(self(), :init_pull) @@ -16,20 +39,37 @@ defmodule SApp.Chat do {:ok, %{channel: channel, id: id, manifest: manifest, store: store, peers: %{}}} end + @doc """ + Implementation of the :manifest call that returns the chat room's manifest + """ def handle_call(:manifest, _from, state) do {:reply, state.manifest, state} end + @doc """ + Implementation of the :redundant handler: if another process is already + synchronizing this channel then we exit. + """ def handle_cast({:redundant, _}, _state) do exit :normal end + @doc """ + Implementation of the :init_pull handler, which is called when the + process starts. It contacts all currently connected peers and asks them to + send data for this channel if they have some. + """ def handle_cast(:init_pull, state) do GenServer.call(SNet.Manager, :get_all) |> Enum.each(&(GenServer.cast(&1, {:send_msg, {:interested, [state.id]}}))) {:noreply, state} end + @doc """ + Implementation of the :chat_send handler. This is the main handler that is used + to send a message to the chat room. Puts the message in the store and syncs + with all connected peers. + """ def handle_cast({:chat_send, msg}, state) do msgitem = {(System.os_time :seconds), Shard.Identity.get_nickname(), @@ -43,15 +83,27 @@ defmodule SApp.Chat do {:noreply, state} end + @doc """ + Implementation of the :interested handler, this is called when a peer we are + connected to asks to recieve data for this channel. + """ def handle_cast({:interested, peer_id, peer_pid}, state) do push_messages(state, peer_pid, nil, 10) new_peers = Map.put(state.peers, peer_id, peer_pid) {:noreply, %{ state | peers: new_peers }} end + @doc """ + Implementation of the :msg handler, which is the main handler for messages + comming from other peers concerning this chat room. + + Messages are: + - `{:get, start}`: get some messages starting at a given Merkle hash + - `{:info, start, list, rest}`: put some messages and informs of the + Merkle hash of the store of older messages. + """ def handle_cast({:msg, peer_id, peer_pid, msg}, state) do case msg do - :get_top -> push_messages(peer_id, state, nil, 10) {:get, start} -> push_messages(peer_id, state, start, 20) {:info, _start, list, rest} -> if rest != nil and not GenServer.call(state.store, {:has, rest}) do @@ -70,6 +122,7 @@ defmodule SApp.Chat do end end + defp push_messages(state, to, start, num) do case GenServer.call(state.store, {:read, start, num}) do {:ok, list, rest} -> @@ -78,12 +131,11 @@ defmodule SApp.Chat do end end - - def msg_callback(chan, {ts, nick, msg}) do + defp msg_callback(chan, {ts, nick, msg}) do IO.puts "#{ts |> DateTime.from_unix! |> DateTime.to_iso8601} ##{chan} <#{nick}> #{msg}" end - def msg_cmp({ts1, nick1, msg1}, {ts2, nick2, msg2}) do + defp msg_cmp({ts1, nick1, msg1}, {ts2, nick2, msg2}) do SData.MerkleList.cmp_ts_str({ts1, nick1<>"|"<>msg1}, {ts2, nick2<>"|"<>msg2}) end |