lib/code_reloader.ex

defmodule OauthMockServer.CodeReloader do
  @moduledoc "Hot reloading of code in the lib folder"

  use GenServer

  require Logger

  alias Mix.Tasks.Compile.Elixir, as: ExCompile

  def start_link do
    Process.send_after(__MODULE__, :poll_and_reload, 5000)
    GenServer.start_link(__MODULE__, %{}, name: OauthMockServer.CodeReloader)
  end

  def init(args), do: {:ok, args}

  def handle_info(:poll_and_reload, state) do
    current_mtime = get_current_mtime("lib")
    handle_path("lib", current_mtime, state)

    Process.send_after(__MODULE__, :poll_and_reload, 3000)
    {:noreply, current_mtime}
  end

  def handle_info(_msg, state), do: {:noreply, state}

  defp handle_path(path, current_mtime, current_mtime), do: {path, current_mtime}

  defp handle_path(path, current_mtime, _) do
    Logger.info("Files changed on disk. Recompiling...")
    ExCompile.run(["--ignore-module-conflict"])
    {path, current_mtime}
  end

  defp get_current_mtime(dir) do
    case File.ls(dir) do
      {:ok, files} -> get_current_mtime(files, [], dir)
      _ -> nil
    end
  end

  defp get_current_mtime([], mtimes, _cwd) do
    mtimes |> Enum.sort() |> Enum.reverse() |> List.first()
  end

  defp get_current_mtime([h | tail], mtimes, cwd) do
    mtime =
      case File.dir?("#{cwd}/#{h}") do
        true -> get_current_mtime("#{cwd}/#{h}")
        false -> File.stat!("#{cwd}/#{h}").mtime
      end

    get_current_mtime(tail, [mtime | mtimes], cwd)
  end
end