From cdca53013990ac85967394529476712e6695bbf9 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Nov 2013 12:05:35 -0600 Subject: Implement account list and account list dialog --- logic/lists/MojangAccountList.cpp | 146 ++++++++++++++++++++++++++++++++++++++ logic/lists/MojangAccountList.h | 109 ++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+) create mode 100644 logic/lists/MojangAccountList.cpp create mode 100644 logic/lists/MojangAccountList.h (limited to 'logic/lists') diff --git a/logic/lists/MojangAccountList.cpp b/logic/lists/MojangAccountList.cpp new file mode 100644 index 00000000..d309d63a --- /dev/null +++ b/logic/lists/MojangAccountList.cpp @@ -0,0 +1,146 @@ +/* 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 "logic/auth/MojangAccount.h" + +MojangAccountList::MojangAccountList(QObject *parent) : QAbstractListModel(parent) +{ +} + +MojangAccountPtr MojangAccountList::findAccount(const QString &username) +{ + for (int i = 0; i < count(); i++) + { + MojangAccountPtr account = at(i); + if (account->username() == username) + return account; + } + return MojangAccountPtr(); +} + + +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(); +} + +void MojangAccountList::removeAccount(const QString& username) +{ + beginResetModel(); + for (auto account : m_accounts) + { + if (account->username() == username) + { + m_accounts.removeOne(account); + return; + } + } + endResetModel(); +} + + +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 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 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 1; +} + +void MojangAccountList::updateListData(QList versions) +{ + beginResetModel(); + m_accounts = versions; + endResetModel(); +} diff --git a/logic/lists/MojangAccountList.h b/logic/lists/MojangAccountList.h new file mode 100644 index 00000000..ce16d70d --- /dev/null +++ b/logic/lists/MojangAccountList.h @@ -0,0 +1,109 @@ +/* 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 +#include +#include +#include + +#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 - Name + NameColumn = 0, + }; + + 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); + + /*! + * \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); + +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: + QList m_accounts; + +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 versions); +}; + -- cgit v1.2.3 From a9a0b65358b3799746fa9c8e1aa879e0b59ef526 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Nov 2013 12:58:03 -0600 Subject: Implement loading accounts from list. --- logic/lists/MojangAccountList.cpp | 89 +++++++++++++++++++++++++++++++++++++++ logic/lists/MojangAccountList.h | 14 ++++++ 2 files changed, 103 insertions(+) (limited to 'logic/lists') diff --git a/logic/lists/MojangAccountList.cpp b/logic/lists/MojangAccountList.cpp index d309d63a..40f0ea26 100644 --- a/logic/lists/MojangAccountList.cpp +++ b/logic/lists/MojangAccountList.cpp @@ -14,8 +14,23 @@ */ #include "logic/lists/MojangAccountList.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "logger/QsLog.h" + #include "logic/auth/MojangAccount.h" +#define DEFAULT_ACCOUNT_LIST_FILE "accounts.json" + +#define ACCOUNT_LIST_FORMAT_VERSION 1 + MojangAccountList::MojangAccountList(QObject *parent) : QAbstractListModel(parent) { } @@ -144,3 +159,77 @@ void MojangAccountList::updateListData(QList versions) m_accounts = versions; endResetModel(); } + +bool MojangAccountList::loadList(const QString& filePath) +{ + QString path = filePath; + if (path.isEmpty()) path = DEFAULT_ACCOUNT_LIST_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::ReadOnly)) + { + QLOG_ERROR() << "Failed to read the account list file (" << path << ")."; + 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 = "accountlist-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(); + + return true; +} + diff --git a/logic/lists/MojangAccountList.h b/logic/lists/MojangAccountList.h index ce16d70d..37c928de 100644 --- a/logic/lists/MojangAccountList.h +++ b/logic/lists/MojangAccountList.h @@ -80,6 +80,20 @@ public: */ virtual MojangAccountPtr findAccount(const QString &username); + /*! + * \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=""); + signals: /*! * Signal emitted to indicate that the account list has changed. -- cgit v1.2.3 From 928e0d0b151a4690a5849c2ec4a44d97338937c5 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 19 Nov 2013 12:53:30 -0600 Subject: Implement saving account list to file Currently it only saves when accounts are added or removed. We'll have to fix this, but we need signals for when account objects change first. --- logic/lists/MojangAccountList.cpp | 81 +++++++++++++++++++++++++++++++++++++-- logic/lists/MojangAccountList.h | 24 ++++++++++++ 2 files changed, 102 insertions(+), 3 deletions(-) (limited to 'logic/lists') diff --git a/logic/lists/MojangAccountList.cpp b/logic/lists/MojangAccountList.cpp index 40f0ea26..68a4da18 100644 --- a/logic/lists/MojangAccountList.cpp +++ b/logic/lists/MojangAccountList.cpp @@ -27,8 +27,6 @@ #include "logic/auth/MojangAccount.h" -#define DEFAULT_ACCOUNT_LIST_FILE "accounts.json" - #define ACCOUNT_LIST_FORMAT_VERSION 1 MojangAccountList::MojangAccountList(QObject *parent) : QAbstractListModel(parent) @@ -57,6 +55,7 @@ void MojangAccountList::addAccount(const MojangAccountPtr account) beginResetModel(); m_accounts.append(account); endResetModel(); + onListChanged(); } void MojangAccountList::removeAccount(const QString& username) @@ -71,6 +70,16 @@ void MojangAccountList::removeAccount(const QString& username) } } endResetModel(); + onListChanged(); +} + + +void MojangAccountList::onListChanged() +{ + if (m_autosave) + // TODO: Alert the user if this fails. + saveList(); + emit listChanged(); } @@ -163,7 +172,12 @@ void MojangAccountList::updateListData(QList versions) bool MojangAccountList::loadList(const QString& filePath) { QString path = filePath; - if (path.isEmpty()) path = DEFAULT_ACCOUNT_LIST_FILE; + 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); @@ -233,3 +247,64 @@ bool MojangAccountList::loadList(const QString& filePath) 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); + + // 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() << "Failed to read the account list file (" << path << ")."; + 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; +} + diff --git a/logic/lists/MojangAccountList.h b/logic/lists/MojangAccountList.h index 37c928de..491abf6d 100644 --- a/logic/lists/MojangAccountList.h +++ b/logic/lists/MojangAccountList.h @@ -79,6 +79,15 @@ public: * one doesn't exist. */ virtual MojangAccountPtr findAccount(const QString &username); + + /*! + * 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. @@ -102,8 +111,23 @@ signals: void listChanged(); protected: + /*! + * Called whenever the list changes. + * This emits the listChanged() signal and autosaves the list (if autosave is enabled). + */ + void onListChanged(); + QList m_accounts; + //! 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: /*! -- cgit v1.2.3 From abf8408911c057d8aafe90790f5d2f5de0e1d97c Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 20 Nov 2013 18:31:15 -0600 Subject: Nuke and pave the old login system Also, account list now saves profile lists. --- logic/lists/MojangAccountList.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'logic/lists') diff --git a/logic/lists/MojangAccountList.cpp b/logic/lists/MojangAccountList.cpp index 68a4da18..32317f84 100644 --- a/logic/lists/MojangAccountList.cpp +++ b/logic/lists/MojangAccountList.cpp @@ -185,7 +185,7 @@ bool MojangAccountList::loadList(const QString& filePath) // TODO: We should probably report this error to the user. if (!file.open(QIODevice::ReadOnly)) { - QLOG_ERROR() << "Failed to read the account list file (" << path << ")."; + QLOG_ERROR() << QString("Failed to read the account list file (%1).").arg(path).toUtf8(); return false; } @@ -217,9 +217,9 @@ bool MojangAccountList::loadList(const QString& filePath) // Make sure the format version matches. if (root.value("formatVersion").toVariant().toInt() != ACCOUNT_LIST_FORMAT_VERSION) { - QString newName = "accountlist-old.json"; - QLOG_WARN() << "Format version mismatch when loading account list. Existing one will be renamed to \"" - << newName << "\"."; + 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); @@ -257,7 +257,7 @@ bool MojangAccountList::saveList(const QString& filePath) return false; } - QLOG_INFO() << "Writing account list to \"" << path << "\"..."; + QLOG_INFO() << "Writing account list to" << path; QLOG_DEBUG() << "Building JSON data structure."; // Build the JSON document to write to the list file. @@ -289,7 +289,7 @@ bool MojangAccountList::saveList(const QString& filePath) // TODO: We should probably report this error to the user. if (!file.open(QIODevice::WriteOnly)) { - QLOG_ERROR() << "Failed to read the account list file (" << path << ")."; + QLOG_ERROR() << QString("Failed to read the account list file (%1).").arg(path).toUtf8(); return false; } @@ -297,7 +297,7 @@ bool MojangAccountList::saveList(const QString& filePath) file.write(doc.toJson()); file.close(); - QLOG_INFO() << "Saved account list to \"" << path << "\"."; + QLOG_INFO() << "Saved account list to" << path; return true; } -- cgit v1.2.3 From 23bc195b3c8558cb997789ca8772342612716993 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 22 Nov 2013 10:54:52 -0600 Subject: Implement removing accounts. --- logic/lists/MojangAccountList.cpp | 8 ++++++++ logic/lists/MojangAccountList.h | 5 +++++ 2 files changed, 13 insertions(+) (limited to 'logic/lists') diff --git a/logic/lists/MojangAccountList.cpp b/logic/lists/MojangAccountList.cpp index 32317f84..442ef3af 100644 --- a/logic/lists/MojangAccountList.cpp +++ b/logic/lists/MojangAccountList.cpp @@ -73,6 +73,14 @@ void MojangAccountList::removeAccount(const QString& username) onListChanged(); } +void MojangAccountList::removeAccount(QModelIndex index) +{ + beginResetModel(); + m_accounts.removeAt(index.row()); + endResetModel(); + onListChanged(); +} + void MojangAccountList::onListChanged() { diff --git a/logic/lists/MojangAccountList.h b/logic/lists/MojangAccountList.h index 491abf6d..bccc2f9a 100644 --- a/logic/lists/MojangAccountList.h +++ b/logic/lists/MojangAccountList.h @@ -72,6 +72,11 @@ public: */ 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. -- cgit v1.2.3 From 75e7932607bdd84d2867765eb6f07dcec95ee193 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 22 Nov 2013 12:47:39 -0600 Subject: Properly implement launching and downloading Also added a system to select an active account to log in with. --- logic/lists/MojangAccountList.cpp | 37 ++++++++++++++++++++++++++++++++++--- logic/lists/MojangAccountList.h | 29 ++++++++++++++++++++++++++--- 2 files changed, 60 insertions(+), 6 deletions(-) (limited to 'logic/lists') diff --git a/logic/lists/MojangAccountList.cpp b/logic/lists/MojangAccountList.cpp index 442ef3af..1d67c70f 100644 --- a/logic/lists/MojangAccountList.cpp +++ b/logic/lists/MojangAccountList.cpp @@ -33,7 +33,7 @@ MojangAccountList::MojangAccountList(QObject *parent) : QAbstractListModel(paren { } -MojangAccountPtr MojangAccountList::findAccount(const QString &username) +MojangAccountPtr MojangAccountList::findAccount(const QString &username) const { for (int i = 0; i < count(); i++) { @@ -41,7 +41,7 @@ MojangAccountPtr MojangAccountList::findAccount(const QString &username) if (account->username() == username) return account; } - return MojangAccountPtr(); + return nullptr; } @@ -82,6 +82,25 @@ void MojangAccountList::removeAccount(QModelIndex index) } +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) @@ -112,6 +131,9 @@ QVariant MojangAccountList::data(const QModelIndex &index, int role) const case Qt::DisplayRole: switch (index.column()) { + case ActiveColumn: + return account->username() == m_activeAccount; + case NameColumn: return account->username(); @@ -137,6 +159,9 @@ QVariant MojangAccountList::headerData(int section, Qt::Orientation orientation, case Qt::DisplayRole: switch (section) { + case ActiveColumn: + return "Active?"; + case NameColumn: return "Name"; @@ -167,7 +192,7 @@ int MojangAccountList::rowCount(const QModelIndex &parent) const int MojangAccountList::columnCount(const QModelIndex &parent) const { - return 1; + return 2; } void MojangAccountList::updateListData(QList versions) @@ -251,6 +276,9 @@ bool MojangAccountList::loadList(const QString& filePath) } } endResetModel(); + + // Load the active account. + m_activeAccount = root.value("activeAccount").toString(""); return true; } @@ -285,6 +313,9 @@ 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); + // 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 bccc2f9a..71f472f7 100644 --- a/logic/lists/MojangAccountList.h +++ b/logic/lists/MojangAccountList.h @@ -44,8 +44,12 @@ public: enum VListColumns { // TODO: Add icon column. - // First column - Name - NameColumn = 0, + + // First column - Active? + ActiveColumn = 0, + + // Second column - Name + NameColumn, }; explicit MojangAccountList(QObject *parent = 0); @@ -83,7 +87,7 @@ public: * \return A const pointer to the account with the given username. NULL if * one doesn't exist. */ - virtual MojangAccountPtr findAccount(const QString &username); + virtual MojangAccountPtr findAccount(const QString &username) const; /*! * Sets the default path to save the list file to. @@ -108,6 +112,19 @@ public: */ 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. @@ -124,6 +141,12 @@ protected: QList 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; -- cgit v1.2.3