aboutsummaryrefslogblamecommitdiff
path: root/shard/lib/cli/cli.ex
blob: 3b52fa51ebc52d6c2423f70f126f8a6d606f6b81 (plain) (tree)
1
2
3
4
5
6
7
8
9
                 



                                                       



                                       
              
                                                                             


                                                 
                                    
                                                   

     
                    
                          
 













                                                 

                                     









                                                                                              


                                          




                                                                 

                                              
              
                       
                                                                     
           
                  


       
                                
              
                                                    

                                           






                                                                                                                                                  

                           
                              



                   
                                                            

                                                       
                                            
         

     
                                         
                                     
 
                                                                                           

                        
         

     

                                         

                                           

                                                              



                                                                                                                     
       
         

     
                                                


                                                                         

     























                                                                                                  



                                                                      
        
           


       











                                                                

     
                                     
                             
         

     
defmodule SCLI do
  @moduledoc """
  Small command line interface for the chat application
  """

  defmodule State do
    defstruct [:room_pid, :id_pid, :pk]
  end

  def run() do
    for {_chid, %SApp.Chat.Manifest{}, chpid} <- Shard.Manager.list_shards do
      GenServer.cast(chpid, {:subscribe, self()})
    end

    pk = Shard.Keys.get_any_identity
    run(%State{room_pid: nil, id_pid: nil, pk: pk})
  end

  defp run(state) do
    handle_messages(state)

    id_pid = case state.id_pid do
      nil -> SApp.Identity.find_proc(state.pk)
      x -> x
    end
    state = put_in(state.id_pid, id_pid)

    nick = case id_pid do
      nil -> SApp.Identity.default_nick(state.pk)
      _ ->
        info = GenServer.call(id_pid, :get_info)
        info.nick
    end

    prompt = case state.room_pid do
      nil -> "(no channel) #{nick}: "
      _ -> 
        case GenServer.call(state.room_pid, :manifest) do
          %SApp.Chat.Manifest{channel: chan} ->
            "##{chan} #{nick}: "
          %SApp.Chat.PrivChat.Manifest{pk_list: pk_list} ->
            nicks = pk_list
                    |> Enum.filter(&(&1 != state.pk))
                    |> Enum.map(&("#{SApp.Identity.get_nick &1} #{Shard.Keys.pk_display &1}"))
                    |> Enum.join(", ")
            "PM #{nicks}  #{nick}: "
        end
    end

    str = prompt |> IO.gets |> String.trim
    cond do
      str == "/quit" ->
        nil
      String.slice(str, 0..0) == "/" ->
        command = str |> String.slice(1..-1) |> String.split(" ")
        state = handle_command(state, command)
        run(state)
      true -> 
        if str != "" do
          GenServer.cast(state.room_pid, {:chat_send, state.pk, str})
        end
        run(state)
    end
  end

  defp handle_messages(state) do
    receive do
      {:chat_recv, manifest, {pk, msgbin, _sign}} ->
        {ts, msg} = SData.term_unbin msgbin
        nick = SApp.Identity.get_nick pk
        case manifest do
          %SApp.Chat.Manifest{channel: chan} ->
            IO.puts "#{ts |> DateTime.from_unix! |> DateTime.to_iso8601}  ##{chan}  <#{nick} #{Shard.Keys.pk_display pk}> #{msg}"
          %SApp.Chat.PrivChat.Manifest{pk_list: pk_list} ->
            IO.puts "#{ts |> DateTime.from_unix! |> DateTime.to_iso8601}  PM(#{Enum.count pk_list})  <#{nick} #{Shard.Keys.pk_display pk}> #{msg}"
        end
        handle_messages(state)
      {:chat_send, _, _} ->
        # do nothing
        handle_messages(state)
    after 10 -> nil
    end
  end

  defp handle_command(state, ["connect", ipstr, portstr]) do
    {:ok, ip} = :inet.parse_address (to_charlist ipstr)
    {port, _} = Integer.parse portstr
    SNet.Manager.add_peer({:inet, ip, port})
    state
  end

  defp handle_command(state, ["list"]) do
    IO.puts "List of known channels:"

    for {_chid, %SApp.Chat.Manifest{channel: chan}, _chpid} <- Shard.Manager.list_shards do
      IO.puts "##{chan}"
    end
    state
  end

  defp handle_command(state, ["hist"]) do
    if state.room_pid == nil do
      IO.puts "Not currently on a channel!"
    else
      GenServer.call(state.room_pid, {:read_history, nil, 25})
      |> Enum.each(fn {{pk, msgbin, _sign}, true} ->
          {ts, msg} = SData.term_unbin msgbin
          nick = SApp.Identity.get_nick pk
          IO.puts "#{ts |> DateTime.from_unix! |> DateTime.to_iso8601}  <#{nick} #{Shard.Keys.pk_display pk}> #{msg}"
        end)
    end
    state
  end

  defp handle_command(state, ["join", qchan]) do
    pid = Shard.Manager.find_or_start %SApp.Chat.Manifest{channel: qchan}
    IO.puts "Switching to ##{qchan}"
    %{state | room_pid: pid}
  end

  defp handle_command(state, ["pm" | people_list]) do
    known_people = for {_, %SApp.Identity.Manifest{pk: pk}, pid} <- Shard.Manager.list_shards() do
      info = GenServer.call(pid, :get_info)
      {pk, info.nick}
    end
    pk_list = for qname <- people_list do
      candidates = for {pk, nick} <- known_people,
        :binary.longest_common_prefix([qname, nick]) == byte_size(qname)
          or :binary.longest_common_prefix([qname, Shard.Keys.pk_display pk]) == byte_size(qname),
        do: {pk, nick}
      case candidates do
        [] ->
          IO.puts "Not found: #{qname}"
          :error
        [{pk, _}] -> pk
        _ ->
          IO.puts "Several people matching for #{qname}:"
          for {pk, nick} <- candidates do
            IO.puts "- #{nick} #{Shard.Keys.pk_display pk}"
          end
          :error
      end
    end
    if Enum.all?(pk_list, &(&1 != :error)) do
      manifest = SApp.Chat.PrivChat.Manifest.new([state.pk | pk_list])
      pid = Shard.Manager.find_or_start manifest
      IO.puts "Switching to private conversation."
      %{state | room_pid: pid}
    else
      state
    end
  end

  defp handle_command(state, ["nick", nick]) do
    pid = case state.id_pid do
      nil -> SApp.Identity.find_proc state.pk
      x -> x
    end
    if pid == nil do
      IO.puts "Sorry, we have a problem with the identity shard"
    else
      info = GenServer.call(pid, :get_info)
      GenServer.call(pid, {:set_info, %{info | nick: nick}})
    end
    state
  end

  defp handle_command(state, _cmd) do
    IO.puts "Invalid command"
    state
  end
end