lib/mdns_lite/vintage_net_monitor.ex

defmodule MdnsLite.VintageNetMonitor do
  @moduledoc """
  Network monitor that using VintageNet

  Use this network monitor to detect new network interfaces and their
  IP addresses when using Nerves. It is the default.
  """
  use GenServer

  alias MdnsLite.CoreMonitor

  @addresses_topic ["interface", :_, "addresses"]

  @spec start_link([CoreMonitor.option()]) :: GenServer.on_start()
  def start_link(opts \\ []) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  @impl GenServer
  def init(opts) do
    # VintageNet is an optional dependency. Optional dependencies aren't
    # guaranteed to be started in order. See
    # https://github.com/elixir-lang/elixir/issues/10433. To work around this,
    # try to start it.
    _ = Application.ensure_all_started(:vintage_net)

    VintageNet.subscribe(@addresses_topic)

    {:ok, CoreMonitor.init(opts), {:continue, :initialization}}
  end

  @impl GenServer
  def handle_continue(:initialization, state) do
    new_state =
      VintageNet.match(@addresses_topic)
      |> Enum.reduce(state, &set_vn_address_reducer/2)
      |> CoreMonitor.flush_todo_list()

    {:noreply, new_state}
  end

  @impl GenServer
  def handle_info({VintageNet, ["interface", ifname, "addresses"], _old, new, _}, state) do
    new_state =
      state
      |> set_vn_address(ifname, new)
      |> CoreMonitor.flush_todo_list()

    {:noreply, new_state}
  end

  defp set_vn_address_reducer({["interface", ifname, "addresses"], addresses}, state) do
    set_vn_address(state, ifname, addresses)
  end

  defp set_vn_address(state, ifname, nil) do
    # nil gets passed when the network interface goes away.
    set_vn_address(state, ifname, [])
  end

  defp set_vn_address(state, ifname, addresses) do
    ip_list = Enum.map(addresses, fn %{address: ip} -> ip end)
    CoreMonitor.set_ip_list(state, ifname, ip_list)
  end
end