cowboy/elixir: Get rid of intermediate debug output once again.

#
# src/elixir/lib/dnsresolvd.ex
# =============================================================================
# DNS Resolver Daemon (dnsresolvd). Version 0.1
# =============================================================================
# A daemon that performs DNS lookups for the given hostname
# passed in an HTTP request, with the focus on its implementation
# using various programming languages. (Cowboy-boosted impl.)
# =============================================================================
# Copyright (C) 2017-2018 Radislav (Radicchio) Golubtsov
#
# (See the LICENSE file at the top of the source tree.)
#

defmodule DnsResolvd do
    @moduledoc "The main Application module of the daemon."

    use Application

    @doc """
    Starts up the daemon.

    It has to be the application module callback, but used directly
    from the startup script of the daemon.

    **Args:**

        `args`: A list containing the server port number to listen on
                as the first element.

    **Returns:**

        The tuple containing the PID of the top supervisor process
        and the state of the running application (or an empty list).
    """
    @impl (true)
    def start(_, args) do
        [port_number | _] = args

        # Using this call instead of passing "--app dnsresolvd"
        # to the Elixir interpreter. See the file "dnsresolvd.app"
        # for explanation.
        {:ok, _} = Application.ensure_all_started(:cowboy)

        dispatch = :cowboy_router.compile([
            {:_, [
                {'/', ReqHandler, []}
            ]}
        ])

        # Starting up the plain HTTP listener on <port_number>.
        {:ok, _} = :cowboy.start_clear(:http_listener, [
            port: port_number
        ], %{
            env: %{:dispatch => dispatch}
        })

        # Starting up the daemon's provided Supervisor (optional).
        DnsResolvs.start_link()

        # Trapping exit signals, i.e. transforming them into {:EXIT} message.
        Process.flag(:trap_exit, true)

        # Inspecting the daemon's Application process message queue
        # for the incoming {:EXIT} message, i.e. doing same things
        # as the Process.sleep(:infinity) inactive call below
        # until the message received.
        receive do
            {:EXIT, from, reason} ->
                IO.inspect(from)
                IO.inspect(reason)
        end

        # --- Use message queue (as above) instead of sleep() call below ------
        # Using this call instead of passing "--no-halt"
        # to the Elixir interpreter. See the file "dnsresolvd.app"
        # for explanation.
        # Process.sleep(:infinity)
        # ---------------------------------------------------------------------
    end
end

defmodule ReqHandler do
    @moduledoc "The default HTTP request handler."

    @doc """
    The request handler `init/2` callback.

    Gets called when a new incoming HTTP request is received.

    **Args:**

        `req`:   The incoming HTTP request object.
        `state`: The initial state of the HTTP handler.

    **Returns:**

        The tuple containing the HTTP response to be rendered
        and a new state of the HTTP handler.
    """
    def init(req, state) do
        mtd = :cowboy_req.method(req)

        # ---------------------------------------------------------------------
        # --- Parsing and validating request params - Begin -------------------
        # ---------------------------------------------------------------------
        {:ok, params, req} = cond do
            (mtd === AUX._MTD_HTTP_GET ) ->
                {:ok, :cowboy_req.parse_qs(req), req}
            (mtd === AUX._MTD_HTTP_POST) ->
                :cowboy_req.read_urlencoded_body(req)
        end

        hostname = for (param <- params) do
            {k, v} = param; if (k === "h"), do: v # <---+-----------------+
        end #     +----GET----+-----+-----+             |                 |
        #         |     |     |     |     |       +-----+      +----------+-+
        #         v     v     v     v     v       |            |          | |
        # $ curl 'http://localhost:<port_number>/?h=<hostname>&f=<fmt>'   | |
        # $                                                               | |
        # $ curl -d 'h=<hostname>&f=<fmt>' http://localhost:<port_number> | |
        #         ^  |            |                                       | |
        #         |  +------------+---------------------------------------+ |
        #         |               |                                         |
        # POST----+               +---------------------+                   |
        #                                               |                   |
        fmt      = for (param <- params) do #           |                   |
            {k, v} = param; if (k === "f"), do: v # <---+-------------------+
        end

        hostname = hostname |> Enum.filter(fn(v) -> (v !== nil) end)
        hostname = hostname |> Enum.at(length(hostname) - 1)

        fmt      = fmt      |> Enum.filter(fn(v) -> (v !== nil) end)
        fmt      = fmt      |> Enum.at(length(fmt     ) - 1)

        hostname = if ((              hostname  === nil )
                   or  (              hostname  === true)
                   or  (String.length(hostname) === 0   )) do
            AUX._DEF_HOSTNAME
        else
            hostname
        end

        fmt      = if ((              fmt       === nil )
                   or  (              fmt       === true)
                   or  (String.length(fmt     ) === 0   )) do
            AUX._PRM_FMT_JSON
        else
            String.downcase(fmt, :ascii)
        end

        # This if-construct is almost identically ported
        # from its Pythonic equivalent ! :-)
        fmt = if (fmt not in [
            AUX._PRM_FMT_HTML,
            AUX._PRM_FMT_JSON,
        ]) do
            AUX._PRM_FMT_JSON
        else
            fmt
        end
        # ---------------------------------------------------------------------
        # --- Parsing and validating request params - End ---------------------
        # ---------------------------------------------------------------------

        # Performing DNS lookup for the given hostname.
        addr_ver = dns_lookup(hostname)

        addr = elem(addr_ver, 0)
        ver  = elem(addr_ver, 1)

        resp_buffer = if (fmt === AUX._PRM_FMT_HTML) do
            "<!DOCTYPE html>"                                                                <> AUX._NEW_LINE
<> ""                                                       <> AUX._NEW_LINE
<> ""                                                                                <> AUX._NEW_LINE
<> "<meta http-equiv=\""      <> AUX._HDR_CONTENT_TYPE_N      <>          "\"    content=\""
                              <> AUX._HDR_CONTENT_TYPE_V_HTML <>          "\"           />"  <> AUX._NEW_LINE
<> "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"                            />"  <> AUX._NEW_LINE
<> "<meta       name=\"viewport\"        content=\"width=device-width,initial-scale=1\" />"  <> AUX._NEW_LINE
<> "<title>" <> AUX._DMN_NAME <> "</title>"                                                     <> AUX._NEW_LINE
<> ""                                                                                <> AUX._NEW_LINE
<> ""                                                                                <> AUX._NEW_LINE
<> "
"   <> hostname      <> AUX._ONE_SPACE_STRING
        else          if (fmt === AUX._PRM_FMT_JSON) do
               AUX._CB1
            <> AUX._DAT_HOSTNAME_N
            <> AUX._DQ1
            <> hostname
            <> AUX._DQ2
        end
            # --- Block-separator-prettifier ---
        end

        # If lookup error occurred.
        resp_buffer = if (addr === AUX._ERR_PREFIX) do
                 if (fmt === AUX._PRM_FMT_HTML) do
                resp_buffer <> AUX._ERR_PREFIX
                            <> AUX._COLON_SPACE_SEP
                            <> AUX._ERR_COULD_NOT_LOOKUP
            else if (fmt === AUX._PRM_FMT_JSON) do
                resp_buffer <> AUX._ERR_PREFIX
                            <> AUX._DQ1
                            <> AUX._ERR_COULD_NOT_LOOKUP
            end
                # --- Block-separator-prettifier ---
            end
        else
                 if (fmt === AUX._PRM_FMT_HTML) do
                resp_buffer <> to_string(addr)
                            <> AUX._ONE_SPACE_STRING
                            <> AUX._DAT_VERSION_V
                            <> to_string(ver )
            else if (fmt === AUX._PRM_FMT_JSON) do
                resp_buffer <> AUX._DAT_ADDRESS_N
                            <> AUX._DQ1
                            <> to_string(addr)
                            <> AUX._DQ2
                            <> AUX._DAT_VERSION_N
                            <> AUX._DQ1
                            <> AUX._DAT_VERSION_V
                            <> to_string(ver )
            end
                # --- Block-separator-prettifier ---
            end
        end

        resp_buffer = if (fmt === AUX._PRM_FMT_HTML) do
            resp_buffer <> "
"  <> AUX._NEW_LINE
                        <> "" <> AUX._NEW_LINE
                        <> "" <> AUX._NEW_LINE
        else          if (fmt === AUX._PRM_FMT_JSON) do
            resp_buffer <> AUX._CB2
        end
            # --- Block-separator-prettifier ---
        end

        # Adding headers to the response.
        req = AUX._add_response_headers(fmt, req)

        :cowboy_req.reply(AUX._RSC_HTTP_200_OK, req)

        {:ok,
            req,  # <== For the moment the response is the same as the request.
            state # <== The state of the handler doesn't need to be changed.
        }
    end

    ###
    # Performs DNS lookup action for the given hostname,
    # i.e. (in this case) IP address retrieval by hostname.
    #
    # Args:
    #     hostname: The effective hostname to look up for.
    #
    # Returns:
    #     The tuple containing IP address of the analyzing host/service
    #     and corresponding IP version (family) used to look up in DNS:
    #     "4" for IPv4-only hosts, "6" for IPv6-capable hosts.
    #
    defp dns_lookup(hostname) do
        hostent4 = :inet.gethostbyname(String.to_charlist(hostname), :inet )

        # If the host doesn't have an A record (IPv4),
        # trying to find its AAAA record (IPv6).
        hostent6 = if (elem(hostent4, 0) === :error) do
                   :inet.gethostbyname(String.to_charlist(hostname), :inet6)
        end

             if (elem(hostent4, 0) === :ok) do
            {
                elem(hostent4, 1)
                    |> elem(5)
                    |> hd()
                    |> :inet.ntoa(),
                4
            }
        else if (elem(hostent6, 0) === :ok) do
            {
                elem(hostent6, 1)
                    |> elem(5)
                    |> hd()
                    |> :inet.ntoa(),
                6
            }
        else
            {AUX._ERR_PREFIX, nil}
        end
            # --- Block-separator-prettifier ---
        end
    end
end

defmodule DnsResolvs do
    @moduledoc "The main Supervisor module of the daemon."

    use Supervisor

    @doc """
    Supervisor startup helper.

    Used to directly start up the `DnsResolvs` supervisor process.

    **Returns:**

        The PID of the `DnsResolvs` supervisor process.
    """
    def start_link() do
        Supervisor.start_link(__MODULE__, [])
    end

    @doc """
    Supervisor `init/1` callback.

    Gets called when the `DnsResolvs` supervisor is about to be started up.

    **Returns:**

        The tuple containing supervisor flags
        and child processes' specifications.
    """
    @impl (true)
    def init(_) do
        child_procs = [] # <== No child processes do we need.

        Supervisor.init(child_procs, strategy: :one_for_one)
    end
end

# vim:set nu ts=4 sw=4:

#
# src/elixir/lib/dnsresolvd.ex
# =============================================================================
# DNS Resolver Daemon (dnsresolvd). Version 0.1
# =============================================================================
# A daemon that performs DNS lookups for the given hostname
# passed in an HTTP request, with the focus on its implementation
# using various programming languages. (Cowboy-boosted impl.)
# =============================================================================
# Copyright (C) 2017-2018 Radislav (Radicchio) Golubtsov
#
# (See the LICENSE file at the top of the source tree.)
#

defmodule DnsResolvd do
    @moduledoc "The main Application module of the daemon."

    use Application

    @doc """
    Starts up the daemon.

    It has to be the application module callback, but used directly
    from the startup script of the daemon.

    **Args:**

        `args`: A list containing the server port number to listen on
                as the first element.

    **Returns:**

        The tuple containing the PID of the top supervisor process
        and the state of the running application (or an empty list).
    """
    @impl (true)
    def start(_, args) do
        [port_number | _] = args

        # Using this call instead of passing "--app dnsresolvd"
        # to the Elixir interpreter. See the file "dnsresolvd.app"
        # for explanation.
        {:ok, _} = Application.ensure_all_started(:cowboy)

        dispatch = :cowboy_router.compile([
            {:_, [
                {'/', ReqHandler, []}
            ]}
        ])

        # Starting up the plain HTTP listener on <port_number>.
        {:ok, _} = :cowboy.start_clear(:http_listener, [
            port: port_number
        ], %{
            env: %{:dispatch => dispatch}
        })

        # Starting up the daemon's provided Supervisor (optional).
        DnsResolvs.start_link()

        # Trapping exit signals, i.e. transforming them into {:EXIT} message.
        Process.flag(:trap_exit, true)

        # Inspecting the daemon's Application process message queue
        # for the incoming {:EXIT} message, i.e. doing same things
        # as the Process.sleep(:infinity) inactive call below
        # until the message received.
        receive do
            {:EXIT, from, reason} ->
                IO.inspect(from)
                IO.inspect(reason)
        end

        # --- Use message queue (as above) instead of sleep() call below ------
        # Using this call instead of passing "--no-halt"
        # to the Elixir interpreter. See the file "dnsresolvd.app"
        # for explanation.
        # Process.sleep(:infinity)
        # ---------------------------------------------------------------------
    end
end

defmodule ReqHandler do
    @moduledoc "The default HTTP request handler."

    @doc """
    The request handler `init/2` callback.

    Gets called when a new incoming HTTP request is received.

    **Args:**

        `req`:   The incoming HTTP request object.
        `state`: The initial state of the HTTP handler.

    **Returns:**

        The tuple containing the HTTP response to be rendered
        and a new state of the HTTP handler.
    """
    def init(req, state) do
        mtd = :cowboy_req.method(req)

        # ---------------------------------------------------------------------
        # --- Parsing and validating request params - Begin -------------------
        # ---------------------------------------------------------------------
        {:ok, params, req} = cond do
            (mtd === AUX._MTD_HTTP_GET ) ->
                {:ok, :cowboy_req.parse_qs(req), req}
            (mtd === AUX._MTD_HTTP_POST) ->
                :cowboy_req.read_urlencoded_body(req)
        end

        hostname = for (param <- params) do
            {k, v} = param; if (k === "h"), do: v # <---+-----------------+
        end #     +----GET----+-----+-----+             |                 |
        #         |     |     |     |     |       +-----+      +----------+-+
        #         v     v     v     v     v       |            |          | |
        # $ curl 'http://localhost:<port_number>/?h=<hostname>&f=<fmt>'   | |
        # $                                                               | |
        # $ curl -d 'h=<hostname>&f=<fmt>' http://localhost:<port_number> | |
        #         ^  |            |                                       | |
        #         |  +------------+---------------------------------------+ |
        #         |               |                                         |
        # POST----+               +---------------------+                   |
        #                                               |                   |
        fmt      = for (param <- params) do #           |                   |
            {k, v} = param; if (k === "f"), do: v # <---+-------------------+
        end

        hostname = hostname |> Enum.filter(fn(v) -> (v !== nil) end)
        hostname = hostname |> Enum.at(length(hostname) - 1)

        fmt      = fmt      |> Enum.filter(fn(v) -> (v !== nil) end)
        fmt      = fmt      |> Enum.at(length(fmt     ) - 1)

        hostname = if ((              hostname  === nil )
                   or  (              hostname  === true)
                   or  (String.length(hostname) === 0   )) do
            AUX._DEF_HOSTNAME
        else
            hostname
        end

        fmt      = if ((              fmt       === nil )
                   or  (              fmt       === true)
                   or  (String.length(fmt     ) === 0   )) do
            AUX._PRM_FMT_JSON
        else
            String.downcase(fmt, :ascii)
        end

        # This if-construct is almost identically ported
        # from its Pythonic equivalent ! :-)
        fmt = if (fmt not in [
            AUX._PRM_FMT_HTML,
            AUX._PRM_FMT_JSON,
        ]) do
            AUX._PRM_FMT_JSON
        else
            fmt
        end
        # ---------------------------------------------------------------------
        # --- Parsing and validating request params - End ---------------------
        # ---------------------------------------------------------------------

        # Performing DNS lookup for the given hostname.
        addr_ver = dns_lookup(hostname)

        addr = elem(addr_ver, 0)
        ver  = elem(addr_ver, 1)

        IO.puts(addr)
        IO.puts(ver )
        IO.puts(fmt )

        resp_buffer = if (fmt === AUX._PRM_FMT_HTML) do
            "<!DOCTYPE html>"                                                                <> AUX._NEW_LINE
<> ""                                                       <> AUX._NEW_LINE
<> ""                                                                                <> AUX._NEW_LINE
<> "<meta http-equiv=\""      <> AUX._HDR_CONTENT_TYPE_N      <>          "\"    content=\""
                              <> AUX._HDR_CONTENT_TYPE_V_HTML <>          "\"           />"  <> AUX._NEW_LINE
<> "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"                            />"  <> AUX._NEW_LINE
<> "<meta       name=\"viewport\"        content=\"width=device-width,initial-scale=1\" />"  <> AUX._NEW_LINE
<> "<title>" <> AUX._DMN_NAME <> "</title>"                                                     <> AUX._NEW_LINE
<> ""                                                                                <> AUX._NEW_LINE
<> ""                                                                                <> AUX._NEW_LINE
<> "
"   <> hostname      <> AUX._ONE_SPACE_STRING
        else          if (fmt === AUX._PRM_FMT_JSON) do
               AUX._CB1
            <> AUX._DAT_HOSTNAME_N
            <> AUX._DQ1
            <> hostname
            <> AUX._DQ2
        end
            # --- Block-separator-prettifier ---
        end

        # If lookup error occurred.
        resp_buffer = if (addr === AUX._ERR_PREFIX) do
                 if (fmt === AUX._PRM_FMT_HTML) do
                resp_buffer <> AUX._ERR_PREFIX
                            <> AUX._COLON_SPACE_SEP
                            <> AUX._ERR_COULD_NOT_LOOKUP
            else if (fmt === AUX._PRM_FMT_JSON) do
                resp_buffer <> AUX._ERR_PREFIX
                            <> AUX._DQ1
                            <> AUX._ERR_COULD_NOT_LOOKUP
            end
                # --- Block-separator-prettifier ---
            end
        else
                 if (fmt === AUX._PRM_FMT_HTML) do
                resp_buffer <> to_string(addr)
                            <> AUX._ONE_SPACE_STRING
                            <> AUX._DAT_VERSION_V
                            <> to_string(ver )
            else if (fmt === AUX._PRM_FMT_JSON) do
                resp_buffer <> AUX._DAT_ADDRESS_N
                            <> AUX._DQ1
                            <> to_string(addr)
                            <> AUX._DQ2
                            <> AUX._DAT_VERSION_N
                            <> AUX._DQ1
                            <> AUX._DAT_VERSION_V
                            <> to_string(ver )
            end
                # --- Block-separator-prettifier ---
            end
        end

        resp_buffer = if (fmt === AUX._PRM_FMT_HTML) do
            resp_buffer <> "
"  <> AUX._NEW_LINE
                        <> "" <> AUX._NEW_LINE
                        <> "" <> AUX._NEW_LINE
        else          if (fmt === AUX._PRM_FMT_JSON) do
            resp_buffer <> AUX._CB2
        end
            # --- Block-separator-prettifier ---
        end

        IO.puts(resp_buffer)

        # Adding headers to the response.
        req = AUX._add_response_headers(fmt, req)

        :cowboy_req.reply(AUX._RSC_HTTP_200_OK, req)

        {:ok,
            req,  # <== For the moment the response is the same as the request.
            state # <== The state of the handler doesn't need to be changed.
        }
    end

    ###
    # Performs DNS lookup action for the given hostname,
    # i.e. (in this case) IP address retrieval by hostname.
    #
    # Args:
    #     hostname: The effective hostname to look up for.
    #
    # Returns:
    #     The tuple containing IP address of the analyzing host/service
    #     and corresponding IP version (family) used to look up in DNS:
    #     "4" for IPv4-only hosts, "6" for IPv6-capable hosts.
    #
    defp dns_lookup(hostname) do
        hostent4 = :inet.gethostbyname(String.to_charlist(hostname), :inet )

        # If the host doesn't have an A record (IPv4),
        # trying to find its AAAA record (IPv6).
        hostent6 = if (elem(hostent4, 0) === :error) do
                   :inet.gethostbyname(String.to_charlist(hostname), :inet6)
        end

             if (elem(hostent4, 0) === :ok) do
            {
                elem(hostent4, 1)
                    |> elem(5)
                    |> hd()
                    |> :inet.ntoa(),
                4
            }
        else if (elem(hostent6, 0) === :ok) do
            {
                elem(hostent6, 1)
                    |> elem(5)
                    |> hd()
                    |> :inet.ntoa(),
                6
            }
        else
            {AUX._ERR_PREFIX, nil}
        end
            # --- Block-separator-prettifier ---
        end
    end
end

defmodule DnsResolvs do
    @moduledoc "The main Supervisor module of the daemon."

    use Supervisor

    @doc """
    Supervisor startup helper.

    Used to directly start up the `DnsResolvs` supervisor process.

    **Returns:**

        The PID of the `DnsResolvs` supervisor process.
    """
    def start_link() do
        Supervisor.start_link(__MODULE__, [])
    end

    @doc """
    Supervisor `init/1` callback.

    Gets called when the `DnsResolvs` supervisor is about to be started up.

    **Returns:**

        The tuple containing supervisor flags
        and child processes' specifications.
    """
    @impl (true)
    def init(_) do
        child_procs = [] # <== No child processes do we need.

        Supervisor.init(child_procs, strategy: :one_for_one)
    end
end

# vim:set nu ts=4 sw=4:


Print details of the assertion error (#60)

defprotocol WhiteBread.Formatter.FailedStep do
  def text(failure_type, failing_step, failure_data)
end

defimpl WhiteBread.Formatter.FailedStep, for: Atom do
  alias WhiteBread.CodeGenerator
  alias WhiteBread.Outputers.Style

  def text(:missing_step, step, _error) do
    %{text: step_text} = step
    code_to_implement = CodeGenerator.Step.regex_code_for_step(step)
    Style.info "undefined step: #{step_text}"
    <> " implement with\n\n" <> code_to_implement
  end

  def text(:no_clause_match, step, error) do
    %{text: step_text} = step
    {_clause_match_error, stacktrace} = error
    trace_message = Exception.format_stacktrace(stacktrace)
    Style.failed "unable to match clauses: #{step_text}:\n" <>
    "trace:\n#{trace_message}"
  end

  def text(:other_failure, step, {other_failure, stacktrace}) do
    %{text: step_text} = step
    trace_message = Exception.format_stacktrace(stacktrace)
    "execution failure: #{step_text}:\n" <>
    Style.exception "Exception: #{Exception.message other_failure}: \n" <>
    trace_message
  end
end

defimpl WhiteBread.Formatter.FailedStep,
for: [ESpec.AssertionError, ExUnit.AssertionError] do
  def text(_, step, assertion_failure) do
    print_assertion_failure assertion_failure
    %{text: step_text} = step
    %{message: assestion_message} = assertion_failure
    "#{step_text}: #{assestion_message}"
  end

  defp print_assertion_failure(assertion_failure) do
    msg = ExUnit.AssertionError.message(assertion_failure)
    formatted = [:red | msg]
                |> IO.ANSI.format(true)
                |> IO.iodata_to_binary
    IO.puts formatted
  end
end

defprotocol WhiteBread.Formatter.FailedStep do
  def text(failure_type, failing_step, failure_data)
end

defimpl WhiteBread.Formatter.FailedStep, for: Atom do
  alias WhiteBread.CodeGenerator
  alias WhiteBread.Outputers.Style

  def text(:missing_step, step, _error) do
    %{text: step_text} = step
    code_to_implement = CodeGenerator.Step.regex_code_for_step(step)
    Style.info "undefined step: #{step_text}"
    <> " implement with\n\n" <> code_to_implement
  end

  def text(:no_clause_match, step, error) do
    %{text: step_text} = step
    {_clause_match_error, stacktrace} = error
    trace_message = Exception.format_stacktrace(stacktrace)
    Style.failed "unable to match clauses: #{step_text}:\n" <>
    "trace:\n#{trace_message}"
  end

  def text(:other_failure, step, {other_failure, stacktrace}) do
    %{text: step_text} = step
    trace_message = Exception.format_stacktrace(stacktrace)
    "execution failure: #{step_text}:\n" <>
    Style.exception "Exception: #{Exception.message other_failure}: \n" <>
    trace_message
  end
end

defimpl WhiteBread.Formatter.FailedStep,
for: [ESpec.AssertionError, ExUnit.AssertionError] do
  def text(_, step, assertion_failure) do
    %{text: step_text} = step
    %{message: assestion_message} = assertion_failure
    "#{step_text}: #{assestion_message}"
  end
end


log requests (sort of)

log requests (sort of)

defmodule Yodado.Web.FeatureStatusHandler do
  def init(_transport, _req, _opts) do
    {:upgrade, :protocol, :cowboy_rest}
  end

  def rest_init(req, _opts) do
    {:ok, req, []}
  end

  def allowed_methods(req, state) do
    {["GET"], req, state}
  end

  def service_available(req, state) do
    persistence_ok = Yodado.FeaturePersistence.ping
    {persistence_ok, req, state}
  end

  def resource_exists(req, state) do
    {feature_id, req} = :cowboy_req.binding(:feature_id, req)
    state = Keyword.put(state, :feature_id, feature_id)
    case Yodado.FeaturePersistence.find_feature(feature_id) do
      :not_found ->     {false, req, state}
      {:ok, feature} -> state = Keyword.put(state, :feature, feature)
                        {true, req, state}
    end
  end

  def content_types_provided(req, state) do
    providers = [
      {"*", :render_feature_state}
    ]

    {providers, req, state}
  end

  def render_feature_state(req, state) do
    {params, req} = :cowboy_req.qs_vals(req)

    req = :cowboy_req.set_resp_header("content-type", "application/json; charset=utf-8", req)

    {:ok, result} = Yodado.Feature.do?(state[:feature], params)

    IO.puts "feature_id=#{state[:feature_id]} params=#{params |> inspect} result=#{result}"

    body = [state: result] |> JSEX.encode!

    {body, req, state}
  end

end

defmodule Yodado.Web.FeatureStatusHandler do
  def init(_transport, _req, _opts) do
    {:upgrade, :protocol, :cowboy_rest}
  end

  def rest_init(req, _opts) do
    {:ok, req, []}
  end

  def allowed_methods(req, state) do
    {["GET"], req, state}
  end

  def service_available(req, state) do
    persistence_ok = Yodado.FeaturePersistence.ping
    {persistence_ok, req, state}
  end

  def resource_exists(req, state) do
    {feature_id, req} = :cowboy_req.binding(:feature_id, req)
    case Yodado.FeaturePersistence.find_feature(feature_id) do
      :not_found ->     {false, req, state}
      {:ok, feature} -> state = Keyword.put(state, :feature, feature)
                        {true, req, state}
    end
  end

  def content_types_provided(req, state) do
    providers = [
      {"*", :render_feature_state}
    ]

    {providers, req, state}
  end

  def render_feature_state(req, state) do
    {params, req} = :cowboy_req.qs_vals(req)

    req = :cowboy_req.set_resp_header("content-type", "application/json; charset=utf-8", req)

    {:ok, result} = Yodado.Feature.do?(state[:feature], params)
    body = [state: result] |> JSEX.encode!

    {body, req, state}
  end

end


Run tests for generated app

Run tests for generated app

Code.require_file "../../mix_helper.exs", __DIR__

defmodule Mix.Tasks.Phoenix.NewTest do
  # This test case needs to be sync because we rely on
  # changing the current working directory which is global.
  use ExUnit.Case
  use Plug.Test

  import MixHelper
  import ExUnit.CaptureIO

  @app_name  "photo_blog"
  @tmp_path  tmp_path()
  @project_path Path.join(@tmp_path, @app_name)
  @epoch {{1970, 1, 1}, {0, 0, 0}}

  setup_all do
    # Clean up and create a new project
    File.rm_rf(@project_path)
    Mix.Tasks.Phoenix.New.run(["--dev", @app_name, @project_path])

    # Copy artifacts from Phoenix so we can compile and run tests
    File.cp_r "_build",   Path.join(@project_path, "_build")
    File.cp_r "deps",     Path.join(@project_path, "deps")
    File.cp_r "mix.lock", Path.join(@project_path, "mix.lock")

    :ok
  end

  test "creates files and directories" do
    File.cd! @project_path, fn ->
      assert_file ".gitignore"
      assert_file "README.md"
      assert_file "lib/photo_blog.ex", ~r/defmodule PhotoBlog do/
      assert_file "lib/photo_blog/endpoint.ex", ~r/defmodule PhotoBlog.Endpoint do/

      assert_file "priv/static/css/phoenix.css"
      assert_file "priv/static/images/phoenix.png"
      assert_file "priv/static/js/phoenix.js"

      assert_file "test/photo_blog_test.exs"
      assert_file "test/test_helper.exs"

      assert_file "web/controllers/page_controller.ex", ~r/defmodule PhotoBlog.PageController/
      assert_file "web/router.ex", ~r/defmodule PhotoBlog.Router/
    end
  end

  test "compiles and recompiles project" do
    Logger.disable(self())
    Application.put_env(:phoenix, :code_reloader, true)

    Application.put_env(:phoenix, PhotoBlog.Endpoint,
      secret_key_base: String.duplicate("abcdefgh", 8))

    in_project :photo_blog, @project_path, fn _ ->
      Mix.Task.run "compile", ["--no-deps-check"]
      assert_received {:mix_shell, :info, ["Compiled lib/photo_blog.ex"]}
      assert_received {:mix_shell, :info, ["Compiled web/router.ex"]}
      Mix.shell.flush
      Mix.Task.clear

      # Adding a new template touches file (through mix)
      File.touch! "web/views/layout_view.ex", @epoch
      File.write! "web/templates/layout/another.html.eex", "oops"
      Mix.Task.run "compile", ["--no-deps-check"]
      assert File.stat!("web/views/layout_view.ex").mtime > @epoch

      # Adding a new template triggers recompilation (through request)
      File.touch! "web/views/page_view.ex", @epoch
      File.write! "web/templates/page/another.html.eex", "oops"

      PhotoBlog.Endpoint.start()

      try do
        PhotoBlog.Endpoint.call(conn(:get, "/"), [])
      after
        PhotoBlog.Endpoint.stop()
      end

      assert File.stat!("web/views/page_view.ex").mtime > @epoch

      assert capture_io(fn ->
        Mix.Task.run("test")
      end) =~ "1 tests, 0 failures"
    end
  after
    Application.put_env(:phoenix, :code_reloader, false)
  end

  test "missing name and/or path arguments" do
    assert_raise Mix.Error, fn ->
      Mix.Tasks.Phoenix.New.run([])
    end
  end

  defp in_project(app, path, fun) do
    %{name: name, file: file} = Mix.Project.pop

    try do
      capture_io :stderr, fn ->
        Mix.Project.in_project app, path, [], fun
      end
    after
      Mix.Project.push name, file
    end
  end
end

Code.require_file "../../mix_helper.exs", __DIR__

defmodule Mix.Tasks.Phoenix.NewTest do
  # This test case needs to be sync because we rely on
  # changing the current working directory which is global.
  use ExUnit.Case
  use Plug.Test

  import MixHelper
  import ExUnit.CaptureIO

  @app_name  "photo_blog"
  @tmp_path  tmp_path()
  @project_path Path.join(@tmp_path, @app_name)
  @epoch {{1970, 1, 1}, {0, 0, 0}}

  setup_all do
    # Clean up and create a new project
    File.rm_rf(@project_path)
    Mix.Tasks.Phoenix.New.run(["--dev", @app_name, @project_path])

    # Copy artifacts from Phoenix so we can compile and run tests
    File.cp_r "_build",   Path.join(@project_path, "_build")
    File.cp_r "deps",     Path.join(@project_path, "deps")
    File.cp_r "mix.lock", Path.join(@project_path, "mix.lock")

    :ok
  end

  test "creates files and directories" do
    File.cd! @project_path, fn ->
      assert_file ".gitignore"
      assert_file "README.md"
      assert_file "lib/photo_blog.ex", ~r/defmodule PhotoBlog do/
      assert_file "lib/photo_blog/endpoint.ex", ~r/defmodule PhotoBlog.Endpoint do/

      assert_file "priv/static/css/phoenix.css"
      assert_file "priv/static/images/phoenix.png"
      assert_file "priv/static/js/phoenix.js"

      assert_file "test/photo_blog_test.exs"
      assert_file "test/test_helper.exs"

      assert_file "web/controllers/page_controller.ex", ~r/defmodule PhotoBlog.PageController/
      assert_file "web/router.ex", ~r/defmodule PhotoBlog.Router/
    end
  end

  test "compiles and recompiles project" do
    Logger.disable(self())
    Application.put_env(:phoenix, :code_reloader, true)

    Application.put_env(:phoenix, PhotoBlog.Endpoint,
      secret_key_base: String.duplicate("abcdefgh", 8))

    in_project :photo_blog, @project_path, fn _ ->
      Mix.Task.run "compile", ["--no-deps-check"]
      assert_received {:mix_shell, :info, ["Compiled lib/photo_blog.ex"]}
      assert_received {:mix_shell, :info, ["Compiled web/router.ex"]}
      Mix.shell.flush
      Mix.Task.clear

      # Adding a new template touches file (through mix)
      File.touch! "web/views/layout_view.ex", @epoch
      File.write! "web/templates/layout/another.html.eex", "oops"
      Mix.Task.run "compile", ["--no-deps-check"]
      assert File.stat!("web/views/layout_view.ex").mtime > @epoch

      # Adding a new template triggers recompilation (through request)
      File.touch! "web/views/page_view.ex", @epoch
      File.write! "web/templates/page/another.html.eex", "oops"

      PhotoBlog.Endpoint.start()

      try do
        PhotoBlog.Endpoint.call(conn(:get, "/"), [])
      after
        PhotoBlog.Endpoint.stop()
      end

      assert File.stat!("web/views/page_view.ex").mtime > @epoch
    end
  after
    Application.put_env(:phoenix, :code_reloader, false)
  end

  test "missing name and/or path arguments" do
    assert_raise Mix.Error, fn ->
      Mix.Tasks.Phoenix.New.run([])
    end
  end

  defp in_project(app, path, fun) do
    %{name: name, file: file} = Mix.Project.pop

    try do
      capture_io :stderr, fn ->
        Mix.Project.in_project app, path, [], fun
      end
    after
      Mix.Project.push name, file
    end
  end
end


More thoroughly test HTTPotion methods in Api

defmodule ExTwilio.ApiTest do
  use ExUnit.Case, async: false
  alias ExTwilio.Api
  alias ExTwilio.Config
  import Mock

  defmodule Resource do
    defstruct sid: nil, name: nil
  end

  doctest ExTwilio.Api

  test ".stream should automatically page through resources, yielding each resource" do
    page1 = %{
      "api_test/resources" => [
        %{sid: "1", name: "first"},
      ],
      next_page_uri: "?page=2"
    }

    page2 = %{
      "api_test/resources" => [
        %{sid: "2", name: "second"},
      ],
      next_page_uri: nil
    }

    with_fixture {:get, fn "ApiTest.Resources.json?page=2" -> json_response(page2, 200)
                           _url -> json_response(page1, 200)
                        end}, fn ->
      expected = [%Resource{sid: "1", name: "first"}, %Resource{sid: "2", name: "second"}]
      assert expected == Enum.into Api.stream(Resource), []
    end
  end

  test ".list should fetch the first page of results" do
    with_list_fixture fn(expected) ->
      assert expected == Api.list(Resource)
    end
  end

  test ".fetch_page should fetch a page of results, given a page url" do
    with_list_fixture fn(expected) ->
      assert expected == Api.fetch_page(Resource, "next_page_path")
    end
  end

  test ".fetch_page should return an error if passed a nil path" do
    assert {:error, "That page does not exist."} == Api.fetch_page(Resource, nil)
  end

  test ".find should return the resource if it exists" do
    json = json_response(%{sid: "id"}, 200)

    with_fixture :get, json, fn ->
      assert {:ok, %Resource{sid: "id"}} == Api.find(Resource, "id")
      assert called Api.get("ApiTest.Resources/id")
    end
  end

  test ".find should return an error from Twilio if the resource does not exist" do
    json = json_response(%{message: "Error message"}, 404)

    with_fixture :get, json, fn ->
      assert {:error, "Error message", 404} == Api.find(Resource, "id")
    end
  end

  test ".create should return the resource if successful" do
    json = json_response(%{sid: "id"}, 200)

    with_fixture :post, json, fn ->
      assert {:ok, %Resource{sid: "id"}} == Api.create(Resource, [field: "value"])
      assert called Api.post("ApiTest.Resources", body: [field: "value"])
    end
  end

  test ".create should return an error from Twilio if the resource could not be created" do
    json = json_response(%{message: "Resource couldn't be created."}, 500)

    with_fixture :post, json, fn ->
      assert {:error, "Resource couldn't be created.", 500} == Api.create(Resource, [field: "value"])
    end
  end

  test ".update should return an updated resource if successful" do
    json = json_response(%{sid: "id", name: "Hello, World!"}, 200)

    with_fixture :post, json, fn ->
      expected = {:ok, %Resource{sid: "id", name: "Hello, World!"}}
      assert expected == Api.update(Resource, "id", name: "Hello, World!")
      assert called Api.post("ApiTest.Resources/id", body: [name: "Hello, World!"])
    end
  end

  test ".update should return an error if unsuccessful" do
    json = json_response(%{message: "The requested resource could not be found."}, 404)

    with_fixture :post, json, fn ->
      expected = {:error, "The requested resource could not be found.", 404}
      assert expected == Api.update(Resource, "nonexistent", name: "Hello, World!")
    end
  end

  test ".destroy should return :ok if successful" do
    with_fixture :delete, %{body: "", status_code: 204}, fn ->
      assert :ok == Api.destroy(Resource, "id")
      assert called Api.delete("ApiTest.Resources/id")
    end
  end

  test ".destroy should return an error if unsuccessful" do
    json = json_response(%{message: "not found"}, 404)

    with_fixture :delete, json, fn ->
      assert {:error, "not found", 404} == Api.destroy(Resource, "id")
    end
  end

  ###
  # HTTPotion API
  ###

  test ".process_url produces a formatted URL" do
    expected = Config.base_url <> "Accounts/#{Config.account_sid}/Calls.json"
    assert expected == Api.process_url("Calls")
  end

  test ".process_url doesn't append .json when it's already there" do
    expected = Config.base_url <> "Accounts/#{Config.account_sid}/Calls.json"
    assert expected == Api.process_url("Calls.json")
  end

  test ".process_url doesn't nest under 'Accounts' when the url contains 'Accounts'" do
    expected = Config.base_url <> "Accounts.json"
    assert expected == Api.process_url("Accounts")
  end

  test ".process_options adds in the basic HTTP auth" do
    expected = [basic_auth: { Config.account_sid, Config.auth_token }]
    assert expected == Api.process_options([])
  end

  test ".process_request_headers adds the correct 'Content-Type' header" do
    expected = %{:"Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8"}
    assert expected == Api.process_request_headers(%{})
  end

  test ".process_request_body converts body to a query string when passed a list" do
    assert "FieldName=value" == Api.process_request_body([field_name: "value"])
  end

  test ".process_request_body does not modify the body when passed a not-list" do
    assert "unmodified" == Api.process_request_body("unmodified")
  end

  ###
  # Helpers
  ###

  def with_list_fixture(fun) do
    data = %{
      "api_test/resources" => [
        %{sid: "1", name: "first"},
        %{sid: "2", name: "second"}
      ],
      next_page_uri: "/some/path"
    }
    json = json_response(data, 200)

    with_fixture :get, json, fn ->
      expected = {:ok, [
        %Resource{sid: "1", name: "first"},
        %Resource{sid: "2", name: "second"}
      ], %{ "next_page_uri" => "/some/path" }}

      fun.(expected)
    end
  end

  def with_fixture(:get, response, fun),    do: with_fixture({:get,    fn(_url) -> response end}, fun)
  def with_fixture(:post, response, fun),   do: with_fixture({:post,   fn(_url, _options) -> response end}, fun)
  def with_fixture(:delete, response, fun), do: with_fixture({:delete, fn(_url) -> response end}, fun)
  def with_fixture(stub, fun) do
    with_mock Api, [:passthrough], [stub] do
      fun.()
    end
  end

  def json_response(map, status) do
    {:ok, json} = Poison.encode(map)
    %{body: json, status_code: status}
  end
end

defmodule ExTwilio.ApiTest do
  use ExUnit.Case, async: false
  alias ExTwilio.Api
  import Mock

  defmodule Resource do
    defstruct sid: nil, name: nil
  end

  doctest ExTwilio.Api

  test ".stream should automatically page through resources, yielding each resource" do
    page1 = %{
      "api_test/resources" => [
        %{sid: "1", name: "first"},
      ],
      next_page_uri: "?page=2"
    }

    page2 = %{
      "api_test/resources" => [
        %{sid: "2", name: "second"},
      ],
      next_page_uri: nil
    }

    with_fixture {:get, fn "ApiTest.Resources.json?page=2" -> json_response(page2, 200)
                           _url -> json_response(page1, 200)
                        end}, fn ->
      expected = [%Resource{sid: "1", name: "first"}, %Resource{sid: "2", name: "second"}]
      assert expected == Enum.into Api.stream(Resource), []
    end
  end

  test ".list should fetch the first page of results" do
    with_list_fixture fn(expected) ->
      assert expected == Api.list(Resource)
    end
  end

  test ".fetch_page should fetch a page of results, given a page url" do
    with_list_fixture fn(expected) ->
      assert expected == Api.fetch_page(Resource, "next_page_path")
    end
  end

  test ".fetch_page should return an error if passed a nil path" do
    assert {:error, "That page does not exist."} == Api.fetch_page(Resource, nil)
  end

  test ".find should return the resource if it exists" do
    json = json_response(%{sid: "id"}, 200)

    with_fixture :get, json, fn ->
      assert {:ok, %Resource{sid: "id"}} == Api.find(Resource, "id")
      assert called Api.get("ApiTest.Resources/id")
    end
  end

  test ".find should return an error from Twilio if the resource does not exist" do
    json = json_response(%{message: "Error message"}, 404)

    with_fixture :get, json, fn ->
      assert {:error, "Error message", 404} == Api.find(Resource, "id")
    end
  end

  test ".create should return the resource if successful" do
    json = json_response(%{sid: "id"}, 200)

    with_fixture :post, json, fn ->
      assert {:ok, %Resource{sid: "id"}} == Api.create(Resource, [field: "value"])
      assert called Api.post("ApiTest.Resources", body: [field: "value"])
    end
  end

  test ".create should return an error from Twilio if the resource could not be created" do
    json = json_response(%{message: "Resource couldn't be created."}, 500)

    with_fixture :post, json, fn ->
      assert {:error, "Resource couldn't be created.", 500} == Api.create(Resource, [field: "value"])
    end
  end

  test ".update should return an updated resource if successful" do
    json = json_response(%{sid: "id", name: "Hello, World!"}, 200)

    with_fixture :post, json, fn ->
      expected = {:ok, %Resource{sid: "id", name: "Hello, World!"}}
      assert expected == Api.update(Resource, "id", name: "Hello, World!")
      assert called Api.post("ApiTest.Resources/id", body: [name: "Hello, World!"])
    end
  end

  test ".update should return an error if unsuccessful" do
    json = json_response(%{message: "The requested resource could not be found."}, 404)

    with_fixture :post, json, fn ->
      expected = {:error, "The requested resource could not be found.", 404}
      assert expected == Api.update(Resource, "nonexistent", name: "Hello, World!")
    end
  end

  test ".destroy should return :ok if successful" do
    with_fixture :delete, %{body: "", status_code: 204}, fn ->
      assert :ok == Api.destroy(Resource, "id")
      assert called Api.delete("ApiTest.Resources/id")
    end
  end

  test ".destroy should return an error if unsuccessful" do
    json = json_response(%{message: "not found"}, 404)

    with_fixture :delete, json, fn ->
      assert {:error, "not found", 404} == Api.destroy(Resource, "id")
    end
  end

  def with_list_fixture(fun) do
    data = %{
      "api_test/resources" => [
        %{sid: "1", name: "first"},
        %{sid: "2", name: "second"}
      ],
      next_page_uri: "/some/path"
    }
    json = json_response(data, 200)

    with_fixture :get, json, fn ->
      expected = {:ok, [
        %Resource{sid: "1", name: "first"},
        %Resource{sid: "2", name: "second"}
      ], %{ "next_page_uri" => "/some/path" }}

      fun.(expected)
    end
  end

  def with_fixture(:get, response, fun),    do: with_fixture({:get,    fn(_url) -> response end}, fun)
  def with_fixture(:post, response, fun),   do: with_fixture({:post,   fn(_url, _options) -> response end}, fun)
  def with_fixture(:delete, response, fun), do: with_fixture({:delete, fn(_url) -> response end}, fun)
  def with_fixture(stub, fun) do
    with_mock Api, [:passthrough], [stub] do
      fun.()
    end
  end

  def json_response(map, status) do
    {:ok, json} = Poison.encode(map)
    %{body: json, status_code: status}
  end
end


Revert "Allow direct-upload"

Revert "Allow direct-upload"

This reverts commit 462681d5da4e1a92e61a42735cc03a0911312203.

defmodule FarmbotExt.API do
  alias FarmbotExt.{API, JWT}

  alias FarmbotCore.JSON

  alias FarmbotCore.Asset.{
    FbosConfig,
    FirmwareConfig,
    StorageAuth
  }

  alias FarmbotCore.{BotState, BotState.JobProgress.Percent, Project}

  require FarmbotCore.Logger
  import FarmbotCore.Config, only: [get_config_value: 3]

  use Tesla
  alias Tesla.Multipart

  @version Project.version()
  @target Project.target()

  # adapter(Tesla.Adapter.Hackney)
  plug(Tesla.Middleware.JSON, decode: &JSON.decode/1, encode: &JSON.encode/1)
  plug(Tesla.Middleware.FollowRedirects)
  # plug(Tesla.Middleware.Logger)

  def client do
    binary_token = get_config_value(:string, "authorization", "token")
    server = get_config_value(:string, "authorization", "server")
    {:ok, _tkn} = JWT.decode(binary_token)

    uri = URI.parse(server)
    url = (uri.scheme || "https") <> "://" <> uri.host <> ":" <> to_string(uri.port)
    user_agent = "FarmbotOS/#{@version} (#{@target}) #{@target} ()"

    Tesla.client([
      {Tesla.Middleware.BaseUrl, url},
      {Tesla.Middleware.Headers,
       [
         {"content-type", "application/json"},
         {"authorization", "Bearer: " <> binary_token},
         {"user-agent", user_agent}
       ]}
    ])
  end

  def storage_client(%StorageAuth{url: url}) do
    user_agent = "FarmbotOS/#{@version} (#{@target}) #{@target} ()"

    Tesla.client([
      {Tesla.Middleware.BaseUrl, "https:" <> url},
      {Tesla.Middleware.Headers,
       [
         {"user-agent", user_agent}
       ]},
      {Tesla.Middleware.FormUrlencoded, []}
    ])
  end

  @file_chunk 4096
  @progress_steps 50

  def upload_image(image_filename, meta \\ %{}) do
    {:ok, changeset} = API.get_changeset(StorageAuth)
    storage_auth = %StorageAuth{form_data: form_data} = Ecto.Changeset.apply_changes(changeset)

    content_length = :filelib.file_size(image_filename)
    {:ok, pid} = Agent.start_link(fn -> 0 end)

    prog = %Percent{
      status: "working",
      percent: 0,
      file_type: Path.extname(image_filename),
      type: :image,
      time: DateTime.utc_now()
    }

    stream =
      image_filename
      |> File.stream!([], @file_chunk)
      |> Stream.each(fn chunk ->
        Agent.update(pid, fn sent ->
          size = sent + byte_size(chunk)
          prog = put_progress(prog, size, content_length)
          BotState.set_job_progress(image_filename, prog)
          size
        end)
      end)

    mp =
      Multipart.new()
      |> Multipart.add_field("key", form_data.key)
      |> Multipart.add_field("acl", form_data.acl)
      |> Multipart.add_field("policy", form_data.policy)
      |> Multipart.add_field("signature", form_data.signature)
      |> Multipart.add_field("Content-Type", form_data."Content-Type")
      |> Multipart.add_field("GoogleAccessId", form_data."GoogleAccessId")
      |> Multipart.add_file_content(stream, Path.basename(image_filename), name: "file")

    storage_resp =
      storage_auth
      |> API.storage_client()
      |> API.request(
        method: String.to_existing_atom(String.downcase(storage_auth.verb)),
        body: mp
      )

    with {:ok, %{url: url, status: s}} when s > 199 and s < 300 <- storage_resp,
         attachment_url <- Path.join(url, form_data.key),
         client <- API.client(),
         body <- %{attachment_url: attachment_url, meta: meta},
         {:ok, %{status: s}} = r when s > 199 and s < 300 <- API.post(client, "/api/images", body) do
      BotState.set_job_progress(image_filename, %{prog | status: "complete", percent: 100})
      r
    else
      er ->
        FarmbotCore.Logger.error(1, "Failed to upload image: #{inspect(er)}")
        BotState.set_job_progress(image_filename, %{prog | percent: -1, status: "error"})
        er
    end
  end

  def put_progress(prog, size, max) do
    fraction = size / max
    completed = trunc(fraction * @progress_steps)
    percent = trunc(fraction * 100)
    unfilled = @progress_steps - completed

    IO.write(
      :stderr,
      "\r|#{String.duplicate("=", completed)}#{String.duplicate(" ", unfilled)}| #{percent}% (#{
        bytes_to_mb(size)
      } / #{bytes_to_mb(max)}) MB"
    )

    status = if percent == 100, do: "complete", else: "working"

    %Percent{
      prog
      | status: status,
        percent: percent
    }
  end

  defp bytes_to_mb(bytes) do
    trunc(bytes / 1024 / 1024)
  end

  @doc "helper for `GET`ing a path."
  def get_body!(path) do
    case API.get(API.client(), path) do
      {:ok, %{body: body, status: 200}} ->
        {:ok, body}

      {:ok, %{body: error, status: status}} when is_binary(error) ->
        get_body_error(path, status, error)

      {:ok, %{body: %{"error" => error}, status: status}} when is_binary(error) ->
        get_body_error(path, status, error)

      {:ok, %{body: error, status: status}} when is_binary(error) ->
        get_body_error(path, status, inspect(error))

      {:error, reason} ->
        {:error, reason}
    end
  end

  defp get_body_error(path, status, error) when is_binary(error) do
    msg = """
    HTTP Error getting: #{path}
    Status Code = #{status}

    #{error}
    """

    {:error, msg}
  end

  @callback get_changeset(module) :: {:ok, %Ecto.Changeset{}} | {:error, term()}
  @callback get_changeset(data :: module | map(), Path.t()) ::
              {:ok, %Ecto.Changeset{}} | {:error, term()}

  @doc "helper for `GET`ing api resources."
  def get_changeset(module) when is_atom(module) do
    get_changeset(struct(module))
  end

  def get_changeset(%module{} = data) do
    get_body!(module.path())
    |> case do
      {:ok, %{} = single} ->
        {:ok, module.changeset(data, single)}

      {:ok, many} when is_list(many) ->
        {:ok, Enum.map(many, &module.changeset(data, &1))}

      {:error, reason} ->
        {:error, reason}
    end
  end

  @doc "helper for `GET`ing api resources."
  def get_changeset(asset, path)

  # Hacks for dealing with these resources not having #show
  def get_changeset(FbosConfig, _),
    do: get_changeset(FbosConfig)

  def get_changeset(FirmwareConfig, _),
    do: get_changeset(FirmwareConfig)

  def get_changeset(%FbosConfig{} = data, _),
    do: get_changeset(data)

  def get_changeset(%FirmwareConfig{} = data, _),
    do: get_changeset(data)

  def get_changeset(module, path) when is_atom(module) do
    get_changeset(struct(module), path)
  end

  def get_changeset(%module{} = data, path) do
    get_body!(Path.join(module.path(), to_string(path)))
    |> case do
      {:ok, %{} = single} ->
        {:ok, module.changeset(data, single)}

      {:ok, many} when is_list(many) ->
        {:ok, Enum.map(many, &module.changeset(data, &1))}

      {:error, reason} ->
        {:error, reason}
    end
  end
end

defmodule FarmbotExt.API do
  alias FarmbotExt.{API, JWT}

  alias FarmbotCore.JSON

  alias FarmbotCore.Asset.{
    FbosConfig,
    FirmwareConfig,
    StorageAuth
  }

  alias FarmbotCore.{BotState, BotState.JobProgress.Percent, Project}

  require FarmbotCore.Logger
  import FarmbotCore.Config, only: [get_config_value: 3]

  use Tesla
  alias Tesla.Multipart

  @version Project.version()
  @target Project.target()

  # adapter(Tesla.Adapter.Hackney)
  plug(Tesla.Middleware.JSON, decode: &JSON.decode/1, encode: &JSON.encode/1)
  plug(Tesla.Middleware.FollowRedirects)
  plug(Tesla.Middleware.Logger)

  def client do
    adapter = {Tesla.Adapter.Hackney, [recv_timeout: 30_000]}
    binary_token = get_config_value(:string, "authorization", "token")
    server = get_config_value(:string, "authorization", "server")
    {:ok, _tkn} = JWT.decode(binary_token)

    uri = URI.parse(server)
    url = (uri.scheme || "https") <> "://" <> uri.host <> ":" <> to_string(uri.port)
    user_agent = "FarmbotOS/#{@version} (#{@target}) #{@target} ()"

    Tesla.client(
      [
        {Tesla.Middleware.BaseUrl, url},
        {Tesla.Middleware.Headers,
         [
           {"content-type", "application/json"},
           {"authorization", "Bearer: " <> binary_token},
           {"user-agent", user_agent}
         ]}
      ],
      adapter
    )
  end

  def storage_client(%StorageAuth{url: url}) do
    adapter = {Tesla.Adapter.Hackney, [recv_timeout: 30_000]}
    server = get_config_value(:string, "authorization", "server")
    uri = URI.parse(server)
    user_agent = "FarmbotOS/#{@version} (#{@target}) #{@target} ()"

    Tesla.client(
      [
        {Tesla.Middleware.FormUrlencoded, []},
        {Tesla.Middleware.BaseUrl, "#{uri.scheme}:#{url}"},
        {Tesla.Middleware.Headers,
         [
           {"user-agent", user_agent}
         ]}
      ],
      adapter
    )
  end

  @file_chunk 4096
  @progress_steps 50

  def upload_image(image_filename, meta \\ %{}) do
    {:ok, changeset} = API.get_changeset(StorageAuth)
    storage_auth = Ecto.Changeset.apply_changes(changeset)

    if String.contains?(storage_auth.url, "direct_upload") do
      direct_upload(storage_auth, image_filename, meta)
    else
      gcs_upload(storage_auth, image_filename, meta)
    end
  end

  def direct_upload(%StorageAuth{form_data: form_data} = storage_auth, image_filename, meta) do
    mp =
      Map.from_struct(form_data)
      |> Enum.map(fn
        {:file, _} -> {"file", File.read!(image_filename)}
        {key, value} -> {to_string(key), value}
      end)

    headers = []
    server = get_config_value(:string, "authorization", "server")
    uri = URI.parse(server)

    case :hackney.request(:post, "#{uri.scheme}:#{storage_auth.url}", headers, {:multipart, mp}) do
      {:ok, 200, _headers, _ref} ->
        body = %{attachment_url: Path.join(server, form_data.key), meta: meta}
        API.post(client(), "/api/images", body)

      error ->
        error
    end
  end

  def gcs_upload(%StorageAuth{form_data: form_data} = storage_auth, image_filename, meta) do
    content_length = :filelib.file_size(image_filename)
    {:ok, pid} = Agent.start_link(fn -> 0 end)

    prog = %Percent{
      status: "working",
      percent: 0,
      file_type: Path.extname(image_filename),
      type: :image,
      time: DateTime.utc_now()
    }

    stream =
      image_filename
      |> File.stream!([], @file_chunk)
      |> Stream.each(fn chunk ->
        Agent.update(pid, fn sent ->
          size = sent + byte_size(chunk)
          prog = put_progress(prog, size, content_length)
          BotState.set_job_progress(image_filename, prog)
          size
        end)
      end)

    mp =
      Multipart.new()
      |> Multipart.add_field("key", form_data.key)
      |> Multipart.add_field("acl", form_data.acl)
      |> Multipart.add_field("policy", form_data.policy)
      |> Multipart.add_field("signature", form_data.signature)
      |> Multipart.add_field("Content-Type", form_data."Content-Type")
      |> Multipart.add_field("GoogleAccessId", form_data."GoogleAccessId")
      |> Multipart.add_file_content(stream, Path.basename(image_filename), name: "file")

    storage_resp =
      storage_auth
      |> API.storage_client()
      |> API.request(
        method: String.to_existing_atom(String.downcase(storage_auth.verb)),
        body: mp
      )

    with {:ok, %{url: url, status: s}} when s > 199 and s < 300 <- storage_resp,
         attachment_url <- Path.join(url, form_data.key),
         client <- API.client(),
         body <- %{attachment_url: attachment_url, meta: meta},
         {:ok, %{status: s}} = r when s > 199 and s < 300 <- API.post(client, "/api/images", body) do
      BotState.set_job_progress(image_filename, %{prog | status: "complete", percent: 100})
      r
    else
      er ->
        FarmbotCore.Logger.error(1, "Failed to upload image: #{inspect(er)}")
        BotState.set_job_progress(image_filename, %{prog | percent: -1, status: "error"})
        er
    end
  end

  def put_progress(prog, size, max) do
    fraction = size / max
    completed = trunc(fraction * @progress_steps)
    percent = trunc(fraction * 100)
    unfilled = @progress_steps - completed

    IO.write(
      :stderr,
      "\r|#{String.duplicate("=", completed)}#{String.duplicate(" ", unfilled)}| #{percent}% (#{
        bytes_to_mb(size)
      } / #{bytes_to_mb(max)}) MB"
    )

    status = if percent == 100, do: "complete", else: "working"

    %Percent{
      prog
      | status: status,
        percent: percent
    }
  end

  defp bytes_to_mb(bytes) do
    trunc(bytes / 1024 / 1024)
  end

  @doc "helper for `GET`ing a path."
  def get_body!(path) do
    case API.get(API.client(), path) do
      {:ok, %{body: body, status: 200}} ->
        {:ok, body}

      {:ok, %{body: error, status: status}} when is_binary(error) ->
        get_body_error(path, status, error)

      {:ok, %{body: %{"error" => error}, status: status}} when is_binary(error) ->
        get_body_error(path, status, error)

      {:ok, %{body: error, status: status}} when is_binary(error) ->
        get_body_error(path, status, inspect(error))

      {:error, reason} ->
        {:error, reason}
    end
  end

  defp get_body_error(path, status, error) when is_binary(error) do
    msg = """
    HTTP Error getting: #{path}
    Status Code = #{status}

    #{error}
    """

    {:error, msg}
  end

  @callback get_changeset(module) :: {:ok, %Ecto.Changeset{}} | {:error, term()}
  @callback get_changeset(data :: module | map(), Path.t()) ::
              {:ok, %Ecto.Changeset{}} | {:error, term()}

  @doc "helper for `GET`ing api resources."
  def get_changeset(module) when is_atom(module) do
    get_changeset(struct(module))
  end

  def get_changeset(%module{} = data) do
    get_body!(module.path())
    |> case do
      {:ok, %{} = single} ->
        {:ok, module.changeset(data, single)}

      {:ok, many} when is_list(many) ->
        {:ok, Enum.map(many, &module.changeset(data, &1))}

      {:error, reason} ->
        {:error, reason}
    end
  end

  @doc "helper for `GET`ing api resources."
  def get_changeset(asset, path)

  # Hacks for dealing with these resources not having #show
  def get_changeset(FbosConfig, _),
    do: get_changeset(FbosConfig)

  def get_changeset(FirmwareConfig, _),
    do: get_changeset(FirmwareConfig)

  def get_changeset(%FbosConfig{} = data, _),
    do: get_changeset(data)

  def get_changeset(%FirmwareConfig{} = data, _),
    do: get_changeset(data)

  def get_changeset(module, path) when is_atom(module) do
    get_changeset(struct(module), path)
  end

  def get_changeset(%module{} = data, path) do
    get_body!(Path.join(module.path(), to_string(path)))
    |> case do
      {:ok, %{} = single} ->
        {:ok, module.changeset(data, single)}

      {:ok, many} when is_list(many) ->
        {:ok, Enum.map(many, &module.changeset(data, &1))}

      {:error, reason} ->
        {:error, reason}
    end
  end
end


Added general MC docs

Added general MC docs

defmodule SMPPEX.MC do
  @moduledoc """
  This is a module for launching a TCP listener (or any other listener supported
  by `ranch`, for example, `ssl`) which handles incoming connections with the
  passed `SMPPEX.Session` implementations.

  To start an MC one generally should do the following.

  1. Implement an `SMPPEX.Session` behaviour.

  ```elixir

  defmodule MyMCSession do
    use SMPPEX.Session

    # ...Callback implementation

  end

  ```

  2. Start a listener passing implemented behaviour as a callback module.

  ```elixir

  {:ok, listener} = SMPPEX.MC.start({MyESMESession, some_args},
                                    transport_opts: [port: 2775])
  ```

  The important things to note are:
  * There is no `start_link` method, since started listener is not a standalone
  `GenServer` but a pool of socket acceptors running under `Ranch` supervisor.
  * Each received connection is served with its own process which uses passed
  callback module (`MyESMESession`) for handling connection events.
  """

  alias :ranch, as: Ranch

  @default_transport :ranch_tcp
  @default_acceptor_count 50

  @spec start({module, args :: term}, opts :: Keyword.t) ::
    {:ok, listener_ref :: Ranch.ref} |
    {:error, reason :: term}

  def start({_module, _args} = mod_with_args, opts \\ []) do
    acceptor_count = Keyword.get(opts, :acceptor_count, @default_acceptor_count)
    transport = Keyword.get(opts, :transport, @default_transport)
    transport_opts = Keyword.get(opts, :transport_opts, [{:port, 0}])
    mc_opts = Keyword.get(opts, :mc_opts)
    ref = make_ref()

    start_result = Ranch.start_listener(
      ref,
      acceptor_count,
      transport,
      transport_opts,
      SMPPEX.TransportSession,
      {SMPPEX.Session, [mod_with_args, mc_opts]}
    )

    case start_result do
      {:error, _} = error -> error
      {:ok, _, _} -> {:ok, ref}
      {:ok, _} -> {:ok, ref}
    end

  end

  @spec stop(Ranch.ref) :: :ok

  @doc """
  Stops MC listener and all its sessions.
  """

  def stop(listener) do
    Ranch.stop_listener(listener)
  end

end

defmodule SMPPEX.MC do

  alias :ranch, as: Ranch

  @default_transport :ranch_tcp
  @default_acceptor_count 50

  @spec start({module, args :: term}, opts :: Keyword.t) :: {:ok, listener_ref :: Ranch.ref} | {:error, reason :: term}

  def start({_module, _args} = mod_with_args, opts \\ []) do
    acceptor_count = Keyword.get(opts, :acceptor_count, @default_acceptor_count)
    transport = Keyword.get(opts, :transport, @default_transport)
    transport_opts = Keyword.get(opts, :transport_opts, [{:port, 0}])
    mc_opts = Keyword.get(opts, :mc_opts)
    ref = make_ref()

    start_result = Ranch.start_listener(
      ref,
      acceptor_count,
      transport,
      transport_opts,
      SMPPEX.TransportSession,
      {SMPPEX.Session, [mod_with_args, mc_opts]}
    )

    case start_result do
      {:error, _} = error -> error
      {:ok, _, _} -> {:ok, ref}
      {:ok, _} -> {:ok, ref}
    end

  end

  @spec stop(Ranch.ref) :: :ok

  @doc """
  Stops MC listener and all its sessions.
  """

  def stop(listener) do
    Ranch.stop_listener(listener)
  end

end


Load avatars in 64x64

Load avatars in 64x64

defmodule ElixirStatus.Avatar do
  def load!(user_name, avatar_url) do
    avatar_url
      |> String.replace("?v=3", "?v=3&s=64")
      |> load_image!("#{avatar_path(user_name, Mix.env)}")
  end

  defp static_path(user_name) do
    "images/github/#{user_name}.jpg"
  end

  defp load_image!(url, filename) do
    File.mkdir_p! Path.dirname(filename)

    %HTTPoison.Response{body: body} = HTTPoison.get!(url)
    File.write!(filename, body)
  end

  defp avatar_path(user_name, :test) do
    Path.expand("../../tmp/test/#{static_path(user_name)}", __DIR__)
  end

  defp avatar_path(user_name, _) do
    Path.expand("../../priv/static/#{static_path(user_name)}", __DIR__)
  end
end

defmodule ElixirStatus.Avatar do
  def load!(user_name, avatar_url) do
    load_image!(avatar_url, "#{avatar_path(user_name, Mix.env)}")
  end

  defp static_path(user_name) do
    "images/github/#{user_name}.jpg"
  end

  defp load_image!(url, filename) do
    File.mkdir_p! Path.dirname(filename)

    %HTTPoison.Response{body: body} = HTTPoison.get!(url)
    File.write!(filename, body)
  end

  defp avatar_path(user_name, :test) do
    Path.expand("../../tmp/test/#{static_path(user_name)}", __DIR__)
  end

  defp avatar_path(user_name, _) do
    Path.expand("../../priv/static/#{static_path(user_name)}", __DIR__)
  end
end


Fix door ID lookup typo

Fix door ID lookup typo

defmodule Gatekeeper.DoorBell do
  require Logger

  use GenServer
  use Timex

  @notify_url "http://10.3.18.99:4000/api/door_bell"

  def start_link(gpio_number, type \\ Gatekeeper.GpioDummy, opts \\ []) do
    opts = Dict.merge(opts, name: __MODULE__)
    GenServer.start_link(__MODULE__, [gpio_number, type], opts)
  end

  def server do
    Process.whereis(__MODULE__) ||
      raise "could not find process #{__MODULE__}. Have you started the application?"
  end

  def init([gpio_number, type]) do
    Logger.info "Starting door bell watcher on GPIO pin #{gpio_number}"
    pid = if type == Gpio do
      System.cmd("/usr/bin/gpio", ["-g", "mode", "#{gpio_number}", "in"])
      System.cmd("/usr/bin/gpio", ["-g", "mode", "#{gpio_number}", "up"])
      {:ok, pid} = Gpio.start_link(gpio_number, :input)
      Gpio.set_int(pid, :rising)
      pid
    else
      nil
    end

    {:ok, %{gpio_number: gpio_number, type: type, gpio_pid: pid, last_signal: Time.now()}}
  end

  def handle_call(request, from, state) do
    Logger.error "Received Call #{inspect request} - from #{inspect from} - state #{inspect state}"
    {:reply, :ok, state}
  end

  def handle_cast(request, from, state) do
    Logger.error "Received Cast #{inspect request} - from #{inspect from} - state #{inspect state}"
    {:noreply, state}
  end

  def handle_info({:gpio_interrupt, _gpio_port, :falling}, state = %{last_signal: last_signal}) do
    {_, seconds, ms} = Time.diff(Time.now, last_signal)
    if seconds < 2 do
      Logger.debug "Ignoring bounce on GPIO; last signal was #{seconds}.#{ms} seconds ago"
    else
      notify()
      state = Map.put(state, :last_signal, Time.now)
    end
    {:noreply, state}
  end
  def handle_info(msg, state) do
    Logger.debug "Received Info msg - #{inspect msg} - state #{inspect state}"
    {:noreply, state}
  end

  def notify() do
    Logger.info "Ding dong! Notifying..."
    door_id = Application.get_env(:gatekeeper, :doorlock)[:door_id]
    {:ok, _response} = HTTPoison.post @notify_url, {:form, [{"door_id", "#{door_id}"}]}
  end
end

defmodule Gatekeeper.DoorBell do
  require Logger

  use GenServer
  use Timex

  @notify_url "http://10.3.18.99:4000/api/door_bell"

  def start_link(gpio_number, type \\ Gatekeeper.GpioDummy, opts \\ []) do
    opts = Dict.merge(opts, name: __MODULE__)
    GenServer.start_link(__MODULE__, [gpio_number, type], opts)
  end

  def server do
    Process.whereis(__MODULE__) ||
      raise "could not find process #{__MODULE__}. Have you started the application?"
  end

  def init([gpio_number, type]) do
    Logger.info "Starting door bell watcher on GPIO pin #{gpio_number}"
    pid = if type == Gpio do
      System.cmd("/usr/bin/gpio", ["-g", "mode", "#{gpio_number}", "in"])
      System.cmd("/usr/bin/gpio", ["-g", "mode", "#{gpio_number}", "up"])
      {:ok, pid} = Gpio.start_link(gpio_number, :input)
      Gpio.set_int(pid, :rising)
      pid
    else
      nil
    end

    {:ok, %{gpio_number: gpio_number, type: type, gpio_pid: pid, last_signal: Time.now()}}
  end

  def handle_call(request, from, state) do
    Logger.error "Received Call #{inspect request} - from #{inspect from} - state #{inspect state}"
    {:reply, :ok, state}
  end

  def handle_cast(request, from, state) do
    Logger.error "Received Cast #{inspect request} - from #{inspect from} - state #{inspect state}"
    {:noreply, state}
  end

  def handle_info({:gpio_interrupt, _gpio_port, :falling}, state = %{last_signal: last_signal}) do
    {_, seconds, ms} = Time.diff(Time.now, last_signal)
    if seconds < 2 do
      Logger.debug "Ignoring bounce on GPIO; last signal was #{seconds}.#{ms} seconds ago"
    else
      notify()
      state = Map.put(state, :last_signal, Time.now)
    end
    {:noreply, state}
  end
  def handle_info(msg, state) do
    Logger.debug "Received Info msg - #{inspect msg} - state #{inspect state}"
    {:noreply, state}
  end

  def notify() do
    Logger.info "Ding dong! Notifying..."
    door_id = Application.get_env(:gatekeeper, :door_lock)[:door_id]
    {:ok, _response} = HTTPoison.post @notify_url, {:multipart, [{"door_id", "#{door_id}"}]}
  end
end


cleanup fix action

cleanup fix action

defmodule Pan.MaintenanceController do
  use Pan.Web, :controller

  def vienna_beamers(conn, _params) do
    redirect(conn, external: "https://blog.panoptikum.io/vienna-beamers/")
  end

  def blog_2016(conn, %{"month" => month, "day" => day, "file" => file}) do
    redirect(conn, external: "https://blog.panoptikum.io/2016/#{month}/#{day}/#{file}")
  end

  def blog_2017(conn, %{"month" => month, "day" => day, "file" => file}) do
    redirect(conn, external: "https://blog.panoptikum.io/2017/#{month}/#{day}/#{file}")
  end

  def fix(conn, _params) do

    render(conn, "done.html", %{})
  end
end

defmodule Pan.MaintenanceController do
  use Pan.Web, :controller

  def vienna_beamers(conn, _params) do
    redirect(conn, external: "https://blog.panoptikum.io/vienna-beamers/")
  end

  def blog_2016(conn, %{"month" => month, "day" => day, "file" => file}) do
    redirect(conn, external: "https://blog.panoptikum.io/2016/#{month}/#{day}/#{file}")
  end

  def blog_2017(conn, %{"month" => month, "day" => day, "file" => file}) do
    redirect(conn, external: "https://blog.panoptikum.io/2017/#{month}/#{day}/#{file}")
  end

  def fix(conn, _params) do
    from(a in Pan.AlternateFeed, where: a.url == ^"http://reachmd.com/")
    |> Repo.delete_all()

    alternate_feeds = from(a in Pan.AlternateFeed, where: like(a.url, "https://reachmd.com/%"),
                                                   preload: :feed)
                      |> Repo.all()

    for alternate_feed <- alternate_feeds do
      Pan.Feed.changeset(alternate_feed.feed, %{self_link_url: alternate_feed.url})
      |> Repo.update()
    end

    from(a in Pan.AlternateFeed, where: like(a.url, "https://reachmd.com/%"))
                                 |> Repo.delete_all()

    render(conn, "done.html", %{})
  end
end


search case sensitive

search case sensitive

defmodule Pan.SearchFrontendView do
  use Pan.Web, :view

  def highlight(result, searchstring) do
    [left, right] =  String.split(result, searchstring, parts: 2)

    left = left
           |> HtmlSanitizeEx.strip_tags
           |> String.reverse
           |> C.String.truncate(50)
           |> String.reverse
    right = right
            |> HtmlSanitizeEx.strip_tags
            |> C.String.truncate(50)

    left <> "" <> searchstring <> "" <> right
    |> raw
  end
end

defmodule Pan.SearchFrontendView do
  use Pan.Web, :view

  def highlight(result, searchstring) do
    [left, right] =  String.split(String.downcase(result), String.downcase(searchstring), parts: 2)

    left = left
           |> HtmlSanitizeEx.strip_tags
           |> String.reverse
           |> C.String.truncate(50)
           |> String.reverse
    right = right
            |> HtmlSanitizeEx.strip_tags
            |> C.String.truncate(50)

    left <> "" <> searchstring <> "" <> right
    |> raw
  end
end


Fix monitor tests when run locally

Fix monitor tests when run locally

Previously, the order in which the tests were run affected the monitor
tests, as they depend on the currently existing monitors, which is a
global value.

This patch first finds the monitors, then adds one to assert a new one
was created to make the tests more robust.

defmodule Appsignal.MonitorTest do
  use ExUnit.Case
  import AppsignalTest.Utils
  alias Appsignal.{Monitor, Test}

  setup do
    start_supervised!(Test.Nif)
    :ok
  end

  test "is started by the main supervisor" do
    assert is_pid(monitor_pid())
  end

  test "monitors a process" do
    {:monitors, previous} = Process.info(monitor_pid(), :monitors)

    Monitor.add()

    until(fn ->
      assert Process.info(monitor_pid(), :monitors) ==
               {:monitors, previous ++ [{:process, self()}]}
    end)
  end

  test "does not monitor a process more than once" do
    {:monitors, previous} = Process.info(monitor_pid(), :monitors)

    Monitor.add()
    Monitor.add()

    until(fn ->
      assert Process.info(monitor_pid(), :monitors) ==
               {:monitors, previous ++ [{:process, self()}]}
    end)
  end

  test "removes entries from the registry when their processes exit" do
    pid =
      spawn(fn ->
        :ets.insert(:"$appsignal_registry", {self(), "span"})
        Monitor.add()
      end)

    until(fn ->
      assert lookup(pid) == [{pid, "span"}]
    end)

    until(fn ->
      assert lookup(pid) == []
    end)
  end

  test "automatically removes pids that don't exist from the monitor list" do
    pid = :erlang.list_to_pid('<0.999.0>')
    GenServer.cast(Appsignal.Monitor, {:monitor, pid})

    until(fn ->
      assert MapSet.member?(:sys.get_state(Appsignal.Monitor), pid)
    end)

    until(fn ->
      refute MapSet.member?(:sys.get_state(Appsignal.Monitor), pid)
    end)
  end

  test "syncs the monitors list" do
    Monitor.add()
    :sys.replace_state(Appsignal.Monitor, fn _ -> MapSet.new() end)

    until(fn ->
      assert MapSet.size(:sys.get_state(Appsignal.Monitor)) == 0
    end)

    send(Appsignal.Monitor, :sync)

    until(fn ->
      assert MapSet.member?(:sys.get_state(Appsignal.Monitor), self())
    end)
  end

  defp lookup(pid) do
    :ets.lookup(:"$appsignal_registry", pid)
  end

  defp monitor_pid do
    Process.whereis(Monitor)
  end
end

defmodule Appsignal.MonitorTest do
  use ExUnit.Case
  import AppsignalTest.Utils
  alias Appsignal.{Monitor, Test}

  setup do
    start_supervised!(Test.Nif)
    :ok
  end

  test "is started by the main supervisor" do
    assert is_pid(monitor_pid())
  end

  test "monitors a process" do
    Monitor.add()

    until(fn ->
      assert Process.info(monitor_pid(), :monitors) == {:monitors, [{:process, self()}]}
    end)
  end

  test "does not monitor a process more than once" do
    Monitor.add()
    Monitor.add()

    until(fn ->
      assert Process.info(monitor_pid(), :monitors) == {:monitors, [{:process, self()}]}
    end)
  end

  test "removes entries from the registry when their processes exit" do
    pid =
      spawn(fn ->
        :ets.insert(:"$appsignal_registry", {self(), "span"})
        Monitor.add()
      end)

    until(fn ->
      assert lookup(pid) == [{pid, "span"}]
    end)

    until(fn ->
      assert lookup(pid) == []
    end)
  end

  test "automatically removes pids that don't exist from the monitor list" do
    pid = :erlang.list_to_pid('<0.999.0>')
    GenServer.cast(Appsignal.Monitor, {:monitor, pid})

    until(fn ->
      assert MapSet.size(:sys.get_state(Appsignal.Monitor)) == 0
    end)
  end

  test "syncs the monitors list" do
    Monitor.add()
    :sys.replace_state(Appsignal.Monitor, fn _ -> MapSet.new() end)

    until(fn ->
      assert MapSet.size(:sys.get_state(Appsignal.Monitor)) == 0
    end)

    send(Appsignal.Monitor, :sync)

    until(fn ->
      assert MapSet.member?(:sys.get_state(Appsignal.Monitor), self())
    end)
  end

  defp lookup(pid) do
    :ets.lookup(:"$appsignal_registry", pid)
  end

  defp monitor_pid do
    Process.whereis(Monitor)
  end
end


remove unused alias, and merge with master

defmodule CoursePlanner.BulkHelper do
  @moduledoc """
    Handle bulk creation specific logics
  """
  alias CoursePlanner.{Repo, Accounts.User}
  alias Coherence.ControllerHelpers
  alias Ecto.{DateTime, Multi}

  def bulk_user_creation(csv_file_stream) do
    csv_parsed_result = csv_parsing(csv_file_stream)

    case validate_column_count(csv_parsed_result, 5) do
      {:ok, parsed_params} ->
        create_users_with_transaction(parsed_params)
      {:validate_error, row_index} ->
        {:error, "parsing_csv", "Input data in row ##{row_index + 1} is not matching the column number.", ""}
      {:error, :empty} ->
        {:error, "parsing_csv", "Input can not be empty.", ""}
      {:error, message} ->
        {:error, "parsing_csv", message, ""}
    end
  end

  defp csv_parsing(csv_file_stream) do
    csv_file_stream
    |> CSV.decode
    |> Enum.reduce_while({:ok, []}, fn(parsed_row, {_out_result, out_value}) ->
         case parsed_row do
           {:ok, value} -> {:cont, {:ok, [value | out_value]}}
           {:error, value} -> {:halt, {:error, value}}
         end
       end)
  end

  def validate_column_count({:error, parsed_params}, _column_count), do: {:error, parsed_params}
  def validate_column_count({:ok, []}, _column_count), do: {:error, :empty}
  def validate_column_count({:ok, trimmed_splitted_csv}, column_count) do
    trimmed_splitted_csv
    |> Enum.with_index
    |> Enum.reduce_while({:ok, []}, fn({row, index}, {_out_result, out_value}) ->
          case validate_row(row, column_count) do
            false -> {:halt, {:validate_error, index}}
            true  -> {:cont, {:ok, [row | out_value]}}
          end
       end)
  end

  def validate_row(row_data, required_column_count) do
    row_data
    |> length()
    |> rem(required_column_count)
    |> Kernel.==(0)
  end

  defp create_users_with_transaction(user_records) do
    multi =
      Enum.reduce(user_records, Multi.new, fn(user, out) ->
        [name, family_name, nickname, email, role] = user
        token = ControllerHelpers.random_string 48
        params = %{}
          |> Map.put_new("name", name)
          |> Map.put_new("family_name", family_name)
          |> Map.put_new("nickname", nickname)
          |> Map.put_new("email", email)
          |> Map.put_new("role", role)
          |> Map.put_new("reset_password_token", token)
          |> Map.put_new("reset_password_sent_at", DateTime.utc())
          |> Map.put_new("password", "fakepassword")
          |> Map.put_new("password_confirmation", "fakepassword")

        changeset = User.changeset(%User{}, params, :bulk)

        Multi.insert(out, token, changeset)
      end)
    Repo.transaction(multi)
  end
end

defmodule CoursePlanner.BulkHelper do
  @moduledoc """
    Handle bulk creation specific logics
  """
  alias CoursePlanner.{CsvParser, Repo, Accounts.User}
  alias Coherence.ControllerHelpers
  alias Ecto.{DateTime, Multi}

  def bulk_user_creation(csv_file_stream) do
    csv_parsed_result = csv_parsing(csv_file_stream)

    case validate_column_count(csv_parsed_result, 5) do
      {:ok, parsed_params} ->
        create_users_with_transaction(parsed_params)
      {:validate_error, row_index} ->
        {:error, "parsing_csv", "Input data in row ##{row_index + 1} is not matching the column number.", ""}
      {:error, :empty} ->
        {:error, "parsing_csv", "Input can not be empty.", ""}
      {:error, message} ->
        {:error, "parsing_csv", message, ""}
    end
  end

  defp csv_parsing(csv_file_stream) do
    csv_file_stream
    |> CSV.decode
    |> Enum.reduce_while({:ok, []}, fn(parsed_row, {_out_result, out_value}) ->
         case parsed_row do
           {:ok, value} -> {:cont, {:ok, [value | out_value]}}
           {:error, value} -> {:halt, {:error, value}}
         end
       end)
  end

  def validate_column_count({:error, parsed_params}, _column_count), do: {:error, parsed_params}
  def validate_column_count({:ok, []}, _column_count), do: {:error, :empty}
  def validate_column_count({:ok, trimmed_splitted_csv}, column_count) do
    trimmed_splitted_csv
    |> Enum.with_index
    |> Enum.reduce_while({:ok, []}, fn({row, index}, {_out_result, out_value}) ->
          case validate_row(row, column_count) do
            false -> {:halt, {:validate_error, index}}
            true  -> {:cont, {:ok, [row | out_value]}}
          end
       end)
  end

  def validate_row(row_data, required_column_count) do
    row_data
    |> length()
    |> rem(required_column_count)
    |> Kernel.==(0)
  end

  defp create_users_with_transaction(user_records) do
    multi =
      Enum.reduce(user_records, Multi.new, fn(user, out) ->
        [name, family_name, nickname, email, role] = user
        token = ControllerHelpers.random_string 48
        params = %{}
          |> Map.put_new("name", name)
          |> Map.put_new("family_name", family_name)
          |> Map.put_new("nickname", nickname)
          |> Map.put_new("email", email)
          |> Map.put_new("role", role)
          |> Map.put_new("reset_password_token", token)
          |> Map.put_new("reset_password_sent_at", DateTime.utc())
          |> Map.put_new("password", "fakepassword")
          |> Map.put_new("password_confirmation", "fakepassword")

        changeset = User.changeset(%User{}, params, :bulk)

        Multi.insert(out, token, changeset)
      end)
    Repo.transaction(multi)
  end
end


process: add a code of spawn_link

process: add a code of spawn_link

IO.puts "## Base"
send self(), {:hello, "world"}

IO.inspect(receive do
  {:hello, msg} -> msg
  {:world, _} -> "won't match"
end)
IO.puts ""

IO.puts "## Config Process"
defmodule KV do
  def start_link do
    {:ok, spawn_link(fn -> loop(%{}) end)}
  end

  defp loop(map) do
    receive do
      {:get, key, caller} ->
        send caller, Map.get(map, key)
        loop(map)
      {:put, key, value} ->
        loop(Map.put(map, key, value))
    end
  end
end

IO.inspect {:ok, pid} = KV.start_link
IO.inspect send pid, {:put, :hello, :world}
IO.inspect send pid, {:get, :hello, self()}

receive do
  value -> IO.inspect value
end
IO.puts ""

IO.puts "## OTP: GenServer"
defmodule KV2 do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, %{})
  end

  def get(pid, key) do
    GenServer.call(pid, {:get, key})
  end

  def put(pid, key, value) do
    GenServer.call(pid, {:put, key, value})
  end

  def handle_call({:get, key}, _from, state) do
    {:reply, Map.get(state, key), state}
  end

  def handle_call({:put, key, value}, _from, state) do
    {:reply, value, Map.put(state, key, value)}
  end
end
IO.inspect {:ok, pid} = KV2.start_link
IO.inspect KV2.get(pid, :a)
IO.inspect KV2.put(pid, :a, 1)
IO.inspect KV2.get(pid, :a)
IO.puts ""

IO.puts "## OTP: Agent"
defmodule KV3 do
  def start_link do
    Agent.start_link fn -> %{} end
  end

  def get(pid, key) do
    Agent.get pid, fn (map) -> Map.get(map, key) end
  end

  def put(pid, key, value) do
    Agent.update pid, fn (map) -> Map.put(map, key, value) end
  end
end

IO.inspect {:ok, pid} = KV3.start_link
IO.inspect KV3.get(pid, :a)
IO.inspect KV3.put(pid, :a, 1)
IO.inspect KV3.get(pid, :a)
IO.puts ""

IO.puts "## OTP: Task"
defmodule TaskModule do
  def parallel do
    [
      fn -> :timer.sleep(200); IO.puts 1 end,
      fn -> :timer.sleep(400); IO.puts 2 end,
      fn -> :timer.sleep(600); IO.puts 3 end,
      fn -> :timer.sleep(800); IO.puts 4 end
    ]
    |> Enum.map(&Task.async/1)
    |> Enum.map(&Task.await/1)
  end
end
TaskModule.parallel()
IO.puts ""

IO.puts "## Process.monitor"
pid = spawn fn ->
  :timer.sleep(100)
  raise RuntimeError
end
Process.monitor pid

receive do
  {:DOWN, ref, :process, pid, msg} ->
    IO.inspect ref
    IO.inspect pid
    IO.inspect msg
end
IO.puts ""

IO.puts "## OTP: Process.link"
pid = spawn fn ->
  pid = spawn fn ->
    :timer.sleep(500)
    raise RuntimeError
  end
  Process.link pid
  IO.puts "start to link with other process"
  :timer.sleep(2000)
  IO.puts "never display this message"
end
Process.monitor pid
IO.puts "start the linking process"
IO.inspect pid

receive do
  {:DOWN, ref, :process, pid, msg} ->
    IO.puts "killed the linking process"
    IO.inspect pid
    IO.inspect msg
end
IO.puts ""

IO.puts "## OTP: spawn_link"
pid = spawn fn ->
  pid = spawn_link fn ->
    :timer.sleep(500)
    raise RuntimeError
  end
  IO.puts "start to link with other process"
  :timer.sleep(2000)
  IO.puts "never display this message"
end
Process.monitor pid
IO.puts "start the linking process"
IO.inspect pid

receive do
  {:DOWN, ref, :process, pid, msg} ->
    IO.puts "killed the linking process"
    IO.inspect pid
    IO.inspect msg
end
IO.puts ""

IO.puts "## Base"
send self(), {:hello, "world"}

IO.inspect(receive do
  {:hello, msg} -> msg
  {:world, _} -> "won't match"
end)
IO.puts ""

IO.puts "## Config Process"
defmodule KV do
  def start_link do
    {:ok, spawn_link(fn -> loop(%{}) end)}
  end

  defp loop(map) do
    receive do
      {:get, key, caller} ->
        send caller, Map.get(map, key)
        loop(map)
      {:put, key, value} ->
        loop(Map.put(map, key, value))
    end
  end
end

IO.inspect {:ok, pid} = KV.start_link
IO.inspect send pid, {:put, :hello, :world}
IO.inspect send pid, {:get, :hello, self()}

receive do
  value -> IO.inspect value
end
IO.puts ""

IO.puts "## OTP: GenServer"
defmodule KV2 do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, %{})
  end

  def get(pid, key) do
    GenServer.call(pid, {:get, key})
  end

  def put(pid, key, value) do
    GenServer.call(pid, {:put, key, value})
  end

  def handle_call({:get, key}, _from, state) do
    {:reply, Map.get(state, key), state}
  end

  def handle_call({:put, key, value}, _from, state) do
    {:reply, value, Map.put(state, key, value)}
  end
end
IO.inspect {:ok, pid} = KV2.start_link
IO.inspect KV2.get(pid, :a)
IO.inspect KV2.put(pid, :a, 1)
IO.inspect KV2.get(pid, :a)
IO.puts ""

IO.puts "## OTP: Agent"
defmodule KV3 do
  def start_link do
    Agent.start_link fn -> %{} end
  end

  def get(pid, key) do
    Agent.get pid, fn (map) -> Map.get(map, key) end
  end

  def put(pid, key, value) do
    Agent.update pid, fn (map) -> Map.put(map, key, value) end
  end
end

IO.inspect {:ok, pid} = KV3.start_link
IO.inspect KV3.get(pid, :a)
IO.inspect KV3.put(pid, :a, 1)
IO.inspect KV3.get(pid, :a)
IO.puts ""

IO.puts "## OTP: Task"
defmodule TaskModule do
  def parallel do
    [
      fn -> :timer.sleep(200); IO.puts 1 end,
      fn -> :timer.sleep(400); IO.puts 2 end,
      fn -> :timer.sleep(600); IO.puts 3 end,
      fn -> :timer.sleep(800); IO.puts 4 end
    ]
    |> Enum.map(&Task.async/1)
    |> Enum.map(&Task.await/1)
  end
end
TaskModule.parallel()
IO.puts ""

IO.puts "## Process.monitor"
pid = spawn fn ->
  :timer.sleep(100)
  raise RuntimeError
end
Process.monitor pid

receive do
  {:DOWN, ref, :process, pid, msg} ->
    IO.inspect ref
    IO.inspect pid
    IO.inspect msg
end
IO.puts ""

IO.puts "## Process.link"
pid = spawn fn ->
  pid = spawn fn ->
    :timer.sleep(500)
    raise RuntimeError
  end
  Process.link pid
  IO.puts "start to link with other process"
  :timer.sleep(2000)
  IO.puts "never display this message"
end
Process.monitor pid
IO.puts "start the linking process"
IO.inspect pid

receive do
  {:DOWN, ref, :process, pid, msg} ->
    IO.puts "killed the linking process"
    IO.inspect pid
    IO.inspect msg
end
IO.puts ""


Remove confusing current_user parameter from TweakController actions

defmodule AtomTweaksWeb.TweakController do
  use AtomTweaksWeb, :controller

  alias AtomTweaks.Tweaks
  alias AtomTweaks.Tweaks.Tweak
  alias AtomTweaksWeb.NotLoggedInError
  alias AtomTweaksWeb.PageMetadata
  alias AtomTweaksWeb.WrongUserError

  require Logger

  @doc """
  Creates a new tweak with the given parameters.
  """
  @spec create(Plug.Conn.t(), Map.t()) :: Plug.Conn.t()
  def create(conn, params)

  def create(conn = %{assigns: %{current_user: nil}}, _), do: raise(NotLoggedInError, conn: conn)

  def create(conn, %{"tweak" => tweak_params}) do
    current_user = conn.assigns.current_user
    params = Map.merge(tweak_params, %{"created_by" => current_user.id})
    changeset = Tweak.changeset(%Tweak{}, params)

    case Repo.insert(changeset) do
      {:ok, tweak} -> redirect(conn, to: tweak_path(conn, :show, tweak))
      {:error, changeset} -> render(conn, "new.html", changeset: changeset)
    end
  end

  @doc """
  Deletes the given tweak.

  **Not implemented.**
  """
  @spec delete(Plug.Conn.t(), Map.t()) :: Plug.Conn.t()
  def delete(conn, _params) do
    conn
  end

  @doc """
  Displays the edit tweak form.
  """
  @spec edit(Plug.Conn.t(), Map.t()) :: Plug.Conn.t()
  def edit(conn, params)

  def edit(conn = %{assigns: %{current_user: nil}}, _), do: raise(NotLoggedInError, conn: conn)

  def edit(conn, %{"id" => id}) do
    current_user = conn.assigns.current_user

    tweak =
      Tweak
      |> Repo.get(id)
      |> Repo.preload([:user])

    if current_user.id != tweak.user.id do
      raise WrongUserError, conn: conn, current_user: current_user, resource_owner: tweak.user
    end

    changeset = Tweak.changeset(tweak)

    render(
      conn,
      "edit.html",
      changeset: changeset,
      tweak: tweak
    )
  end

  @doc """
  Displays the new tweak form.
  """
  @spec new(Plug.Conn.t(), Map.t()) :: Plug.Conn.t()
  def new(conn, params)

  def new(conn = %{assigns: %{current_user: nil}}, _), do: raise(NotLoggedInError, conn: conn)

  def new(conn, _params) do
    changeset = Tweaks.change_tweak(%Tweak{})

    render(conn, "new.html", changeset: changeset)
  end

  @doc """
  Displays the given tweak.
  """
  @spec show(Plug.Conn.t(), Map.t()) :: Plug.Conn.t()
  def show(conn, params)

  def show(conn, %{"id" => id}) do
    current_user = conn.assigns.current_user

    tweak =
      Tweak
      |> Repo.get(id)
      |> Repo.preload([:stargazers, :user])

    starred = Tweaks.is_starred?(tweak, current_user)

    conn
    |> PageMetadata.add(Tweak.to_metadata(tweak))
    |> render("show.html", starred: starred, tweak: tweak)
  end

  @doc """
  Updates a tweak with the given parameters.
  """
  @spec update(Plug.Conn.t(), Map.t()) :: Plug.Conn.t()
  def update(conn, params)

  def update(conn, %{"user_id" => name, "id" => id, "tweak" => tweak_params}) do
    tweak = Repo.get(Tweak, id)
    changeset = Tweak.changeset(tweak, tweak_params)

    case Repo.update(changeset) do
      {:ok, tweak} ->
        redirect(conn, to: tweak_path(conn, :show, tweak))

      {:error, changeset} ->
        render(
          conn,
          "edit.html",
          name: name,
          tweak: tweak,
          changeset: changeset,
          errors: changeset.errors
        )
    end
  end
end

defmodule AtomTweaksWeb.TweakController do
  use AtomTweaksWeb, :controller

  alias AtomTweaks.Accounts.User
  alias AtomTweaks.Tweaks
  alias AtomTweaks.Tweaks.Tweak
  alias AtomTweaksWeb.NotLoggedInError
  alias AtomTweaksWeb.PageMetadata
  alias AtomTweaksWeb.WrongUserError

  require Logger

  @type current_user :: User.t() | atom

  @doc """
  Adds a third argument to every action that contains the current user or the atom `:guest`.

  Runs before all other actions in the controller.
  """
  @spec action(Plug.Conn.t(), Map.t()) :: Plug.Conn.t()
  def action(conn, params)

  def action(conn, _) do
    args = [conn, conn.params, conn.assigns[:current_user] || :guest]
    apply(__MODULE__, action_name(conn), args)
  end

  @doc """
  Creates a new tweak with the given parameters.
  """
  @spec create(Plug.Conn.t(), Map.t(), current_user) :: Plug.Conn.t()
  def create(conn, params, current_user)

  def create(conn, _, :guest), do: raise(NotLoggedInError, conn: conn)

  def create(conn, %{"tweak" => tweak_params}, current_user) do
    params = Map.merge(tweak_params, %{"created_by" => current_user.id})
    changeset = Tweak.changeset(%Tweak{}, params)

    case Repo.insert(changeset) do
      {:ok, tweak} -> redirect(conn, to: tweak_path(conn, :show, tweak))
      {:error, changeset} -> render(conn, "new.html", changeset: changeset)
    end
  end

  @doc """
  Deletes the given tweak.

  **Not implemented.**
  """
  @spec delete(Plug.Conn.t(), Map.t(), current_user) :: Plug.Conn.t()
  def delete(conn, _params, _current_user) do
    conn
  end

  @doc """
  Displays the edit tweak form.
  """
  @spec edit(Plug.Conn.t(), Map.t(), current_user) :: Plug.Conn.t()
  def edit(conn, params, current_user)

  def edit(conn, _, :guest), do: raise(NotLoggedInError, conn: conn)

  def edit(conn, %{"id" => id}, current_user) do
    tweak =
      Tweak
      |> Repo.get(id)
      |> Repo.preload([:user])

    if current_user.id != tweak.user.id do
      raise WrongUserError, conn: conn, current_user: current_user, resource_owner: tweak.user
    end

    changeset = Tweak.changeset(tweak)

    render(
      conn,
      "edit.html",
      changeset: changeset,
      tweak: tweak
    )
  end

  @doc """
  Displays the new tweak form.
  """
  @spec new(Plug.Conn.t(), Map.t(), current_user) :: Plug.Conn.t()
  def new(conn, params, current_user)

  def new(conn, _, :guest), do: raise(NotLoggedInError, conn: conn)

  def new(conn, _params, _current_user) do
    changeset = Tweaks.change_tweak(%Tweak{})

    render(conn, "new.html", changeset: changeset)
  end

  @doc """
  Displays the given tweak.
  """
  @spec show(Plug.Conn.t(), Map.t(), current_user) :: Plug.Conn.t()
  def show(conn, params, current_user)

  def show(conn, %{"id" => id}, current_user) do
    tweak =
      Tweak
      |> Repo.get(id)
      |> Repo.preload([:stargazers, :user])

    starred = Tweaks.is_starred?(tweak, current_user)

    conn
    |> PageMetadata.add(Tweak.to_metadata(tweak))
    |> render("show.html", starred: starred, tweak: tweak)
  end

  @doc """
  Updates a tweak with the given parameters.
  """
  @spec update(Plug.Conn.t(), Map.t(), current_user) :: Plug.Conn.t()
  def update(conn, params, current_user)

  def update(conn, %{"user_id" => name, "id" => id, "tweak" => tweak_params}, _current_user) do
    tweak = Repo.get(Tweak, id)
    changeset = Tweak.changeset(tweak, tweak_params)

    case Repo.update(changeset) do
      {:ok, tweak} ->
        redirect(conn, to: tweak_path(conn, :show, tweak))

      {:error, changeset} ->
        render(
          conn,
          "edit.html",
          name: name,
          tweak: tweak,
          changeset: changeset,
          errors: changeset.errors
        )
    end
  end
end