summaryrefslogtreecommitdiff
path: root/lib/lsg
diff options
context:
space:
mode:
Diffstat (limited to 'lib/lsg')
-rw-r--r--lib/lsg/application.ex37
-rw-r--r--lib/lsg/icecast.ex117
-rw-r--r--lib/lsg/icecast_agent.ex17
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
+