Fix race conditions in logger translator tests

Remove use of :timer.sleep/1 and monitor parent instead

defmodule Logger.TranslatorTest do
  use Logger.Case
  import Supervisor.Spec

  defmodule MyGenServer do
    use GenServer

    def handle_call(:error, _, _) do
      raise "oops"
    end
  end

  defmodule MyGenEvent do
    use GenEvent

    def handle_call(:error, _) do
      raise "oops"
    end
  end

  defmodule MyBridge do
    @behaviour :supervisor_bridge

    def init(reason) do
      {:ok, pid} = Task.start_link(Kernel, :exit, [reason])
      {:ok, pid, pid}
    end

    def terminate(_reason, pid) do
      Process.exit(pid, :shutdown)
    end
  end

  setup_all do
    sasl_reports? = Application.get_env(:logger, :handle_sasl_reports, false)
    Application.put_env(:logger, :handle_sasl_reports, true)

    # Restart the app but change the level before to avoid warnings
    level = Logger.level()
    Logger.configure(level: :error)
    Logger.App.stop()
    Application.start(:logger)
    Logger.configure(level: level)

    on_exit(fn() ->
      Application.put_env(:logger, :handle_sasl_reports, sasl_reports?)
      Logger.App.stop()
      Application.start(:logger)
    end)
  end

  test "translates GenServer crashes" do
    {:ok, pid} = GenServer.start(MyGenServer, :ok)

    assert capture_log(:info, fn ->
      catch_exit(GenServer.call(pid, :error))
    end) =~ """
    [error] GenServer #{inspect pid} terminating
    ** (RuntimeError) oops
    """
  end

  test "translates GenServer crashes with custom inspect options" do
    {:ok, pid} = GenServer.start(MyGenServer, List.duplicate(:ok, 1000))
    Application.put_env(:logger, :translator_inspect_opts, [limit: 3])

    assert capture_log(:debug, fn ->
      catch_exit(GenServer.call(pid, :error))
    end) =~ """
    [:ok, :ok, :ok, ...]
    """
  after
    Application.put_env(:logger, :translator_inspect_opts, [])
  end

  test "translates GenServer crashes on debug" do
    {:ok, pid} = GenServer.start(MyGenServer, :ok)

    assert capture_log(:debug, fn ->
      catch_exit(GenServer.call(pid, :error))
    end) =~ ~r"""
    \[error\] GenServer #PID<\d+\.\d+\.\d+> terminating
    \*\* \(RuntimeError\) oops
    .*
    Last message: :error
    State: :ok
    """s
  end

  test "translates GenEvent crashes" do
    {:ok, pid} = GenEvent.start()
    :ok = GenEvent.add_handler(pid, MyGenEvent, :ok)

    assert capture_log(:info, fn ->
      GenEvent.call(pid, MyGenEvent, :error)
    end) =~ """
    [error] GenEvent handler Logger.TranslatorTest.MyGenEvent installed in #{inspect pid} terminating
    ** (RuntimeError) oops
    """
  end

  test "translates GenEvent crashes on debug" do
    {:ok, pid} = GenEvent.start()
    :ok = GenEvent.add_handler(pid, MyGenEvent, :ok)

    assert capture_log(:debug, fn ->
      GenEvent.call(pid, MyGenEvent, :error)
    end) =~ ~r"""
    \[error\] GenEvent handler Logger.TranslatorTest.MyGenEvent installed in #PID<\d+\.\d+\.\d+> terminating
    \*\* \(RuntimeError\) oops
    .*
    Last message: :error
    State: :ok
    """s
  end

  test "translates Task crashes" do
    {:ok, pid} = Task.start_link(__MODULE__, :task, [self()])

    assert capture_log(fn ->
      ref = Process.monitor(pid)
      send(pid, :go)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Task #PID<\d+\.\d+\.\d+> started from #PID<\d+\.\d+\.\d+> terminating
    \*\* \(RuntimeError\) oops
    .*
    Function: &Logger.TranslatorTest.task\/1
        Args: \[#PID<\d+\.\d+\.\d+>\]
    """s
  end

  test "translates Task undef module crash" do
    assert capture_log(fn ->
      {:ok, pid} = Task.start(:module_does_not_exist, :undef, [])
      ref = Process.monitor(pid)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Task #PID<\d+\.\d+\.\d+> started from #PID<\d+\.\d+\.\d+> terminating
    \*\* \(UndefinedFunctionError\) function :module_does_not_exist.undef/0 is undefined \(module :module_does_not_exist is not available\)
    .*
    Function: &:module_does_not_exist.undef/0
        Args: \[\]
    """s
  end

  test "translates Task undef function crash" do
    assert capture_log(fn ->
      {:ok, pid} = Task.start(__MODULE__, :undef, [])
      ref = Process.monitor(pid)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Task #PID<\d+\.\d+\.\d+> started from #PID<\d+\.\d+\.\d+> terminating
    \*\* \(UndefinedFunctionError\) function Logger.TranslatorTest.undef/0 is undefined or private
    .*
    Function: &Logger.TranslatorTest.undef/0
        Args: \[\]
    """s
  end

  test "translates Task raising ErlangError" do
    assert capture_log(fn ->
      exception = try do
          :erlang.error(:foo)
        rescue
         x ->
           x
        end
      {:ok, pid} = Task.start(:erlang, :error, [exception])
      ref = Process.monitor(pid)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Task #PID<\d+\.\d+\.\d+> started from #PID<\d+\.\d+\.\d+> terminating
    \*\* \(ErlangError\) erlang error: :foo
    .*
    Function: &:erlang\.error/1
        Args: \[%ErlangError{.*}\]
    """s
  end

  test "translates Task raising Erlang badarg error" do
    assert capture_log(fn ->
      {:ok, pid} = Task.start(:erlang, :error, [:badarg])
      ref = Process.monitor(pid)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Task #PID<\d+\.\d+\.\d+> started from #PID<\d+\.\d+\.\d+> terminating
    \*\* \(ArgumentError\) argument error
    .*
    Function: &:erlang\.error/1
        Args: \[:badarg\]
    """s
  end

  test "translates Task exiting abnormally" do
    assert capture_log(fn ->
      {:ok, pid} = Task.start(:erlang, :exit, [:abnormal])
      ref = Process.monitor(pid)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Task #PID<\d+\.\d+\.\d+> started from #PID<\d+\.\d+\.\d+> terminating
    \*\* \(stop\) :abnormal
    .*
    Function: &:erlang\.exit/1
        Args: \[:abnormal\]
    """s
  end

  test "translates application stop" do
    assert capture_log(fn ->
    :ok = Application.start(:eex)
      Application.stop(:eex)
    end) =~ """
    Application eex exited: :stopped
    """
  end

  test "translates application start" do
    assert capture_log(fn ->
      Application.start(:eex)
      Application.stop(:eex)
    end) =~ """
    Application eex started at #{inspect(node())}
    """
  end

  test "translates :proc_lib crashes" do
    {:ok, pid} = Task.start_link(__MODULE__, :task, [self()])

    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      send(pid, :message)
      send(pid, :go)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Process #PID<\d+\.\d+\.\d+> terminating
    \*\* \(exit\) an exception was raised:
        \*\* \(RuntimeError\) oops
    .*
    Initial Call: Logger.TranslatorTest.task/1
    Ancestors: \[#PID<\d+\.\d+\.\d+>\]
    """s
  end

  test "translates Process crashes" do
    assert capture_log(:info, fn ->
      {_, ref} = spawn_monitor(fn() -> raise "oops" end)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
      # Even though the monitor has been received the emulator may not have
      # sent the message to the error logger
      :timer.sleep(200)
    end) =~ ~r"""
    \[error\] Process #PID<\d+\.\d+\.\d+>\ raised an exception
    \*\* \(RuntimeError\) oops
    """
  end

  test "translates :proc_lib crashes with name" do
    {:ok, pid} = Task.start_link(__MODULE__, :task,
                                 [self(), fn() ->
                                            Process.register(self(), __MODULE__)
                                            raise "oops"
                                          end])

    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      send(pid, :message)
      send(pid, :go)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Process Logger.TranslatorTest \(#PID<\d+\.\d+\.\d+>\) terminating
    \*\* \(exit\) an exception was raised:
        \*\* \(RuntimeError\) oops
    .*
    Initial Call: Logger.TranslatorTest.task/2
    Ancestors: \[#PID<\d+\.\d+\.\d+>\]
    """s
  end

  test "translates :proc_lib crashes without initial call" do
    {:ok, pid} = Task.start_link(__MODULE__, :task,
                                 [self(), fn() ->
                                            Process.delete(:"$initial_call")
                                            raise "oops"
                                          end])

    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      send(pid, :message)
      send(pid, :go)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Process #PID<\d+\.\d+\.\d+> terminating
    \*\* \(exit\) an exception was raised:
        \*\* \(RuntimeError\) oops
    .*
    Ancestors: \[#PID<\d+\.\d+\.\d+>\]
    """s
  end

  test "translates :proc_lib crashes with neighbour" do
    {:ok, pid} = Task.start_link(__MODULE__, :sub_task, [self()])

    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      send(pid, :message)
      send(pid, :go)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    Ancestors: \[#PID<\d+\.\d+\.\d+>\]
    Neighbours:
        #PID<\d+\.\d+\.\d+>
            Initial Call: Logger.TranslatorTest.sleep/1
            Current Call: Logger.TranslatorTest.sleep/1
            Ancestors: \[#PID<\d+\.\d+\.\d+>, #PID<\d+\.\d+\.\d+>\]
    """
  end

  test "translates :proc_lib crashes with neighbour with name" do
    {:ok, pid} = Task.start_link(__MODULE__, :sub_task,
                                 [self(), fn(pid2) ->
                                            Process.register(pid2, __MODULE__)
                                            raise "oops"
                                          end])

    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      send(pid, :message)
      send(pid, :go)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    Ancestors: \[#PID<\d+\.\d+\.\d+>\]
    Neighbours:
        Logger.TranslatorTest \(#PID<\d+\.\d+\.\d+>\)
            Initial Call: Logger.TranslatorTest.sleep/1
            Current Call: Logger.TranslatorTest.sleep/1
            Ancestors: \[#PID<\d+\.\d+\.\d+>, #PID<\d+\.\d+\.\d+>\]
    """
  end

  test "translates :proc_lib crashes on debug" do
    {:ok, pid} = Task.start_link(__MODULE__, :task, [self()])

    assert capture_log(:debug, fn ->
      ref = Process.monitor(pid)
      send(pid, :message)
      send(pid, :go)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    Ancestors: \[#PID<\d+\.\d+\.\d+>\]
    Messages: \[:message\]
    Links: \[\]
    Dictionary: \[\]
    Trapping Exits: false
    Status: :running
    Heap Size: \d+
    Stack Size: \d+
    Reductions: \d+
    """
  end

  test "translates :proc_lib crashes with neighbour on debug" do
    {:ok, pid} = Task.start_link(__MODULE__, :sub_task, [self()])

    assert capture_log(:debug, fn ->
      ref = Process.monitor(pid)
      send(pid, :message)
      send(pid, :go)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
            Ancestors: \[#PID<\d+\.\d+\.\d+>, #PID<\d+\.\d+\.\d+>\]
            Messages: \[\]
            Links: \[#PID<\d+\.\d+\.\d+>\]
            Dictionary: \[\]
            Trapping Exits: false
            Status: :waiting
            Heap Size: \d+
            Stack Size: \d+
            Reductions: \d+
    """
  end

  test "translates Supervisor progress" do
    {:ok, pid} = Supervisor.start_link([], [strategy: :one_for_one])
    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      Supervisor.start_child(pid, worker(Task, [__MODULE__, :sleep, [self()]]))
      Process.exit(pid, :normal)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[info\]  Child Task of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) started
    Pid: #PID<\d+\.\d+\.\d+>
    Start Call: Task.start_link\(Logger.TranslatorTest, :sleep, \[#PID<\d+\.\d+\.\d+>\]\)
    """
  end

  test "translates Supervisor progress with name" do
    {:ok, pid} = Supervisor.start_link([],
      [strategy: :one_for_one, name: __MODULE__])
    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      Supervisor.start_child(pid, worker(Task, [__MODULE__, :sleep, [self()]]))
      Process.exit(pid, :normal)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[info\]  Child Task of Supervisor Logger.TranslatorTest started
    """

    {:ok, pid} = Supervisor.start_link([],
      [strategy: :one_for_one, name: {:global, __MODULE__}])
    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      Supervisor.start_child(pid, worker(Task, [__MODULE__, :sleep, [self()]]))
      Process.exit(pid, :normal)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[info\]  Child Task of Supervisor Logger.TranslatorTest started
    """

    {:ok, pid} = Supervisor.start_link([],
      [strategy: :one_for_one, name: {:via, :global, __MODULE__}])
    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      Supervisor.start_child(pid, worker(Task, [__MODULE__, :sleep, [self()]]))
      Process.exit(pid, :normal)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[info\]  Child Task of Supervisor Logger.TranslatorTest started
    """
  end

  test "translates Supervisor progress on debug" do
    {:ok, pid} = Supervisor.start_link([], [strategy: :one_for_one])
    assert capture_log(:debug, fn ->
      ref = Process.monitor(pid)
      Supervisor.start_child(pid, worker(Task, [__MODULE__, :sleep, [self()]]))
      Process.exit(pid, :normal)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    Start Call: Task.start_link\(Logger.TranslatorTest, :sleep, \[#PID<\d+\.\d+\.\d+>\]\)
    Restart: :permanent
    Shutdown: 5000
    Type: :worker
    """
  end

  test "translates Supervisor reports start error" do
    assert capture_log(:info, fn ->
      trap = Process.flag(:trap_exit, true)
      Supervisor.start_link([worker(__MODULE__, [], [function: :error])],
        [strategy: :one_for_one])
      receive do: ({:EXIT, _, {:shutdown, {:failed_to_start_child, _, _}}} -> :ok)
      Process.flag(:trap_exit, trap)
    end) =~ ~r"""
    \[error\] Child Logger.TranslatorTest of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) failed to start
    \*\* \(exit\) :stop
    Start Call: Logger.TranslatorTest.error\(\)
    """
  end

  test "translates Supervisor reports start error with raise" do
    assert capture_log(:info, fn ->
      trap = Process.flag(:trap_exit, true)
      Supervisor.start_link([worker(__MODULE__, [], [function: :undef])],
        [strategy: :one_for_one])
      receive do: ({:EXIT, _, {:shutdown, {:failed_to_start_child, _, _}}} -> :ok)
      Process.flag(:trap_exit, trap)
    end) =~ ~r"""
    \[error\] Child Logger.TranslatorTest of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) failed to start
    \*\* \(exit\) an exception was raised:
        \*\* \(UndefinedFunctionError\) function Logger.TranslatorTest.undef/0 is undefined or private
        .*
    Start Call: Logger.TranslatorTest.undef\(\)
    """s
  end

  test "translates Supervisor reports terminated" do
    assert capture_log(:info, fn ->
      trap = Process.flag(:trap_exit, true)
      {:ok, pid} = Supervisor.start_link([worker(Task, [Kernel, :exit, [:stop]])],
        [strategy: :one_for_one, max_restarts: 0])
      receive do: ({:EXIT, ^pid, _} -> :ok)
      Process.flag(:trap_exit, trap)
    end) =~ ~r"""
    \[error\] Child Task of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) terminated
    \*\* \(exit\) :stop
    Pid: #PID<\d+\.\d+\.\d+>
    Start Call: Task.start_link\(Kernel, :exit, \[:stop\]\)
    """
  end

  test "translates Supervisor reports max restarts shutdown" do
    assert capture_log(:info, fn ->
      trap = Process.flag(:trap_exit, true)
      {:ok, pid} = Supervisor.start_link([worker(Task, [Kernel, :exit, [:stop]])],
        [strategy: :one_for_one, max_restarts: 0])
      receive do: ({:EXIT, ^pid, _} -> :ok)
      Process.flag(:trap_exit, trap)
    end) =~ ~r"""
    \[error\] Child Task of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) caused shutdown
    \*\* \(exit\) :reached_max_restart_intensity
    Start Call: Task.start_link\(Kernel, :exit, \[:stop\]\)
    """
  end

  test "translates Supervisor reports abnormal shutdown" do
    assert capture_log(:info, fn ->
      {:ok, pid} = Supervisor.start_link([worker(__MODULE__, [], [function: :abnormal])],
        [strategy: :one_for_one])
      :ok = Supervisor.terminate_child(pid, __MODULE__)
    end) =~ ~r"""
    \[error\] Child Logger.TranslatorTest of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) shutdown abnormally
    \*\* \(exit\) :stop
    Pid: #PID<\d+\.\d+\.\d+>
    Start Call: Logger.TranslatorTest.abnormal\(\)
    """
  end

  test "translates Supervisor reports abnormal shutdown on debug" do
    assert capture_log(:debug, fn ->
      {:ok, pid} = Supervisor.start_link([worker(__MODULE__, [],
          [function: :abnormal, restart: :permanent, shutdown: 5000])],
        [strategy: :one_for_one])
      :ok = Supervisor.terminate_child(pid, __MODULE__)
    end) =~ ~r"""
    \*\* \(exit\) :stop
    Pid: #PID<\d+\.\d+\.\d+>
    Start Call: Logger.TranslatorTest.abnormal\(\)
    Restart: :permanent
    Shutdown: 5000
    Type: :worker
    """
  end

  test "translates Supervisor reports abnormal shutdown in simple_one_for_one" do
    assert capture_log(:info, fn ->
      trap = Process.flag(:trap_exit, true)
      {:ok, pid} = Supervisor.start_link([worker(__MODULE__, [], [function: :abnormal])],
        [strategy: :simple_one_for_one])
      {:ok, _pid2} = Supervisor.start_child(pid, [])
      Process.exit(pid, :normal)
      receive do: ({:EXIT, ^pid, _} -> :ok)
      Process.flag(:trap_exit, trap)
    end) =~ ~r"""
    \[error\] Children Logger.TranslatorTest of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) shutdown abnormally
    \*\* \(exit\) :stop
    Number: 1
    Start Call: Logger.TranslatorTest.abnormal\(\)
    """
  end

  test "translates :supervisor_bridge progress" do
    assert capture_log(:info, fn ->
      trap = Process.flag(:trap_exit, true)
      {:ok, pid} = :supervisor_bridge.start_link(MyBridge, :normal)
      receive do: ({:EXIT, ^pid, _} -> :ok)
      Process.flag(:trap_exit, trap)
    end) =~ ~r"""
    \[info\]  Child of Supervisor #PID<\d+\.\d+\.\d+> \(Logger\.TranslatorTest\.MyBridge\) started
    Pid: #PID<\d+\.\d+\.\d+>
    Start Call: Logger.TranslatorTest.MyBridge.init\(:normal\)
    """
  end

  test "translates :supervisor_bridge reports" do
    assert capture_log(:info, fn ->
      trap = Process.flag(:trap_exit, true)
      {:ok, pid} = :supervisor_bridge.start_link(MyBridge, :stop)
      receive do: ({:EXIT, ^pid, _} -> :ok)
      Process.flag(:trap_exit, trap)
    end) =~ ~r"""
    \[error\] Child of Supervisor #PID<\d+\.\d+\.\d+> \(Logger\.TranslatorTest\.MyBridge\) terminated
    \*\* \(exit\) :stop
    Pid: #PID<\d+\.\d+\.\d+>
    Start Module: Logger.TranslatorTest.MyBridge
    """
  end

  test "handles :undefined MFA properly" do
    children = [Supervisor.Spec.worker(GenServer, [], restart: :temporary)]
    opts = [strategy: :simple_one_for_one]
    {:ok, sup} = Supervisor.start_link(children, opts)
    assert capture_log(:info, fn ->
      {:ok, pid} = Supervisor.start_child(sup, [MyGenServer, []])
      catch_exit(GenServer.call(pid, :error))
      [] = Supervisor.which_children(sup)
    end) =~ "Start Call: GenServer.start_link/?"
  end

  def task(parent, fun \\ (fn() -> raise "oops" end)) do
    mon = Process.monitor(parent)
    Process.unlink(parent)
    receive do
      :go ->
        fun.()
      {:DOWN, ^mon, _, _, _} ->
        exit(:shutdown)
    end
  end

  def sub_task(parent, fun \\ (fn(_) -> raise "oops" end)) do
    mon = Process.monitor(parent)
    Process.unlink(parent)
    {:ok, pid} = Task.start_link(__MODULE__, :sleep, [self()])
    receive do: (:sleeping -> :ok)
    receive do
      :go ->
        fun.(pid)
      {:DOWN, ^mon, _, _, _} ->
        exit(:shutdown)
    end
  end

  def sleep(pid) do
    mon = Process.monitor(pid)
    send(pid, :sleeping)
    receive do: ({:DOWN, ^mon, _, _, _} -> exit(:shutdown))
  end

  def error(), do: {:error, :stop}

  def abnormal() do
    :proc_lib.start_link(__MODULE__, :abnormal_init, [])
  end

  def abnormal_init() do
    Process.flag(:trap_exit, true)
    :proc_lib.init_ack({:ok, self()})
    receive do: ({:EXIT, _, _} -> exit(:stop))
  end

end

defmodule Logger.TranslatorTest do
  use Logger.Case
  import Supervisor.Spec

  defmodule MyGenServer do
    use GenServer

    def handle_call(:error, _, _) do
      raise "oops"
    end
  end

  defmodule MyGenEvent do
    use GenEvent

    def handle_call(:error, _) do
      raise "oops"
    end
  end

  defmodule MyBridge do
    @behaviour :supervisor_bridge

    def init(reason) do
      {:ok, pid} = Task.start_link(Kernel, :exit, [reason])
      {:ok, pid, pid}
    end

    def terminate(_reason, pid) do
      Process.exit(pid, :shutdown)
    end
  end

  setup_all do
    sasl_reports? = Application.get_env(:logger, :handle_sasl_reports, false)
    Application.put_env(:logger, :handle_sasl_reports, true)

    # Restart the app but change the level before to avoid warnings
    level = Logger.level()
    Logger.configure(level: :error)
    Logger.App.stop()
    Application.start(:logger)
    Logger.configure(level: level)

    on_exit(fn() ->
      Application.put_env(:logger, :handle_sasl_reports, sasl_reports?)
      Logger.App.stop()
      Application.start(:logger)
    end)
  end

  test "translates GenServer crashes" do
    {:ok, pid} = GenServer.start(MyGenServer, :ok)

    assert capture_log(:info, fn ->
      catch_exit(GenServer.call(pid, :error))
    end) =~ """
    [error] GenServer #{inspect pid} terminating
    ** (RuntimeError) oops
    """
  end

  test "translates GenServer crashes with custom inspect options" do
    {:ok, pid} = GenServer.start(MyGenServer, List.duplicate(:ok, 1000))
    Application.put_env(:logger, :translator_inspect_opts, [limit: 3])

    assert capture_log(:debug, fn ->
      catch_exit(GenServer.call(pid, :error))
    end) =~ """
    [:ok, :ok, :ok, ...]
    """
  after
    Application.put_env(:logger, :translator_inspect_opts, [])
  end

  test "translates GenServer crashes on debug" do
    {:ok, pid} = GenServer.start(MyGenServer, :ok)

    assert capture_log(:debug, fn ->
      catch_exit(GenServer.call(pid, :error))
    end) =~ ~r"""
    \[error\] GenServer #PID<\d+\.\d+\.\d+> terminating
    \*\* \(RuntimeError\) oops
    .*
    Last message: :error
    State: :ok
    """s
  end

  test "translates GenEvent crashes" do
    {:ok, pid} = GenEvent.start()
    :ok = GenEvent.add_handler(pid, MyGenEvent, :ok)

    assert capture_log(:info, fn ->
      GenEvent.call(pid, MyGenEvent, :error)
    end) =~ """
    [error] GenEvent handler Logger.TranslatorTest.MyGenEvent installed in #{inspect pid} terminating
    ** (RuntimeError) oops
    """
  end

  test "translates GenEvent crashes on debug" do
    {:ok, pid} = GenEvent.start()
    :ok = GenEvent.add_handler(pid, MyGenEvent, :ok)

    assert capture_log(:debug, fn ->
      GenEvent.call(pid, MyGenEvent, :error)
    end) =~ ~r"""
    \[error\] GenEvent handler Logger.TranslatorTest.MyGenEvent installed in #PID<\d+\.\d+\.\d+> terminating
    \*\* \(RuntimeError\) oops
    .*
    Last message: :error
    State: :ok
    """s
  end

  test "translates Task crashes" do
    {:ok, pid} = Task.start_link(__MODULE__, :task, [self()])

    assert capture_log(fn ->
      ref = Process.monitor(pid)
      send(pid, :go)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Task #PID<\d+\.\d+\.\d+> started from #PID<\d+\.\d+\.\d+> terminating
    \*\* \(RuntimeError\) oops
    .*
    Function: &Logger.TranslatorTest.task\/1
        Args: \[#PID<\d+\.\d+\.\d+>\]
    """s
  end

  test "translates Task undef module crash" do
    assert capture_log(fn ->
      {:ok, pid} = Task.start(:module_does_not_exist, :undef, [])
      ref = Process.monitor(pid)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Task #PID<\d+\.\d+\.\d+> started from #PID<\d+\.\d+\.\d+> terminating
    \*\* \(UndefinedFunctionError\) function :module_does_not_exist.undef/0 is undefined \(module :module_does_not_exist is not available\)
    .*
    Function: &:module_does_not_exist.undef/0
        Args: \[\]
    """s
  end

  test "translates Task undef function crash" do
    assert capture_log(fn ->
      {:ok, pid} = Task.start(__MODULE__, :undef, [])
      ref = Process.monitor(pid)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Task #PID<\d+\.\d+\.\d+> started from #PID<\d+\.\d+\.\d+> terminating
    \*\* \(UndefinedFunctionError\) function Logger.TranslatorTest.undef/0 is undefined or private
    .*
    Function: &Logger.TranslatorTest.undef/0
        Args: \[\]
    """s
  end

  test "translates Task raising ErlangError" do
    assert capture_log(fn ->
      exception = try do
          :erlang.error(:foo)
        rescue
         x ->
           x
        end
      {:ok, pid} = Task.start(:erlang, :error, [exception])
      ref = Process.monitor(pid)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Task #PID<\d+\.\d+\.\d+> started from #PID<\d+\.\d+\.\d+> terminating
    \*\* \(ErlangError\) erlang error: :foo
    .*
    Function: &:erlang\.error/1
        Args: \[%ErlangError{.*}\]
    """s
  end

  test "translates Task raising Erlang badarg error" do
    assert capture_log(fn ->
      {:ok, pid} = Task.start(:erlang, :error, [:badarg])
      ref = Process.monitor(pid)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Task #PID<\d+\.\d+\.\d+> started from #PID<\d+\.\d+\.\d+> terminating
    \*\* \(ArgumentError\) argument error
    .*
    Function: &:erlang\.error/1
        Args: \[:badarg\]
    """s
  end

  test "translates Task exiting abnormally" do
    assert capture_log(fn ->
      {:ok, pid} = Task.start(:erlang, :exit, [:abnormal])
      ref = Process.monitor(pid)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Task #PID<\d+\.\d+\.\d+> started from #PID<\d+\.\d+\.\d+> terminating
    \*\* \(stop\) :abnormal
    .*
    Function: &:erlang\.exit/1
        Args: \[:abnormal\]
    """s
  end

  test "translates application stop" do
    assert capture_log(fn ->
    :ok = Application.start(:eex)
      Application.stop(:eex)
    end) =~ """
    Application eex exited: :stopped
    """
  end

  test "translates application start" do
    assert capture_log(fn ->
      Application.start(:eex)
      Application.stop(:eex)
    end) =~ """
    Application eex started at #{inspect(node())}
    """
  end

  test "translates :proc_lib crashes" do
    {:ok, pid} = Task.start_link(__MODULE__, :task, [self()])

    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      send(pid, :message)
      send(pid, :go)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Process #PID<\d+\.\d+\.\d+> terminating
    \*\* \(exit\) an exception was raised:
        \*\* \(RuntimeError\) oops
    .*
    Initial Call: Logger.TranslatorTest.task/1
    Ancestors: \[#PID<\d+\.\d+\.\d+>\]
    """s
  end

  test "translates Process crashes" do
    assert capture_log(:info, fn ->
      {_, ref} = spawn_monitor(fn() -> raise "oops" end)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
      # Even though the monitor has been received the emulator may not have
      # sent the message to the error logger
      :timer.sleep(200)
    end) =~ ~r"""
    \[error\] Process #PID<\d+\.\d+\.\d+>\ raised an exception
    \*\* \(RuntimeError\) oops
    """
  end

  test "translates :proc_lib crashes with name" do
    {:ok, pid} = Task.start_link(__MODULE__, :task,
                                 [self(), fn() ->
                                            Process.register(self(), __MODULE__)
                                            raise "oops"
                                          end])

    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      send(pid, :message)
      send(pid, :go)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Process Logger.TranslatorTest \(#PID<\d+\.\d+\.\d+>\) terminating
    \*\* \(exit\) an exception was raised:
        \*\* \(RuntimeError\) oops
    .*
    Initial Call: Logger.TranslatorTest.task/2
    Ancestors: \[#PID<\d+\.\d+\.\d+>\]
    """s
  end

  test "translates :proc_lib crashes without initial call" do
    {:ok, pid} = Task.start_link(__MODULE__, :task,
                                 [self(), fn() ->
                                            Process.delete(:"$initial_call")
                                            raise "oops"
                                          end])

    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      send(pid, :message)
      send(pid, :go)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[error\] Process #PID<\d+\.\d+\.\d+> terminating
    \*\* \(exit\) an exception was raised:
        \*\* \(RuntimeError\) oops
    .*
    Ancestors: \[#PID<\d+\.\d+\.\d+>\]
    """s
  end

  test "translates :proc_lib crashes with neighbour" do
    {:ok, pid} = Task.start_link(__MODULE__, :sub_task, [self()])

    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      send(pid, :message)
      send(pid, :go)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    Ancestors: \[#PID<\d+\.\d+\.\d+>\]
    Neighbours:
        #PID<\d+\.\d+\.\d+>
            Initial Call: :timer.sleep/1
            Current Call: :timer.sleep/1
            Ancestors: \[#PID<\d+\.\d+\.\d+>, #PID<\d+\.\d+\.\d+>\]
    """
  end
  M

  test "translates :proc_lib crashes with neighbour with name" do
    {:ok, pid} = Task.start_link(__MODULE__, :sub_task,
                                 [self(), fn(pid2) ->
                                            Process.register(pid2, __MODULE__)
                                            raise "oops"
                                          end])

    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      send(pid, :message)
      send(pid, :go)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    Ancestors: \[#PID<\d+\.\d+\.\d+>\]
    Neighbours:
        Logger.TranslatorTest \(#PID<\d+\.\d+\.\d+>\)
            Initial Call: :timer.sleep/1
            Current Call: :timer.sleep/1
            Ancestors: \[#PID<\d+\.\d+\.\d+>, #PID<\d+\.\d+\.\d+>\]
    """
  end

  test "translates :proc_lib crashes on debug" do
    {:ok, pid} = Task.start_link(__MODULE__, :task, [self()])

    assert capture_log(:debug, fn ->
      ref = Process.monitor(pid)
      send(pid, :message)
      send(pid, :go)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    Ancestors: \[#PID<\d+\.\d+\.\d+>\]
    Messages: \[:message\]
    Links: \[\]
    Dictionary: \[\]
    Trapping Exits: false
    Status: :running
    Heap Size: \d+
    Stack Size: \d+
    Reductions: \d+
    """
  end

  test "translates :proc_lib crashes with neighbour on debug" do
    {:ok, pid} = Task.start_link(__MODULE__, :sub_task, [self()])

    assert capture_log(:debug, fn ->
      ref = Process.monitor(pid)
      send(pid, :message)
      send(pid, :go)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
            Ancestors: \[#PID<\d+\.\d+\.\d+>, #PID<\d+\.\d+\.\d+>\]
            Messages: \[\]
            Links: \[#PID<\d+\.\d+\.\d+>\]
            Dictionary: \[\]
            Trapping Exits: false
            Status: :waiting
            Heap Size: \d+
            Stack Size: \d+
            Reductions: \d+
    """
  end

  test "translates Supervisor progress" do
    {:ok, pid} = Supervisor.start_link([], [strategy: :one_for_one])
    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      Supervisor.start_child(pid, worker(Task, [:timer, :sleep, [500]]))
      Process.exit(pid, :normal)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[info\]  Child Task of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) started
    Pid: #PID<\d+\.\d+\.\d+>
    Start Call: Task.start_link\(:timer, :sleep, \[500\]\)
    """
  end

  test "translates Supervisor progress with name" do
    {:ok, pid} = Supervisor.start_link([],
      [strategy: :one_for_one, name: __MODULE__])
    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      Supervisor.start_child(pid, worker(Task, [:timer, :sleep, [500]]))
      Process.exit(pid, :normal)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[info\]  Child Task of Supervisor Logger.TranslatorTest started
    """

    {:ok, pid} = Supervisor.start_link([],
      [strategy: :one_for_one, name: {:global, __MODULE__}])
    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      Supervisor.start_child(pid, worker(Task, [:timer, :sleep, [500]]))
      Process.exit(pid, :normal)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[info\]  Child Task of Supervisor Logger.TranslatorTest started
    """

    {:ok, pid} = Supervisor.start_link([],
      [strategy: :one_for_one, name: {:via, :global, __MODULE__}])
    assert capture_log(:info, fn ->
      ref = Process.monitor(pid)
      Supervisor.start_child(pid, worker(Task, [:timer, :sleep, [500]]))
      Process.exit(pid, :normal)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    \[info\]  Child Task of Supervisor Logger.TranslatorTest started
    """
  end

  test "translates Supervisor progress on debug" do
    {:ok, pid} = Supervisor.start_link([], [strategy: :one_for_one])
    assert capture_log(:debug, fn ->
      ref = Process.monitor(pid)
      Supervisor.start_child(pid, worker(Task, [:timer, :sleep, [500]]))
      Process.exit(pid, :normal)
      receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
    end) =~ ~r"""
    Start Call: Task.start_link\(:timer, :sleep, \[500\]\)
    Restart: :permanent
    Shutdown: 5000
    Type: :worker
    """
  end

  test "translates Supervisor reports start error" do
    assert capture_log(:info, fn ->
      trap = Process.flag(:trap_exit, true)
      Supervisor.start_link([worker(__MODULE__, [], [function: :error])],
        [strategy: :one_for_one])
      receive do: ({:EXIT, _, {:shutdown, {:failed_to_start_child, _, _}}} -> :ok)
      Process.flag(:trap_exit, trap)
    end) =~ ~r"""
    \[error\] Child Logger.TranslatorTest of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) failed to start
    \*\* \(exit\) :stop
    Start Call: Logger.TranslatorTest.error\(\)
    """
  end

  test "translates Supervisor reports start error with raise" do
    assert capture_log(:info, fn ->
      trap = Process.flag(:trap_exit, true)
      Supervisor.start_link([worker(__MODULE__, [], [function: :undef])],
        [strategy: :one_for_one])
      receive do: ({:EXIT, _, {:shutdown, {:failed_to_start_child, _, _}}} -> :ok)
      Process.flag(:trap_exit, trap)
    end) =~ ~r"""
    \[error\] Child Logger.TranslatorTest of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) failed to start
    \*\* \(exit\) an exception was raised:
        \*\* \(UndefinedFunctionError\) function Logger.TranslatorTest.undef/0 is undefined or private
        .*
    Start Call: Logger.TranslatorTest.undef\(\)
    """s
  end

  test "translates Supervisor reports terminated" do
    assert capture_log(:info, fn ->
      trap = Process.flag(:trap_exit, true)
      {:ok, pid} = Supervisor.start_link([worker(Task, [Kernel, :exit, [:stop]])],
        [strategy: :one_for_one, max_restarts: 0])
      receive do: ({:EXIT, ^pid, _} -> :ok)
      Process.flag(:trap_exit, trap)
    end) =~ ~r"""
    \[error\] Child Task of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) terminated
    \*\* \(exit\) :stop
    Pid: #PID<\d+\.\d+\.\d+>
    Start Call: Task.start_link\(Kernel, :exit, \[:stop\]\)
    """
  end

  test "translates Supervisor reports max restarts shutdown" do
    assert capture_log(:info, fn ->
      trap = Process.flag(:trap_exit, true)
      {:ok, pid} = Supervisor.start_link([worker(Task, [Kernel, :exit, [:stop]])],
        [strategy: :one_for_one, max_restarts: 0])
      receive do: ({:EXIT, ^pid, _} -> :ok)
      Process.flag(:trap_exit, trap)
    end) =~ ~r"""
    \[error\] Child Task of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) caused shutdown
    \*\* \(exit\) :reached_max_restart_intensity
    Start Call: Task.start_link\(Kernel, :exit, \[:stop\]\)
    """
  end

  test "translates Supervisor reports abnormal shutdown" do
    assert capture_log(:info, fn ->
      {:ok, pid} = Supervisor.start_link([worker(__MODULE__, [], [function: :abnormal])],
        [strategy: :one_for_one])
      :ok = Supervisor.terminate_child(pid, __MODULE__)
    end) =~ ~r"""
    \[error\] Child Logger.TranslatorTest of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) shutdown abnormally
    \*\* \(exit\) :stop
    Pid: #PID<\d+\.\d+\.\d+>
    Start Call: Logger.TranslatorTest.abnormal\(\)
    """
  end

  test "translates Supervisor reports abnormal shutdown on debug" do
    assert capture_log(:debug, fn ->
      {:ok, pid} = Supervisor.start_link([worker(__MODULE__, [],
          [function: :abnormal, restart: :permanent, shutdown: 5000])],
        [strategy: :one_for_one])
      :ok = Supervisor.terminate_child(pid, __MODULE__)
    end) =~ ~r"""
    \*\* \(exit\) :stop
    Pid: #PID<\d+\.\d+\.\d+>
    Start Call: Logger.TranslatorTest.abnormal\(\)
    Restart: :permanent
    Shutdown: 5000
    Type: :worker
    """
  end

  test "translates Supervisor reports abnormal shutdown in simple_one_for_one" do
    assert capture_log(:info, fn ->
      trap = Process.flag(:trap_exit, true)
      {:ok, pid} = Supervisor.start_link([worker(__MODULE__, [], [function: :abnormal])],
        [strategy: :simple_one_for_one])
      {:ok, _pid2} = Supervisor.start_child(pid, [])
      Process.exit(pid, :normal)
      receive do: ({:EXIT, ^pid, _} -> :ok)
      Process.flag(:trap_exit, trap)
    end) =~ ~r"""
    \[error\] Children Logger.TranslatorTest of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) shutdown abnormally
    \*\* \(exit\) :stop
    Number: 1
    Start Call: Logger.TranslatorTest.abnormal\(\)
    """
  end

  test "translates :supervisor_bridge progress" do
    assert capture_log(:info, fn ->
      trap = Process.flag(:trap_exit, true)
      {:ok, pid} = :supervisor_bridge.start_link(MyBridge, :normal)
      receive do: ({:EXIT, ^pid, _} -> :ok)
      Process.flag(:trap_exit, trap)
    end) =~ ~r"""
    \[info\]  Child of Supervisor #PID<\d+\.\d+\.\d+> \(Logger\.TranslatorTest\.MyBridge\) started
    Pid: #PID<\d+\.\d+\.\d+>
    Start Call: Logger.TranslatorTest.MyBridge.init\(:normal\)
    """
  end

  test "translates :supervisor_bridge reports" do
    assert capture_log(:info, fn ->
      trap = Process.flag(:trap_exit, true)
      {:ok, pid} = :supervisor_bridge.start_link(MyBridge, :stop)
      receive do: ({:EXIT, ^pid, _} -> :ok)
      Process.flag(:trap_exit, trap)
    end) =~ ~r"""
    \[error\] Child of Supervisor #PID<\d+\.\d+\.\d+> \(Logger\.TranslatorTest\.MyBridge\) terminated
    \*\* \(exit\) :stop
    Pid: #PID<\d+\.\d+\.\d+>
    Start Module: Logger.TranslatorTest.MyBridge
    """
  end

  test "handles :undefined MFA properly" do
    children = [Supervisor.Spec.worker(GenServer, [], restart: :temporary)]
    opts = [strategy: :simple_one_for_one]
    {:ok, sup} = Supervisor.start_link(children, opts)
    assert capture_log(:info, fn ->
      {:ok, pid} = Supervisor.start_child(sup, [MyGenServer, []])
      catch_exit(GenServer.call(pid, :error))
      [] = Supervisor.which_children(sup)
    end) =~ "Start Call: GenServer.start_link/?"
  end

  def task(parent, fun \\ (fn() -> raise "oops" end)) do
    Process.unlink(parent)
    receive do: (:go -> fun.())
  end

  def sub_task(parent, fun \\ (fn(_) -> raise "oops" end)) do
    Process.unlink(parent)
    {:ok, pid} = Task.start_link(:timer, :sleep, [500])
    receive do: (:go -> fun.(pid))
  end

  def error(), do: {:error, :stop}

  def abnormal() do
    :proc_lib.start_link(__MODULE__, :abnormal_init, [])
  end

  def abnormal_init() do
    Process.flag(:trap_exit, true)
    :proc_lib.init_ack({:ok, self()})
    receive do: ({:EXIT, _, _} -> exit(:stop))
  end

end


Proof-read Agent docs

Proof-read Agent docs

defmodule Agent do
  @moduledoc """
  Agents are a simple abstraction around state.

  Often in Elixir there is a need to share or store state that
  must be accessed from different processes or by a same process
  in different points in time.

  The Agent module provides a basic server implementation that
  allows state to be retrieved and updated via a simple API.

  ## Examples

  For example, in the Mix tool that ships with Elixir, we need
  to keep a set of all tasks executed by a given project. Since
  this set is shared, we can implement it with an Agent:

      defmodule Mix.TasksServer do
        def start_link do
          Agent.start_link(fn -> HashSet.new end, name: __MODULE__)
        end

        @doc "Checks if the task has already executed"
        def executed?(task, project) do
          item = {task, project}
          Agent.get(__MODULE__, fn set ->
            item in set
          end)
        end

        @doc "Marks a task as executed"
        def put_task(task, project) do
          item = {task, project}
          Agent.update(__MODULE__, &Set.put(&1, item))
        end
      end

  Note that agents still provide a segregation in between the
  client and server APIs, as seen in GenServers. In particular,
  all code inside the function passed to the agent is executed
  by the agent. This distinction is important because you may
  want to avoid expensive operations inside the agent, as it will
  effectively block the agent until the request is fullfilled.

  Consider these two examples:

      # Compute in the agent/server
      def get_something(agent) do
        Agent.get(agent, fn state -> do_something_expensive(state) end)
      end

      # Compute in the agent/client
      def get_something(agent) do
        Agent.get(agent, &(&1)) |> do_something_expensive()
      end

  The first one blocks the agent while the second one copies
  all the state to the client and executes the operation in the client.
  The trade-off here is exactly if the data is small enough to be
  sent to the client cheaply or large enough to require processing on
  the server (or at least some initial processing).

  ## Name registration

  An Agent is bound to the same name registration rules as GenServers.
  Read more about it in the `GenServer` docs.

  ## A word on distributed agents

  It is important to consider the limitations of distributed agents. Agents
  work by sending anonymous functions in between the caller and the agent.
  In a distributed setup with multiple nodes, agents only work if the caller
  (client) and the agent have the same version of a given module.

  This setup may exhibit issues when doing "rolling upgrades". By rolling
  upgrades we mean the following situation: you wish to deploy a new version of
  your software by *shutting down* some of your nodes and replacing them by
  nodes running a new version of the software. In this setup, part of your
  environment will have one version of a given module and the other part
  another version (the newer one) of the same module; this may cause agents to
  crash. That said, if you plan to run in distributed environments, agents
  should likely be avoided.

  Note, however, that agents work fine if you want to perform hot code
  swapping, as it keeps both the old and new versions of a given module.
  We detail how to do hot code swapping with agents in the next section.

  ## Hot code swapping

  An agent can have its code hot swapped live by simply passing a module,
  function and args tuple to the update instruction. For example, imagine
  you have an agent named `:sample` and you want to convert its inner state
  from some dict structure to a map. It can be done with the following
  instruction:

      {:update, :sample, {:advanced, {Enum, :into, [%{}]}}}

  The agent's state will be added to the given list as the first argument.
  """

  @typedoc "Return values of `start*` functions"
  @type on_start :: {:ok, pid} | {:error, {:already_started, pid} | term}

  @typedoc "The agent name"
  @type name :: atom | {:global, term} | {:via, module, term}

  @typedoc "The agent reference"
  @type agent :: pid | {atom, node} | name

  @typedoc "The agent state"
  @type state :: term

  @doc """
  Starts an agent linked to the current process.

  This is often used to start the agent as part of a supervision tree.

  Once the agent is spawned, the given function is invoked and its return
  value is used as the agent state. Note that `start_link` does not return
  until the given function has returned.

  ## Options

  The `:name` option is used for registration as described in the module
  documentation. If the `:timeout` option is present, the agent is allowed to
  spend at most the given amount of milliseconds on initialization or it will
  be terminated and the start function will return `{:error, :timeout}`.

  If the `:debug` option is present, the corresponding function in the
  [`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.

  If the `:spawn_opt` option is present, its value will be passed as options
  to the underlying process as in `Process.spawn/3`.

  ## Return values

  If the server is successfully created and initialized, the function returns
  `{:ok, pid}`, where pid is the pid of the server. If there already exists
  an agent with the specified name, the function returns
  `{:error, {:already_started, pid}}` with the pid of that process.

  If the given function callback fails with `reason`, the function returns
  `{:error, reason}`.
  """
  @spec start_link((() -> term), GenServer.options) :: on_start
  def start_link(fun, options \\ []) when is_function(fun, 0) do
    GenServer.start_link(Agent.Server, fun, options)
  end

  @doc """
  Starts an agent process without links (outside of a supervision tree).

  See `start_link/2` for more information.
  """
  @spec start((() -> term), GenServer.options) :: on_start
  def start(fun, options \\ []) when is_function(fun, 0) do
    GenServer.start(Agent.Server, fun, options)
  end

  @doc """
  Gets the agent value and executes the given function.

  The function `fun` is sent to the `agent` which invokes the function
  passing the agent state. The result of the function invocation is
  returned.

  A timeout can also be specified (it has a default value of 5000).
  """
  @spec get(agent, (state -> a), timeout) :: a when a: var
  def get(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
    GenServer.call(agent, {:get, fun}, timeout)
  end

  @doc """
  Gets and updates the agent state in one operation.

  The function `fun` is sent to the `agent` which invokes the function
  passing the agent state. The function must return a tuple with two
  elements, the first being the value to return (i.e. the get value)
  and the second one is the new state.

  A timeout can also be specified (it has a default value of 5000).
  """
  @spec get_and_update(agent, (state -> {a, state}), timeout) :: a when a: var
  def get_and_update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
    GenServer.call(agent, {:get_and_update, fun}, timeout)
  end

  @doc """
  Updates the agent state.

  The function `fun` is sent to the `agent` which invokes the function
  passing the agent state. The function must return the new state.

  A timeout can also be specified (it has a default value of 5000).
  This function always returns `:ok`.
  """
  @spec update(agent, (state -> state)) :: :ok
  def update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
    GenServer.call(agent, {:update, fun}, timeout)
  end

  @doc """
  Performs a cast (fire and forget) operation on the agent state.

  The function `fun` is sent to the `agent` which invokes the function
  passing the agent state. The function must return the new state.

  Note that `cast` returns `:ok` immediately, regardless of whether the
  destination node or agent exists.
  """
  @spec cast(agent, (state -> state)) :: :ok
  def cast(agent, fun) when is_function(fun, 1) do
    GenServer.cast(agent, fun)
  end

  @doc """
  Stops the agent.

  Returns `:ok` if the agent is stopped within the given `timeout`.
  """
  @spec stop(agent, timeout) :: :ok
  def stop(agent, timeout \\ 5000) do
    GenServer.call(agent, :stop, timeout)
  end
end

defmodule Agent do
  @moduledoc """
  Agents are a simple abstraction around state.

  Often in Elixir there is a need to share or store state that
  must be accessed from different processes or by a same process
  in different points in time.

  The Agent module provides a basic server implementation that
  allows state to be retrieved and updated via a simple API.

  ## Examples

  For example, in the Mix tool that ships with Elixir, we need
  to keep a set of all tasks executed by a given project. Since
  this set is shared, we can implement it with an Agent:

      defmodule Mix.TasksServer do
        def start_link do
          Agent.start_link(fn -> HashSet.new end, name: __MODULE__)
        end

        @doc "Checks if the task has already executed"
        def executed?(task, project) do
          item = {task, project}
          Agent.get(__MODULE__, fn set ->
            item in set
          end)
        end

        @doc "Marks a task as executed"
        def put_task(task, project) do
          item = {task, project}
          Agent.update(__MODULE__, &Set.put(item, &1))
        end
      end

  Note that agents still provide a segregation in between the
  client and server APIs, as seen in GenServers. In particular,
  all code inside the function passed to the agent is executed
  by the agent. This distinction is important because you may
  want to avoid expensive operations inside the agent, as it will
  effectively block the agent until the request is fullfilled.

  Consider these two examples:

      # Compute in the agent/server
      def get_something(agent) do
        Agent.get(agent, fn state -> do_something_expensive(state) end)
      end

      # Compute in the agent/client
      def get_something(agent) do
        Agent.get(agent, &(&1)) |> do_something_expensive()
      end

  The first one blocks the agent while the second one moves copies
  all the state to the client and execute the operation in the client.
  The trade-off here is exactly if the data is small enough to be
  sent to the client cheaply or large enough to require processing on
  the server (or at least some initial processing).

  ## Name registering

  An Agent is bound to the same name registering rules as a `GenServer`.
  Read more about it in the `GenServer` docs.

  ## A word on distributed agents

  It is important to consider the limitation of distributed agents. Agents
  work by sending anonymous functions in between the caller and the agent.
  In a distributed setup with multiple nodes, agents only work if the caller
  (client) and the agent have the same version of a given module.

  This setup may exhibit issues when doing "rolling upgrades". By rolling
  upgrades we mean that you desire to deploy a new version your software
  by *shutting down* some of your nodes and replace them by nodes running
  a new version of the software. In this setup, part of your environment
  will have one version of a given module and other part another version
  of the same module, which may lead agents to crash. That said, if you
  plan to run on distributed environments, agents should likely be avoided.

  Note however agents work fine if you want to perform hot code swap, as
  hot code swapping keeps both the old and new versions of a given module.
  We detail how to do hot code swapping with agents in the next section.

  ## Hot code swapping

  An agent can have its code hot swapped live by simply passing a module,
  function and args tuple to the update instruction. For example, imagine
  you have an agent named `:sample` and you want to convert its inner state
  from some dict structure to a map. It can be done with the following
  instruction:

      {:update, :sample, {:advanced, {Enum, :into, [%{}]}}}

  The agent state will be added as first argument of the arguments list.
  """

  @typedoc "Return values of `start*` functions"
  @type on_start :: {:ok, pid} | {:error, {:already_started, pid} | term}

  @typedoc "The agent name"
  @type name :: atom | {:global, term} | {:via, module, term}

  @typedoc "The agent reference"
  @type agent :: pid | {atom, node} | name

  @typedoc "The agent state"
  @type state :: term

  @doc """
  Starts an agent linked to the current process.

  This is often used to start the agent as part of a supervision tree.

  Once the agent is spawned, the given function is invoked and its return
  value is used as the agent state. Note this function does not return
  until the given function has returned.

  ## Options

  The `:name` option is used for name registered as described in the module
  documentation. If the option `:timeout` option is present, the agent is
  allowed to spend the given milliseconds initializing or it will be terminated
  and the start function will return `{:error, :timeout}`.

  If the option `:debug` is present, the corresponding function in the
  [`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.

  If the option `:spawn_opt` is present, the given options will be passed
  to the underlying process as in `Process.spawn/3`.

  ## Return values

  If the server is successfully created and initialized the function returns
  `{:ok, pid}`, where pid is the pid of the server. If there already exists
  an agent with the specified name the function returns
  `{:error, {:already_started, pid}}` with the pid of that process.

  If the given function callback fails with reason, the function returns
  `{:error, reason}`.
  """
  @spec start_link((() -> term), GenServer.options) :: on_start
  def start_link(fun, options \\ []) when is_function(fun, 0) do
    GenServer.start_link(Agent.Server, fun, options)
  end

  @doc """
  Starts an agent process without links (outside of a supervision tree).

  See `start_link/2` for more information.
  """
  @spec start((() -> term), GenServer.options) :: on_start
  def start(fun, options \\ []) when is_function(fun, 0) do
    GenServer.start(Agent.Server, fun, options)
  end

  @doc """
  Gets the agent value and executes the given function.

  The function `fun` is sent to the `agent` which invokes the function
  passing the agent state. The result of the function invocation is
  returned.

  A timeout can also be specified (it has a default value of 5000).
  """
  @spec get(agent, (state -> a), timeout) :: a when a: var
  def get(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
    GenServer.call(agent, {:get, fun}, timeout)
  end

  @doc """
  Gets and updates the agent state in one operation.

  The function `fun` is sent to the `agent` which invokes the function
  passing the agent state. The function must return a tuple with two
  elements, the first being the value to returned (i.e. the get value)
  and the second one is the new state.

  A timeout can also be specified (it has a default value of 5000).
  """
  @spec get_and_update(agent, (state -> {a, state}), timeout) :: a when a: var
  def get_and_update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
    GenServer.call(agent, {:get_and_update, fun}, timeout)
  end

  @doc """
  Updates the agent state.

  The function `fun` is sent to the `agent` which invokes the function
  passing the agent state. The function must return the new state.

  A timeout can also be specified (it has a default value of 5000).
  This function always return `:ok`.
  """
  @spec update(agent, (state -> state)) :: :ok
  def update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
    GenServer.call(agent, {:update, fun}, timeout)
  end

  @doc """
  Performs a cast (fire and forget) operation on the agent state.

  The function `fun` is sent to the `agent` which invokes the function
  passing the agent state. The function must return the new state.

  Note this function returns `:ok` immediately, ignoring if the
  destination node or agent does not exist.
  """
  @spec cast(agent, (state -> state)) :: :ok
  def cast(agent, fun) when is_function(fun, 1) do
    GenServer.cast(agent, fun)
  end

  @doc """
  Stops the agent.

  Returns `:ok` if the agent is stopped in the given `timeout`.
  """
  @spec stop(agent, timeout) :: :ok
  def stop(agent, timeout \\ 5000) do
    GenServer.call(agent, :stop, timeout)
  end
end


hide network events

hide network events

defmodule Farmbot.Target.Network.Manager do
  use GenServer
  use Farmbot.Logger
  alias Nerves.Network
  alias Farmbot.Target.Network.Ntp
  import Farmbot.Target.Network, only: [test_dns: 0]

  def start_link(interface, opts) do
    GenServer.start_link(__MODULE__, {interface, opts}, [name: :"#{__MODULE__}-#{interface}"])
  end

  def init({interface, opts} = args) do
    unless interface in Nerves.NetworkInterface.interfaces() do
      Logger.busy(3, "Waiting for interface up.")
      Process.sleep(1000)
      init(args)
    end
    SystemRegistry.register()
    {:ok, _} = Registry.register(Nerves.NetworkInterface, interface, [])
    {:ok, _} = Registry.register(Nerves.Udhcpc, interface, [])
    {:ok, _} = Registry.register(Nerves.WpaSupplicant, interface, [])
    Network.setup(interface, opts)
    {:ok, %{interface: interface, ip_address: nil, connected: false}}
  end

  def handle_info({:system_registry, :global, registry}, state) do
    ip = get_in(registry, [:state, :network_interface, state.interface, :ipv4_address])

    if ip != state.ip_address do
      Logger.warn(3, "ip address changed on interface: #{state.interface}: #{ip}")
      :ok = Ntp.set_time()
    end

    connected = match?({:ok, {:hostent, 'nerves-project.org', [], :inet, 4, _}}, test_dns())
    {:noreply, %{state | ip_address: ip, connected: connected || false}}
  end

  def handle_info({Nerves.WpaSupplicant, {:INFO, "WPA: 4-Way Handshake failed - pre-shared key may be incorrect"}, _}, state) do
    Logger.error 1, "Incorrect PSK."
    Farmbot.System.factory_reset("WIFI Authentication failed. (incorrect psk)")
    {:stop, :normal, state}
  end

  def handle_info(_event, state) do
    # Logger.warn 3, "unhandled network event: #{inspect event}"
    {:noreply, state}
  end
end

defmodule Farmbot.Target.Network.Manager do
  use GenServer
  use Farmbot.Logger
  alias Nerves.Network
  alias Farmbot.Target.Network.Ntp
  import Farmbot.Target.Network, only: [test_dns: 0]

  def start_link(interface, opts) do
    GenServer.start_link(__MODULE__, {interface, opts}, [name: :"#{__MODULE__}-#{interface}"])
  end

  def init({interface, opts} = args) do
    unless interface in Nerves.NetworkInterface.interfaces() do
      Logger.busy(3, "Waiting for interface up.")
      Process.sleep(1000)
      init(args)
    end
    SystemRegistry.register()
    {:ok, _} = Registry.register(Nerves.NetworkInterface, interface, [])
    {:ok, _} = Registry.register(Nerves.Udhcpc, interface, [])
    {:ok, _} = Registry.register(Nerves.WpaSupplicant, interface, [])
    Network.setup(interface, opts)
    {:ok, %{interface: interface, ip_address: nil, connected: false}}
  end

  def handle_info({:system_registry, :global, registry}, state) do
    ip = get_in(registry, [:state, :network_interface, state.interface, :ipv4_address])

    if ip != state.ip_address do
      Logger.warn(3, "ip address changed on interface: #{state.interface}: #{ip}")
      :ok = Ntp.set_time()
    end

    connected = match?({:ok, {:hostent, 'nerves-project.org', [], :inet, 4, _}}, test_dns())
    {:noreply, %{state | ip_address: ip, connected: connected || false}}
  end

  def handle_info({Nerves.WpaSupplicant, {:INFO, "WPA: 4-Way Handshake failed - pre-shared key may be incorrect"}, _}, state) do
    Logger.error 1, "Incorrect PSK."
    Farmbot.System.factory_reset("WIFI Authentication failed. (incorrect psk)")
    {:stop, :normal, state}
  end

  def handle_info(event, state) do
    Logger.warn 3, "unhandled network event: #{inspect event}"
    {:noreply, state}
  end
end


Zeropad compile time in i/1

Zeropad compile time in i/1

Because this looks a little odd:

    Compile time
      2016-1-2 23:28:9

defprotocol IEx.Info do
  @fallback_to_any true

  @spec info(term) :: [{atom, String.t}]
  def info(term)
end

defimpl IEx.Info, for: Tuple do
  def info(_tuple) do
    ["Data type": "Tuple",
     "Reference modules": "Tuple"]
  end
end

defimpl IEx.Info, for: Atom do
  def info(atom) do
    specific_info = if Code.ensure_loaded?(atom), do: info_module(atom), else: info_atom(atom)
    ["Data type": "Atom"] ++ specific_info
  end

  defp info_module(mod) do
    extra =
      if Code.get_docs(mod, :moduledoc) do
        "Use h(#{inspect mod}) to access its documentation.\n"
      else
        ""
      end

    mod_info = mod.module_info()
    ["Module bytecode": module_object_file(mod),
     "Source": module_source_file(mod_info),
     "Version": module_version(mod_info),
     "Compile time": module_compile_time(mod_info),
     "Compile options": module_compile_options(mod_info),
     "Description": "#{extra}Call #{inspect mod}.module_info() to access metadata.",
     "Raw representation": ":" <> inspect(Atom.to_string(mod)),
     "Reference modules": "Module, Atom"]
  end

  defp info_atom(_atom) do
    ["Reference modules": "Atom"]
  end

  defp module_object_file(mod) do
    default_or_apply :code.which(mod), fn
      atom when is_atom(atom) -> inspect(atom)
      path                    -> Path.relative_to_cwd(path)
    end
  end

  defp module_version(mod_info) do
    default_or_apply(mod_info[:attributes][:vsn], &inspect/1)
  end

  defp module_source_file(mod_info) do
    default_or_apply(mod_info[:compile][:source], &Path.relative_to_cwd/1)
  end

  defp module_compile_options(mod_info) do
    default_or_apply(mod_info[:compile][:options], &inspect/1)
  end

  defp module_compile_time(mod_info) do
    default_or_apply(mod_info[:compile][:time], &format_time/1)
  end

  defp format_time({year, month, day, hour, min, sec}) do
    "#{year}-#{zeropad(month)}-#{zeropad(day)} #{zeropad(hour)}:#{zeropad(min)}:#{zeropad(sec)}"
  end

  defp default_or_apply(nil, _), do: "no value found"
  defp default_or_apply(data, fun), do: fun.(data)

  defp zeropad(number) when number < 10, do: "0#{number}"
  defp zeropad(number), do: "#{number}"
end

defimpl IEx.Info, for: List do
  def info(list) do
    specific_info =
      cond do
        list == []                    -> info_list(list)
        Inspect.List.printable?(list) -> info_char_list(list)
        Keyword.keyword?(list)        -> info_kw_list(list)
        true                          -> info_list(list)
      end

    ["Data type": "List"] ++ specific_info
  end

  defp info_char_list(char_list) do
    desc = """
    This is a list of integers that is printed as a sequence of characters
    delimited by single quotes because all the integers in it represent valid
    ASCII characters. Conventionally, such lists of integers are referred to as
    "char lists" (more precisely, a char list is a list of Unicode codepoints,
    and ASCII is a subset of Unicode).
    """

    ["Description": desc,
     "Raw representation": inspect(char_list, char_lists: :as_lists),
     "Reference modules": "List"]
  end

  defp info_kw_list(_kw_list) do
    desc = """
    This is what is referred to as a "keyword list". A keyword list is a list
    of two-element tuples where the first element of each tuple is an atom.
    """

    ["Description": desc,
     "Reference modules": "Keyword, List"]
  end

  defp info_list(_list) do
    ["Reference modules": "List"]
  end
end

defimpl IEx.Info, for: BitString do
  def info(bitstring) do
    specific_info =
      cond do
        is_binary(bitstring) and String.printable?(bitstring) -> info_string(bitstring)
        is_binary(bitstring) and String.valid?(bitstring)     -> info_non_printable_string(bitstring)
        is_binary(bitstring)                                  -> info_binary(bitstring)
        is_bitstring(bitstring)                               -> info_bitstring(bitstring)
      end

    ["Data type": "BitString"] ++ specific_info
  end

  defp info_string(bitstring) do
    desc = """
    This is a string: a UTF-8 encoded binary. It's printed surrounded by
    "double quotes" because all UTF-8 encoded codepoints in it are printable.
    """
    ["Byte size": byte_size(bitstring),
     "Description": desc,
     "Raw representation": inspect(bitstring, binaries: :as_binaries),
     "Reference modules": "String, :binary"]
  end

  defp info_non_printable_string(bitstring) do
    first_non_printable =
      bitstring
      |> String.codepoints()
      |> Enum.find(fn cp -> not String.printable?(cp) end)

    desc = """
    This is a string: a UTF-8 encoded binary. It's printed with the `<<>>`
    syntax (as opposed to double quotes) because it contains non-printable
    UTF-8 encoded codepoints (the first non-printable codepoint being `#{inspect first_non_printable}`)
    """
    ["Byte size": byte_size(bitstring),
     "Description": desc,
     "Reference modules": "String, :binary"]
  end

  defp info_binary(bitstring) do
    first_non_valid =
      bitstring
      |> String.codepoints()
      |> Enum.find(fn cp -> not String.valid?(cp) end)

    desc = """
    This is a binary: a collection of bytes. It's printed with the `<<>>`
    syntax (as opposed to double quotes) because it is not a
    UTF-8 encoded binary (the first invalid byte being `#{inspect first_non_valid}`)
    """

    ["Byte size": byte_size(bitstring),
     "Description": desc,
     "Reference modules": ":binary"]
  end

  defp info_bitstring(bitstring) do
    desc = """
    This is a bitstring. It's a chunk of bits that are not divisible by 8
    (the number of bytes isn't whole).
    """

    ["Bits size": bit_size(bitstring),
     "Description": desc]
  end
end

defimpl IEx.Info, for: Integer do
  def info(_) do
    ["Data type": "Integer",
     "Reference modules": "Integer"]
  end
end

defimpl IEx.Info, for: Float do
  def info(_) do
    ["Data type": "Float",
     "Reference modules": "Float"]
  end
end

defimpl IEx.Info, for: Function do
  def info(fun) do
    fun_info = :erlang.fun_info(fun)

    specific_info =
      if fun_info[:type] == :external and fun_info[:env] == [] do
        info_named_fun(fun_info)
      else
        info_anon_fun(fun_info)
      end

    ["Data type": "Function"] ++ specific_info
  end

  defp info_anon_fun(fun_info) do
    ["Type": to_string(fun_info[:type]),
     "Arity": fun_info[:arity],
     "Description": "This is an anonymous function."]
  end

  defp info_named_fun(fun_info) do
    ["Type": to_string(fun_info[:type]),
     "Arity": fun_info[:arity]]
  end
end

defimpl IEx.Info, for: PID do
  @keys [:registered_name, :links, :message_queue_len]

  def info(pid) do
    extra =
      case :rpc.pinfo(pid, @keys) do
        [_|_] = info ->
          ["Alive": true,
           "Name": process_name(info[:registered_name]),
           "Links": links(info[:links]),
           "Message queue length": info[:message_queue_len]]
        _ ->
          ["Alive": false]
      end

    ["Data type": "PID"] ++ extra ++
      ["Description": "Use Process.info/1 to get more info about this process",
       "Reference modules": "Process, Node"]
  end

  defp process_name([]), do: "not registered"
  defp process_name(name), do: inspect(name)

  defp links([]), do: "none"
  defp links(links), do: Enum.map_join(links, ", ", &inspect/1)
end

defimpl IEx.Info, for: Map do
  def info(_) do
    ["Data type": "Map",
     "Reference modules": "Map"]
  end
end

defimpl IEx.Info, for: Port do
  def info(port) do
    connected = :rpc.call(node(port), :erlang, :port_info, [port, :connected])

    ["Data type": "Port",
     "Open": match?({:connected, _}, connected),
     "Reference modules": "Port"]
  end
end

defimpl IEx.Info, for: Reference do
  def info(_) do
    ["Data type": "Reference"]
  end
end

defimpl IEx.Info, for: Any do
  def info(%{__struct__: mod}) do
    ["Data type": inspect(mod),
     "Description": "This is a struct. Structs are maps with a __struct__ key.",
     "Reference modules": inspect(mod) <> ", Map"]
  end
end

defprotocol IEx.Info do
  @fallback_to_any true

  @spec info(term) :: [{atom, String.t}]
  def info(term)
end

defimpl IEx.Info, for: Tuple do
  def info(_tuple) do
    ["Data type": "Tuple",
     "Reference modules": "Tuple"]
  end
end

defimpl IEx.Info, for: Atom do
  def info(atom) do
    specific_info = if Code.ensure_loaded?(atom), do: info_module(atom), else: info_atom(atom)
    ["Data type": "Atom"] ++ specific_info
  end

  defp info_module(mod) do
    extra =
      if Code.get_docs(mod, :moduledoc) do
        "Use h(#{inspect mod}) to access its documentation.\n"
      else
        ""
      end

    mod_info = mod.module_info()
    ["Module bytecode": module_object_file(mod),
     "Source": module_source_file(mod_info),
     "Version": module_version(mod_info),
     "Compile time": module_compile_time(mod_info),
     "Compile options": module_compile_options(mod_info),
     "Description": "#{extra}Call #{inspect mod}.module_info() to access metadata.",
     "Raw representation": ":" <> inspect(Atom.to_string(mod)),
     "Reference modules": "Module, Atom"]
  end

  defp info_atom(_atom) do
    ["Reference modules": "Atom"]
  end

  defp module_object_file(mod) do
    default_or_apply :code.which(mod), fn
      atom when is_atom(atom) -> inspect(atom)
      path                    -> Path.relative_to_cwd(path)
    end
  end

  defp module_version(mod_info) do
    default_or_apply(mod_info[:attributes][:vsn], &inspect/1)
  end

  defp module_source_file(mod_info) do
    default_or_apply(mod_info[:compile][:source], &Path.relative_to_cwd/1)
  end

  defp module_compile_options(mod_info) do
    default_or_apply(mod_info[:compile][:options], &inspect/1)
  end

  defp module_compile_time(mod_info) do
    default_or_apply(mod_info[:compile][:time], &format_time/1)
  end

  defp format_time({year, month, day, hour, min, sec}) do
    "#{year}-#{month}-#{day} #{hour}:#{min}:#{sec}"
  end

  defp default_or_apply(nil, _), do: "no value found"
  defp default_or_apply(data, fun), do: fun.(data)
end

defimpl IEx.Info, for: List do
  def info(list) do
    specific_info =
      cond do
        list == []                    -> info_list(list)
        Inspect.List.printable?(list) -> info_char_list(list)
        Keyword.keyword?(list)        -> info_kw_list(list)
        true                          -> info_list(list)
      end

    ["Data type": "List"] ++ specific_info
  end

  defp info_char_list(char_list) do
    desc = """
    This is a list of integers that is printed as a sequence of characters
    delimited by single quotes because all the integers in it represent valid
    ASCII characters. Conventionally, such lists of integers are referred to as
    "char lists" (more precisely, a char list is a list of Unicode codepoints,
    and ASCII is a subset of Unicode).
    """

    ["Description": desc,
     "Raw representation": inspect(char_list, char_lists: :as_lists),
     "Reference modules": "List"]
  end

  defp info_kw_list(_kw_list) do
    desc = """
    This is what is referred to as a "keyword list". A keyword list is a list
    of two-element tuples where the first element of each tuple is an atom.
    """

    ["Description": desc,
     "Reference modules": "Keyword, List"]
  end

  defp info_list(_list) do
    ["Reference modules": "List"]
  end
end

defimpl IEx.Info, for: BitString do
  def info(bitstring) do
    specific_info =
      cond do
        is_binary(bitstring) and String.printable?(bitstring) -> info_string(bitstring)
        is_binary(bitstring) and String.valid?(bitstring)     -> info_non_printable_string(bitstring)
        is_binary(bitstring)                                  -> info_binary(bitstring)
        is_bitstring(bitstring)                               -> info_bitstring(bitstring)
      end

    ["Data type": "BitString"] ++ specific_info
  end

  defp info_string(bitstring) do
    desc = """
    This is a string: a UTF-8 encoded binary. It's printed surrounded by
    "double quotes" because all UTF-8 encoded codepoints in it are printable.
    """
    ["Byte size": byte_size(bitstring),
     "Description": desc,
     "Raw representation": inspect(bitstring, binaries: :as_binaries),
     "Reference modules": "String, :binary"]
  end

  defp info_non_printable_string(bitstring) do
    first_non_printable =
      bitstring
      |> String.codepoints()
      |> Enum.find(fn cp -> not String.printable?(cp) end)

    desc = """
    This is a string: a UTF-8 encoded binary. It's printed with the `<<>>`
    syntax (as opposed to double quotes) because it contains non-printable
    UTF-8 encoded codepoints (the first non-printable codepoint being `#{inspect first_non_printable}`)
    """
    ["Byte size": byte_size(bitstring),
     "Description": desc,
     "Reference modules": "String, :binary"]
  end

  defp info_binary(bitstring) do
    first_non_valid =
      bitstring
      |> String.codepoints()
      |> Enum.find(fn cp -> not String.valid?(cp) end)

    desc = """
    This is a binary: a collection of bytes. It's printed with the `<<>>`
    syntax (as opposed to double quotes) because it is not a
    UTF-8 encoded binary (the first invalid byte being `#{inspect first_non_valid}`)
    """

    ["Byte size": byte_size(bitstring),
     "Description": desc,
     "Reference modules": ":binary"]
  end

  defp info_bitstring(bitstring) do
    desc = """
    This is a bitstring. It's a chunk of bits that are not divisible by 8
    (the number of bytes isn't whole).
    """

    ["Bits size": bit_size(bitstring),
     "Description": desc]
  end
end

defimpl IEx.Info, for: Integer do
  def info(_) do
    ["Data type": "Integer",
     "Reference modules": "Integer"]
  end
end

defimpl IEx.Info, for: Float do
  def info(_) do
    ["Data type": "Float",
     "Reference modules": "Float"]
  end
end

defimpl IEx.Info, for: Function do
  def info(fun) do
    fun_info = :erlang.fun_info(fun)

    specific_info =
      if fun_info[:type] == :external and fun_info[:env] == [] do
        info_named_fun(fun_info)
      else
        info_anon_fun(fun_info)
      end

    ["Data type": "Function"] ++ specific_info
  end

  defp info_anon_fun(fun_info) do
    ["Type": to_string(fun_info[:type]),
     "Arity": fun_info[:arity],
     "Description": "This is an anonymous function."]
  end

  defp info_named_fun(fun_info) do
    ["Type": to_string(fun_info[:type]),
     "Arity": fun_info[:arity]]
  end
end

defimpl IEx.Info, for: PID do
  @keys [:registered_name, :links, :message_queue_len]

  def info(pid) do
    extra =
      case :rpc.pinfo(pid, @keys) do
        [_|_] = info ->
          ["Alive": true,
           "Name": process_name(info[:registered_name]),
           "Links": links(info[:links]),
           "Message queue length": info[:message_queue_len]]
        _ ->
          ["Alive": false]
      end

    ["Data type": "PID"] ++ extra ++
      ["Description": "Use Process.info/1 to get more info about this process",
       "Reference modules": "Process, Node"]
  end

  defp process_name([]), do: "not registered"
  defp process_name(name), do: inspect(name)

  defp links([]), do: "none"
  defp links(links), do: Enum.map_join(links, ", ", &inspect/1)
end

defimpl IEx.Info, for: Map do
  def info(_) do
    ["Data type": "Map",
     "Reference modules": "Map"]
  end
end

defimpl IEx.Info, for: Port do
  def info(port) do
    connected = :rpc.call(node(port), :erlang, :port_info, [port, :connected])

    ["Data type": "Port",
     "Open": match?({:connected, _}, connected),
     "Reference modules": "Port"]
  end
end

defimpl IEx.Info, for: Reference do
  def info(_) do
    ["Data type": "Reference"]
  end
end

defimpl IEx.Info, for: Any do
  def info(%{__struct__: mod}) do
    ["Data type": inspect(mod),
     "Description": "This is a struct. Structs are maps with a __struct__ key.",
     "Reference modules": inspect(mod) <> ", Map"]
  end
end


revert

revert

defmodule Blockytalky.SonicPi do
  @moduledoc """
  This is a module completely of "functions-as-data" sonicpi programs.
  to achieve subdivisions sleep in % amounts relative to beats.
  e.g.
  to rest for 1/8th + 1/16th and then play a 16th note, it would look like:
    sync :beat1
    sleep 0.75
    play <pitch>
  """
  def listen_port, do:  Application.get_env(:blockytalky, :music_port, 9090)
  def eval_port, do: Application.get_env(:blockytalky, :music_eval_port, 5050)
  ####
  #System-y functions
  def public_cues do
    [:down_beat, :up_beat, :beat1, :beat2, :beat3, :beat4]
  end
  def init do
    set_amp(50) <> tempo(120) <> maestro_beat_pattern(false,4)
  end
  @doc """
  Takes a value between 0 and 100 and converts it to one between 0 and 5
  """
  def set_amp(value) do
    value = value / 20
    """
    $amp = #{value}
    """
  end
  def tempo(val) do
    """
    $tempo = #{val}
    """
  end
  @doc """
  takes the name of the btu to sync to or false as a first param
  takes beats per measure >= 1 as a second param.
  When syncing to a parent btu, it will listen for a first message,
  then it will keep track of time internally for one measure
  #the default sonic pi eval port is pretty slow on raspberry pi
  #this loop listens on a different port to speed it up
  """
  def maestro_beat_pattern(parent, beats_per_measure) do
    sync_to_network = case parent do
      false -> "#"
      name -> #Ruby code to listen until the parent sends a sync message
      """
      synced = false
      until synced  do
        begin
          synced = false
          msg = $u2.recvfrom_nonblock(1024) # "["hostname,tempo",[..args..]]"
          msg_payload = msg[0].split(",")
          host = msg_payload[0]
          if(host == "#{name}")
            t = msg_payload[1]
            beat = msg_payload[2]
            $tempo = t.to_f
            use_bpm $tempo
            $u1.send "#{Blockytalky.RuntimeUtils.btu_id},\#{$tempo\},\#{beat\}", 0, '224.0.0.1', #{listen_port}
            cue beat.to_sym
            if beat != "up_beat"
              cue :down_beat
            end
            synced = true
          end
          sleep 1.0 / 128.0
        rescue
          sleep 1.0 / 64.0
          next
        end
      end
      """
    end
    beat_signaling = cond do
      beats_per_measure > 1 ->
      Enum.reduce(1..beats_per_measure,"",fn(x,acc) ->
        acc
        <>
        """
        cue :down_beat
        cue :beat#{x}
        $u1.send "#{Blockytalky.RuntimeUtils.btu_id},\#{$tempo\},beat#{x}", 0, '224.0.0.1', #{listen_port}
        sleep 0.5
        cue :up_beat
        $u1.send "#{Blockytalky.RuntimeUtils.btu_id},\#{$tempo\},up_beat", 0, '224.0.0.1', #{listen_port}
        sleep 0.5
        """
      end)
      true ->
        "#"
      end
    #return program:
    """
    use_debug false
    if $u1 != nil && !$u1.closed?
      $u1.close
    end
    if $u2 != nil && !$u2.closed?
      $u2.close
    end
    if $u3 != nil && !$u3.closed?
      $u3.close
    end
    $u1 = UDPSocket.new
    $u2 = UDPSocket.new
    $u2.bind("0.0.0.0", #{listen_port})
    # Main tempo cueing / UDP broadcasting thread
    live_loop :beat_pattern do
      use_bpm $tempo
      #{if parent != false, do: sync_to_network, else: beat_signaling}
    end

    $u3 = UDPSocket.new
    $u3.bind("127.0.0.1", #{eval_port})
    if $eval_thread != nil && $eval_thread.alive?
      $eval_thread.kill
    end
    $eval_thread = in_thread do
      loop do
        begin
          program, addr = $u3.recvfrom_nonblock(65655)
          eval(program)
          sleep 1.0 / 64.0
        rescue IO::WaitReadable
          sleep 1.0 / 64.0
          next
        end
      end
    end
    """
  end
  def start_motif(body_program)  do
    """
    $next_motif = define :next_motif do
      #{body_program}
    end
    if $current_motif == nil
      $current_motif = $next_motif
      $next_motif = nil
    end
    if $my_motif_thread == nil || $my_motif_thread.alive? == false
      $my_motif_thread = in_thread do
        until $current_motif == nil do
          use_bpm $tempo
          $current_motif.()
          $current_motif = $next_motif
          $next_motif = nil
        end
      end
    end
    """
  end
  def loop_motif(body_program) do
    """
    $next_motif = define :next_motif do
      #{body_program}
    end
    if $current_motif == nil
      $current_motif = $next_motif
      $next_motif = nil
    end
    if $my_motif_thread == nil || $my_motif_thread.alive? == false
      $my_motif_thread = in_thread do
        loop do
          use_bpm $tempo
          $current_motif.()
          if $next_motif != nil
            $current_motif = $next_motif
            $next_motif = nil
          end
        end
      end
    end
    """
  end
  def stop_motif() do
    """
    $my_motif_thread.kill
    $current_motif = nil
    $next_motif = nil
    """
  end
  def cue(cue_flag) do
    """
    cue #{cue_flag}
    """
  end
  ####
  #User-y functions, e.g. motif body pieces
  @doc """
  The SonicPi specific DSL code-string for playing a pitch.
  """
  def play_synth(pitches, duration) when is_list(pitches) do
    pitches
    |> Enum.map(fn pitch -> play_synth(pitch, duration) end)
    |> Enum.join("\n")
  end
  def play_synth(pitch, duration) do
    p = case pitch do
      n when is_integer(n) -> n
      ":" <> s -> pitch
      non_atom -> ":" <> non_atom
    end
    """
    use_bpm $tempo
    play #{p}, sustain: #{duration}, amp: $amp
    """
  end
  @doc """
  TODO: Make this sensitive to the beats per measure set by the user (when they get that option)
  """
  def sleep(duration), do: sleep(duration, :beats)
  def sleep(duration,units) do
    t = case units do
      :beats -> duration
      :measures -> duration * 4
    end
    t = t * 0.99
    """
    use_bpm $tempo
    sleep #{t}
    """
  end
  def trigger_sample(sample) do
    """
    use_bpm $tempo
    sample #{inspect sample}, amp: $amp
    """
  end
  def sync(flag) do
    "sync :#{flag}"
  end

end

defmodule Blockytalky.SonicPi do
  @moduledoc """
  This is a module completely of "functions-as-data" sonicpi programs.
  to achieve subdivisions sleep in % amounts relative to beats.
  e.g.
  to rest for 1/8th + 1/16th and then play a 16th note, it would look like:
    sync :beat1
    sleep 0.75
    play <pitch>
  """
  def listen_port, do:  Application.get_env(:blockytalky, :music_port, 9090)
  def eval_port, do: Application.get_env(:blockytalky, :music_eval_port, 5050)
  ####
  #System-y functions
  def public_cues do
    [:down_beat, :up_beat, :beat1, :beat2, :beat3, :beat4]
  end
  def init do
    set_amp(50) <> tempo(120) <> maestro_beat_pattern(false,4)
  end
  @doc """
  Takes a value between 0 and 100 and converts it to one between 0 and 5
  """
  def set_amp(value) do
    value = value / 20
    """
    $amp = #{value}
    """
  end
  def tempo(val) do
    """
    $tempo = #{val}
    """
  end
  @doc """
  takes the name of the btu to sync to or false as a first param
  takes beats per measure >= 1 as a second param.
  When syncing to a parent btu, it will listen for a first message,
  then it will keep track of time internally for one measure
  #the default sonic pi eval port is pretty slow on raspberry pi
  #this loop listens on a different port to speed it up
  """
  def maestro_beat_pattern(parent, beats_per_measure) do
    sync_to_network = case parent do
      false -> "#"
      name -> #Ruby code to listen until the parent sends a sync message
      """
      synced = false
      until synced  do
        begin
          synced = false
          msg = $u2.recvfrom(1024) # "["hostname,tempo",[..args..]]"
          msg_payload = msg[0].split(",")
          host = msg_payload[0]
          if(host == "#{name}")
            t = msg_payload[1]
            beat = msg_payload[2]
            $tempo = t.to_f
            use_bpm $tempo
            $u1.send "#{Blockytalky.RuntimeUtils.btu_id},\#{$tempo\},\#{beat\}", 0, '224.0.0.1', #{listen_port}
            cue beat.to_sym
            if beat != "up_beat"
              cue :down_beat
            end
            synced = true
          end
          sleep 1.0 / 128.0
        rescue
          sleep 1.0 / 64.0
          next
        end
      end
      """
    end
    beat_signaling = cond do
      beats_per_measure > 1 ->
      Enum.reduce(1..beats_per_measure,"",fn(x,acc) ->
        acc
        <>
        """
        cue :down_beat
        cue :beat#{x}
        $u1.send "#{Blockytalky.RuntimeUtils.btu_id},\#{$tempo\},beat#{x}", 0, '224.0.0.1', #{listen_port}
        sleep 0.5
        cue :up_beat
        $u1.send "#{Blockytalky.RuntimeUtils.btu_id},\#{$tempo\},up_beat", 0, '224.0.0.1', #{listen_port}
        sleep 0.5
        """
      end)
      true ->
        "#"
      end
    #return program:
    """
    use_debug false
    if $u1 != nil && !$u1.closed?
      $u1.close
    end
    if $u2 != nil && !$u2.closed?
      $u2.close
    end
    if $u3 != nil && !$u3.closed?
      $u3.close
    end
    $u1 = UDPSocket.new
    $u2 = UDPSocket.new
    $u2.bind("0.0.0.0", #{listen_port})
    # Main tempo cueing / UDP broadcasting thread
    live_loop :beat_pattern do
      use_bpm $tempo
      #{if parent != false, do: sync_to_network, else: beat_signaling}
    end

    $u3 = UDPSocket.new
    $u3.bind("127.0.0.1", #{eval_port})
    if $eval_thread != nil && $eval_thread.alive?
      $eval_thread.kill
    end
    $eval_thread = in_thread do
      loop do
        begin
          program, addr = $u3.recvfrom_nonblock(65655)
          eval(program)
          sleep 1.0 / 64.0
        rescue IO::WaitReadable
          sleep 1.0 / 64.0
          next
        end
      end
    end
    """
  end
  def start_motif(body_program)  do
    """
    $next_motif = define :next_motif do
      #{body_program}
    end
    if $current_motif == nil
      $current_motif = $next_motif
      $next_motif = nil
    end
    if $my_motif_thread == nil || $my_motif_thread.alive? == false
      $my_motif_thread = in_thread do
        until $current_motif == nil do
          use_bpm $tempo
          $current_motif.()
          $current_motif = $next_motif
          $next_motif = nil
        end
      end
    end
    """
  end
  def loop_motif(body_program) do
    """
    $next_motif = define :next_motif do
      #{body_program}
    end
    if $current_motif == nil
      $current_motif = $next_motif
      $next_motif = nil
    end
    if $my_motif_thread == nil || $my_motif_thread.alive? == false
      $my_motif_thread = in_thread do
        loop do
          use_bpm $tempo
          $current_motif.()
          if $next_motif != nil
            $current_motif = $next_motif
            $next_motif = nil
          end
        end
      end
    end
    """
  end
  def stop_motif() do
    """
    $my_motif_thread.kill
    $current_motif = nil
    $next_motif = nil
    """
  end
  def cue(cue_flag) do
    """
    cue #{cue_flag}
    """
  end
  ####
  #User-y functions, e.g. motif body pieces
  @doc """
  The SonicPi specific DSL code-string for playing a pitch.
  """
  def play_synth(pitches, duration) when is_list(pitches) do
    pitches
    |> Enum.map(fn pitch -> play_synth(pitch, duration) end)
    |> Enum.join("\n")
  end
  def play_synth(pitch, duration) do
    p = case pitch do
      n when is_integer(n) -> n
      ":" <> s -> pitch
      non_atom -> ":" <> non_atom
    end
    """
    use_bpm $tempo
    play #{p}, sustain: #{duration}, amp: $amp
    """
  end
  @doc """
  TODO: Make this sensitive to the beats per measure set by the user (when they get that option)
  """
  def sleep(duration), do: sleep(duration, :beats)
  def sleep(duration,units) do
    t = case units do
      :beats -> duration
      :measures -> duration * 4
    end
    t = t * 0.99
    """
    use_bpm $tempo
    sleep #{t}
    """
  end
  def trigger_sample(sample) do
    """
    use_bpm $tempo
    sample #{inspect sample}, amp: $amp
    """
  end
  def sync(flag) do
    "sync :#{flag}"
  end

end


cleanup

cleanup

defmodule WhistlerNewsReader.Fetcher do

  alias WhistlerNewsReader.Entry
  alias WhistlerNewsReader.Repo

  require Logger

  def refresh_all(feeds) do
    Enum.each(feeds, fn(feed) ->
      refresh(feed)
    end)
  end

  def refresh(feed) do
    Logger.info "Refresh feed id: #{feed.id}, title: #{feed.title}"

    {:ok, json_body}   = fetch(feed.feed_url)
    parsed_feed        = parse_feed(json_body)

    Enum.each(parsed_feed.entries, fn(entry) ->
      case store_entry(feed, entry) do
        {:ok, _new_entry} ->
          Logger.info "Refresh feed id: #{feed.id}, entry id: #{entry.id} success"
        {:skipping} ->
          Logger.info "Refresh feed id: #{feed.id}, entry id: #{entry.id} skip"
        {:error, changeset} ->
          Logger.info "Refresh feed id: #{feed.id}, entry id: #{entry.id} error: #{inspect changeset.errors}"
      end
    end)
  end

  def import_feed(feed_url) do
    case fetch(feed_url) do
      {:ok, body} ->
        {:ok, parse_feed(body)}
      {:error, reason} ->
        {:error, reason}
    end
  end

  defp parse_feed(xml_body) do
    ElixirFeedParser.parse(xml_body)
  end

  defp fetch(feed_url) do
    HTTPoison.start
    case HTTPoison.get(feed_url) do
    {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
      {:ok, body}
    {:ok, %HTTPoison.Response{status_code: 404}} ->
      {:error, :not_found}
    {:error, %HTTPoison.Error{reason: reason}} ->
      IO.inspect reason
      {:error, reason}
    end
  end

  defp store_entry(feed, entry) do
    published = entry[:updated] |> convert_to_ecto_date_time
    guid = generate_guid(feed.feed_url, entry[:id], published, entry[:title])
    text_content = Floki.text(entry[:content] || entry[:description])

    result = %{
      feed_id: feed.id,
      guid: guid,
      title: entry[:title],
      author: entry[:author],
      url: entry[:url],
      summary: entry[:summary] || String.slice(text_content, 0..255),
      content: entry[:content] || entry[:description],
      published: published
    }

    case Entry |> Entry.for_guid(guid) |> Repo.all do
      [] ->
        Repo.insert(Entry.changeset(%Entry{}, result))
      _ ->
        {:skipping}
    end
  end

  defp convert_to_ecto_date_time(date_time) do
    %Ecto.DateTime{
      year: date_time.year, month: date_time.month, day: date_time.day,
      hour: date_time.hour, min: date_time.min, sec: date_time.sec, usec: date_time.usec
    }
  end

  # generate unique guid since in RSS/Atom entries the guid ist not always unique
  defp generate_guid(feed_url, entry_guid, entry_published, entry_title) do
    unique_id_str = "#{feed_url}#{entry_guid}#{entry_published}#{entry_title}"
    :crypto.hash(:sha256, unique_id_str) |> Base.encode16
  end

end

defmodule WhistlerNewsReader.Fetcher do

  alias WhistlerNewsReader.Feed
  alias WhistlerNewsReader.Entry
  alias WhistlerNewsReader.Repo

  # def refresh_all do
  #   Enum.each(Repo.all(Feed), fn(feed) ->
  #     refresh(feed)
  #   end)
  # end

  def refresh_all(feeds) do
    Enum.each(feeds, fn(feed) ->
      refresh(feed)
    end)
  end

  def refresh(feed) do
    IO.puts "Refresh #{feed.title}"

    {:ok, json_body}   = fetch(feed.feed_url)
    parsed_feed        = parse_feed(json_body)

    Enum.each(parsed_feed.entries, fn(entry) ->
      case store_entry(feed, entry) do
        {:ok, _new_entry} ->
          IO.puts "Updated successfully"
        {:skipping} ->
          IO.puts "Skipping entry"
        {:error, changeset} ->
          IO.puts "Update failed: #{inspect changeset.errors}"
      end
    end)
  end

  # TODO: implement OPML import instead of fixed url list
  # def import_feeds() do
  #   urls = [
  #     "http://heise.de.feedsportal.com/c/35207/f/653902/index.rss",
  #     "http://www.theverge.com/rss/frontpage"
  #   ]
  #
  #   Repo.delete_all(Feed)
  #   Repo.delete_all(Entry)
  #
  #   Enum.each(urls, fn(feed_url) -> import_feed_with_content(feed_url) end)
  # end

  def import_feed(feed_url) do
    case fetch(feed_url) do
      {:ok, body} ->
        {:ok, parse_feed(body)}
      {:error, reason} ->
        {:error, reason}
    end
  end

  defp parse_feed(xml_body) do
    ElixirFeedParser.parse(xml_body)
  end

  defp fetch(feed_url) do
    HTTPoison.start
    case HTTPoison.get(feed_url) do
    {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
      {:ok, body}
    {:ok, %HTTPoison.Response{status_code: 404}} ->
      {:error, :not_found}
    {:error, %HTTPoison.Error{reason: reason}} ->
      IO.inspect reason
      {:error, reason}
    end
  end

  # TODO: store feed.last_build_date
  # defp store_feed(feed_url, feed_attrs) do
  #   changeset = Feed.changeset(%Feed{}, %{
  #     title: feed_attrs.title,
  #     feed_url: feed_url,
  #     site_url: feed_attrs.url
  #   })
  #   Repo.insert(changeset)
  # end

  # defp import_feed_with_content(feed_url) do
  #   IO.puts(feed_url)
  #
  #   {:ok, json_body}   = fetch(feed_url)
  #   feed               = parse_feed(json_body)
  #   {:ok, stored_feed} = store_feed(feed_url, feed)
  #
  #   Enum.each(feed.entries, fn(entry) -> store_entry(stored_feed, entry) end)
  # end

  defp store_entry(feed, entry) do
    published = entry[:updated] |> convert_to_ecto_date_time
    guid = generate_guid(feed.feed_url, entry[:id], published, entry[:title])
    text_content = Floki.text(entry[:content] || entry[:description])

    result = %{
      feed_id: feed.id,
      guid: guid,
      title: entry[:title],
      author: entry[:author],
      url: entry[:url],
      summary: entry[:summary] || String.slice(text_content, 0..255),
      content: entry[:content] || entry[:description],
      published: published
    }

    case Entry |> Entry.for_guid(guid) |> Repo.all do
      [] ->
        Repo.insert(Entry.changeset(%Entry{}, result))
      _ ->
        {:skipping}
    end
  end

  defp convert_to_ecto_date_time(date_time) do
    %Ecto.DateTime{
      year: date_time.year, month: date_time.month, day: date_time.day,
      hour: date_time.hour, min: date_time.min, sec: date_time.sec, usec: date_time.usec
    }
  end

  # generate unique guid since in RSS/Atom entries the guid ist not alwasy unique
  defp generate_guid(feed_url, entry_guid, entry_published, entry_title) do
    unique_id_str = "#{feed_url}#{entry_guid}#{entry_published}#{entry_title}"
    :crypto.hash(:sha256, unique_id_str) |> Base.encode16
  end

end