defmodule Podbox.Download.Producer do @moduledoc """ A GenStage producer which only produces values when connected to the internet. This only works when `VintageNet` is present (ie we're running on a target), otherwise we just assume that we're always connected. """ use GenStage alias Podbox.{Download, Download.Asset, Download.ConnectivityMonitor, Download.PubSub} @poll_interval :timer.seconds(30) @type state :: %{ connected?: boolean, outstanding_demand: non_neg_integer() } @doc false @spec start_link(Keyword.t()) :: GenServer.on_start() def start_link(opts), do: GenStage.start_link(__MODULE__, opts, name: __MODULE__) @doc "Return the current status of the producer" def status, do: GenServer.call(__MODULE__, :status) @doc false @impl true @spec init(Keyword.t()) :: {:producer, state} def init(_opts) do PubSub.subscribe("internet:connected?") PubSub.subscribe("asset:queued") :timer.send_interval(@poll_interval, :poll) {:producer, %{connected?: ConnectivityMonitor.connected?(), outstanding_demand: 0}} end @doc false @impl true @spec handle_info(any, state) :: {:noreply, [], state} def handle_info({"internet:connected?", connect_status}, state), do: {:noreply, [], %{state | connected?: connect_status}} def handle_info({"asset:queued", _}, state) when state.connected? and state.outstanding_demand > 0 do produce(0, state) end def handle_info({"asset:queued", _}, state), do: {:noreply, [], state} def handle_info(:poll, state) when state.connected? do produce(0, state) end def handle_info(:poll, state), do: {:noreply, [], state} @doc false @impl true def handle_call(:status, _, state) do {:reply, state, [], state} end @doc false @impl true @spec handle_demand(non_neg_integer, state) :: {:noreply, [Asset.t()], state} def handle_demand(demand, state) when state.connected? do produce(demand, state) end def handle_demand(demand, state), do: {:noreply, [], %{state | outstanding_demand: state.outstanding_demand + demand}} defp produce(demand, state) do demand = state.outstanding_demand + demand if demand == 0 do {:noreply, [], state} else dequeued = Download.dequeue!(demand) demand = demand - length(dequeued) {:noreply, dequeued, %{state | outstanding_demand: demand}} end end end