summaryrefslogtreecommitdiff
path: root/lib/lsg/icecast.ex
blob: 07dd4fc0c5028f2fdb2a47e45cb40ac062a3385e (plain) (blame)
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
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