Merge branch 'feature_yggdrasil' into develop
Conflicts: gui/MainWindow.cpp logic/OneSixInstance.h Fix missing session id functionality for legacy and old onesix.
This commit is contained in:
commit
82225a21e1
8
.gitignore
vendored
8
.gitignore
vendored
@ -9,3 +9,11 @@ build
|
||||
resources/CMakeFiles
|
||||
resources/MultiMCLauncher.jar
|
||||
*~
|
||||
*.swp
|
||||
|
||||
# Ctags File
|
||||
tags
|
||||
|
||||
# YouCompleteMe config stuff.
|
||||
.ycm_extra_conf.*
|
||||
|
||||
|
@ -217,6 +217,8 @@ gui/dialogs/EditNotesDialog.h
|
||||
gui/dialogs/EditNotesDialog.cpp
|
||||
gui/dialogs/CustomMessageBox.h
|
||||
gui/dialogs/CustomMessageBox.cpp
|
||||
gui/dialogs/AccountListDialog.h
|
||||
gui/dialogs/AccountListDialog.cpp
|
||||
|
||||
# GUI - widgets
|
||||
gui/widgets/InstanceDelegate.h
|
||||
@ -269,6 +271,14 @@ logic/net/LoginTask.cpp
|
||||
logic/net/S3ListBucket.h
|
||||
logic/net/S3ListBucket.cpp
|
||||
|
||||
# Yggdrasil login stuff
|
||||
logic/auth/MojangAccount.h
|
||||
logic/auth/MojangAccount.cpp
|
||||
logic/auth/YggdrasilTask.h
|
||||
logic/auth/YggdrasilTask.cpp
|
||||
logic/auth/AuthenticateTask.h
|
||||
logic/auth/AuthenticateTask.cpp
|
||||
|
||||
|
||||
# legacy instances
|
||||
logic/LegacyInstance.h
|
||||
@ -317,6 +327,8 @@ logic/lists/ForgeVersionList.h
|
||||
logic/lists/ForgeVersionList.cpp
|
||||
logic/lists/JavaVersionList.h
|
||||
logic/lists/JavaVersionList.cpp
|
||||
logic/lists/MojangAccountList.h
|
||||
logic/lists/MojangAccountList.cpp
|
||||
|
||||
# misc model/view
|
||||
logic/EnabledItemFilter.h
|
||||
@ -359,6 +371,7 @@ gui/dialogs/IconPickerDialog.ui
|
||||
gui/dialogs/LegacyModEditDialog.ui
|
||||
gui/dialogs/OneSixModEditDialog.ui
|
||||
gui/dialogs/EditNotesDialog.ui
|
||||
gui/dialogs/AccountListDialog.ui
|
||||
|
||||
# Widgets/other
|
||||
gui/widgets/MCModInfoFrame.ui
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/dialogs/VersionSelectDialog.h"
|
||||
#include "logic/lists/InstanceList.h"
|
||||
#include "logic/lists/MojangAccountList.h"
|
||||
#include "logic/lists/IconList.h"
|
||||
#include "logic/lists/LwjglVersionList.h"
|
||||
#include "logic/lists/MinecraftVersionList.h"
|
||||
@ -146,6 +147,12 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
|
||||
connect(InstDirSetting, SIGNAL(settingChanged(const Setting &, QVariant)),
|
||||
m_instances.get(), SLOT(on_InstFolderChanged(const Setting &, QVariant)));
|
||||
|
||||
// and accounts
|
||||
m_accounts.reset(new MojangAccountList(this));
|
||||
QLOG_INFO() << "Loading accounts...";
|
||||
m_accounts->setListFilePath("accounts.json", true);
|
||||
m_accounts->loadList();
|
||||
|
||||
// init the http meta cache
|
||||
initHttpMetaCache();
|
||||
|
||||
|
@ -13,6 +13,7 @@ class LWJGLVersionList;
|
||||
class HttpMetaCache;
|
||||
class SettingsObject;
|
||||
class InstanceList;
|
||||
class MojangAccountList;
|
||||
class IconList;
|
||||
class QNetworkAccessManager;
|
||||
class ForgeVersionList;
|
||||
@ -57,6 +58,11 @@ public:
|
||||
return m_instances;
|
||||
}
|
||||
|
||||
std::shared_ptr<MojangAccountList> accounts()
|
||||
{
|
||||
return m_accounts;
|
||||
}
|
||||
|
||||
std::shared_ptr<IconList> icons();
|
||||
|
||||
Status status()
|
||||
@ -101,6 +107,7 @@ private:
|
||||
std::shared_ptr<QTranslator> m_mmc_translator;
|
||||
std::shared_ptr<SettingsObject> m_settings;
|
||||
std::shared_ptr<InstanceList> m_instances;
|
||||
std::shared_ptr<MojangAccountList> m_accounts;
|
||||
std::shared_ptr<IconList> m_icons;
|
||||
std::shared_ptr<QNetworkAccessManager> m_qnam;
|
||||
std::shared_ptr<HttpMetaCache> m_metacache;
|
||||
|
@ -57,6 +57,7 @@
|
||||
#include "gui/dialogs/IconPickerDialog.h"
|
||||
#include "gui/dialogs/EditNotesDialog.h"
|
||||
#include "gui/dialogs/CopyInstanceDialog.h"
|
||||
#include "gui/dialogs/AccountListDialog.h"
|
||||
|
||||
#include "gui/ConsoleWindow.h"
|
||||
|
||||
@ -84,11 +85,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
|
||||
ui->setupUi(this);
|
||||
setWindowTitle(QString("MultiMC %1").arg(MMC->version().toString()));
|
||||
|
||||
// Set the selected instance to null
|
||||
m_selectedInstance = nullptr;
|
||||
// Set active instance to null.
|
||||
m_activeInst = nullptr;
|
||||
|
||||
// OSX magic.
|
||||
setUnifiedTitleAndToolBarOnMac(true);
|
||||
|
||||
@ -428,6 +424,12 @@ void MainWindow::on_actionSettings_triggered()
|
||||
proxymodel->sort(0);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionManageAccounts_triggered()
|
||||
{
|
||||
AccountListDialog dialog(this);
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void MainWindow::on_actionReportBug_triggered()
|
||||
{
|
||||
openWebPage(QUrl("http://multimc.myjetbrains.com/youtrack/dashboard#newissue=yes"));
|
||||
@ -538,11 +540,7 @@ void MainWindow::instanceActivated(QModelIndex index)
|
||||
|
||||
NagUtils::checkJVMArgs(inst->settings().get("JvmArgs").toString(), this);
|
||||
|
||||
bool autoLogin = inst->settings().get("AutoLogin").toBool();
|
||||
if (autoLogin)
|
||||
doAutoLogin();
|
||||
else
|
||||
doLogin();
|
||||
doLogin();
|
||||
}
|
||||
|
||||
void MainWindow::on_actionLaunchInstance_triggered()
|
||||
@ -554,106 +552,74 @@ void MainWindow::on_actionLaunchInstance_triggered()
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::doAutoLogin()
|
||||
{
|
||||
if (!m_selectedInstance)
|
||||
return;
|
||||
|
||||
Keyring *k = Keyring::instance();
|
||||
QStringList accounts = k->getStoredAccounts("minecraft");
|
||||
|
||||
if (!accounts.isEmpty())
|
||||
{
|
||||
QString username = accounts[0];
|
||||
QString password = k->getPassword("minecraft", username);
|
||||
|
||||
if (!password.isEmpty())
|
||||
{
|
||||
QLOG_INFO() << "Automatically logging in with stored account: " << username;
|
||||
m_activeInst = m_selectedInstance;
|
||||
doLogin(username, password);
|
||||
}
|
||||
else
|
||||
{
|
||||
QLOG_ERROR() << "Auto login set for account, but no password was found: "
|
||||
<< username;
|
||||
doLogin(tr("Auto login attempted, but no password is stored."));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QLOG_ERROR() << "Auto login set but no accounts were stored.";
|
||||
doLogin(tr("Auto login attempted, but no accounts are stored."));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::doLogin(QString username, QString password)
|
||||
{
|
||||
PasswordLogin uInfo{username, password};
|
||||
|
||||
ProgressDialog *tDialog = new ProgressDialog(this);
|
||||
LoginTask *loginTask = new LoginTask(uInfo, tDialog);
|
||||
connect(loginTask, SIGNAL(succeeded()), SLOT(onLoginComplete()), Qt::QueuedConnection);
|
||||
connect(loginTask, SIGNAL(failed(QString)), SLOT(doLogin(QString)), Qt::QueuedConnection);
|
||||
|
||||
tDialog->exec(loginTask);
|
||||
}
|
||||
|
||||
void MainWindow::doLogin(const QString &errorMsg)
|
||||
{
|
||||
if (!m_selectedInstance)
|
||||
return;
|
||||
|
||||
LoginDialog *loginDlg = new LoginDialog(this, errorMsg);
|
||||
if (!m_selectedInstance->lastLaunch())
|
||||
loginDlg->forceOnline();
|
||||
|
||||
loginDlg->exec();
|
||||
if (loginDlg->result() == QDialog::Accepted)
|
||||
// Find an account to use.
|
||||
std::shared_ptr<MojangAccountList> accounts = MMC->accounts();
|
||||
MojangAccountPtr account = accounts->activeAccount();
|
||||
if (accounts->count() <= 0)
|
||||
{
|
||||
if (loginDlg->isOnline())
|
||||
// Tell the user they need to log in at least one account in order to play.
|
||||
auto reply = CustomMessageBox::selectable(this, tr("No Accounts"),
|
||||
tr("In order to play Minecraft, you must have at least one Mojang or Minecraft account logged in to MultiMC."
|
||||
"Would you like to open the account manager to add an account now?"),
|
||||
QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)->exec();
|
||||
|
||||
if (reply == QMessageBox::Yes)
|
||||
{
|
||||
m_activeInst = m_selectedInstance;
|
||||
doLogin(loginDlg->getUsername(), loginDlg->getPassword());
|
||||
// Open the account manager.
|
||||
on_actionManageAccounts_triggered();
|
||||
}
|
||||
else
|
||||
}
|
||||
else if (account.get() == nullptr)
|
||||
{
|
||||
// Tell the user they need to log in at least one account in order to play.
|
||||
auto reply = CustomMessageBox::selectable(this, tr("No Account Selected"),
|
||||
tr("You don't have an account selected as an active account."
|
||||
"Would you like to open the account manager to select one now?"),
|
||||
QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)->exec();
|
||||
|
||||
if (reply == QMessageBox::Yes)
|
||||
{
|
||||
QString user = loginDlg->getUsername();
|
||||
if (user.length() == 0)
|
||||
user = QString("Player");
|
||||
m_activeLogin = {user, QString("Offline"), user, QString()};
|
||||
m_activeInst = m_selectedInstance;
|
||||
launchInstance(m_activeInst, m_activeLogin);
|
||||
// Open the account manager.
|
||||
on_actionManageAccounts_triggered();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We'll need to validate the access token to make sure the account is still logged in.
|
||||
// TODO: Do that ^
|
||||
|
||||
prepareLaunch(m_selectedInstance, account);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onLoginComplete()
|
||||
void MainWindow::prepareLaunch(BaseInstance* instance, MojangAccountPtr account)
|
||||
{
|
||||
if (!m_activeInst)
|
||||
return;
|
||||
LoginTask *task = (LoginTask *)QObject::sender();
|
||||
m_activeLogin = task->getResult();
|
||||
|
||||
Task *updateTask = m_activeInst->doUpdate();
|
||||
Task *updateTask = instance->doUpdate();
|
||||
if (!updateTask)
|
||||
{
|
||||
launchInstance(m_activeInst, m_activeLogin);
|
||||
launchInstance(instance, account);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgressDialog tDialog(this);
|
||||
connect(updateTask, SIGNAL(succeeded()), SLOT(onGameUpdateComplete()));
|
||||
connect(updateTask, &Task::succeeded, [this, instance, account] { launchInstance(instance, account); });
|
||||
connect(updateTask, SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString)));
|
||||
tDialog.exec(updateTask);
|
||||
delete updateTask;
|
||||
}
|
||||
|
||||
auto job = new NetJob("Player skin: " + m_activeLogin.player_name);
|
||||
QString playerName = account->currentProfile()->name();
|
||||
|
||||
auto meta = MMC->metacache()->resolveEntry("skins", m_activeLogin.player_name + ".png");
|
||||
auto job = new NetJob("Player skin: " + playerName);
|
||||
|
||||
auto meta = MMC->metacache()->resolveEntry("skins", playerName + ".png");
|
||||
auto action = CacheDownload::make(
|
||||
QUrl("http://skins.minecraft.net/MinecraftSkins/" + m_activeLogin.player_name + ".png"),
|
||||
QUrl("http://skins.minecraft.net/MinecraftSkins/" + playerName + ".png"),
|
||||
meta);
|
||||
job->addNetAction(action);
|
||||
meta->stale = true;
|
||||
@ -678,12 +644,12 @@ void MainWindow::onLoginComplete()
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
|
||||
QJsonObject root = jsonDoc.object();
|
||||
QJsonObject mappings = root.value("mappings").toObject();
|
||||
QJsonArray usernames = mappings.value(m_activeLogin.username).toArray();
|
||||
QJsonArray usernames = mappings.value(account->username()).toArray();
|
||||
|
||||
if (!usernames.contains(m_activeLogin.player_name))
|
||||
if (!usernames.contains(playerName))
|
||||
{
|
||||
usernames.prepend(m_activeLogin.player_name);
|
||||
mappings[m_activeLogin.username] = usernames;
|
||||
usernames.prepend(playerName);
|
||||
mappings[account->username()] = usernames;
|
||||
root["mappings"] = mappings;
|
||||
jsonDoc.setObject(root);
|
||||
|
||||
@ -693,22 +659,12 @@ void MainWindow::onLoginComplete()
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onGameUpdateComplete()
|
||||
{
|
||||
launchInstance(m_activeInst, m_activeLogin);
|
||||
}
|
||||
|
||||
void MainWindow::onGameUpdateError(QString error)
|
||||
{
|
||||
CustomMessageBox::selectable(this, tr("Error updating instance"), error,
|
||||
QMessageBox::Warning)->show();
|
||||
}
|
||||
|
||||
void MainWindow::launchInstance(BaseInstance *instance, LoginResponse response)
|
||||
void MainWindow::launchInstance(BaseInstance *instance, MojangAccountPtr account)
|
||||
{
|
||||
Q_ASSERT_X(instance != NULL, "launchInstance", "instance is NULL");
|
||||
Q_ASSERT_X(account.get() != nullptr, "launchInstance", "account is NULL");
|
||||
|
||||
proc = instance->prepareForLaunch(response);
|
||||
proc = instance->prepareForLaunch(account);
|
||||
if (!proc)
|
||||
return;
|
||||
|
||||
@ -717,10 +673,19 @@ void MainWindow::launchInstance(BaseInstance *instance, LoginResponse response)
|
||||
console = new ConsoleWindow(proc);
|
||||
connect(console, SIGNAL(isClosing()), this, SLOT(instanceEnded()));
|
||||
|
||||
proc->setLogin(response.username, response.session_id);
|
||||
// I think this will work...
|
||||
QString username = account->username();
|
||||
QString session_id = account->accessToken();
|
||||
proc->setLogin(username, session_id);
|
||||
proc->launch();
|
||||
}
|
||||
|
||||
void MainWindow::onGameUpdateError(QString error)
|
||||
{
|
||||
CustomMessageBox::selectable(this, tr("Error updating instance"), error,
|
||||
QMessageBox::Warning)->show();
|
||||
}
|
||||
|
||||
void MainWindow::taskStart()
|
||||
{
|
||||
// Nothing to do here yet.
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include "logic/net/LoginTask.h"
|
||||
#include "logic/BaseInstance.h"
|
||||
|
||||
#include "logic/auth/MojangAccount.h"
|
||||
|
||||
class QToolButton;
|
||||
class LabeledToolButton;
|
||||
class QLabel;
|
||||
@ -80,6 +82,8 @@ slots:
|
||||
|
||||
void on_actionSettings_triggered();
|
||||
|
||||
void on_actionManageAccounts_triggered();
|
||||
|
||||
void on_actionReportBug_triggered();
|
||||
|
||||
void on_actionNews_triggered();
|
||||
@ -103,12 +107,18 @@ slots:
|
||||
void on_actionEditInstNotes_triggered();
|
||||
|
||||
void doLogin(const QString &errorMsg = "");
|
||||
void doLogin(QString username, QString password);
|
||||
void doAutoLogin();
|
||||
|
||||
void onLoginComplete();
|
||||
/*!
|
||||
* Launches the given instance with the given account.
|
||||
* This function assumes that the given account has a valid, usable access token.
|
||||
*/
|
||||
void launchInstance(BaseInstance* instance, MojangAccountPtr account);
|
||||
|
||||
/*!
|
||||
* Prepares the given instance for launch with the given account.
|
||||
*/
|
||||
void prepareLaunch(BaseInstance* instance, MojangAccountPtr account);
|
||||
|
||||
void onGameUpdateComplete();
|
||||
void onGameUpdateError(QString error);
|
||||
|
||||
void taskStart();
|
||||
@ -136,8 +146,6 @@ slots:
|
||||
|
||||
void startTask(Task *task);
|
||||
|
||||
void launchInstance(BaseInstance *inst, LoginResponse response);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *ev);
|
||||
void setCatBackground(bool enabled);
|
||||
@ -155,12 +163,6 @@ private:
|
||||
|
||||
BaseInstance *m_selectedInstance;
|
||||
|
||||
// A pointer to the instance we are actively doing stuff with.
|
||||
// This is set when the user launches an instance and is used to refer to that
|
||||
// instance throughout the launching process.
|
||||
BaseInstance *m_activeInst;
|
||||
LoginResponse m_activeLogin;
|
||||
|
||||
Task *m_versionLoadTask;
|
||||
|
||||
QLabel *m_statusLeft;
|
||||
|
@ -70,6 +70,7 @@
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionCheckUpdate"/>
|
||||
<addaction name="actionSettings"/>
|
||||
<addaction name="actionManageAccounts"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionReportBug"/>
|
||||
<addaction name="actionNews"/>
|
||||
@ -465,6 +466,14 @@
|
||||
<string>Add a new instance.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionManageAccounts">
|
||||
<property name="text">
|
||||
<string>Manage Accounts</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Manage your Mojang or Minecraft accounts.</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources>
|
||||
|
112
gui/dialogs/AccountListDialog.cpp
Normal file
112
gui/dialogs/AccountListDialog.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "AccountListDialog.h"
|
||||
#include "ui_AccountListDialog.h"
|
||||
|
||||
#include <QItemSelectionModel>
|
||||
|
||||
#include <logger/QsLog.h>
|
||||
|
||||
#include <logic/auth/AuthenticateTask.h>
|
||||
|
||||
#include <gui/dialogs/LoginDialog.h>
|
||||
#include <gui/dialogs/ProgressDialog.h>
|
||||
|
||||
#include <MultiMC.h>
|
||||
|
||||
AccountListDialog::AccountListDialog(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::AccountListDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
m_accounts = MMC->accounts();
|
||||
// TODO: Make the "Active?" column show checkboxes or radio buttons.
|
||||
ui->listView->setModel(m_accounts.get());
|
||||
}
|
||||
|
||||
AccountListDialog::~AccountListDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
|
||||
void AccountListDialog::on_addAccountBtn_clicked()
|
||||
{
|
||||
doLogin("Please log in to add your account.");
|
||||
}
|
||||
|
||||
void AccountListDialog::on_rmAccountBtn_clicked()
|
||||
{
|
||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.size() > 0)
|
||||
{
|
||||
QModelIndex selected = selection.first();
|
||||
m_accounts->removeAccount(selected);
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListDialog::on_editAccountBtn_clicked()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void AccountListDialog::on_setActiveBtn_clicked()
|
||||
{
|
||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.size() > 0)
|
||||
{
|
||||
QModelIndex selected = selection.first();
|
||||
MojangAccountPtr account = selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>();
|
||||
m_accounts->setActiveAccount(account->username());
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListDialog::on_closeBtnBox_rejected()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void AccountListDialog::doLogin(const QString& errMsg)
|
||||
{
|
||||
// TODO: We can use the login dialog for this for now, but we'll have to make something better for it eventually.
|
||||
LoginDialog loginDialog(this);
|
||||
loginDialog.exec();
|
||||
|
||||
if (loginDialog.result() == QDialog::Accepted)
|
||||
{
|
||||
QString username(loginDialog.getUsername());
|
||||
QString password(loginDialog.getPassword());
|
||||
|
||||
MojangAccountPtr account = MojangAccountPtr(new MojangAccount(username));
|
||||
|
||||
ProgressDialog* progDialog = new ProgressDialog(this);
|
||||
m_authTask = new AuthenticateTask(account, password, progDialog);
|
||||
connect(m_authTask, SIGNAL(succeeded()), SLOT(onLoginComplete()), Qt::QueuedConnection);
|
||||
connect(m_authTask, SIGNAL(failed(QString)), SLOT(doLogin(QString)), Qt::QueuedConnection);
|
||||
progDialog->exec(m_authTask);
|
||||
//delete m_authTask;
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListDialog::onLoginComplete()
|
||||
{
|
||||
// Add the authenticated account to the accounts list.
|
||||
MojangAccountPtr account = m_authTask->getMojangAccount();
|
||||
m_accounts->addAccount(account);
|
||||
//ui->listView->update();
|
||||
}
|
||||
|
63
gui/dialogs/AccountListDialog.h
Normal file
63
gui/dialogs/AccountListDialog.h
Normal file
@ -0,0 +1,63 @@
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "logic/lists/MojangAccountList.h"
|
||||
|
||||
namespace Ui {
|
||||
class AccountListDialog;
|
||||
}
|
||||
|
||||
class AuthenticateTask;
|
||||
|
||||
class AccountListDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AccountListDialog(QWidget *parent = 0);
|
||||
~AccountListDialog();
|
||||
|
||||
public
|
||||
slots:
|
||||
void on_addAccountBtn_clicked();
|
||||
|
||||
void on_rmAccountBtn_clicked();
|
||||
|
||||
void on_editAccountBtn_clicked();
|
||||
|
||||
void on_setActiveBtn_clicked();
|
||||
|
||||
// This will be sent when the "close" button is clicked.
|
||||
void on_closeBtnBox_rejected();
|
||||
|
||||
protected:
|
||||
std::shared_ptr<MojangAccountList> m_accounts;
|
||||
|
||||
AuthenticateTask* m_authTask;
|
||||
|
||||
protected
|
||||
slots:
|
||||
void doLogin(const QString& errMsg="");
|
||||
void onLoginComplete();
|
||||
|
||||
private:
|
||||
Ui::AccountListDialog *ui;
|
||||
};
|
||||
|
93
gui/dialogs/AccountListDialog.ui
Normal file
93
gui/dialogs/AccountListDialog.ui
Normal file
@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AccountListDialog</class>
|
||||
<widget class="QDialog" name="AccountListDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="welcomeLabel">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Welcome! If you're new here, you can click the &quot;Add&quot; button to add your Mojang or Minecraft account.</p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QTreeView" name="listView"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="manageAcctsBtnBox">
|
||||
<item>
|
||||
<widget class="QPushButton" name="addAccountBtn">
|
||||
<property name="text">
|
||||
<string>&Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="editAccountBtn">
|
||||
<property name="text">
|
||||
<string>&Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="rmAccountBtn">
|
||||
<property name="text">
|
||||
<string>&Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="buttonSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="setActiveBtn">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Set the currently selected account as the active account. The active account is the account that is used to log in (unless it is overridden in an instance-specific setting).</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Set Active</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="closeBtnBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -22,7 +22,7 @@
|
||||
|
||||
#include "inifile.h"
|
||||
#include "lists/BaseVersionList.h"
|
||||
#include "net/LoginTask.h"
|
||||
#include "logic/auth/MojangAccount.h"
|
||||
|
||||
class QDialog;
|
||||
class Task;
|
||||
@ -153,8 +153,8 @@ public:
|
||||
/// returns a valid update task if update is needed, NULL otherwise
|
||||
virtual Task *doUpdate() = 0;
|
||||
|
||||
/// returns a valid minecraft process, ready for launch
|
||||
virtual MinecraftProcess *prepareForLaunch(LoginResponse response) = 0;
|
||||
/// returns a valid minecraft process, ready for launch with the given account.
|
||||
virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) = 0;
|
||||
|
||||
/// do any necessary cleanups after the instance finishes. also runs before
|
||||
/// 'prepareForLaunch'
|
||||
|
@ -38,6 +38,8 @@ void InstanceLauncher::onTerminated()
|
||||
|
||||
void InstanceLauncher::onLoginComplete()
|
||||
{
|
||||
// TODO: Fix this.
|
||||
/*
|
||||
LoginTask *task = (LoginTask *)QObject::sender();
|
||||
auto result = task->getResult();
|
||||
auto instance = MMC->instances()->getInstanceById(instId);
|
||||
@ -52,6 +54,7 @@ void InstanceLauncher::onLoginComplete()
|
||||
|
||||
proc->setLogin(result.username, result.session_id);
|
||||
proc->launch();
|
||||
*/
|
||||
}
|
||||
|
||||
void InstanceLauncher::doLogin(const QString &errorMsg)
|
||||
|
@ -50,7 +50,7 @@ Task *LegacyInstance::doUpdate()
|
||||
return new LegacyUpdate(this, this);
|
||||
}
|
||||
|
||||
MinecraftProcess *LegacyInstance::prepareForLaunch(LoginResponse response)
|
||||
MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account)
|
||||
{
|
||||
MinecraftProcess *proc = new MinecraftProcess(this);
|
||||
|
||||
@ -103,8 +103,8 @@ MinecraftProcess *LegacyInstance::prepareForLaunch(LoginResponse response)
|
||||
#endif
|
||||
|
||||
args << "-jar" << LAUNCHER_FILE;
|
||||
args << response.player_name;
|
||||
args << response.session_id;
|
||||
args << account->currentProfile()->name();
|
||||
args << account->sessionId();
|
||||
args << windowTitle;
|
||||
args << windowSize;
|
||||
args << lwjgl;
|
||||
|
@ -80,7 +80,7 @@ public:
|
||||
virtual void setShouldUpdate(bool val);
|
||||
virtual Task *doUpdate();
|
||||
|
||||
virtual MinecraftProcess *prepareForLaunch(LoginResponse response);
|
||||
virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account);
|
||||
virtual void cleanupAfterRun();
|
||||
virtual QDialog *createModEditDialog(QWidget *parent);
|
||||
|
||||
@ -93,4 +93,4 @@ public:
|
||||
protected
|
||||
slots:
|
||||
virtual void jarModsChanged();
|
||||
};
|
||||
};
|
||||
|
@ -66,7 +66,7 @@ QString replaceTokensIn(QString text, QMap<QString, QString> with)
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList OneSixInstance::processMinecraftArgs(LoginResponse response)
|
||||
QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account)
|
||||
{
|
||||
I_D(OneSixInstance);
|
||||
auto version = d->version;
|
||||
@ -74,11 +74,11 @@ QStringList OneSixInstance::processMinecraftArgs(LoginResponse response)
|
||||
|
||||
QMap<QString, QString> token_mapping;
|
||||
// yggdrasil!
|
||||
token_mapping["auth_username"] = response.username;
|
||||
token_mapping["auth_session"] = response.session_id;
|
||||
token_mapping["auth_access_token"] = response.access_token;
|
||||
token_mapping["auth_player_name"] = response.player_name;
|
||||
token_mapping["auth_uuid"] = response.player_id;
|
||||
token_mapping["auth_username"] = account->username();
|
||||
token_mapping["auth_session"] = account->sessionId();
|
||||
token_mapping["auth_access_token"] = account->accessToken();
|
||||
token_mapping["auth_player_name"] = account->currentProfile()->name();
|
||||
token_mapping["auth_uuid"] = account->currentProfile()->id();
|
||||
|
||||
// this is for offline?:
|
||||
/*
|
||||
@ -105,7 +105,7 @@ QStringList OneSixInstance::processMinecraftArgs(LoginResponse response)
|
||||
return parts;
|
||||
}
|
||||
|
||||
MinecraftProcess *OneSixInstance::prepareForLaunch(LoginResponse response)
|
||||
MinecraftProcess *OneSixInstance::prepareForLaunch(MojangAccountPtr account)
|
||||
{
|
||||
I_D(OneSixInstance);
|
||||
cleanupAfterRun();
|
||||
@ -177,7 +177,7 @@ MinecraftProcess *OneSixInstance::prepareForLaunch(LoginResponse response)
|
||||
args << classPath;
|
||||
}
|
||||
args << version->mainClass;
|
||||
args.append(processMinecraftArgs(response));
|
||||
args.append(processMinecraftArgs(account));
|
||||
|
||||
// Set the width and height for 1.6 instances
|
||||
bool maximize = settings().get("LaunchMaximized").toBool();
|
||||
|
@ -40,7 +40,8 @@ public:
|
||||
virtual QString instanceConfigFolder() const;
|
||||
|
||||
virtual Task *doUpdate();
|
||||
virtual MinecraftProcess *prepareForLaunch(LoginResponse response);
|
||||
virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account);
|
||||
|
||||
virtual void cleanupAfterRun();
|
||||
|
||||
virtual QString intendedVersionId() const;
|
||||
@ -72,5 +73,5 @@ public:
|
||||
virtual QString getStatusbarDescription();
|
||||
|
||||
private:
|
||||
QStringList processMinecraftArgs(LoginResponse response);
|
||||
};
|
||||
QStringList processMinecraftArgs(MojangAccountPtr account);
|
||||
};
|
||||
|
180
logic/auth/AuthenticateTask.cpp
Normal file
180
logic/auth/AuthenticateTask.cpp
Normal file
@ -0,0 +1,180 @@
|
||||
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <logic/auth/AuthenticateTask.h>
|
||||
|
||||
#include <logic/auth/MojangAccount.h>
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QVariant>
|
||||
#include <QDebug>
|
||||
|
||||
#include "logger/QsLog.h"
|
||||
|
||||
AuthenticateTask::AuthenticateTask(MojangAccountPtr account, const QString& password, QObject* parent) :
|
||||
YggdrasilTask(account, parent), m_password(password)
|
||||
{
|
||||
}
|
||||
|
||||
QJsonObject AuthenticateTask::getRequestContent() const
|
||||
{
|
||||
/*
|
||||
* {
|
||||
* "agent": { // optional
|
||||
* "name": "Minecraft", // So far this is the only encountered value
|
||||
* "version": 1 // This number might be increased
|
||||
* // by the vanilla client in the future
|
||||
* },
|
||||
* "username": "mojang account name", // Can be an email address or player name for
|
||||
// unmigrated accounts
|
||||
* "password": "mojang account password",
|
||||
* "clientToken": "client identifier" // optional
|
||||
* }
|
||||
*/
|
||||
QJsonObject req;
|
||||
|
||||
{
|
||||
QJsonObject agent;
|
||||
// C++ makes string literals void* for some stupid reason, so we have to tell it QString... Thanks Obama.
|
||||
agent.insert("name", QString("Minecraft"));
|
||||
agent.insert("version", 1);
|
||||
req.insert("agent", agent);
|
||||
}
|
||||
|
||||
req.insert("username", getMojangAccount()->username());
|
||||
req.insert("password", m_password);
|
||||
|
||||
// If we already have a client token, give it to the server.
|
||||
// Otherwise, let the server give us one.
|
||||
if (!getMojangAccount()->clientToken().isEmpty())
|
||||
req.insert("clientToken", getMojangAccount()->clientToken());
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
bool AuthenticateTask::processResponse(QJsonObject responseData)
|
||||
{
|
||||
// Read the response data. We need to get the client token, access token, and the selected profile.
|
||||
QLOG_DEBUG() << "Processing authentication response.";
|
||||
|
||||
// If we already have a client token, make sure the one the server gave us matches our existing one.
|
||||
QLOG_DEBUG() << "Getting client token.";
|
||||
QString clientToken = responseData.value("clientToken").toString("");
|
||||
if (clientToken.isEmpty())
|
||||
{
|
||||
// Fail if the server gave us an empty client token
|
||||
// TODO: Set an error properly to display to the user.
|
||||
QLOG_ERROR() << "Server didn't send a client token.";
|
||||
return false;
|
||||
}
|
||||
if (!getMojangAccount()->clientToken().isEmpty() && clientToken != getMojangAccount()->clientToken())
|
||||
{
|
||||
// The server changed our client token! Obey its wishes, but complain. That's what I do for my parents, so...
|
||||
QLOG_WARN() << "Server changed our client token to '" << clientToken
|
||||
<< "'. This shouldn't happen, but it isn't really a big deal.";
|
||||
}
|
||||
// Set the client token.
|
||||
getMojangAccount()->setClientToken(clientToken);
|
||||
|
||||
|
||||
// Now, we set the access token.
|
||||
QLOG_DEBUG() << "Getting access token.";
|
||||
QString accessToken = responseData.value("accessToken").toString("");
|
||||
if (accessToken.isEmpty())
|
||||
{
|
||||
// Fail if the server didn't give us an access token.
|
||||
// TODO: Set an error properly to display to the user.
|
||||
QLOG_ERROR() << "Server didn't send an access token.";
|
||||
}
|
||||
// Set the access token.
|
||||
getMojangAccount()->setAccessToken(accessToken);
|
||||
|
||||
|
||||
// Now we load the list of available profiles.
|
||||
// Mojang hasn't yet implemented the profile system,
|
||||
// but we might as well support what's there so we
|
||||
// don't have trouble implementing it later.
|
||||
QLOG_DEBUG() << "Loading profile list.";
|
||||
QJsonArray availableProfiles = responseData.value("availableProfiles").toArray();
|
||||
ProfileList loadedProfiles;
|
||||
for (auto iter : availableProfiles)
|
||||
{
|
||||
QJsonObject profile = iter.toObject();
|
||||
// Profiles are easy, we just need their ID and name.
|
||||
QString id = profile.value("id").toString("");
|
||||
QString name = profile.value("name").toString("");
|
||||
|
||||
if (id.isEmpty() || name.isEmpty())
|
||||
{
|
||||
// This should never happen, but we might as well
|
||||
// warn about it if it does so we can debug it easily.
|
||||
// You never know when Mojang might do something truly derpy.
|
||||
QLOG_WARN() << "Found entry in available profiles list with missing ID or name field. Ignoring it.";
|
||||
}
|
||||
|
||||
// Now, add a new AccountProfile entry to the list.
|
||||
loadedProfiles.append(AccountProfile(id, name));
|
||||
}
|
||||
// Put the list of profiles we loaded into the MojangAccount object.
|
||||
getMojangAccount()->loadProfiles(loadedProfiles);
|
||||
|
||||
|
||||
// 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
|
||||
// is actually in the available profiles list.
|
||||
// If it isn't, we'll just fail horribly (*shouldn't* ever happen, but you never know).
|
||||
QLOG_DEBUG() << "Setting current profile.";
|
||||
QJsonObject currentProfile = responseData.value("selectedProfile").toObject();
|
||||
QString currentProfileId = currentProfile.value("id").toString("");
|
||||
if (currentProfileId.isEmpty())
|
||||
{
|
||||
// TODO: Set an error to display to the user.
|
||||
QLOG_ERROR() << "Server didn't specify a currently selected profile.";
|
||||
return false;
|
||||
}
|
||||
if (!getMojangAccount()->setProfile(currentProfileId))
|
||||
{
|
||||
// TODO: Set an error to display to the user.
|
||||
QLOG_ERROR() << "Server specified a selected profile that wasn't in the available profiles list.";
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// We've made it through the minefield of possible errors. Return true to indicate that we've succeeded.
|
||||
QLOG_DEBUG() << "Finished reading authentication response.";
|
||||
return true;
|
||||
}
|
||||
|
||||
QString AuthenticateTask::getEndpoint() const
|
||||
{
|
||||
return "authenticate";
|
||||
}
|
||||
|
||||
QString AuthenticateTask::getStateMessage(const YggdrasilTask::State state) const
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case STATE_SENDING_REQUEST:
|
||||
return tr("Authenticating: Sending request.");
|
||||
case STATE_PROCESSING_RESPONSE:
|
||||
return tr("Authenticating: Processing response.");
|
||||
default:
|
||||
return YggdrasilTask::getStateMessage(state);
|
||||
}
|
||||
}
|
||||
|
46
logic/auth/AuthenticateTask.h
Normal file
46
logic/auth/AuthenticateTask.h
Normal file
@ -0,0 +1,46 @@
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <logic/auth/YggdrasilTask.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
|
||||
/**
|
||||
* The authenticate task takes a MojangAccount with no access token and password and attempts to authenticate with Mojang's servers.
|
||||
* If successful, it will set the MojangAccount's access token.
|
||||
*/
|
||||
class AuthenticateTask : public YggdrasilTask
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AuthenticateTask(MojangAccountPtr account, const QString& password, QObject* parent=0);
|
||||
|
||||
protected:
|
||||
virtual QJsonObject getRequestContent() const;
|
||||
|
||||
virtual QString getEndpoint() const;
|
||||
|
||||
virtual bool processResponse(QJsonObject responseData);
|
||||
|
||||
QString getStateMessage(const YggdrasilTask::State state) const;
|
||||
|
||||
private:
|
||||
QString m_password;
|
||||
};
|
||||
|
220
logic/auth/MojangAccount.cpp
Normal file
220
logic/auth/MojangAccount.cpp
Normal file
@ -0,0 +1,220 @@
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "MojangAccount.h"
|
||||
|
||||
#include <QUuid>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
#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)
|
||||
{
|
||||
// The JSON object must at least have a username for it to be valid.
|
||||
if (!object.value("username").isString())
|
||||
{
|
||||
QLOG_ERROR() << "Can't load Mojang account info from JSON object. Username field is missing or of the wrong type.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString username = object.value("username").toString("");
|
||||
QString clientToken = object.value("clientToken").toString("");
|
||||
QString accessToken = object.value("accessToken").toString("");
|
||||
|
||||
QJsonArray profileArray = object.value("profiles").toArray();
|
||||
if (profileArray.size() < 1)
|
||||
{
|
||||
QLOG_ERROR() << "Can't load Mojang account with username \"" << username << "\". No profiles found.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ProfileList profiles;
|
||||
for (QJsonValue profileVal : profileArray)
|
||||
{
|
||||
QJsonObject profileObject = profileVal.toObject();
|
||||
QString id = profileObject.value("id").toString("");
|
||||
QString name = profileObject.value("name").toString("");
|
||||
if (id.isEmpty() || name.isEmpty())
|
||||
{
|
||||
QLOG_WARN() << "Unable to load a profile because it was missing an ID or a name.";
|
||||
continue;
|
||||
}
|
||||
profiles.append(AccountProfile(id, name));
|
||||
}
|
||||
|
||||
MojangAccountPtr account(new MojangAccount(username, clientToken, accessToken));
|
||||
account->loadProfiles(profiles);
|
||||
|
||||
// Get the currently selected profile.
|
||||
QString currentProfile = object.value("activeProfile").toString("");
|
||||
if (!currentProfile.isEmpty())
|
||||
account->setProfile(currentProfile);
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
QJsonObject MojangAccount::saveToJson()
|
||||
{
|
||||
QJsonObject json;
|
||||
json.insert("username", username());
|
||||
json.insert("clientToken", clientToken());
|
||||
json.insert("accessToken", accessToken());
|
||||
|
||||
QJsonArray profileArray;
|
||||
for (AccountProfile profile : m_profiles)
|
||||
{
|
||||
QJsonObject profileObj;
|
||||
profileObj.insert("id", profile.id());
|
||||
profileObj.insert("name", profile.name());
|
||||
profileArray.append(profileObj);
|
||||
}
|
||||
json.insert("profiles", profileArray);
|
||||
|
||||
if (currentProfile() != nullptr)
|
||||
json.insert("activeProfile", currentProfile()->id());
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
|
||||
AccountProfile::AccountProfile(const QString& id, const QString& name)
|
||||
{
|
||||
m_id = id;
|
||||
m_name = name;
|
||||
}
|
||||
|
||||
AccountProfile::AccountProfile(const AccountProfile& other)
|
||||
{
|
||||
m_id = other.m_id;
|
||||
m_name = other.m_name;
|
||||
}
|
||||
|
||||
QString AccountProfile::id() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
QString AccountProfile::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
|
154
logic/auth/MojangAccount.h
Normal file
154
logic/auth/MojangAccount.h
Normal file
@ -0,0 +1,154 @@
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class MojangAccount;
|
||||
|
||||
typedef std::shared_ptr<MojangAccount> MojangAccountPtr;
|
||||
Q_DECLARE_METATYPE(MojangAccountPtr)
|
||||
|
||||
|
||||
/**
|
||||
* Class that represents a profile within someone's Mojang account.
|
||||
*
|
||||
* 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
|
||||
* we don't have to rip the code to pieces to add it later.
|
||||
*/
|
||||
class AccountProfile
|
||||
{
|
||||
public:
|
||||
AccountProfile(const QString& id, const QString& name);
|
||||
AccountProfile(const AccountProfile& other);
|
||||
|
||||
QString id() const;
|
||||
QString name() const;
|
||||
protected:
|
||||
QString m_id;
|
||||
QString m_name;
|
||||
};
|
||||
|
||||
|
||||
typedef QList<AccountProfile> ProfileList;
|
||||
|
||||
|
||||
/**
|
||||
* Object that stores information about a certain Mojang account.
|
||||
*
|
||||
* Said information may include things such as that account's username, client token, and access
|
||||
* token if the user chose to stay logged in.
|
||||
*/
|
||||
class MojangAccount : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* Constructs a new MojangAccount with the given username.
|
||||
* The client token will be generated automatically and the access token will be blank.
|
||||
*/
|
||||
explicit MojangAccount(const QString& username, QObject* parent = 0);
|
||||
|
||||
/**
|
||||
* Constructs a new MojangAccount with the given username, client token, and access token.
|
||||
*/
|
||||
explicit MojangAccount(const QString& username, const QString& clientToken, const QString& accessToken, QObject* parent = 0);
|
||||
|
||||
/**
|
||||
* Constructs a new MojangAccount matching the given account.
|
||||
*/
|
||||
MojangAccount(const MojangAccount& other, QObject* parent);
|
||||
|
||||
/**
|
||||
* Loads a MojangAccount from the given JSON object.
|
||||
*/
|
||||
static MojangAccountPtr loadFromJson(const QJsonObject& json);
|
||||
|
||||
/**
|
||||
* Saves a MojangAccount to a JSON object and returns it.
|
||||
*/
|
||||
QJsonObject saveToJson();
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
bool setProfile(const QString& profileId);
|
||||
|
||||
/**
|
||||
* Clears the current account profile list and replaces it with the given profile list.
|
||||
*/
|
||||
void loadProfiles(const ProfileList& profiles);
|
||||
|
||||
|
||||
protected:
|
||||
QString m_username;
|
||||
QString m_clientToken;
|
||||
QString m_accessToken; // Blank if not logged in.
|
||||
int m_currentProfile; // Index of the selected profile within the list of available profiles. -1 if nothing is selected.
|
||||
ProfileList m_profiles; // List of available profiles.
|
||||
};
|
||||
|
182
logic/auth/YggdrasilTask.cpp
Normal file
182
logic/auth/YggdrasilTask.cpp
Normal file
@ -0,0 +1,182 @@
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <logic/auth/YggdrasilTask.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QNetworkReply>
|
||||
#include <QByteArray>
|
||||
|
||||
#include <MultiMC.h>
|
||||
#include <logic/auth/MojangAccount.h>
|
||||
|
||||
YggdrasilTask::YggdrasilTask(MojangAccountPtr account, QObject* parent) : Task(parent)
|
||||
{
|
||||
m_error = nullptr;
|
||||
m_account = account;
|
||||
}
|
||||
|
||||
|
||||
YggdrasilTask::~YggdrasilTask()
|
||||
{
|
||||
if (m_error)
|
||||
delete m_error;
|
||||
}
|
||||
|
||||
void YggdrasilTask::executeTask()
|
||||
{
|
||||
setStatus(getStateMessage(STATE_SENDING_REQUEST));
|
||||
|
||||
// Get the content of the request we're going to send to the server.
|
||||
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");
|
||||
|
||||
m_netReply = worker->post(netRequest, doc.toJson());
|
||||
}
|
||||
|
||||
void YggdrasilTask::processReply(QNetworkReply* reply)
|
||||
{
|
||||
setStatus(getStateMessage(STATE_PROCESSING_RESPONSE));
|
||||
|
||||
if (m_netReply != reply)
|
||||
// Wrong reply for some reason...
|
||||
return;
|
||||
|
||||
// Check for errors.
|
||||
switch (reply->error())
|
||||
{
|
||||
case QNetworkReply::NoError:
|
||||
{
|
||||
// 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();
|
||||
|
||||
switch (responseCode)
|
||||
{
|
||||
case 200:
|
||||
{
|
||||
// If the response code was 200, then there shouldn't be an error. Make sure anyways.
|
||||
switch (jsonError.error)
|
||||
{
|
||||
case QJsonParseError::NoError:
|
||||
if (!processResponse(doc.object()))
|
||||
{
|
||||
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();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
emitFailed(tr("Failed to parse Yggdrasil JSON response: \"%1\".").arg(jsonError.errorString()));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// If the response code was something else, 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.
|
||||
switch (jsonError.error)
|
||||
{
|
||||
case 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.
|
||||
processError(doc.object());
|
||||
break;
|
||||
|
||||
default:
|
||||
// The server didn't say anything regarding the error. Give the user an unknown error.
|
||||
emitFailed(tr("Login failed: Unknown HTTP code %1 encountered.").arg(responseCode));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
emitFailed(tr("Login canceled."));
|
||||
break;
|
||||
|
||||
default:
|
||||
emitFailed(tr("An unknown error occurred when trying to communicate with the authentication server."));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QString YggdrasilTask::processError(QJsonObject responseData)
|
||||
{
|
||||
QJsonValue errorVal = responseData.value("error");
|
||||
QJsonValue msgVal = responseData.value("errorMessage");
|
||||
QJsonValue causeVal = responseData.value("cause");
|
||||
|
||||
if (errorVal.isString() && msgVal.isString() && causeVal.isString())
|
||||
{
|
||||
m_error = new Error(errorVal.toString(""), msgVal.toString(""), causeVal.toString(""));
|
||||
return m_error->getDisplayMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Error is not in standard format. Don't set m_error and return unknown error.
|
||||
return tr("An unknown Yggdrasil error occurred.");
|
||||
}
|
||||
}
|
||||
|
||||
QString YggdrasilTask::getStateMessage(const YggdrasilTask::State state) const
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case STATE_SENDING_REQUEST:
|
||||
return tr("Sending request to auth servers.");
|
||||
case STATE_PROCESSING_RESPONSE:
|
||||
return tr("Processing response from servers.");
|
||||
default:
|
||||
return tr("Processing. Please wait.");
|
||||
}
|
||||
}
|
||||
|
||||
YggdrasilTask::Error *YggdrasilTask::getError() const
|
||||
{
|
||||
return this->m_error;
|
||||
}
|
||||
|
||||
MojangAccountPtr YggdrasilTask::getMojangAccount() const
|
||||
{
|
||||
return this->m_account;
|
||||
}
|
||||
|
128
logic/auth/YggdrasilTask.h
Normal file
128
logic/auth/YggdrasilTask.h
Normal file
@ -0,0 +1,128 @@
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <logic/tasks/Task.h>
|
||||
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "logic/auth/MojangAccount.h"
|
||||
|
||||
class QNetworkReply;
|
||||
|
||||
|
||||
/**
|
||||
* A Yggdrasil task is a task that performs an operation on a given mojang account.
|
||||
*/
|
||||
class YggdrasilTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit YggdrasilTask(MojangAccountPtr account, QObject* parent=0);
|
||||
~YggdrasilTask();
|
||||
|
||||
/**
|
||||
* Class describing a Yggdrasil error response.
|
||||
*/
|
||||
class 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_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.
|
||||
* If no error has occurred, returns a null pointer.
|
||||
*/
|
||||
virtual Error* getError() const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Enum for describing the state of the current task.
|
||||
* Used by the getStateMessage function to determine what the status message should be.
|
||||
*/
|
||||
enum State
|
||||
{
|
||||
STATE_SENDING_REQUEST,
|
||||
STATE_PROCESSING_RESPONSE,
|
||||
STATE_OTHER,
|
||||
};
|
||||
|
||||
virtual void executeTask();
|
||||
|
||||
/**
|
||||
* Gets the JSON object that will be sent to the authentication server.
|
||||
* Should be overridden by subclasses.
|
||||
*/
|
||||
virtual QJsonObject getRequestContent() const = 0;
|
||||
|
||||
/**
|
||||
* Gets the endpoint to POST to.
|
||||
* No leading slash.
|
||||
*/
|
||||
virtual QString getEndpoint() const = 0;
|
||||
|
||||
/**
|
||||
* Processes the response received from the server.
|
||||
* If an error occurred, this should emit a failed signal and return false.
|
||||
* If Yggdrasil gave an error response, it should call setError() first, and then return false.
|
||||
* Otherwise, it should return true.
|
||||
*/
|
||||
virtual bool processResponse(QJsonObject responseData) = 0;
|
||||
|
||||
/**
|
||||
* Processes an error response received from the server.
|
||||
* The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error.
|
||||
* \returns a QString error message that will be passed to emitFailed.
|
||||
*/
|
||||
virtual QString processError(QJsonObject responseData);
|
||||
|
||||
/**
|
||||
* Returns the state message for the given state.
|
||||
* Used to set the status message for the task.
|
||||
* Should be overridden by subclasses that want to change messages for a given state.
|
||||
*/
|
||||
virtual QString getStateMessage(const State state) const;
|
||||
|
||||
MojangAccountPtr m_account;
|
||||
|
||||
QNetworkReply* m_netReply;
|
||||
|
||||
Error* m_error;
|
||||
|
||||
protected slots:
|
||||
void processReply(QNetworkReply* reply);
|
||||
};
|
||||
|
349
logic/lists/MojangAccountList.cpp
Normal file
349
logic/lists/MojangAccountList.cpp
Normal file
@ -0,0 +1,349 @@
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "logic/lists/MojangAccountList.h"
|
||||
|
||||
#include <QIODevice>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
|
||||
#include "logger/QsLog.h"
|
||||
|
||||
#include "logic/auth/MojangAccount.h"
|
||||
|
||||
#define ACCOUNT_LIST_FORMAT_VERSION 1
|
||||
|
||||
MojangAccountList::MojangAccountList(QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
MojangAccountPtr MojangAccountList::findAccount(const QString &username) const
|
||||
{
|
||||
for (int i = 0; i < count(); i++)
|
||||
{
|
||||
MojangAccountPtr account = at(i);
|
||||
if (account->username() == username)
|
||||
return account;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
const MojangAccountPtr MojangAccountList::at(int i) const
|
||||
{
|
||||
return MojangAccountPtr(m_accounts.at(i));
|
||||
}
|
||||
|
||||
void MojangAccountList::addAccount(const MojangAccountPtr account)
|
||||
{
|
||||
beginResetModel();
|
||||
m_accounts.append(account);
|
||||
endResetModel();
|
||||
onListChanged();
|
||||
}
|
||||
|
||||
void MojangAccountList::removeAccount(const QString& username)
|
||||
{
|
||||
beginResetModel();
|
||||
for (auto account : m_accounts)
|
||||
{
|
||||
if (account->username() == username)
|
||||
{
|
||||
m_accounts.removeOne(account);
|
||||
return;
|
||||
}
|
||||
}
|
||||
endResetModel();
|
||||
onListChanged();
|
||||
}
|
||||
|
||||
void MojangAccountList::removeAccount(QModelIndex index)
|
||||
{
|
||||
beginResetModel();
|
||||
m_accounts.removeAt(index.row());
|
||||
endResetModel();
|
||||
onListChanged();
|
||||
}
|
||||
|
||||
|
||||
MojangAccountPtr MojangAccountList::activeAccount() const
|
||||
{
|
||||
if (m_activeAccount.isEmpty())
|
||||
return nullptr;
|
||||
else
|
||||
return findAccount(m_activeAccount);
|
||||
}
|
||||
|
||||
void MojangAccountList::setActiveAccount(const QString& username)
|
||||
{
|
||||
beginResetModel();
|
||||
for (MojangAccountPtr account : m_accounts)
|
||||
if (account->username() == username)
|
||||
m_activeAccount = username;
|
||||
endResetModel();
|
||||
onListChanged();
|
||||
}
|
||||
|
||||
|
||||
void MojangAccountList::onListChanged()
|
||||
{
|
||||
if (m_autosave)
|
||||
// TODO: Alert the user if this fails.
|
||||
saveList();
|
||||
emit listChanged();
|
||||
}
|
||||
|
||||
|
||||
int MojangAccountList::count() const
|
||||
{
|
||||
return m_accounts.count();
|
||||
}
|
||||
|
||||
|
||||
QVariant MojangAccountList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
if (index.row() > count())
|
||||
return QVariant();
|
||||
|
||||
MojangAccountPtr account = at(index.row());
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
switch (index.column())
|
||||
{
|
||||
case ActiveColumn:
|
||||
return account->username() == m_activeAccount;
|
||||
|
||||
case NameColumn:
|
||||
return account->username();
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
return account->username();
|
||||
|
||||
case PointerRole:
|
||||
return qVariantFromValue(account);
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant MojangAccountList::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
switch (section)
|
||||
{
|
||||
case ActiveColumn:
|
||||
return "Active?";
|
||||
|
||||
case NameColumn:
|
||||
return "Name";
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
switch (section)
|
||||
{
|
||||
case NameColumn:
|
||||
return "The name of the version.";
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
int MojangAccountList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// Return count
|
||||
return count();
|
||||
}
|
||||
|
||||
int MojangAccountList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
void MojangAccountList::updateListData(QList<MojangAccountPtr> versions)
|
||||
{
|
||||
beginResetModel();
|
||||
m_accounts = versions;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
bool MojangAccountList::loadList(const QString& filePath)
|
||||
{
|
||||
QString path = filePath;
|
||||
if (path.isEmpty()) path = m_listFilePath;
|
||||
if (path.isEmpty())
|
||||
{
|
||||
QLOG_ERROR() << "Can't load Mojang account list. No file path given and no default set.";
|
||||
return false;
|
||||
}
|
||||
|
||||
QFile file(path);
|
||||
|
||||
// Try to open the file and fail if we can't.
|
||||
// TODO: We should probably report this error to the user.
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
QLOG_ERROR() << QString("Failed to read the account list file (%1).").arg(path).toUtf8();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the file and close it.
|
||||
QByteArray jsonData = file.readAll();
|
||||
file.close();
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
|
||||
|
||||
// Fail if the JSON is invalid.
|
||||
if (parseError.error != QJsonParseError::NoError)
|
||||
{
|
||||
QLOG_ERROR() << QString("Failed to parse account list file: %1 at offset %2")
|
||||
.arg(parseError.errorString(), QString::number(parseError.offset))
|
||||
.toUtf8();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the root is an object.
|
||||
if (!jsonDoc.isObject())
|
||||
{
|
||||
QLOG_ERROR() << "Invalid account list JSON: Root should be an array.";
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject root = jsonDoc.object();
|
||||
|
||||
// Make sure the format version matches.
|
||||
if (root.value("formatVersion").toVariant().toInt() != ACCOUNT_LIST_FORMAT_VERSION)
|
||||
{
|
||||
QString newName = "accounts-old.json";
|
||||
QLOG_WARN() << "Format version mismatch when loading account list. Existing one will be renamed to"
|
||||
<< newName;
|
||||
|
||||
// Attempt to rename the old version.
|
||||
file.rename(newName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now, load the accounts array.
|
||||
beginResetModel();
|
||||
QJsonArray accounts = root.value("accounts").toArray();
|
||||
for (QJsonValue accountVal : accounts)
|
||||
{
|
||||
QJsonObject accountObj = accountVal.toObject();
|
||||
MojangAccountPtr account = MojangAccount::loadFromJson(accountObj);
|
||||
if (account.get() != nullptr)
|
||||
{
|
||||
m_accounts.append(account);
|
||||
}
|
||||
else
|
||||
{
|
||||
QLOG_WARN() << "Failed to load an account.";
|
||||
}
|
||||
}
|
||||
endResetModel();
|
||||
|
||||
// Load the active account.
|
||||
m_activeAccount = root.value("activeAccount").toString("");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MojangAccountList::saveList(const QString& filePath)
|
||||
{
|
||||
QString path(filePath);
|
||||
if (path.isEmpty()) path = m_listFilePath;
|
||||
if (path.isEmpty())
|
||||
{
|
||||
QLOG_ERROR() << "Can't save Mojang account list. No file path given and no default set.";
|
||||
return false;
|
||||
}
|
||||
|
||||
QLOG_INFO() << "Writing account list to" << path;
|
||||
|
||||
QLOG_DEBUG() << "Building JSON data structure.";
|
||||
// Build the JSON document to write to the list file.
|
||||
QJsonObject root;
|
||||
|
||||
root.insert("formatVersion", ACCOUNT_LIST_FORMAT_VERSION);
|
||||
|
||||
// Build a list of accounts.
|
||||
QLOG_DEBUG() << "Building account array.";
|
||||
QJsonArray accounts;
|
||||
for (MojangAccountPtr account : m_accounts)
|
||||
{
|
||||
QJsonObject accountObj = account->saveToJson();
|
||||
accounts.append(accountObj);
|
||||
}
|
||||
|
||||
// Insert the account list into the root object.
|
||||
root.insert("accounts", accounts);
|
||||
|
||||
// Save the active account.
|
||||
root.insert("activeAccount", m_activeAccount);
|
||||
|
||||
// Create a JSON document object to convert our JSON to bytes.
|
||||
QJsonDocument doc(root);
|
||||
|
||||
|
||||
// Now that we're done building the JSON object, we can write it to the file.
|
||||
QLOG_DEBUG() << "Writing account list to file.";
|
||||
QFile file(path);
|
||||
|
||||
// Try to open the file and fail if we can't.
|
||||
// TODO: We should probably report this error to the user.
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
{
|
||||
QLOG_ERROR() << QString("Failed to read the account list file (%1).").arg(path).toUtf8();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write the JSON to the file.
|
||||
file.write(doc.toJson());
|
||||
file.close();
|
||||
|
||||
QLOG_INFO() << "Saved account list to" << path;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MojangAccountList::setListFilePath(QString path, bool autosave)
|
||||
{
|
||||
m_listFilePath = path;
|
||||
autosave = autosave;
|
||||
}
|
||||
|
175
logic/lists/MojangAccountList.h
Normal file
175
logic/lists/MojangAccountList.h
Normal file
@ -0,0 +1,175 @@
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
#include <QAbstractListModel>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "logic/auth/MojangAccount.h"
|
||||
|
||||
|
||||
/*!
|
||||
* \brief List of available Mojang accounts.
|
||||
* This should be loaded in the background by MultiMC on startup.
|
||||
*
|
||||
* This class also inherits from QAbstractListModel. Methods from that
|
||||
* class determine how this list shows up in a list view. Said methods
|
||||
* all have a default implementation, but they can be overridden by subclasses to
|
||||
* change the behavior of the list.
|
||||
*/
|
||||
class MojangAccountList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ModelRoles
|
||||
{
|
||||
PointerRole = 0x34B1CB48
|
||||
};
|
||||
|
||||
enum VListColumns
|
||||
{
|
||||
// TODO: Add icon column.
|
||||
|
||||
// First column - Active?
|
||||
ActiveColumn = 0,
|
||||
|
||||
// Second column - Name
|
||||
NameColumn,
|
||||
};
|
||||
|
||||
explicit MojangAccountList(QObject *parent = 0);
|
||||
|
||||
//! Gets the account at the given index.
|
||||
virtual const MojangAccountPtr at(int i) const;
|
||||
|
||||
//! Returns the number of accounts in the list.
|
||||
virtual int count() const;
|
||||
|
||||
//////// List Model Functions ////////
|
||||
virtual QVariant data(const QModelIndex &index, int role) const;
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
|
||||
virtual int rowCount(const QModelIndex &parent) const;
|
||||
virtual int columnCount(const QModelIndex &parent) const;
|
||||
|
||||
/*!
|
||||
* Adds a the given Mojang account to the account list.
|
||||
*/
|
||||
virtual void addAccount(const MojangAccountPtr account);
|
||||
|
||||
/*!
|
||||
* Removes the mojang account with the given username from the account list.
|
||||
*/
|
||||
virtual void removeAccount(const QString& username);
|
||||
|
||||
/*!
|
||||
* Removes the account at the given QModelIndex.
|
||||
*/
|
||||
virtual void removeAccount(QModelIndex index);
|
||||
|
||||
/*!
|
||||
* \brief Finds an account by its username.
|
||||
* \param The username of the account to find.
|
||||
* \return A const pointer to the account with the given username. NULL if
|
||||
* one doesn't exist.
|
||||
*/
|
||||
virtual MojangAccountPtr findAccount(const QString &username) const;
|
||||
|
||||
/*!
|
||||
* Sets the default path to save the list file to.
|
||||
* If autosave is true, this list will automatically save to the given path whenever it changes.
|
||||
* THIS FUNCTION DOES NOT LOAD THE LIST. If you set autosave, be sure to call loadList() immediately
|
||||
* after calling this function to ensure an autosaved change doesn't overwrite the list you intended
|
||||
* to load.
|
||||
*/
|
||||
virtual void setListFilePath(QString path, bool autosave=false);
|
||||
|
||||
/*!
|
||||
* \brief Loads the account list from the given file path.
|
||||
* If the given file is an empty string (default), will load from the default account list file.
|
||||
* \return True if successful, otherwise false.
|
||||
*/
|
||||
virtual bool loadList(const QString& file="");
|
||||
|
||||
/*!
|
||||
* \brief Saves the account list to the given file.
|
||||
* If the given file is an empty string (default), will save from the default account list file.
|
||||
* \return True if successful, otherwise false.
|
||||
*/
|
||||
virtual bool saveList(const QString& file="");
|
||||
|
||||
/*!
|
||||
* \brief Gets a pointer to the account that the user has selected as their "active" account.
|
||||
* Which account is active can be overridden on a per-instance basis, but this will return the one that
|
||||
* is set as active globally.
|
||||
* \return The currently active MojangAccount. If there isn't an active account, returns a null pointer.
|
||||
*/
|
||||
virtual MojangAccountPtr activeAccount() const;
|
||||
|
||||
/*!
|
||||
* Sets the given account as the current active account.
|
||||
*/
|
||||
virtual void setActiveAccount(const QString& username);
|
||||
|
||||
signals:
|
||||
/*!
|
||||
* Signal emitted to indicate that the account list has changed.
|
||||
* This will also fire if the value of an element in the list changes (will be implemented later).
|
||||
*/
|
||||
void listChanged();
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Called whenever the list changes.
|
||||
* This emits the listChanged() signal and autosaves the list (if autosave is enabled).
|
||||
*/
|
||||
void onListChanged();
|
||||
|
||||
QList<MojangAccountPtr> m_accounts;
|
||||
|
||||
/*!
|
||||
* Username of the account that is currently active.
|
||||
* Empty string if no account is active.
|
||||
*/
|
||||
QString m_activeAccount;
|
||||
|
||||
//! Path to the account list file. Empty string if there isn't one.
|
||||
QString m_listFilePath;
|
||||
|
||||
/*!
|
||||
* If true, the account list will automatically save to the account list path when it changes.
|
||||
* Ignored if m_listFilePath is blank.
|
||||
*/
|
||||
bool m_autosave;
|
||||
|
||||
protected
|
||||
slots:
|
||||
/*!
|
||||
* Updates this list with the given list of accounts.
|
||||
* This is done by copying each account in the given list and inserting it
|
||||
* into this one.
|
||||
* We need to do this so that we can set the parents of the accounts are set to this
|
||||
* account list. This can't be done in the load task, because the accounts the load
|
||||
* task creates are on the load task's thread and Qt won't allow their parents
|
||||
* to be set to something created on another thread.
|
||||
* To get around that problem, we invoke this method on the GUI thread, which
|
||||
* then copies the accounts and sets their parents correctly.
|
||||
* \param accounts List of accounts whose parents should be set.
|
||||
*/
|
||||
virtual void updateListData(QList<MojangAccountPtr> versions);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user