From 3a8b238052163952831fb5924b2483a375e86ebd Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Thu, 28 May 2015 19:38:29 +0200 Subject: NOISSUE Various changes from multiauth that are unrelated to it --- .travis.yml | 3 +- application/CMakeLists.txt | 2 + application/MainWindow.cpp | 3 +- application/MultiMC.cpp | 34 ++ application/MultiMC.h | 5 +- application/main.cpp | 1 - application/pages/VersionPage.cpp | 10 +- application/pages/global/AccountListPage.h | 2 +- .../resources/multimc/150x150/hourglass.png | Bin 0 -> 11831 bytes application/resources/multimc/16x16/hourglass.png | Bin 0 -> 705 bytes application/resources/multimc/22x22/hourglass.png | Bin 0 -> 1037 bytes application/resources/multimc/32x32/hourglass.png | Bin 0 -> 1574 bytes application/resources/multimc/48x48/hourglass.png | Bin 0 -> 2679 bytes application/resources/multimc/index.theme | 3 + application/resources/multimc/multimc.qrc | 7 + application/widgets/ProgressWidget.cpp | 74 ++++ application/widgets/ProgressWidget.h | 32 ++ logic/AbstractCommonModel.cpp | 133 ++++++ logic/AbstractCommonModel.h | 462 +++++++++++++++++++++ logic/BaseConfigObject.cpp | 119 ++++++ logic/BaseConfigObject.h | 50 +++ logic/CMakeLists.txt | 30 +- logic/Env.cpp | 3 +- logic/Exception.h | 41 ++ logic/FileSystem.cpp | 56 +++ logic/FileSystem.h | 13 + logic/Json.cpp | 278 +++++++++++++ logic/Json.h | 239 +++++++++++ logic/MMCError.h | 25 -- logic/MMCJson.cpp | 142 ------- logic/MMCJson.h | 101 ----- logic/QObjectPtr.h | 5 + logic/forge/ForgeInstaller.cpp | 3 +- logic/liteloader/LiteLoaderInstaller.cpp | 3 +- logic/liteloader/LiteLoaderVersionList.cpp | 4 +- logic/minecraft/JarMod.cpp | 4 +- logic/minecraft/MinecraftProfile.cpp | 7 +- logic/minecraft/MinecraftVersionList.cpp | 31 +- logic/minecraft/OneSixInstance.cpp | 3 +- logic/minecraft/OneSixProfileStrategy.cpp | 4 +- logic/minecraft/OneSixUpdate.cpp | 3 +- logic/minecraft/ParseUtils.cpp | 1 - logic/minecraft/ProfileUtils.cpp | 10 +- logic/minecraft/RawLibrary.cpp | 6 +- logic/minecraft/VersionBuildError.h | 8 +- logic/minecraft/VersionFile.cpp | 4 +- logic/minecraft/VersionFile.h | 3 +- logic/net/CacheDownload.h | 29 ++ logic/resources/IconResourceHandler.cpp | 60 +++ logic/resources/IconResourceHandler.h | 22 + logic/resources/Resource.cpp | 121 ++++++ logic/resources/Resource.h | 116 ++++++ logic/resources/ResourceHandler.cpp | 28 ++ logic/resources/ResourceHandler.h | 33 ++ logic/resources/ResourceObserver.cpp | 55 +++ logic/resources/ResourceObserver.h | 67 +++ logic/resources/ResourceProxyModel.cpp | 103 +++++ logic/resources/ResourceProxyModel.h | 36 ++ logic/resources/WebResourceHandler.cpp | 67 +++ logic/resources/WebResourceHandler.h | 23 + logic/tasks/StandardTask.cpp | 120 ++++++ logic/tasks/StandardTask.h | 43 ++ logic/tasks/Task.cpp | 1 + logic/tasks/Task.h | 2 + tests/tst_Resource.cpp | 101 +++++ 65 files changed, 2661 insertions(+), 333 deletions(-) create mode 100644 application/resources/multimc/150x150/hourglass.png create mode 100644 application/resources/multimc/16x16/hourglass.png create mode 100644 application/resources/multimc/22x22/hourglass.png create mode 100644 application/resources/multimc/32x32/hourglass.png create mode 100644 application/resources/multimc/48x48/hourglass.png create mode 100644 application/widgets/ProgressWidget.cpp create mode 100644 application/widgets/ProgressWidget.h create mode 100644 logic/AbstractCommonModel.cpp create mode 100644 logic/AbstractCommonModel.h create mode 100644 logic/BaseConfigObject.cpp create mode 100644 logic/BaseConfigObject.h create mode 100644 logic/Exception.h create mode 100644 logic/FileSystem.cpp create mode 100644 logic/FileSystem.h create mode 100644 logic/Json.cpp create mode 100644 logic/Json.h delete mode 100644 logic/MMCError.h delete mode 100644 logic/MMCJson.cpp delete mode 100644 logic/MMCJson.h create mode 100644 logic/resources/IconResourceHandler.cpp create mode 100644 logic/resources/IconResourceHandler.h create mode 100644 logic/resources/Resource.cpp create mode 100644 logic/resources/Resource.h create mode 100644 logic/resources/ResourceHandler.cpp create mode 100644 logic/resources/ResourceHandler.h create mode 100644 logic/resources/ResourceObserver.cpp create mode 100644 logic/resources/ResourceObserver.h create mode 100644 logic/resources/ResourceProxyModel.cpp create mode 100644 logic/resources/ResourceProxyModel.h create mode 100644 logic/resources/WebResourceHandler.cpp create mode 100644 logic/resources/WebResourceHandler.h create mode 100644 logic/tasks/StandardTask.cpp create mode 100644 logic/tasks/StandardTask.h create mode 100644 tests/tst_Resource.cpp diff --git a/.travis.yml b/.travis.yml index ad0bdee5..9ed7a045 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,6 @@ before_script: - cd build - cmake -DCMAKE_PREFIX_PATH=/opt/qt53/lib/cmake .. script: - - make -j4 - - make test ARGS="-V" + - make -j4 && make test ARGS="-V" notifications: email: false diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt index d7cb5777..d3962819 100644 --- a/application/CMakeLists.txt +++ b/application/CMakeLists.txt @@ -251,6 +251,8 @@ SET(MULTIMC_SOURCES widgets/ServerStatus.h widgets/VersionListView.cpp widgets/VersionListView.h + widgets/ProgressWidget.h + widgets/ProgressWidget.cpp # GUI - instance group view diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp index 9ff120bd..99c94bf8 100644 --- a/application/MainWindow.cpp +++ b/application/MainWindow.cpp @@ -383,6 +383,7 @@ namespace Ui { #include "JavaCommon.h" #include "InstancePageProvider.h" #include "minecraft/SkinUtils.h" +#include "resources/Resource.h" //#include "minecraft/LegacyInstance.h" @@ -1758,7 +1759,7 @@ void MainWindow::launchInstance(InstancePtr instance, AuthSessionPtr session, this->hide(); console = new ConsoleWindow(proc); - connect(console, SIGNAL(isClosing()), this, SLOT(instanceEnded())); + connect(console, &ConsoleWindow::isClosing, this, &MainWindow::instanceEnded); proc->setHeader("MultiMC version: " + BuildConfig.printableVersionString() + "\n\n"); proc->arm(); diff --git a/application/MultiMC.cpp b/application/MultiMC.cpp index 39cc8503..2c6b387c 100644 --- a/application/MultiMC.cpp +++ b/application/MultiMC.cpp @@ -40,6 +40,8 @@ #include "settings/Setting.h" #include "trans/TranslationDownloader.h" +#include "resources/Resource.h" +#include "resources/IconResourceHandler.h" #include "ftb/FTBPlugin.h" @@ -331,6 +333,37 @@ void MultiMC::initIcons() { ENV.m_icons->directoryChanged(value.toString()); }); + + Resource::registerTransformer([](const QVariantMap &map) -> QIcon + { + QIcon icon; + for (auto it = map.constBegin(); it != map.constEnd(); ++it) + { + icon.addFile(it.key(), QSize(it.value().toInt(), it.value().toInt())); + } + return icon; + }); + Resource::registerTransformer([](const QVariantMap &map) -> QPixmap + { + QVariantList sizes = map.values(); + if (sizes.isEmpty()) + { + return QPixmap(); + } + std::sort(sizes.begin(), sizes.end()); + if (sizes.last().toInt() != -1) // only scalable available + { + return QPixmap(map.key(sizes.last())); + } + else + { + return QPixmap(); + } + }); + Resource::registerTransformer([](const QByteArray &data) -> QPixmap + { return QPixmap::fromImage(QImage::fromData(data)); }); + Resource::registerTransformer([](const QByteArray &data) -> QIcon + { return QIcon(QPixmap::fromImage(QImage::fromData(data))); }); } @@ -610,6 +643,7 @@ void MultiMC::installUpdates(const QString updateFilesDir, UpdateFlags flags) void MultiMC::setIconTheme(const QString& name) { XdgIcon::setThemeName(name); + IconResourceHandler::setTheme(name); } QIcon MultiMC::getThemedIcon(const QString& name) diff --git a/application/MultiMC.h b/application/MultiMC.h index 8215e4ad..e4a54115 100644 --- a/application/MultiMC.h +++ b/application/MultiMC.h @@ -146,13 +146,10 @@ private slots: private: void initLogger(); - void initIcons(); - void initGlobalSettings(bool test_mode); - void initTranslations(); - void initSSL(); + void initSSL(); private: friend class UpdateCheckerTest; diff --git a/application/main.cpp b/application/main.cpp index 111a61ac..12c97f09 100644 --- a/application/main.cpp +++ b/application/main.cpp @@ -13,7 +13,6 @@ int main_gui(MultiMC &app) mainWin.checkInstancePathForProblems(); return app.exec(); } - int main(int argc, char *argv[]) { // initialize Qt diff --git a/application/pages/VersionPage.cpp b/application/pages/VersionPage.cpp index cbb5c107..efc0b446 100644 --- a/application/pages/VersionPage.cpp +++ b/application/pages/VersionPage.cpp @@ -47,7 +47,7 @@ #include #include #include "icons/IconList.h" - +#include "Exception.h" QIcon VersionPage::icon() const { @@ -118,7 +118,7 @@ bool VersionPage::reloadMinecraftProfile() m_inst->reloadProfile(); return true; } - catch (MMCError &e) + catch (Exception &e) { QMessageBox::critical(this, tr("Error"), e.cause()); return false; @@ -199,7 +199,7 @@ void VersionPage::on_resetOrderBtn_clicked() { m_version->resetOrder(); } - catch (MMCError &e) + catch (Exception &e) { QMessageBox::critical(this, tr("Error"), e.cause()); } @@ -212,7 +212,7 @@ void VersionPage::on_moveUpBtn_clicked() { m_version->move(currentRow(), MinecraftProfile::MoveUp); } - catch (MMCError &e) + catch (Exception &e) { QMessageBox::critical(this, tr("Error"), e.cause()); } @@ -225,7 +225,7 @@ void VersionPage::on_moveDownBtn_clicked() { m_version->move(currentRow(), MinecraftProfile::MoveDown); } - catch (MMCError &e) + catch (Exception &e) { QMessageBox::critical(this, tr("Error"), e.cause()); } diff --git a/application/pages/global/AccountListPage.h b/application/pages/global/AccountListPage.h index bfadc1bd..7803e044 100644 --- a/application/pages/global/AccountListPage.h +++ b/application/pages/global/AccountListPage.h @@ -21,7 +21,7 @@ #include "pages/BasePage.h" #include "auth/MojangAccountList.h" -#include +#include "MultiMC.h" namespace Ui { diff --git a/application/resources/multimc/150x150/hourglass.png b/application/resources/multimc/150x150/hourglass.png new file mode 100644 index 00000000..f2623d1e Binary files /dev/null and b/application/resources/multimc/150x150/hourglass.png differ diff --git a/application/resources/multimc/16x16/hourglass.png b/application/resources/multimc/16x16/hourglass.png new file mode 100644 index 00000000..ab36234b Binary files /dev/null and b/application/resources/multimc/16x16/hourglass.png differ diff --git a/application/resources/multimc/22x22/hourglass.png b/application/resources/multimc/22x22/hourglass.png new file mode 100644 index 00000000..8cb343ac Binary files /dev/null and b/application/resources/multimc/22x22/hourglass.png differ diff --git a/application/resources/multimc/32x32/hourglass.png b/application/resources/multimc/32x32/hourglass.png new file mode 100644 index 00000000..a558ec99 Binary files /dev/null and b/application/resources/multimc/32x32/hourglass.png differ diff --git a/application/resources/multimc/48x48/hourglass.png b/application/resources/multimc/48x48/hourglass.png new file mode 100644 index 00000000..8f10ab7a Binary files /dev/null and b/application/resources/multimc/48x48/hourglass.png differ diff --git a/application/resources/multimc/index.theme b/application/resources/multimc/index.theme index 5f7d3f3f..a21fea2c 100644 --- a/application/resources/multimc/index.theme +++ b/application/resources/multimc/index.theme @@ -35,6 +35,9 @@ Size=64 [256x256] Size=256 +[150x150] +Size=150 + [scalable] Size=48 Type=Scalable diff --git a/application/resources/multimc/multimc.qrc b/application/resources/multimc/multimc.qrc index 4ced586a..31a7b44f 100644 --- a/application/resources/multimc/multimc.qrc +++ b/application/resources/multimc/multimc.qrc @@ -207,6 +207,13 @@ 48x48/log.png 64x64/log.png + + 16x16/hourglass.png + 22x22/hourglass.png + 32x32/hourglass.png + 48x48/hourglass.png + 150x150/hourglass.png + scalable/screenshot-placeholder.svg diff --git a/application/widgets/ProgressWidget.cpp b/application/widgets/ProgressWidget.cpp new file mode 100644 index 00000000..7b51eca0 --- /dev/null +++ b/application/widgets/ProgressWidget.cpp @@ -0,0 +1,74 @@ +// Licensed under the Apache-2.0 license. See README.md for details. + +#include "ProgressWidget.h" + +#include +#include +#include +#include + +#include "tasks/Task.h" + +ProgressWidget::ProgressWidget(QWidget *parent) + : QWidget(parent) +{ + m_label = new QLabel(this); + m_label->setWordWrap(true); + m_bar = new QProgressBar(this); + m_bar->setMinimum(0); + m_bar->setMaximum(100); + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(m_label); + layout->addWidget(m_bar); + layout->addStretch(); + setLayout(layout); +} + +void ProgressWidget::start(std::shared_ptr task) +{ + if (m_task) + { + disconnect(m_task.get(), 0, this, 0); + } + m_task = task; + connect(m_task.get(), &Task::finished, this, &ProgressWidget::handleTaskFinish); + connect(m_task.get(), &Task::status, this, &ProgressWidget::handleTaskStatus); + connect(m_task.get(), &Task::progress, this, &ProgressWidget::handleTaskProgress); + connect(m_task.get(), &Task::destroyed, this, &ProgressWidget::taskDestroyed); + if (!m_task->isRunning()) + { + QMetaObject::invokeMethod(m_task.get(), "start", Qt::QueuedConnection); + } +} +bool ProgressWidget::exec(std::shared_ptr task) +{ + QEventLoop loop; + connect(task.get(), &Task::finished, &loop, &QEventLoop::quit); + start(task); + if (task->isRunning()) + { + loop.exec(); + } + return task->successful(); +} + +void ProgressWidget::handleTaskFinish() +{ + if (!m_task->successful()) + { + m_label->setText(m_task->failReason()); + } +} +void ProgressWidget::handleTaskStatus(const QString &status) +{ + m_label->setText(status); +} +void ProgressWidget::handleTaskProgress(qint64 current, qint64 total) +{ + m_bar->setMaximum(total); + m_bar->setValue(current); +} +void ProgressWidget::taskDestroyed() +{ + m_task = nullptr; +} diff --git a/application/widgets/ProgressWidget.h b/application/widgets/ProgressWidget.h new file mode 100644 index 00000000..08d8a157 --- /dev/null +++ b/application/widgets/ProgressWidget.h @@ -0,0 +1,32 @@ +// Licensed under the Apache-2.0 license. See README.md for details. + +#pragma once + +#include +#include + +class Task; +class QProgressBar; +class QLabel; + +class ProgressWidget : public QWidget +{ + Q_OBJECT +public: + explicit ProgressWidget(QWidget *parent = nullptr); + +public slots: + void start(std::shared_ptr task); + bool exec(std::shared_ptr task); + +private slots: + void handleTaskFinish(); + void handleTaskStatus(const QString &status); + void handleTaskProgress(qint64 current, qint64 total); + void taskDestroyed(); + +private: + QLabel *m_label; + QProgressBar *m_bar; + std::shared_ptr m_task; +}; diff --git a/logic/AbstractCommonModel.cpp b/logic/AbstractCommonModel.cpp new file mode 100644 index 00000000..71d75829 --- /dev/null +++ b/logic/AbstractCommonModel.cpp @@ -0,0 +1,133 @@ +/* Copyright 2015 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 "AbstractCommonModel.h" + +BaseAbstractCommonModel::BaseAbstractCommonModel(const Qt::Orientation orientation, QObject *parent) + : QAbstractListModel(parent), m_orientation(orientation) +{ +} + +int BaseAbstractCommonModel::rowCount(const QModelIndex &parent) const +{ + return m_orientation == Qt::Horizontal ? entryCount() : size(); +} +int BaseAbstractCommonModel::columnCount(const QModelIndex &parent) const +{ + return m_orientation == Qt::Horizontal ? size() : entryCount(); +} +QVariant BaseAbstractCommonModel::data(const QModelIndex &index, int role) const +{ + if (!hasIndex(index.row(), index.column(), index.parent())) + { + return QVariant(); + } + const int i = m_orientation == Qt::Horizontal ? index.column() : index.row(); + const int entry = m_orientation == Qt::Horizontal ? index.row() : index.column(); + return formatData(i, role, get(i, entry, role)); +} +QVariant BaseAbstractCommonModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != m_orientation && role == Qt::DisplayRole) + { + return entryTitle(section); + } + else + { + return QVariant(); + } +} +bool BaseAbstractCommonModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + const int i = m_orientation == Qt::Horizontal ? index.column() : index.row(); + const int entry = m_orientation == Qt::Horizontal ? index.row() : index.column(); + const bool result = set(i, entry, role, sanetizeData(i, role, value)); + if (result) + { + emit dataChanged(index, index, QVector() << role); + } + return result; +} +Qt::ItemFlags BaseAbstractCommonModel::flags(const QModelIndex &index) const +{ + if (!hasIndex(index.row(), index.column(), index.parent())) + { + return Qt::NoItemFlags; + } + + const int entry = m_orientation == Qt::Horizontal ? index.row() : index.column(); + if (canSet(entry)) + { + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled; + } + else + { + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + } +} + +void BaseAbstractCommonModel::notifyAboutToAddObject(const int at) +{ + if (m_orientation == Qt::Horizontal) + { + beginInsertColumns(QModelIndex(), at, at); + } + else + { + beginInsertRows(QModelIndex(), at, at); + } +} +void BaseAbstractCommonModel::notifyObjectAdded() +{ + if (m_orientation == Qt::Horizontal) + { + endInsertColumns(); + } + else + { + endInsertRows(); + } +} +void BaseAbstractCommonModel::notifyAboutToRemoveObject(const int at) +{ + if (m_orientation == Qt::Horizontal) + { + beginRemoveColumns(QModelIndex(), at, at); + } + else + { + beginRemoveRows(QModelIndex(), at, at); + } +} +void BaseAbstractCommonModel::notifyObjectRemoved() +{ + if (m_orientation == Qt::Horizontal) + { + endRemoveColumns(); + } + else + { + endRemoveRows(); + } +} + +void BaseAbstractCommonModel::notifyBeginReset() +{ + beginResetModel(); +} +void BaseAbstractCommonModel::notifyEndReset() +{ + endResetModel(); +} diff --git a/logic/AbstractCommonModel.h b/logic/AbstractCommonModel.h new file mode 100644 index 00000000..31b86a23 --- /dev/null +++ b/logic/AbstractCommonModel.h @@ -0,0 +1,462 @@ +/* Copyright 2015 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 + +class BaseAbstractCommonModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit BaseAbstractCommonModel(const Qt::Orientation orientation, QObject *parent = nullptr); + + // begin QAbstractItemModel interface + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + // end QAbstractItemModel interface + + virtual int size() const = 0; + virtual int entryCount() const = 0; + + virtual QVariant formatData(const int index, int role, const QVariant &data) const { return data; } + virtual QVariant sanetizeData(const int index, int role, const QVariant &data) const { return data; } + +protected: + virtual QVariant get(const int index, const int entry, const int role) const = 0; + virtual bool set(const int index, const int entry, const int role, const QVariant &value) = 0; + virtual bool canSet(const int entry) const = 0; + virtual QString entryTitle(const int entry) const = 0; + + void notifyAboutToAddObject(const int at); + void notifyObjectAdded(); + void notifyAboutToRemoveObject(const int at); + void notifyObjectRemoved(); + void notifyBeginReset(); + void notifyEndReset(); + + const Qt::Orientation m_orientation; +}; + +template +class AbstractCommonModel : public BaseAbstractCommonModel +{ +public: + explicit AbstractCommonModel(const Qt::Orientation orientation) + : BaseAbstractCommonModel(orientation) {} + virtual ~AbstractCommonModel() {} + + int size() const override { return m_objects.size(); } + int entryCount() const override { return m_entries.size(); } + + void append(const Object &object) + { + notifyAboutToAddObject(size()); + m_objects.append(object); + notifyObjectAdded(); + } + void prepend(const Object &object) + { + notifyAboutToAddObject(0); + m_objects.prepend(object); + notifyObjectAdded(); + } + void insert(const Object &object, const int index) + { + if (index >= size()) + { + prepend(object); + } + else if (index <= 0) + { + append(object); + } + else + { + notifyAboutToAddObject(index); + m_objects.insert(index, object); + notifyObjectAdded(); + } + } + void remove(const int index) + { + notifyAboutToRemoveObject(index); + m_objects.removeAt(index); + notifyObjectRemoved(); + } + Object get(const int index) const + { + return m_objects.at(index); + } + +private: + friend class CommonModel; + QVariant get(const int index, const int entry, const int role) const override + { + if (m_entries.size() < entry || !m_entries[entry].second.contains(role)) + { + return QVariant(); + } + return m_entries[entry].second.value(role)->get(m_objects.at(index)); + } + bool set(const int index, const int entry, const int role, const QVariant &value) override + { + if (m_entries.size() < entry || !m_entries[entry].second.contains(role)) + { + return false; + } + IEntry *e = m_entries[entry].second.value(role); + if (!e->canSet()) + { + return false; + } + e->set(m_objects[index], value); + return true; + } + bool canSet(const int entry) const override + { + if (m_entries.size() < entry || !m_entries[entry].second.contains(Qt::EditRole)) + { + return false; + } + IEntry *e = m_entries[entry].second.value(Qt::EditRole); + return e->canSet(); + } + + QString entryTitle(const int entry) const override + { + return m_entries.at(entry).first; + } + +private: + struct IEntry + { + virtual ~IEntry() {} + virtual void set(Object &object, const QVariant &value) = 0; + virtual QVariant get(const Object &object) const = 0; + virtual bool canSet() const = 0; + }; + template + struct VariableEntry : public IEntry + { + typedef T (Object::*Member); + + explicit VariableEntry(Member member) + : m_member(member) {} + + void set(Object &object, const QVariant &value) override + { + object.*m_member = value.value(); + } + QVariant get(const Object &object) const override + { + return QVariant::fromValue(object.*m_member); + } + bool canSet() const override { return true; } + + private: + Member m_member; + }; + template + struct FunctionEntry : public IEntry + { + typedef T (Object::*Getter)() const; + typedef void (Object::*Setter)(T); + + explicit FunctionEntry(Getter getter, Setter setter) + : m_getter(m_getter), m_setter(m_setter) {} + + void set(Object &object, const QVariant &value) override + { + object.*m_setter(value.value()); + } + QVariant get(const Object &object) const override + { + return QVariant::fromValue(object.*m_getter()); + } + bool canSet() const override { return !!m_setter; } + + private: + Getter m_getter; + Setter m_setter; + }; + + QList m_objects; + QVector>> m_entries; + + void addEntryInternal(IEntry *e, const int entry, const int role) + { + if (m_entries.size() <= entry) + { + m_entries.resize(entry + 1); + } + m_entries[entry].second.insert(role, e); + } + +protected: + template + typename std::enable_if::value && std::is_member_function_pointer::value, void>::type + addEntry(Getter getter, Setter setter, const int entry, const int role) + { + addEntryInternal(new FunctionEntry::type>(getter, setter), entry, role); + } + template + typename std::enable_if::value, void>::type + addEntry(Getter getter, const int entry, const int role) + { + addEntryInternal(new FunctionEntry::type>(getter, nullptr), entry, role); + } + template + typename std::enable_if::value, void>::type + addEntry(T (Object::*member), const int entry, const int role) + { + addEntryInternal(new VariableEntry(member), entry, role); + } + + void setEntryTitle(const int entry, const QString &title) + { + m_entries[entry].first = title; + } +}; +template +class AbstractCommonModel : public BaseAbstractCommonModel +{ +public: + explicit AbstractCommonModel(const Qt::Orientation orientation) + : BaseAbstractCommonModel(orientation) {} + virtual ~AbstractCommonModel() + { + qDeleteAll(m_objects); + } + + int size() const override { return m_objects.size(); } + int entryCount() const override { return m_entries.size(); } + + void append(Object *object) + { + notifyAboutToAddObject(size()); + m_objects.append(object); + notifyObjectAdded(); + } + void prepend(Object *object) + { + notifyAboutToAddObject(0); + m_objects.prepend(object); + notifyObjectAdded(); + } + void insert(Object *object, const int index) + { + if (index >= size()) + { + prepend(object); + } + else if (index <= 0) + { + append(object); + } + else + { + notifyAboutToAddObject(index); + m_objects.insert(index, object); + notifyObjectAdded(); + } + } + void remove(const int index) + { + notifyAboutToRemoveObject(index); + m_objects.removeAt(index); + notifyObjectRemoved(); + } + Object *get(const int index) const + { + return m_objects.at(index); + } + int find(Object * const obj) const + { + return m_objects.indexOf(obj); + } + + QList getAll() const + { + return m_objects; + } + +private: + friend class CommonModel; + QVariant get(const int index, const int entry, const int role) const override + { + if (m_entries.size() < entry || !m_entries[entry].second.contains(role)) + { + return QVariant(); + } + return m_entries[entry].second.value(role)->get(m_objects.at(index)); + } + bool set(const int index, const int entry, const int role, const QVariant &value) override + { + if (m_entries.size() < entry || !m_entries[entry].second.contains(role)) + { + return false; + } + IEntry *e = m_entries[entry].second.value(role); + if (!e->canSet()) + { + return false; + } + e->set(m_objects[index], value); + return true; + } + bool canSet(const int entry) const override + { + if (m_entries.size() < entry || !m_entries[entry].second.contains(Qt::EditRole)) + { + return false; + } + IEntry *e = m_entries[entry].second.value(Qt::EditRole); + return e->canSet(); + } + + QString entryTitle(const int entry) const override + { + return m_entries.at(entry).first; + } + +private: + struct IEntry + { + virtual ~IEntry() {} + virtual void set(Object *object, const QVariant &value) = 0; + virtual QVariant get(Object *object) const = 0; + virtual bool canSet() const = 0; + }; + template + struct VariableEntry : public IEntry + { + typedef T (Object::*Member); + + explicit VariableEntry(Member member) + : m_member(member) {} + + void set(Object *object, const QVariant &value) override + { + object->*m_member = value.value(); + } + QVariant get(Object *object) const override + { + return QVariant::fromValue(object->*m_member); + } + bool canSet() const override { return true; } + + private: + Member m_member; + }; + template + struct FunctionEntry : public IEntry + { + typedef T (Object::*Getter)() const; + typedef void (Object::*Setter)(T); + + explicit FunctionEntry(Getter getter, Setter setter) + : m_getter(getter), m_setter(setter) {} + + void set(Object *object, const QVariant &value) override + { + (object->*m_setter)(value.value()); + } + QVariant get(Object *object) const override + { + return QVariant::fromValue((object->*m_getter)()); + } + bool canSet() const override { return !!m_setter; } + + private: + Getter m_getter; + Setter m_setter; + }; + template + struct LambdaEntry : public IEntry + { + using Getter = std::function; + + explicit LambdaEntry(Getter getter) + : m_getter(getter) {} + + void set(Object *object, const QVariant &value) override {} + QVariant get(Object *object) const override + { + return QVariant::fromValue(m_getter(object)); + } + bool canSet() const override { return false; } + + private: + Getter m_getter; + }; + + QList m_objects; + QVector>> m_entries; + + void addEntryInternal(IEntry *e, const int entry, const int role) + { + if (m_entries.size() <= entry) + { + m_entries.resize(entry + 1); + } + m_entries[entry].second.insert(role, e); + } + +protected: + template + typename std::enable_if::value && std::is_member_function_pointer::value, void>::type + addEntry(const int entry, const int role, Getter getter, Setter setter) + { + addEntryInternal(new FunctionEntry::type>(getter, setter), entry, role); + } + template + typename std::enable_if::Getter>::value, void>::type + addEntry(const int entry, const int role, typename FunctionEntry::Getter getter) + { + addEntryInternal(new FunctionEntry(getter, nullptr), entry, role); + } + template + typename std::enable_if::value, void>::type + addEntry(const int entry, const int role, T (Object::*member)) + { + addEntryInternal(new VariableEntry(member), entry, role); + } + template + void addEntry(const int entry, const int role, typename LambdaEntry::Getter lambda) + { + addEntryInternal(new LambdaEntry(lambda), entry, role); + } + + void setEntryTitle(const int entry, const QString &title) + { + m_entries[entry].first = title; + } + + void setAll(const QList objects) + { + notifyBeginReset(); + qDeleteAll(m_objects); + m_objects = objects; + notifyEndReset(); + } +}; diff --git a/logic/BaseConfigObject.cpp b/logic/BaseConfigObject.cpp new file mode 100644 index 00000000..ff698ad0 --- /dev/null +++ b/logic/BaseConfigObject.cpp @@ -0,0 +1,119 @@ +/* Copyright 2015 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 "BaseConfigObject.h" + +#include +#include +#include +#include +#include + +#include "Exception.h" + +BaseConfigObject::BaseConfigObject(const QString &filename) + : m_filename(filename) +{ + m_saveTimer = new QTimer; + m_saveTimer->setSingleShot(true); + // cppcheck-suppress pureVirtualCall + QObject::connect(m_saveTimer, &QTimer::timeout, [this](){saveNow();}); + setSaveTimeout(250); + + m_initialReadTimer = new QTimer; + m_initialReadTimer->setSingleShot(true); + QObject::connect(m_initialReadTimer, &QTimer::timeout, [this]() + { + loadNow(); + m_initialReadTimer->deleteLater(); + m_initialReadTimer = 0; + }); + m_initialReadTimer->start(0); + + // cppcheck-suppress pureVirtualCall + m_appQuitConnection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [this](){saveNow();}); +} +BaseConfigObject::~BaseConfigObject() +{ + delete m_saveTimer; + if (m_initialReadTimer) + { + delete m_initialReadTimer; + } + QObject::disconnect(m_appQuitConnection); +} + +void BaseConfigObject::setSaveTimeout(int msec) +{ + m_saveTimer->setInterval(msec); +} + +void BaseConfigObject::scheduleSave() +{ + m_saveTimer->stop(); + m_saveTimer->start(); +} +void BaseConfigObject::saveNow() +{ + if (m_saveTimer->isActive()) + { + m_saveTimer->stop(); + } + if (m_disableSaving) + { + return; + } + + QSaveFile file(m_filename); + if (!file.open(QFile::WriteOnly)) + { + qWarning() << "Couldn't open" << m_filename << "for writing:" << file.errorString(); + return; + } + // cppcheck-suppress pureVirtualCall + file.write(doSave()); + + if (!file.commit()) + { + qCritical() << "Unable to commit the file" << file.fileName() << ":" << file.errorString(); + file.cancelWriting(); + } +} +void BaseConfigObject::loadNow() +{ + if (m_saveTimer->isActive()) + { + saveNow(); + } + + QFile file(m_filename); + if (!file.exists()) + { + return; + } + if (!file.open(QFile::ReadOnly)) + { + qWarning() << "Couldn't open" << m_filename << "for reading:" << file.errorString(); + return; + } + try + { + doLoad(file.readAll()); + } + catch (Exception &e) + { + qWarning() << "Error loading" << m_filename << ":" << e.cause(); + } +} diff --git a/logic/BaseConfigObject.h b/logic/BaseConfigObject.h new file mode 100644 index 00000000..1c96b3d1 --- /dev/null +++ b/logic/BaseConfigObject.h @@ -0,0 +1,50 @@ +/* Copyright 2015 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 + +class QTimer; + +class BaseConfigObject +{ +public: + void setSaveTimeout(int msec); + +protected: + explicit BaseConfigObject(const QString &filename); + virtual ~BaseConfigObject(); + + // cppcheck-suppress pureVirtualCall + virtual QByteArray doSave() const = 0; + virtual void doLoad(const QByteArray &data) = 0; + + void setSavingDisabled(bool savingDisabled) { m_disableSaving = savingDisabled; } + + QString fileName() const { return m_filename; } + +public: + void scheduleSave(); + void saveNow(); + void loadNow(); + +private: + QTimer *m_saveTimer; + QTimer *m_initialReadTimer; + QString m_filename; + QMetaObject::Connection m_appQuitConnection; + bool m_disableSaving = false; +}; diff --git a/logic/CMakeLists.txt b/logic/CMakeLists.txt index de1940ad..d91fc694 100644 --- a/logic/CMakeLists.txt +++ b/logic/CMakeLists.txt @@ -1,6 +1,6 @@ project(MultiMC-Logic) -SET(LOGIC_SOURCES +set(LOGIC_SOURCES # LOGIC - Base classes and infrastructure BaseInstaller.h BaseInstaller.cpp @@ -14,11 +14,14 @@ SET(LOGIC_SOURCES BaseInstance.h BaseInstance.cpp NullInstance.h - MMCError.h MMCZip.h MMCZip.cpp MMCStrings.h MMCStrings.cpp + BaseConfigObject.h + BaseConfigObject.cpp + AbstractCommonModel.h + AbstractCommonModel.cpp # Prefix tree where node names are strings between separators SeparatorPrefixTree.h @@ -28,8 +31,11 @@ SET(LOGIC_SOURCES Env.cpp # JSON parsing helpers - MMCJson.h - MMCJson.cpp + Json.h + Json.cpp + FileSystem.h + FileSystem.cpp + Exception.h # RW lock protected map RWStorage.h @@ -40,6 +46,20 @@ SET(LOGIC_SOURCES # a smart pointer wrapper intended for safer use with Qt signal/slot mechanisms QObjectPtr.h + # Resources + resources/IconResourceHandler.cpp + resources/IconResourceHandler.h + resources/Resource.cpp + resources/Resource.h + resources/ResourceHandler.cpp + resources/ResourceHandler.h + resources/ResourceObserver.cpp + resources/ResourceObserver.h + resources/WebResourceHandler.cpp + resources/WebResourceHandler.h + resources/ResourceProxyModel.h + resources/ResourceProxyModel.cpp + # network stuffs net/NetAction.h net/MD5EtagDownload.h @@ -183,6 +203,8 @@ SET(LOGIC_SOURCES tasks/ThreadTask.cpp tasks/SequentialTask.h tasks/SequentialTask.cpp + tasks/StandardTask.h + tasks/StandardTask.cpp # Settings settings/INIFile.cpp diff --git a/logic/Env.cpp b/logic/Env.cpp index 0607c7ea..2f26f211 100644 --- a/logic/Env.cpp +++ b/logic/Env.cpp @@ -148,6 +148,7 @@ void Env::initHttpMetaCache(QString rootPath, QString staticDataPath) m_metacache->addBase("skins", QDir("accounts/skins").absolutePath()); m_metacache->addBase("root", QDir(rootPath).absolutePath()); m_metacache->addBase("translations", QDir(staticDataPath + "/translations").absolutePath()); + m_metacache->addBase("icons", QDir("cache/icons").absolutePath()); m_metacache->Load(); } @@ -214,4 +215,4 @@ void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QStr qDebug() << proxyDesc; } -#include "Env.moc" \ No newline at end of file +#include "Env.moc" diff --git a/logic/Exception.h b/logic/Exception.h new file mode 100644 index 00000000..2664910e --- /dev/null +++ b/logic/Exception.h @@ -0,0 +1,41 @@ +// Licensed under the Apache-2.0 license. See README.md for details. + +#pragma once + +#include +#include +#include + +class Exception : public std::exception +{ +public: + Exception(const QString &message) : std::exception(), m_message(message) + { + qCritical() << "Exception:" << message; + } + Exception(const Exception &other) + : std::exception(), m_message(other.cause()) + { + } + virtual ~Exception() noexcept {} + const char *what() const noexcept + { + return m_message.toLatin1().constData(); + } + QString cause() const + { + return m_message; + } + +private: + QString m_message; +}; + +#define DECLARE_EXCEPTION(name) \ + class name##Exception : public ::Exception \ + { \ + public: \ + name##Exception(const QString &message) : Exception(message) \ + { \ + } \ + } diff --git a/logic/FileSystem.cpp b/logic/FileSystem.cpp new file mode 100644 index 00000000..b8d82c51 --- /dev/null +++ b/logic/FileSystem.cpp @@ -0,0 +1,56 @@ +// Licensed under the Apache-2.0 license. See README.md for details. + +#include "FileSystem.h" + +#include +#include +#include + +void ensureExists(const QDir &dir) +{ + if (!QDir().mkpath(dir.absolutePath())) + { + throw FS::FileSystemException("Unable to create directory " + dir.dirName() + " (" + + dir.absolutePath() + ")"); + } +} + +void FS::write(const QString &filename, const QByteArray &data) +{ + ensureExists(QFileInfo(filename).dir()); + QSaveFile file(filename); + if (!file.open(QSaveFile::WriteOnly)) + { + throw FileSystemException("Couldn't open " + filename + " for writing: " + + file.errorString()); + } + if (data.size() != file.write(data)) + { + throw FileSystemException("Error writing data to " + filename + ": " + + file.errorString()); + } + if (!file.commit()) + { + throw FileSystemException("Error while committing data to " + filename + ": " + + file.errorString()); + } +} + +QByteArray FS::read(const QString &filename) +{ + QFile file(filename); + if (!file.open(QFile::ReadOnly)) + { + throw FileSystemException("Unable to open " + filename + " for reading: " + + file.errorString()); + } + const qint64 size = file.size(); + QByteArray data(int(size), 0); + const qint64 ret = file.read(data.data(), size); + if (ret == -1 || ret != size) + { + throw FileSystemException("Error reading data from " + filename + ": " + + file.errorString()); + } + return data; +} diff --git a/logic/FileSystem.h b/logic/FileSystem.h new file mode 100644 index 00000000..e70f3165 --- /dev/null +++ b/logic/FileSystem.h @@ -0,0 +1,13 @@ +// Licensed under the Apache-2.0 license. See README.md for details. + +#pragma once + +#include "Exception.h" + +namespace FS +{ +DECLARE_EXCEPTION(FileSystem); + +void write(const QString &filename, const QByteArray &data); +QByteArray read(const QString &filename); +} diff --git a/logic/Json.cpp b/logic/Json.cpp new file mode 100644 index 00000000..46055909 --- /dev/null +++ b/logic/Json.cpp @@ -0,0 +1,278 @@ +// Licensed under the Apache-2.0 license. See README.md for details. + +#include "Json.h" + +#include +#include + +#include "FileSystem.h" +#include + +namespace Json +{ +void write(const QJsonDocument &doc, const QString &filename) +{ + FS::write(filename, doc.toJson()); +} +void write(const QJsonObject &object, const QString &filename) +{ + write(QJsonDocument(object), filename); +} +void write(const QJsonArray &array, const QString &filename) +{ + write(QJsonDocument(array), filename); +} + +QByteArray toBinary(const QJsonObject &obj) +{ + return QJsonDocument(obj).toBinaryData(); +} +QByteArray toBinary(const QJsonArray &array) +{ + return QJsonDocument(array).toBinaryData(); +} +QByteArray toText(const QJsonObject &obj) +{ + return QJsonDocument(obj).toJson(QJsonDocument::Compact); +} +QByteArray toText(const QJsonArray &array) +{ + return QJsonDocument(array).toJson(QJsonDocument::Compact); +} + +static bool isBinaryJson(const QByteArray &data) +{ + decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag; + return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0; +} +QJsonDocument ensureDocument(const QByteArray &data, const QString &what) +{ + if (isBinaryJson(data)) + { + QJsonDocument doc = QJsonDocument::fromBinaryData(data); + if (doc.isNull()) + { + throw JsonException(what + ": Invalid JSON (binary JSON detected)"); + } + return doc; + } + else + { + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) + { + throw JsonException(what + ": Error parsing JSON: " + error.errorString()); + } + return doc; + } +} +QJsonDocument ensureDocument(const QString &filename, const QString &what) +{ + return ensureDocument(FS::read(filename), what); +} +QJsonObject ensureObject(const QJsonDocument &doc, const QString &what) +{ + if (!doc.isObject()) + { + throw JsonException(what + " is not an object"); + } + return doc.object(); +} +QJsonArray ensureArray(const QJsonDocument &doc, const QString &what) +{ + if (!doc.isArray()) + { + throw JsonException(what + " is not an array"); + } + return doc.array(); +} + +void writeString(QJsonObject &to, const QString &key, const QString &value) +{ + if (!value.isEmpty()) + { + to.insert(key, value); + } +} + +void writeStringList(QJsonObject &to, const QString &key, const QStringList &values) +{ + if (!values.isEmpty()) + { + QJsonArray array; + for(auto value: values) + { + array.append(value); + } + to.insert(key, array); + } +} + +template<> +QJsonValue toJson(const QUrl &url) +{ + return QJsonValue(url.toString(QUrl::FullyEncoded)); +} +template<> +QJsonValue toJson(const QByteArray &data) +{ + return QJsonValue(QString::fromLatin1(data.toHex())); +} +template<> +QJsonValue toJson(const QDateTime &datetime) +{ + return QJsonValue(datetime.toString(Qt::ISODate)); +} +template<> +QJsonValue toJson(const QDir &dir) +{ + return QDir::current().relativeFilePath(dir.absolutePath()); +} +template<> +QJsonValue toJson(const QUuid &uuid) +{ + return uuid.toString(); +} +template<> +QJsonValue toJson(const QVariant &variant) +{ + return QJsonValue::fromVariant(variant); +} + + +template<> QByteArray ensureIsType(const QJsonValue &value, const Requirement, + const QString &what) +{ + const QString string = ensureIsType(value, Required, what); + // ensure that the string can be safely cast to Latin1 + if (string != QString::fromLatin1(string.toLatin1())) + { + throw JsonException(what + " is not encodable as Latin1"); + } + return QByteArray::fromHex(string.toLatin1()); +} + +template<> QJsonArray ensureIsType(const QJsonValue &value, const Requirement, const QString &what) +{ + if (!value.isArray()) + { + throw JsonException(what + " is not an array"); + } + return value.toArray(); +} + + +template<> QString ensureIsType(const QJsonValue &value, const Requirement, const QString &what) +{ + if (!value.isString()) + { + throw JsonException(what + " is not a string"); + } + return value.toString(); +} + +template<> bool ensureIsType(const QJsonValue &value, const Requirement, + const QString &what) +{ + if (!value.isBool()) + { + throw JsonException(what + " is not a bool"); + } + return value.toBool(); +} + +template<> double ensureIsType(const QJsonValue &value, const Requirement, + const QString &what) +{ + if (!value.isDouble()) + { + throw JsonException(what + " is not a double"); + } + return value.toDouble(); +} + +template<> int ensureIsType(const QJsonValue &value, const Requirement, + const QString &what) +{ + const double doubl = ensureIsType(value, Required, what); + if (fmod(doubl, 1) != 0) + { + throw JsonException(what + " is not an integer"); + } + return int(doubl); +} + +template<> QDateTime ensureIsType(const QJsonValue &value, const Requirement, + const QString &what) +{ + const QString string = ensureIsType(value, Required, what); + const QDateTime datetime = QDateTime::fromString(string, Qt::ISODate); + if (!datetime.isValid()) + { + throw JsonException(what + " is not a ISO formatted date/time value"); + } + return datetime; +} + +template<> QUrl ensureIsType(const QJsonValue &value, const Requirement, + const QString &what) +{ + const QString string = ensureIsType(value, Required, what); + if (string.isEmpty()) + { + return QUrl(); + } + const QUrl url = QUrl(string, QUrl::StrictMode); + if (!url.isValid()) + { + throw JsonException(what + " is not a correctly formatted URL"); + } + return url; +} + +template<> QDir ensureIsType(const QJsonValue &value, const Requirement, const QString &what) +{ + const QString string = ensureIsType(value, Required, what); + return QDir::current().absoluteFilePath(string); +} + +template<> QUuid ensureIsType(const QJsonValue &value, const Requirement, const QString &what) +{ + const QString string = ensureIsType(value, Required, what); + const QUuid uuid = QUuid(string); + if (uuid.toString() != string) // converts back => valid + { + throw JsonException(what + " is not a valid UUID"); + } + return uuid; +} + +template<> QJsonObject ensureIsType(const QJsonValue &value, const Requirement, const QString &what) +{ + if (!value.isObject()) + { + throw JsonException(what + " is not an object"); + } + return value.toObject(); +} + +template<> QVariant ensureIsType(const QJsonValue &value, const Requirement, const QString &what) +{ + if (value.isNull() || value.isUndefined()) + { + throw JsonException(what + " is null or undefined"); + } + return value.toVariant(); +} + +template<> QJsonValue ensureIsType(const QJsonValue &value, const Requirement, const QString &what) +{ + if (value.isNull() || value.isUndefined()) + { + throw JsonException(what + " is null or undefined"); + } + return value; +} + +} diff --git a/logic/Json.h b/logic/Json.h new file mode 100644 index 00000000..d22aa606 --- /dev/null +++ b/logic/Json.h @@ -0,0 +1,239 @@ +// Licensed under the Apache-2.0 license. See README.md for details. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Exception.h" + +namespace Json +{ +DECLARE_EXCEPTION(Json); + +enum Requirement +{ + Required +}; + +void write(const QJsonDocument &doc, const QString &filename); +void write(const QJsonObject &object, const QString &filename); +void write(const QJsonArray &array, const QString &filename); +QByteArray toBinary(const QJsonObject &obj); +QByteArray toBinary(const QJsonArray &array); +QByteArray toText(const QJsonObject &obj); +QByteArray toText(const QJsonArray &array); + +QJsonDocument ensureDocument(const QByteArray &data, const QString &what = "Document"); +QJsonDocument ensureDocument(const QString &filename, const QString &what = "Document"); +QJsonObject ensureObject(const QJsonDocument &doc, const QString &what = "Document"); +QJsonArray ensureArray(const QJsonDocument &doc, const QString &what = "Document"); + +/////////////////// WRITING //////////////////// + +void writeString(QJsonObject & to, const QString &key, const QString &value); +void writeStringList(QJsonObject & to, const QString &key, const QStringList &values); + +template +void writeObjectList(QJsonObject & to, QString key, QList> values) +{ + if (!values.isEmpty()) + { + QJsonArray array; + for (auto value: values) + { + array.append(value->toJson()); + } + to.insert(key, array); + } +} +template +void writeObjectList(QJsonObject & to, QString key, QList values) +{ + if (!values.isEmpty()) + { + QJsonArray array; + for (auto value: values) + { + array.append(value.toJson()); + } + to.insert(key, array); + } +} + +template +QJsonValue toJson(const T &t) +{ + return QJsonValue(t); +} +template<> +QJsonValue toJson(const QUrl &url); +template<> +QJsonValue toJson(const QByteArray &data); +template<> +QJsonValue toJson(const QDateTime &datetime); +template<> +QJsonValue toJson(const QDir &dir); +template<> +QJsonValue toJson(const QUuid &uuid); +template<> +QJsonValue toJson(const QVariant &variant); + +template +QJsonArray toJsonArray(const QList &container) +{ + QJsonArray array; + for (const T item : container) + { + array.append(toJson(item)); + } + return array; +} + +////////////////// READING //////////////////// + +template +T ensureIsType(const QJsonValue &value, const Requirement requirement = Required, const QString &what = "Value"); + +template<> double ensureIsType(const QJsonValue &value, const Requirement, const QString &what); +template<> bool ensureIsType(const QJsonValue &value, const Requirement, const QString &what); +template<> int ensureIsType(const QJsonValue &value, const Requirement, const QString &what); +template<> QJsonObject ensureIsType(const QJsonValue &value, const Requirement, const QString &what); +template<> QJsonArray ensureIsType(const QJsonValue &value, const Requirement, const QString &what); +template<> QJsonValue ensureIsType(const QJsonValue &value, const Requirement, const QString &what); +template<> QByteArray ensureIsType(const QJsonValue &value, const Requirement, const QString &what); +template<> QDateTime ensureIsType(const QJsonValue &value, const Requirement, const QString &what); +template<> QVariant ensureIsType(const QJsonValue &value, const Requirement, const QString &what); +template<> QString ensureIsType(const QJsonValue &value, const Requirement, const QString &what); +template<> QUuid ensureIsType(const QJsonValue &value, const Requirement, const QString &what); +template<> QDir ensureIsType(const QJsonValue &value, const Requirement, const QString &what); +template<> QUrl ensureIsType(const QJsonValue &value, const Requirement, const QString &what); + +// the following functions are higher level functions, that make use of the above functions for +// type conversion +template +T ensureIsType(const QJsonValue &value, const T default_, const QString &what = "Value") +{ + if (value.isUndefined()) + { + return default_; + } + return ensureIsType(value, Required, what); +} +template +T ensureIsType(const QJsonObject &parent, const QString &key, + const Requirement requirement = Required, + const QString &what = "__placeholder__") +{ + const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); + if (!parent.contains(key)) + { + throw JsonException(localWhat + "s parent does not contain " + localWhat); + } + return ensureIsType(parent.value(key), requirement, localWhat); +} +template +T ensureIsType(const QJsonObject &parent, const QString &key, const T default_, + const QString &what = "__placeholder__") +{ + const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); + if (!parent.contains(key)) + { + return default_; + } + return ensureIsType(parent.value(key), default_, localWhat); +} + +template +QList ensureIsArrayOf(const QJsonDocument &doc) +{ + const QJsonArray array = ensureArray(doc); + QList out; + for (const QJsonValue val : array) + { + out.append(ensureIsType(val, Required, "Document")); + } + return out; +} +template +QList ensureIsArrayOf(const QJsonValue &value, const Requirement = Required, + const QString &what = "Value") +{ + const QJsonArray array = ensureIsType(value, Required, what); + QList out; + for (const QJsonValue val : array) + { + out.append(ensureIsType(val, Required, what)); + } + return out; +} +template +QList ensureIsArrayOf(const QJsonValue &value, const QList default_, + const QString &what = "Value") +{ + if (value.isUndefined()) + { + return default_; + } + return ensureIsArrayOf(value, Required, what); +} +template +QList ensureIsArrayOf(const QJsonObject &parent, const QString &key, + const Requirement requirement = Required, + const QString &what = "__placeholder__") +{ + const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); + if (!parent.contains(key)) + { + throw JsonException(localWhat + "s parent does not contain " + localWhat); + } + return ensureIsArrayOf(parent.value(key), requirement, localWhat); +} +template +QList ensureIsArrayOf(const QJsonObject &parent, const QString &key, + const QList &default_, const QString &what = "__placeholder__") +{ + const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); + if (!parent.contains(key)) + { + return default_; + } + return ensureIsArrayOf(parent.value(key), default_, localWhat); +} + +// this macro part could be replaced by variadic functions that just pass on their arguments, but that wouldn't work well with IDE helpers +#define JSON_HELPERFUNCTIONS(NAME, TYPE) \ + inline TYPE ensure##NAME(const QJsonValue &value, const Requirement requirement = Required, const QString &what = "Value") \ +{ return ensureIsType(value, requirement, what); } \ + inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_, const QString &what = "Value") \ +{ return ensureIsType(value, default_, what); } \ + inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const Requirement requirement = Required, const QString &what = "__placeholder__") \ +{ return ensureIsType(parent, key, requirement, what); } \ + inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_, const QString &what = "__placeholder") \ +{ return ensureIsType(parent, key, default_, what); } + +JSON_HELPERFUNCTIONS(Array, QJsonArray) +JSON_HELPERFUNCTIONS(Object, QJsonObject) +JSON_HELPERFUNCTIONS(JsonValue, QJsonValue) +JSON_HELPERFUNCTIONS(String, QString) +JSON_HELPERFUNCTIONS(Boolean, bool) +JSON_HELPERFUNCTIONS(Double, double) +JSON_HELPERFUNCTIONS(Integer, int) +JSON_HELPERFUNCTIONS(DateTime, QDateTime) +JSON_HELPERFUNCTIONS(Url, QUrl) +JSON_HELPERFUNCTIONS(ByteArray, QByteArray) +JSON_HELPERFUNCTIONS(Dir, QDir) +JSON_HELPERFUNCTIONS(Uuid, QUuid) +JSON_HELPERFUNCTIONS(Variant, QVariant) + +#undef JSON_HELPERFUNCTIONS + +} +using JSONValidationError = Json::JsonException; diff --git a/logic/MMCError.h b/logic/MMCError.h deleted file mode 100644 index e81054a6..00000000 --- a/logic/MMCError.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#include -#include -#include - -class MMCError : public std::exception -{ -public: - MMCError(QString cause) - { - exceptionCause = cause; - qCritical() << "Exception: " + cause; - }; - virtual ~MMCError() noexcept {} - virtual const char *what() const noexcept - { - return exceptionCause.toLocal8Bit(); - }; - virtual QString cause() const - { - return exceptionCause; - } -private: - QString exceptionCause; -}; diff --git a/logic/MMCJson.cpp b/logic/MMCJson.cpp deleted file mode 100644 index 23af4fff..00000000 --- a/logic/MMCJson.cpp +++ /dev/null @@ -1,142 +0,0 @@ -#include "MMCJson.h" - -#include -#include -#include -#include - -QJsonDocument MMCJson::parseDocument(const QByteArray &data, const QString &what) -{ - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(data, &error); - if (error.error != QJsonParseError::NoError) - { - throw JSONValidationError(what + " is not valid JSON: " + error.errorString() + " at " + error.offset); - } - return doc; -} - -bool MMCJson::ensureBoolean(const QJsonValue val, const QString what) -{ - if (!val.isBool()) - throw JSONValidationError(what + " is not boolean"); - return val.toBool(); -} - -QJsonValue MMCJson::ensureExists(QJsonValue val, const QString what) -{ - if(val.isUndefined() || val.isUndefined()) - throw JSONValidationError(what + " does not exist"); - return val; -} - -QJsonArray MMCJson::ensureArray(const QJsonValue val, const QString what) -{ - if (!val.isArray()) - throw JSONValidationError(what + " is not an array"); - return val.toArray(); -} - -QJsonArray MMCJson::ensureArray(const QJsonDocument &val, const QString &what) -{ - if (!val.isArray()) - { - throw JSONValidationError(what + " is not an array"); - } - return val.array(); -} - -double MMCJson::ensureDouble(const QJsonValue val, const QString what) -{ - if (!val.isDouble()) - throw JSONValidationError(what + " is not a number"); - return val.toDouble(); -} - -int MMCJson::ensureInteger(const QJsonValue val, const QString what) -{ - double ret = ensureDouble(val, what); - if (fmod(ret, 1) != 0) - throw JSONValidationError(what + " is not an integer"); - return ret; -} - -QJsonObject MMCJson::ensureObject(const QJsonValue val, const QString what) -{ - if (!val.isObject()) - throw JSONValidationError(what + " is not an object"); - return val.toObject(); -} - -QJsonObject MMCJson::ensureObject(const QJsonDocument val, const QString what) -{ - if (!val.isObject()) - throw JSONValidationError(what + " is not an object"); - return val.object(); -} - -QString MMCJson::ensureString(const QJsonValue val, const QString what) -{ - if (!val.isString()) - throw JSONValidationError(what + " is not a string"); - return val.toString(); -} - -QUrl MMCJson::ensureUrl(const QJsonValue &val, const QString &what) -{ - const QUrl url = QUrl(ensureString(val, what)); - if (!url.isValid()) - { - throw JSONValidationError(what + " is not an url"); - } - return url; -} - -QJsonDocument MMCJson::parseFile(const QString &filename, const QString &what) -{ - QFile f(filename); - if (!f.open(QFile::ReadOnly)) - { - throw FileOpenError(f); - } - return parseDocument(f.readAll(), what); -} - -int MMCJson::ensureInteger(const QJsonValue val, QString what, const int def) -{ - if (val.isUndefined()) - return def; - return ensureInteger(val, what); -} - -void MMCJson::writeString(QJsonObject &to, QString key, QString value) -{ - if (!value.isEmpty()) - { - to.insert(key, value); - } -} - -void MMCJson::writeStringList(QJsonObject &to, QString key, QStringList values) -{ - if (!values.isEmpty()) - { - QJsonArray array; - for(auto value: values) - { - array.append(value); - } - to.insert(key, array); - } -} - -QStringList MMCJson::ensureStringList(const QJsonValue val, QString what) -{ - const QJsonArray array = ensureArray(val, what); - QStringList out; - for (const auto value : array) - { - out.append(ensureString(value)); - } - return out; -} diff --git a/logic/MMCJson.h b/logic/MMCJson.h deleted file mode 100644 index dc0b4224..00000000 --- a/logic/MMCJson.h +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Some de-bullshitting for Qt JSON failures. - * - * Simple exception-throwing - */ - -#pragma once -#include -#include -#include -#include -#include -#include -#include "MMCError.h" - -class JSONValidationError : public MMCError -{ -public: - JSONValidationError(QString cause) : MMCError(cause) {} -}; -class FileOpenError : public MMCError -{ -public: - FileOpenError(const QFile &file) : MMCError(QObject::tr("Error opening %1: %2").arg(file.fileName(), file.errorString())) {} -}; - -namespace MMCJson -{ -/// parses the data into a json document. throws if there's a parse error -QJsonDocument parseDocument(const QByteArray &data, const QString &what); - -/// tries to open and then parses the specified file. throws if there's an error -QJsonDocument parseFile(const QString &filename, const QString &what); - -/// make sure the value exists. throw otherwise. -QJsonValue ensure