From 3a8b238052163952831fb5924b2483a375e86ebd Mon Sep 17 00:00:00 2001
From: Jan Dalheimer <jan@dalheimer.de>
Date: Thu, 28 May 2015 19:38:29 +0200
Subject: NOISSUE Various changes from multiauth that are unrelated to it

---
 logic/resources/IconResourceHandler.cpp |  60 ++++++++++++++++
 logic/resources/IconResourceHandler.h   |  22 ++++++
 logic/resources/Resource.cpp            | 121 ++++++++++++++++++++++++++++++++
 logic/resources/Resource.h              | 116 ++++++++++++++++++++++++++++++
 logic/resources/ResourceHandler.cpp     |  28 ++++++++
 logic/resources/ResourceHandler.h       |  33 +++++++++
 logic/resources/ResourceObserver.cpp    |  55 +++++++++++++++
 logic/resources/ResourceObserver.h      |  67 ++++++++++++++++++
 logic/resources/ResourceProxyModel.cpp  | 103 +++++++++++++++++++++++++++
 logic/resources/ResourceProxyModel.h    |  36 ++++++++++
 logic/resources/WebResourceHandler.cpp  |  67 ++++++++++++++++++
 logic/resources/WebResourceHandler.h    |  23 ++++++
 12 files changed, 731 insertions(+)
 create mode 100644 logic/resources/IconResourceHandler.cpp
 create mode 100644 logic/resources/IconResourceHandler.h
 create mode 100644 logic/resources/Resource.cpp
 create mode 100644 logic/resources/Resource.h
 create mode 100644 logic/resources/ResourceHandler.cpp
 create mode 100644 logic/resources/ResourceHandler.h
 create mode 100644 logic/resources/ResourceObserver.cpp
 create mode 100644 logic/resources/ResourceObserver.h
 create mode 100644 logic/resources/ResourceProxyModel.cpp
 create mode 100644 logic/resources/ResourceProxyModel.h
 create mode 100644 logic/resources/WebResourceHandler.cpp
 create mode 100644 logic/resources/WebResourceHandler.h

(limited to 'logic/resources')

diff --git a/logic/resources/IconResourceHandler.cpp b/logic/resources/IconResourceHandler.cpp
new file mode 100644
index 00000000..d47dcc3d
--- /dev/null
+++ b/logic/resources/IconResourceHandler.cpp
@@ -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;
+}
diff --git a/logic/resources/IconResourceHandler.h b/logic/resources/IconResourceHandler.h
new file mode 100644
index 00000000..dedfecb2
--- /dev/null
+++ b/logic/resources/IconResourceHandler.h
@@ -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;
+};
diff --git a/logic/resources/Resource.cpp b/logic/resources/Resource.cpp
new file mode 100644
index 00000000..16ed3d2d
--- /dev/null
+++ b/logic/resources/Resource.cpp
@@ -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);
+}
diff --git a/logic/resources/Resource.h b/logic/resources/Resource.h
new file mode 100644
index 00000000..d566b2a2
--- /dev/null
+++ b/logic/resources/Resource.h
@@ -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;
+};
diff --git a/logic/resources/ResourceHandler.cpp b/logic/resources/ResourceHandler.cpp
new file mode 100644
index 00000000..46a4422c
--- /dev/null
+++ b/logic/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/logic/resources/ResourceHandler.h b/logic/resources/ResourceHandler.h
new file mode 100644
index 00000000..c1105efc
--- /dev/null
+++ b/logic/resources/ResourceHandler.h
@@ -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;
+};
diff --git a/logic/resources/ResourceObserver.cpp b/logic/resources/ResourceObserver.cpp
new file mode 100644
index 00000000..4f168fd2
--- /dev/null
+++ b/logic/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/logic/resources/ResourceObserver.h b/logic/resources/ResourceObserver.h
new file mode 100644
index 00000000..27430d42
--- /dev/null
+++ b/logic/resources/ResourceObserver.h
@@ -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>());
+	}
+};
diff --git a/logic/resources/ResourceProxyModel.cpp b/logic/resources/ResourceProxyModel.cpp
new file mode 100644
index 00000000..6ff11367
--- /dev/null
+++ b/logic/resources/ResourceProxyModel.cpp
@@ -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);
+}
diff --git a/logic/resources/ResourceProxyModel.h b/logic/resources/ResourceProxyModel.h
new file mode 100644
index 00000000..9db09545
--- /dev/null
+++ b/logic/resources/ResourceProxyModel.h
@@ -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;
+};
diff --git a/logic/resources/WebResourceHandler.cpp b/logic/resources/WebResourceHandler.cpp
new file mode 100644
index 00000000..7ced5bc6
--- /dev/null
+++ b/logic/resources/WebResourceHandler.cpp
@@ -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());
+	}
+}
diff --git a/logic/resources/WebResourceHandler.h b/logic/resources/WebResourceHandler.h
new file mode 100644
index 00000000..88804af3
--- /dev/null
+++ b/logic/resources/WebResourceHandler.h
@@ -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);
+};
-- 
cgit