212 lines
6.4 KiB
Elixir
212 lines
6.4 KiB
Elixir
defmodule Farmbot.HTTP do
|
|
@moduledoc "Wraps an HTTP Adapter."
|
|
|
|
# credo:disable-for-this-file Credo.Check.Refactor.FunctionArity
|
|
|
|
use GenServer
|
|
alias Farmbot.HTTP.{Adapter, Error, Response}
|
|
alias Farmbot.JSON
|
|
|
|
@adapter Application.get_env(:farmbot_ext, :behaviour)[:http_adapter]
|
|
@adapter || raise("No http adapter.")
|
|
|
|
@typep method :: Adapter.method
|
|
@typep url :: Adapter.url
|
|
@typep body :: Adapter.body
|
|
@typep headers :: Adapter.headers
|
|
@typep opts :: Adapter.opts
|
|
|
|
alias Farmbot.Asset.{
|
|
Device,
|
|
FarmEvent,
|
|
Peripheral,
|
|
PinBinding,
|
|
Point,
|
|
Regimen,
|
|
Sensor,
|
|
Sequence,
|
|
Tool,
|
|
}
|
|
|
|
def device, do: fetch_and_decode("/api/device.json", Device)
|
|
def farm_events, do: fetch_and_decode("/api/farm_events.json", FarmEvent)
|
|
def peripherals, do: fetch_and_decode("/api/peripherals.json", Peripheral)
|
|
def pin_bindings, do: fetch_and_decode("/api/pin_bindings.json", PinBinding)
|
|
def points, do: fetch_and_decode("/api/points.json", Point)
|
|
def regimens, do: fetch_and_decode("/api/regimens.json", Regimen)
|
|
def sensors, do: fetch_and_decode("/api/sensors.json", Sensor)
|
|
def sequences, do: fetch_and_decode("/api/sequences.json", Sequence)
|
|
def tools, do: fetch_and_decode("/api/tools.json", Tool)
|
|
|
|
def fetch_and_decode(url, kind) do
|
|
url
|
|
|> get!()
|
|
|> Map.fetch!(:body)
|
|
|> JSON.decode!()
|
|
|> Farmbot.Asset.to_asset(kind)
|
|
end
|
|
|
|
@doc """
|
|
Make an http request. Will not raise.
|
|
* `method` - can be any http verb
|
|
* `url` - fully formatted url or an api slug.
|
|
* `body` - body can be any of:
|
|
* binary
|
|
* `{:multipart, [{binary_key, binary_value}]}`
|
|
* headers - `[{binary_key, binary_value}]`
|
|
* opts - Keyword opts to be passed to adapter (hackney/httpoison)
|
|
* `file` - option to be passed if the output should be saved to a file.
|
|
"""
|
|
@spec request(method, url, body, headers, opts)
|
|
:: {:ok, Response.t} | {:error, term}
|
|
def request(method, url, body \\ "", headers \\ [], opts \\ [])
|
|
|
|
def request(method, url, body, headers, opts) do
|
|
call = {:request, method, url, body, headers, opts}
|
|
GenServer.call(__MODULE__, call, :infinity)
|
|
end
|
|
|
|
@doc "Same as `request/5` but raises."
|
|
@spec request!(method, url, body, headers, opts) :: Response.t | no_return
|
|
def request!(method, url, body \\ "", headers \\ [], opts \\ [])
|
|
|
|
def request!(method, url, body, headers, opts) do
|
|
case request(method, url, body, headers, opts) do
|
|
{:ok, %Response{status_code: code} = resp}
|
|
when code > 199 and code < 300 -> resp
|
|
|
|
{:ok, %Response{} = resp} -> raise Error, resp
|
|
|
|
{:error, reason} -> raise Error, reason
|
|
end
|
|
end
|
|
|
|
@doc "HTTP GET request."
|
|
@spec get(url, headers, opts) :: {:ok, Response.t} | {:error, term}
|
|
def get(url, headers \\ [], opts \\ [])
|
|
|
|
def get(url, headers, opts) do
|
|
request(:get, url, "", headers, opts)
|
|
end
|
|
|
|
@doc "Same as `get/3` but raises."
|
|
@spec get!(url, headers, opts) :: Response.t | no_return
|
|
def get!(url, headers \\ [], opts \\ [])
|
|
|
|
def get!(url, headers, opts) do
|
|
request!(:get, url, "", headers, opts)
|
|
end
|
|
|
|
@doc "HTTP POST request."
|
|
@spec post(url, headers, opts) :: {:ok, Response.t} | {:error, term}
|
|
def post(url, body, headers \\ [], opts \\ [])
|
|
|
|
def post(url, body, headers, opts) do
|
|
request(:post, url, body, headers, opts)
|
|
end
|
|
|
|
@doc "Same as `post/4` but raises."
|
|
@spec post!(url, body, headers, opts) :: Response.t | no_return
|
|
def post!(url, body, headers \\ [], opts \\ [])
|
|
|
|
def post!(url, body, headers, opts) do
|
|
request!(:post, url, body, headers, opts)
|
|
end
|
|
|
|
@doc "HTTP PUT request."
|
|
@spec put(url, body, headers, opts) :: {:ok, Response.t} | {:error, term}
|
|
def put(url, body, headers \\ [], opts \\ [])
|
|
|
|
def put(url, body, headers, opts) do
|
|
request(:put, url, body, headers, opts)
|
|
end
|
|
|
|
@doc "Same as `put/4` but raises."
|
|
@spec put!(url, body, headers, opts) :: Response.t | no_return
|
|
def put!(url, body, headers \\ [], opts \\ [])
|
|
|
|
def put!(url, body, headers, opts) do
|
|
request!(:put, url, body, headers, opts)
|
|
end
|
|
|
|
@doc "HTTP DELETE request."
|
|
@spec delete(url, headers, opts) :: {:ok, Response.t} | {:error, term}
|
|
def delete(url, headers \\ [], opts \\ [])
|
|
|
|
def delete(url, headers, opts) do
|
|
request!(:delete, url, "", headers, opts)
|
|
end
|
|
|
|
@doc "Same as `delete/3` but raises."
|
|
@spec delete!(url, headers, opts) :: Response.t | no_return
|
|
def delete!(url, headers \\ [], opts \\ [])
|
|
|
|
def delete!(url, headers, opts) do
|
|
request!(:delete, url, "", headers, opts)
|
|
end
|
|
|
|
@doc "Download a file to the filesystem."
|
|
def download_file(url,
|
|
path,
|
|
progress_callback \\ nil,
|
|
payload \\ "",
|
|
headers \\ [],
|
|
stream_fun \\ nil)
|
|
|
|
def download_file(url, path, progress_callback, payload, hddrs, stream_fun) do
|
|
opts = {url, path, progress_callback, payload, hddrs, stream_fun}
|
|
call = {:download_file, opts}
|
|
GenServer.call(__MODULE__, call, :infinity)
|
|
end
|
|
|
|
@doc "Upload a file to FB storage."
|
|
def upload_file(path, meta \\ nil) do
|
|
if File.exists?(path) do
|
|
GenServer.call(__MODULE__, {:upload_file, {path, meta}}, :infinity)
|
|
else
|
|
{:error, "#{path} not found"}
|
|
end
|
|
end
|
|
|
|
@doc "Start HTTP Services."
|
|
def start_link(args) do
|
|
GenServer.start_link(__MODULE__, args, name: __MODULE__)
|
|
end
|
|
|
|
def init([]) do
|
|
{:ok, adapter} = @adapter.start_link()
|
|
Process.link(adapter)
|
|
{:ok, %{adapter: adapter}}
|
|
end
|
|
|
|
def handle_call({:request, _, _, _, _, _} = req, _from, state) do
|
|
{:request, method, url, body, headers, opts} = req
|
|
args = [state.adapter, method, url, body, headers, opts]
|
|
res = case apply(@adapter, :request, args) do
|
|
{:ok, %Response{}} = res -> res
|
|
{:error, _} = res -> res
|
|
end
|
|
{:reply, res, state}
|
|
end
|
|
|
|
def handle_call({:download_file, call}, _from, %{adapter: adapter} = state) do
|
|
{url, path, progress_callback, payload, headers, stream_fun} = call
|
|
args = [adapter, url, path, progress_callback, payload, headers, stream_fun]
|
|
res = case apply(@adapter, :download_file, args) do
|
|
{:ok, _} = res -> res
|
|
{:error, _} = res -> res
|
|
end
|
|
{:reply, res, state}
|
|
end
|
|
|
|
def handle_call({:upload_file, {path, meta}}, _from, state) do
|
|
meta_arg = meta || %{x: -1, y: -1, z: -1}
|
|
args = [state.adapter, path, meta_arg]
|
|
res = case apply(@adapter, :upload_file, args) do
|
|
{:ok, _} = res -> res
|
|
{:error, _} = res -> res
|
|
end
|
|
{:reply, res, state}
|
|
end
|
|
end
|