mirror of
https://github.com/netfun2000/hipudding-teslamate.git
synced 2026-02-27 09:44:28 +08:00
179 lines
4.9 KiB
Elixir
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
|