diff --git a/lib/philomena/channels.ex b/lib/philomena/channels.ex index 9bd8d9ff..d1d31bce 100644 --- a/lib/philomena/channels.ex +++ b/lib/philomena/channels.ex @@ -6,49 +6,15 @@ defmodule Philomena.Channels do import Ecto.Query, warn: false alias Philomena.Repo + alias Philomena.Channels.AutomaticUpdater alias Philomena.Channels.Channel - alias Philomena.Channels.PicartoChannel - alias Philomena.Channels.PiczelChannel alias Philomena.Notifications @doc """ - Updates all the tracked channels for which an update - scheme is known. + Updates all the tracked channels for which an update scheme is known. """ def update_tracked_channels! do - now = DateTime.utc_now() |> DateTime.truncate(:second) - - picarto_channels = PicartoChannel.live_channels(now) - live_picarto_channels = Map.keys(picarto_channels) - - piczel_channels = PiczelChannel.live_channels(now) - live_piczel_channels = Map.keys(piczel_channels) - - # Update all channels which are offline to reflect offline status - offline_query = - from c in Channel, - where: c.type == "PicartoChannel" and c.short_name not in ^live_picarto_channels, - or_where: c.type == "PiczelChannel" and c.short_name not in ^live_piczel_channels - - Repo.update_all(offline_query, set: [is_live: false, updated_at: now]) - - # Update all channels which are online to reflect online status using - # changeset functions - online_query = - from c in Channel, - where: c.type == "PicartoChannel" and c.short_name in ^live_picarto_channels, - or_where: c.type == "PiczelChannel" and c.short_name in ^live_picarto_channels - - online_query - |> Repo.all() - |> Enum.map(fn - %{type: "PicartoChannel", short_name: name} = channel -> - Channel.update_changeset(channel, Map.get(picarto_channels, name, [])) - - %{type: "PiczelChannel", short_name: name} = channel -> - Channel.update_changeset(channel, Map.get(piczel_channels, name, [])) - end) - |> Enum.map(&Repo.update!/1) + AutomaticUpdater.update_tracked_channels!() end @doc """ @@ -103,6 +69,24 @@ defmodule Philomena.Channels do |> Repo.update() end + @doc """ + Updates a channel's state when it goes live. + + ## Examples + + iex> update_channel_state(channel, %{field: new_value}) + {:ok, %Channel{}} + + iex> update_channel_state(channel, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_channel_state(%Channel{} = channel, attrs) do + channel + |> Channel.update_changeset(attrs) + |> Repo.update() + end + @doc """ Deletes a Channel. diff --git a/lib/philomena/channels/automatic_updater.ex b/lib/philomena/channels/automatic_updater.ex new file mode 100644 index 00000000..3da29870 --- /dev/null +++ b/lib/philomena/channels/automatic_updater.ex @@ -0,0 +1,64 @@ +defmodule Philomena.Channels.AutomaticUpdater do + @moduledoc """ + Automatic update routine for streams. + + Calls APIs for each stream provider to remove channels which are no longer online, + and to restore channels which are currently online. + """ + + import Ecto.Query, warn: false + alias Philomena.Repo + + alias Philomena.Channels + alias Philomena.Channels.Channel + alias Philomena.Channels.PicartoChannel + alias Philomena.Channels.PiczelChannel + + @doc """ + Updates all the tracked channels for which an update scheme is known. + """ + def update_tracked_channels! do + now = DateTime.utc_now(:second) + Enum.each(providers(), &update_provider(&1, now)) + end + + defp providers do + [ + {"PicartoChannel", PicartoChannel.live_channels()}, + {"PiczelChannel", PiczelChannel.live_channels()} + ] + end + + defp update_provider({provider_name, live_channels}, now) do + channel_names = Map.keys(live_channels) + + provider_name + |> update_offline_query(channel_names, now) + |> Repo.update_all([]) + + provider_name + |> online_query(channel_names) + |> Repo.all() + |> Enum.each(&update_online_channel(&1, live_channels, now)) + end + + defp update_offline_query(provider_name, channel_names, now) do + from c in Channel, + where: c.type == ^provider_name and c.short_name not in ^channel_names, + update: [set: [is_live: false, updated_at: ^now]] + end + + defp online_query(provider_name, channel_names) do + from c in Channel, + where: c.type == ^provider_name and c.short_name in ^channel_names + end + + defp update_online_channel(channel, live_channels, now) do + attrs = + live_channels + |> Map.get(channel.short_name, %{}) + |> Map.merge(%{last_live_at: now, last_fetched_at: now}) + + Channels.update_channel_state(channel, attrs) + end +end diff --git a/lib/philomena/channels/picarto_channel.ex b/lib/philomena/channels/picarto_channel.ex index 1eacb28f..e1799f4f 100644 --- a/lib/philomena/channels/picarto_channel.ex +++ b/lib/philomena/channels/picarto_channel.ex @@ -1,30 +1,28 @@ defmodule Philomena.Channels.PicartoChannel do @api_online "https://api.picarto.tv/api/v1/online?adult=true&gaming=true" - @spec live_channels(DateTime.t()) :: map() - def live_channels(now) do + @spec live_channels() :: map() + def live_channels do @api_online |> PhilomenaProxy.Http.get() |> case do {:ok, %{body: body, status: 200}} -> body |> Jason.decode!() - |> Map.new(&{&1["name"], fetch(&1, now)}) + |> Map.new(&{&1["name"], fetch(&1)}) _error -> %{} end end - defp fetch(api, now) do + defp fetch(api) do %{ title: api["title"], is_live: true, nsfw: api["adult"], viewers: api["viewers"], thumbnail_url: api["thumbnails"]["web"], - last_fetched_at: now, - last_live_at: now, description: nil } end diff --git a/lib/philomena/channels/piczel_channel.ex b/lib/philomena/channels/piczel_channel.ex index 817dd486..39aae28e 100644 --- a/lib/philomena/channels/piczel_channel.ex +++ b/lib/philomena/channels/piczel_channel.ex @@ -1,30 +1,28 @@ defmodule Philomena.Channels.PiczelChannel do @api_online "https://api.piczel.tv/api/streams" - @spec live_channels(DateTime.t()) :: map() - def live_channels(now) do + @spec live_channels() :: map() + def live_channels do @api_online |> PhilomenaProxy.Http.get() |> case do {:ok, %{body: body, status: 200}} -> body |> Jason.decode!() - |> Map.new(&{&1["slug"], fetch(&1, now)}) + |> Map.new(&{&1["slug"], fetch(&1)}) _error -> %{} end end - defp fetch(api, now) do + defp fetch(api) do %{ title: api["title"], is_live: api["live"], nsfw: api["adult"], viewers: api["viewers"], - thumbnail_url: api["user"]["avatar"]["avatar"]["url"], - last_fetched_at: now, - last_live_at: now + thumbnail_url: api["user"]["avatar"]["avatar"]["url"] } end end