NOISSUE Various changes from multiauth that are unrelated to it

This commit is contained in:
Jan Dalheimer 2015-05-28 19:38:29 +02:00 committed by Petr Mrázek
parent 161dc66c2c
commit 3a8b238052
65 changed files with 2661 additions and 333 deletions

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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)

View File

@ -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;

View File

@ -13,7 +13,6 @@ int main_gui(MultiMC &app)
mainWin.checkInstancePathForProblems();
return app.exec();
}
int main(int argc, char *argv[])
{
// initialize Qt

View File

@ -47,7 +47,7 @@
#include <minecraft/MinecraftVersion.h>
#include <minecraft/MinecraftVersionList.h>
#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());
}

View File

@ -21,7 +21,7 @@
#include "pages/BasePage.h"
#include "auth/MojangAccountList.h"
#include <MultiMC.h>
#include "MultiMC.h"
namespace Ui
{

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -35,6 +35,9 @@ Size=64
[256x256]
Size=256
[150x150]
Size=150
[scalable]
Size=48
Type=Scalable

View File

@ -207,6 +207,13 @@
<file>48x48/log.png</file>
<file>64x64/log.png</file>
<!-- Hour glass, CC-BY, http://findicons.com/icon/84653/hourglass?id=360551 -->
<file>16x16/hourglass.png</file>
<file>22x22/hourglass.png</file>
<file>32x32/hourglass.png</file>
<file>48x48/hourglass.png</file>
<file>150x150/hourglass.png</file>
<!-- placeholder when loading screenshot images -->
<file>scalable/screenshot-placeholder.svg</file>
</qresource>

View File

@ -0,0 +1,74 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#include "ProgressWidget.h"
#include <QProgressBar>
#include <QLabel>
#include <QVBoxLayout>
#include <QEventLoop>
#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> 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> 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;
}

View File

@ -0,0 +1,32 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#pragma once
#include <QWidget>
#include <memory>
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> task);
bool exec(std::shared_ptr<Task> 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<Task> m_task;
};

View File

@ -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<int>() << 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();
}

462
logic/AbstractCommonModel.h Normal file
View File

@ -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 <QAbstractListModel>
#include <type_traits>
#include <functional>
#include <memory>
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<typename Object>
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<typename T>
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<T>();
}
QVariant get(const Object &object) const override
{
return QVariant::fromValue<T>(object.*m_member);
}
bool canSet() const override { return true; }
private:
Member m_member;
};
template<typename T>
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<T>());
}
QVariant get(const Object &object) const override
{
return QVariant::fromValue<T>(object.*m_getter());
}
bool canSet() const override { return !!m_setter; }
private:
Getter m_getter;
Setter m_setter;
};
QList<Object> m_objects;
QVector<QPair<QString, QMap<int, IEntry *>>> 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 Getter, typename Setter>
typename std::enable_if<std::is_member_function_pointer<Getter>::value && std::is_member_function_pointer<Getter>::value, void>::type
addEntry(Getter getter, Setter setter, const int entry, const int role)
{
addEntryInternal(new FunctionEntry<typename std::result_of<Getter>::type>(getter, setter), entry, role);
}
template<typename Getter>
typename std::enable_if<std::is_member_function_pointer<Getter>::value, void>::type
addEntry(Getter getter, const int entry, const int role)
{
addEntryInternal(new FunctionEntry<typename std::result_of<Getter>::type>(getter, nullptr), entry, role);
}
template<typename T>
typename std::enable_if<!std::is_member_function_pointer<T (Object::*)>::value, void>::type
addEntry(T (Object::*member), const int entry, const int role)
{
addEntryInternal(new VariableEntry<T>(member), entry, role);
}
void setEntryTitle(const int entry, const QString &title)
{
m_entries[entry].first = title;
}
};
template<typename Object>
class AbstractCommonModel<Object *> : 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<Object *> 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<typename T>
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<T>();
}
QVariant get(Object *object) const override
{
return QVariant::fromValue<T>(object->*m_member);
}
bool canSet() const override { return true; }
private:
Member m_member;
};
template<typename T>
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<T>());
}
QVariant get(Object *object) const override
{
return QVariant::fromValue<T>((object->*m_getter)());
}
bool canSet() const override { return !!m_setter; }
private:
Getter m_getter;
Setter m_setter;
};
template<typename T>
struct LambdaEntry : public IEntry
{
using Getter = std::function<T(Object *)>;
explicit LambdaEntry(Getter getter)
: m_getter(getter) {}
void set(Object *object, const QVariant &value) override {}
QVariant get(Object *object) const override
{
return QVariant::fromValue<T>(m_getter(object));
}
bool canSet() const override { return false; }
private:
Getter m_getter;
};
QList<Object *> m_objects;
QVector<QPair<QString, QMap<int, IEntry *>>> 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 Getter, typename Setter>
typename std::enable_if<std::is_member_function_pointer<Getter>::value && std::is_member_function_pointer<Getter>::value, void>::type
addEntry(const int entry, const int role, Getter getter, Setter setter)
{
addEntryInternal(new FunctionEntry<typename std::result_of<Getter>::type>(getter, setter), entry, role);
}
template<typename T>
typename std::enable_if<std::is_member_function_pointer<typename FunctionEntry<T>::Getter>::value, void>::type
addEntry(const int entry, const int role, typename FunctionEntry<T>::Getter getter)
{
addEntryInternal(new FunctionEntry<T>(getter, nullptr), entry, role);
}
template<typename T>
typename std::enable_if<!std::is_member_function_pointer<T (Object::*)>::value, void>::type
addEntry(const int entry, const int role, T (Object::*member))
{
addEntryInternal(new VariableEntry<T>(member), entry, role);
}
template<typename T>
void addEntry(const int entry, const int role, typename LambdaEntry<T>::Getter lambda)
{
addEntryInternal(new LambdaEntry<T>(lambda), entry, role);
}
void setEntryTitle(const int entry, const QString &title)
{
m_entries[entry].first = title;
}
void setAll(const QList<Object *> objects)
{
notifyBeginReset();
qDeleteAll(m_objects);
m_objects = objects;
notifyEndReset();
}
};

119
logic/BaseConfigObject.cpp Normal file
View File

@ -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 <QTimer>
#include <QSaveFile>
#include <QFile>
#include <QCoreApplication>
#include <QDebug>
#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();
}
}

50
logic/BaseConfigObject.h Normal file
View File

@ -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 <QObject>
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;
};

View File

@ -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

View File

@ -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"
#include "Env.moc"

41
logic/Exception.h Normal file
View File

@ -0,0 +1,41 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#pragma once
#include <QString>
#include <QLoggingCategory>
#include <exception>
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) \
{ \
} \
}

56
logic/FileSystem.cpp Normal file
View File

@ -0,0 +1,56 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#include "FileSystem.h"
#include <QDir>
#include <QSaveFile>
#include <QFileInfo>
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;
}

13
logic/FileSystem.h Normal file
View File

@ -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);
}

278
logic/Json.cpp Normal file
View File

@ -0,0 +1,278 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#include "Json.h"
#include <QFile>
#include <QSaveFile>
#include "FileSystem.h"
#include <math.h>
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<QUrl>(const QUrl &url)
{
return QJsonValue(url.toString(QUrl::FullyEncoded));
}
template<>
QJsonValue toJson<QByteArray>(const QByteArray &data)
{
return QJsonValue(QString::fromLatin1(data.toHex()));
}
template<>
QJsonValue toJson<QDateTime>(const QDateTime &datetime)
{
return QJsonValue(datetime.toString(Qt::ISODate));
}
template<>
QJsonValue toJson<QDir>(const QDir &dir)
{
return QDir::current().relativeFilePath(dir.absolutePath());
}
template<>
QJsonValue toJson<QUuid>(const QUuid &uuid)
{
return uuid.toString();
}
template<>
QJsonValue toJson<QVariant>(const QVariant &variant)
{
return QJsonValue::fromVariant(variant);
}
template<> QByteArray ensureIsType<QByteArray>(const QJsonValue &value, const Requirement,
const QString &what)
{
const QString string = ensureIsType<QString>(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<QJsonArray>(const QJsonValue &value, const Requirement, const QString &what)
{
if (!value.isArray())
{
throw JsonException(what + " is not an array");
}
return value.toArray();
}
template<> QString ensureIsType<QString>(const QJsonValue &value, const Requirement, const QString &what)
{
if (!value.isString())
{
throw JsonException(what + " is not a string");
}
return value.toString();
}
template<> bool ensureIsType<bool>(const QJsonValue &value, const Requirement,
const QString &what)
{
if (!value.isBool())
{
throw JsonException(what + " is not a bool");
}
return value.toBool();
}
template<> double ensureIsType<double>(const QJsonValue &value, const Requirement,
const QString &what)
{
if (!value.isDouble())
{
throw JsonException(what + " is not a double");
}
return value.toDouble();
}
template<> int ensureIsType<int>(const QJsonValue &value, const Requirement,
const QString &what)
{
const double doubl = ensureIsType<double>(value, Required, what);
if (fmod(doubl, 1) != 0)
{
throw JsonException(what + " is not an integer");
}
return int(doubl);
}
template<> QDateTime ensureIsType<QDateTime>(const QJsonValue &value, const Requirement,
const QString &what)
{
const QString string = ensureIsType<QString>(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<QUrl>(const QJsonValue &value, const Requirement,
const QString &what)
{
const QString string = ensureIsType<QString>(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<QDir>(const QJsonValue &value, const Requirement, const QString &what)
{
const QString string = ensureIsType<QString>(value, Required, what);
return QDir::current().absoluteFilePath(string);
}
template<> QUuid ensureIsType<QUuid>(const QJsonValue &value, const Requirement, const QString &what)
{
const QString string = ensureIsType<QString>(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<QJsonObject>(const QJsonValue &value, const Requirement, const QString &what)
{
if (!value.isObject())
{
throw JsonException(what + " is not an object");
}
return value.toObject();
}
template<> QVariant ensureIsType<QVariant>(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<QJsonValue>(const QJsonValue &value, const Requirement, const QString &what)
{
if (value.isNull() || value.isUndefined())
{
throw JsonException(what + " is null or undefined");
}
return value;
}
}

239
logic/Json.h Normal file
View File

@ -0,0 +1,239 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#pragma once
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QDateTime>
#include <QUrl>
#include <QDir>
#include <QUuid>
#include <QVariant>
#include <memory>
#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 <typename T>
void writeObjectList(QJsonObject & to, QString key, QList<std::shared_ptr<T>> values)
{
if (!values.isEmpty())
{
QJsonArray array;
for (auto value: values)
{
array.append(value->toJson());
}
to.insert(key, array);
}
}
template <typename T>
void writeObjectList(QJsonObject & to, QString key, QList<T> values)
{
if (!values.isEmpty())
{
QJsonArray array;
for (auto value: values)
{
array.append(value.toJson());
}
to.insert(key, array);
}
}
template<typename T>
QJsonValue toJson(const T &t)
{
return QJsonValue(t);
}
template<>
QJsonValue toJson<QUrl>(const QUrl &url);
template<>
QJsonValue toJson<QByteArray>(const QByteArray &data);
template<>
QJsonValue toJson<QDateTime>(const QDateTime &datetime);
template<>
QJsonValue toJson<QDir>(const QDir &dir);
template<>
QJsonValue toJson<QUuid>(const QUuid &uuid);
template<>
QJsonValue toJson<QVariant>(const QVariant &variant);
template<typename T>
QJsonArray toJsonArray(const QList<T> &container)
{
QJsonArray array;
for (const T item : container)
{
array.append(toJson<T>(item));
}
return array;
}
////////////////// READING ////////////////////
template <typename T>
T ensureIsType(const QJsonValue &value, const Requirement requirement = Required, const QString &what = "Value");
template<> double ensureIsType<double>(const QJsonValue &value, const Requirement, const QString &what);
template<> bool ensureIsType<bool>(const QJsonValue &value, const Requirement, const QString &what);
template<> int ensureIsType<int>(const QJsonValue &value, const Requirement, const QString &what);
template<> QJsonObject ensureIsType<QJsonObject>(const QJsonValue &value, const Requirement, const QString &what);
template<> QJsonArray ensureIsType<QJsonArray>(const QJsonValue &value, const Requirement, const QString &what);
template<> QJsonValue ensureIsType<QJsonValue>(const QJsonValue &value, const Requirement, const QString &what);
template<> QByteArray ensureIsType<QByteArray>(const QJsonValue &value, const Requirement, const QString &what);
template<> QDateTime ensureIsType<QDateTime>(const QJsonValue &value, const Requirement, const QString &what);
template<> QVariant ensureIsType<QVariant>(const QJsonValue &value, const Requirement, const QString &what);
template<> QString ensureIsType<QString>(const QJsonValue &value, const Requirement, const QString &what);
template<> QUuid ensureIsType<QUuid>(const QJsonValue &value, const Requirement, const QString &what);
template<> QDir ensureIsType<QDir>(const QJsonValue &value, const Requirement, const QString &what);
template<> QUrl ensureIsType<QUrl>(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 <typename T>
T ensureIsType(const QJsonValue &value, const T default_, const QString &what = "Value")
{
if (value.isUndefined())
{
return default_;
}
return ensureIsType<T>(value, Required, what);
}
template <typename T>
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<T>(parent.value(key), requirement, localWhat);
}
template <typename T>
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<T>(parent.value(key), default_, localWhat);
}
template <typename T>
QList<T> ensureIsArrayOf(const QJsonDocument &doc)
{
const QJsonArray array = ensureArray(doc);
QList<T> out;
for (const QJsonValue val : array)
{
out.append(ensureIsType<T>(val, Required, "Document"));
}
return out;
}
template <typename T>
QList<T> ensureIsArrayOf(const QJsonValue &value, const Requirement = Required,
const QString &what = "Value")
{
const QJsonArray array = ensureIsType<QJsonArray>(value, Required, what);
QList<T> out;
for (const QJsonValue val : array)
{
out.append(ensureIsType<T>(val, Required, what));
}
return out;
}
template <typename T>
QList<T> ensureIsArrayOf(const QJsonValue &value, const QList<T> default_,
const QString &what = "Value")
{
if (value.isUndefined())
{
return default_;
}
return ensureIsArrayOf<T>(value, Required, what);
}
template <typename T>
QList<T> 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<T>(parent.value(key), requirement, localWhat);
}
template <typename T>
QList<T> ensureIsArrayOf(const QJsonObject &parent, const QString &key,
const QList<T> &default_, const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key))
{
return default_;
}
return ensureIsArrayOf<T>(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<TYPE>(value, requirement, what); } \
inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_, const QString &what = "Value") \
{ return ensureIsType<TYPE>(value, default_, what); } \
inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const Requirement requirement = Required, const QString &what = "__placeholder__") \
{ return ensureIsType<TYPE>(parent, key, requirement, what); } \
inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_, const QString &what = "__placeholder") \
{ return ensureIsType<TYPE>(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;

View File

@ -1,25 +0,0 @@
#pragma once
#include <exception>
#include <QString>
#include <QDebug>
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;
};

View File

@ -1,142 +0,0 @@
#include "MMCJson.h"
#include <QString>
#include <QUrl>
#include <QStringList>
#include <math.h>
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;
}

View File

@ -1,101 +0,0 @@
/**
* Some de-bullshitting for Qt JSON failures.
*
* Simple exception-throwing
*/
#pragma once
#include <QJsonValue>
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
#include <QFile>
#include <memory>
#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 ensureExists(QJsonValue val, const QString what = "value");
/// make sure the value is converted into an object. throw otherwise.
QJsonObject ensureObject(const QJsonValue val, const QString what = "value");
/// make sure the document is converted into an object. throw otherwise.
QJsonObject ensureObject(const QJsonDocument val, const QString what = "document");
/// make sure the value is converted into an array. throw otherwise.
QJsonArray ensureArray(const QJsonValue val, QString what = "value");
/// make sure the document is converted into an array. throw otherwise.
QJsonArray ensureArray(const QJsonDocument &val, const QString &what = "document");
/// make sure the value is converted into a string. throw otherwise.
QString ensureString(const QJsonValue val, QString what = "value");
/// make sure the value is converted into a string that's parseable as an url. throw otherwise.
QUrl ensureUrl(const QJsonValue &val, const QString &what = "value");
/// make sure the value is converted into a boolean. throw otherwise.
bool ensureBoolean(const QJsonValue val, QString what = "value");
/// make sure the value is converted into an integer. throw otherwise.
int ensureInteger(const QJsonValue val, QString what = "value");
/// make sure the value is converted into an integer. throw otherwise. this version will return the default value if the field is undefined.
int ensureInteger(const QJsonValue val, QString what, const int def);
/// make sure the value is converted into a double precision floating number. throw otherwise.
double ensureDouble(const QJsonValue val, QString what = "value");
QStringList ensureStringList(const QJsonValue val, QString what);
void writeString(QJsonObject & to, QString key, QString value);
void writeStringList(QJsonObject & to, QString key, QStringList values);
template <typename T>
void writeObjectList(QJsonObject & to, QString key, QList<std::shared_ptr<T>> values)
{
if (!values.isEmpty())
{
QJsonArray array;
for (auto value: values)
{
array.append(value->toJson());
}
to.insert(key, array);
}
}
template <typename T>
void writeObjectList(QJsonObject & to, QString key, QList<T> values)
{
if (!values.isEmpty())
{
QJsonArray array;
for (auto value: values)
{
array.append(value.toJson());
}
to.insert(key, array);
}
}
}

View File

@ -19,6 +19,11 @@ public:
{
m_ptr = other.m_ptr;
}
template<typename Derived>
QObjectPtr(const QObjectPtr<Derived> &other)
{
m_ptr = other.unwrap();
}
public:
void reset(T * wrap)

View File

@ -22,6 +22,7 @@
#include "forge/ForgeVersionList.h"
#include "minecraft/VersionFilterData.h"
#include "Env.h"
#include "Exception.h"
#include <quazip.h>
#include <quazipfile.h>
@ -412,7 +413,7 @@ protected:
m_instance->reloadProfile();
emitSucceeded();
}
catch (MMCError &e)
catch (Exception &e)
{
emitFailed(e.cause());
}

View File

@ -24,6 +24,7 @@
#include "minecraft/OneSixLibrary.h"
#include "minecraft/OneSixInstance.h"
#include "liteloader/LiteLoaderVersionList.h"
#include "Exception.h"
LiteLoaderInstaller::LiteLoaderInstaller() : BaseInstaller()
{
@ -118,7 +119,7 @@ protected:
m_instance->reloadProfile();
emitSucceeded();
}
catch (MMCError &e)
catch (Exception &e)
{
emitFailed(e.cause());
}

View File

@ -16,7 +16,7 @@
#include "LiteLoaderVersionList.h"
#include "Env.h"
#include "net/URLConstants.h"
#include "MMCError.h"
#include "Exception.h"
#include <QtXml>
@ -254,7 +254,7 @@ void LLListLoadTask::listDownloaded()
}
version->libraries.append(lib);
}
catch (MMCError &e)
catch (Exception &e)
{
qCritical() << "Couldn't read JSON object:";
continue;

View File

@ -1,6 +1,6 @@
#include "JarMod.h"
#include "MMCJson.h"
using namespace MMCJson;
#include "Json.h"
using namespace Json;
JarmodPtr Jarmod::fromJson(const QJsonObject &libObj, const QString &filename, const QString &originalName)
{

View File

@ -17,12 +17,13 @@
#include <QDir>
#include <QJsonDocument>
#include <QJsonArray>
#include <QDebug>
#include <pathutils.h>
#include "minecraft/MinecraftProfile.h"
#include "ProfileUtils.h"
#include "NullProfileStrategy.h"
#include "VersionBuildError.h"
#include "Exception.h"
MinecraftProfile::MinecraftProfile(ProfileStrategy *strategy)
: QAbstractListModel()
@ -277,7 +278,7 @@ std::shared_ptr<MinecraftProfile> MinecraftProfile::fromJson(const QJsonObject &
file->applyTo(version.get());
version->appendPatch(file);
}
catch(MMCError & err)
catch(Exception &err)
{
return 0;
}
@ -424,7 +425,7 @@ bool MinecraftProfile::reapplySafe()
{
reapply();
}
catch(MMCError & error)
catch (Exception & error)
{
clear();
qWarning() << "Couldn't apply profile patches because: " << error.cause();

View File

@ -14,12 +14,12 @@
*/
#include <QtXml>
#include "MMCJson.h"
#include "Json.h"
#include <QtAlgorithms>
#include <QtNetwork>
#include "Env.h"
#include "MMCError.h"
#include "Exception.h"
#include "MinecraftVersionList.h"
#include "net/URLConstants.h"
@ -71,10 +71,10 @@ protected:
MinecraftVersionList *m_list;
};
class ListLoadError : public MMCError
class ListLoadError : public Exception
{
public:
ListLoadError(QString cause) : MMCError(cause) {};
ListLoadError(QString cause) : Exception(cause) {};
virtual ~ListLoadError() noexcept
{
}
@ -142,7 +142,7 @@ void MinecraftVersionList::loadCachedList()
}
loadMojangList(jsonDoc, Local);
}
catch (MMCError &e)
catch (Exception &e)
{
// the cache has gone bad for some reason... flush it.
qCritical() << "The minecraft version cache is corrupted. Flushing cache.";
@ -157,12 +157,11 @@ void MinecraftVersionList::loadBuiltinList()
qDebug() << "Loading builtin version list.";
// grab the version list data from internal resources.
const QJsonDocument doc =
MMCJson::parseFile(":/versions/minecraft.json",
"builtin version list");
Json::ensureDocument(QString(":/versions/minecraft.json"), "builtin version list");
const QJsonObject root = doc.object();
// parse all the versions
for (const auto version : MMCJson::ensureArray(root.value("versions")))
for (const auto version : Json::ensureArray(root.value("versions")))
{
QJsonObject versionObj = version.toObject();
QString versionID = versionObj.value("id").toString("");
@ -204,9 +203,9 @@ void MinecraftVersionList::loadBuiltinList()
mcVersion->m_processArguments = versionObj.value("processArguments").toString("legacy");
if (versionObj.contains("+traits"))
{
for (auto traitVal : MMCJson::ensureArray(versionObj.value("+traits")))
for (auto traitVal : Json::ensureArray(versionObj.value("+traits")))
{
mcVersion->m_traits.insert(MMCJson::ensureString(traitVal));
mcVersion->m_traits.insert(Json::ensureString(traitVal));
}
}
m_lookup[versionID] = mcVersion;
@ -227,11 +226,11 @@ void MinecraftVersionList::loadMojangList(QJsonDocument jsonDoc, VersionSource s
try
{
QJsonObject latest = MMCJson::ensureObject(root.value("latest"));
m_latestReleaseID = MMCJson::ensureString(latest.value("release"));
m_latestSnapshotID = MMCJson::ensureString(latest.value("snapshot"));
QJsonObject latest = Json::ensureObject(root.value("latest"));
m_latestReleaseID = Json::ensureString(latest.value("release"));
m_latestSnapshotID = Json::ensureString(latest.value("snapshot"));
}
catch (MMCError &err)
catch (Exception &err)
{
qCritical()
<< tr("Error parsing version list JSON: couldn't determine latest versions");
@ -481,7 +480,7 @@ void MCVListLoadTask::list_downloaded()
}
m_list->loadMojangList(jsonDoc, Remote);
}
catch (MMCError &e)
catch (Exception &e)
{
emitFailed(e.cause());
return;
@ -532,7 +531,7 @@ void MCVListVersionUpdateTask::json_downloaded()
{
file = VersionFile::fromJson(jsonDoc, "net.minecraft.json", false);
}
catch (MMCError &e)
catch (Exception &e)
{
emitFailed(tr("Couldn't process version file: %1").arg(e.cause()));
return;

View File

@ -16,7 +16,6 @@
#include <QIcon>
#include <pathutils.h>
#include <QDebug>
#include "MMCError.h"
#include "minecraft/OneSixInstance.h"
@ -338,7 +337,7 @@ void OneSixInstance::reloadProfile()
catch (VersionIncomplete &error)
{
}
catch (MMCError &error)
catch (Exception &error)
{
m_version->clear();
setFlag(VersionBrokenFlag);

View File

@ -294,7 +294,7 @@ bool OneSixProfileStrategy::customizePatch(ProfilePatchPtr patch)
{
qDebug() << "Version was incomplete:" << error.cause();
}
catch (MMCError &error)
catch (Exception &error)
{
qWarning() << "Version could not be loaded:" << error.cause();
}
@ -324,7 +324,7 @@ bool OneSixProfileStrategy::revertPatch(ProfilePatchPtr patch)
{
qDebug() << "Version was incomplete:" << error.cause();
}
catch (MMCError &error)
catch (Exception &error)
{
qWarning() << "Version could not be loaded:" << error.cause();
}

View File

@ -33,6 +33,7 @@
#include "forge/ForgeMirrors.h"
#include "net/URLConstants.h"
#include "minecraft/AssetsUtils.h"
#include "Exception.h"
#include "MMCZip.h"
OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
@ -182,7 +183,7 @@ void OneSixUpdate::jarlibStart()
{
inst->reloadProfile();
}
catch (MMCError &e)
catch (Exception &e)
{
emitFailed(e.cause());
return;

View File

@ -1,7 +1,6 @@
#include <QDateTime>
#include <QString>
#include "ParseUtils.h"
#include <MMCJson.h>
QDateTime timeFromS3Time(QString str)
{

View File

@ -1,6 +1,6 @@
#include "ProfileUtils.h"
#include "minecraft/VersionFilterData.h"
#include "MMCJson.h"
#include "Json.h"
#include <QDebug>
#include <QJsonDocument>
@ -74,18 +74,18 @@ bool readOverrideOrders(QString path, PatchOrder &order)
// and then read it and process it if all above is true.
try
{
auto obj = MMCJson::ensureObject(doc);
auto obj = Json::ensureObject(doc);
// check order file version.
auto version = MMCJson::ensureInteger(obj.value("version"), "version");
auto version = Json::ensureInteger(obj.value("version"));
if (version != currentOrderFileVersion)
{
throw JSONValidationError(QObject::tr("Invalid order file version, expected %1")
.arg(currentOrderFileVersion));
}
auto orderArray = MMCJson::ensureArray(obj.value("order"));
auto orderArray = Json::ensureArray(obj.value("order"));
for(auto item: orderArray)
{
order.append(MMCJson::ensureString(item));
order.append(Json::ensureString(item));
}
}
catch (JSONValidationError &err)

View File

@ -1,5 +1,5 @@
#include "MMCJson.h"
using namespace MMCJson;
#include "Json.h"
using namespace Json;
#include "RawLibrary.h"
#include <pathutils.h>
@ -74,7 +74,7 @@ RawLibraryPtr RawLibrary::fromJsonPlus(const QJsonObject &libObj, const QString
auto lib = RawLibrary::fromJson(libObj, filename);
if (libObj.contains("insert"))
{
QJsonValue insertVal = ensureExists(libObj.value("insert"), "library insert rule");
QJsonValue insertVal = ensureJsonValue(libObj.value("insert"), "library insert rule");
if (insertVal.isString())
{
// it's just a simple string rule. OK.

View File

@ -1,9 +1,9 @@
#include "MMCError.h"
#include "Exception.h"
class VersionBuildError : public MMCError
class VersionBuildError : public Exception
{
public:
VersionBuildError(QString cause) : MMCError(cause) {};
explicit VersionBuildError(QString cause) : Exception(cause) {}
virtual ~VersionBuildError() noexcept
{
}
@ -55,4 +55,4 @@ public:
virtual ~VersionIncomplete() noexcept
{
}
};
};

View File

@ -10,8 +10,8 @@
#include "minecraft/JarMod.h"
#include "ParseUtils.h"
#include "MMCJson.h"
using namespace MMCJson;
#include "Json.h"
using namespace Json;
#include "VersionBuildError.h"

View File

@ -3,11 +3,12 @@
#include <QString>
#include <QStringList>
#include <QDateTime>
#include <QSet>
#include <memory>
#include "minecraft/OpSys.h"
#include "minecraft/OneSixRule.h"
#include "ProfilePatch.h"
#include "MMCError.h"
#include "OneSixLibrary.h"
#include "JarMod.h"

View File

@ -20,6 +20,29 @@
#include <QCryptographicHash>
#include <QSaveFile>
class INetworkValidator
{
public:
virtual ~INetworkValidator() {}
virtual void validate(const QByteArray &data) = 0;
};
class JsonValidator : public INetworkValidator
{
public:
void validate(const QByteArray &data) override;
};
class MD5HashValidator : public INetworkValidator
{
public:
explicit MD5HashValidator(const QByteArray &expected)
: m_expected(expected) {}
void validate(const QByteArray &data) override;
private:
QByteArray m_expected;
};
typedef std::shared_ptr<class CacheDownload> CacheDownloadPtr;
class CacheDownload : public NetAction
{
@ -33,6 +56,8 @@ private:
/// the hash-as-you-download
QCryptographicHash md5sum;
INetworkValidator *m_validator = nullptr;
bool wroteAnyData = false;
public:
@ -46,6 +71,10 @@ public:
{
return m_target_path;
}
void setValidator(INetworkValidator *validator)
{
m_validator = validator;
}
protected
slots:
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);

View File

@ -0,0 +1,60 @@
#include "IconResourceHandler.h"
#include <QDir>
#include <QDebug>
QString IconResourceHandler::m_theme = "multimc";
QList<std::weak_ptr<IconResourceHandler>> IconResourceHandler::m_iconHandlers;
IconResourceHandler::IconResourceHandler(const QString &key)
: m_key(key)
{
}
void IconResourceHandler::setTheme(const QString &theme)
{
m_theme = theme;
for (auto handler : m_iconHandlers)
{
std::shared_ptr<IconResourceHandler> ptr = handler.lock();
if (ptr)
{
ptr->setResult(ptr->get());
}
}
}
void IconResourceHandler::init(std::shared_ptr<ResourceHandler> &ptr)
{
m_iconHandlers.append(std::dynamic_pointer_cast<IconResourceHandler>(ptr));
setResult(get());
}
QVariant IconResourceHandler::get() const
{
const QDir iconsDir = QDir(":/icons/" + m_theme);
QVariantMap out;
for (const QFileInfo &sizeInfo : iconsDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot))
{
const QDir dir = QDir(sizeInfo.absoluteFilePath());
const QString dirName = sizeInfo.fileName();
const int size = dirName.left(dirName.indexOf('x')).toInt();
if (dir.exists(m_key + ".png") && dirName != "scalable")
{
out.insert(dir.absoluteFilePath(m_key + ".png"), size);
}
else if (dir.exists(m_key + ".svg") && dirName == "scalable")
{
out.insert(dir.absoluteFilePath(m_key + ".svg"), size);
}
}
if (out.isEmpty())
{
qWarning() << "Couldn't find any icons for" << m_key;
}
return out;
}

View File

@ -0,0 +1,22 @@
#pragma once
#include <memory>
#include "ResourceHandler.h"
class IconResourceHandler : public ResourceHandler
{
public:
explicit IconResourceHandler(const QString &key);
static void setTheme(const QString &theme);
private:
void init(std::shared_ptr<ResourceHandler> &ptr) override;
QString m_key;
static QString m_theme;
static QList<std::weak_ptr<IconResourceHandler>> m_iconHandlers;
QVariant get() const;
};

View File

@ -0,0 +1,121 @@
#include "Resource.h"
#include <QDebug>
#include "WebResourceHandler.h"
#include "IconResourceHandler.h"
#include "ResourceObserver.h"
QMap<QString, std::function<std::shared_ptr<ResourceHandler>(const QString &)>> Resource::m_handlers;
QMap<QPair<int, int>, std::function<QVariant(QVariant)>> Resource::m_transfomers;
QMap<QString, std::weak_ptr<Resource>> Resource::m_resources;
Resource::Resource(const QString &resource)
{
if (!m_handlers.contains("web"))
{
registerHandler<WebResourceHandler>("web");
}
if (!m_handlers.contains("icon"))
{
registerHandler<IconResourceHandler>("icon");
}
Q_ASSERT(resource.contains(':'));
const QString resourceId = resource.left(resource.indexOf(':'));
Q_ASSERT(m_handlers.contains(resourceId));
m_handler = m_handlers.value(resourceId)(resource.mid(resource.indexOf(':') + 1));
m_handler->init(m_handler);
m_handler->setResource(this);
Q_ASSERT(m_handler);
}
Resource::~Resource()
{
qDeleteAll(m_observers);
}
Resource::Ptr Resource::create(const QString &resource)
{
Resource::Ptr ptr = m_resources.contains(resource)
? m_resources.value(resource).lock()
: nullptr;
if (!ptr)
{
struct ConstructableResource : public Resource
{
explicit ConstructableResource(const QString &resource)
: Resource(resource) {}
};
ptr = std::make_shared<ConstructableResource>(resource);
m_resources.insert(resource, ptr);
}
return ptr;
}
Resource::Ptr Resource::applyTo(ResourceObserver *observer)
{
m_observers.append(observer);
observer->setSource(shared_from_this()); // give the observer a shared_ptr for us so we don't get deleted
observer->resourceUpdated();
return shared_from_this();
}
Resource::Ptr Resource::applyTo(QObject *target, const char *property)
{
// the cast to ResourceObserver* is required to ensure the right overload gets choosen
return applyTo(static_cast<ResourceObserver *>(new QObjectResourceObserver(target, property)));
}
Resource::Ptr Resource::placeholder(Resource::Ptr other)
{
m_placeholder = other;
for (ResourceObserver *observer : m_observers)
{
observer->resourceUpdated();
}
return shared_from_this();
}
QVariant Resource::getResourceInternal(const int typeId) const
{
if (m_handler->result().isNull() && m_placeholder)
{
return m_placeholder->getResourceInternal(typeId);
}
const QVariant variant = m_handler->result();
const auto typePair = qMakePair(int(variant.type()), typeId);
if (m_transfomers.contains(typePair))
{
return m_transfomers.value(typePair)(variant);
}
else
{
return variant;
}
}
void Resource::reportResult()
{
for (ResourceObserver *observer : m_observers)
{
observer->resourceUpdated();
}
}
void Resource::reportFailure(const QString &reason)
{
for (ResourceObserver *observer : m_observers)
{
observer->setFailure(reason);
}
}
void Resource::reportProgress(const int progress)
{
for (ResourceObserver *observer : m_observers)
{
observer->setProgress(progress);
}
}
void Resource::notifyObserverDeleted(ResourceObserver *observer)
{
m_observers.removeAll(observer);
}

116
logic/resources/Resource.h Normal file
View File

@ -0,0 +1,116 @@
#pragma once
#include <QString>
#include <QMap>
#include <QVariant>
#include <functional>
#include <memory>
#include "ResourceObserver.h"
class ResourceHandler;
namespace Detail
{
template <typename T> struct Function : public Function<decltype(&T::operator())> {};
template <typename Ret, typename Arg> struct Function<Ret(*)(Arg)> : public Function<Ret(Arg)> {};
template <typename Ret, typename Arg> struct Function<Ret(Arg)>
{
using ReturnType = Ret;
using Argument = Arg;
};
template <class C, typename Ret, typename Arg> struct Function<Ret(C::*)(Arg)> : public Function<Ret(Arg)> {};
template <class C, typename Ret, typename Arg> struct Function<Ret(C::*)(Arg) const> : public Function<Ret(Arg)> {};
template <typename F> struct Function<F&> : public Function<F> {};
template <typename F> struct Function<F&&> : public Function<F> {};
}
/** Frontend class for resources
*
* Usage:
* Resource::create("icon:noaccount")->applyTo(accountsAction);
* Resource::create("web:http://asdf.com/image.png")->applyTo(imageLbl)->placeholder(Resource::create("icon:loading"));
*
* Memory management:
* Resource caches ResourcePtrs using weak pointers, so while a resource is still existing
* when a new resource is created the resources will be the same (including the same handler).
*
* ResourceObservers keep a shared pointer to the resource, as does the Resource itself to it's
* placeholder (if present). This means a resource stays valid while it's still used ("applied to" etc.)
* by something. When nothing uses it anymore it gets deleted.
*
* \note Always pass resource around using ResourcePtr! Copy and move constructors are disabled for a reason.
*/
class Resource : public std::enable_shared_from_this<Resource>
{
explicit Resource(const QString &resource);
Resource(const Resource &) = delete;
Resource(Resource &&) = delete;
public:
using Ptr = std::shared_ptr<Resource>;
~Resource();
/// The returned pointer needs to be stored until either Resource::then is called, or it is used as the argument to Resource::placeholder.
static Ptr create(const QString &resource);
/// This can e.g. be used to set a local icon as the placeholder while a slow (remote) icon is fetched
Ptr placeholder(Ptr other);
/// Use these functions to specify what should happen when e.g. the resource changes
Ptr applyTo(ResourceObserver *observer);
Ptr applyTo(QObject *target, const char *property = nullptr);
template<typename Func>
Ptr then(Func &&func)
{
using Arg = typename std::remove_cv<
typename std::remove_reference<typename Detail::Function<Func>::Argument>::type
>::type;
return applyTo(new FunctionResourceObserver<
typename Detail::Function<Func>::ReturnType,
Arg, Func
>(std::forward<Func>(func)));
}
/// Retrieve the currently active resource. If it's type is different from T a conversion will be attempted.
template<typename T>
T getResource() const { return getResourceInternal(qMetaTypeId<T>()).template value<T>(); }
QVariant getResourceInternal(const int typeId) const;
template<typename T>
static void registerHandler(const QString &id)
{
m_handlers.insert(id, [](const QString &res) { return std::make_shared<T>(res); });
}
template<typename Func>
static void registerTransformer(Func &&func)
{
using Out = typename Detail::Function<Func>::ReturnType;
using In = typename std::remove_cv<typename std::remove_reference<typename Detail::Function<Func>::Argument>::type>::type;
static_assert(!std::is_same<Out, In>::value, "It does not make sense to transform a value to itself");
m_transfomers.insert(qMakePair(qMetaTypeId<In>(), qMetaTypeId<Out>()), [func](const QVariant &in)
{
return QVariant::fromValue<Out>(func(in.value<In>()));
});
}
private:
friend class ResourceHandler;
void reportResult();
void reportFailure(const QString &reason);
void reportProgress(const int progress);
friend class ResourceObserver;
void notifyObserverDeleted(ResourceObserver *observer);
private:
QList<ResourceObserver *> m_observers;
std::shared_ptr<ResourceHandler> m_handler = nullptr;
Ptr m_placeholder = nullptr;
// a list of resource handler factories, registered using registerHandler
static QMap<QString, std::function<std::shared_ptr<ResourceHandler>(const QString &)>> m_handlers;
// a list of resource transformers, registered using registerTransformer
static QMap<QPair<int, int>, std::function<QVariant(QVariant)>> m_transfomers;
static QMap<QString, std::weak_ptr<Resource>> m_resources;
};

View File

@ -0,0 +1,28 @@
#include "ResourceHandler.h"
#include "Resource.h"
void ResourceHandler::setResult(const QVariant &result)
{
m_result = result;
if (m_resource)
{
m_resource->reportResult();
}
}
void ResourceHandler::setFailure(const QString &reason)
{
if (m_resource)
{
m_resource->reportFailure(reason);
}
}
void ResourceHandler::setProgress(const int progress)
{
if (m_resource)
{
m_resource->reportProgress(progress);
}
}

View File

@ -0,0 +1,33 @@
#pragma once
#include <QVariant>
#include <memory>
class Resource;
/** Base class for things that can retrieve a resource.
*
* Subclass, provide a constructor that takes a single QString as argument, and
* call Resource::registerHandler<MyResourceHandler>("<id>"), where <id> is the
* prefix of the resource ("web", "icon", etc.)
*/
class ResourceHandler
{
public:
virtual ~ResourceHandler() {}
void setResource(Resource *resource) { m_resource = resource; }
// reimplement this if you need to do something after you have been put in a shared pointer
virtual void init(std::shared_ptr<ResourceHandler>&) {}
QVariant result() const { return m_result; }
protected: // use these methods to notify the resource of changes
void setResult(const QVariant &result);
void setFailure(const QString &reason);
void setProgress(const int progress);
private:
QVariant m_result;
Resource *m_resource = nullptr;
};

View File

@ -0,0 +1,55 @@
#include "ResourceObserver.h"
#include <QDebug>
#include "Resource.h"
static const char *defaultPropertyForTarget(QObject *target)
{
if (target->inherits("QLabel"))
{
return "pixmap";
}
else if (target->inherits("QAction") ||
target->inherits("QMenu") ||
target->inherits("QAbstractButton"))
{
return "icon";
}
// for unit tests
else if (target->inherits("DummyObserverObject"))
{
return "property";
}
else
{
Q_ASSERT_X(false, "ResourceObserver.cpp: defaultPropertyForTarget", "Unrecognized QObject subclass");
return nullptr;
}
}
QObjectResourceObserver::QObjectResourceObserver(QObject *target, const char *property)
: QObject(target), m_target(target)
{
const QMetaObject *mo = m_target->metaObject();
m_property = mo->property(mo->indexOfProperty(
property ?
property
: defaultPropertyForTarget(target)));
}
void QObjectResourceObserver::resourceUpdated()
{
m_property.write(m_target, getInternal(m_property.type()));
}
ResourceObserver::~ResourceObserver()
{
m_resource->notifyObserverDeleted(this);
}
QVariant ResourceObserver::getInternal(const int typeId) const
{
Q_ASSERT(m_resource);
return m_resource->getResourceInternal(typeId);
}

View File

@ -0,0 +1,67 @@
#pragma once
#include <memory>
#include <functional>
#include <QObject>
#include <QMetaProperty>
class QVariant;
class Resource;
/// Base class for things that can use a resource
class ResourceObserver
{
public:
virtual ~ResourceObserver();
protected: // these methods are called by the Resource when something changes
virtual void resourceUpdated() = 0;
virtual void setFailure(const QString &) {}
virtual void setProgress(const int) {}
private:
friend class Resource;
void setSource(std::shared_ptr<Resource> resource) { m_resource = resource; }
protected:
template<typename T>
T get() const { return getInternal(qMetaTypeId<T>()).template value<T>(); }
QVariant getInternal(const int typeId) const;
private:
std::shared_ptr<Resource> m_resource;
};
/** Observer for QObject properties
*
* Give it a target and the name of a property, and that property will be set when the resource changes.
*
* If no name is given an attempt to find a default property for some common classes is done.
*/
class QObjectResourceObserver : public QObject, public ResourceObserver
{
public:
explicit QObjectResourceObserver(QObject *target, const char *property = nullptr);
void resourceUpdated() override;
private:
QObject *m_target;
QMetaProperty m_property;
};
template <typename Ret, typename Arg, typename Func>
class FunctionResourceObserver : public ResourceObserver
{
std::function<Ret(Arg)> m_function;
public:
template <typename T>
explicit FunctionResourceObserver(T &&func)
: m_function(std::forward<Func>(func)) {}
void resourceUpdated() override
{
m_function(get<Arg>());
}
};

View File

@ -0,0 +1,103 @@
#include "ResourceProxyModel.h"
#include <QItemSelectionRange>
#include "Resource.h"
#include "ResourceObserver.h"
//Q_DECLARE_METATYPE(QVector<int>)
class ModelResourceObserver : public ResourceObserver
{
public:
explicit ModelResourceObserver(const QModelIndex &index, const int role)
: m_index(index), m_role(role)
{
qRegisterMetaType<QVector<int>>("QVector<int>");
}
void resourceUpdated() override
{
if (m_index.isValid())
{
QMetaObject::invokeMethod(const_cast<QAbstractItemModel *>(m_index.model()),
"dataChanged", Qt::QueuedConnection,
Q_ARG(QModelIndex, m_index), Q_ARG(QModelIndex, m_index), Q_ARG(QVector<int>, QVector<int>() << m_role));
}
}
private:
QPersistentModelIndex m_index;
int m_role;
};
ResourceProxyModel::ResourceProxyModel(const int resultTypeId, QObject *parent)
: QIdentityProxyModel(parent), m_resultTypeId(resultTypeId)
{
}
QVariant ResourceProxyModel::data(const QModelIndex &proxyIndex, int role) const
{
const QModelIndex mapped = mapToSource(proxyIndex);
if (mapped.isValid() && role == Qt::DecorationRole && !mapToSource(proxyIndex).data(role).toString().isEmpty())
{
if (!m_resources.contains(mapped))
{
Resource::Ptr res = Resource::create(mapToSource(proxyIndex).data(role).toString())
->applyTo(new ModelResourceObserver(proxyIndex, role));
const QVariant placeholder = mapped.data(PlaceholderRole);
if (!placeholder.isNull() && placeholder.type() == QVariant::String)
{
res->placeholder(Resource::create(placeholder.toString()));
}
m_resources.insert(mapped, res);
}
return m_resources.value(mapped)->getResourceInternal(m_resultTypeId);
}
return mapped.data(role);
}
void ResourceProxyModel::setSourceModel(QAbstractItemModel *model)
{
if (sourceModel())
{
disconnect(sourceModel(), 0, this, 0);
}
if (model)
{
connect(model, &QAbstractItemModel::dataChanged, this, [this](const QModelIndex &tl, const QModelIndex &br, const QVector<int> &roles)
{
if (roles.contains(Qt::DecorationRole) || roles.isEmpty())
{
const QItemSelectionRange range(tl, br);
for (const QModelIndex &index : range.indexes())
{
m_resources.remove(index);
}
}
else if (roles.contains(PlaceholderRole))
{
const QItemSelectionRange range(tl, br);
for (const QModelIndex &index : range.indexes())
{
if (m_resources.contains(index))
{
const QVariant placeholder = index.data(PlaceholderRole);
if (!placeholder.isNull() && placeholder.type() == QVariant::String)
{
m_resources.value(index)->placeholder(Resource::create(placeholder.toString()));
}
else
{
m_resources.value(index)->placeholder(nullptr);
}
}
}
}
});
}
QIdentityProxyModel::setSourceModel(model);
}

View File

@ -0,0 +1,36 @@
#pragma once
#include <QIdentityProxyModel>
#include <memory>
/// Convenience proxy model that transforms resource identifiers (strings) for Qt::DecorationRole into other types.
class ResourceProxyModel : public QIdentityProxyModel
{
Q_OBJECT
public:
// resultTypeId is found using qMetaTypeId<T>()
explicit ResourceProxyModel(const int resultTypeId, QObject *parent = nullptr);
enum
{
// provide this role from your model if you want to show a placeholder
PlaceholderRole = Qt::UserRole + 0xabc // some random offset to not collide with other stuff
};
QVariant data(const QModelIndex &proxyIndex, int role) const override;
void setSourceModel(QAbstractItemModel *model) override;
template <typename T>
static QAbstractItemModel *mixin(QAbstractItemModel *model)
{
ResourceProxyModel *proxy = new ResourceProxyModel(qMetaTypeId<T>(), model);
proxy->setSourceModel(model);
return proxy;
}
private:
// mutable because it needs to be available from the const data()
mutable QMap<QPersistentModelIndex, std::shared_ptr<class Resource>> m_resources;
const int m_resultTypeId;
};

View File

@ -0,0 +1,67 @@
#include "WebResourceHandler.h"
#include "net/CacheDownload.h"
#include "net/HttpMetaCache.h"
#include "net/NetJob.h"
#include "FileSystem.h"
#include "Env.h"
QMap<QString, NetJob *> WebResourceHandler::m_activeDownloads;
WebResourceHandler::WebResourceHandler(const QString &url)
: QObject(), m_url(url)
{
MetaEntryPtr entry = ENV.metacache()->resolveEntry("icons", url);
if (!entry->stale)
{
setResultFromFile(entry->getFullPath());
}
else if (m_activeDownloads.contains(url))
{
NetJob *job = m_activeDownloads.value(url);
connect(job, &NetJob::succeeded, this, &WebResourceHandler::succeeded);
connect(job, &NetJob::failed, this, [job, this]() {setFailure(job->failReason());});
connect(job, &NetJob::progress, this, &WebResourceHandler::progress);
}
else
{
NetJob *job = new NetJob("Icon download");
job->addNetAction(CacheDownload::make(QUrl(url), entry));
connect(job, &NetJob::succeeded, this, &WebResourceHandler::succeeded);
connect(job, &NetJob::failed, this, [job, this]() {setFailure(job->failReason());});
connect(job, &NetJob::progress, this, &WebResourceHandler::progress);
connect(job, &NetJob::finished, job, [job](){m_activeDownloads.remove(m_activeDownloads.key(job));job->deleteLater();});
m_activeDownloads.insert(url, job);
job->start();
}
}
void WebResourceHandler::succeeded()
{
MetaEntryPtr entry = ENV.metacache()->resolveEntry("icons", m_url);
setResultFromFile(entry->getFullPath());
m_activeDownloads.remove(m_activeDownloads.key(qobject_cast<NetJob *>(sender())));
}
void WebResourceHandler::progress(qint64 current, qint64 total)
{
if (total == 0)
{
setProgress(101);
}
else
{
setProgress(current / total);
}
}
void WebResourceHandler::setResultFromFile(const QString &file)
{
try
{
setResult(FS::read(file));
}
catch (Exception &e)
{
setFailure(e.cause());
}
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <QObject>
#include "ResourceHandler.h"
class NetJob;
class WebResourceHandler : public QObject, public ResourceHandler
{
public:
explicit WebResourceHandler(const QString &url);
private slots:
void succeeded();
void progress(qint64 current, qint64 total);
private:
static QMap<QString, NetJob *> m_activeDownloads;
QString m_url;
void setResultFromFile(const QString &file);
};

View File

@ -0,0 +1,120 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#include "StandardTask.h"
#include <QEventLoop>
#include <QProcess>
#include "net/CacheDownload.h"
#include "net/ByteArrayDownload.h"
#include "net/NetJob.h"
#include "FileSystem.h"
#include "Exception.h"
#include "Env.h"
StandardTask::StandardTask(QObject *parent)
: Task(parent)
{
m_loop = new QEventLoop(this);
}
void StandardTask::runTask(QObjectPtr<Task> other)
{
connect(other.get(), &Task::succeeded, m_loop, &QEventLoop::quit);
connect(other.get(), &Task::failed, m_loop, &QEventLoop::quit);
connect(other.get(), &Task::progress, this, [this](qint64 current, qint64 total){setProgress(current / total);});
connect(other.get(), &Task::status, this, &StandardTask::setStatus);
if (!other->isRunning())
{
other->start();
}
if (other->isRunning())
{
m_loop->exec();
}
disconnect(other.get(), 0, m_loop, 0);
disconnect(other.get(), 0, this, 0);
other->deleteLater();
if (!other->successful())
{
throw Exception(other->failReason());
}
}
void StandardTask::runTaskNonBlocking(QObjectPtr<Task> other)
{
if (!other)
{
return;
}
m_pendingTasks.append(other.get());
m_pendingTaskPtrs.append(other);
other->start();
}
QByteArray StandardTask::networkGet(const QUrl &url)
{
ByteArrayDownloadPtr task = ByteArrayDownload::make(url);
runTask(wrapDownload("", task));
return task->m_data;
}
QByteArray StandardTask::networkGetCached(const QString &name, const QString &base, const QString &path, const QUrl &url, const bool alwaysRefetch,
INetworkValidator *validator)
{
MetaEntryPtr entry = ENV.metacache()->resolveEntry(base, path);
if (!alwaysRefetch && !entry->stale)
{
if (validator) { delete validator; }
return FS::read(entry->getFullPath());
}
else if (alwaysRefetch)
{
entry->stale = true;
}
CacheDownloadPtr task = CacheDownload::make(url, entry);
task->setValidator(validator);
runTask(wrapDownload(name, task));
return FS::read(entry->getFullPath());
}
QByteArray StandardTask::networkGetCached(const QString &name, const QString &base, const QString &path, const QUrl &url, const QMap<QString, QString> &headers,
INetworkValidator *validator)
{
MetaEntryPtr entry = ENV.metacache()->resolveEntry(base, path);
if (!entry->stale)
{
if (validator) { delete validator; }
return FS::read(entry->getFullPath());
}
CacheDownloadPtr task = CacheDownload::make(url, entry);
//task->setHeaders(headers);
task->setValidator(validator);
runTask(wrapDownload(name, task));
return FS::read(entry->getFullPath());
}
void StandardTask::networkGetCachedNonBlocking(const QString &name, const QString &base, const QString &path, const QUrl &url, const bool alwaysRefetch,
INetworkValidator *validator)
{
MetaEntryPtr entry = ENV.metacache()->resolveEntry(base, path);
if (!alwaysRefetch && !entry->stale)
{
return;
}
CacheDownloadPtr dl = CacheDownload::make(url, entry);
dl->setValidator(validator);
runTaskNonBlocking(wrapDownload(name, dl));
}
void StandardTask::waitOnPending()
{
for (int i = 0; i < m_pendingTasks.size(); ++i)
{
if (m_pendingTasks.at(i) && m_pendingTasks.at(i)->isRunning())
{
runTask(m_pendingTaskPtrs.at(i));
}
}
}
QObjectPtr<NetJob> StandardTask::wrapDownload(const QString &name, std::shared_ptr<NetAction> action)
{
NetJobPtr task = NetJobPtr(new NetJob(name));
task->addNetAction(action);
return task;
}

View File

@ -0,0 +1,43 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#pragma once
#include "Task.h"
#include <QPointer>
#include <memory>
#include "QObjectPtr.h"
class QEventLoop;
class QDir;
class NetAction;
class NetJob;
class INetworkValidator;
class StandardTask : public Task
{
Q_OBJECT
public:
explicit StandardTask(QObject *parent = nullptr);
protected:
// TODO: switch to a future-based system
void runTask(QObjectPtr<Task> other);
void runTaskNonBlocking(QObjectPtr<Task> other);
QByteArray networkGet(const QUrl &url);
QByteArray networkGetCached(const QString &name, const QString &base, const QString &path, const QUrl &url, const bool alwaysRefetch = false,
INetworkValidator *validator = nullptr);
QByteArray networkGetCached(const QString &name, const QString &base, const QString &path, const QUrl &url, const QMap<QString, QString> &headers,
INetworkValidator *validator = nullptr);
void networkGetCachedNonBlocking(const QString &name, const QString &base, const QString &path, const QUrl &url, const bool alwaysRefetch = false,
INetworkValidator *validator = nullptr);
void waitOnPending();
private:
QEventLoop *m_loop;
QList<QPointer<Task>> m_pendingTasks; // only used to check if the object was deleted
QList<QObjectPtr<Task>> m_pendingTaskPtrs;
QObjectPtr<NetJob> wrapDownload(const QString &name, std::shared_ptr<NetAction> action);
};

View File

@ -14,6 +14,7 @@
*/
#include "Task.h"
#include <QDebug>
Task::Task(QObject *parent) : QObject(parent)

View File

@ -39,6 +39,8 @@ public:
*/
virtual QString failReason() const;
virtual bool canAbort() const { return false; }
signals:
void started();
void progress(qint64 current, qint64 total);

101
tests/tst_Resource.cpp Normal file
View File

@ -0,0 +1,101 @@
#include <QTest>
#include <QAction>
#include "TestUtil.h"
#include "resources/Resource.h"
#include "resources/ResourceHandler.h"
#include "resources/ResourceObserver.h"
class DummyStringResourceHandler : public ResourceHandler
{
public:
explicit DummyStringResourceHandler(const QString &key)
: m_key(key) {}
void init(std::shared_ptr<ResourceHandler> &) override
{
setResult(m_key);
}
QString m_key;
};
class DummyObserver : public ResourceObserver
{
public:
void resourceUpdated() override
{
values += get<QString>();
}
QStringList values;
};
class DummyObserverObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QString property MEMBER property)
public:
explicit DummyObserverObject(QObject *parent = nullptr) : QObject(parent) {}
QString property;
};
class ResourceTest : public QObject
{
Q_OBJECT
private
slots:
void initTestCase()
{
Resource::registerHandler<DummyStringResourceHandler>("dummy");
}
void cleanupTestCase()
{
}
void test_Then()
{
QString val;
Resource::create("dummy:test_Then")
->then([&val](const QString &key) { val = key; });
QCOMPARE(val, QStringLiteral("test_Then"));
}
void test_Object()
{
DummyObserver *observer = new DummyObserver;
Resource::create("dummy:test_Object")->applyTo(observer);
QCOMPARE(observer->values, QStringList() << "test_Object");
}
void test_QObjectProperty()
{
DummyObserverObject *object = new DummyObserverObject;
Resource::create("dummy:test_QObjectProperty")->applyTo(object);
QCOMPARE(object->property, QStringLiteral("test_QObjectProperty"));
}
void test_DontRequestPlaceholder()
{
auto resource = Resource::create("dummy:asdf")
->then([](const QString &key) { QCOMPARE(key, QStringLiteral("asdf")); });
// the following call should not notify the observer. if it does the above QCOMPARE would fail.
resource->placeholder(Resource::create("dummy:fdsa"));
}
void test_MergedResources()
{
auto r1 = Resource::create("dummy:asdf");
auto r2 = Resource::create("dummy:asdf");
auto r3 = Resource::create("dummy:fdsa");
auto r4 = Resource::create("dummy:asdf");
QCOMPARE(r1, r2);
QCOMPARE(r1, r4);
QVERIFY(r1 != r3);
QVERIFY(r2 != r3);
QVERIFY(r4 != r3);
}
};
QTEST_GUILESS_MAIN(ResourceTest)
#include "tst_Resource.moc"