farmbot_os/farmbot_ext/lib/farmbot_ext/api/dirty_worker.ex

143 lines
3.9 KiB
Elixir
Raw Normal View History

2019-03-05 11:38:25 -07:00
defmodule FarmbotExt.API.DirtyWorker do
@moduledoc "Handles uploading/downloading of data from the API."
2019-03-05 11:38:25 -07:00
alias FarmbotCore.Asset.{Private, Repo}
alias FarmbotExt.{API, API.DirtyWorker}
import API.View, only: [render: 2]
require Logger
2020-05-15 17:42:16 -06:00
require FarmbotCore.Logger
use GenServer
2020-05-16 18:02:10 -06:00
@timeout 1
# these resources can't be accessed by `id`.
@singular [
FarmbotCore.Asset.Device,
FarmbotCore.Asset.FirmwareConfig,
FarmbotCore.Asset.FbosConfig
]
@doc false
def child_spec(module) when is_atom(module) do
%{
2019-03-05 11:38:25 -07:00
id: {DirtyWorker, module},
start: {__MODULE__, :start_link, [[module: module, timeout: @timeout]]},
type: :worker,
restart: :permanent,
shutdown: 500
}
end
@doc "Start an instance of a DirtyWorker"
def start_link(args) do
GenServer.start_link(__MODULE__, args)
end
@impl GenServer
def init(args) do
module = Keyword.fetch!(args, :module)
2020-05-15 19:11:37 -06:00
Process.send_after(self(), :do_work, @timeout)
2020-05-15 17:24:57 -06:00
{:ok, %{module: module}}
end
@impl GenServer
2020-05-15 17:24:57 -06:00
def handle_info(:do_work, %{module: module} = state) do
Process.sleep(1000)
list = Enum.uniq((Private.list_dirty(module) ++ Private.list_local(module)))
stale = Enum.find(list, fn item ->
2020-05-17 12:13:16 -06:00
if (item.id) do
if item == Repo.get_by(module, id: item.id) do
false
else
IO.inspect(item, label: "=== STALE RECORD DETECTED!")
true
2020-05-17 12:25:16 -06:00
end
2020-05-17 12:13:16 -06:00
else
false
2020-05-17 12:13:16 -06:00
end
end)
2020-05-17 10:04:55 -06:00
if stale do
Process.send_after(self(), :do_work, @timeout)
{:noreply, state}
else
Enum.map(list, fn dirty -> work(dirty, module) end)
Process.send_after(self(), :do_work, @timeout)
{:noreply, state}
end
end
2020-05-15 17:24:57 -06:00
def work(dirty, module) do
case http_request(dirty, module) do
# Valid data
{:ok, %{status: s, body: body}} when s > 199 and s < 300 ->
2020-05-15 17:24:57 -06:00
dirty |> module.changeset(body) |> handle_changeset(module)
# Invalid data
{:ok, %{status: s, body: %{} = body}} when s > 399 and s < 500 ->
2020-05-15 17:42:16 -06:00
FarmbotCore.Logger.error(2, "HTTP Error #{s}. #{inspect(body)}")
changeset = module.changeset(dirty)
Enum.reduce(body, changeset, fn {key, val}, changeset ->
Ecto.Changeset.add_error(changeset, key, val)
end)
2020-05-15 17:24:57 -06:00
|> handle_changeset(module)
# Invalid data, but the API didn't say why
{:ok, %{status: s, body: _body}} when s > 399 and s < 500 ->
2020-05-15 17:42:16 -06:00
FarmbotCore.Logger.error(2, "HTTP Error #{s}. #{inspect(dirty)}")
2020-05-16 08:11:49 -06:00
module.changeset(dirty)
|> Map.put(:valid?, false)
2020-05-15 17:24:57 -06:00
|> handle_changeset(module)
# HTTP Error. (500, network error, timeout etc.)
2019-03-25 10:41:58 -06:00
error ->
2020-05-16 08:11:49 -06:00
FarmbotCore.Logger.error(
2,
2020-05-15 17:24:57 -06:00
"[#{module} #{dirty.local_id} #{inspect(self())}] HTTP Error: #{module} #{
2019-04-18 10:32:34 -06:00
inspect(error)
}"
)
end
end
# If the changeset was valid, update the record.
2020-05-15 17:24:57 -06:00
def handle_changeset(%{valid?: true} = changeset, _module) do
Private.mark_clean!(Repo.update!(changeset))
:ok
end
2020-05-15 17:24:57 -06:00
def handle_changeset(%{valid?: false, data: data} = changeset, module) do
message =
2019-04-18 10:32:34 -06:00
Enum.map(changeset.errors, fn
{key, {msg, _meta}} when is_binary(key) -> "\t#{key}: #{msg}"
{key, msg} when is_binary(key) -> "\t#{key}: #{msg}"
end)
|> Enum.join("\n")
2020-05-16 08:11:49 -06:00
FarmbotCore.Logger.error(3, "Failed to sync: #{module} \n #{message}")
_ = Repo.delete!(data)
2020-05-15 17:24:57 -06:00
:ok
end
2020-05-15 17:24:57 -06:00
defp http_request(%{id: nil} = dirty, module) do
path = module.path()
data = render(module, dirty)
API.post(API.client(), path, data)
end
2020-05-15 17:24:57 -06:00
defp http_request(dirty, module) when module in @singular do
path = path = module.path()
data = render(module, dirty)
API.patch(API.client(), path, data)
end
2020-05-15 17:24:57 -06:00
defp http_request(dirty, module) do
path = Path.join(module.path(), to_string(dirty.id))
data = render(module, dirty)
API.patch(API.client(), path, data)
end
end