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.
This commit is contained in:
parent
f028aa76bc
commit
0cb8ff40b2
@ -312,8 +312,6 @@ logic/auth/flows/RefreshTask.cpp
|
|||||||
logic/auth/flows/RefreshTask.cpp
|
logic/auth/flows/RefreshTask.cpp
|
||||||
logic/auth/flows/ValidateTask.h
|
logic/auth/flows/ValidateTask.h
|
||||||
logic/auth/flows/ValidateTask.cpp
|
logic/auth/flows/ValidateTask.cpp
|
||||||
logic/auth/flows/InvalidateTask.h
|
|
||||||
logic/auth/flows/InvalidateTask.cpp
|
|
||||||
|
|
||||||
# legacy instances
|
# legacy instances
|
||||||
logic/LegacyInstance.h
|
logic/LegacyInstance.h
|
||||||
|
@ -777,83 +777,58 @@ void MainWindow::doLaunch()
|
|||||||
accounts->setActiveAccount(account->username());
|
accounts->setActiveAccount(account->username());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (account.get() != nullptr)
|
// if no account is selected, we bail
|
||||||
{
|
if (!account.get())
|
||||||
doLaunchInst(m_selectedInstance, account);
|
return;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::doLaunchInst(BaseInstance* instance, MojangAccountPtr account)
|
// do the login. if the account has an access token, try to refresh it first.
|
||||||
{
|
if(account->accountStatus() != NotVerified)
|
||||||
// We'll need to validate the access token to make sure the account is still logged in.
|
|
||||||
/*
|
|
||||||
ProgressDialog progDialog(this);
|
|
||||||
RefreshTask refreshtask(account, &progDialog);
|
|
||||||
progDialog.exec(&refreshtask);
|
|
||||||
|
|
||||||
if (refreshtask.successful())
|
|
||||||
{
|
{
|
||||||
prepareLaunch(m_selectedInstance, account);
|
// We'll need to validate the access token to make sure the account is still logged in.
|
||||||
}
|
ProgressDialog progDialog(this);
|
||||||
else
|
progDialog.setSkipButton(true, tr("Play Offline"));
|
||||||
{
|
auto task = account->login();
|
||||||
YggdrasilTask::Error *error = refreshtask.getError();
|
progDialog.exec(task.get());
|
||||||
|
|
||||||
if (error != nullptr)
|
auto status = account->accountStatus();
|
||||||
|
if(status == Online) // Online mode! Refresh the token.
|
||||||
{
|
{
|
||||||
if (error->getErrorMessage().contains("invalid token", Qt::CaseInsensitive))
|
updateInstance(m_selectedInstance, account);
|
||||||
{
|
return;
|
||||||
// TODO: Allow the user to enter their password and "refresh" their access token.
|
|
||||||
if (doRefreshToken(account, tr("Your account's access token is invalid. Please enter your password to log in again.")))
|
|
||||||
doLaunchInst(instance, account);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CustomMessageBox::selectable(
|
|
||||||
this, tr("Access Token Validation Error"),
|
|
||||||
tr("There was an error when trying to validate your access token.\n"
|
|
||||||
"Details: %s").arg(error->getDisplayMessage()),
|
|
||||||
QMessageBox::Warning, QMessageBox::Ok)->exec();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else if(status == Verified) // Offline mode with a verified account
|
||||||
{
|
{
|
||||||
CustomMessageBox::selectable(
|
launchInstance(m_selectedInstance, account);
|
||||||
this, tr("Access Token Validation Error"),
|
return;
|
||||||
tr("There was an unknown error when trying to validate your access token."
|
|
||||||
"The authentication server might be down, or you might not be connected to "
|
|
||||||
"the Internet."),
|
|
||||||
QMessageBox::Warning, QMessageBox::Ok)->exec();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
if (loginWithPassword(account, tr("Your account is currently not logged in. Please enter your password to log in again.")))
|
||||||
|
updateInstance(m_selectedInstance, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::doRefreshToken(MojangAccountPtr account, const QString& errorMsg)
|
bool MainWindow::loginWithPassword(MojangAccountPtr account, const QString& errorMsg)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
EditAccountDialog passDialog(errorMsg, this, EditAccountDialog::PasswordField);
|
EditAccountDialog passDialog(errorMsg, this, EditAccountDialog::PasswordField);
|
||||||
if (passDialog.exec() == QDialog::Accepted)
|
if (passDialog.exec() == QDialog::Accepted)
|
||||||
{
|
{
|
||||||
// To refresh the token, we just create an authenticate task with the given account and the user's password.
|
// To refresh the token, we just create an authenticate task with the given account and the user's password.
|
||||||
ProgressDialog progDialog(this);
|
ProgressDialog progDialog(this);
|
||||||
AuthenticateTask authTask(account, passDialog.password(), &progDialog);
|
auto task = account->login(passDialog.password());
|
||||||
progDialog.exec(&authTask);
|
progDialog.exec(task.get());
|
||||||
if (authTask.successful())
|
if(task->successful())
|
||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If the authentication task failed, recurse with the task's error message.
|
// If the authentication task failed, recurse with the task's error message.
|
||||||
return doRefreshToken(account, authTask.failReason());
|
return loginWithPassword(account, task->failReason());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else return false;*/
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::prepareLaunch(BaseInstance* instance, MojangAccountPtr account)
|
void MainWindow::updateInstance(BaseInstance* instance, MojangAccountPtr account)
|
||||||
{
|
{
|
||||||
Task *updateTask = instance->doUpdate(true);
|
auto updateTask = instance->doUpdate(true);
|
||||||
if (!updateTask)
|
if (!updateTask)
|
||||||
{
|
{
|
||||||
launchInstance(instance, account);
|
launchInstance(instance, account);
|
||||||
@ -861,10 +836,9 @@ void MainWindow::prepareLaunch(BaseInstance* instance, MojangAccountPtr account)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
ProgressDialog tDialog(this);
|
ProgressDialog tDialog(this);
|
||||||
connect(updateTask, &Task::succeeded, [this, instance, account] { launchInstance(instance, account); });
|
connect(updateTask.get(), &Task::succeeded, [this, instance, account] { launchInstance(instance, account); });
|
||||||
connect(updateTask, SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString)));
|
connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString)));
|
||||||
tDialog.exec(updateTask);
|
tDialog.exec(updateTask.get());
|
||||||
delete updateTask;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,18 +110,13 @@ slots:
|
|||||||
* If no default account is selected, prompts the user to pick an account.
|
* If no default account is selected, prompts the user to pick an account.
|
||||||
*/
|
*/
|
||||||
void doLaunch();
|
void doLaunch();
|
||||||
|
|
||||||
/*!
|
|
||||||
* Launches the given instance with the given account.
|
|
||||||
*/
|
|
||||||
void doLaunchInst(BaseInstance* instance, MojangAccountPtr account);
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Opens an input dialog, allowing the user to input their password and refresh its access token.
|
* Opens an input dialog, allowing the user to input their password and refresh its access token.
|
||||||
* This function will execute the proper Yggdrasil task to refresh the access token.
|
* This function will execute the proper Yggdrasil task to refresh the access token.
|
||||||
* Returns true if successful. False if the user cancelled.
|
* Returns true if successful. False if the user cancelled.
|
||||||
*/
|
*/
|
||||||
bool doRefreshToken(MojangAccountPtr account, const QString& errorMsg="");
|
bool loginWithPassword(MojangAccountPtr account, const QString& errorMsg="");
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Launches the given instance with the given account.
|
* Launches the given instance with the given account.
|
||||||
@ -132,7 +127,7 @@ slots:
|
|||||||
/*!
|
/*!
|
||||||
* Prepares the given instance for launch with the given account.
|
* Prepares the given instance for launch with the given account.
|
||||||
*/
|
*/
|
||||||
void prepareLaunch(BaseInstance* instance, MojangAccountPtr account);
|
void updateInstance(BaseInstance* instance, MojangAccountPtr account);
|
||||||
|
|
||||||
void onGameUpdateError(QString error);
|
void onGameUpdateError(QString error);
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include <gui/dialogs/EditAccountDialog.h>
|
#include <gui/dialogs/EditAccountDialog.h>
|
||||||
#include <gui/dialogs/ProgressDialog.h>
|
#include <gui/dialogs/ProgressDialog.h>
|
||||||
#include <gui/dialogs/AccountSelectDialog.h>
|
#include <gui/dialogs/AccountSelectDialog.h>
|
||||||
|
#include <logic/tasks/Task.h>
|
||||||
|
|
||||||
#include <MultiMC.h>
|
#include <MultiMC.h>
|
||||||
|
|
||||||
@ -117,14 +118,14 @@ void AccountListDialog::addAccount(const QString& errMsg)
|
|||||||
QString password(loginDialog.password());
|
QString password(loginDialog.password());
|
||||||
|
|
||||||
MojangAccountPtr account = MojangAccount::createFromUsername(username);
|
MojangAccountPtr account = MojangAccount::createFromUsername(username);
|
||||||
/*
|
|
||||||
ProgressDialog progDialog(this);
|
ProgressDialog progDialog(this);
|
||||||
AuthenticateTask authTask(account, password, &progDialog);
|
auto task = account->login(password);
|
||||||
if (progDialog.exec(&authTask))
|
progDialog.exec(task.get());
|
||||||
|
if(task->successful())
|
||||||
{
|
{
|
||||||
// Add the authenticated account to the accounts list.
|
|
||||||
MojangAccountPtr account = authTask.getMojangAccount();
|
|
||||||
m_accounts->addAccount(account);
|
m_accounts->addAccount(account);
|
||||||
|
if (m_accounts->count() == 1)
|
||||||
|
m_accounts->setActiveAccount(account->username());
|
||||||
|
|
||||||
// Grab associated player skins
|
// Grab associated player skins
|
||||||
auto job = new NetJob("Player skins: " + account->username());
|
auto job = new NetJob("Player skins: " + account->username());
|
||||||
@ -141,6 +142,5 @@ void AccountListDialog::addAccount(const QString& errMsg)
|
|||||||
|
|
||||||
job->start();
|
job->start();
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,25 @@ ProgressDialog::ProgressDialog(QWidget *parent) : QDialog(parent), ui(new Ui::Pr
|
|||||||
{
|
{
|
||||||
MultiMCPlatform::fixWM_CLASS(this);
|
MultiMCPlatform::fixWM_CLASS(this);
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
updateSize();
|
this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||||
|
setSkipButton(false);
|
||||||
changeProgress(0, 100);
|
changeProgress(0, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProgressDialog::setSkipButton(bool present, QString label)
|
||||||
|
{
|
||||||
|
ui->skipButton->setEnabled(present);
|
||||||
|
ui->skipButton->setVisible(present);
|
||||||
|
ui->skipButton->setText(label);
|
||||||
|
updateSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProgressDialog::on_skipButton_clicked(bool checked)
|
||||||
|
{
|
||||||
|
Q_UNUSED(checked);
|
||||||
|
task->abort();
|
||||||
|
}
|
||||||
|
|
||||||
ProgressDialog::~ProgressDialog()
|
ProgressDialog::~ProgressDialog()
|
||||||
{
|
{
|
||||||
delete ui;
|
delete ui;
|
||||||
@ -51,9 +65,13 @@ int ProgressDialog::exec(ProgressProvider *task)
|
|||||||
connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString &)));
|
connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString &)));
|
||||||
connect(task, SIGNAL(progress(qint64, qint64)), SLOT(changeProgress(qint64, qint64)));
|
connect(task, SIGNAL(progress(qint64, qint64)), SLOT(changeProgress(qint64, qint64)));
|
||||||
|
|
||||||
// this makes sure that the task is started after the dialog is created
|
// if this didn't connect to an already running task, invoke start
|
||||||
QMetaObject::invokeMethod(task, "start", Qt::QueuedConnection);
|
if(!task->isRunning())
|
||||||
return QDialog::exec();
|
task->start();
|
||||||
|
if(task->isRunning())
|
||||||
|
return QDialog::exec();
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgressProvider *ProgressDialog::getTask()
|
ProgressProvider *ProgressDialog::getTask()
|
||||||
|
@ -35,6 +35,7 @@ public:
|
|||||||
void updateSize();
|
void updateSize();
|
||||||
|
|
||||||
int exec(ProgressProvider *task);
|
int exec(ProgressProvider *task);
|
||||||
|
void setSkipButton(bool present, QString label = QString());
|
||||||
|
|
||||||
ProgressProvider *getTask();
|
ProgressProvider *getTask();
|
||||||
|
|
||||||
@ -47,7 +48,10 @@ slots:
|
|||||||
void changeStatus(const QString &status);
|
void changeStatus(const QString &status);
|
||||||
void changeProgress(qint64 current, qint64 total);
|
void changeProgress(qint64 current, qint64 total);
|
||||||
|
|
||||||
signals:
|
|
||||||
|
private
|
||||||
|
slots:
|
||||||
|
void on_skipButton_clicked(bool checked);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void keyPressEvent(QKeyEvent *e);
|
virtual void keyPressEvent(QKeyEvent *e);
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>400</width>
|
<width>400</width>
|
||||||
<height>68</height>
|
<height>100</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
@ -25,8 +25,8 @@
|
|||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Please wait...</string>
|
<string>Please wait...</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item>
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="statusLabel">
|
<widget class="QLabel" name="statusLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Task Status...</string>
|
<string>Task Status...</string>
|
||||||
@ -36,7 +36,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="1" column="0">
|
||||||
<widget class="QProgressBar" name="taskProgressBar">
|
<widget class="QProgressBar" name="taskProgressBar">
|
||||||
<property name="value">
|
<property name="value">
|
||||||
<number>24</number>
|
<number>24</number>
|
||||||
@ -46,6 +46,19 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QPushButton" name="skipButton">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Skip</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
@ -150,7 +150,7 @@ public:
|
|||||||
virtual SettingsObject &settings() const;
|
virtual SettingsObject &settings() const;
|
||||||
|
|
||||||
/// returns a valid update task if update is needed, NULL otherwise
|
/// returns a valid update task if update is needed, NULL otherwise
|
||||||
virtual Task *doUpdate(bool prepare_for_launch) = 0;
|
virtual std::shared_ptr<Task> doUpdate(bool prepare_for_launch) = 0;
|
||||||
|
|
||||||
/// returns a valid minecraft process, ready for launch with the given account.
|
/// returns a valid minecraft process, ready for launch with the given account.
|
||||||
virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) = 0;
|
virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) = 0;
|
||||||
|
@ -44,12 +44,12 @@ LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings,
|
|||||||
settings->registerSetting(new Setting("IntendedJarVersion", ""));
|
settings->registerSetting(new Setting("IntendedJarVersion", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
Task *LegacyInstance::doUpdate(bool prepare_for_launch)
|
std::shared_ptr<Task> LegacyInstance::doUpdate(bool prepare_for_launch)
|
||||||
{
|
{
|
||||||
// make sure the jar mods list is initialized by asking for it.
|
// make sure the jar mods list is initialized by asking for it.
|
||||||
auto list = jarModList();
|
auto list = jarModList();
|
||||||
// create an update task
|
// create an update task
|
||||||
return new LegacyUpdate(this, prepare_for_launch , this);
|
return std::shared_ptr<Task> (new LegacyUpdate(this, prepare_for_launch , this));
|
||||||
}
|
}
|
||||||
|
|
||||||
MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account)
|
MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account)
|
||||||
|
@ -76,7 +76,7 @@ public:
|
|||||||
|
|
||||||
virtual bool shouldUpdate() const override;
|
virtual bool shouldUpdate() const override;
|
||||||
virtual void setShouldUpdate(bool val) override;
|
virtual void setShouldUpdate(bool val) override;
|
||||||
virtual Task *doUpdate(bool prepare_for_launch) override;
|
virtual std::shared_ptr<Task> doUpdate(bool prepare_for_launch) override;
|
||||||
|
|
||||||
virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override;
|
virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override;
|
||||||
virtual void cleanupAfterRun() override;
|
virtual void cleanupAfterRun() override;
|
||||||
|
@ -37,9 +37,9 @@ OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_o
|
|||||||
reloadFullVersion();
|
reloadFullVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
Task *OneSixInstance::doUpdate(bool prepare_for_launch)
|
std::shared_ptr<Task> OneSixInstance::doUpdate(bool prepare_for_launch)
|
||||||
{
|
{
|
||||||
return new OneSixUpdate(this, prepare_for_launch);
|
return std::shared_ptr<Task> (new OneSixUpdate(this, prepare_for_launch));
|
||||||
}
|
}
|
||||||
|
|
||||||
QString replaceTokensIn(QString text, QMap<QString, QString> with)
|
QString replaceTokensIn(QString text, QMap<QString, QString> with)
|
||||||
|
@ -39,7 +39,7 @@ public:
|
|||||||
QString loaderModsDir() const;
|
QString loaderModsDir() const;
|
||||||
virtual QString instanceConfigFolder() const override;
|
virtual QString instanceConfigFolder() const override;
|
||||||
|
|
||||||
virtual Task *doUpdate(bool prepare_for_launch) override;
|
virtual std::shared_ptr<Task> doUpdate(bool prepare_for_launch) override;
|
||||||
virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override;
|
virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override;
|
||||||
|
|
||||||
virtual void cleanupAfterRun() override;
|
virtual void cleanupAfterRun() override;
|
||||||
|
@ -48,6 +48,7 @@ slots:
|
|||||||
|
|
||||||
// extract the appropriate libraries
|
// extract the appropriate libraries
|
||||||
void prepareForLaunch();
|
void prepareForLaunch();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NetJobPtr specificVersionDownloadJob;
|
NetJobPtr specificVersionDownloadJob;
|
||||||
NetJobPtr jarlibDownloadJob;
|
NetJobPtr jarlibDownloadJob;
|
||||||
|
@ -134,22 +134,21 @@ AccountStatus MojangAccount::accountStatus() const
|
|||||||
return Online;
|
return Online;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MojangAccount::login(QString password)
|
std::shared_ptr<Task> MojangAccount::login(QString password)
|
||||||
{
|
{
|
||||||
if(m_currentTask)
|
if(m_currentTask)
|
||||||
return false;
|
return m_currentTask;
|
||||||
if(password.isEmpty())
|
if(password.isEmpty())
|
||||||
{
|
{
|
||||||
m_currentTask.reset(new RefreshTask(this, this));
|
m_currentTask.reset(new RefreshTask(this));
|
||||||
}
|
}
|
||||||
else
|
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(succeeded()), SLOT(authSucceeded()));
|
||||||
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
|
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
|
||||||
m_currentTask->start();
|
return m_currentTask;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MojangAccount::authSucceeded()
|
void MojangAccount::authSucceeded()
|
||||||
@ -161,8 +160,13 @@ void MojangAccount::authSucceeded()
|
|||||||
|
|
||||||
void MojangAccount::authFailed(QString reason)
|
void MojangAccount::authFailed(QString reason)
|
||||||
{
|
{
|
||||||
m_online = false;
|
// This is emitted when the yggdrasil tasks time out or are cancelled.
|
||||||
m_accessToken = QString();
|
// -> we treat the error as no-op
|
||||||
|
if(reason != "Yggdrasil task cancelled.")
|
||||||
|
{
|
||||||
|
m_online = false;
|
||||||
|
m_accessToken = QString();
|
||||||
|
emit changed();
|
||||||
|
}
|
||||||
m_currentTask.reset();
|
m_currentTask.reset();
|
||||||
emit changed();
|
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
class Task;
|
||||||
class YggdrasilTask;
|
class YggdrasilTask;
|
||||||
class MojangAccount;
|
class MojangAccount;
|
||||||
|
|
||||||
@ -94,7 +95,7 @@ public: /* manipulation */
|
|||||||
* Attempt to login. Empty password means we use the token.
|
* Attempt to login. Empty password means we use the token.
|
||||||
* If the attempt fails because we already are performing some task, it returns false.
|
* If the attempt fails because we already are performing some task, it returns false.
|
||||||
*/
|
*/
|
||||||
bool login(QString password = QString());
|
std::shared_ptr<Task> login(QString password = QString());
|
||||||
|
|
||||||
public: /* queries */
|
public: /* queries */
|
||||||
const QString &username() const
|
const QString &username() const
|
||||||
|
@ -30,12 +30,6 @@ YggdrasilTask::YggdrasilTask(MojangAccount *account, QObject *parent)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
YggdrasilTask::~YggdrasilTask()
|
|
||||||
{
|
|
||||||
if (m_error)
|
|
||||||
delete m_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
void YggdrasilTask::executeTask()
|
void YggdrasilTask::executeTask()
|
||||||
{
|
{
|
||||||
setStatus(getStateMessage(STATE_SENDING_REQUEST));
|
setStatus(getStateMessage(STATE_SENDING_REQUEST));
|
||||||
@ -44,107 +38,124 @@ void YggdrasilTask::executeTask()
|
|||||||
QJsonDocument doc(getRequestContent());
|
QJsonDocument doc(getRequestContent());
|
||||||
|
|
||||||
auto worker = MMC->qnam();
|
auto worker = MMC->qnam();
|
||||||
connect(worker.get(), SIGNAL(finished(QNetworkReply *)), this,
|
|
||||||
SLOT(processReply(QNetworkReply *)));
|
|
||||||
|
|
||||||
QUrl reqUrl("https://authserver.mojang.com/" + getEndpoint());
|
QUrl reqUrl("https://authserver.mojang.com/" + getEndpoint());
|
||||||
QNetworkRequest netRequest(reqUrl);
|
QNetworkRequest netRequest(reqUrl);
|
||||||
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
QByteArray requestData = doc.toJson();
|
QByteArray requestData = doc.toJson();
|
||||||
m_netReply = worker->post(netRequest, requestData);
|
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)
|
||||||
|
{
|
||||||
|
timeout_keeper.stop();
|
||||||
|
timeout_keeper.start(timeout_max);
|
||||||
|
progress(count = 0, timeout_max);
|
||||||
|
}
|
||||||
|
void YggdrasilTask::heartbeat()
|
||||||
|
{
|
||||||
|
count += time_step;
|
||||||
|
progress(count, timeout_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
void YggdrasilTask::abort()
|
||||||
|
{
|
||||||
|
progress(timeout_max, timeout_max);
|
||||||
|
m_netReply->abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
void YggdrasilTask::processReply()
|
||||||
{
|
{
|
||||||
setStatus(getStateMessage(STATE_PROCESSING_RESPONSE));
|
setStatus(getStateMessage(STATE_PROCESSING_RESPONSE));
|
||||||
|
|
||||||
if (m_netReply != reply)
|
if (m_netReply->error() == QNetworkReply::OperationCanceledError)
|
||||||
// Wrong reply for some reason...
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::OperationCanceledError)
|
|
||||||
{
|
{
|
||||||
|
// WARNING/FIXME: the value here is used in MojangAccount to detect the cancel/timeout
|
||||||
emitFailed("Yggdrasil task cancelled.");
|
emitFailed("Yggdrasil task cancelled.");
|
||||||
return;
|
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 = reply->readAll();
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError);
|
|
||||||
// Check the response code.
|
|
||||||
int responseCode = reply->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 = m_netReply->readAll();
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError);
|
||||||
|
// Check the response code.
|
||||||
|
int responseCode = m_netReply->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
|
if (processResponse(replyData.size() > 0 ? doc.object() : QJsonObject()))
|
||||||
// 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()))
|
emitSucceeded();
|
||||||
{
|
return;
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// errors happened anyway?
|
||||||
|
emitFailed(m_error ? m_error->m_errorMessageVerbose
|
||||||
|
: tr("An unknown error occurred when processing the response "
|
||||||
|
"from the authentication server."));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If the response code was not 200, then Yggdrasil may have given us information
|
emitFailed(tr("Failed to parse Yggdrasil JSON response: %1 at offset %2.")
|
||||||
// about the error.
|
.arg(jsonError.errorString())
|
||||||
// If we can parse the response, then get information from it. Otherwise just say
|
.arg(jsonError.offset));
|
||||||
// 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()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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)
|
QString YggdrasilTask::processError(QJsonObject responseData)
|
||||||
{
|
{
|
||||||
QJsonValue errorVal = responseData.value("error");
|
QJsonValue errorVal = responseData.value("error");
|
||||||
QJsonValue msgVal = responseData.value("errorMessage");
|
QJsonValue errorMessageValue = responseData.value("errorMessage");
|
||||||
QJsonValue causeVal = responseData.value("cause");
|
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(""));
|
m_error = std::shared_ptr<Error>(new Error{
|
||||||
return m_error->getDisplayMessage();
|
errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")});
|
||||||
|
return m_error->m_errorMessageVerbose;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -165,8 +176,3 @@ QString YggdrasilTask::getStateMessage(const YggdrasilTask::State state) const
|
|||||||
return tr("Processing. Please wait.");
|
return tr("Processing. Please wait.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
YggdrasilTask::Error *YggdrasilTask::getError() const
|
|
||||||
{
|
|
||||||
return this->m_error;
|
|
||||||
}
|
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
#include "logic/auth/MojangAccount.h"
|
#include "logic/auth/MojangAccount.h"
|
||||||
|
|
||||||
@ -32,39 +33,17 @@ class YggdrasilTask : public Task
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0);
|
explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0);
|
||||||
~YggdrasilTask();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class describing a Yggdrasil error response.
|
* Class describing a Yggdrasil error response.
|
||||||
*/
|
*/
|
||||||
class Error
|
struct Error
|
||||||
{
|
{
|
||||||
public:
|
QString m_errorMessageShort;
|
||||||
Error(const QString& shortError, const QString& errorMessage, const QString& cause) :
|
QString m_errorMessageVerbose;
|
||||||
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_cause;
|
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:
|
protected:
|
||||||
/**
|
/**
|
||||||
* Enum for describing the state of the current task.
|
* Enum for describing the state of the current task.
|
||||||
@ -115,13 +94,25 @@ protected:
|
|||||||
*/
|
*/
|
||||||
virtual QString getStateMessage(const State state) const;
|
virtual QString getStateMessage(const State state) const;
|
||||||
|
|
||||||
MojangAccount *m_account = nullptr;
|
|
||||||
|
|
||||||
QNetworkReply *m_netReply;
|
|
||||||
|
|
||||||
Error *m_error = nullptr;
|
|
||||||
|
|
||||||
protected
|
protected
|
||||||
slots:
|
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<Error> 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;
|
||||||
};
|
};
|
||||||
|
@ -143,9 +143,9 @@ QString RefreshTask::getStateMessage(const YggdrasilTask::State state) const
|
|||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case STATE_SENDING_REQUEST:
|
case STATE_SENDING_REQUEST:
|
||||||
return tr("Refreshing: Sending request.");
|
return tr("Refreshing login token.");
|
||||||
case STATE_PROCESSING_RESPONSE:
|
case STATE_PROCESSING_RESPONSE:
|
||||||
return tr("Refreshing: Processing response.");
|
return tr("Refreshing login token: Processing response.");
|
||||||
default:
|
default:
|
||||||
return YggdrasilTask::getStateMessage(state);
|
return YggdrasilTask::getStateMessage(state);
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* :FIXME: DEAD CODE, DEAD CODE, DEAD CODE! :FIXME:
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <logic/auth/YggdrasilTask.h>
|
#include <logic/auth/YggdrasilTask.h>
|
||||||
|
@ -94,6 +94,8 @@ signals:
|
|||||||
public
|
public
|
||||||
slots:
|
slots:
|
||||||
virtual void start();
|
virtual void start();
|
||||||
|
// FIXME: implement
|
||||||
|
virtual void abort() {};
|
||||||
private
|
private
|
||||||
slots:
|
slots:
|
||||||
void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal);
|
void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal);
|
||||||
|
@ -38,4 +38,5 @@ public:
|
|||||||
public
|
public
|
||||||
slots:
|
slots:
|
||||||
virtual void start() = 0;
|
virtual void start() = 0;
|
||||||
|
virtual void abort() = 0;
|
||||||
};
|
};
|
||||||
|
@ -44,6 +44,7 @@ public:
|
|||||||
public
|
public
|
||||||
slots:
|
slots:
|
||||||
virtual void start();
|
virtual void start();
|
||||||
|
virtual void abort() {};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void executeTask() = 0;
|
virtual void executeTask() = 0;
|
||||||
|
Loading…
Reference in New Issue
Block a user