1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
defmodule Nola.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))
Nola.IcecastAgent.update(stats)
Registry.dispatch(Nola.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(:nola, :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", "Nola-API[115ans.net, sys.115ans.net] href@random.sh"}] ++ headers
options = @httpoison_opts
# :fuse.ask(@fuse, :sync) do
case :ok 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
|