summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mainwindow.cpp68
-rw-r--r--src/mainwindow.h5
-rw-r--r--src/mainwindow.ui46
-rw-r--r--src/nexusinterface.cpp314
-rw-r--r--src/nexusinterface.h150
-rw-r--r--src/nxmaccessmanager.cpp15
-rw-r--r--src/nxmaccessmanager.h5
-rw-r--r--src/statusbar.cpp53
-rw-r--r--src/statusbar.h6
9 files changed, 428 insertions, 234 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 67d0f2ff..6b3ee11f 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -225,7 +225,7 @@ MainWindow::MainWindow(QSettings &initSettings
QWebEngineProfile::defaultProfile()->setCachePath(m_OrganizerCore.settings().getCacheDirectory());
QWebEngineProfile::defaultProfile()->setPersistentStoragePath(m_OrganizerCore.settings().getCacheDirectory());
ui->setupUi(this);
- updateWindowTitle(QString(), 0, false);
+ updateWindowTitle({});
languageChange(m_OrganizerCore.settings().language());
@@ -340,13 +340,6 @@ MainWindow::MainWindow(QSettings &initSettings
ui->bossButton->setToolTip(tr("There is no supported sort mechanism for this game. You will probably have to use a third-party tool."));
}
- ui->apiRequests->setAutoFillBackground(true);
- QPalette palette = ui->apiRequests->palette();
- palette.setColor(ui->apiRequests->backgroundRole(), Qt::darkGreen);
- palette.setColor(ui->apiRequests->foregroundRole(), Qt::white);
- ui->apiRequests->setPalette(palette);
- ui->apiRequests->setVisible(!m_OrganizerCore.settings().hideAPICounter());
-
connect(&m_PluginContainer, SIGNAL(diagnosisUpdate()), this, SLOT(updateProblemsButton()));
connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(saveSelectionChanged(QListWidgetItem*)));
@@ -379,11 +372,24 @@ MainWindow::MainWindow(QSettings &initSettings
connect(NexusInterface::instance(&pluginContainer), SIGNAL(nxmDownloadURLsAvailable(QString,int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(QString,int,int,QVariant,QVariant,int)));
connect(NexusInterface::instance(&pluginContainer), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusApi()));
connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(validateFailed(QString)), this, SLOT(validationFailed(QString)));
- connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(credentialsReceived(const QString&, int, bool, std::tuple<int, int, int, int>)),
- this, SLOT(updateWindowTitle(const QString&, int, bool)));
- connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(credentialsReceived(const QString&, int, bool, std::tuple<int, int, int, int>)),
- NexusInterface::instance(&m_PluginContainer), SLOT(setRateMax(const QString&, int, bool, std::tuple<int, int, int, int>)));
- connect(NexusInterface::instance(&pluginContainer), SIGNAL(requestsChanged(int, std::tuple<int, int, int, int>)), this, SLOT(updateAPICounter(int, std::tuple<int, int, int, int>)));
+
+ connect(
+ NexusInterface::instance(&pluginContainer)->getAccessManager(),
+ SIGNAL(credentialsReceived(const APIUserAccount&)),
+ this,
+ SLOT(updateWindowTitle(const APIUserAccount&)));
+
+ connect(
+ NexusInterface::instance(&pluginContainer)->getAccessManager(),
+ SIGNAL(credentialsReceived(const APIUserAccount&)),
+ NexusInterface::instance(&m_PluginContainer),
+ SLOT(setUserAccount(const APIUserAccount&)));
+
+ connect(
+ NexusInterface::instance(&pluginContainer),
+ SIGNAL(requestsChanged(const APIStats&, const APIUserAccount&)),
+ this,
+ SLOT(onRequestsChanged(const APIStats&, const APIUserAccount&)));
connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString)));
connect(ui->tabWidget, SIGNAL(currentChanged(int)), &TutorialManager::instance(), SIGNAL(tabChanged(int)));
@@ -548,20 +554,27 @@ MainWindow::~MainWindow()
}
-void MainWindow::updateWindowTitle(const QString &accountName, int, bool premium)
+void MainWindow::updateWindowTitle(const APIUserAccount& user)
{
QString title = QString("%1 Mod Organizer v%2").arg(
m_OrganizerCore.managedGame()->gameName(),
m_OrganizerCore.getVersion().displayString(3));
- if (!accountName.isEmpty()) {
- title.append(QString(" (%1%2)").arg(accountName, premium ? "*" : ""));
+ if (!user.name().isEmpty()) {
+ const QString premium = (user.type() == APIUserAccountTypes::Premium ? "*" : "");
+ title.append(QString(" (%1%2)").arg(user.name(), premium));
}
this->setWindowTitle(title);
}
+void MainWindow::onRequestsChanged(const APIStats& stats, const APIUserAccount& user)
+{
+ m_statusBar->updateAPI(stats, user);
+}
+
+
void MainWindow::disconnectPlugins()
{
if (ui->actionTool->menu() != nullptr) {
@@ -5267,8 +5280,7 @@ void MainWindow::on_actionSettings_triggered()
activateProxy(settings.useProxy());
}
- ui->apiRequests->setVisible(!settings.hideAPICounter());
-
+ m_statusBar->checkSettings(m_OrganizerCore.settings());
updateDownloadView();
m_OrganizerCore.updateVFSParams(settings.logLevel(), settings.crashDumpsType(), settings.executablesBlacklist());
@@ -6067,26 +6079,6 @@ void MainWindow::nxmRequestFailed(QString gameName, int modID, int, QVariant, in
}
-void MainWindow::updateAPICounter(int queueCount, std::tuple<int, int, int, int> limits)
-{
- ui->apiRequests->setText(QString("API: Q: %1 | D: %2 | H: %3").arg(queueCount).arg(std::get<0>(limits)).arg(std::get<2>(limits)));
- int requestsRemaining = std::get<0>(limits) + std::get<2>(limits);
- if (requestsRemaining > 300) {
- QPalette palette = ui->apiRequests->palette();
- palette.setColor(ui->apiRequests->backgroundRole(), Qt::darkGreen);
- ui->apiRequests->setPalette(palette);
- } else if (requestsRemaining < 150) {
- QPalette palette = ui->apiRequests->palette();
- palette.setColor(ui->apiRequests->backgroundRole(), Qt::darkRed);
- ui->apiRequests->setPalette(palette);
- } else {
- QPalette palette = ui->apiRequests->palette();
- palette.setColor(ui->apiRequests->backgroundRole(), Qt::darkYellow);
- ui->apiRequests->setPalette(palette);
- }
-}
-
-
BSA::EErrorCode MainWindow::extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination,
QProgressDialog &progress)
{
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 734ece88..e2c6ce8b 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -416,8 +416,7 @@ private:
private slots:
- void updateWindowTitle(const QString &accountName, int, bool premium);
-
+ void updateWindowTitle(const APIUserAccount& user);
void showMessage(const QString &message);
void showError(const QString &message);
@@ -544,7 +543,7 @@ private slots:
void nxmDownloadURLs(QString, int modID, int fileID, QVariant userData, QVariant resultData, int requestID);
void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, QNetworkReply::NetworkError error, const QString &errorString);
- void updateAPICounter(int queueCount, std::tuple<int, int, int, int> limits);
+ void onRequestsChanged(const APIStats& stats, const APIUserAccount& user);
void editCategories();
void deselectFilters();
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
index d3f9ef39..bbcb734c 100644
--- a/src/mainwindow.ui
+++ b/src/mainwindow.ui
@@ -412,7 +412,7 @@ p, li { white-space: pre-wrap; }
</widget>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0,1,1,0,0,1,1,1">
+ <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0,1,1,0,1,1">
<item>
<widget class="QPushButton" name="displayCategoriesBtn">
<property name="maximumSize">
@@ -471,37 +471,6 @@ p, li { white-space: pre-wrap; }
</property>
</spacer>
</item>
- <item>
- <widget class="QLabel" name="apiRequests">
- <property name="toolTip">
- <string>Nexus API Queued and Remaining Requests</string>
- </property>
- <property name="whatsThis">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This tracks the number of queued Nexus API requests on the left (&lt;span style=&quot; font-weight:600;&quot;&gt;Q&lt;/span&gt;) and the remaining daily (&lt;span style=&quot; font-weight:600;&quot;&gt;D&lt;/span&gt;) and hourly (&lt;span style=&quot; font-weight:600;&quot;&gt;H&lt;/span&gt;) requests on the right. The Nexus API limits you to a pool of requests per day and requests per hour. It is dynamically updated every time a request is completed. If you run out of requests, you will be unable to queue downloads, check updates, parse mod info, or even log in. Both pools must be consumed before this happens.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="frameShape">
- <enum>QFrame::StyledPanel</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Sunken</enum>
- </property>
- <property name="lineWidth">
- <number>2</number>
- </property>
- <property name="midLineWidth">
- <number>1</number>
- </property>
- <property name="text">
- <string>API: Q: 0 | D: 0 | H: 0</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- <property name="margin">
- <number>2</number>
- </property>
- </widget>
- </item>
<item alignment="Qt::AlignLeft">
<widget class="QPushButton" name="clearFiltersButton">
<property name="sizePolicy">
@@ -547,19 +516,6 @@ p, li { white-space: pre-wrap; }
</widget>
</item>
<item>
- <spacer name="horizontalSpacer_4">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
<widget class="QComboBox" name="groupCombo">
<property name="baseSize">
<size>
diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp
index c677add0..8362143a 100644
--- a/src/nexusinterface.cpp
+++ b/src/nexusinterface.cpp
@@ -38,6 +38,82 @@ using namespace MOBase;
using namespace MOShared;
+void throttledWarning(const APIUserAccount& user)
+{
+ qCritical() <<
+ QString(
+ "You have fewer than %1 requests remaining (%2). Only downloads and "
+ "login validation are being allowed.")
+ .arg(APIUserAccount::ThrottleThreshold)
+ .arg(user.remainingRequests());
+}
+
+
+APIUserAccount::APIUserAccount()
+ : m_type(APIUserAccountTypes::None)
+{
+}
+
+const QString& APIUserAccount::id() const
+{
+ return m_id;
+}
+
+const QString& APIUserAccount::name() const
+{
+ return m_name;
+}
+
+APIUserAccountTypes APIUserAccount::type() const
+{
+ return m_type;
+}
+
+const APILimits& APIUserAccount::limits() const
+{
+ return m_limits;
+}
+
+APIUserAccount& APIUserAccount::id(const QString& id)
+{
+ m_id = id;
+ return *this;
+}
+
+APIUserAccount& APIUserAccount::name(const QString& name)
+{
+ m_name = name;
+ return *this;
+}
+
+APIUserAccount& APIUserAccount::type(APIUserAccountTypes type)
+{
+ m_type = type;
+ return *this;
+}
+
+APIUserAccount& APIUserAccount::limits(const APILimits& limits)
+{
+ m_limits = limits;
+ return *this;
+}
+
+int APIUserAccount::remainingRequests() const
+{
+ return m_limits.remainingDailyRequests + m_limits.remainingHourlyRequests;
+}
+
+bool APIUserAccount::shouldThrottle() const
+{
+ return (remainingRequests() < ThrottleThreshold);
+}
+
+bool APIUserAccount::exhausted() const
+{
+ return (remainingRequests() <= 0);
+}
+
+
NexusBridge::NexusBridge(PluginContainer *pluginContainer, const QString &subModule)
: m_Interface(NexusInterface::instance(pluginContainer))
, m_SubModule(subModule)
@@ -179,15 +255,39 @@ void NexusBridge::nxmRequestFailed(QString gameName, int modID, int fileID, QVar
QAtomicInt NexusInterface::NXMRequestInfo::s_NextID(0);
+APILimits NexusInterface::defaultAPILimits()
+{
+ // https://app.swaggerhub.com/apis-docs/NexusMods/nexus-mods_public_api_params_in_form_data/1.0#/
+ const int MaxDaily = 2500;
+ const int MaxHourly = 100;
+
+ APILimits limits;
+
+ limits.maxDailyRequests = MaxDaily;
+ limits.remainingDailyRequests = MaxDaily;
+ limits.maxHourlyRequests = MaxHourly;
+ limits.remainingHourlyRequests = MaxHourly;
+
+ return limits;
+}
+
+APILimits NexusInterface::parseLimits(const QNetworkReply* reply)
+{
+ APILimits limits;
+
+ limits.maxDailyRequests = reply->rawHeader("x-rl-daily-limit").toInt();
+ limits.remainingDailyRequests = reply->rawHeader("x-rl-daily-remaining").toInt();
+ limits.maxHourlyRequests = reply->rawHeader("x-rl-hourly-limit").toInt();
+ limits.remainingHourlyRequests = reply->rawHeader("x-rl-hourly-remaining").toInt();
+
+ return limits;
+}
+
+
NexusInterface::NexusInterface(PluginContainer *pluginContainer)
: m_PluginContainer(pluginContainer)
- , m_RemainingDailyRequests(2500)
- , m_RemainingHourlyRequests(100)
- , m_MaxDailyRequests(2500)
- , m_MaxHourlyRequests(100)
- , m_IsPremium(false)
- , m_UserID(0)
{
+ m_User.limits(defaultAPILimits());
m_MOVersion = createVersionInfo();
m_AccessManager = new NXMAccessManager(this, m_MOVersion.displayString(3));
@@ -222,15 +322,10 @@ void NexusInterface::loginCompleted()
nextRequest();
}
-void NexusInterface::setRateMax(const QString&, int userId, bool isPremium, std::tuple<int, int, int, int> limits)
+void NexusInterface::setUserAccount(const APIUserAccount& user)
{
- m_RemainingDailyRequests = std::get<0>(limits);
- m_MaxDailyRequests = std::get<1>(limits);
- m_RemainingHourlyRequests = std::get<2>(limits);
- m_MaxHourlyRequests = std::get<3>(limits);
- m_IsPremium = isPremium;
- m_UserID = userId;
- emit requestsChanged(m_RequestQueue.size(), limits);
+ m_User = user;
+ emit requestsChanged(stats(), m_User);
}
void NexusInterface::interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query)
@@ -369,70 +464,70 @@ int NexusInterface::requestDescription(QString gameName, int modID, QObject *rec
int NexusInterface::requestModInfo(QString gameName, int modID, QObject *receiver, QVariant userData,
const QString &subModule, MOBase::IPluginGame const *game)
{
- if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) {
- NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_MODINFO, userData, subModule, game);
- m_RequestQueue.enqueue(requestInfo);
+ if (m_User.shouldThrottle()) {
+ throttledWarning(m_User);
+ return -1;
+ }
- connect(this, SIGNAL(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)),
- receiver, SLOT(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection);
+ NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_MODINFO, userData, subModule, game);
+ m_RequestQueue.enqueue(requestInfo);
- connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
- receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+ connect(this, SIGNAL(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)),
+ receiver, SLOT(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection);
- nextRequest();
- return requestInfo.m_ID;
- }
- qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.")
- .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests));
- return -1;
+ connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
+ receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+
+ nextRequest();
+ return requestInfo.m_ID;
}
int NexusInterface::requestUpdateInfo(QString gameName, NexusInterface::UpdatePeriod period, QObject *receiver, QVariant userData,
const QString &subModule, const MOBase::IPluginGame *game)
{
- if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) {
- NXMRequestInfo requestInfo(period, NXMRequestInfo::TYPE_CHECKUPDATES, userData, subModule, game);
- m_RequestQueue.enqueue(requestInfo);
+ if (m_User.shouldThrottle()) {
+ throttledWarning(m_User);
+ return -1;
+ }
- connect(this, SIGNAL(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)),
- receiver, SLOT(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)), Qt::UniqueConnection);
+ NXMRequestInfo requestInfo(period, NXMRequestInfo::TYPE_CHECKUPDATES, userData, subModule, game);
+ m_RequestQueue.enqueue(requestInfo);
- connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
- receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+ connect(this, SIGNAL(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)),
+ receiver, SLOT(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)), Qt::UniqueConnection);
- nextRequest();
- return requestInfo.m_ID;
- }
- qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.")
- .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests));
- return -1;
+ connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
+ receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+
+ nextRequest();
+ return requestInfo.m_ID;
}
int NexusInterface::requestUpdates(const int &modID, QObject *receiver, QVariant userData,
QString gameName, const QString &subModule)
{
- if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) {
- IPluginGame *game = getGame(gameName);
- if (game == nullptr) {
- qCritical("requestUpdates can't find plugin for %s", qUtf8Printable(gameName));
- return -1;
- }
+ if (m_User.shouldThrottle()) {
+ throttledWarning(m_User);
+ return -1;
+ }
+
+ IPluginGame *game = getGame(gameName);
+ if (game == nullptr) {
+ qCritical("requestUpdates can't find plugin for %s", qUtf8Printable(gameName));
+ return -1;
+ }
- NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_GETUPDATES, userData, subModule, game);
- m_RequestQueue.enqueue(requestInfo);
+ NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_GETUPDATES, userData, subModule, game);
+ m_RequestQueue.enqueue(requestInfo);
- connect(this, SIGNAL(nxmUpdatesAvailable(QString, int, QVariant, QVariant, int)),
- receiver, SLOT(nxmUpdatesAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection);
+ connect(this, SIGNAL(nxmUpdatesAvailable(QString, int, QVariant, QVariant, int)),
+ receiver, SLOT(nxmUpdatesAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection);
- connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
- receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+ connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
+ receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
- nextRequest();
- return requestInfo.m_ID;
- }
- qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.")
- .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests));
- return -1;
+ nextRequest();
+ return requestInfo.m_ID;
}
@@ -527,23 +622,23 @@ int NexusInterface::requestEndorsementInfo(QObject *receiver, QVariant userData,
int NexusInterface::requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QObject *receiver, QVariant userData,
const QString &subModule, MOBase::IPluginGame const *game)
{
- if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) {
- NXMRequestInfo requestInfo(modID, modVersion, NXMRequestInfo::TYPE_TOGGLEENDORSEMENT, userData, subModule, game);
- requestInfo.m_Endorse = endorse;
- m_RequestQueue.enqueue(requestInfo);
+ if (m_User.shouldThrottle()) {
+ throttledWarning(m_User);
+ return -1;
+ }
- connect(this, SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)),
- receiver, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection);
+ NXMRequestInfo requestInfo(modID, modVersion, NXMRequestInfo::TYPE_TOGGLEENDORSEMENT, userData, subModule, game);
+ requestInfo.m_Endorse = endorse;
+ m_RequestQueue.enqueue(requestInfo);
- connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
- receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+ connect(this, SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)),
+ receiver, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection);
- nextRequest();
- return requestInfo.m_ID;
- }
- qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.")
- .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests));
- return -1;
+ connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
+ receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+
+ nextRequest();
+ return requestInfo.m_ID;
}
int NexusInterface::requestTrackingInfo(QObject *receiver, QVariant userData, const QString &subModule)
@@ -564,23 +659,23 @@ int NexusInterface::requestTrackingInfo(QObject *receiver, QVariant userData, co
int NexusInterface::requestToggleTracking(QString gameName, int modID, bool track, QObject *receiver, QVariant userData,
const QString &subModule, MOBase::IPluginGame const *game)
{
- if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) {
- NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLETRACKING, userData, subModule, game);
- requestInfo.m_Track = track;
- m_RequestQueue.enqueue(requestInfo);
+ if (m_User.shouldThrottle()) {
+ throttledWarning(m_User);
+ return -1;
+ }
- connect(this, SIGNAL(nxmTrackingToggled(QString, int, QVariant, bool, int)),
- receiver, SLOT(nxmTrackingToggled(QString, int, QVariant, bool, int)), Qt::UniqueConnection);
+ NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLETRACKING, userData, subModule, game);
+ requestInfo.m_Track = track;
+ m_RequestQueue.enqueue(requestInfo);
- connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
- receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+ connect(this, SIGNAL(nxmTrackingToggled(QString, int, QVariant, bool, int)),
+ receiver, SLOT(nxmTrackingToggled(QString, int, QVariant, bool, int)), Qt::UniqueConnection);
- nextRequest();
- return requestInfo.m_ID;
- }
- qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.")
- .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests));
- return -1;
+ connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
+ receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+
+ nextRequest();
+ return requestInfo.m_ID;
}
int NexusInterface::requestInfoFromMd5(QString gameName, QByteArray &hash, QObject *receiver, QVariant userData,
@@ -645,7 +740,7 @@ void NexusInterface::nextRequest()
}
}
- if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) <= 0) {
+ if (m_User.exhausted()) {
m_RequestQueue.clear();
QTime time = QTime::currentTime();
QTime targetTime;
@@ -696,9 +791,9 @@ void NexusInterface::nextRequest()
} break;
case NXMRequestInfo::TYPE_DOWNLOADURL: {
ModRepositoryFileInfo *fileInfo = qobject_cast<ModRepositoryFileInfo*>(qvariant_cast<QObject*>(info.m_UserData));
- if (m_IsPremium) {
+ if (m_User.type() == APIUserAccountTypes::Premium) {
url = QString("%1/games/%2/mods/%3/files/%4/download_link").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID);
- } else if (!fileInfo->nexusKey.isEmpty() && fileInfo->nexusExpires && fileInfo->nexusDownloadUser == m_UserID) {
+ } else if (!fileInfo->nexusKey.isEmpty() && fileInfo->nexusExpires && fileInfo->nexusDownloadUser == m_User.id().toInt()) {
url = QString("%1/games/%2/mods/%3/files/%4/download_link?key=%5&expires=%6")
.arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID).arg(fileInfo->nexusKey).arg(fileInfo->nexusExpires);
} else {
@@ -780,23 +875,16 @@ void NexusInterface::requestFinished(std::list<NXMRequestInfo>::iterator iter)
if (iter->m_AllowedErrors.contains(error) && iter->m_AllowedErrors[error].contains(statusCode)) {
// These errors are allows to silently happen. They should be handled in nxmRequestFailed below.
} else if (statusCode == 429) {
- m_RemainingDailyRequests = reply->rawHeader("x-rl-daily-remaining").toInt();
- m_MaxDailyRequests = reply->rawHeader("x-rl-daily-limit").toInt();
- m_RemainingHourlyRequests = reply->rawHeader("x-rl-hourly-remaining").toInt();
- m_MaxHourlyRequests = reply->rawHeader("x-rl-hourly-limit").toInt();
+ m_User.limits(parseLimits(reply));
- if (m_RemainingDailyRequests || m_RemainingHourlyRequests)
+ if (!m_User.exhausted()) {
qWarning("You appear to be making requests to the Nexus API too quickly and are being throttled. Please inform the MO2 team.");
- else
+ }
+ else {
qWarning("All API requests have been consumed and are now being denied.");
+ }
- emit requestsChanged(m_RequestQueue.size(), std::tuple<int, int, int, int>(std::make_tuple(
- m_RemainingDailyRequests,
- m_MaxDailyRequests,
- m_RemainingHourlyRequests,
- m_MaxHourlyRequests
- )));
-
+ emit requestsChanged(stats(), m_User);
qWarning("Error: %s", reply->errorString().toUtf8().constData());
} else {
qWarning("request failed: %s", reply->errorString().toUtf8().constData());
@@ -871,17 +959,8 @@ void NexusInterface::requestFinished(std::list<NXMRequestInfo>::iterator iter)
} break;
}
- m_RemainingDailyRequests = reply->rawHeader("x-rl-daily-remaining").toInt();
- m_MaxDailyRequests = reply->rawHeader("x-rl-daily-limit").toInt();
- m_RemainingHourlyRequests = reply->rawHeader("x-rl-hourly-remaining").toInt();
- m_MaxHourlyRequests = reply->rawHeader("x-rl-hourly-limit").toInt();
-
- emit requestsChanged(m_RequestQueue.size(), std::tuple<int, int, int, int>(std::make_tuple(
- m_RemainingDailyRequests,
- m_MaxDailyRequests,
- m_RemainingHourlyRequests,
- m_MaxHourlyRequests
- )));
+ m_User.limits(parseLimits(reply));
+ emit requestsChanged(stats(), m_User);
} else {
emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->error(), tr("invalid response"));
}
@@ -938,6 +1017,15 @@ void NexusInterface::requestTimeout()
}
}
+APIStats NexusInterface::stats() const
+{
+ APIStats stats;
+ stats.requestsQueued = m_RequestQueue.size();
+
+ return stats;
+}
+
+
namespace {
QString get_management_url()
{
diff --git a/src/nexusinterface.h b/src/nexusinterface.h
index 70c1c9c9..6c4e8d11 100644
--- a/src/nexusinterface.h
+++ b/src/nexusinterface.h
@@ -39,6 +39,131 @@ namespace MOBase { class IPluginGame; }
class NexusInterface;
class NXMAccessManager;
+
+/**
+ * represents user account types on a mod provider website such as nexus
+ */
+enum class APIUserAccountTypes
+{
+ // not logged in
+ None = 0,
+
+ // regular account
+ Regular,
+
+ // premium account
+ Premium
+};
+
+
+/**
+ * current limits imposed on the user account
+ **/
+struct APILimits
+{
+ // maximum number of requests per day
+ int maxDailyRequests = 0;
+
+ // remaining number of requests today
+ int remainingDailyRequests = 0;
+
+ // maximum number of requests per hour
+ int maxHourlyRequests = 0;
+
+ // remaining number of requests this hour
+ int remainingHourlyRequests = 0;
+};
+
+
+/**
+ * API statistics
+ */
+struct APIStats
+{
+ // number of API requests currently queued
+ int requestsQueued = 0;
+};
+
+
+/**
+ * represents a user account on the mod provier website
+ */
+class APIUserAccount
+{
+public:
+ // when the number of remanining requests is under this number, further
+ // requests will be throttled by avoiding non-critical ones
+ static const int ThrottleThreshold = 300;
+
+ APIUserAccount();
+
+ /**
+ * user id
+ */
+ const QString& id() const;
+
+ /**
+ * user name
+ */
+ const QString& name() const;
+
+ /**
+ * account type
+ */
+ APIUserAccountTypes type() const;
+
+ /**
+ * current API limits
+ */
+ const APILimits& limits() const;
+
+
+ /**
+ * sets the user id
+ */
+ APIUserAccount& id(const QString& id);
+
+ /**
+ * sets the user name
+ **/
+ APIUserAccount& name(const QString& name);
+
+ /**
+ * sets the acount type
+ */
+ APIUserAccount& type(APIUserAccountTypes type);
+
+ /**
+ * sets the current limits
+ */
+ APIUserAccount& limits(const APILimits& limits);
+
+
+ /**
+ * returns the number of remaining requests
+ */
+ int remainingRequests() const;
+
+ /**
+ * whether the number of remaining requests is low enough that further
+ * requests should be throttled
+ */
+ bool shouldThrottle() const;
+
+ /**
+ * true if all the remaining requests have been used and the API will refuse
+ * further requests
+ */
+ bool exhausted() const;
+
+private:
+ QString m_id, m_name;
+ APIUserAccountTypes m_type;
+ APILimits m_limits;
+ APIStats m_stats;
+};
+
+
/**
* @brief convenience class to make nxm requests easier
* usually, all objects that started a nxm request will be signaled if one finished.
@@ -53,7 +178,6 @@ class NexusBridge : public MOBase::IModRepositoryBridge
Q_OBJECT
public:
-
NexusBridge(PluginContainer *pluginContainer, const QString &subModule = "");
/**
@@ -147,6 +271,8 @@ public:
};
public:
+ static APILimits defaultAPILimits();
+ static APILimits parseLimits(const QNetworkReply* reply);
~NexusInterface();
@@ -380,7 +506,7 @@ public:
/**
*
*/
- int requestInfoFromMd5(QString gameName, QByteArray &hash, QObject *receiver, QVariant userData, const QString &subModule,
+ int requestInfoFromMd5(QString gameName, QByteArray &hash, QObject *receiver, QVariant userData, const QString &subModule,
MOBase::IPluginGame const *game);
/**
@@ -459,11 +585,11 @@ signals:
void nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int requestID);
void nxmTrackingToggled(QString gameName, int modID, QVariant userData, bool tracked, int requestID);
void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, QNetworkReply::NetworkError error, const QString &errorString);
- void requestsChanged(int queueCount, std::tuple<int,int,int,int> requestsRemaining);
+ void requestsChanged(const APIStats& stats, const APIUserAccount& user);
public slots:
- void setRateMax(const QString&, int userId, bool isPremium, std::tuple<int,int,int,int> limits);
+ void setUserAccount(const APIUserAccount& user);
private slots:
@@ -534,27 +660,15 @@ private:
QString getOldModsURL(QString gameName) const;
private:
-
QNetworkDiskCache *m_DiskCache;
-
NXMAccessManager *m_AccessManager;
-
std::list<NXMRequestInfo> m_ActiveRequest;
QQueue<NXMRequestInfo> m_RequestQueue;
-
MOBase::VersionInfo m_MOVersion;
-
PluginContainer *m_PluginContainer;
+ APIUserAccount m_User;
- int m_RemainingDailyRequests;
- int m_RemainingHourlyRequests;
- int m_MaxDailyRequests;
- int m_MaxHourlyRequests;
-
- int m_UserID;
-
- bool m_IsPremium;
-
+ APIStats stats() const;
};
#endif // NEXUSINTERFACE_H
diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp
index 684a53fa..5886d3ee 100644
--- a/src/nxmaccessmanager.cpp
+++ b/src/nxmaccessmanager.cpp
@@ -18,8 +18,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nxmaccessmanager.h"
-
#include "iplugingame.h"
+#include "nexusinterface.h"
#include "nxmurl.h"
#include "report.h"
#include "utility.h"
@@ -280,14 +280,11 @@ void NXMAccessManager::validateFinished()
QString name = credentialsData.value("name").toString();
bool premium = credentialsData.value("is_premium").toBool();
- std::tuple<int, int, int, int> limits(std::make_tuple(
- m_ValidateReply->rawHeader("x-rl-daily-remaining").toInt(),
- m_ValidateReply->rawHeader("x-rl-daily-limit").toInt(),
- m_ValidateReply->rawHeader("x-rl-hourly-remaining").toInt(),
- m_ValidateReply->rawHeader("x-rl-hourly-limit").toInt()
- ));
-
- emit credentialsReceived(name, id, premium, limits);
+ emit credentialsReceived(APIUserAccount()
+ .id(QString("%1").arg(id))
+ .name(name)
+ .type(premium ? APIUserAccountTypes::Premium : APIUserAccountTypes::Regular)
+ .limits(NexusInterface::parseLimits(m_ValidateReply)));
m_ValidateReply->deleteLater();
m_ValidateReply = nullptr;
diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h
index e3608f8d..1d23faf9 100644
--- a/src/nxmaccessmanager.h
+++ b/src/nxmaccessmanager.h
@@ -20,7 +20,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#ifndef NXMACCESSMANAGER_H
#define NXMACCESSMANAGER_H
-
#include <QNetworkAccessManager>
#include <QTimer>
#include <QNetworkReply>
@@ -29,6 +28,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
namespace MOBase { class IPluginGame; }
+class APIUserAccount;
+
/**
* @brief access manager extended to handle nxm links
**/
@@ -78,7 +79,7 @@ signals:
void validateFailed(const QString &message);
- void credentialsReceived(const QString &userName, int userId, bool premium, std::tuple<int, int, int, int> limits);
+ void credentialsReceived(const APIUserAccount& user);
private slots:
diff --git a/src/statusbar.cpp b/src/statusbar.cpp
index 7370662a..6af10c56 100644
--- a/src/statusbar.cpp
+++ b/src/statusbar.cpp
@@ -1,23 +1,26 @@
#include "statusbar.h"
+#include "nexusinterface.h"
+#include "settings.h"
StatusBar::StatusBar(QStatusBar* bar)
- : m_bar(bar), m_nexusAPI(new QLabel), m_progress(new QProgressBar)
+ : m_bar(bar), m_api(new QLabel), m_progress(new QProgressBar)
{
m_progress->setTextVisible(true);
m_progress->setRange(0, 100);
- m_progress->setValue(0);
- m_progress->setVisible(false);
- m_bar->addPermanentWidget(m_nexusAPI);
+ m_bar->addPermanentWidget(m_api);
m_bar->addPermanentWidget(m_progress);
+ m_api->setObjectName("apistats");
+ m_api->setStyleSheet("QLabel{ padding-left: 0.1em; padding-right: 0.1em; }");
+
m_bar->clearMessage();
+ setProgress(-1);
+ updateAPI({}, {});
}
void StatusBar::setProgress(int percent)
{
- qDebug().nospace() << "progress: " << percent;
-
if (percent < 0 || percent >= 100) {
m_progress->setVisible(false);
} else if (!m_progress->isVisible()) {
@@ -25,3 +28,41 @@ void StatusBar::setProgress(int percent)
m_progress->setValue(percent);
}
}
+
+void StatusBar::updateAPI(const APIStats& stats, const APIUserAccount& user)
+{
+ m_api->setText(
+ QString("API: Q: %1 | D: %2 | H: %3")
+ .arg(stats.requestsQueued)
+ .arg(user.limits().remainingDailyRequests)
+ .arg(user.limits().remainingHourlyRequests));
+
+ QColor textColor;
+ QColor backgroundColor;
+
+ if (user.type() == APIUserAccountTypes::None) {
+ backgroundColor = Qt::transparent;
+ } else if (user.remainingRequests() > 300) {
+ textColor = "white";
+ backgroundColor = Qt::darkGreen;
+ } else if (user.remainingRequests() < 150) {
+ textColor = "white";
+ backgroundColor = Qt::darkRed;
+ } else {
+ textColor = "black";
+ backgroundColor = Qt::darkYellow;
+ }
+
+ QPalette palette = m_api->palette();
+
+ palette.setColor(QPalette::WindowText, textColor);
+ palette.setColor(QPalette::Background, backgroundColor);
+
+ m_api->setPalette(palette);
+ m_api->setAutoFillBackground(true);
+}
+
+void StatusBar::checkSettings(const Settings& settings)
+{
+ m_api->setVisible(!settings.hideAPICounter());
+}
diff --git a/src/statusbar.h b/src/statusbar.h
index f3ad3081..47a0e56d 100644
--- a/src/statusbar.h
+++ b/src/statusbar.h
@@ -4,12 +4,18 @@
#include <QStatusBar>
#include <QProgressBar>
+struct APIStats;
+class APIUserAccount;
+class Settings;
+
class StatusBar
{
public:
StatusBar(QStatusBar* bar);
void setProgress(int percent);
+ void updateAPI(const APIStats& stats, const APIUserAccount& user);
+ void checkSettings(const Settings& settings);
private:
QStatusBar* m_bar;