Files
archived-hipudding-teslamate/lib/teslamate/import/fake_api.ex
2021-02-06 15:24:09 +01:00

179 lines
4.9 KiB
Elixir

defmodule TeslaMate.Import.FakeApi do
use GenServer
require Logger
alias TeslaApi.Vehicle
defmodule State do
defstruct pid: nil,
from: nil,
events: [],
event_chunks: {%{}, _idx = nil, _max_idx = nil},
event_streams: [],
current_chunk: nil,
date_limit: nil
end
# API
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: Keyword.fetch!(opts, :name))
end
def get_vehicle(name, _id) do
GenServer.call(name, :get_vehicle, :infinity)
end
def get_vehicle_with_state(name, _id) do
GenServer.call(name, :get_vehicle_with_state, :infinity)
end
def stream(_name, _vid, _receiver) do
{:ok, nil}
end
# Callbacks
@impl true
def init(opts) do
pid = Keyword.fetch!(opts, :pid)
event_streams = Keyword.fetch!(opts, :event_streams)
date_limit = Keyword.fetch!(opts, :date_limit)
date_limit_ts =
with %DateTime{} <- date_limit do
DateTime.to_unix(date_limit) * 1000
end
{:ok, %State{pid: pid, event_streams: event_streams, date_limit: date_limit_ts}}
end
@impl true
def handle_call(_action, from, %State{} = state) do
case pop(state) do
{:error, :chunk_not_yet_received, state} ->
{:noreply, %State{state | from: from}}
{:done, state} ->
processing_complete(state)
{:noreply, state}
{:event, %Vehicle{} = vehicle, state}
when vehicle.drive_state.timestamp >= state.date_limit ->
processing_complete(state)
{:noreply, state}
{:event, %Vehicle{} = vehicle, state} ->
{:reply, {:ok, vehicle}, state}
end
end
@impl true
def handle_info({:processed_events, max_idx}, %State{} = state) do
{:noreply, %State{state | event_chunks: set_max_chunk_idx(state.event_chunks, max_idx)}}
end
def handle_info({:events, events, idx}, %State{from: nil} = state) do
{:noreply, %State{state | event_chunks: insert_event_chunk(state.event_chunks, idx, events)}}
end
def handle_info({:events, events, idx}, %State{from: from} = state) do
state = %State{
state
| event_chunks: insert_event_chunk(state.event_chunks, idx, events),
from: nil
}
case pop(state) do
{:done, state} ->
processing_complete(state)
{:noreply, state}
{:event, %Vehicle{} = vehicle, state}
when vehicle.drive_state.timestamp >= state.date_limit ->
processing_complete(state)
{:noreply, state}
{:event, %Vehicle{} = vehicle, state} ->
GenServer.reply(from, {:ok, vehicle})
{:noreply, state}
end
end
def handle_info(:abort, %State{} = state) do
{:noreply, %State{state | events: [], event_chunks: {%{}, 0, 0}, event_streams: []}}
end
## Private
defp pop(%State{events: [], event_chunks: {chunks, i, i}, event_streams: []} = state)
when is_number(i) and chunks == %{} do
{:done, state}
end
defp pop(%State{events: [], event_chunks: {_, i, i}, event_streams: [{c, s} | streams]} = state) do
if state.current_chunk != nil, do: send(state.pid, {:done, state.current_chunk})
state = %State{state | event_streams: streams, current_chunk: c}
parent = self()
spawn_link(fn ->
try do
s
|> Stream.chunk_every(500)
|> Stream.with_index()
|> Enum.reduce(-1, fn {event_chunk, idx}, _ ->
send(parent, {:events, event_chunk, idx})
idx
end)
|> case do
-1 -> send(parent, :no_events)
max_idx -> send(parent, {:processed_events, max_idx})
end
catch
:vehicle_changed -> send(parent, :abort)
end
end)
receive do
:abort ->
pop(%State{state | events: [], event_chunks: {%{}, 0, 0}, event_streams: []})
:no_events ->
Logger.warning("Processed empty chunk: #{inspect(c)}")
pop(%State{state | events: [], event_chunks: {%{}, nil, nil}})
{:events, [event | events], 0} ->
{:event, event, %State{state | events: events, event_chunks: {%{}, 0, nil}}}
end
end
defp pop(%State{events: [], event_chunks: {event_chunks, idx, max_idx}} = state) do
case Map.pop(event_chunks, new_idx = idx + 1) do
{nil, _event_chunks} ->
{:error, :chunk_not_yet_received, state}
{events, event_chunks} ->
pop(%State{state | events: events, event_chunks: {event_chunks, new_idx, max_idx}})
end
end
defp pop(%State{events: [event | events]} = state) do
{:event, event, %State{state | events: events}}
end
defp insert_event_chunk({chunks, chunk_idx, chunk_max_idx}, idx, events) do
{Map.put(chunks, idx, events), chunk_idx, chunk_max_idx}
end
defp set_max_chunk_idx({chunks, chunk_idx, _}, chunk_max_idx) do
{chunks, chunk_idx, chunk_max_idx}
end
defp processing_complete(%State{} = state) do
send(state.pid, {:done, state.current_chunk})
send(state.pid, :done)
end
end