NOISSUE Various changes from multiauth that are unrelated to it
This commit is contained in:
parent
161dc66c2c
commit
3a8b238052
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -13,7 +13,6 @@ int main_gui(MultiMC &app)
|
||||
mainWin.checkInstancePathForProblems();
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// initialize Qt
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
#include "pages/BasePage.h"
|
||||
|
||||
#include "auth/MojangAccountList.h"
|
||||
#include <MultiMC.h>
|
||||
#include "MultiMC.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
|
BIN
application/resources/multimc/150x150/hourglass.png
Normal file
BIN
application/resources/multimc/150x150/hourglass.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
application/resources/multimc/16x16/hourglass.png
Normal file
BIN
application/resources/multimc/16x16/hourglass.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 705 B |
BIN
application/resources/multimc/22x22/hourglass.png
Normal file
BIN
application/resources/multimc/22x22/hourglass.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
application/resources/multimc/32x32/hourglass.png
Normal file
BIN
application/resources/multimc/32x32/hourglass.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
application/resources/multimc/48x48/hourglass.png
Normal file
BIN
application/resources/multimc/48x48/hourglass.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
@ -35,6 +35,9 @@ Size=64
|
||||
[256x256]
|
||||
Size=256
|
||||
|
||||
[150x150]
|
||||
Size=150
|
||||
|
||||
[scalable]
|
||||
Size=48
|
||||
Type=Scalable
|
||||
|
@ -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>
|
||||
|
74
application/widgets/ProgressWidget.cpp
Normal file
74
application/widgets/ProgressWidget.cpp
Normal 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;
|
||||
}
|
32
application/widgets/ProgressWidget.h
Normal file
32
application/widgets/ProgressWidget.h
Normal 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;
|
||||
};
|
133
logic/AbstractCommonModel.cpp
Normal file
133
logic/AbstractCommonModel.cpp
Normal 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
462
logic/AbstractCommonModel.h
Normal 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
119
logic/BaseConfigObject.cpp
Normal 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
50
logic/BaseConfigObject.h
Normal 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;
|
||||
};
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
41
logic/Exception.h
Normal file
41
logic/Exception.h
Normal 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
56
logic/FileSystem.cpp
Normal 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
13
logic/FileSystem.h
Normal 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
278
logic/Json.cpp
Normal 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
239
logic/Json.h
Normal 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;
|
@ -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;
|
||||
};
|
@ -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;
|
||||
}
|
101
logic/MMCJson.h
101
logic/MMCJson.h
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -1,7 +1,6 @@
|
||||
#include <QDateTime>
|
||||
#include <QString>
|
||||
#include "ParseUtils.h"
|
||||
#include <MMCJson.h>
|
||||
|
||||
QDateTime timeFromS3Time(QString str)
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
{
|
||||
}
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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);
|
||||
|
60
logic/resources/IconResourceHandler.cpp
Normal file
60
logic/resources/IconResourceHandler.cpp
Normal 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;
|
||||
}
|
22
logic/resources/IconResourceHandler.h
Normal file
22
logic/resources/IconResourceHandler.h
Normal 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;
|
||||
};
|
121
logic/resources/Resource.cpp
Normal file
121
logic/resources/Resource.cpp
Normal 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
116
logic/resources/Resource.h
Normal 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;
|
||||
};
|
28
logic/resources/ResourceHandler.cpp
Normal file
28
logic/resources/ResourceHandler.cpp
Normal 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);
|
||||
}
|
||||
}
|
33
logic/resources/ResourceHandler.h
Normal file
33
logic/resources/ResourceHandler.h
Normal 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;
|
||||
};
|
55
logic/resources/ResourceObserver.cpp
Normal file
55
logic/resources/ResourceObserver.cpp
Normal 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);
|
||||
}
|
67
logic/resources/ResourceObserver.h
Normal file
67
logic/resources/ResourceObserver.h
Normal 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>());
|
||||
}
|
||||
};
|
103
logic/resources/ResourceProxyModel.cpp
Normal file
103
logic/resources/ResourceProxyModel.cpp
Normal 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);
|
||||
}
|
36
logic/resources/ResourceProxyModel.h
Normal file
36
logic/resources/ResourceProxyModel.h
Normal 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;
|
||||
};
|
67
logic/resources/WebResourceHandler.cpp
Normal file
67
logic/resources/WebResourceHandler.cpp
Normal 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());
|
||||
}
|
||||
}
|
23
logic/resources/WebResourceHandler.h
Normal file
23
logic/resources/WebResourceHandler.h
Normal 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);
|
||||
};
|
120
logic/tasks/StandardTask.cpp
Normal file
120
logic/tasks/StandardTask.cpp
Normal 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;
|
||||
}
|
43
logic/tasks/StandardTask.h
Normal file
43
logic/tasks/StandardTask.h
Normal 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);
|
||||
};
|
@ -14,6 +14,7 @@
|
||||
*/
|
||||
|
||||
#include "Task.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
Task::Task(QObject *parent) : QObject(parent)
|
||||
|
@ -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
101
tests/tst_Resource.cpp
Normal 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"
|
Loading…
Reference in New Issue
Block a user