mirror of
https://github.com/teslamate-org/teslamate.git
synced 2026-01-24 21:06:08 +08:00
153 lines
3.6 KiB
Elixir
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
|