Files
archived-teslamate/lib/teslamate/repair.ex
T
2023-06-10 18:44:18 +02:00

153 lines
3.6 KiB
Elixir

defmodule TeslaMate.Repair do
use GenServer
require Logger
import Ecto.Query
alias TeslaMate.Log.{Drive, Position, ChargingProcess}
alias TeslaMate.Locations.Address
alias TeslaMate.{Repo, Locations}
defmodule State do
defstruct [:limit]
end
# API
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
def trigger_run do
GenServer.cast(__MODULE__, :repair)
end
@impl true
def init(opts) do
{:ok, _ref} =
opts
|> Keyword.get_lazy(:interval, fn -> :timer.hours(1) end)
|> :timer.send_interval(self(), :repair)
:ok = trigger_run()
{:ok, %State{limit: Keyword.get(opts, :limit, 5000)}}
end
## Repair
@impl true
def handle_cast(:repair, %State{limit: limit} = state) do
from(d in Drive,
join: sp in assoc(d, :start_position),
join: ep in assoc(d, :end_position),
select: [
:id,
:car_id,
:start_date,
{:start_position, [:id, :latitude, :longitude]},
{:end_position, [:id, :latitude, :longitude]}
],
where:
(is_nil(d.start_address_id) or is_nil(d.end_address_id)) and
(not is_nil(d.start_position_id) and not is_nil(d.end_position_id)),
order_by: [desc: :id],
preload: [start_position: sp, end_position: ep],
limit: ^limit
)
|> Repo.all()
|> repair()
from(c in ChargingProcess,
join: p in assoc(c, :position),
select: [:id, :car_id, :start_date, {:position, [:id, :latitude, :longitude]}],
where: is_nil(c.address_id) and not is_nil(c.position_id),
order_by: [desc: :id],
preload: [position: p],
limit: ^limit
)
|> Repo.all()
|> repair()
{:noreply, state}
end
@impl true
def handle_info(:repair, state) do
:ok = trigger_run()
{:noreply, state}
end
def handle_info(msg, state) do
Logger.warning("Unexpected message: #{inspect(msg, pretty: true)}")
{:noreply, state}
end
# Private
defp repair([]), do: :ok
defp repair([entity | rest]) do
case entity do
%Drive{} = drive ->
Logger.info("Repairing drive ##{drive.id} ...")
drive
|> Drive.changeset(%{
start_address_id: get_address_id(drive.start_position),
end_address_id: get_address_id(drive.end_position)
})
|> Repo.update()
%ChargingProcess{} = charge ->
Logger.info("Repairing charging process ##{charge.id} ...")
charge
|> ChargingProcess.changeset(%{address_id: get_address_id(charge.position)})
|> Repo.update()
end
|> case do
{:error, reason} -> Logger.warning("Failure: #{inspect(reason, pretty: true)}")
{:ok, _entity} -> Logger.info("OK")
end
repair(rest)
end
defp get_address_id(nil), do: nil
defp get_address_id(%Position{} = position) do
case :fuse.ask(:addr_fuse, :sync) do
:ok ->
Process.sleep(1500)
case Locations.find_address(position) do
{:error, {:geocoding_failed, reason}} ->
Logger.warning("Geocoding failed: #{reason}")
nil
{:error, reason} ->
:fuse.melt(:addr_fuse)
Logger.warning("Address not found: #{inspect(reason)}")
nil
{:ok, %Address{display_name: _name, id: id}} ->
id
end
:blown ->
nil
{:error, :not_found} ->
Logger.debug("Installing circuit-breaker :addr_fuse ...")
:fuse.install(
:addr_fuse,
{{:standard, 5, :timer.minutes(3)}, {:reset, :timer.minutes(15)}}
)
get_address_id(position)
end
end
end