summaryrefslogtreecommitdiffstats
path: root/api/dead
diff options
context:
space:
mode:
authorPetr Mrázek <peterix@gmail.com>2016-04-10 17:01:24 +0200
committerPetr Mrázek <peterix@gmail.com>2016-05-01 00:00:24 +0200
commit1be7d573326570d63e55e36235537ed2b1831ae1 (patch)
treeba7037671cde8688e87e69acb753df80ec4cd4f9 /api/dead
parentaa4842a91d35481264ae5a7c0ac17ea43610b600 (diff)
downloadMultiMC-1be7d573326570d63e55e36235537ed2b1831ae1.tar
MultiMC-1be7d573326570d63e55e36235537ed2b1831ae1.tar.gz
MultiMC-1be7d573326570d63e55e36235537ed2b1831ae1.tar.lz
MultiMC-1be7d573326570d63e55e36235537ed2b1831ae1.tar.xz
MultiMC-1be7d573326570d63e55e36235537ed2b1831ae1.zip
NOISSUE re/move some dead code and unused build system parts
Diffstat (limited to 'api/dead')
-rw-r--r--api/dead/README.md3
-rw-r--r--api/dead/src/AbstractCommonModel.cpp133
-rw-r--r--api/dead/src/AbstractCommonModel.h462
-rw-r--r--api/dead/src/BaseConfigObject.cpp103
-rw-r--r--api/dead/src/BaseConfigObject.h50
-rw-r--r--api/dead/src/TypeMagic.h37
-rw-r--r--api/dead/src/handlers/IconResourceHandler.cpp37
-rw-r--r--api/dead/src/handlers/IconResourceHandler.h23
-rw-r--r--api/dead/src/handlers/WebResourceHandler.cpp68
-rw-r--r--api/dead/src/handlers/WebResourceHandler.h23
-rw-r--r--api/dead/src/resources/Resource.cpp155
-rw-r--r--api/dead/src/resources/Resource.h132
-rw-r--r--api/dead/src/resources/ResourceHandler.cpp28
-rw-r--r--api/dead/src/resources/ResourceHandler.h36
-rw-r--r--api/dead/src/resources/ResourceObserver.cpp55
-rw-r--r--api/dead/src/resources/ResourceObserver.h73
-rw-r--r--api/dead/src/resources/ResourceProxyModel.cpp89
-rw-r--r--api/dead/src/resources/ResourceProxyModel.h39
-rw-r--r--api/dead/test/tst_Resource.cpp116
19 files changed, 1662 insertions, 0 deletions
diff --git a/api/dead/README.md b/api/dead/README.md
new file mode 100644
index 00000000..520b18d3
--- /dev/null
+++ b/api/dead/README.md
@@ -0,0 +1,3 @@
+This stuff is dead code I collected from the repository that still looks like it might be useful for something.
+
+Moved on 10. April 2016 - if it hasn't been moved or used in a while, delete this.
diff --git a/api/dead/src/AbstractCommonModel.cpp b/api/dead/src/AbstractCommonModel.cpp
new file mode 100644
index 00000000..71d75829
--- /dev/null
+++ b/api/dead/src/AbstractCommonModel.cpp
@@ -0,0 +1,133 @@
+/* Copyright 2015 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AbstractCommonModel.h"
+
+BaseAbstractCommonModel::BaseAbstractCommonModel(const Qt::Orientation orientation, QObject *parent)
+ : QAbstractListModel(parent), m_orientation(orientation)
+{
+}
+
+int BaseAbstractCommonModel::rowCount(const QModelIndex &parent) const
+{
+ return m_orientation == Qt::Horizontal ? entryCount() : size();
+}
+int BaseAbstractCommonModel::columnCount(const QModelIndex &parent) const
+{
+ return m_orientation == Qt::Horizontal ? size() : entryCount();
+}
+QVariant BaseAbstractCommonModel::data(const QModelIndex &index, int role) const
+{
+ if (!hasIndex(index.row(), index.column(), index.parent()))
+ {
+ return QVariant();
+ }
+ const int i = m_orientation == Qt::Horizontal ? index.column() : index.row();
+ const int entry = m_orientation == Qt::Horizontal ? index.row() : index.column();
+ return formatData(i, role, get(i, entry, role));
+}
+QVariant BaseAbstractCommonModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation != m_orientation && role == Qt::DisplayRole)
+ {
+ return entryTitle(section);
+ }
+ else
+ {
+ return QVariant();
+ }
+}
+bool BaseAbstractCommonModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ const int i = m_orientation == Qt::Horizontal ? index.column() : index.row();
+ const int entry = m_orientation == Qt::Horizontal ? index.row() : index.column();
+ const bool result = set(i, entry, role, sanetizeData(i, role, value));
+ if (result)
+ {
+ emit dataChanged(index, index, QVector<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();
+}
diff --git a/api/dead/src/AbstractCommonModel.h b/api/dead/src/AbstractCommonModel.h
new file mode 100644
index 00000000..31b86a23
--- /dev/null
+++ b/api/dead/src/AbstractCommonModel.h
@@ -0,0 +1,462 @@
+/* Copyright 2015 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <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();
+ }
+};
diff --git a/api/dead/src/BaseConfigObject.cpp b/api/dead/src/BaseConfigObject.cpp
new file mode 100644
index 00000000..3040ac2e
--- /dev/null
+++ b/api/dead/src/BaseConfigObject.cpp
@@ -0,0 +1,103 @@
+/* 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 <QFile>
+#include <QCoreApplication>
+#include <QDebug>
+
+#include "Exception.h"
+#include "FileSystem.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;
+ }
+
+ try
+ {
+ FS::write(m_filename, doSave());
+ }
+ catch (Exception & e)
+ {
+ qCritical() << e.cause();
+ }
+}
+void BaseConfigObject::loadNow()
+{
+ if (m_saveTimer->isActive())
+ {
+ saveNow();
+ }
+
+ try
+ {
+ doLoad(FS::read(m_filename));
+ }
+ catch (Exception & e)
+ {
+ qWarning() << "Error loading" << m_filename << ":" << e.cause();
+ }
+}
diff --git a/api/dead/src/BaseConfigObject.h b/api/dead/src/BaseConfigObject.h
new file mode 100644
index 00000000..1c96b3d1
--- /dev/null
+++ b/api/dead/src/BaseConfigObject.h
@@ -0,0 +1,50 @@
+/* Copyright 2015 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <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;
+};
diff --git a/api/dead/src/TypeMagic.h b/api/dead/src/TypeMagic.h
new file mode 100644
index 00000000..fa9d12a9
--- /dev/null
+++ b/api/dead/src/TypeMagic.h
@@ -0,0 +1,37 @@
+#pragma once
+
+namespace TypeMagic
+{
+/** "Cleans" the given type T by stripping references (&) and cv-qualifiers (const, volatile) from it
+ * const int => int
+ * QString & => QString
+ * const unsigned long long & => unsigned long long
+ *
+ * Usage:
+ * using Cleaned = Detail::CleanType<const int>;
+ * static_assert(std::is_same<Cleaned, int>, "Cleaned == int");
+ */
+// the order of remove_cv and remove_reference matters!
+template <typename T>
+using CleanType = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
+
+/// For functors (structs with operator()), including lambdas, which in **most** cases are functors
+/// "Calls" Function<Ret(*)(Arg)> or Function<Ret(C::*)(Arg)>
+template <typename T> struct Function : public Function<decltype(&T::operator())> {};
+/// For function pointers (&function), including static members (&Class::member)
+template <typename Ret, typename Arg> struct Function<Ret(*)(Arg)> : public Function<Ret(Arg)> {};
+/// Default specialization used by others.
+template <typename Ret, typename Arg> struct Function<Ret(Arg)>
+{
+ using ReturnType = Ret;
+ using Argument = Arg;
+};
+/// For member functions. Also used by the lambda overload if the lambda captures [this]
+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)> {};
+/// Overload for references
+template <typename F> struct Function<F&> : public Function<F> {};
+/// Overload for rvalues
+template <typename F> struct Function<F&&> : public Function<F> {};
+// for more info: https://functionalcpp.wordpress.com/2013/08/05/function-traits/
+}
diff --git a/api/dead/src/handlers/IconResourceHandler.cpp b/api/dead/src/handlers/IconResourceHandler.cpp
new file mode 100644
index 00000000..b03553fd
--- /dev/null
+++ b/api/dead/src/handlers/IconResourceHandler.cpp
@@ -0,0 +1,37 @@
+#include "IconResourceHandler.h"
+#include <xdgicon.h>
+
+#include <QDir>
+#include <QDebug>
+
+QList<std::weak_ptr<IconResourceHandler>> IconResourceHandler::m_iconHandlers;
+
+IconResourceHandler::IconResourceHandler(const QString &key)
+ : m_key(key)
+{
+}
+
+void IconResourceHandler::setTheme(const QString &theme)
+{
+ // notify everyone
+ 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));
+ // we always have a result, so lets report it now!
+ setResult(get());
+}
+
+QVariant IconResourceHandler::get() const
+{
+ return XdgIcon::fromTheme(m_key);
+}
diff --git a/api/dead/src/handlers/IconResourceHandler.h b/api/dead/src/handlers/IconResourceHandler.h
new file mode 100644
index 00000000..0cc4de4f
--- /dev/null
+++ b/api/dead/src/handlers/IconResourceHandler.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <memory>
+#include <resources/ResourceHandler.h>
+
+class IconResourceHandler : public ResourceHandler
+{
+public:
+ explicit IconResourceHandler(const QString &key);
+
+ /// Sets the current theme and notifies all IconResourceHandlers of the change
+ static void setTheme(const QString &theme);
+
+private:
+ // we need to keep track of all IconResourceHandlers so that we can update them if the theme changes
+ void init(std::shared_ptr<ResourceHandler> &ptr) override;
+ static QList<std::weak_ptr<IconResourceHandler>> m_iconHandlers;
+
+ QString m_key;
+
+ // the workhorse, returns QVariantMap (filename => size) for m_key/m_theme
+ QVariant get() const;
+};
diff --git a/api/dead/src/handlers/WebResourceHandler.cpp b/api/dead/src/handlers/WebResourceHandler.cpp
new file mode 100644
index 00000000..757b870a
--- /dev/null
+++ b/api/dead/src/handlers/WebResourceHandler.cpp
@@ -0,0 +1,68 @@
+#include "WebResourceHandler.h"
+
+#include "net/CacheDownload.h"
+#include "net/HttpMetaCache.h"
+#include "net/NetJob.h"
+#include "FileSystem.h"
+#include "Env.h"
+
+//FIXME: wrong. needs to be done elsewhere.
+QMap<QString, NetJob *> WebResourceHandler::m_activeDownloads;
+
+WebResourceHandler::WebResourceHandler(const QString &url)
+ : QObject(), m_url(url)
+{
+ MetaEntryPtr entry = ENV.metacache()->resolveEntry("icons", url);
+ if (!entry->isStale())
+ {
+ 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());
+ }
+}
diff --git a/api/dead/src/handlers/WebResourceHandler.h b/api/dead/src/handlers/WebResourceHandler.h
new file mode 100644
index 00000000..54bef6a1
--- /dev/null
+++ b/api/dead/src/handlers/WebResourceHandler.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <QObject>
+#include <resources/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);
+};
diff --git a/api/dead/src/resources/Resource.cpp b/api/dead/src/resources/Resource.cpp
new file mode 100644
index 00000000..e95675d7
--- /dev/null
+++ b/api/dead/src/resources/Resource.cpp
@@ -0,0 +1,155 @@
+#include "Resource.h"
+
+#include <QDebug>
+
+#include "ResourceObserver.h"
+#include "ResourceHandler.h"
+
+// definition of static members of Resource
+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;
+
+struct NullResourceResult {};
+Q_DECLARE_METATYPE(NullResourceResult)
+class NullResourceHandler : public ResourceHandler
+{
+public:
+ explicit NullResourceHandler()
+ {
+ setResult(QVariant::fromValue<NullResourceResult>(NullResourceResult()));
+ }
+};
+
+Resource::Resource(const QString &resource)
+ : m_resource(resource)
+{
+ if (!resource.isEmpty())
+ {
+ // a valid resource identifier has the format <id>:<data>
+ Q_ASSERT(resource.contains(':'));
+ // "parse" the resource identifier into id and data
+ const QString resourceId = resource.left(resource.indexOf(':'));
+ const QString resourceData = resource.mid(resource.indexOf(':') + 1);
+
+ // create and set up the handler
+ Q_ASSERT(m_handlers.contains(resourceId));
+ m_handler = m_handlers.value(resourceId)(resourceData);
+ }
+ else
+ {
+ m_handler = std::make_shared<NullResourceHandler>();
+ }
+
+ Q_ASSERT(m_handler);
+ m_handler->init(m_handler);
+ m_handler->setResource(this);
+}
+Resource::~Resource()
+{
+ qDeleteAll(m_observers);
+}
+
+Resource::Ptr Resource::create(const QString &resource, Ptr placeholder)
+{
+ const QString storageId = storageIdentifier(resource, placeholder);
+
+ // do we already have a resource? even if m_resources contains it it might not be valid any longer (weak_ptr)
+ Resource::Ptr ptr = m_resources.contains(storageId)
+ ? m_resources.value(storageId).lock()
+ : nullptr;
+ // did we have one? and is it still valid?
+ if (!ptr)
+ {
+ /* We don't want Resource to have a public constructor, but std::make_shared needs it,
+ * so we create a subclass of Resource here that exposes the constructor as public.
+ * The alternative would be making the allocator for std::make_shared a friend, but it
+ * differs between different STL implementations, so that would be a pain.
+ */
+ struct ConstructableResource : public Resource
+ {
+ explicit ConstructableResource(const QString &resource)
+ : Resource(resource) {}
+ };
+ ptr = std::make_shared<ConstructableResource>(resource);
+ ptr->m_placeholder = placeholder;
+ m_resources.insert(storageId, 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(); // ask the observer to poll us immediently, we might already have data
+ return shared_from_this(); // allow chaining
+}
+Resource::Ptr Resource::applyTo(QObject *target, const char *property)
+{
+ // the cast to ResourceObserver* is required to ensure the right overload gets choosen,
+ // since QObjectResourceObserver also inherits from QObject
+ return applyTo(static_cast<ResourceObserver *>(new QObjectResourceObserver(target, property)));
+}
+
+QVariant Resource::getResourceInternal(const int typeId) const
+{
+ // no result (yet), but a placeholder? delegate to the placeholder.
+ 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);
+
+ // do we have an explicit transformer? use it.
+ if (m_transfomers.contains(typePair))
+ {
+ return m_transfomers.value(typePair)(variant);
+ }
+ else
+ {
+ // we do not have an explicit transformer, so we just pass the QVariant, which will automatically
+ // transform some types for us (different numbers to each other etc.)
+ 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);
+}
+
+QString Resource::storageIdentifier(const QString &id, Resource::Ptr placeholder)
+{
+ if (placeholder)
+ {
+ return id + '#' + storageIdentifier(placeholder->m_resource, placeholder->m_placeholder);
+ }
+ else
+ {
+ return id;
+ }
+}
diff --git a/api/dead/src/resources/Resource.h b/api/dead/src/resources/Resource.h
new file mode 100644
index 00000000..63e97b88
--- /dev/null
+++ b/api/dead/src/resources/Resource.h
@@ -0,0 +1,132 @@
+#pragma once
+
+#include <QString>
+#include <QMap>
+#include <QVariant>
+#include <functional>
+#include <memory>
+
+#include "ResourceObserver.h"
+#include "TypeMagic.h"
+
+#include "multimc_logic_export.h"
+
+class ResourceHandler;
+
+/** 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 Resource::Ptr! Copy and move constructors are disabled for a reason.
+ */
+class MULTIMC_LOGIC_EXPORT Resource : public std::enable_shared_from_this<Resource>
+{
+ // only allow creation from Resource::create and disallow passing around non-pointers
+ 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::applyTo or Resource::then is called, or it is passed as
+ /// a placeholder to Resource::create itself.
+ static Ptr create(const QString &resource, Ptr placeholder = nullptr);
+
+ /// 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)
+ {
+ // Arg will be the functions argument with references and cv-qualifiers (const, volatile) removed
+ using Arg = TypeMagic::CleanType<typename TypeMagic::Function<Func>::Argument>;
+ // Ret will be the functions return type
+ using Ret = typename TypeMagic::Function<Func>::ReturnType;
+
+ // FunctionResourceObserver<ReturnType, ArgumentType, FunctionSignature>
+ return applyTo(new FunctionResourceObserver<Ret, 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>(); }
+
+ /// @internal Used by ResourceObserver and ResourceProxyModel
+ QVariant getResourceInternal(const int typeId) const;
+
+ /** Register a new ResourceHandler. T needs to inherit from ResourceHandler
+ * Usage: Resource::registerHandler<MyResourceHandler>("myid");
+ */
+ template<typename T>
+ static void registerHandler(const QString &id)
+ {
+ m_handlers.insert(id, [](const QString &res) { return std::make_shared<T>(res); });
+ }
+ /** Register a new resource transformer
+ * Resource transformers are functions that are responsible for converting between different types,
+ * for example converting from a QByteArray to a QPixmap. They are registered "externally" because not
+ * all types might be available in this library, for example gui types like QPixmap.
+ *
+ * Usage: Resource::registerTransformer([](const InputType &type) { return OutputType(type); });
+ * This assumes that OutputType has a constructor that takes InputType as an argument. More
+ * complicated transformers can of course also be registered.
+ *
+ * When a ResourceObserver requests a type that's different from the actual resource type, a matching
+ * transformer will be looked up from the list of transformers.
+ * @note Only one-stage transforms will be performed (you can't registerTransformers for QString => int
+ * and int => float and expect QString to automatically be transformed into a float.
+ */
+ template<typename Func>
+ static void registerTransformer(Func &&func)
+ {
+ using Out = typename TypeMagic::Function<Func>::ReturnType;
+ using In = TypeMagic::CleanType<typename TypeMagic::Function<Func>::Argument>;
+ 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: // half private, implementation details
+ friend class ResourceHandler;
+ // the following three functions are called by ResourceHandlers
+ /** Notifies the observers. They will call Resource::getResourceInternal which will call ResourceHandler::result
+ * or delegate to it's placeholder.
+ */
+ void reportResult();
+ void reportFailure(const QString &reason);
+ void reportProgress(const int progress);
+
+ friend class ResourceObserver;
+ /// Removes observer from the list of observers so that we don't attempt to notify something that doesn't exist
+ void notifyObserverDeleted(ResourceObserver *observer);
+
+private: // truly private
+ QList<ResourceObserver *> m_observers;
+ std::shared_ptr<ResourceHandler> m_handler = nullptr;
+ Ptr m_placeholder = nullptr;
+ const QString m_resource;
+
+ static QString storageIdentifier(const QString &id, Ptr placeholder = nullptr);
+ QString storageIdentifier() const;
+
+ // 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;
+ // a list of resources so that we can reuse them
+ static QMap<QString, std::weak_ptr<Resource>> m_resources;
+};
diff --git a/api/dead/src/resources/ResourceHandler.cpp b/api/dead/src/resources/ResourceHandler.cpp
new file mode 100644
index 00000000..46a4422c
--- /dev/null
+++ b/api/dead/src/resources/ResourceHandler.cpp
@@ -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);
+ }
+}
diff --git a/api/dead/src/resources/ResourceHandler.h b/api/dead/src/resources/ResourceHandler.h
new file mode 100644
index 00000000..f09d8904
--- /dev/null
+++ b/api/dead/src/resources/ResourceHandler.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <QVariant>
+#include <memory>
+
+#include "multimc_logic_export.h"
+
+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 MULTIMC_LOGIC_EXPORT 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
+ // we do this instead of inheriting from std::enable_shared_from_this
+ 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;
+};
diff --git a/api/dead/src/resources/ResourceObserver.cpp b/api/dead/src/resources/ResourceObserver.cpp
new file mode 100644
index 00000000..4f168fd2
--- /dev/null
+++ b/api/dead/src/resources/ResourceObserver.cpp
@@ -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);
+}
diff --git a/api/dead/src/resources/ResourceObserver.h b/api/dead/src/resources/ResourceObserver.h
new file mode 100644
index 00000000..c42e41ba
--- /dev/null
+++ b/api/dead/src/resources/ResourceObserver.h
@@ -0,0 +1,73 @@
+#pragma once
+
+#include <memory>
+#include <functional>
+
+#include <QObject>
+#include <QMetaProperty>
+#include "multimc_logic_export.h"
+
+class QVariant;
+class Resource;
+
+/// Base class for things that can use a resource
+class MULTIMC_LOGIC_EXPORT 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 MULTIMC_LOGIC_EXPORT QObjectResourceObserver : public QObject, public ResourceObserver
+{
+public:
+ explicit QObjectResourceObserver(QObject *target, const char *property = nullptr);
+
+ void resourceUpdated() override;
+
+private:
+ QObject *m_target;
+ QMetaProperty m_property;
+};
+
+/** Observer for functions, lambdas etc.
+ * Template arguments:
+ * * We need Ret and Arg in order to create the std::function
+ * * We need Func in order to std::forward the function
+ */
+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>());
+ }
+};
diff --git a/api/dead/src/resources/ResourceProxyModel.cpp b/api/dead/src/resources/ResourceProxyModel.cpp
new file mode 100644
index 00000000..f026d9a9
--- /dev/null
+++ b/api/dead/src/resources/ResourceProxyModel.cpp
@@ -0,0 +1,89 @@
+#include "ResourceProxyModel.h"
+
+#include <QItemSelectionRange>
+
+#include "Resource.h"
+#include "ResourceObserver.h"
+
+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())
+ {
+ // the resource changed, pretend to be the model and notify the views of the update. they will re-poll the model which will return the new resource value
+ 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);
+ // valid cell that's a Qt::DecorationRole and that contains a non-empty string
+ if (mapped.isValid() && role == Qt::DecorationRole && !mapToSource(proxyIndex).data(role).toString().isEmpty())
+ {
+ // do we already have a resource for this index?
+ if (!m_resources.contains(mapped))
+ {
+ Resource::Ptr placeholder;
+ const QVariant placeholderIdentifier = mapped.data(PlaceholderRole);
+ if (!placeholderIdentifier.isNull() && placeholderIdentifier.type() == QVariant::String)
+ {
+ placeholder = Resource::create(placeholderIdentifier.toString());
+ }
+
+ // create the Resource and apply the observer for models
+ Resource::Ptr res = Resource::create(mapToSource(proxyIndex).data(role).toString(), placeholder)
+ ->applyTo(new ModelResourceObserver(proxyIndex, role));
+
+ m_resources.insert(mapped, res);
+ }
+
+ return m_resources.value(mapped)->getResourceInternal(m_resultTypeId);
+ }
+ // otherwise fall back to the source model
+ 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)
+ {
+ // invalidate resources so that they will be re-created
+ if (roles.contains(Qt::DecorationRole) || roles.contains(PlaceholderRole) || roles.isEmpty())
+ {
+ const QItemSelectionRange range(tl, br);
+ for (const QModelIndex &index : range.indexes())
+ {
+ m_resources.remove(index);
+ }
+ }
+ });
+ }
+ QIdentityProxyModel::setSourceModel(model);
+}
diff --git a/api/dead/src/resources/ResourceProxyModel.h b/api/dead/src/resources/ResourceProxyModel.h
new file mode 100644
index 00000000..98a3dbd1
--- /dev/null
+++ b/api/dead/src/resources/ResourceProxyModel.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <QIdentityProxyModel>
+#include <memory>
+
+#include "multimc_logic_export.h"
+
+/// Convenience proxy model that transforms resource identifiers (strings) for Qt::DecorationRole into other types.
+class MULTIMC_LOGIC_EXPORT 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;
+
+ /// Helper function, usage: m_view->setModel(ResourceProxyModel::mixin<QIcon>(m_model));
+ 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;
+};
diff --git a/api/dead/test/tst_Resource.cpp b/api/dead/test/tst_Resource.cpp
new file mode 100644
index 00000000..4bf41a03
--- /dev/null
+++ b/api/dead/test/tst_Resource.cpp
@@ -0,0 +1,116 @@
+#include <QTest>
+#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()
+ {
+ // since dummy:asdf immediently gives a value we should not get the placeholder
+ Resource::create("dummy:asdf", Resource::create("dummy:fdsa"))
+ ->then([](const QString &key) { QCOMPARE(key, QStringLiteral("asdf")); });
+ }
+
+ 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);
+ }
+
+ void test_MergedResourceWithPlaceholder()
+ {
+ auto p1 = Resource::create("dummy:placeA");
+ auto p2 = Resource::create("dummy:placeB");
+
+ auto r1 = Resource::create("dummy:asdf");
+ auto r2 = Resource::create("dummy:asdf", p1);
+ auto r3 = Resource::create("dummy:asdf", p2);
+ auto r4 = Resource::create("dummy:asdf", p1);
+
+ QCOMPARE(r2, r4);
+ QVERIFY(r1 != r2);
+ QVERIFY(r1 != r3);
+ QVERIFY(r1 != r4);
+ QVERIFY(r2 != r3);
+ }
+};
+
+QTEST_GUILESS_MAIN(ResourceTest)
+
+#include "tst_Resource.moc"