Offline mode support, part 1

Refactor MojangAccount so it exposes a less generic interface and supports login. Hide the ugly details.
Yggdrasil tasks are now only used from MojangAccount.
This commit is contained in:
Petr Mrázek 2013-12-05 02:39:52 +01:00
parent 613699b362
commit f028aa76bc
18 changed files with 265 additions and 322 deletions

View File

@ -69,10 +69,6 @@
#include "logic/lists/IconList.h" #include "logic/lists/IconList.h"
#include "logic/lists/JavaVersionList.h" #include "logic/lists/JavaVersionList.h"
#include "logic/auth/flows/AuthenticateTask.h"
#include "logic/auth/flows/RefreshTask.h"
#include "logic/auth/flows/ValidateTask.h"
#include "logic/BaseInstance.h" #include "logic/BaseInstance.h"
#include "logic/InstanceFactory.h" #include "logic/InstanceFactory.h"
#include "logic/MinecraftProcess.h" #include "logic/MinecraftProcess.h"
@ -210,9 +206,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
for(AccountProfile profile : account->profiles()) for(AccountProfile profile : account->profiles())
{ {
auto meta = MMC->metacache()->resolveEntry("skins", profile.name() + ".png"); auto meta = MMC->metacache()->resolveEntry("skins", profile.name + ".png");
auto action = CacheDownload::make( auto action = CacheDownload::make(
QUrl("http://skins.minecraft.net/MinecraftSkins/" + profile.name() + ".png"), QUrl("http://skins.minecraft.net/MinecraftSkins/" + profile.name + ".png"),
meta); meta);
job->addNetAction(action); job->addNetAction(action);
meta->stale = true; meta->stale = true;
@ -310,9 +306,9 @@ void MainWindow::repopulateAccountsMenu()
section->setEnabled(false); section->setEnabled(false);
accountMenu->addAction(section); accountMenu->addAction(section);
for (AccountProfile profile : account->profiles()) for (auto profile : account->profiles())
{ {
QAction *action = new QAction(profile.name(), this); QAction *action = new QAction(profile.name, this);
action->setData(account->username()); action->setData(account->username());
action->setCheckable(true); action->setCheckable(true);
if(active_username == account->username()) if(active_username == account->username())
@ -320,7 +316,7 @@ void MainWindow::repopulateAccountsMenu()
action->setChecked(true); action->setChecked(true);
} }
action->setIcon(SkinUtils::getFaceFromCache(profile.name())); action->setIcon(SkinUtils::getFaceFromCache(profile.name));
accountMenu->addAction(action); accountMenu->addAction(action);
connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount())); connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount()));
} }
@ -378,7 +374,7 @@ void MainWindow::activeAccountChanged()
const AccountProfile *profile = account->currentProfile(); const AccountProfile *profile = account->currentProfile();
if (profile != nullptr) if (profile != nullptr)
{ {
accountMenuButton->setIcon(SkinUtils::getFaceFromCache(profile->name())); accountMenuButton->setIcon(SkinUtils::getFaceFromCache(profile->name));
return; return;
} }
} }
@ -790,6 +786,7 @@ void MainWindow::doLaunch()
void MainWindow::doLaunchInst(BaseInstance* instance, MojangAccountPtr account) void MainWindow::doLaunchInst(BaseInstance* instance, MojangAccountPtr account)
{ {
// We'll need to validate the access token to make sure the account is still logged in. // We'll need to validate the access token to make sure the account is still logged in.
/*
ProgressDialog progDialog(this); ProgressDialog progDialog(this);
RefreshTask refreshtask(account, &progDialog); RefreshTask refreshtask(account, &progDialog);
progDialog.exec(&refreshtask); progDialog.exec(&refreshtask);
@ -829,10 +826,12 @@ void MainWindow::doLaunchInst(BaseInstance* instance, MojangAccountPtr account)
QMessageBox::Warning, QMessageBox::Ok)->exec(); QMessageBox::Warning, QMessageBox::Ok)->exec();
} }
} }
*/
} }
bool MainWindow::doRefreshToken(MojangAccountPtr account, const QString& errorMsg) bool MainWindow::doRefreshToken(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)
{ {
@ -848,7 +847,8 @@ bool MainWindow::doRefreshToken(MojangAccountPtr account, const QString& errorMs
return doRefreshToken(account, authTask.failReason()); return doRefreshToken(account, authTask.failReason());
} }
} }
else return false; else return false;*/
return false;
} }
void MainWindow::prepareLaunch(BaseInstance* instance, MojangAccountPtr account) void MainWindow::prepareLaunch(BaseInstance* instance, MojangAccountPtr account)

View File

@ -20,7 +20,6 @@
#include <logger/QsLog.h> #include <logger/QsLog.h>
#include <logic/auth/flows/AuthenticateTask.h>
#include <logic/net/NetJob.h> #include <logic/net/NetJob.h>
#include <gui/dialogs/EditAccountDialog.h> #include <gui/dialogs/EditAccountDialog.h>
@ -117,8 +116,8 @@ void AccountListDialog::addAccount(const QString& errMsg)
QString username(loginDialog.username()); QString username(loginDialog.username());
QString password(loginDialog.password()); QString password(loginDialog.password());
MojangAccountPtr account = MojangAccountPtr(new MojangAccount(username)); MojangAccountPtr account = MojangAccount::createFromUsername(username);
/*
ProgressDialog progDialog(this); ProgressDialog progDialog(this);
AuthenticateTask authTask(account, password, &progDialog); AuthenticateTask authTask(account, password, &progDialog);
if (progDialog.exec(&authTask)) if (progDialog.exec(&authTask))
@ -132,9 +131,9 @@ void AccountListDialog::addAccount(const QString& errMsg)
for(AccountProfile profile : account->profiles()) for(AccountProfile profile : account->profiles())
{ {
auto meta = MMC->metacache()->resolveEntry("skins", profile.name() + ".png"); auto meta = MMC->metacache()->resolveEntry("skins", profile.name + ".png");
auto action = CacheDownload::make( auto action = CacheDownload::make(
QUrl("http://skins.minecraft.net/MinecraftSkins/" + profile.name() + ".png"), QUrl("http://skins.minecraft.net/MinecraftSkins/" + profile.name + ".png"),
meta); meta);
job->addNetAction(action); job->addNetAction(action);
meta->stale = true; meta->stale = true;
@ -142,5 +141,6 @@ void AccountListDialog::addAccount(const QString& errMsg)
job->start(); job->start();
} }
*/
} }
} }

View File

@ -20,8 +20,6 @@
#include <logger/QsLog.h> #include <logger/QsLog.h>
#include <logic/auth/flows/AuthenticateTask.h>
#include <gui/dialogs/ProgressDialog.h> #include <gui/dialogs/ProgressDialog.h>
#include <MultiMC.h> #include <MultiMC.h>

View File

@ -105,7 +105,7 @@ MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account)
#endif #endif
args << "-jar" << LAUNCHER_FILE; args << "-jar" << LAUNCHER_FILE;
args << account->currentProfile()->name(); args << account->currentProfile()->name;
args << account->sessionId(); args << account->sessionId();
args << windowTitle; args << windowTitle;
args << windowSize; args << windowSize;

View File

@ -75,20 +75,22 @@ QString MinecraftProcess::censorPrivateInfo(QString in)
{ {
if(!m_account) if(!m_account)
return in; return in;
else
QString sessionId = m_account->sessionId();
QString accessToken = m_account->accessToken();
QString clientToken = m_account->clientToken();
in.replace(sessionId, "<SESSION ID>");
in.replace(accessToken, "<ACCESS TOKEN>");
in.replace(clientToken, "<CLIENT TOKEN>");
auto profile = m_account->currentProfile();
if(profile)
{ {
QString sessionId = m_account->sessionId(); QString profileId = profile->id;
QString accessToken = m_account->accessToken(); QString profileName = profile->name;
QString clientToken = m_account->clientToken();
QString profileId = m_account->currentProfile()->id();
QString profileName = m_account->currentProfile()->name();
in.replace(sessionId, "<SESSION ID>");
in.replace(accessToken, "<ACCESS TOKEN>");
in.replace(clientToken, "<CLIENT TOKEN>");
in.replace(profileId, "<PROFILE ID>"); in.replace(profileId, "<PROFILE ID>");
in.replace(profileName, "<PROFILE NAME>"); in.replace(profileName, "<PROFILE NAME>");
return in;
} }
return in;
} }
// console window // console window

View File

@ -77,8 +77,8 @@ QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account)
token_mapping["auth_username"] = account->username(); token_mapping["auth_username"] = account->username();
token_mapping["auth_session"] = account->sessionId(); token_mapping["auth_session"] = account->sessionId();
token_mapping["auth_access_token"] = account->accessToken(); token_mapping["auth_access_token"] = account->accessToken();
token_mapping["auth_player_name"] = account->currentProfile()->name(); token_mapping["auth_player_name"] = account->currentProfile()->name;
token_mapping["auth_uuid"] = account->currentProfile()->id(); token_mapping["auth_uuid"] = account->currentProfile()->id;
// this is for offline?: // this is for offline?:
/* /*

View File

@ -16,113 +16,16 @@
*/ */
#include "MojangAccount.h" #include "MojangAccount.h"
#include "flows/RefreshTask.h"
#include "flows/AuthenticateTask.h"
#include <QUuid> #include <QUuid>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonArray> #include <QJsonArray>
#include <QRegExp>
#include <logger/QsLog.h> #include <logger/QsLog.h>
MojangAccount::MojangAccount(const QString &username, QObject *parent) : QObject(parent)
{
// Generate a client token.
m_clientToken = QUuid::createUuid().toString();
m_username = username;
m_currentProfile = -1;
}
MojangAccount::MojangAccount(const QString &username, const QString &clientToken,
const QString &accessToken, QObject *parent)
: QObject(parent)
{
m_username = username;
m_clientToken = clientToken;
m_accessToken = accessToken;
m_currentProfile = -1;
}
MojangAccount::MojangAccount(const MojangAccount &other, QObject *parent)
{
m_username = other.username();
m_clientToken = other.clientToken();
m_accessToken = other.accessToken();
m_profiles = other.m_profiles;
m_currentProfile = other.m_currentProfile;
}
QString MojangAccount::username() const
{
return m_username;
}
QString MojangAccount::clientToken() const
{
return m_clientToken;
}
void MojangAccount::setClientToken(const QString &clientToken)
{
m_clientToken = clientToken;
}
QString MojangAccount::accessToken() const
{
return m_accessToken;
}
void MojangAccount::setAccessToken(const QString &accessToken)
{
m_accessToken = accessToken;
}
QString MojangAccount::sessionId() const
{
return "token:" + m_accessToken + ":" + currentProfile()->id();
}
const QList<AccountProfile> MojangAccount::profiles() const
{
return m_profiles;
}
const AccountProfile *MojangAccount::currentProfile() const
{
if (m_currentProfile < 0)
{
if (m_profiles.length() > 0)
return &m_profiles.at(0);
else
return nullptr;
}
else
return &m_profiles.at(m_currentProfile);
}
bool MojangAccount::setProfile(const QString &profileId)
{
const QList<AccountProfile> &profiles = this->profiles();
for (int i = 0; i < profiles.length(); i++)
{
if (profiles.at(i).id() == profileId)
{
m_currentProfile = i;
return true;
}
}
return false;
}
void MojangAccount::loadProfiles(const ProfileList &profiles)
{
m_profiles.clear();
for (auto profile : profiles)
m_profiles.append(profile);
}
MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object) MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
{ {
// The JSON object must at least have a username for it to be valid. // The JSON object must at least have a username for it to be valid.
@ -143,7 +46,7 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
return nullptr; return nullptr;
} }
ProfileList profiles; QList<AccountProfile> profiles;
for (QJsonValue profileVal : profileArray) for (QJsonValue profileVal : profileArray)
{ {
QJsonObject profileObject = profileVal.toObject(); QJsonObject profileObject = profileVal.toObject();
@ -154,67 +57,112 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
QLOG_WARN() << "Unable to load a profile because it was missing an ID or a name."; QLOG_WARN() << "Unable to load a profile because it was missing an ID or a name.";
continue; continue;
} }
profiles.append(AccountProfile(id, name)); profiles.append({id, name});
} }
MojangAccountPtr account(new MojangAccount(username, clientToken, accessToken)); MojangAccountPtr account(new MojangAccount());
account->loadProfiles(profiles); account->m_username = username;
account->m_clientToken = clientToken;
account->m_accessToken = accessToken;
account->m_profiles = profiles;
// Get the currently selected profile. // Get the currently selected profile.
QString currentProfile = object.value("activeProfile").toString(""); QString currentProfile = object.value("activeProfile").toString("");
if (!currentProfile.isEmpty()) if (!currentProfile.isEmpty())
account->setProfile(currentProfile); account->setCurrentProfile(currentProfile);
return account; return account;
} }
QJsonObject MojangAccount::saveToJson() MojangAccountPtr MojangAccount::createFromUsername(const QString& username)
{
MojangAccountPtr account(new MojangAccount());
account->m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
account->m_username = username;
return account;
}
QJsonObject MojangAccount::saveToJson() const
{ {
QJsonObject json; QJsonObject json;
json.insert("username", username()); json.insert("username", m_username);
json.insert("clientToken", clientToken()); json.insert("clientToken", m_clientToken);
json.insert("accessToken", accessToken()); json.insert("accessToken", m_accessToken);
QJsonArray profileArray; QJsonArray profileArray;
for (AccountProfile profile : m_profiles) for (AccountProfile profile : m_profiles)
{ {
QJsonObject profileObj; QJsonObject profileObj;
profileObj.insert("id", profile.id()); profileObj.insert("id", profile.id);
profileObj.insert("name", profile.name()); profileObj.insert("name", profile.name);
profileArray.append(profileObj); profileArray.append(profileObj);
} }
json.insert("profiles", profileArray); json.insert("profiles", profileArray);
if (currentProfile() != nullptr) if (m_currentProfile != -1)
json.insert("activeProfile", currentProfile()->id()); json.insert("activeProfile", currentProfile()->id);
return json; return json;
} }
bool MojangAccount::setCurrentProfile(const QString &profileId)
AccountProfile::AccountProfile(const QString& id, const QString& name)
{ {
m_id = id; for (int i = 0; i < m_profiles.length(); i++)
m_name = name; {
if (m_profiles[i].id == profileId)
{
m_currentProfile = i;
return true;
}
}
return false;
} }
AccountProfile::AccountProfile(const AccountProfile &other) const AccountProfile* MojangAccount::currentProfile() const
{ {
m_id = other.m_id; if(m_currentProfile == -1)
m_name = other.m_name; return nullptr;
return &m_profiles[m_currentProfile];
} }
QString AccountProfile::id() const AccountStatus MojangAccount::accountStatus() const
{ {
return m_id; if(m_accessToken.isEmpty())
return NotVerified;
if(!m_online)
return Verified;
return Online;
} }
QString AccountProfile::name() const bool MojangAccount::login(QString password)
{ {
return m_name; if(m_currentTask)
return false;
if(password.isEmpty())
{
m_currentTask.reset(new RefreshTask(this, this));
}
else
{
m_currentTask.reset(new AuthenticateTask(this, password, this));
}
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
m_currentTask->start();
return true;
} }
void MojangAccount::propagateChange() void MojangAccount::authSucceeded()
{ {
m_online = true;
m_currentTask.reset();
emit changed();
}
void MojangAccount::authFailed(QString reason)
{
m_online = false;
m_accessToken = QString();
m_currentTask.reset();
emit changed(); emit changed();
} }

View File

@ -23,34 +23,25 @@
#include <memory> #include <memory>
class YggdrasilTask;
class MojangAccount; class MojangAccount;
typedef std::shared_ptr<MojangAccount> MojangAccountPtr; typedef std::shared_ptr<MojangAccount> MojangAccountPtr;
Q_DECLARE_METATYPE(MojangAccountPtr) Q_DECLARE_METATYPE(MojangAccountPtr)
/** /**
* Class that represents a profile within someone's Mojang account. * A profile within someone's Mojang account.
* *
* Currently, the profile system has not been implemented by Mojang yet, * Currently, the profile system has not been implemented by Mojang yet,
* but we might as well add some things for it in MultiMC right now so * but we might as well add some things for it in MultiMC right now so
* we don't have to rip the code to pieces to add it later. * we don't have to rip the code to pieces to add it later.
*/ */
class AccountProfile struct AccountProfile
{ {
public: QString id;
AccountProfile(const QString &id, const QString &name); QString name;
AccountProfile(const AccountProfile &other);
QString id() const;
QString name() const;
protected:
QString m_id;
QString m_name;
}; };
typedef QList<AccountProfile> ProfileList;
struct User struct User
{ {
QString id; QString id;
@ -59,6 +50,13 @@ struct User
QList<QPair<QString, QString>> properties; QList<QPair<QString, QString>> properties;
}; };
enum AccountStatus
{
NotVerified,
Verified,
Online
};
/** /**
* Object that stores information about a certain Mojang account. * Object that stores information about a certain Mojang account.
* *
@ -68,106 +66,112 @@ struct User
class MojangAccount : public QObject class MojangAccount : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public: /* construction */
/** //! Do not copy accounts. ever.
* Constructs a new MojangAccount with the given username. explicit MojangAccount(const MojangAccount &other, QObject *parent) = delete;
* The client token will be generated automatically and the access token will be blank.
*/
explicit MojangAccount(const QString &username, QObject *parent = 0);
/** //! Default constructor
* Constructs a new MojangAccount with the given username, client token, and access token. explicit MojangAccount(QObject *parent = 0) : QObject(parent) {};
*/
explicit MojangAccount(const QString &username, const QString &clientToken,
const QString &accessToken, QObject *parent = 0);
/** //! Creates an empty account for the specified user name.
* Constructs a new MojangAccount matching the given account. static MojangAccountPtr createFromUsername(const QString &username);
*/
MojangAccount(const MojangAccount &other, QObject *parent);
/** //! Loads a MojangAccount from the given JSON object.
* Loads a MojangAccount from the given JSON object.
*/
static MojangAccountPtr loadFromJson(const QJsonObject &json); static MojangAccountPtr loadFromJson(const QJsonObject &json);
/** //! Saves a MojangAccount to a JSON object and returns it.
* Saves a MojangAccount to a JSON object and returns it. QJsonObject saveToJson() const;
*/
QJsonObject saveToJson();
/**
* Update the account on disk and lists (it changed, for whatever reason)
* This is called by various Yggdrasil tasks.
*/
void propagateChange();
/**
* This MojangAccount's username. May be an email address if the account is migrated.
*/
QString username() const;
/**
* This MojangAccount's client token. This is a UUID used by Mojang's auth servers to identify this client.
* This is unique for each MojangAccount.
*/
QString clientToken() const;
/**
* Sets the MojangAccount's client token to the given value.
*/
void setClientToken(const QString &token);
/**
* This MojangAccount's access token.
* If the user has not chosen to stay logged in, this will be an empty string.
*/
QString accessToken() const;
/**
* Changes this MojangAccount's access token to the given value.
*/
void setAccessToken(const QString &token);
/**
* Get full session ID
*/
QString sessionId() const;
/**
* Returns a list of the available account profiles.
*/
const ProfileList profiles() const;
/**
* Returns a pointer to the currently selected profile.
* If no profile is selected, returns the first profile in the profile list or nullptr if there are none.
*/
const AccountProfile *currentProfile() const;
public: /* manipulation */
/** /**
* Sets the currently selected profile to the profile with the given ID string. * Sets the currently selected profile to the profile with the given ID string.
* If profileId is not in the list of available profiles, the function will simply return false. * If profileId is not in the list of available profiles, the function will simply return
* false.
*/ */
bool setProfile(const QString &profileId); bool setCurrentProfile(const QString &profileId);
/** /**
* Clears the current account profile list and replaces it with the given profile list. * Attempt to login. Empty password means we use the token.
* If the attempt fails because we already are performing some task, it returns false.
*/ */
void loadProfiles(const ProfileList &profiles); bool login(QString password = QString());
public: /* queries */
const QString &username() const
{
return m_username;
}
const QString &clientToken() const
{
return m_clientToken;
}
const QString &accessToken() const
{
return m_accessToken;
}
const QList<AccountProfile> &profiles() const
{
return m_profiles;
}
//! Get the session ID required for legacy Minecraft versions
QString sessionId() const
{
if (m_currentProfile != -1 && !m_accessToken.isEmpty())
return "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id;
return "-";
}
//! Returns the currently selected profile (if none, returns nullptr)
const AccountProfile *currentProfile() const;
//! Returns whether the account is NotVerified, Verified or Online
AccountStatus accountStatus() const;
signals: signals:
/** /**
* This isgnal is emitted whrn the account changes * This signal is emitted when the account changes
*/ */
void changed(); void changed();
protected: // TODO: better signalling for the various possible state changes - especially errors
protected: /* variables */
QString m_username; QString m_username;
// Used to identify the client - the user can have multiple clients for the same account
// Think: different launchers, all connecting to the same account/profile
QString m_clientToken; QString m_clientToken;
QString m_accessToken; // Blank if not logged in.
int m_currentProfile; // Index of the selected profile within the list of available // Blank if not logged in.
// profiles. -1 if nothing is selected. QString m_accessToken;
ProfileList m_profiles; // List of available profiles.
User m_user; // the user structure, whatever it is. // Index of the selected profile within the list of available
// profiles. -1 if nothing is selected.
int m_currentProfile = -1;
// List of available profiles.
QList<AccountProfile> m_profiles;
// the user structure, whatever it is.
User m_user;
// true when the account is verified
bool m_online = false;
// current task we are executing here
std::shared_ptr<YggdrasilTask> m_currentTask;
private slots:
void authSucceeded();
void authFailed(QString reason);
public:
friend class YggdrasilTask;
friend class AuthenticateTask;
friend class ValidateTask;
friend class RefreshTask;
}; };

View File

@ -25,10 +25,9 @@
#include <MultiMC.h> #include <MultiMC.h>
#include <logic/auth/MojangAccount.h> #include <logic/auth/MojangAccount.h>
YggdrasilTask::YggdrasilTask(MojangAccountPtr account, QObject *parent) : Task(parent) YggdrasilTask::YggdrasilTask(MojangAccount *account, QObject *parent)
: Task(parent), m_account(account)
{ {
m_error = nullptr;
m_account = account;
} }
YggdrasilTask::~YggdrasilTask() YggdrasilTask::~YggdrasilTask()
@ -81,8 +80,9 @@ void YggdrasilTask::processReply(QNetworkReply *reply)
if (responseCode == 200) if (responseCode == 200)
{ {
// If the response code was 200, then there shouldn't be an error. Make sure anyways. // If the response code was 200, then there shouldn't be an error. Make sure
// Also, sometimes an empty reply indicates success. If there was no data received, // anyways.
// Also, sometimes an empty reply indicates success. If there was no data received,
// pass an empty json object to the processResponse function. // pass an empty json object to the processResponse function.
if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0)
{ {
@ -102,25 +102,34 @@ void YggdrasilTask::processReply(QNetworkReply *reply)
} }
else else
{ {
emitFailed(tr("Failed to parse Yggdrasil JSON response: %1 at offset %2.").arg(jsonError.errorString()).arg(jsonError.offset)); emitFailed(tr("Failed to parse Yggdrasil JSON response: %1 at offset %2.")
.arg(jsonError.errorString())
.arg(jsonError.offset));
} }
} }
else else
{ {
// If the response code was not 200, then Yggdrasil may have given us information about the error. // If the response code was not 200, then Yggdrasil may have given us information
// If we can parse the response, then get information from it. Otherwise just say there was an unknown error. // 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) if (jsonError.error == QJsonParseError::NoError)
{ {
// We were able to parse the server's response. Woo! // 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. // Call processError. If a subclass has overridden it then they'll handle their
QLOG_DEBUG() << "The request failed, but the server gave us an error message. Processing error."; // stuff there.
QLOG_DEBUG() << "The request failed, but the server gave us an error message. "
"Processing error.";
emitFailed(processError(doc.object())); emitFailed(processError(doc.object()));
} }
else else
{ {
// The server didn't say anything regarding the error. Give the user an unknown error. // The server didn't say anything regarding the error. Give the user an unknown
QLOG_DEBUG() << "The request failed and the server gave no error message. Unknown error."; // error.
emitFailed(tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(reply->errorString())); 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()));
} }
} }
} }
@ -161,8 +170,3 @@ YggdrasilTask::Error *YggdrasilTask::getError() const
{ {
return this->m_error; return this->m_error;
} }
MojangAccountPtr YggdrasilTask::getMojangAccount() const
{
return this->m_account;
}

View File

@ -31,7 +31,7 @@ class YggdrasilTask : public Task
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit YggdrasilTask(MojangAccountPtr account, QObject *parent = 0); explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0);
~YggdrasilTask(); ~YggdrasilTask();
/** /**
@ -59,11 +59,6 @@ public:
QString m_cause; QString m_cause;
}; };
/**
* Gets the Mojang account that this task is operating on.
*/
virtual MojangAccountPtr getMojangAccount() const;
/** /**
* Returns a pointer to a YggdrasilTask::Error object if an error has occurred. * Returns a pointer to a YggdrasilTask::Error object if an error has occurred.
* If no error has occurred, returns a null pointer. * If no error has occurred, returns a null pointer.
@ -120,11 +115,11 @@ protected:
*/ */
virtual QString getStateMessage(const State state) const; virtual QString getStateMessage(const State state) const;
MojangAccountPtr m_account; MojangAccount *m_account = nullptr;
QNetworkReply *m_netReply; QNetworkReply *m_netReply;
Error *m_error; Error *m_error = nullptr;
protected protected
slots: slots:

View File

@ -26,7 +26,7 @@
#include "logger/QsLog.h" #include "logger/QsLog.h"
AuthenticateTask::AuthenticateTask(MojangAccountPtr account, const QString &password, AuthenticateTask::AuthenticateTask(MojangAccount * account, const QString &password,
QObject *parent) QObject *parent)
: YggdrasilTask(account, parent), m_password(password) : YggdrasilTask(account, parent), m_password(password)
{ {
@ -59,14 +59,14 @@ QJsonObject AuthenticateTask::getRequestContent() const
req.insert("agent", agent); req.insert("agent", agent);
} }
req.insert("username", getMojangAccount()->username()); req.insert("username", m_account->username());
req.insert("password", m_password); req.insert("password", m_password);
req.insert("requestUser", true); req.insert("requestUser", true);
// If we already have a client token, give it to the server. // If we already have a client token, give it to the server.
// Otherwise, let the server give us one. // Otherwise, let the server give us one.
if (!getMojangAccount()->clientToken().isEmpty()) if (!m_account->m_clientToken.isEmpty())
req.insert("clientToken", getMojangAccount()->clientToken()); req.insert("clientToken", m_account->m_clientToken);
return req; return req;
} }
@ -88,8 +88,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData)
QLOG_ERROR() << "Server didn't send a client token."; QLOG_ERROR() << "Server didn't send a client token.";
return false; return false;
} }
if (!getMojangAccount()->clientToken().isEmpty() && if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken)
clientToken != getMojangAccount()->clientToken())
{ {
// The server changed our client token! Obey its wishes, but complain. That's what I do // The server changed our client token! Obey its wishes, but complain. That's what I do
// for my parents, so... // for my parents, so...
@ -97,7 +96,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData)
<< "'. This shouldn't happen, but it isn't really a big deal."; << "'. This shouldn't happen, but it isn't really a big deal.";
} }
// Set the client token. // Set the client token.
getMojangAccount()->setClientToken(clientToken); m_account->m_clientToken = clientToken;
// Now, we set the access token. // Now, we set the access token.
QLOG_DEBUG() << "Getting access token."; QLOG_DEBUG() << "Getting access token.";
@ -109,7 +108,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData)
QLOG_ERROR() << "Server didn't send an access token."; QLOG_ERROR() << "Server didn't send an access token.";
} }
// Set the access token. // Set the access token.
getMojangAccount()->setAccessToken(accessToken); m_account->m_accessToken = accessToken;
// Now we load the list of available profiles. // Now we load the list of available profiles.
// Mojang hasn't yet implemented the profile system, // Mojang hasn't yet implemented the profile system,
@ -117,7 +116,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData)
// don't have trouble implementing it later. // don't have trouble implementing it later.
QLOG_DEBUG() << "Loading profile list."; QLOG_DEBUG() << "Loading profile list.";
QJsonArray availableProfiles = responseData.value("availableProfiles").toArray(); QJsonArray availableProfiles = responseData.value("availableProfiles").toArray();
ProfileList loadedProfiles; QList<AccountProfile> loadedProfiles;
for (auto iter : availableProfiles) for (auto iter : availableProfiles)
{ {
QJsonObject profile = iter.toObject(); QJsonObject profile = iter.toObject();
@ -135,10 +134,10 @@ bool AuthenticateTask::processResponse(QJsonObject responseData)
} }
// Now, add a new AccountProfile entry to the list. // Now, add a new AccountProfile entry to the list.
loadedProfiles.append(AccountProfile(id, name)); loadedProfiles.append({id, name});
} }
// Put the list of profiles we loaded into the MojangAccount object. // Put the list of profiles we loaded into the MojangAccount object.
getMojangAccount()->loadProfiles(loadedProfiles); m_account->m_profiles = loadedProfiles;
// Finally, we set the current profile to the correct value. This is pretty simple. // Finally, we set the current profile to the correct value. This is pretty simple.
// We do need to make sure that the current profile that the server gave us // We do need to make sure that the current profile that the server gave us
@ -153,7 +152,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData)
QLOG_ERROR() << "Server didn't specify a currently selected profile."; QLOG_ERROR() << "Server didn't specify a currently selected profile.";
return false; return false;
} }
if (!getMojangAccount()->setProfile(currentProfileId)) if (!m_account->setCurrentProfile(currentProfileId))
{ {
// TODO: Set an error to display to the user. // TODO: Set an error to display to the user.
QLOG_ERROR() << "Server specified a selected profile that wasn't in the available " QLOG_ERROR() << "Server specified a selected profile that wasn't in the available "

View File

@ -30,7 +30,7 @@ class AuthenticateTask : public YggdrasilTask
{ {
Q_OBJECT Q_OBJECT
public: public:
AuthenticateTask(MojangAccountPtr account, const QString &password, QObject *parent = 0); AuthenticateTask(MojangAccount *account, const QString &password, QObject *parent = 0);
protected: protected:
virtual QJsonObject getRequestContent() const; virtual QJsonObject getRequestContent() const;

View File

@ -25,7 +25,7 @@
#include "logger/QsLog.h" #include "logger/QsLog.h"
RefreshTask::RefreshTask(MojangAccountPtr account, QObject *parent) RefreshTask::RefreshTask(MojangAccount *account, QObject *parent)
: YggdrasilTask(account, parent) : YggdrasilTask(account, parent)
{ {
} }
@ -44,13 +44,12 @@ QJsonObject RefreshTask::getRequestContent() const
* "requestUser": true/false // request the user structure * "requestUser": true/false // request the user structure
* } * }
*/ */
auto account = getMojangAccount();
QJsonObject req; QJsonObject req;
req.insert("clientToken", account->clientToken()); req.insert("clientToken", m_account->m_clientToken);
req.insert("accessToken", account->accessToken()); req.insert("accessToken", m_account->m_accessToken);
/* /*
{ {
auto currentProfile = account->currentProfile(); auto currentProfile = m_account->currentProfile();
QJsonObject profile; QJsonObject profile;
profile.insert("id", currentProfile->id()); profile.insert("id", currentProfile->id());
profile.insert("name", currentProfile->name()); profile.insert("name", currentProfile->name());
@ -64,8 +63,6 @@ QJsonObject RefreshTask::getRequestContent() const
bool RefreshTask::processResponse(QJsonObject responseData) bool RefreshTask::processResponse(QJsonObject responseData)
{ {
auto account = getMojangAccount();
// Read the response data. We need to get the client token, access token, and the selected // Read the response data. We need to get the client token, access token, and the selected
// profile. // profile.
QLOG_DEBUG() << "Processing authentication response."; QLOG_DEBUG() << "Processing authentication response.";
@ -80,7 +77,7 @@ bool RefreshTask::processResponse(QJsonObject responseData)
QLOG_ERROR() << "Server didn't send a client token."; QLOG_ERROR() << "Server didn't send a client token.";
return false; return false;
} }
if (!account->clientToken().isEmpty() && clientToken != account->clientToken()) if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken)
{ {
// The server changed our client token! Obey its wishes, but complain. That's what I do // The server changed our client token! Obey its wishes, but complain. That's what I do
// for my parents, so... // for my parents, so...
@ -104,7 +101,7 @@ bool RefreshTask::processResponse(QJsonObject responseData)
// profile) // profile)
QJsonObject currentProfile = responseData.value("selectedProfile").toObject(); QJsonObject currentProfile = responseData.value("selectedProfile").toObject();
QString currentProfileId = currentProfile.value("id").toString(""); QString currentProfileId = currentProfile.value("id").toString("");
if (account->currentProfile()->id() != currentProfileId) if (m_account->currentProfile()->id != currentProfileId)
{ {
// TODO: Set an error to display to the user. // TODO: Set an error to display to the user.
QLOG_ERROR() << "Server didn't specify the same selected profile as ours."; QLOG_ERROR() << "Server didn't specify the same selected profile as ours.";
@ -132,8 +129,7 @@ bool RefreshTask::processResponse(QJsonObject responseData)
// we've succeeded. // we've succeeded.
QLOG_DEBUG() << "Finished reading refresh response."; QLOG_DEBUG() << "Finished reading refresh response.";
// Reset the access token. // Reset the access token.
account->setAccessToken(accessToken); m_account->m_accessToken = accessToken;
account->propagateChange();
return true; return true;
} }

View File

@ -30,7 +30,7 @@ class RefreshTask : public YggdrasilTask
{ {
Q_OBJECT Q_OBJECT
public: public:
RefreshTask(MojangAccountPtr account, QObject *parent = 0); RefreshTask(MojangAccount * account, QObject *parent = 0);
protected: protected:
virtual QJsonObject getRequestContent() const; virtual QJsonObject getRequestContent() const;

View File

@ -26,7 +26,7 @@
#include "logger/QsLog.h" #include "logger/QsLog.h"
ValidateTask::ValidateTask(MojangAccountPtr account, QObject *parent) ValidateTask::ValidateTask(MojangAccount * account, QObject *parent)
: YggdrasilTask(account, parent) : YggdrasilTask(account, parent)
{ {
} }
@ -34,7 +34,7 @@ ValidateTask::ValidateTask(MojangAccountPtr account, QObject *parent)
QJsonObject ValidateTask::getRequestContent() const QJsonObject ValidateTask::getRequestContent() const
{ {
QJsonObject req; QJsonObject req;
req.insert("accessToken", getMojangAccount()->accessToken()); req.insert("accessToken", m_account->m_accessToken);
return req; return req;
} }

View File

@ -28,7 +28,7 @@ class ValidateTask : public YggdrasilTask
{ {
Q_OBJECT Q_OBJECT
public: public:
ValidateTask(MojangAccountPtr account, QObject *parent = 0); ValidateTask(MojangAccount *account, QObject *parent = 0);
protected: protected:
virtual QJsonObject getRequestContent() const; virtual QJsonObject getRequestContent() const;

View File

@ -83,10 +83,7 @@ void MojangAccountList::removeAccount(QModelIndex index)
MojangAccountPtr MojangAccountList::activeAccount() const MojangAccountPtr MojangAccountList::activeAccount() const
{ {
if (m_activeAccount.isEmpty()) return m_activeAccount;
return nullptr;
else
return findAccount(m_activeAccount);
} }
void MojangAccountList::setActiveAccount(const QString &username) void MojangAccountList::setActiveAccount(const QString &username)
@ -94,14 +91,14 @@ void MojangAccountList::setActiveAccount(const QString &username)
beginResetModel(); beginResetModel();
if (username.isEmpty()) if (username.isEmpty())
{ {
m_activeAccount = ""; m_activeAccount = nullptr;
} }
else else
{ {
for (MojangAccountPtr account : m_accounts) for (MojangAccountPtr account : m_accounts)
{ {
if (account->username() == username) if (account->username() == username)
m_activeAccount = username; m_activeAccount = account;
} }
} }
endResetModel(); endResetModel();
@ -152,7 +149,7 @@ QVariant MojangAccountList::data(const QModelIndex &index, int role) const
switch (index.column()) switch (index.column())
{ {
case ActiveColumn: case ActiveColumn:
return account->username() == m_activeAccount; return account == m_activeAccount;
case NameColumn: case NameColumn:
return account->username(); return account->username();
@ -297,11 +294,9 @@ bool MojangAccountList::loadList(const QString &filePath)
QLOG_WARN() << "Failed to load an account."; QLOG_WARN() << "Failed to load an account.";
} }
} }
endResetModel();
// Load the active account. // Load the active account.
m_activeAccount = root.value("activeAccount").toString(""); m_activeAccount = findAccount(root.value("activeAccount").toString(""));
endResetModel();
return true; return true;
} }
@ -336,8 +331,11 @@ bool MojangAccountList::saveList(const QString &filePath)
// Insert the account list into the root object. // Insert the account list into the root object.
root.insert("accounts", accounts); root.insert("accounts", accounts);
// Save the active account. if(m_activeAccount)
root.insert("activeAccount", m_activeAccount); {
// Save the active account.
root.insert("activeAccount", m_activeAccount->username());
}
// Create a JSON document object to convert our JSON to bytes. // Create a JSON document object to convert our JSON to bytes.
QJsonDocument doc(root); QJsonDocument doc(root);

View File

@ -161,10 +161,9 @@ protected:
QList<MojangAccountPtr> m_accounts; QList<MojangAccountPtr> m_accounts;
/*! /*!
* Username of the account that is currently active. * Account that is currently active.
* Empty string if no account is active.
*/ */
QString m_activeAccount; MojangAccountPtr m_activeAccount;
//! Path to the account list file. Empty string if there isn't one. //! Path to the account list file. Empty string if there isn't one.
QString m_listFilePath; QString m_listFilePath;