diff options
Diffstat (limited to 'lib/lsg')
-rw-r--r-- | lib/lsg/application.ex | 37 | ||||
-rw-r--r-- | lib/lsg/icecast.ex | 117 | ||||
-rw-r--r-- | lib/lsg/icecast_agent.ex | 17 |
3 files changed, 171 insertions, 0 deletions
diff --git a/lib/lsg/application.ex b/lib/lsg/application.ex new file mode 100644 index 0000000..73ec04d --- /dev/null +++ b/lib/lsg/application.ex @@ -0,0 +1,37 @@ +defmodule LSG.Application do + use Application + + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + def start(_type, _args) do + import Supervisor.Spec + {:ok, irc_client} = ExIRC.start_link! + + # Define workers and child supervisors to be supervised + children = [ + # Start the endpoint when the application starts + supervisor(LSGWeb.Endpoint, []), + # Start your own worker by calling: LSG.Worker.start_link(arg1, arg2, arg3) + # worker(LSG.Worker, [arg1, arg2, arg3]), + worker(Registry, [[keys: :duplicate, name: LSG.BroadcastRegistry]]), + worker(LSG.IcecastAgent, []), + worker(LSG.Icecast, []), + worker(LSG.IRC.ConnectionHandler, [irc_client]), + worker(LSG.IRC.LoginHandler, [irc_client]), + worker(LSG.IRC.BroadcastHandler, [irc_client]), + worker(LSG.IRC.NpHandler, [irc_client]), + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: LSG.Supervisor] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + def config_change(changed, _new, removed) do + LSGWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/lib/lsg/icecast.ex b/lib/lsg/icecast.ex new file mode 100644 index 0000000..ec168c3 --- /dev/null +++ b/lib/lsg/icecast.ex @@ -0,0 +1,117 @@ +defmodule LSG.Icecast do + use GenServer + require Logger + @interval 10_000 + @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 + :timer.send_after(5_000, :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[lsg.goulag.org] 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 diff --git a/lib/lsg/icecast_agent.ex b/lib/lsg/icecast_agent.ex new file mode 100644 index 0000000..8f8a86a --- /dev/null +++ b/lib/lsg/icecast_agent.ex @@ -0,0 +1,17 @@ +defmodule LSG.IcecastAgent do + use Agent + + def start_link() do + Agent.start_link(fn -> nil end, name: __MODULE__) + end + + def update(stats) do + Agent.update(__MODULE__, fn(_old) -> stats end) + end + + def get do + Agent.get(__MODULE__, fn(stats) -> stats end) + end + +end + |