From 0cb8ff40b26401a707781c2c4171d3ec6c114077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 8 Dec 2013 17:34:45 +0100 Subject: Finish preliminary offline support * ProgressProvider now has an abort() call * Abort button support added to the progress dialog * YggdrasilTask and MojangAccount adapted to support abort YggdrasilTask will time out after 10 seconds of no network activity, or when the user pushes the Play Offline button. In offline mode, all instance update tasks are skipped! This will need further work. --- logic/BaseInstance.h | 2 +- logic/LegacyInstance.cpp | 4 +- logic/LegacyInstance.h | 2 +- logic/OneSixInstance.cpp | 4 +- logic/OneSixInstance.h | 2 +- logic/OneSixUpdate.h | 1 + logic/auth/MojangAccount.cpp | 22 +++-- logic/auth/MojangAccount.h | 3 +- logic/auth/YggdrasilTask.cpp | 166 +++++++++++++++++++----------------- logic/auth/YggdrasilTask.h | 55 +++++------- logic/auth/flows/InvalidateTask.cpp | 0 logic/auth/flows/InvalidateTask.h | 0 logic/auth/flows/RefreshTask.cpp | 4 +- logic/auth/flows/ValidateTask.h | 4 + logic/net/NetJob.h | 2 + logic/tasks/ProgressProvider.h | 1 + logic/tasks/Task.h | 1 + 17 files changed, 142 insertions(+), 131 deletions(-) delete mode 100644 logic/auth/flows/InvalidateTask.cpp delete mode 100644 logic/auth/flows/InvalidateTask.h (limited to 'logic') diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index 2d7537d6..603f6105 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -150,7 +150,7 @@ public: virtual SettingsObject &settings() const; /// returns a valid update task if update is needed, NULL otherwise - virtual Task *doUpdate(bool prepare_for_launch) = 0; + virtual std::shared_ptr doUpdate(bool prepare_for_launch) = 0; /// returns a valid minecraft process, ready for launch with the given account. virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) = 0; diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp index decfd39d..55523048 100644 --- a/logic/LegacyInstance.cpp +++ b/logic/LegacyInstance.cpp @@ -44,12 +44,12 @@ LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings, settings->registerSetting(new Setting("IntendedJarVersion", "")); } -Task *LegacyInstance::doUpdate(bool prepare_for_launch) +std::shared_ptr LegacyInstance::doUpdate(bool prepare_for_launch) { // make sure the jar mods list is initialized by asking for it. auto list = jarModList(); // create an update task - return new LegacyUpdate(this, prepare_for_launch , this); + return std::shared_ptr (new LegacyUpdate(this, prepare_for_launch , this)); } MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account) diff --git a/logic/LegacyInstance.h b/logic/LegacyInstance.h index a17ef281..948d4be4 100644 --- a/logic/LegacyInstance.h +++ b/logic/LegacyInstance.h @@ -76,7 +76,7 @@ public: virtual bool shouldUpdate() const override; virtual void setShouldUpdate(bool val) override; - virtual Task *doUpdate(bool prepare_for_launch) override; + virtual std::shared_ptr doUpdate(bool prepare_for_launch) override; virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override; virtual void cleanupAfterRun() override; diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index 5362e894..7a1d7dad 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -37,9 +37,9 @@ OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_o reloadFullVersion(); } -Task *OneSixInstance::doUpdate(bool prepare_for_launch) +std::shared_ptr OneSixInstance::doUpdate(bool prepare_for_launch) { - return new OneSixUpdate(this, prepare_for_launch); + return std::shared_ptr (new OneSixUpdate(this, prepare_for_launch)); } QString replaceTokensIn(QString text, QMap with) diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h index 042c104b..7ea2d08b 100644 --- a/logic/OneSixInstance.h +++ b/logic/OneSixInstance.h @@ -39,7 +39,7 @@ public: QString loaderModsDir() const; virtual QString instanceConfigFolder() const override; - virtual Task *doUpdate(bool prepare_for_launch) override; + virtual std::shared_ptr doUpdate(bool prepare_for_launch) override; virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override; virtual void cleanupAfterRun() override; diff --git a/logic/OneSixUpdate.h b/logic/OneSixUpdate.h index b86c205f..5fd2c59f 100644 --- a/logic/OneSixUpdate.h +++ b/logic/OneSixUpdate.h @@ -48,6 +48,7 @@ slots: // extract the appropriate libraries void prepareForLaunch(); + private: NetJobPtr specificVersionDownloadJob; NetJobPtr jarlibDownloadJob; diff --git a/logic/auth/MojangAccount.cpp b/logic/auth/MojangAccount.cpp index 9ab71077..b1acfb25 100644 --- a/logic/auth/MojangAccount.cpp +++ b/logic/auth/MojangAccount.cpp @@ -134,22 +134,21 @@ AccountStatus MojangAccount::accountStatus() const return Online; } -bool MojangAccount::login(QString password) +std::shared_ptr MojangAccount::login(QString password) { if(m_currentTask) - return false; + return m_currentTask; if(password.isEmpty()) { - m_currentTask.reset(new RefreshTask(this, this)); + m_currentTask.reset(new RefreshTask(this)); } else { - m_currentTask.reset(new AuthenticateTask(this, password, this)); + m_currentTask.reset(new AuthenticateTask(this, password)); } connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); - m_currentTask->start(); - return true; + return m_currentTask; } void MojangAccount::authSucceeded() @@ -161,8 +160,13 @@ void MojangAccount::authSucceeded() void MojangAccount::authFailed(QString reason) { - m_online = false; - m_accessToken = QString(); + // This is emitted when the yggdrasil tasks time out or are cancelled. + // -> we treat the error as no-op + if(reason != "Yggdrasil task cancelled.") + { + m_online = false; + m_accessToken = QString(); + emit changed(); + } m_currentTask.reset(); - emit changed(); } diff --git a/logic/auth/MojangAccount.h b/logic/auth/MojangAccount.h index 751aafe5..8c44ce58 100644 --- a/logic/auth/MojangAccount.h +++ b/logic/auth/MojangAccount.h @@ -23,6 +23,7 @@ #include +class Task; class YggdrasilTask; class MojangAccount; @@ -94,7 +95,7 @@ public: /* manipulation */ * Attempt to login. Empty password means we use the token. * If the attempt fails because we already are performing some task, it returns false. */ - bool login(QString password = QString()); + std::shared_ptr login(QString password = QString()); public: /* queries */ const QString &username() const diff --git a/logic/auth/YggdrasilTask.cpp b/logic/auth/YggdrasilTask.cpp index e06ae182..6b938ea7 100644 --- a/logic/auth/YggdrasilTask.cpp +++ b/logic/auth/YggdrasilTask.cpp @@ -30,12 +30,6 @@ YggdrasilTask::YggdrasilTask(MojangAccount *account, QObject *parent) { } -YggdrasilTask::~YggdrasilTask() -{ - if (m_error) - delete m_error; -} - void YggdrasilTask::executeTask() { setStatus(getStateMessage(STATE_SENDING_REQUEST)); @@ -44,107 +38,124 @@ void YggdrasilTask::executeTask() QJsonDocument doc(getRequestContent()); auto worker = MMC->qnam(); - connect(worker.get(), SIGNAL(finished(QNetworkReply *)), this, - SLOT(processReply(QNetworkReply *))); - QUrl reqUrl("https://authserver.mojang.com/" + getEndpoint()); QNetworkRequest netRequest(reqUrl); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QByteArray requestData = doc.toJson(); m_netReply = worker->post(netRequest, requestData); + connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply); + connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers); + connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers); + timeout_keeper.setSingleShot(true); + timeout_keeper.start(timeout_max); + counter.setSingleShot(false); + counter.start(time_step); + progress(0, timeout_max); + connect(&timeout_keeper, &QTimer::timeout, this, &YggdrasilTask::abort); + connect(&counter, &QTimer::timeout, this, &YggdrasilTask::heartbeat); } -void YggdrasilTask::processReply(QNetworkReply *reply) +void YggdrasilTask::refreshTimers(qint64, qint64) { - setStatus(getStateMessage(STATE_PROCESSING_RESPONSE)); + timeout_keeper.stop(); + timeout_keeper.start(timeout_max); + progress(count = 0, timeout_max); +} +void YggdrasilTask::heartbeat() +{ + count += time_step; + progress(count, timeout_max); +} - if (m_netReply != reply) - // Wrong reply for some reason... - return; +void YggdrasilTask::abort() +{ + progress(timeout_max, timeout_max); + m_netReply->abort(); +} + +void YggdrasilTask::processReply() +{ + setStatus(getStateMessage(STATE_PROCESSING_RESPONSE)); - if (reply->error() == QNetworkReply::OperationCanceledError) + if (m_netReply->error() == QNetworkReply::OperationCanceledError) { + // WARNING/FIXME: the value here is used in MojangAccount to detect the cancel/timeout emitFailed("Yggdrasil task cancelled."); return; } - else + + // Try to parse the response regardless of the response code. + // Sometimes the auth server will give more information and an error code. + QJsonParseError jsonError; + QByteArray replyData = m_netReply->readAll(); + QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError); + // Check the response code. + int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (responseCode == 200) { - // Try to parse the response regardless of the response code. - // Sometimes the auth server will give more information and an error code. - QJsonParseError jsonError; - QByteArray replyData = reply->readAll(); - QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError); - // Check the response code. - int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if (responseCode == 200) + // If the response code was 200, then there shouldn't be an error. Make sure + // anyways. + // Also, sometimes an empty reply indicates success. If there was no data received, + // pass an empty json object to the processResponse function. + if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) { - // If the response code was 200, then there shouldn't be an error. Make sure - // anyways. - // Also, sometimes an empty reply indicates success. If there was no data received, - // pass an empty json object to the processResponse function. - if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) + if (processResponse(replyData.size() > 0 ? doc.object() : QJsonObject())) { - if (!processResponse(replyData.size() > 0 ? doc.object() : QJsonObject())) - { - YggdrasilTask::Error *err = getError(); - if (err) - emitFailed(err->getErrorMessage()); - else - emitFailed(tr("An unknown error occurred when processing the response " - "from the authentication server.")); - } - else - { - emitSucceeded(); - } - } - else - { - emitFailed(tr("Failed to parse Yggdrasil JSON response: %1 at offset %2.") - .arg(jsonError.errorString()) - .arg(jsonError.offset)); + emitSucceeded(); + return; } + + // errors happened anyway? + emitFailed(m_error ? m_error->m_errorMessageVerbose + : tr("An unknown error occurred when processing the response " + "from the authentication server.")); } else { - // If the response code was not 200, then Yggdrasil may have given us information - // about the error. - // If we can parse the response, then get information from it. Otherwise just say - // there was an unknown error. - if (jsonError.error == QJsonParseError::NoError) - { - // We were able to parse the server's response. Woo! - // Call processError. If a subclass has overridden it then they'll handle their - // stuff there. - QLOG_DEBUG() << "The request failed, but the server gave us an error message. " - "Processing error."; - emitFailed(processError(doc.object())); - } - else - { - // The server didn't say anything regarding the error. Give the user an unknown - // error. - QLOG_DEBUG() << "The request failed and the server gave no error message. " - "Unknown error."; - emitFailed(tr("An unknown error occurred when trying to communicate with the " - "authentication server: %1").arg(reply->errorString())); - } + emitFailed(tr("Failed to parse Yggdrasil JSON response: %1 at offset %2.") + .arg(jsonError.errorString()) + .arg(jsonError.offset)); } + return; + } + + // If the response code was not 200, then Yggdrasil may have given us information + // about the error. + // If we can parse the response, then get information from it. Otherwise just say + // there was an unknown error. + if (jsonError.error == QJsonParseError::NoError) + { + // We were able to parse the server's response. Woo! + // Call processError. If a subclass has overridden it then they'll handle their + // stuff there. + QLOG_DEBUG() << "The request failed, but the server gave us an error message. " + "Processing error."; + emitFailed(processError(doc.object())); + } + else + { + // The server didn't say anything regarding the error. Give the user an unknown + // error. + QLOG_DEBUG() << "The request failed and the server gave no error message. " + "Unknown error."; + emitFailed(tr("An unknown error occurred when trying to communicate with the " + "authentication server: %1").arg(m_netReply->errorString())); } } QString YggdrasilTask::processError(QJsonObject responseData) { QJsonValue errorVal = responseData.value("error"); - QJsonValue msgVal = responseData.value("errorMessage"); + QJsonValue errorMessageValue = responseData.value("errorMessage"); QJsonValue causeVal = responseData.value("cause"); - if (errorVal.isString() && msgVal.isString()) + if (errorVal.isString() && errorMessageValue.isString()) { - m_error = new Error(errorVal.toString(""), msgVal.toString(""), causeVal.toString("")); - return m_error->getDisplayMessage(); + m_error = std::shared_ptr(new Error{ + errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")}); + return m_error->m_errorMessageVerbose; } else { @@ -165,8 +176,3 @@ QString YggdrasilTask::getStateMessage(const YggdrasilTask::State state) const return tr("Processing. Please wait."); } } - -YggdrasilTask::Error *YggdrasilTask::getError() const -{ - return this->m_error; -} diff --git a/logic/auth/YggdrasilTask.h b/logic/auth/YggdrasilTask.h index 18d3dc61..1f81a2d0 100644 --- a/logic/auth/YggdrasilTask.h +++ b/logic/auth/YggdrasilTask.h @@ -19,6 +19,7 @@ #include #include +#include #include "logic/auth/MojangAccount.h" @@ -32,39 +33,17 @@ class YggdrasilTask : public Task Q_OBJECT public: explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0); - ~YggdrasilTask(); /** * Class describing a Yggdrasil error response. */ - class Error + struct Error { - public: - Error(const QString& shortError, const QString& errorMessage, const QString& cause) : - m_shortError(shortError), m_errorMessage(errorMessage), m_cause(cause) {} - - QString getShortError() const { return m_shortError; } - QString getErrorMessage() const { return m_errorMessage; } - QString getCause() const { return m_cause; } - - /// Gets the string to display in the GUI for describing this error. - QString getDisplayMessage() - { - return getErrorMessage(); - } - - protected: - QString m_shortError; - QString m_errorMessage; + QString m_errorMessageShort; + QString m_errorMessageVerbose; QString m_cause; }; - /** - * Returns a pointer to a YggdrasilTask::Error object if an error has occurred. - * If no error has occurred, returns a null pointer. - */ - virtual Error *getError() const; - protected: /** * Enum for describing the state of the current task. @@ -115,13 +94,25 @@ protected: */ virtual QString getStateMessage(const State state) const; - MojangAccount *m_account = nullptr; - - QNetworkReply *m_netReply; - - Error *m_error = nullptr; - protected slots: - void processReply(QNetworkReply *reply); + void processReply(); + void refreshTimers(qint64, qint64); + void heartbeat(); + +public +slots: + virtual void abort() override; + +protected: + // FIXME: segfault disaster waiting to happen + MojangAccount *m_account = nullptr; + QNetworkReply *m_netReply = nullptr; + std::shared_ptr m_error; + QTimer timeout_keeper; + QTimer counter; + int count = 0; // num msec since time reset + + const int timeout_max = 10000; + const int time_step = 50; }; diff --git a/logic/auth/flows/InvalidateTask.cpp b/logic/auth/flows/InvalidateTask.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/logic/auth/flows/InvalidateTask.h b/logic/auth/flows/InvalidateTask.h deleted file mode 100644 index e69de29b..00000000 diff --git a/logic/auth/flows/RefreshTask.cpp b/logic/auth/flows/RefreshTask.cpp index 39fb493f..bd38eb10 100644 --- a/logic/auth/flows/RefreshTask.cpp +++ b/logic/auth/flows/RefreshTask.cpp @@ -143,9 +143,9 @@ QString RefreshTask::getStateMessage(const YggdrasilTask::State state) const switch (state) { case STATE_SENDING_REQUEST: - return tr("Refreshing: Sending request."); + return tr("Refreshing login token."); case STATE_PROCESSING_RESPONSE: - return tr("Refreshing: Processing response."); + return tr("Refreshing login token: Processing response."); default: return YggdrasilTask::getStateMessage(state); } diff --git a/logic/auth/flows/ValidateTask.h b/logic/auth/flows/ValidateTask.h index 9788f20b..0e34f0c3 100644 --- a/logic/auth/flows/ValidateTask.h +++ b/logic/auth/flows/ValidateTask.h @@ -13,6 +13,10 @@ * limitations under the License. */ +/* + * :FIXME: DEAD CODE, DEAD CODE, DEAD CODE! :FIXME: + */ + #pragma once #include diff --git a/logic/net/NetJob.h b/logic/net/NetJob.h index 021a1550..a7027e10 100644 --- a/logic/net/NetJob.h +++ b/logic/net/NetJob.h @@ -94,6 +94,8 @@ signals: public slots: virtual void start(); + // FIXME: implement + virtual void abort() {}; private slots: void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal); diff --git a/logic/tasks/ProgressProvider.h b/logic/tasks/ProgressProvider.h index f6f2906a..15e453a3 100644 --- a/logic/tasks/ProgressProvider.h +++ b/logic/tasks/ProgressProvider.h @@ -38,4 +38,5 @@ public: public slots: virtual void start() = 0; + virtual void abort() = 0; }; diff --git a/logic/tasks/Task.h b/logic/tasks/Task.h index d08ef560..80d5e38b 100644 --- a/logic/tasks/Task.h +++ b/logic/tasks/Task.h @@ -44,6 +44,7 @@ public: public slots: virtual void start(); + virtual void abort() {}; protected: virtual void executeTask() = 0; -- cgit v1.2.3