From 3fe54c8431b9c1e792a235b9b370267447c3210c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 10:11:55 -0400 Subject: now using ui file for validation progress dialog moved elapsed timer to NexusKeyValidator, progress dialog now just shows what the validator is doing, which allows for hiding and showing it at any time recreate the dialog when the parent changes, avoids theme errors --- src/nxmaccessmanager.cpp | 92 ++++++++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 38 deletions(-) (limited to 'src/nxmaccessmanager.cpp') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 3cc1b7d9..731feafb 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -18,6 +18,7 @@ along with Mod Organizer. If not, see . */ #include "nxmaccessmanager.h" +#include "ui_validationprogressdialog.h" #include "iplugingame.h" #include "nexusinterface.h" #include "nxmurl.h" @@ -48,24 +49,14 @@ const QString NexusSSO("wss://sso.nexusmods.com"); const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2"); -ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) : - m_timeout(t), m_bar(nullptr), m_buttons(nullptr), m_timer(nullptr), - m_first(true) +ValidationProgressDialog::ValidationProgressDialog(const NexusKeyValidator& v) + : m_validator(v), m_updateTimer(nullptr), m_first(true) { - m_bar = new QProgressBar; - m_bar->setTextVisible(false); + ui.reset(new Ui::ValidationProgressDialog); + ui->setupUi(this); - auto* label = new QLabel(tr("Validating Nexus Connection")); - label->setAlignment(Qt::AlignHCenter); - - auto* vbox = new QVBoxLayout(this); - vbox->addWidget(label); - vbox->addWidget(m_bar); - - m_buttons = new QDialogButtonBox; - m_buttons->addButton(tr("Hide"), QDialogButtonBox::RejectRole); - connect(m_buttons, &QDialogButtonBox::clicked, [&](auto* b){ onButton(b); }); - vbox->addWidget(m_buttons); + connect(ui->hide, &QPushButton::clicked, [&]{ onHide(); }); + connect(ui->cancel, &QPushButton::clicked, [&]{ onCancel(); }); } void ValidationProgressDialog::setParentWidget(QWidget* w) @@ -75,30 +66,32 @@ void ValidationProgressDialog::setParentWidget(QWidget* w) hide(); setParent(w, windowFlags() | Qt::Dialog); setModal(false); - setVisible(wasVisible); + + if (w && wasVisible) { + setVisible(true); + } } void ValidationProgressDialog::start() { - if (!m_timer) { - m_timer = new QTimer(this); - connect(m_timer, &QTimer::timeout, [&]{ onTimer(); }); - m_timer->setInterval(100ms); + if (!m_updateTimer) { + m_updateTimer = new QTimer(this); + connect(m_updateTimer, &QTimer::timeout, [&]{ onTimer(); }); + m_updateTimer->setInterval(100ms); } - m_bar->setRange(0, m_timeout.count()); - m_bar->setValue(0); + ui->progress->setRange(0, m_validator.timeout().count()); + ui->progress->setValue(0); - m_elapsed.start(); - m_timer->start(); + m_updateTimer->start(); show(); } void ValidationProgressDialog::stop() { - if (m_timer) { - m_timer->stop(); + if (m_updateTimer) { + m_updateTimer->stop(); } hide(); @@ -118,18 +111,18 @@ void ValidationProgressDialog::closeEvent(QCloseEvent* e) e->ignore(); } -void ValidationProgressDialog::onButton(QAbstractButton* b) +void ValidationProgressDialog::onHide() +{ + hide(); +} + +void ValidationProgressDialog::onCancel() { - if (m_buttons->buttonRole(b) == QDialogButtonBox::RejectRole) { - hide(); - } else { - qCritical() << "validation dialog: unknown button pressed"; - } } void ValidationProgressDialog::onTimer() { - m_bar->setValue(m_elapsed.elapsed() / 1000); + ui->progress->setValue(m_validator.elapsed().elapsed() / 1000); } @@ -395,6 +388,7 @@ void NexusKeyValidator::start(const QString& key) m_active = true; setState(Connecting); + m_elapsed.start(); const QString requestUrl(NexusBaseUrl + "/users/validate"); QNetworkRequest request(requestUrl); @@ -437,6 +431,16 @@ bool NexusKeyValidator::isActive() const return m_active; } +QElapsedTimer NexusKeyValidator::elapsed() const +{ + return m_elapsed; +} + +std::chrono::seconds NexusKeyValidator::timeout() const +{ + return NXMAccessManager::ValidationTimeout; +} + void NexusKeyValidator::close() { m_active = false; @@ -563,11 +567,12 @@ void NexusKeyValidator::handleError( NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) : QNetworkAccessManager(parent) - , m_ProgressDialog(new ValidationProgressDialog(ValidationTimeout)) , m_MOVersion(moVersion) , m_validator(*this) , m_validationState(NotChecked) { + m_ProgressDialog.reset(new ValidationProgressDialog(m_validator)); + m_validator.stateChanged = [&](auto&& s, auto&& e){ onValidatorState(s, e); }; m_validator.finished = [&](auto&& user){ onValidatorFinished(user); }; @@ -582,7 +587,13 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) void NXMAccessManager::setTopLevelWidget(QWidget* w) { - m_ProgressDialog->setParentWidget(w); + if (w) { + m_ProgressDialog->setParentWidget(w); + } else { + const auto v = m_ProgressDialog->isVisible(); + m_ProgressDialog.reset(new ValidationProgressDialog(m_validator)); + m_validator.cancel(); + } } QNetworkReply *NXMAccessManager::createRequest( @@ -641,8 +652,13 @@ void NXMAccessManager::onValidatorState( } m_ProgressDialog->stop(); - m_validationState = Invalid; - emit validateFailed(NexusKeyValidator::stateToString(s, e)); + + if (s == NexusKeyValidator::Cancelled) { + m_validationState = NotChecked; + } else { + m_validationState = Invalid; + emit validateFailed(NexusKeyValidator::stateToString(s, e)); + } } void NXMAccessManager::onValidatorFinished(const APIUserAccount& user) -- cgit v1.3.1 From fc909d4b2eaff02cb8b7f38ca5079dc9b12cef68 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 10:41:23 -0400 Subject: implemented cancel button on progress dialog fixed crash when exiting MO because the dialog is destroyed in atexit(), way after Qt is gone --- src/nxmaccessmanager.cpp | 32 +++++++++++++++++++++++--------- src/nxmaccessmanager.h | 10 ++++++---- 2 files changed, 29 insertions(+), 13 deletions(-) (limited to 'src/nxmaccessmanager.cpp') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 731feafb..036b22ff 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -49,7 +49,7 @@ const QString NexusSSO("wss://sso.nexusmods.com"); const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2"); -ValidationProgressDialog::ValidationProgressDialog(const NexusKeyValidator& v) +ValidationProgressDialog::ValidationProgressDialog(NexusKeyValidator& v) : m_validator(v), m_updateTimer(nullptr), m_first(true) { ui.reset(new Ui::ValidationProgressDialog); @@ -118,6 +118,7 @@ void ValidationProgressDialog::onHide() void ValidationProgressDialog::onCancel() { + m_validator.cancel(); } void ValidationProgressDialog::onTimer() @@ -571,8 +572,6 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) , m_validator(*this) , m_validationState(NotChecked) { - m_ProgressDialog.reset(new ValidationProgressDialog(m_validator)); - m_validator.stateChanged = [&](auto&& s, auto&& e){ onValidatorState(s, e); }; m_validator.finished = [&](auto&& user){ onValidatorFinished(user); }; @@ -590,8 +589,7 @@ void NXMAccessManager::setTopLevelWidget(QWidget* w) if (w) { m_ProgressDialog->setParentWidget(w); } else { - const auto v = m_ProgressDialog->isVisible(); - m_ProgressDialog.reset(new ValidationProgressDialog(m_validator)); + m_ProgressDialog.reset(); m_validator.cancel(); } } @@ -640,7 +638,7 @@ void NXMAccessManager::startValidationCheck(const QString& key) { m_validationState = NotChecked; m_validator.start(key); - m_ProgressDialog->start(); + startProgress(); } void NXMAccessManager::onValidatorState( @@ -651,7 +649,7 @@ void NXMAccessManager::onValidatorState( return; } - m_ProgressDialog->stop(); + stopProgress(); if (s == NexusKeyValidator::Cancelled) { m_validationState = NotChecked; @@ -663,7 +661,7 @@ void NXMAccessManager::onValidatorState( void NXMAccessManager::onValidatorFinished(const APIUserAccount& user) { - m_ProgressDialog->stop(); + stopProgress(); m_validationState = Valid; emit credentialsReceived(user); @@ -673,7 +671,7 @@ void NXMAccessManager::onValidatorFinished(const APIUserAccount& user) bool NXMAccessManager::validated() const { if (m_validator.isActive()) { - m_ProgressDialog->show(); + const_cast(this)->startProgress(); } return (m_validationState == Valid); @@ -739,3 +737,19 @@ void NXMAccessManager::clearApiKey() m_validator.cancel(); emit credentialsReceived(APIUserAccount()); } + +void NXMAccessManager::startProgress() +{ + if (!m_ProgressDialog) { + m_ProgressDialog.reset(new ValidationProgressDialog(m_validator)); + } + + m_ProgressDialog->start(); +} + +void NXMAccessManager::stopProgress() +{ + if (m_ProgressDialog) { + m_ProgressDialog->stop(); + } +} diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 2a4c066c..f0bdd8a6 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -137,7 +137,7 @@ class ValidationProgressDialog : public QDialog Q_OBJECT; public: - ValidationProgressDialog(const NexusKeyValidator& v); + ValidationProgressDialog(NexusKeyValidator& v); void setParentWidget(QWidget* w); @@ -150,7 +150,7 @@ protected: private: std::unique_ptr ui; - const NexusKeyValidator& m_validator; + NexusKeyValidator& m_validator; QTimer* m_updateTimer; bool m_first; @@ -169,8 +169,7 @@ class NXMAccessManager : public QNetworkAccessManager public: static const std::chrono::seconds ValidationTimeout; - explicit NXMAccessManager(QObject *parent, const QString &moVersion); - + NXMAccessManager(QObject *parent, const QString &moVersion); void setTopLevelWidget(QWidget* w); @@ -233,6 +232,9 @@ private: void startValidationCheck(const QString& key); void onValidatorState(NexusKeyValidator::States s, const QString& e); void onValidatorFinished(const APIUserAccount& user); + + void startProgress(); + void stopProgress(); }; #endif // NXMACCESSMANAGER_H -- cgit v1.3.1 From 8269ac1b489d30ca6065ec2b97542ddbb22298b3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 13:36:27 -0400 Subject: rework of the nexus key validator to allow multiple attempts before failing --- src/nxmaccessmanager.cpp | 495 ++++++++++++++++++++++++++++++-------------- src/nxmaccessmanager.h | 100 ++++++--- src/settingsdialognexus.cpp | 45 ++-- src/settingsdialognexus.h | 5 +- 4 files changed, 428 insertions(+), 217 deletions(-) (limited to 'src/nxmaccessmanager.cpp') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 036b22ff..99b93048 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -44,7 +44,6 @@ using namespace MOBase; using namespace std::chrono_literals; const QString NexusBaseUrl("https://api.nexusmods.com/v1"); -const std::chrono::seconds NXMAccessManager::ValidationTimeout = 10s; const QString NexusSSO("wss://sso.nexusmods.com"); const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2"); @@ -69,6 +68,7 @@ void ValidationProgressDialog::setParentWidget(QWidget* w) if (w && wasVisible) { setVisible(true); + raise(); } } @@ -80,9 +80,7 @@ void ValidationProgressDialog::start() m_updateTimer->setInterval(100ms); } - ui->progress->setRange(0, m_validator.timeout().count()); - ui->progress->setValue(0); - + updateProgress(); m_updateTimer->start(); show(); @@ -123,14 +121,35 @@ void ValidationProgressDialog::onCancel() void ValidationProgressDialog::onTimer() { - ui->progress->setValue(m_validator.elapsed().elapsed() / 1000); + updateProgress(); +} + +void ValidationProgressDialog::updateProgress() +{ + const auto* current = m_validator.currentAttempt(); + + if (current) { + ui->progress->setRange(0, current->timeout().count()); + ui->progress->setValue(current->elapsed().elapsed() / 1000); + } else { + // indeterminate + ui->progress->setRange(0, 0); + } + + if (const auto* a=m_validator.lastAttempt()) { + ui->label->setText(a->message() + ". " + tr("Trying again...")); + } else if (current) { + ui->label->setText(tr("Connecting to Nexus...")); + } else { + ui->label->setText("?"); + } } NexusSSOLogin::NexusSSOLogin() : m_keyReceived(false), m_active(false) { - m_timeout.setInterval(NXMAccessManager::ValidationTimeout); + m_timeout.setInterval(10s); m_timeout.setSingleShot(true); QObject::connect( @@ -167,20 +186,22 @@ QString NexusSSOLogin::stateToString(States s, const QString& e) return QObject::tr("Waiting for Nexus..."); case WaitingForBrowser: - return QObject::tr( - "Opened Nexus in browser.\n" - "Switch to your browser and accept the request."); + return + QObject::tr("Opened Nexus in browser.") + "\n" + + QObject::tr("Switch to your browser and accept the request."); case Finished: return QObject::tr("Finished."); case Timeout: - return QObject::tr( - "No answer from Nexus.\n" - "A firewall might be blocking Mod Organizer."); + return + QObject::tr("No answer from Nexus.") + "\n" + + QObject::tr("A firewall might be blocking Mod Organizer."); case ClosedByRemote: - return QObject::tr("Nexus closed the connection."); + return + QObject::tr("Nexus closed the connection.") + "\n" + + QObject::tr("A firewall might be blocking Mod Organizer."); case Cancelled: return QObject::tr("Cancelled."); @@ -300,10 +321,11 @@ void NexusSSOLogin::onMessage(const QString& s) void NexusSSOLogin::onDisconnected() { if (m_active) { - m_active = false; - if (!m_keyReceived) { + close(); setState(ClosedByRemote); + } else { + m_active = false; } } } @@ -332,84 +354,48 @@ void NexusSSOLogin::onTimeout() } -NexusKeyValidator::NexusKeyValidator(NXMAccessManager& am) - : m_manager(am), m_reply(nullptr), m_active(false) +ValidationAttempt::ValidationAttempt(std::chrono::seconds timeout) + : m_reply(nullptr), m_result(None) { - m_timeout.setInterval(NXMAccessManager::ValidationTimeout); m_timeout.setSingleShot(true); + m_timeout.setInterval(timeout); QObject::connect(&m_timeout, &QTimer::timeout, [&]{ onTimeout(); }); } -NexusKeyValidator::~NexusKeyValidator() -{ - abort(); -} - -QString NexusKeyValidator::stateToString(States s, const QString& e) -{ - switch (s) - { - case NexusKeyValidator::Connecting: - return QObject::tr("Connecting to Nexus..."); - - case NexusKeyValidator::Finished: - return QObject::tr("Finished."); - - case NexusKeyValidator::InvalidJson: - return QObject::tr("Invalid JSON"); - - case NexusKeyValidator::BadResponse: - return QObject::tr("Bad response"); - - case NexusKeyValidator::Timeout: - return QObject::tr("There was a timeout during the request"); - - case NexusKeyValidator::Cancelled: - return QObject::tr("Cancelled"); - - case NexusKeyValidator::Error: // fall-through - default: - { - if (e.isEmpty()) { - return QString("%1").arg(s); - } else { - return e; - } - } - } -} - -void NexusKeyValidator::start(const QString& key) +void ValidationAttempt::start(NXMAccessManager& m, const QString& key) { - if (m_reply) { - abort(); + if (!sendRequest(m, key)) { return; } - m_active = true; - setState(Connecting); m_elapsed.start(); + m_timeout.start(); + + log::debug( + "validator: attempt started with timeout of {} seconds", timeout().count()); +} +bool ValidationAttempt::sendRequest( + NXMAccessManager& m, const QString& key) +{ const QString requestUrl(NexusBaseUrl + "/users/validate"); QNetworkRequest request(requestUrl); request.setRawHeader("APIKEY", key.toUtf8()); - request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, m_manager.userAgent().toUtf8()); + request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, m.userAgent().toUtf8()); request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Protocol-Version", "1.0.0"); request.setRawHeader("Application-Name", "MO2"); - request.setRawHeader("Application-Version", m_manager.MOVersion().toUtf8()); + request.setRawHeader("Application-Version", m.MOVersion().toUtf8()); + + m_reply = m.get(request); - m_reply = m_manager.get(request); if (!m_reply) { - close(); - setState(Error, QObject::tr("Failed to request %1").arg(requestUrl)); - return; + setFailure(SoftError, QObject::tr("Failed to request %1").arg(requestUrl)); + return false; } - m_timeout.start(NXMAccessManager::ValidationTimeout); - QObject::connect( m_reply, &QNetworkReply::finished, [&]{ onFinished(); }); @@ -417,93 +403,114 @@ void NexusKeyValidator::start(const QString& key) QObject::connect( m_reply, &QNetworkReply::sslErrors, [&](auto&& errors){ onSslErrors(errors); }); + + return true; } -void NexusKeyValidator::cancel() +void ValidationAttempt::cancel() { - if (m_active) { - abort(); - setState(Cancelled); + if (!m_reply || m_result != None) { + // not running + return; } + + setFailure(Cancelled, QObject::tr("Cancelled")); + + if (m_reply) { + m_reply->abort(); + } + + cleanup(); } -bool NexusKeyValidator::isActive() const +bool ValidationAttempt::done() const { - return m_active; + return (m_result != None); } -QElapsedTimer NexusKeyValidator::elapsed() const +ValidationAttempt::Result ValidationAttempt::result() const { - return m_elapsed; + return m_result; } -std::chrono::seconds NexusKeyValidator::timeout() const +const QString& ValidationAttempt::message() const { - return NXMAccessManager::ValidationTimeout; + return m_message; } -void NexusKeyValidator::close() +std::chrono::seconds ValidationAttempt::timeout() const { - m_active = false; - m_timeout.stop(); - - if (m_reply) { - m_reply->disconnect(); - m_reply->deleteLater(); - m_reply = nullptr; - } + return std::chrono::duration_cast( + m_timeout.intervalAsDuration()); } -void NexusKeyValidator::abort() +QElapsedTimer ValidationAttempt::elapsed() const { - m_active = false; - m_timeout.stop(); - - if (m_reply) { - m_reply->disconnect(); - m_reply->abort(); - m_reply->deleteLater(); - m_reply = nullptr; - } + return m_elapsed; } -void NexusKeyValidator::setState(States s, const QString& error) +void ValidationAttempt::onFinished() { - if (stateChanged) { - stateChanged(s, error); + if (m_result == Cancelled) { + return; } -} -void NexusKeyValidator::onFinished() -{ + log::debug("validator attempt: request has finished"); + if (!m_reply) { // shouldn't happen + log::error("validator attempt: reply is null"); + setFailure(HardError, QObject::tr("Internal error")); return; } - m_timeout.stop(); + const auto code = m_reply->attribute( + QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (code == 0) { + // request wasn't even sent + log::error("validator attempt: code is 0"); + setFailure(SoftError, m_reply->errorString()); + return; + } - const auto code = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); const auto doc = QJsonDocument::fromJson(m_reply->readAll()); const auto headers = m_reply->rawHeaderPairs(); - const auto error = m_reply->errorString(); - - close(); + const auto httpError = m_reply->errorString(); const QJsonObject data = doc.object(); if (code != 200) { - handleError(code, data.value("message").toString(), error); + // http request failed + + QString s = m_reply->errorString(); + + const auto nexusMessage = data.value("message").toString(); + if (!nexusMessage.isEmpty()) { + if (!s.isEmpty()) { + s += ", "; + } + + s += nexusMessage; + } + + if (s.isEmpty()) { + s = QObject::tr("HTTP code %1").arg(code); + } else { + s += QString(" (%1)").arg(code); + } + + setFailure(SoftError, s); return; } if (doc.isNull()) { - setState(InvalidJson); + setFailure(HardError, QObject::tr("Invalid JSON")); return; } if (!data.contains("user_id")) { - setState(BadResponse); + setFailure(HardError, QObject::tr("Bad response")); return; } @@ -512,6 +519,11 @@ void NexusKeyValidator::onFinished() const QString name = data.value("name").toString(); const bool premium = data.value("is_premium").toBool(); + if (key.isEmpty()) { + setFailure(HardError, QObject::tr("API key is empty")); + return; + } + const auto user = APIUserAccount() .apiKey(key) .id(QString("%1").arg(id)) @@ -519,51 +531,218 @@ void NexusKeyValidator::onFinished() .type(premium ? APIUserAccountTypes::Premium : APIUserAccountTypes::Regular) .limits(NexusInterface::parseLimits(headers)); - if (finished) { - setState(Finished); - finished(user); + setSuccess(user); +} + +void ValidationAttempt::onSslErrors(const QList& errors) +{ + log::error("validator attempt: ssl errors"); + + for (auto& e : errors) { + log::error(" . {}", e.errorString()); } + + setFailure(HardError, QObject::tr("SSL error")); } -void NexusKeyValidator::onSslErrors(const QList& errors) +void ValidationAttempt::onTimeout() { - if (m_active) { - for (const auto& e : errors) { - setState(Error, e.errorString()); - } + setFailure(SoftError, QObject::tr("Timed out")); +} + +void ValidationAttempt::setFailure(Result r, const QString& error) +{ + if (r != Cancelled) { + // don't spam the log + log::error("validator attempt: {}", error); + } + + cleanup(); + + m_result = r; + m_message = error; + + if (failure) { + failure(); } } -void NexusKeyValidator::onTimeout() +void ValidationAttempt::setSuccess(const APIUserAccount& user) +{ + log::debug("validator attempt successful"); + cleanup(); + + m_result = Success; + m_message = ""; + + if (success) { + success(user); + } +} + +void ValidationAttempt::cleanup() +{ + m_timeout.stop(); + + if (m_reply) { + m_reply->disconnect(); + m_reply->deleteLater(); + m_reply = nullptr; + } +} + + +NexusKeyValidator::NexusKeyValidator(NXMAccessManager& am) + : m_manager(am) { - abort(); - setState(Timeout); } -void NexusKeyValidator::handleError( - int code, const QString& nexusMessage, const QString& httpError) +NexusKeyValidator::~NexusKeyValidator() { - QString s = httpError; + cancel(); +} - if (!nexusMessage.isEmpty()) { - if (!s.isEmpty()) { - s += ", "; +void NexusKeyValidator::start(const QString& key, Behaviour b) +{ + if (isActive()) { + log::debug("validator: trying to start while ongoing; ignoring"); + return; + } + + m_key = key; + + switch (b) + { + case OneShot: + { + createAttempts({10s}); + break; } - s += nexusMessage; + case Retry: + { + createAttempts({5s, 5s, 10s}); + break; + } } - if (code != 0) { - if (s.isEmpty()) { - s = QString("HTTP code %1").arg(code); + nextTry(); +} + +void NexusKeyValidator::createAttempts( + const std::vector& timeouts) +{ + m_attempts.clear(); + + for (auto&& t : timeouts) { + m_attempts.push_back(std::make_unique(t)); + } +} + +void NexusKeyValidator::cancel() +{ + log::debug("validator: cancelled"); + + for (auto&& a : m_attempts) { + a->cancel(); + } +} + +bool NexusKeyValidator::isActive() const +{ + for (auto&& a : m_attempts) { + if (!a->done()) { + return true; + } + } + + return false; +} + +const ValidationAttempt* NexusKeyValidator::lastAttempt() const +{ + const ValidationAttempt* last = nullptr; + + for (auto&& a : m_attempts) { + if (a->done()) { + last = a.get(); } else { - s += QString(" (%1)").arg(code); + break; } } - setState(Error, s); + return last; } +const ValidationAttempt* NexusKeyValidator::currentAttempt() const +{ + for (auto&& a : m_attempts) { + if (!a->done()) { + return a.get(); + } + } + + return nullptr; +} + +bool NexusKeyValidator::nextTry() +{ + for (auto&& a : m_attempts) { + if (!a->done()) { + a->success = [&](auto&& user){ onAttemptSuccess(*a, user); }; + a->failure = [&]{ onAttemptFailure(*a); }; + + a->start(m_manager, m_key); + return true; + } + } + + // no more + return false; +} + +void NexusKeyValidator::onAttemptSuccess( + const ValidationAttempt&, const APIUserAccount& u) +{ + setFinished(ValidationAttempt::Success, "", u); +} + +void NexusKeyValidator::onAttemptFailure(const ValidationAttempt& a) +{ + switch (a.result()) + { + case ValidationAttempt::SoftError: + { + if (!nextTry()) { + setFinished(a.result(), a.message(), {}); + } + + break; + } + + case ValidationAttempt::HardError: + { + cancel(); + setFinished(a.result(), a.message(), {}); + break; + } + + case ValidationAttempt::Cancelled: + { + setFinished(ValidationAttempt::Cancelled, QObject::tr("Cancelled"), {}); + break; + } + } +} + +void NexusKeyValidator::setFinished( + ValidationAttempt::Result r, const QString& message, + std::optional user) +{ + if (finished) { + finished(r, message, user); + } +} NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) @@ -572,8 +751,9 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) , m_validator(*this) , m_validationState(NotChecked) { - m_validator.stateChanged = [&](auto&& s, auto&& e){ onValidatorState(s, e); }; - m_validator.finished = [&](auto&& user){ onValidatorFinished(user); }; + m_validator.finished = [&](auto&& r, auto&& m, auto&& u) { + onValidatorFinished(r, m, u); + }; setCookieJar(new PersistentCookieJar(QDir::fromNativeSeparators( Settings::instance().paths().cache() + "/nexus_cookies.dat"))); @@ -587,7 +767,9 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) void NXMAccessManager::setTopLevelWidget(QWidget* w) { if (w) { - m_ProgressDialog->setParentWidget(w); + if (m_ProgressDialog) { + m_ProgressDialog->setParentWidget(w); + } } else { m_ProgressDialog.reset(); m_validator.cancel(); @@ -637,37 +819,30 @@ void NXMAccessManager::clearCookies() void NXMAccessManager::startValidationCheck(const QString& key) { m_validationState = NotChecked; - m_validator.start(key); + m_validator.start(key, NexusKeyValidator::Retry); startProgress(); } -void NXMAccessManager::onValidatorState( - NexusKeyValidator::States s, const QString& e) +void NXMAccessManager::onValidatorFinished( + ValidationAttempt::Result r, const QString& message, + std::optional user) { - if (s == NexusKeyValidator::Connecting || s == NexusKeyValidator::Finished) { - // no-op, success is handled in onValidatorFinished() - return; - } - stopProgress(); - if (s == NexusKeyValidator::Cancelled) { - m_validationState = NotChecked; + if (user) { + m_validationState = Valid; + emit credentialsReceived(*user); + emit validateSuccessful(true); } else { - m_validationState = Invalid; - emit validateFailed(NexusKeyValidator::stateToString(s, e)); + if (r == ValidationAttempt::Cancelled) { + m_validationState = NotChecked; + } else { + m_validationState = Invalid; + emit validateFailed(message); + } } } -void NXMAccessManager::onValidatorFinished(const APIUserAccount& user) -{ - stopProgress(); - - m_validationState = Valid; - emit credentialsReceived(user); - emit validateSuccessful(true); -} - bool NXMAccessManager::validated() const { if (m_validator.isActive()) { diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index f0bdd8a6..b0ea45e7 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -82,53 +82,93 @@ private: }; -class NexusKeyValidator +class ValidationAttempt { public: - enum States + enum Result { - Connecting, - Finished, - InvalidJson, - BadResponse, - Timeout, - Cancelled, - Error + None, + Success, + SoftError, + HardError, + Cancelled }; - std::function finished; - std::function stateChanged; - - static QString stateToString(States s, const QString& e); + std::function success; + std::function failure; - NexusKeyValidator(NXMAccessManager& am); - ~NexusKeyValidator(); + ValidationAttempt(std::chrono::seconds timeout); + ValidationAttempt(const ValidationAttempt&) = delete; + ValidationAttempt& operator=(const ValidationAttempt&) = delete; - void start(const QString& key); + void start(NXMAccessManager& m, const QString& key); void cancel(); - bool isActive() const; - QElapsedTimer elapsed() const; + bool done() const; + Result result() const; + const QString& message() const; std::chrono::seconds timeout() const; + QElapsedTimer elapsed() const; private: - NXMAccessManager& m_manager; QNetworkReply* m_reply; + Result m_result; + QString m_message; QTimer m_timeout; - bool m_active; QElapsedTimer m_elapsed; - void setState(States s, const QString& error={}); - - void close(); - void abort(); + bool sendRequest(NXMAccessManager& m, const QString& key); void onFinished(); void onSslErrors(const QList& errors); void onTimeout(); - void handleError( - int code, const QString& nexusMessage, const QString& httpError); + void setFailure(Result r, const QString& error); + void setSuccess(const APIUserAccount& user); + + void cleanup(); +}; + + +class NexusKeyValidator +{ +public: + enum Behaviour + { + OneShot = 0, + Retry + }; + + using FinishedCallback = void ( + ValidationAttempt::Result, const QString&, + std::optional); + + std::function finished; + + NexusKeyValidator(NXMAccessManager& am); + ~NexusKeyValidator(); + + void start(const QString& key, Behaviour b); + void cancel(); + + bool isActive() const; + const ValidationAttempt* lastAttempt() const; + const ValidationAttempt* currentAttempt() const; + +private: + NXMAccessManager& m_manager; + QString m_key; + std::vector> m_attempts; + + void createAttempts(const std::vector& timeouts); + + bool nextTry(); + void onAttemptSuccess(const ValidationAttempt& a, const APIUserAccount& u); + void onAttemptFailure(const ValidationAttempt& a); + + void setFinished( + ValidationAttempt::Result r, const QString& message, + std::optional user); }; @@ -157,6 +197,7 @@ private: void onHide(); void onCancel(); void onTimer(); + void updateProgress(); }; @@ -167,8 +208,6 @@ class NXMAccessManager : public QNetworkAccessManager { Q_OBJECT public: - static const std::chrono::seconds ValidationTimeout; - NXMAccessManager(QObject *parent, const QString &moVersion); void setTopLevelWidget(QWidget* w); @@ -230,8 +269,9 @@ private: States m_validationState; void startValidationCheck(const QString& key); - void onValidatorState(NexusKeyValidator::States s, const QString& e); - void onValidatorFinished(const APIUserAccount& user); + void onValidatorFinished( + ValidationAttempt::Result r, const QString& message, + std::optional); void startProgress(); void stopProgress(); diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 2021bdc1..209ed661 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -240,17 +240,13 @@ void NexusSettingsTab::validateKey(const QString& key) m_nexusValidator.reset(new NexusKeyValidator( *NexusInterface::instance(dialog().pluginContainer())->getAccessManager())); - m_nexusValidator->stateChanged = [&](auto&& s, auto&& e){ - onValidatorStateChanged(s, e); - }; - - m_nexusValidator->finished = [&](auto&& user) { - onValidatorFinished(user); + m_nexusValidator->finished = [&](auto&& r, auto&& m, auto&& u) { + onValidatorFinished(r, m, u); }; } addNexusLog(QObject::tr("Checking API key...")); - m_nexusValidator->start(key); + m_nexusValidator->start(key, NexusKeyValidator::OneShot); } void NexusSettingsTab::onSSOKeyChanged(const QString& key) @@ -277,32 +273,31 @@ void NexusSettingsTab::onSSOStateChanged(NexusSSOLogin::States s, const QString& updateNexusState(); } -void NexusSettingsTab::onValidatorStateChanged( - NexusKeyValidator::States s, const QString& e) +void NexusSettingsTab::onValidatorFinished( + ValidationAttempt::Result r, const QString& message, + std::optional user) { - if (s != NexusKeyValidator::Finished) { - // finished state is handled in onValidatorFinished() - const auto log = NexusKeyValidator::stateToString(s, e); + if (user) { + NexusInterface::instance(dialog().pluginContainer())->setUserAccount(*user); + addNexusLog(QObject::tr("Received user acount information")); - for (auto&& line : log.split("\n")) { - addNexusLog(line); + if (setKey(user->apiKey())) { + addNexusLog(QObject::tr("Linked with Nexus successfully.")); + } else { + addNexusLog(QObject::tr("Failed to set API key")); + } + } else { + if (message.isEmpty()) { + // shouldn't happen + addNexusLog("Unknown error"); + } else { + addNexusLog(message); } } updateNexusState(); } -void NexusSettingsTab::onValidatorFinished(const APIUserAccount& user) -{ - NexusInterface::instance(dialog().pluginContainer())->setUserAccount(user); - - if (!user.apiKey().isEmpty()) { - if (setKey(user.apiKey())) { - addNexusLog(QObject::tr("Linked with Nexus successfully.")); - } - } -} - void NexusSettingsTab::addNexusLog(const QString& s) { ui->nexusLog->addItem(s); diff --git a/src/settingsdialognexus.h b/src/settingsdialognexus.h index 89a6618f..2cb1cc1e 100644 --- a/src/settingsdialognexus.h +++ b/src/settingsdialognexus.h @@ -32,8 +32,9 @@ private: void onSSOKeyChanged(const QString& key); void onSSOStateChanged(NexusSSOLogin::States s, const QString& e); - void onValidatorStateChanged(NexusKeyValidator::States s, const QString& e); - void onValidatorFinished(const APIUserAccount& user); + void onValidatorFinished( + ValidationAttempt::Result r, const QString& message, + std::optional useR); void addNexusLog(const QString& s); }; -- cgit v1.3.1 From e7cc4774e1a7d03fb0710875f6a91ec703b0005e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 13:47:11 -0400 Subject: don't show the progress dialog on startup until after the first failure --- src/nxmaccessmanager.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++-- src/nxmaccessmanager.h | 4 ++++ 2 files changed, 47 insertions(+), 2 deletions(-) (limited to 'src/nxmaccessmanager.cpp') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 99b93048..5d8b8b9e 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -702,13 +702,21 @@ bool NexusKeyValidator::nextTry() } void NexusKeyValidator::onAttemptSuccess( - const ValidationAttempt&, const APIUserAccount& u) + const ValidationAttempt& a, const APIUserAccount& u) { + if (attemptFinished) { + attemptFinished(a); + } + setFinished(ValidationAttempt::Success, "", u); } void NexusKeyValidator::onAttemptFailure(const ValidationAttempt& a) { + if (attemptFinished) { + attemptFinished(a); + } + switch (a.result()) { case ValidationAttempt::SoftError: @@ -755,6 +763,10 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) onValidatorFinished(r, m, u); }; + m_validator.attemptFinished = [&](auto&& a) { + onValidatorAttemptFinished(a); + }; + setCookieJar(new PersistentCookieJar(QDir::fromNativeSeparators( Settings::instance().paths().cache() + "/nexus_cookies.dat"))); @@ -820,7 +832,12 @@ void NXMAccessManager::startValidationCheck(const QString& key) { m_validationState = NotChecked; m_validator.start(key, NexusKeyValidator::Retry); - startProgress(); + + if (m_ProgressDialog) { + // don't show the progress dialog on startup for the first attempt; the + // dialog will be shown in onValidatorAttemptFinished() if it failed + startProgress(); + } } void NXMAccessManager::onValidatorFinished( @@ -843,6 +860,30 @@ void NXMAccessManager::onValidatorFinished( } } +void NXMAccessManager::onValidatorAttemptFinished(const ValidationAttempt& a) +{ + if (!m_ProgressDialog) { + switch (a.result()) + { + case ValidationAttempt::SoftError: + case ValidationAttempt::HardError: + { + startProgress(); + break; + } + + case ValidationAttempt::None: + case ValidationAttempt::Success: + case ValidationAttempt::Cancelled: + default: + { + // don't show the dialog + break; + } + } + } +} + bool NXMAccessManager::validated() const { if (m_validator.isActive()) { diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index b0ea45e7..6a45d880 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -144,6 +144,7 @@ public: std::optional); std::function finished; + std::function attemptFinished; NexusKeyValidator(NXMAccessManager& am); ~NexusKeyValidator(); @@ -269,10 +270,13 @@ private: States m_validationState; void startValidationCheck(const QString& key); + void onValidatorFinished( ValidationAttempt::Result r, const QString& message, std::optional); + void onValidatorAttemptFinished(const ValidationAttempt& a); + void startProgress(); void stopProgress(); }; -- cgit v1.3.1