defmodule LSG.Icecast do
use GenServer
require Logger
@hackney_pool :default
@httpoison_opts [hackney: [pool: @hackney_pool]]
@fuse __MODULE__
def start_link, do: GenServer.start_link(__MODULE__, [], [])
def init(_) do
GenServer.cast(self(), :poll)
{:ok, nil}
end
def handle_cast(:poll, state) do
state = poll(state)
{:noreply, state}
end
def handle_info(:poll, state) do
state = poll(state)
{:noreply, state}
end
defp poll(state) do
state = case request(base_url(), :get) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
#update_json_stats(Jason.decode(body))
stats = update_stats(body)
if state != stats do
Logger.info "Icecast Update: " <> inspect(stats)
LSG.IcecastAgent.update(stats)
Registry.dispatch(LSG.BroadcastRegistry, "icecast", fn ws ->
for {pid, _} <- ws, do: send(pid, {:icecast, stats})
end)
stats
else
state
end
error ->
Logger.error "Icecast HTTP Error: #{inspect error}"
state
end
interval = Application.get_env(:lsg, :icecast_poll_interval, 60_000)
:timer.send_after(interval, :poll)
state
end
defp update_stats(html) do
raw = Floki.find(html, "div.roundbox")
|> Enum.map(fn(html) ->
html = Floki.raw_html(html)
[{"h3", _, ["Mount Point /"<>mount]}] = Floki.find(html, "h3.mount")
stats = Floki.find(html, "tr")
|> Enum.map(fn({"tr", _, tds}) ->
[{"td", _, keys}, {"td", _, values}] = tds
key = List.first(keys)
value = List.first(values)
{key, value}
end)
|> Enum.into(Map.new)
{mount, stats}
end)
|> Enum.into(Map.new)
live? = if Map.get(raw["live"], "Content Type:", false), do: true, else: false
np = if live? do
raw["live"]["Currently playing:"]
else
raw["autodj"]["Currently playing:"]
end
genre = raw["live"]["Genre:"] || nil
%{np: np || "", live: live? || false, genre: genre}
end
defp update_json_stats({:ok, body}) do
Logger.debug "JSON STATS: #{inspect body}"
end
defp update_json_stats(error) do
Logger.error "Failed to decode JSON Stats: #{inspect error}"
end
defp request(uri, method, body \\ [], headers \\ []) do
headers = [{"user-agent", "LSG-API[115ans.net, sys.115ans.net] href@random.sh"}] ++ headers
options = @httpoison_opts
case :ok do #:fuse.ask(@fuse, :sync) do
:ok -> run_request(method, uri, body, headers, options)
:blown -> :blown
end
end
# This is to work around hackney's behaviour of returning `{:error, :closed}` when a pool connection has been closed
# (keep-alive expired). We just retry the request immediatly up to five times.
defp run_request(method, uri, body, headers, options), do: run_request(method, uri, body, headers, options, 0)
defp run_request(method, uri, body, headers, options, retries) when retries < 4 do
case HTTPoison.request(method, uri, body, headers, options) do
{:error, :closed} -> run_request(method, uri, body, headers, options, retries + 1)
other -> other
end
end
defp run_request(method, uri, body, headers, options, _exceeded_retries), do: {:error, :unavailable}
#
# -- URIs
#
defp stats_json_url do
base_url() <> "/status-json.xsl"
end
defp base_url do
"http://91.121.59.45:8089"
end
end