diff options
| author | isanae <14251494+isanae@users.noreply.github.com> | 2019-10-07 02:28:34 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-10-07 02:28:34 -0400 |
| commit | 97d58a17bdf7b8b29b413c54490c863b875b8a59 (patch) | |
| tree | e74d5561f536a56d930a14496334c4868496d240 /src | |
| parent | 73556ad0845ee6dea2ec9f1fa50903de0448fa91 (diff) | |
| parent | 925dbcba875477e1111453878f9b69e069bc8fd2 (diff) | |
Merge pull request #853 from isanae/validation-progress-dialog-fixes
Validation progress dialog fixes
Diffstat (limited to 'src')
| -rw-r--r-- | src/nxmaccessmanager.cpp | 608 | ||||
| -rw-r--r-- | src/nxmaccessmanager.h | 169 | ||||
| -rw-r--r-- | src/settingsdialognexus.cpp | 51 | ||||
| -rw-r--r-- | src/settingsdialognexus.h | 5 | ||||
| -rw-r--r-- | src/validationprogressdialog.ui | 117 |
5 files changed, 681 insertions, 269 deletions
diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 3cc1b7d9..5d8b8b9e 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -18,6 +18,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. */ #include "nxmaccessmanager.h" +#include "ui_validationprogressdialog.h" #include "iplugingame.h" #include "nexusinterface.h" #include "nxmurl.h" @@ -43,29 +44,18 @@ 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"); -ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) : - m_timeout(t), m_bar(nullptr), m_buttons(nullptr), m_timer(nullptr), - m_first(true) +ValidationProgressDialog::ValidationProgressDialog(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 +65,31 @@ void ValidationProgressDialog::setParentWidget(QWidget* w) hide(); setParent(w, windowFlags() | Qt::Dialog); setModal(false); - setVisible(wasVisible); + + if (w && wasVisible) { + setVisible(true); + raise(); + } } 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); - - m_elapsed.start(); - m_timer->start(); + updateProgress(); + m_updateTimer->start(); show(); } void ValidationProgressDialog::stop() { - if (m_timer) { - m_timer->stop(); + if (m_updateTimer) { + m_updateTimer->stop(); } hide(); @@ -118,25 +109,47 @@ void ValidationProgressDialog::closeEvent(QCloseEvent* e) e->ignore(); } -void ValidationProgressDialog::onButton(QAbstractButton* b) +void ValidationProgressDialog::onHide() { - if (m_buttons->buttonRole(b) == QDialogButtonBox::RejectRole) { - hide(); - } else { - qCritical() << "validation dialog: unknown button pressed"; - } + hide(); +} + +void ValidationProgressDialog::onCancel() +{ + m_validator.cancel(); } void ValidationProgressDialog::onTimer() { - m_bar->setValue(m_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( @@ -173,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."); @@ -306,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; } } } @@ -338,83 +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) +void ValidationAttempt::start(NXMAccessManager& m, const QString& key) { - 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"); + if (!sendRequest(m, key)) { + return; + } - case NexusKeyValidator::Cancelled: - return QObject::tr("Cancelled"); + m_elapsed.start(); + m_timeout.start(); - case NexusKeyValidator::Error: // fall-through - default: - { - if (e.isEmpty()) { - return QString("%1").arg(s); - } else { - return e; - } - } - } + log::debug( + "validator: attempt started with timeout of {} seconds", timeout().count()); } -void NexusKeyValidator::start(const QString& key) +bool ValidationAttempt::sendRequest( + NXMAccessManager& m, const QString& key) { - if (m_reply) { - abort(); - return; - } - - m_active = true; - setState(Connecting); - 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(); }); @@ -422,83 +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); } -void NexusKeyValidator::close() +ValidationAttempt::Result ValidationAttempt::result() const { - m_active = false; - m_timeout.stop(); - - if (m_reply) { - m_reply->disconnect(); - m_reply->deleteLater(); - m_reply = nullptr; - } + return m_result; } -void NexusKeyValidator::abort() +const QString& ValidationAttempt::message() const { - m_active = false; - m_timeout.stop(); + return m_message; +} - if (m_reply) { - m_reply->disconnect(); - m_reply->abort(); - m_reply->deleteLater(); - m_reply = nullptr; - } +std::chrono::seconds ValidationAttempt::timeout() const +{ + return std::chrono::duration_cast<std::chrono::seconds>( + m_timeout.intervalAsDuration()); } -void NexusKeyValidator::setState(States s, const QString& error) +QElapsedTimer ValidationAttempt::elapsed() const { - if (stateChanged) { - stateChanged(s, error); - } + return m_elapsed; } -void NexusKeyValidator::onFinished() +void ValidationAttempt::onFinished() { + if (m_result == Cancelled) { + return; + } + + 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; } @@ -507,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)) @@ -514,62 +531,241 @@ 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<QSslError>& 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<QSslError>& 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) { - abort(); - setState(Timeout); + log::debug("validator attempt successful"); + cleanup(); + + m_result = Success; + m_message = ""; + + if (success) { + success(user); + } } -void NexusKeyValidator::handleError( - int code, const QString& nexusMessage, const QString& httpError) +void ValidationAttempt::cleanup() { - QString s = httpError; + m_timeout.stop(); + + if (m_reply) { + m_reply->disconnect(); + m_reply->deleteLater(); + m_reply = nullptr; + } +} + + +NexusKeyValidator::NexusKeyValidator(NXMAccessManager& am) + : m_manager(am) +{ +} + +NexusKeyValidator::~NexusKeyValidator() +{ + cancel(); +} + +void NexusKeyValidator::start(const QString& key, Behaviour b) +{ + if (isActive()) { + log::debug("validator: trying to start while ongoing; ignoring"); + return; + } + + m_key = key; - if (!nexusMessage.isEmpty()) { - if (!s.isEmpty()) { - s += ", "; + 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<std::chrono::seconds>& timeouts) +{ + m_attempts.clear(); + + for (auto&& t : timeouts) { + m_attempts.push_back(std::make_unique<ValidationAttempt>(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& 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: + { + 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<APIUserAccount> user) +{ + if (finished) { + finished(r, message, user); + } +} NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) : QNetworkAccessManager(parent) - , m_ProgressDialog(new ValidationProgressDialog(ValidationTimeout)) , m_MOVersion(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); + }; + + m_validator.attemptFinished = [&](auto&& a) { + onValidatorAttemptFinished(a); + }; setCookieJar(new PersistentCookieJar(QDir::fromNativeSeparators( Settings::instance().paths().cache() + "/nexus_cookies.dat"))); @@ -582,7 +778,14 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) void NXMAccessManager::setTopLevelWidget(QWidget* w) { - m_ProgressDialog->setParentWidget(w); + if (w) { + if (m_ProgressDialog) { + m_ProgressDialog->setParentWidget(w); + } + } else { + m_ProgressDialog.reset(); + m_validator.cancel(); + } } QNetworkReply *NXMAccessManager::createRequest( @@ -628,36 +831,63 @@ void NXMAccessManager::clearCookies() void NXMAccessManager::startValidationCheck(const QString& key) { m_validationState = NotChecked; - m_validator.start(key); - m_ProgressDialog->start(); + m_validator.start(key, NexusKeyValidator::Retry); + + 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::onValidatorState( - NexusKeyValidator::States s, const QString& e) +void NXMAccessManager::onValidatorFinished( + ValidationAttempt::Result r, const QString& message, + std::optional<APIUserAccount> user) { - if (s == NexusKeyValidator::Connecting || s == NexusKeyValidator::Finished) { - // no-op, success is handled in onValidatorFinished() - return; - } + stopProgress(); - m_ProgressDialog->stop(); - m_validationState = Invalid; - emit validateFailed(NexusKeyValidator::stateToString(s, e)); + if (user) { + m_validationState = Valid; + emit credentialsReceived(*user); + emit validateSuccessful(true); + } else { + if (r == ValidationAttempt::Cancelled) { + m_validationState = NotChecked; + } else { + m_validationState = Invalid; + emit validateFailed(message); + } + } } -void NXMAccessManager::onValidatorFinished(const APIUserAccount& user) +void NXMAccessManager::onValidatorAttemptFinished(const ValidationAttempt& a) { - m_ProgressDialog->stop(); + if (!m_ProgressDialog) { + switch (a.result()) + { + case ValidationAttempt::SoftError: + case ValidationAttempt::HardError: + { + startProgress(); + break; + } - m_validationState = Valid; - emit credentialsReceived(user); - emit validateSuccessful(true); + case ValidationAttempt::None: + case ValidationAttempt::Success: + case ValidationAttempt::Cancelled: + default: + { + // don't show the dialog + break; + } + } + } } bool NXMAccessManager::validated() const { if (m_validator.isActive()) { - m_ProgressDialog->show(); + const_cast<NXMAccessManager*>(this)->startProgress(); } return (m_validationState == Valid); @@ -723,3 +953,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 0c85153b..6a45d880 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -31,39 +31,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <set> namespace MOBase { class IPluginGame; } +namespace Ui { class ValidationProgressDialog; } class NXMAccessManager; -class ValidationProgressDialog : private QDialog -{ - Q_OBJECT; - -public: - ValidationProgressDialog(std::chrono::seconds timeout); - - void setParentWidget(QWidget* w); - - void start(); - void stop(); - - using QDialog::show; - -protected: - void showEvent(QShowEvent* e) override; - void closeEvent(QCloseEvent* e) override; - -private: - std::chrono::seconds m_timeout; - QProgressBar* m_bar; - QDialogButtonBox* m_buttons; - QTimer* m_timer; - QElapsedTimer m_elapsed; - bool m_first; - - void onButton(QAbstractButton* b); - void onTimer(); -}; - - class NexusSSOLogin { public: @@ -112,50 +82,123 @@ private: }; +class ValidationAttempt +{ +public: + enum Result + { + None, + Success, + SoftError, + HardError, + Cancelled + }; + + std::function<void (APIUserAccount)> success; + std::function<void ()> failure; + + ValidationAttempt(std::chrono::seconds timeout); + ValidationAttempt(const ValidationAttempt&) = delete; + ValidationAttempt& operator=(const ValidationAttempt&) = delete; + + void start(NXMAccessManager& m, const QString& key); + void cancel(); + + bool done() const; + Result result() const; + const QString& message() const; + std::chrono::seconds timeout() const; + QElapsedTimer elapsed() const; + +private: + QNetworkReply* m_reply; + Result m_result; + QString m_message; + QTimer m_timeout; + QElapsedTimer m_elapsed; + + bool sendRequest(NXMAccessManager& m, const QString& key); + + void onFinished(); + void onSslErrors(const QList<QSslError>& errors); + void onTimeout(); + + void setFailure(Result r, const QString& error); + void setSuccess(const APIUserAccount& user); + + void cleanup(); +}; + + class NexusKeyValidator { public: - enum States + enum Behaviour { - Connecting, - Finished, - InvalidJson, - BadResponse, - Timeout, - Cancelled, - Error + OneShot = 0, + Retry }; - std::function<void (APIUserAccount)> finished; - std::function<void (States, QString)> stateChanged; + using FinishedCallback = void ( + ValidationAttempt::Result, const QString&, + std::optional<APIUserAccount>); - static QString stateToString(States s, const QString& e); + std::function<FinishedCallback> finished; + std::function<void (const ValidationAttempt&)> attemptFinished; NexusKeyValidator(NXMAccessManager& am); ~NexusKeyValidator(); - void start(const QString& key); + void start(const QString& key, Behaviour b); void cancel(); bool isActive() const; + const ValidationAttempt* lastAttempt() const; + const ValidationAttempt* currentAttempt() const; private: NXMAccessManager& m_manager; - QNetworkReply* m_reply; - QTimer m_timeout; - bool m_active; + QString m_key; + std::vector<std::unique_ptr<ValidationAttempt>> m_attempts; - void setState(States s, const QString& error={}); + void createAttempts(const std::vector<std::chrono::seconds>& timeouts); - void close(); - void abort(); + bool nextTry(); + void onAttemptSuccess(const ValidationAttempt& a, const APIUserAccount& u); + void onAttemptFailure(const ValidationAttempt& a); - void onFinished(); - void onSslErrors(const QList<QSslError>& errors); - void onTimeout(); + void setFinished( + ValidationAttempt::Result r, const QString& message, + std::optional<APIUserAccount> user); +}; + + +class ValidationProgressDialog : public QDialog +{ + Q_OBJECT; + +public: + ValidationProgressDialog(NexusKeyValidator& v); + + void setParentWidget(QWidget* w); + + void start(); + void stop(); + +protected: + void showEvent(QShowEvent* e) override; + void closeEvent(QCloseEvent* e) override; + +private: + std::unique_ptr<Ui::ValidationProgressDialog> ui; + NexusKeyValidator& m_validator; + QTimer* m_updateTimer; + bool m_first; - void handleError( - int code, const QString& nexusMessage, const QString& httpError); + void onHide(); + void onCancel(); + void onTimer(); + void updateProgress(); }; @@ -166,10 +209,7 @@ class NXMAccessManager : public QNetworkAccessManager { Q_OBJECT public: - static const std::chrono::seconds ValidationTimeout; - - explicit NXMAccessManager(QObject *parent, const QString &moVersion); - + NXMAccessManager(QObject *parent, const QString &moVersion); void setTopLevelWidget(QWidget* w); @@ -224,14 +264,21 @@ private: }; QWidget* m_TopLevel; - mutable ValidationProgressDialog* m_ProgressDialog; + mutable std::unique_ptr<ValidationProgressDialog> m_ProgressDialog; QString m_MOVersion; NexusKeyValidator m_validator; 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<APIUserAccount>); + + void onValidatorAttemptFinished(const ValidationAttempt& a); + + void startProgress(); + void stopProgress(); }; #endif // NXMACCESSMANAGER_H diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 2021bdc1..2dd8b998 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -112,6 +112,12 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) QObject::connect(ui->clearCacheButton, &QPushButton::clicked, [&]{ on_clearCacheButton_clicked(); }); QObject::connect(ui->associateButton, &QPushButton::clicked, [&]{ on_associateButton_clicked(); }); + if (settings().nexus().hasApiKey()) { + addNexusLog(QObject::tr("Connected.")); + } else { + addNexusLog(QObject::tr("Not connected.")); + } + updateNexusState(); } @@ -240,17 +246,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 +279,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<APIUserAccount> 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<APIUserAccount> useR); void addNexusLog(const QString& s); }; diff --git a/src/validationprogressdialog.ui b/src/validationprogressdialog.ui new file mode 100644 index 00000000..1c0cbcb8 --- /dev/null +++ b/src/validationprogressdialog.ui @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ValidationProgressDialog</class> + <widget class="QDialog" name="ValidationProgressDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>305</width> + <height>93</height> + </rect> + </property> + <property name="windowTitle"> + <string>Validating Nexus Connection</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QWidget" name="widget" native="true"> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Connecting to Nexus...</string> + </property> + </widget> + </item> + <item> + <widget class="QProgressBar" name="progress"> + <property name="value"> + <number>24</number> + </property> + <property name="textVisible"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="widget_2" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="cancel"> + <property name="text"> + <string>Cancel</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>122</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="hide"> + <property name="text"> + <string>Hide</string> + </property> + <property name="default"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> |
