156 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			156 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #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;
 | |
| 	}
 | |
| }
 |