summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt4
-rw-r--r--gui/ConsoleWindow.cpp15
-rw-r--r--gui/ConsoleWindow.h2
-rw-r--r--gui/ConsoleWindow.ui7
-rw-r--r--gui/MainWindow.cpp100
-rw-r--r--gui/MainWindow.h9
-rw-r--r--gui/dialogs/AccountListDialog.cpp18
-rw-r--r--gui/dialogs/AccountSelectDialog.cpp2
-rw-r--r--gui/dialogs/ProgressDialog.cpp26
-rw-r--r--gui/dialogs/ProgressDialog.h6
-rw-r--r--gui/dialogs/ProgressDialog.ui21
-rw-r--r--logic/BaseInstance.h2
-rw-r--r--logic/LegacyInstance.cpp6
-rw-r--r--logic/LegacyInstance.h2
-rw-r--r--logic/LegacyUpdate.cpp22
-rw-r--r--logic/LegacyUpdate.h4
-rw-r--r--logic/MinecraftProcess.cpp22
-rw-r--r--logic/OneSixInstance.cpp8
-rw-r--r--logic/OneSixInstance.h2
-rw-r--r--logic/OneSixUpdate.cpp54
-rw-r--r--logic/OneSixUpdate.h8
-rw-r--r--logic/auth/MojangAccount.cpp202
-rw-r--r--logic/auth/MojangAccount.h199
-rw-r--r--logic/auth/YggdrasilTask.cpp168
-rw-r--r--logic/auth/YggdrasilTask.h62
-rw-r--r--logic/auth/flows/AuthenticateTask.cpp23
-rw-r--r--logic/auth/flows/AuthenticateTask.h2
-rw-r--r--logic/auth/flows/InvalidateTask.cpp0
-rw-r--r--logic/auth/flows/InvalidateTask.h0
-rw-r--r--logic/auth/flows/RefreshTask.cpp22
-rw-r--r--logic/auth/flows/RefreshTask.h2
-rw-r--r--logic/auth/flows/ValidateTask.cpp4
-rw-r--r--logic/auth/flows/ValidateTask.h6
-rw-r--r--logic/lists/MojangAccountList.cpp24
-rw-r--r--logic/lists/MojangAccountList.h5
-rw-r--r--logic/net/NetJob.h2
-rw-r--r--logic/net/PasteUpload.cpp84
-rw-r--r--logic/net/PasteUpload.h26
-rw-r--r--logic/tasks/ProgressProvider.h1
-rw-r--r--logic/tasks/Task.h1
40 files changed, 654 insertions, 519 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 62876595..2c27ca81 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -300,6 +300,8 @@ logic/net/HttpMetaCache.h
logic/net/HttpMetaCache.cpp
logic/net/S3ListBucket.h
logic/net/S3ListBucket.cpp
+logic/net/PasteUpload.h
+logic/net/PasteUpload.cpp
# Yggdrasil login stuff
logic/auth/MojangAccount.h
@@ -312,8 +314,6 @@ logic/auth/flows/RefreshTask.cpp
logic/auth/flows/RefreshTask.cpp
logic/auth/flows/ValidateTask.h
logic/auth/flows/ValidateTask.cpp
-logic/auth/flows/InvalidateTask.h
-logic/auth/flows/InvalidateTask.cpp
# legacy instances
logic/LegacyInstance.h
diff --git a/gui/ConsoleWindow.cpp b/gui/ConsoleWindow.cpp
index d0210df6..5db4442e 100644
--- a/gui/ConsoleWindow.cpp
+++ b/gui/ConsoleWindow.cpp
@@ -22,6 +22,9 @@
#include <gui/Platform.h>
#include <gui/dialogs/CustomMessageBox.h>
+#include <gui/dialogs/ProgressDialog.h>
+
+#include "logic/net/PasteUpload.h"
ConsoleWindow::ConsoleWindow(MinecraftProcess *mcproc, QWidget *parent)
: QMainWindow(parent), ui(new Ui::ConsoleWindow), proc(mcproc)
@@ -179,3 +182,15 @@ void ConsoleWindow::onLaunchFailed(BaseInstance *instance)
if(!isVisible())
show();
}
+
+void ConsoleWindow::on_btnPaste_clicked()
+{
+ auto text = ui->text->toPlainText();
+ ProgressDialog dialog(this);
+ PasteUpload* paste=new PasteUpload(this, text);
+ dialog.exec(paste);
+ if(!paste->successful())
+ {
+ CustomMessageBox::selectable(this, "Upload failed", paste->failReason(), QMessageBox::Critical)->exec();
+ }
+}
diff --git a/gui/ConsoleWindow.h b/gui/ConsoleWindow.h
index 2d948484..731c616c 100644
--- a/gui/ConsoleWindow.h
+++ b/gui/ConsoleWindow.h
@@ -76,6 +76,8 @@ slots:
// FIXME: add handlers for the other MinecraftProcess signals (pre/post launch command
// failures)
+ void on_btnPaste_clicked();
+
protected:
void closeEvent(QCloseEvent *);
diff --git a/gui/ConsoleWindow.ui b/gui/ConsoleWindow.ui
index ed1b627b..62cc89ac 100644
--- a/gui/ConsoleWindow.ui
+++ b/gui/ConsoleWindow.ui
@@ -48,6 +48,13 @@
<number>6</number>
</property>
<item>
+ <widget class="QPushButton" name="btnPaste">
+ <property name="text">
+ <string>Upload Log</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp
index 26e7b27b..b080f610 100644
--- a/gui/MainWindow.cpp
+++ b/gui/MainWindow.cpp
@@ -69,10 +69,6 @@
#include "logic/lists/IconList.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/InstanceFactory.h"
#include "logic/MinecraftProcess.h"
@@ -212,9 +208,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
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(
- QUrl("http://skins.minecraft.net/MinecraftSkins/" + profile.name() + ".png"),
+ QUrl("http://skins.minecraft.net/MinecraftSkins/" + profile.name + ".png"),
meta);
job->addNetAction(action);
meta->stale = true;
@@ -314,9 +310,9 @@ void MainWindow::repopulateAccountsMenu()
section->setEnabled(false);
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->setCheckable(true);
if(active_username == account->username())
@@ -324,7 +320,7 @@ void MainWindow::repopulateAccountsMenu()
action->setChecked(true);
}
- action->setIcon(SkinUtils::getFaceFromCache(profile.name()));
+ action->setIcon(SkinUtils::getFaceFromCache(profile.name));
accountMenu->addAction(action);
connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount()));
}
@@ -382,7 +378,7 @@ void MainWindow::activeAccountChanged()
const AccountProfile *profile = account->currentProfile();
if (profile != nullptr)
{
- accountMenuButton->setIcon(SkinUtils::getFaceFromCache(profile->name()));
+ accountMenuButton->setIcon(SkinUtils::getFaceFromCache(profile->name));
return;
}
}
@@ -785,79 +781,56 @@ void MainWindow::doLaunch()
accounts->setActiveAccount(account->username());
}
- if (account.get() != nullptr)
- {
- doLaunchInst(m_selectedInstance, 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.
- ProgressDialog progDialog(this);
- RefreshTask refreshtask(account, &progDialog);
- progDialog.exec(&refreshtask);
+ // if no account is selected, we bail
+ if (!account.get())
+ return;
- if (refreshtask.successful())
- {
- prepareLaunch(m_selectedInstance, account);
- }
- else
+ // do the login. if the account has an access token, try to refresh it first.
+ if(account->accountStatus() != NotVerified)
{
- YggdrasilTask::Error *error = refreshtask.getError();
+ // We'll need to validate the access token to make sure the account is still logged in.
+ ProgressDialog progDialog(this);
+ progDialog.setSkipButton(true, tr("Play Offline"));
+ auto task = account->login();
+ progDialog.exec(task.get());
- if (error != nullptr)
- {
- if (error->getErrorMessage().contains("invalid token", Qt::CaseInsensitive))
- {
- // TODO: Allow the user to enter their password and "refresh" their access token.
- if (doRefreshToken(account, tr("Your account's access token is invalid. Please enter your password to log in again.")))
- doLaunchInst(instance, account);
- }
- else
- {
- CustomMessageBox::selectable(
- this, tr("Access Token Validation Error"),
- tr("There was an error when trying to validate your access token.\n"
- "Details: %s").arg(error->getDisplayMessage()),
- QMessageBox::Warning, QMessageBox::Ok)->exec();
- }
- }
- else
+ auto status = account->accountStatus();
+ if(status != NotVerified)
{
- CustomMessageBox::selectable(
- this, tr("Access Token Validation Error"),
- tr("There was an unknown error when trying to validate your access token."
- "The authentication server might be down, or you might not be connected to "
- "the Internet."),
- QMessageBox::Warning, QMessageBox::Ok)->exec();
+ updateInstance(m_selectedInstance, account);
}
+ // revert from online to verified.
+ account->downgrade();
+ return;
}
+ if (loginWithPassword(account, tr("Your account is currently not logged in. Please enter your password to log in again.")))
+ updateInstance(m_selectedInstance, account);
}
-bool MainWindow::doRefreshToken(MojangAccountPtr account, const QString& errorMsg)
+bool MainWindow::loginWithPassword(MojangAccountPtr account, const QString& errorMsg)
{
EditAccountDialog passDialog(errorMsg, this, EditAccountDialog::PasswordField);
if (passDialog.exec() == QDialog::Accepted)
{
// To refresh the token, we just create an authenticate task with the given account and the user's password.
ProgressDialog progDialog(this);
- AuthenticateTask authTask(account, passDialog.password(), &progDialog);
- progDialog.exec(&authTask);
- if (authTask.successful())
+ auto task = account->login(passDialog.password());
+ progDialog.exec(task.get());
+ if(task->successful())
return true;
else
{
// If the authentication task failed, recurse with the task's error message.
- return doRefreshToken(account, authTask.failReason());
+ return loginWithPassword(account, task->failReason());
}
}
- else return false;
+ return false;
}
-void MainWindow::prepareLaunch(BaseInstance* instance, MojangAccountPtr account)
+void MainWindow::updateInstance(BaseInstance* instance, MojangAccountPtr account)
{
- Task *updateTask = instance->doUpdate(true);
+ bool only_prepare = account->accountStatus() != Online;
+ auto updateTask = instance->doUpdate(only_prepare);
if (!updateTask)
{
launchInstance(instance, account);
@@ -865,10 +838,9 @@ void MainWindow::prepareLaunch(BaseInstance* instance, MojangAccountPtr account)
else
{
ProgressDialog tDialog(this);
- connect(updateTask, &Task::succeeded, [this, instance, account] { launchInstance(instance, account); });
- connect(updateTask, SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString)));
- tDialog.exec(updateTask);
- delete updateTask;
+ connect(updateTask.get(), &Task::succeeded, [this, instance, account] { launchInstance(instance, account); });
+ connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString)));
+ tDialog.exec(updateTask.get());
}
}
diff --git a/gui/MainWindow.h b/gui/MainWindow.h
index 59cfa3c9..62c9797e 100644
--- a/gui/MainWindow.h
+++ b/gui/MainWindow.h
@@ -110,18 +110,13 @@ slots:
* If no default account is selected, prompts the user to pick an account.
*/
void doLaunch();
-
- /*!
- * Launches the given instance with the given account.
- */
- void doLaunchInst(BaseInstance* instance, MojangAccountPtr account);
/*!
* Opens an input dialog, allowing the user to input their password and refresh its access token.
* This function will execute the proper Yggdrasil task to refresh the access token.
* Returns true if successful. False if the user cancelled.
*/
- bool doRefreshToken(MojangAccountPtr account, const QString& errorMsg="");
+ bool loginWithPassword(MojangAccountPtr account, const QString& errorMsg="");
/*!
* Launches the given instance with the given account.
@@ -132,7 +127,7 @@ slots:
/*!
* Prepares the given instance for launch with the given account.
*/
- void prepareLaunch(BaseInstance* instance, MojangAccountPtr account);
+ void updateInstance(BaseInstance* instance, MojangAccountPtr account);
void onGameUpdateError(QString error);
diff --git a/gui/dialogs/AccountListDialog.cpp b/gui/dialogs/AccountListDialog.cpp
index f5268b61..8dae5f07 100644
--- a/gui/dialogs/AccountListDialog.cpp
+++ b/gui/dialogs/AccountListDialog.cpp
@@ -20,12 +20,12 @@
#include <logger/QsLog.h>
-#include <logic/auth/flows/AuthenticateTask.h>
#include <logic/net/NetJob.h>
#include <gui/dialogs/EditAccountDialog.h>
#include <gui/dialogs/ProgressDialog.h>
#include <gui/dialogs/AccountSelectDialog.h>
+#include <logic/tasks/Task.h>
#include <MultiMC.h>
@@ -117,24 +117,24 @@ void AccountListDialog::addAccount(const QString& errMsg)
QString username(loginDialog.username());
QString password(loginDialog.password());
- MojangAccountPtr account = MojangAccountPtr(new MojangAccount(username));
-
+ MojangAccountPtr account = MojangAccount::createFromUsername(username);
ProgressDialog progDialog(this);
- AuthenticateTask authTask(account, password, &progDialog);
- if (progDialog.exec(&authTask))
+ auto task = account->login(password);
+ progDialog.exec(task.get());
+ if(task->successful())
{
- // Add the authenticated account to the accounts list.
- MojangAccountPtr account = authTask.getMojangAccount();
m_accounts->addAccount(account);
+ if (m_accounts->count() == 1)
+ m_accounts->setActiveAccount(account->username());
// Grab associated player skins
auto job = new NetJob("Player skins: " + account->username());
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(
- QUrl("http://skins.minecraft.net/MinecraftSkins/" + profile.name() + ".png"),
+ QUrl("http://skins.minecraft.net/MinecraftSkins/" + profile.name + ".png"),
meta);
job->addNetAction(action);
meta->stale = true;
diff --git a/gui/dialogs/AccountSelectDialog.cpp b/gui/dialogs/AccountSelectDialog.cpp
index b8fa9e42..ec2f09be 100644
--- a/gui/dialogs/AccountSelectDialog.cpp
+++ b/gui/dialogs/AccountSelectDialog.cpp
@@ -20,8 +20,6 @@
#include <logger/QsLog.h>
-#include <logic/auth/flows/AuthenticateTask.h>
-
#include <gui/dialogs/ProgressDialog.h>
#include <MultiMC.h>
diff --git a/gui/dialogs/ProgressDialog.cpp b/gui/dialogs/ProgressDialog.cpp
index ca433dab..ba14cca2 100644
--- a/gui/dialogs/ProgressDialog.cpp
+++ b/gui/dialogs/ProgressDialog.cpp
@@ -25,9 +25,23 @@ ProgressDialog::ProgressDialog(QWidget *parent) : QDialog(parent), ui(new Ui::Pr
{
MultiMCPlatform::fixWM_CLASS(this);
ui->setupUi(this);
+ this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
+ setSkipButton(false);
+ changeProgress(0, 100);
+}
+
+void ProgressDialog::setSkipButton(bool present, QString label)
+{
+ ui->skipButton->setEnabled(present);
+ ui->skipButton->setVisible(present);
+ ui->skipButton->setText(label);
updateSize();
+}
- changeProgress(0, 100);
+void ProgressDialog::on_skipButton_clicked(bool checked)
+{
+ Q_UNUSED(checked);
+ task->abort();
}
ProgressDialog::~ProgressDialog()
@@ -51,9 +65,13 @@ int ProgressDialog::exec(ProgressProvider *task)
connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString &)));
connect(task, SIGNAL(progress(qint64, qint64)), SLOT(changeProgress(qint64, qint64)));
- // this makes sure that the task is started after the dialog is created
- QMetaObject::invokeMethod(task, "start", Qt::QueuedConnection);
- return QDialog::exec();
+ // if this didn't connect to an already running task, invoke start
+ if(!task->isRunning())
+ task->start();
+ if(task->isRunning())
+ return QDialog::exec();
+ else
+ return 0;
}
ProgressProvider *ProgressDialog::getTask()
diff --git a/gui/dialogs/ProgressDialog.h b/gui/dialogs/ProgressDialog.h
index 0029d3ec..fe63a826 100644
--- a/gui/dialogs/ProgressDialog.h
+++ b/gui/dialogs/ProgressDialog.h
@@ -35,6 +35,7 @@ public:
void updateSize();
int exec(ProgressProvider *task);
+ void setSkipButton(bool present, QString label = QString());
ProgressProvider *getTask();
@@ -47,7 +48,10 @@ slots:
void changeStatus(const QString &status);
void changeProgress(qint64 current, qint64 total);
-signals:
+
+private
+slots:
+ void on_skipButton_clicked(bool checked);
protected:
virtual void keyPressEvent(QKeyEvent *e);
diff --git a/gui/dialogs/ProgressDialog.ui b/gui/dialogs/ProgressDialog.ui
index a56d2a92..04b8fef3 100644
--- a/gui/dialogs/ProgressDialog.ui
+++ b/gui/dialogs/ProgressDialog.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>400</width>
- <height>68</height>
+ <height>100</height>
</rect>
</property>
<property name="minimumSize">
@@ -25,8 +25,8 @@
<property name="windowTitle">
<string>Please wait...</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
<widget class="QLabel" name="statusLabel">
<property name="text">
<string>Task Status...</string>
@@ -36,7 +36,7 @@
</property>
</widget>
</item>
- <item>
+ <item row="1" column="0">
<widget class="QProgressBar" name="taskProgressBar">
<property name="value">
<number>24</number>
@@ -46,6 +46,19 @@
</property>
</widget>
</item>
+ <item row="2" column="0">
+ <widget class="QPushButton" name="skipButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Skip</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
<resources/>
diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h
index 2d7537d6..93e57414 100644
--- a/logic/BaseInstance.h
+++ b/logic/BaseInstance.h
@@ -150,7 +150,7 @@ public:
virtual SettingsObject &settings() const;
/// returns a valid update task if update is needed, NULL otherwise
- virtual Task *doUpdate(bool prepare_for_launch) = 0;
+ virtual std::shared_ptr<Task> doUpdate(bool only_prepare) = 0;
/// returns a valid minecraft process, ready for launch with the given account.
virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) = 0;
diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp
index 72b6c51a..fef27bcd 100644
--- a/logic/LegacyInstance.cpp
+++ b/logic/LegacyInstance.cpp
@@ -44,12 +44,12 @@ LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings,
settings->registerSetting(new Setting("IntendedJarVersion", ""));
}
-Task *LegacyInstance::doUpdate(bool prepare_for_launch)
+std::shared_ptr<Task> LegacyInstance::doUpdate(bool only_prepare)
{
// make sure the jar mods list is initialized by asking for it.
auto list = jarModList();
// create an update task
- return new LegacyUpdate(this, prepare_for_launch , this);
+ return std::shared_ptr<Task> (new LegacyUpdate(this, only_prepare , this));
}
MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account)
@@ -105,7 +105,7 @@ MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account)
#endif
args << "-jar" << LAUNCHER_FILE;
- args << account->currentProfile()->name();
+ args << account->currentProfile()->name;
args << account->sessionId();
args << windowTitle;
args << windowSize;
diff --git a/logic/LegacyInstance.h b/logic/LegacyInstance.h
index a17ef281..1e7d9eb6 100644
--- a/logic/LegacyInstance.h
+++ b/logic/LegacyInstance.h
@@ -76,7 +76,7 @@ public:
virtual bool shouldUpdate() const override;
virtual void setShouldUpdate(bool val) override;
- virtual Task *doUpdate(bool prepare_for_launch) override;
+ virtual std::shared_ptr<Task> doUpdate(bool only_prepare) override;
virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override;
virtual void cleanupAfterRun() override;
diff --git a/logic/LegacyUpdate.cpp b/logic/LegacyUpdate.cpp
index 3fc17351..6125101b 100644
--- a/logic/LegacyUpdate.cpp
+++ b/logic/LegacyUpdate.cpp
@@ -26,14 +26,30 @@
#include <JlCompress.h>
#include "logger/QsLog.h"
-LegacyUpdate::LegacyUpdate(BaseInstance *inst, bool prepare_for_launch, QObject *parent)
- : Task(parent), m_inst(inst), m_prepare_for_launch(prepare_for_launch)
+LegacyUpdate::LegacyUpdate(BaseInstance *inst, bool only_prepare, QObject *parent)
+ : Task(parent), m_inst(inst), m_only_prepare(only_prepare)
{
}
void LegacyUpdate::executeTask()
{
- lwjglStart();
+ if(m_only_prepare)
+ {
+ // FIXME: think this through some more.
+ LegacyInstance *inst = (LegacyInstance *)m_inst;
+ if (!inst->shouldUpdate() || inst->shouldUseCustomBaseJar())
+ {
+ ModTheJar();
+ }
+ else
+ {
+ emitSucceeded();
+ }
+ }
+ else
+ {
+ lwjglStart();
+ }
}
void LegacyUpdate::lwjglStart()
diff --git a/logic/LegacyUpdate.h b/logic/LegacyUpdate.h
index d753197f..0b573ca5 100644
--- a/logic/LegacyUpdate.h
+++ b/logic/LegacyUpdate.h
@@ -31,7 +31,7 @@ class LegacyUpdate : public Task
{
Q_OBJECT
public:
- explicit LegacyUpdate(BaseInstance *inst, bool prepare_for_launch, QObject *parent = 0);
+ explicit LegacyUpdate(BaseInstance *inst, bool only_prepare, QObject *parent = 0);
virtual void executeTask();
private
@@ -72,5 +72,5 @@ private:
private:
NetJobPtr legacyDownloadJob;
BaseInstance *m_inst = nullptr;
- bool m_prepare_for_launch = false;
+ bool m_only_prepare = false;
};
diff --git a/logic/MinecraftProcess.cpp b/logic/MinecraftProcess.cpp
index 5d99bfae..209929b7 100644
--- a/logic/MinecraftProcess.cpp
+++ b/logic/MinecraftProcess.cpp
@@ -75,20 +75,22 @@ QString MinecraftProcess::censorPrivateInfo(QString in)
{
if(!m_account)
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 accessToken = m_account->accessToken();
- 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>");
+ QString profileId = profile->id;
+ QString profileName = profile->name;
in.replace(profileId, "<PROFILE ID>");
in.replace(profileName, "<PROFILE NAME>");
- return in;
}
+ return in;
}
// console window
diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp
index a6b439a1..e804de11 100644
--- a/logic/OneSixInstance.cpp
+++ b/logic/OneSixInstance.cpp
@@ -39,9 +39,9 @@ OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_o
reloadFullVersion();
}
-Task *OneSixInstance::doUpdate(bool prepare_for_launch)
+std::shared_ptr<Task> OneSixInstance::doUpdate(bool only_prepare)
{
- return new OneSixUpdate(this, prepare_for_launch);
+ return std::shared_ptr<Task> (new OneSixUpdate(this, only_prepare));
}
QString replaceTokensIn(QString text, QMap<QString, QString> with)
@@ -136,8 +136,8 @@ QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account)
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();
+ token_mapping["auth_player_name"] = account->currentProfile()->name;
+ token_mapping["auth_uuid"] = account->currentProfile()->id;
// this is for offline?:
/*
diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h
index cc98d822..f869e345 100644
--- a/logic/OneSixInstance.h
+++ b/logic/OneSixInstance.h
@@ -40,7 +40,7 @@ public:
QString loaderModsDir() const;
virtual QString instanceConfigFolder() const override;
- virtual Task *doUpdate(bool prepare_for_launch) override;
+ virtual std::shared_ptr<Task> doUpdate(bool only_prepare) override;
virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override;
virtual void cleanupAfterRun() override;
diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp
index 25e16328..7be0c056 100644
--- a/logic/OneSixUpdate.cpp
+++ b/logic/OneSixUpdate.cpp
@@ -33,8 +33,8 @@
#include "pathutils.h"
#include <JlCompress.h>
-OneSixUpdate::OneSixUpdate(BaseInstance *inst, bool prepare_for_launch, QObject *parent)
- : Task(parent), m_inst(inst), m_prepare_for_launch(prepare_for_launch)
+OneSixUpdate::OneSixUpdate(BaseInstance *inst, bool only_prepare, QObject *parent)
+ : Task(parent), m_inst(inst), m_only_prepare(only_prepare)
{
}
@@ -50,6 +50,23 @@ void OneSixUpdate::executeTask()
return;
}
+ if(m_only_prepare)
+ {
+ if (m_inst->shouldUpdate())
+ {
+ emitFailed("Unable to update instance in offline mode.");
+ return;
+ }
+ setStatus("Testing the Java installation.");
+ QString java_path = m_inst->settings().get("JavaPath").toString();
+
+ checker.reset(new JavaChecker());
+ connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this,
+ SLOT(checkFinishedOffline(JavaCheckResult)));
+ checker->performCheck(java_path);
+ return;
+ }
+
if (m_inst->shouldUpdate())
{
// Get a pointer to the version object that corresponds to the instance's version.
@@ -65,35 +82,43 @@ void OneSixUpdate::executeTask()
}
else
{
- checkJava();
+ checkJavaOnline();
}
}
-void OneSixUpdate::checkJava()
+void OneSixUpdate::checkJavaOnline()
{
- QLOG_INFO() << m_inst->name() << ": checking java binary";
setStatus("Testing the Java installation.");
- // TODO: cache this so we don't have to run an extra java process every time.
QString java_path = m_inst->settings().get("JavaPath").toString();
checker.reset(new JavaChecker());
connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this,
- SLOT(checkFinished(JavaCheckResult)));
+ SLOT(checkFinishedOnline(JavaCheckResult)));
checker->performCheck(java_path);
}
-void OneSixUpdate::checkFinished(JavaCheckResult result)
+void OneSixUpdate::checkFinishedOnline(JavaCheckResult result)
{
if (result.valid)
{
- QLOG_INFO() << m_inst->name() << ": java is "
- << (result.is_64bit ? "64 bit" : "32 bit");
java_is_64bit = result.is_64bit;
jarlibStart();
}
else
{
- QLOG_INFO() << m_inst->name() << ": java isn't valid";
+ emitFailed("The java binary doesn't work. Check the settings and correct the problem");
+ }
+}
+
+void OneSixUpdate::checkFinishedOffline(JavaCheckResult result)
+{
+ if (result.valid)
+ {
+ java_is_64bit = result.is_64bit;
+ prepareForLaunch();
+ }
+ else
+ {
emitFailed("The java binary doesn't work. Check the settings and correct the problem");
}
}
@@ -160,7 +185,7 @@ void OneSixUpdate::versionFileFinished()
}
inst->reloadFullVersion();
- checkJava();
+ checkJavaOnline();
}
void OneSixUpdate::versionFileFailed()
@@ -240,10 +265,7 @@ void OneSixUpdate::jarlibStart()
void OneSixUpdate::jarlibFinished()
{
- if (m_prepare_for_launch)
- prepareForLaunch();
- else
- emitSucceeded();
+ prepareForLaunch();
}
void OneSixUpdate::jarlibFailed()
diff --git a/logic/OneSixUpdate.h b/logic/OneSixUpdate.h
index b86c205f..7ff9d881 100644
--- a/logic/OneSixUpdate.h
+++ b/logic/OneSixUpdate.h
@@ -43,11 +43,13 @@ slots:
void jarlibFinished();
void jarlibFailed();
- void checkJava();
- void checkFinished(JavaCheckResult result);
+ void checkJavaOnline();
+ void checkFinishedOnline(JavaCheckResult result);
+ void checkFinishedOffline(JavaCheckResult result);
// extract the appropriate libraries
void prepareForLaunch();
+
private:
NetJobPtr specificVersionDownloadJob;
NetJobPtr jarlibDownloadJob;
@@ -55,7 +57,7 @@ private:
// target version, determined during this task
std::shared_ptr<MinecraftVersion> targetVersion;
BaseInstance *m_inst = nullptr;
- bool m_prepare_for_launch = false;
+ bool m_only_prepare = false;
std::shared_ptr<JavaChecker> checker;
bool java_is_64bit = false;
diff --git a/logic/auth/MojangAccount.cpp b/logic/auth/MojangAccount.cpp
index 4a61cf19..b1acfb25 100644
--- a/logic/auth/MojangAccount.cpp
+++ b/logic/auth/MojangAccount.cpp
@@ -16,113 +16,16 @@
*/
#include "MojangAccount.h"
+#include "flows/RefreshTask.h"
+#include "flows/AuthenticateTask.h"
#include <QUuid>
#include <QJsonObject>
#include <QJsonArray>
+#include <QRegExp>
#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.
@@ -143,7 +46,7 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
return nullptr;
}
- ProfileList profiles;
+ QList<AccountProfile> profiles;
for (QJsonValue profileVal : profileArray)
{
QJsonObject profileObject = profileVal.toObject();
@@ -154,67 +57,116 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
QLOG_WARN() << "Unable to load a profile because it was missing an ID or a name.";
continue;
}
- profiles.append(AccountProfile(id, name));
+ profiles.append({id, name});
}
- MojangAccountPtr account(new MojangAccount(username, clientToken, accessToken));
- account->loadProfiles(profiles);
+ MojangAccountPtr account(new MojangAccount());
+ account->m_username = username;
+ account->m_clientToken = clientToken;
+ account->m_accessToken = accessToken;
+ account->m_profiles = profiles;
// Get the currently selected profile.
QString currentProfile = object.value("activeProfile").toString("");
if (!currentProfile.isEmpty())
- account->setProfile(currentProfile);
+ account->setCurrentProfile(currentProfile);
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;
- json.insert("username", username());
- json.insert("clientToken", clientToken());
- json.insert("accessToken", accessToken());
+ json.insert("username", m_username);
+ json.insert("clientToken", m_clientToken);
+ json.insert("accessToken", m_accessToken);
QJsonArray profileArray;
for (AccountProfile profile : m_profiles)
{
QJsonObject profileObj;
- profileObj.insert("id", profile.id());
- profileObj.insert("name", profile.name());
+ 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());
+ if (m_currentProfile != -1)
+ json.insert("activeProfile", currentProfile()->id);
return json;
}
-
-AccountProfile::AccountProfile(const QString& id, const QString& name)
+bool MojangAccount::setCurrentProfile(const QString &profileId)
{
- m_id = id;
- m_name = name;
+ for (int i = 0; i < m_profiles.length(); i++)
+ {
+ 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;
- m_name = other.m_name;
+ if(m_currentProfile == -1)
+ 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
+std::shared_ptr<Task> MojangAccount::login(QString password)
{
- return m_name;
+ if(m_currentTask)
+ return m_currentTask;
+ if(password.isEmpty())
+ {
+ m_currentTask.reset(new RefreshTask(this));
+ }
+ else
+ {
+ m_currentTask.reset(new AuthenticateTask(this, password));
+ }
+ connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
+ connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
+ return m_currentTask;
}
-void MojangAccount::propagateChange()
+void MojangAccount::authSucceeded()
{
+ m_online = true;
+ m_currentTask.reset();
emit changed();
}
+
+void MojangAccount::authFailed(QString reason)
+{
+ // This is emitted when the yggdrasil tasks time out or are cancelled.
+ // -> we treat the error as no-op
+ if(reason != "Yggdrasil task cancelled.")
+ {
+ m_online = false;
+ m_accessToken = QString();
+ emit changed();
+ }
+ m_currentTask.reset();
+}
diff --git a/logic/auth/MojangAccount.h b/logic/auth/MojangAccount.h
index 25a85790..95f777ce 100644
--- a/logic/auth/MojangAccount.h
+++ b/logic/auth/MojangAccount.h
@@ -23,34 +23,26 @@
#include <memory>
+class Task;
+class YggdrasilTask;
class MojangAccount;
typedef std::shared_ptr<MojangAccount> 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,
* 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
+struct 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;
+ QString id;
+ QString name;
};
-typedef QList<AccountProfile> ProfileList;
-
struct User
{
QString id;
@@ -59,6 +51,13 @@ struct User
QList<QPair<QString, QString>> properties;
};
+enum AccountStatus
+{
+ NotVerified,
+ Verified,
+ Online
+};
+
/**
* Object that stores information about a certain Mojang account.
*
@@ -68,106 +67,116 @@ struct User
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);
+public: /* construction */
+ //! Do not copy accounts. ever.
+ explicit MojangAccount(const MojangAccount &other, QObject *parent) = delete;
- /**
- * 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);
+ //! Default constructor
+ explicit MojangAccount(QObject *parent = 0) : QObject(parent) {};
- /**
- * Constructs a new MojangAccount matching the given account.
- */
- MojangAccount(const MojangAccount &other, QObject *parent);
+ //! Creates an empty account for the specified user name.
+ static MojangAccountPtr createFromUsername(const QString &username);
- /**
- * Loads a MojangAccount from the given JSON object.
- */
+ //! 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();
+ //! Saves a MojangAccount to a JSON object and returns it.
+ QJsonObject saveToJson() const;
+public: /* manipulation */
/**
- * Update the account on disk and lists (it changed, for whatever reason)
- * This is called by various Yggdrasil tasks.
- */
- void propagateChange();
+ * 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 setCurrentProfile(const QString &profileId);
+
+ /**
+ * Attempt to login. Empty password means we use the token.
+ * If the attempt fails because we already are performing some task, it returns false.
+ */
+ std::shared_ptr<Task> login(QString password = QString());
+
+ void downgrade()
+ {
+ m_online = false;
+ }
+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;
- /**
- * This MojangAccount's username. May be an email address if the account is migrated.
- */
- QString username() const;
+ //! Returns whether the account is NotVerified, Verified or Online
+ AccountStatus accountStatus() const;
+signals:
/**
- * 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.
+ * This signal is emitted when the account changes
*/
- QString clientToken() const;
+ void changed();
- /**
- * Sets the MojangAccount's client token to the given value.
- */
- void setClientToken(const QString &token);
+ // TODO: better signalling for the various possible state changes - especially errors
- /**
- * This MojangAccount's access token.
- * If the user has not chosen to stay logged in, this will be an empty string.
- */
- QString accessToken() const;
+protected: /* variables */
+ QString m_username;
- /**
- * Changes this MojangAccount's access token to the given value.
- */
- void setAccessToken(const QString &token);
+ // 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;
- /**
- * Get full session ID
- */
- QString sessionId() const;
+ // Blank if not logged in.
+ QString m_accessToken;
- /**
- * Returns a list of the available account profiles.
- */
- const ProfileList profiles() const;
+ // Index of the selected profile within the list of available
+ // profiles. -1 if nothing is selected.
+ int m_currentProfile = -1;
- /**
- * 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;
+ // List of available profiles.
+ QList<AccountProfile> m_profiles;
- /**
- * 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);
+ // the user structure, whatever it is.
+ User m_user;
- /**
- * Clears the current account profile list and replaces it with the given profile list.
- */
- void loadProfiles(const ProfileList &profiles);
+ // true when the account is verified
+ bool m_online = false;
-signals:
- /**
- * This isgnal is emitted whrn the account changes
- */
- void changed();
+ // current task we are executing here
+ std::shared_ptr<YggdrasilTask> m_currentTask;
-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.
- User m_user; // the user structure, whatever it is.
+private slots:
+ void authSucceeded();
+ void authFailed(QString reason);
+
+public:
+ friend class YggdrasilTask;
+ friend class AuthenticateTask;
+ friend class ValidateTask;
+ friend class RefreshTask;
};
diff --git a/logic/auth/YggdrasilTask.cpp b/logic/auth/YggdrasilTask.cpp
index 797e84cd..45155058 100644
--- a/logic/auth/YggdrasilTask.cpp
+++ b/logic/auth/YggdrasilTask.cpp
@@ -25,16 +25,9 @@
#include <MultiMC.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()
-{
- if (m_error)
- delete m_error;
}
void YggdrasilTask::executeTask()
@@ -45,97 +38,126 @@ void YggdrasilTask::executeTask()
QJsonDocument doc(getRequestContent());
auto worker = MMC->qnam();
- connect(worker.get(), SIGNAL(finished(QNetworkReply *)), this,
- SLOT(processReply(QNetworkReply *)));
-
QUrl reqUrl("https://authserver.mojang.com/" + getEndpoint());
QNetworkRequest netRequest(reqUrl);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QByteArray requestData = doc.toJson();
m_netReply = worker->post(netRequest, requestData);
+ connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply);
+ connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers);
+ connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers);
+ timeout_keeper.setSingleShot(true);
+ timeout_keeper.start(timeout_max);
+ counter.setSingleShot(false);
+ counter.start(time_step);
+ progress(0, timeout_max);
+ connect(&timeout_keeper, &QTimer::timeout, this, &YggdrasilTask::abort);
+ connect(&counter, &QTimer::timeout, this, &YggdrasilTask::heartbeat);
}
-void YggdrasilTask::processReply(QNetworkReply *reply)
+void YggdrasilTask::refreshTimers(qint64, qint64)
{
- setStatus(getStateMessage(STATE_PROCESSING_RESPONSE));
+ timeout_keeper.stop();
+ timeout_keeper.start(timeout_max);
+ progress(count = 0, timeout_max);
+}
+void YggdrasilTask::heartbeat()
+{
+ count += time_step;
+ progress(count, timeout_max);
+}
- if (m_netReply != reply)
- // Wrong reply for some reason...
- return;
+void YggdrasilTask::abort()
+{
+ progress(timeout_max, timeout_max);
+ m_netReply->abort();
+}
- if (reply->error() == QNetworkReply::OperationCanceledError)
+void YggdrasilTask::processReply()
+{
+ setStatus(getStateMessage(STATE_PROCESSING_RESPONSE));
+
+ // any network errors lead to offline mode right now
+ if (m_netReply->error() >= QNetworkReply::ConnectionRefusedError &&
+ m_netReply->error() <= QNetworkReply::UnknownNetworkError)
{
+ // WARNING/FIXME: the value here is used in MojangAccount to detect the cancel/timeout
emitFailed("Yggdrasil task cancelled.");
return;
}
- else
+
+ // Try to parse the response regardless of the response code.
+ // Sometimes the auth server will give more information and an error code.
+ QJsonParseError jsonError;
+ QByteArray replyData = m_netReply->readAll();
+ QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError);
+ // Check the response code.
+ int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
+ if (responseCode == 200)
{
- // Try to parse the response regardless of the response code.
- // Sometimes the auth server will give more information and an error code.
- QJsonParseError jsonError;
- QByteArray replyData = reply->readAll();
- QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError);
- // Check the response code.
- int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (responseCode == 200)
+ // If the response code was 200, then there shouldn't be an error. Make sure
+ // anyways.
+ // Also, sometimes an empty reply indicates success. If there was no data received,
+ // pass an empty json object to the processResponse function.
+ if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0)
{
- // If the response code was 200, then there shouldn't be an error. Make sure anyways.
- // Also, sometimes an empty reply indicates success. If there was no data received,
- // pass an empty json object to the processResponse function.
- if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0)
- {
- if (!processResponse(replyData.size() > 0 ? doc.object() : QJsonObject()))
- {
- YggdrasilTask::Error *err = getError();
- if (err)
- emitFailed(err->getErrorMessage());
- else
- emitFailed(tr("An unknown error occurred when processing the response "
- "from the authentication server."));
- }
- else
- {
- emitSucceeded();
- }
- }
- else
+ if (processResponse(replyData.size() > 0 ? doc.object() : QJsonObject()))
{
- emitFailed(tr("Failed to parse Yggdrasil JSON response: %1 at offset %2.").arg(jsonError.errorString()).arg(jsonError.offset));
+ emitSucceeded();
+ return;
}
+
+ // errors happened anyway?
+ emitFailed(m_error ? m_error->m_errorMessageVerbose
+ : tr("An unknown error occurred when processing the response "
+ "from the authentication server."));
}
else
{
- // If the response code was not 200, then Yggdrasil may have given us information about the error.
- // If we can parse the response, then get information from it. Otherwise just say there was an unknown error.
- if (jsonError.error == QJsonParseError::NoError)
- {
- // We were able to parse the server's response. Woo!
- // Call processError. If a subclass has overridden it then they'll handle their stuff there.
- QLOG_DEBUG() << "The request failed, but the server gave us an error message. Processing error.";
- emitFailed(processError(doc.object()));
- }
- else
- {
- // The server didn't say anything regarding the error. Give the user an unknown error.
- QLOG_DEBUG() << "The request failed and the server gave no error message. Unknown error.";
- emitFailed(tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(reply->errorString()));
- }
+ emitFailed(tr("Failed to parse Yggdrasil JSON response: %1 at offset %2.")
+ .arg(jsonError.errorString())
+ .arg(jsonError.offset));
}
+ return;
+ }
+
+ // If the response code was not 200, then Yggdrasil may have given us information
+ // about the error.
+ // If we can parse the response, then get information from it. Otherwise just say
+ // there was an unknown error.
+ if (jsonError.error == QJsonParseError::NoError)
+ {
+ // We were able to parse the server's response. Woo!
+ // Call processError. If a subclass has overridden it then they'll handle their
+ // stuff there.
+ QLOG_DEBUG() << "The request failed, but the server gave us an error message. "
+ "Processing error.";
+ emitFailed(processError(doc.object()));
+ }
+ else
+ {
+ // The server didn't say anything regarding the error. Give the user an unknown
+ // error.
+ QLOG_DEBUG() << "The request failed and the server gave no error message. "
+ "Unknown error.";
+ emitFailed(tr("An unknown error occurred when trying to communicate with the "
+ "authentication server: %1").arg(m_netReply->errorString()));
}
}
QString YggdrasilTask::processError(QJsonObject responseData)
{
QJsonValue errorVal = responseData.value("error");
- QJsonValue msgVal = responseData.value("errorMessage");
+ QJsonValue errorMessageValue = responseData.value("errorMessage");
QJsonValue causeVal = responseData.value("cause");
- if (errorVal.isString() && msgVal.isString())
+ if (errorVal.isString() && errorMessageValue.isString())
{
- m_error = new Error(errorVal.toString(""), msgVal.toString(""), causeVal.toString(""));
- return m_error->getDisplayMessage();
+ m_error = std::shared_ptr<Error>(new Error{
+ errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")});
+ return m_error->m_errorMessageVerbose;
}
else
{
@@ -156,13 +178,3 @@ QString YggdrasilTask::getStateMessage(const YggdrasilTask::State state) const
return tr("Processing. Please wait.");
}
}
-
-YggdrasilTask::Error *YggdrasilTask::getError() const
-{
- return this->m_error;
-}
-
-MojangAccountPtr YggdrasilTask::getMojangAccount() const
-{
- return this->m_account;
-}
diff --git a/logic/auth/YggdrasilTask.h b/logic/auth/YggdrasilTask.h
index 62638c9d..1f81a2d0 100644
--- a/logic/auth/YggdrasilTask.h
+++ b/logic/auth/YggdrasilTask.h
@@ -19,6 +19,7 @@
#include <QString>
#include <QJsonObject>
+#include <QTimer>
#include "logic/auth/MojangAccount.h"
@@ -31,45 +32,18 @@ class YggdrasilTask : public Task
{
Q_OBJECT
public:
- explicit YggdrasilTask(MojangAccountPtr account, QObject *parent = 0);
- ~YggdrasilTask();
+ explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0);
/**
* Class describing a Yggdrasil error response.
*/
- class Error
+ struct Error
{
- public:
- Error(const QString& shortError, const QString& errorMessage, const QString& cause) :
- m_shortError(shortError), m_errorMessage(errorMessage), m_cause(cause) {}
-
- QString getShortError() const { return m_shortError; }
- QString getErrorMessage() const { return m_errorMessage; }
- QString getCause() const { return m_cause; }
-
- /// Gets the string to display in the GUI for describing this error.
- QString getDisplayMessage()
- {
- return getErrorMessage();
- }
-
- protected:
- QString m_shortError;
- QString m_errorMessage;
+ QString m_errorMessageShort;
+ QString m_errorMessageVerbose;
QString m_cause;
};
- /**
- * 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.
@@ -120,13 +94,25 @@ protected:
*/
virtual QString getStateMessage(const State state) const;
- MojangAccountPtr m_account;
-
- QNetworkReply *m_netReply;
-
- Error *m_error;
-
protected
slots:
- void processReply(QNetworkReply *reply);
+ void processReply();
+ void refreshTimers(qint64, qint64);
+ void heartbeat();
+
+public
+slots:
+ virtual void abort() override;
+
+protected:
+ // FIXME: segfault disaster waiting to happen
+ MojangAccount *m_account = nullptr;
+ QNetworkReply *m_netReply = nullptr;
+ std::shared_ptr<Error> m_error;
+ QTimer timeout_keeper;
+ QTimer counter;
+ int count = 0; // num msec since time reset
+
+ const int timeout_max = 10000;
+ const int time_step = 50;
};
diff --git a/logic/auth/flows/AuthenticateTask.cpp b/logic/auth/flows/AuthenticateTask.cpp
index ec2004d6..966548ec 100644
--- a/logic/auth/flows/AuthenticateTask.cpp
+++ b/logic/auth/flows/AuthenticateTask.cpp
@@ -26,7 +26,7 @@
#include "logger/QsLog.h"
-AuthenticateTask::AuthenticateTask(MojangAccountPtr account, const QString &password,
+AuthenticateTask::AuthenticateTask(MojangAccount * account, const QString &password,
QObject *parent)
: YggdrasilTask(account, parent), m_password(password)
{
@@ -59,14 +59,14 @@ QJsonObject AuthenticateTask::getRequestContent() const
req.insert("agent", agent);
}
- req.insert("username", getMojangAccount()->username());
+ req.insert("username", m_account->username());
req.insert("password", m_password);
req.insert("requestUser", true);
// 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());
+ if (!m_account->m_clientToken.isEmpty())
+ req.insert("clientToken", m_account->m_clientToken);
return req;
}
@@ -88,8 +88,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData)
QLOG_ERROR() << "Server didn't send a client token.";
return false;
}
- if (!getMojangAccount()->clientToken().isEmpty() &&
- clientToken != getMojangAccount()->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
// 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.";
}
// Set the client token.
- getMojangAccount()->setClientToken(clientToken);
+ m_account->m_clientToken = clientToken;
// Now, we set the 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.";
}
// Set the access token.
- getMojangAccount()->setAccessToken(accessToken);
+ m_account->m_accessToken = accessToken;
// Now we load the list of available profiles.
// Mojang hasn't yet implemented the profile system,
@@ -117,7 +116,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData)
// don't have trouble implementing it later.
QLOG_DEBUG() << "Loading profile list.";
QJsonArray availableProfiles = responseData.value("availableProfiles").toArray();
- ProfileList loadedProfiles;
+ QList<AccountProfile> loadedProfiles;
for (auto iter : availableProfiles)
{
QJsonObject profile = iter.toObject();
@@ -135,10 +134,10 @@ bool AuthenticateTask::processResponse(QJsonObject responseData)
}
// 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.
- getMojangAccount()->loadProfiles(loadedProfiles);
+ m_account->m_profiles = 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
@@ -153,7 +152,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData)
QLOG_ERROR() << "Server didn't specify a currently selected profile.";
return false;
}
- if (!getMojangAccount()->setProfile(currentProfileId))
+ if (!m_account->setCurrentProfile(currentProfileId))
{
// TODO: Set an error to display to the user.
QLOG_ERROR() << "Server specified a selected profile that wasn't in the available "
diff --git a/logic/auth/flows/AuthenticateTask.h b/logic/auth/flows/AuthenticateTask.h
index 3b99caad..b6564657 100644
--- a/logic/auth/flows/AuthenticateTask.h
+++ b/logic/auth/flows/AuthenticateTask.h
@@ -30,7 +30,7 @@ class AuthenticateTask : public YggdrasilTask
{
Q_OBJECT
public:
- AuthenticateTask(MojangAccountPtr account, const QString &password, QObject *parent = 0);
+ AuthenticateTask(MojangAccount *account, const QString &password, QObject *parent = 0);
protected:
virtual QJsonObject getRequestContent() const;
diff --git a/logic/auth/flows/InvalidateTask.cpp b/logic/auth/flows/InvalidateTask.cpp
deleted file mode 100644
index e69de29b..00000000
--- a/logic/auth/flows/InvalidateTask.cpp
+++ /dev/null
diff --git a/logic/auth/flows/InvalidateTask.h b/logic/auth/flows/InvalidateTask.h
deleted file mode 100644
index e69de29b..00000000
--- a/logic/auth/flows/InvalidateTask.h
+++ /dev/null
diff --git a/logic/auth/flows/RefreshTask.cpp b/logic/auth/flows/RefreshTask.cpp
index b56ed9bc..bd38eb10 100644
--- a/logic/auth/flows/RefreshTask.cpp
+++ b/logic/auth/flows/RefreshTask.cpp
@@ -25,7 +25,7 @@
#include "logger/QsLog.h"
-RefreshTask::RefreshTask(MojangAccountPtr account, QObject *parent)
+RefreshTask::RefreshTask(MojangAccount *account, QObject *parent)
: YggdrasilTask(account, parent)
{
}
@@ -44,13 +44,12 @@ QJsonObject RefreshTask::getRequestContent() const
* "requestUser": true/false // request the user structure
* }
*/
- auto account = getMojangAccount();
QJsonObject req;
- req.insert("clientToken", account->clientToken());
- req.insert("accessToken", account->accessToken());
+ req.insert("clientToken", m_account->m_clientToken);
+ req.insert("accessToken", m_account->m_accessToken);
/*
{
- auto currentProfile = account->currentProfile();
+ auto currentProfile = m_account->currentProfile();
QJsonObject profile;
profile.insert("id", currentProfile->id());
profile.insert("name", currentProfile->name());
@@ -64,8 +63,6 @@ QJsonObject RefreshTask::getRequestContent() const
bool RefreshTask::processResponse(QJsonObject responseData)
{
- auto account = getMojangAccount();
-
// Read the response data. We need to get the client token, access token, and the selected
// profile.
QLOG_DEBUG() << "Processing authentication response.";
@@ -80,7 +77,7 @@ bool RefreshTask::processResponse(QJsonObject responseData)
QLOG_ERROR() << "Server didn't send a client token.";
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
// for my parents, so...
@@ -104,7 +101,7 @@ bool RefreshTask::processResponse(QJsonObject responseData)
// profile)
QJsonObject currentProfile = responseData.value("selectedProfile").toObject();
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.
QLOG_ERROR() << "Server didn't specify the same selected profile as ours.";
@@ -132,8 +129,7 @@ bool RefreshTask::processResponse(QJsonObject responseData)
// we've succeeded.
QLOG_DEBUG() << "Finished reading refresh response.";
// Reset the access token.
- account->setAccessToken(accessToken);
- account->propagateChange();
+ m_account->m_accessToken = accessToken;
return true;
}
@@ -147,9 +143,9 @@ QString RefreshTask::getStateMessage(const YggdrasilTask::State state) const
switch (state)
{
case STATE_SENDING_REQUEST:
- return tr("Refreshing: Sending request.");
+ return tr("Refreshing login token.");
case STATE_PROCESSING_RESPONSE:
- return tr("Refreshing: Processing response.");
+ return tr("Refreshing login token: Processing response.");
default:
return YggdrasilTask::getStateMessage(state);
}
diff --git a/logic/auth/flows/RefreshTask.h b/logic/auth/flows/RefreshTask.h
index 2596f6c7..2fd50c60 100644
--- a/logic/auth/flows/RefreshTask.h
+++ b/logic/auth/flows/RefreshTask.h
@@ -30,7 +30,7 @@ class RefreshTask : public YggdrasilTask
{
Q_OBJECT
public:
- RefreshTask(MojangAccountPtr account, QObject *parent = 0);
+ RefreshTask(MojangAccount * account, QObject *parent = 0);
protected:
virtual QJsonObject getRequestContent() const;
diff --git a/logic/auth/flows/ValidateTask.cpp b/logic/auth/flows/ValidateTask.cpp
index d9e0e46b..84d5e703 100644
--- a/logic/auth/flows/ValidateTask.cpp
+++ b/logic/auth/flows/ValidateTask.cpp
@@ -26,7 +26,7 @@
#include "logger/QsLog.h"
-ValidateTask::ValidateTask(MojangAccountPtr account, QObject *parent)
+ValidateTask::ValidateTask(MojangAccount * account, QObject *parent)
: YggdrasilTask(account, parent)
{
}
@@ -34,7 +34,7 @@ ValidateTask::ValidateTask(MojangAccountPtr account, QObject *parent)
QJsonObject ValidateTask::getRequestContent() const
{
QJsonObject req;
- req.insert("accessToken", getMojangAccount()->accessToken());
+ req.insert("accessToken", m_account->m_accessToken);
return req;
}
diff --git a/logic/auth/flows/ValidateTask.h b/logic/auth/flows/ValidateTask.h
index 3ff78c6a..0e34f0c3 100644
--- a/logic/auth/flows/ValidateTask.h
+++ b/logic/auth/flows/ValidateTask.h
@@ -13,6 +13,10 @@
* limitations under the License.
*/
+/*
+ * :FIXME: DEAD CODE, DEAD CODE, DEAD CODE! :FIXME:
+ */
+
#pragma once
#include <logic/auth/YggdrasilTask.h>
@@ -28,7 +32,7 @@ class ValidateTask : public YggdrasilTask
{
Q_OBJECT
public:
- ValidateTask(MojangAccountPtr account, QObject *parent = 0);
+ ValidateTask(MojangAccount *account, QObject *parent = 0);
protected:
virtual QJsonObject getRequestContent() const;
diff --git a/logic/lists/MojangAccountList.cpp b/logic/lists/MojangAccountList.cpp
index 466cc934..defa5d8c 100644
--- a/logic/lists/MojangAccountList.cpp
+++ b/logic/lists/MojangAccountList.cpp
@@ -83,10 +83,7 @@ void MojangAccountList::removeAccount(QModelIndex index)
MojangAccountPtr MojangAccountList::activeAccount() const
{
- if (m_activeAccount.isEmpty())
- return nullptr;
- else
- return findAccount(m_activeAccount);
+ return m_activeAccount;
}
void MojangAccountList::setActiveAccount(const QString &username)
@@ -94,14 +91,14 @@ void MojangAccountList::setActiveAccount(const QString &username)
beginResetModel();
if (username.isEmpty())
{
- m_activeAccount = "";
+ m_activeAccount = nullptr;
}
else
{
for (MojangAccountPtr account : m_accounts)
{
if (account->username() == username)
- m_activeAccount = username;
+ m_activeAccount = account;
}
}
endResetModel();
@@ -152,7 +149,7 @@ QVariant MojangAccountList::data(const QModelIndex &index, int role) const
switch (index.column())
{
case ActiveColumn:
- return account->username() == m_activeAccount;
+ return account == m_activeAccount;
case NameColumn:
return account->username();
@@ -297,11 +294,9 @@ bool MojangAccountList::loadList(const QString &filePath)
QLOG_WARN() << "Failed to load an account.";
}
}
- endResetModel();
-
// Load the active account.
- m_activeAccount = root.value("activeAccount").toString("");
-
+ m_activeAccount = findAccount(root.value("activeAccount").toString(""));
+ endResetModel();
return true;
}
@@ -336,8 +331,11 @@ bool MojangAccountList::saveList(const QString &filePath)
// Insert the account list into the root object.
root.insert("accounts", accounts);
- // Save the active account.
- root.insert("activeAccount", m_activeAccount);
+ if(m_activeAccount)
+ {
+ // Save the active account.
+ root.insert("activeAccount", m_activeAccount->username());
+ }
// Create a JSON document object to convert our JSON to bytes.
QJsonDocument doc(root);
diff --git a/logic/lists/MojangAccountList.h b/logic/lists/MojangAccountList.h
index 744f3c51..b3301bf6 100644
--- a/logic/lists/MojangAccountList.h
+++ b/logic/lists/MojangAccountList.h
@@ -161,10 +161,9 @@ protected:
QList<MojangAccountPtr> m_accounts;
/*!
- * Username of the account that is currently active.
- * Empty string if no account is active.
+ * Account that is currently active.
*/
- QString m_activeAccount;
+ MojangAccountPtr m_activeAccount;
//! Path to the account list file. Empty string if there isn't one.
QString m_listFilePath;
diff --git a/logic/net/NetJob.h b/logic/net/NetJob.h
index 021a1550..a7027e10 100644
--- a/logic/net/NetJob.h
+++ b/logic/net/NetJob.h
@@ -94,6 +94,8 @@ signals:
public
slots:
virtual void start();
+ // FIXME: implement
+ virtual void abort() {};
private
slots:
void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal);
diff --git a/logic/net/PasteUpload.cpp b/logic/net/PasteUpload.cpp
new file mode 100644
index 00000000..acf09291
--- /dev/null
+++ b/logic/net/PasteUpload.cpp
@@ -0,0 +1,84 @@
+#include "PasteUpload.h"
+#include "MultiMC.h"
+#include "logger/QsLog.h"
+#include <QJsonObject>
+#include <QJsonDocument>
+#include "gui/dialogs/CustomMessageBox.h"
+#include <QDesktopServices>
+
+PasteUpload::PasteUpload(QWidget *window, QString text) : m_text(text), m_window(window)
+{
+}
+
+void PasteUpload::executeTask()
+{
+ QNetworkRequest request(QUrl("http://paste.ee/api"));
+ request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)");
+ QByteArray content(
+ "key=public&description=MultiMC5+Log+File&language=plain&format=json&paste=" +
+ m_text.toUtf8());
+ request.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
+ request.setRawHeader("Content-Length", QByteArray::number(content.size()));
+
+ auto worker = MMC->qnam();
+ QNetworkReply *rep = worker->post(request, content);
+
+ m_reply = std::shared_ptr<QNetworkReply>(rep);
+ connect(rep, &QNetworkReply::downloadProgress, [&](qint64 value, qint64 max)
+ { setProgress(value / max * 100); });
+ connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this,
+ SLOT(downloadError(QNetworkReply::NetworkError)));
+ connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
+}
+
+void PasteUpload::downloadError(QNetworkReply::NetworkError error)
+{
+ // error happened during download.
+ QLOG_ERROR() << "Network error: " << error;
+ emitFailed(m_reply->errorString());
+}
+
+void PasteUpload::downloadFinished()
+{
+ // if the download succeeded
+ if (m_reply->error() == QNetworkReply::NetworkError::NoError)
+ {
+ QByteArray data = m_reply->readAll();
+ m_reply.reset();
+ QJsonParseError jsonError;
+ QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
+ if (jsonError.error != QJsonParseError::NoError)
+ {
+ emitFailed(jsonError.errorString());
+ return;
+ }
+ QString error;
+ if (!parseResult(doc, &error))
+ {
+ emitFailed(error);
+ return;
+ }
+ }
+ // else the download failed
+ else
+ {
+ emitFailed(QString("Network error: %1").arg(m_reply->errorString()));
+ m_reply.reset();
+ return;
+ }
+ emitSucceeded();
+}
+
+bool PasteUpload::parseResult(QJsonDocument doc, QString *parseError)
+{
+ auto object = doc.object();
+ auto status = object.value("status").toString("error");
+ if (status == "error")
+ {
+ parseError = new QString(object.value("error").toString());
+ return false;
+ }
+ QString pasteUrl = object.value("paste").toObject().value("link").toString();
+ QDesktopServices::openUrl(pasteUrl);
+ return true;
+}
diff --git a/logic/net/PasteUpload.h b/logic/net/PasteUpload.h
new file mode 100644
index 00000000..917a0016
--- /dev/null
+++ b/logic/net/PasteUpload.h
@@ -0,0 +1,26 @@
+#pragma once
+#include "logic/tasks/Task.h"
+#include <QMessageBox>
+#include <QNetworkReply>
+#include <memory>
+
+class PasteUpload : public Task
+{
+ Q_OBJECT
+public:
+ PasteUpload(QWidget *window, QString text);
+
+protected:
+ virtual void executeTask();
+
+private:
+ bool parseResult(QJsonDocument doc, QString *parseError);
+ QString m_text;
+ QString m_error;
+ QWidget *m_window;
+ std::shared_ptr<QNetworkReply> m_reply;
+public
+slots:
+ void downloadError(QNetworkReply::NetworkError);
+ void downloadFinished();
+};
diff --git a/logic/tasks/ProgressProvider.h b/logic/tasks/ProgressProvider.h
index f6f2906a..15e453a3 100644
--- a/logic/tasks/ProgressProvider.h
+++ b/logic/tasks/ProgressProvider.h
@@ -38,4 +38,5 @@ public:
public
slots:
virtual void start() = 0;
+ virtual void abort() = 0;
};
diff --git a/logic/tasks/Task.h b/logic/tasks/Task.h
index d08ef560..80d5e38b 100644
--- a/logic/tasks/Task.h
+++ b/logic/tasks/Task.h
@@ -44,6 +44,7 @@ public:
public
slots:
virtual void start();
+ virtual void abort() {};
protected:
virtual void executeTask() = 0;