Skip to content

Commit

Permalink
Batch stream live requests
Browse files Browse the repository at this point in the history
  • Loading branch information
pajlada committed Jul 1, 2023
1 parent d2f1516 commit bb9a845
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 0 deletions.
7 changes: 7 additions & 0 deletions src/Application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# include "controllers/plugins/PluginController.hpp"
#endif
#include "controllers/sound/SoundController.hpp"
#include "controllers/twitch/LiveController.hpp"
#include "controllers/userdata/UserDataController.hpp"
#include "debug/AssertInGuiThread.hpp"
#include "messages/Message.hpp"
Expand Down Expand Up @@ -88,6 +89,7 @@ Application::Application(Settings &_settings, Paths &_paths)
, seventvBadges(&this->emplace<SeventvBadges>())
, userData(&this->emplace<UserDataController>())
, sound(&this->emplace<SoundController>())
, twitchLiveController(&this->emplace<TwitchLiveController>())
#ifdef CHATTERINO_HAVE_PLUGINS
, plugins(&this->emplace<PluginController>())
#endif
Expand Down Expand Up @@ -245,6 +247,11 @@ IUserDataController *Application::getUserData()
return this->userData;
}

ITwitchLiveController *Application::getTwitchLiveController()
{
return this->twitchLiveController;
}

ITwitchIrcServer *Application::getTwitch()
{
return this->twitch;
Expand Down
8 changes: 8 additions & 0 deletions src/Application.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class HotkeyController;
class IUserDataController;
class UserDataController;
class SoundController;
class ITwitchLiveController;
class TwitchLiveController;
#ifdef CHATTERINO_HAVE_PLUGINS
class PluginController;
#endif
Expand Down Expand Up @@ -62,6 +64,7 @@ class IApplication
virtual ChatterinoBadges *getChatterinoBadges() = 0;
virtual FfzBadges *getFfzBadges() = 0;
virtual IUserDataController *getUserData() = 0;
virtual ITwitchLiveController *getTwitchLiveController() = 0;
};

class Application : public IApplication
Expand Down Expand Up @@ -101,6 +104,10 @@ class Application : public IApplication
UserDataController *const userData{};
SoundController *const sound{};

private:
TwitchLiveController *const twitchLiveController{};

public:
#ifdef CHATTERINO_HAVE_PLUGINS
PluginController *const plugins{};
#endif
Expand Down Expand Up @@ -154,6 +161,7 @@ class Application : public IApplication
return this->ffzBadges;
}
IUserDataController *getUserData() override;
ITwitchLiveController *getTwitchLiveController() override;

pajlada::Signals::NoArgSignal streamerModeChanged;

Expand Down
3 changes: 3 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ set(SOURCE_FILES
controllers/userdata/UserDataController.hpp
controllers/userdata/UserData.hpp

controllers/twitch/LiveController.cpp
controllers/twitch/LiveController.hpp

controllers/sound/SoundController.cpp
controllers/sound/SoundController.hpp

Expand Down
179 changes: 179 additions & 0 deletions src/controllers/twitch/LiveController.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#include "controllers/twitch/LiveController.hpp"

#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "singletons/Paths.hpp"
#include "util/CombinePath.hpp"
#include "util/Helpers.hpp"

#include <QDebug>

namespace chatterino {

TwitchLiveController::TwitchLiveController()
{
QObject::connect(&this->refreshTimer, &QTimer::timeout, [this] {
this->request();
});
this->refreshTimer.start(60 * 1000);

QObject::connect(&this->immediateRequestTimer, &QTimer::timeout, [this] {
QStringList channelIDs;

{
std::unique_lock immediateRequestsLock(
this->immediateRequestsMutex);
for (const auto &channelID : this->immediateRequests)
{
channelIDs.append(channelID);
}
this->immediateRequests.clear();
}

this->request(channelIDs);
});
this->immediateRequestTimer.start(1 * 1000);

qDebug() << "XD";
}

void TwitchLiveController::add(std::shared_ptr<TwitchChannel> newChannel)
{
assert(newChannel != nullptr);

const auto channelID = newChannel->roomId();
assert(!channelID.isEmpty());

qDebug() << "XXX: Add" << channelID;

std::unique_lock lock(this->channelsMutex);
auto &channelList = this->channels[channelID];

if (channelList.empty())
{
std::unique_lock immediateRequestsLock(this->immediateRequestsMutex);
this->immediateRequests.emplace(channelID);
}

channelList.emplace_back(newChannel);
}

void TwitchLiveController::remove(std::shared_ptr<TwitchChannel> channel)
{
const auto channelID = channel->roomId();
assert(!channelID.isEmpty());

qDebug() << "XXX: Remove" << channelID;

std::unique_lock lock(this->channelsMutex);
auto &channelList = this->channels[channelID];

channelList.erase(std::remove_if(channelList.begin(), channelList.end(),
[](const auto &c) {
return c.expired();
}),
channelList.end());

if (channelList.empty())
{
this->channels.erase(channelID);
}
}

void TwitchLiveController::request(std::optional<QStringList> optChannelIDs)
{
QStringList channelIDs;

if (optChannelIDs)
{
qDebug() << "XXX Load requests from channels map" << channelIDs;
channelIDs = *optChannelIDs;
}
else
{
std::shared_lock lock(this->channelsMutex);

for (const auto &channelList : this->channels)
{
channelIDs.append(channelList.first);
}
}

if (channelIDs.isEmpty())
{
qDebug() << "XXX: eraly out, no requests";
return;
}

auto batches = splitListIntoBatches(channelIDs, 3);

for (const auto &batch : batches)
{
qDebug() << "XXX MAKE REQUEST" << batch;

// TODO: make concurrent
getHelix()->fetchStreams(
batch, {},
[this, batch{batch}](const auto &streams) {
// on success
qDebug() << "XXX: success xd";
std::unordered_map<QString, std::optional<HelixStream>> results;

for (const auto &channelID : batch)
{
results[channelID] = std::nullopt;
}

for (const auto &stream : streams)
{
results[stream.userId] = stream;
qDebug() << "XXX: stream" << stream.userLogin;
}

{
std::unique_lock lock(this->channelsMutex);
QStringList deadChannels;
for (const auto &result : results)
{
auto it = this->channels.find(result.first);
if (it != channels.end())
{
const auto &weakChannelList = it->second;
for (const auto &weakChannel : weakChannelList)
{
auto channel = weakChannel.lock();
if (channel)
{
qDebug() << "XXX: channel is alive"
<< channel->getName();
channel->updateLiveStatus(result.second);
// POST LIVE STATUS
}
else
{
qDebug() << "XXX: channel is dead"
<< result.first;
// channel is dead, mark as dead
deadChannels.append(result.first);
}
}
}
}

for (const auto &deadChannel : deadChannels)
{
this->channels.erase(deadChannel);
}
}
},
[] {
qDebug() << "XXX: failed xd";
// on failure
},
[] {
// finally
});
}
}

} // namespace chatterino
59 changes: 59 additions & 0 deletions src/controllers/twitch/LiveController.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#pragma once

#include "common/Singleton.hpp"
#include "util/QStringHash.hpp"
#include "util/RapidjsonHelpers.hpp"
#include "util/RapidJsonSerializeQString.hpp"
#include "util/serialize/Container.hpp"

#include <boost/optional.hpp>
#include <QColor>
#include <QString>
#include <QTimer>

#include <memory>
#include <mutex>
#include <optional>
#include <shared_mutex>
#include <unordered_map>
#include <unordered_set>

namespace chatterino {

class TwitchChannel;

class ITwitchLiveController
{
public:
virtual ~ITwitchLiveController() = default;

virtual void add(std::shared_ptr<TwitchChannel> newChannel) = 0;
virtual void remove(std::shared_ptr<TwitchChannel> channel) = 0;
};

class TwitchLiveController : public ITwitchLiveController, public Singleton
{
public:
TwitchLiveController();

void add(std::shared_ptr<TwitchChannel> newChannel) override;
void remove(std::shared_ptr<TwitchChannel> channel) override;

private:
void request(std::optional<QStringList> optChannelIDs = std::nullopt);

// List of channel IDs pointing to Twitch Channels
std::unordered_map<QString, std::vector<std::weak_ptr<TwitchChannel>>>
channels;
std::shared_mutex channelsMutex;

QTimer refreshTimer;

// List of channels that need an immediate live status update
std::unordered_set<QString> immediateRequests;
std::mutex immediateRequestsMutex;

QTimer immediateRequestTimer;
};

} // namespace chatterino
17 changes: 17 additions & 0 deletions src/providers/twitch/TwitchChannel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "common/QLogging.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/notifications/NotificationController.hpp"
#include "controllers/twitch/LiveController.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
#include "messages/Link.hpp"
Expand Down Expand Up @@ -106,6 +107,8 @@ TwitchChannel::TwitchChannel(const QString &name)
this->refreshBTTVChannelEmotes(false);
this->refreshSevenTVChannelEmotes(false);
this->joinBttvChannel();
getIApp()->getTwitchLiveController()->add(
std::dynamic_pointer_cast<TwitchChannel>(shared_from_this()));
});

this->connected.connect([this]() {
Expand Down Expand Up @@ -340,6 +343,20 @@ boost::optional<ChannelPointReward> TwitchChannel::channelPointReward(
return it->second;
}

void TwitchChannel::updateLiveStatus(
const std::optional<HelixStream> &helixStream)
{
qDebug() << "XXX: Update live status for " << this->getName();
if (helixStream)
{
qDebug() << "XXX: stream is live";
}
else
{
qDebug() << "XXX: stream is not live";
}
}

void TwitchChannel::showLoginMessage()
{
const auto linkColor = MessageColor(MessageColor::Link);
Expand Down
3 changes: 3 additions & 0 deletions src/providers/twitch/TwitchChannel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ class TwitchChannel : public Channel, public ChannelChatters
boost::optional<ChannelPointReward> channelPointReward(
const QString &rewardId) const;

// Live status
void updateLiveStatus(const std::optional<HelixStream> &helixStream);

private:
struct NameOptions {
QString displayName;
Expand Down

0 comments on commit bb9a845

Please sign in to comment.