summaryrefslogtreecommitdiffstats
path: root/api/logic/resources/Resource.h
blob: 63e97b889ba617ffd1ee098b0fa0ae00b8d9ba63 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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;
};