From 94a2cd6ec21bf0466a9a50d6e4a0a956ed47bc82 Mon Sep 17 00:00:00 2001 From: Christian Mollekopf Date: Thu, 19 Nov 2015 23:23:56 +0100 Subject: Move implementations to the cpp file. I finally figured out how to do that with cpp files. It requires instantiating the code with all expected classes, but that's not a big problem since we know all types. This will hopefully greatly reduce the compiletimes... --- common/clientapi.cpp | 151 ++++++++++++++++++++++++- common/clientapi.h | 109 ++---------------- common/domain/applicationdomaintype.h | 2 + common/modelresult.cpp | 203 ++++++++++++++++++++++++++++++++++ common/modelresult.h | 167 +++------------------------- examples/dummyresource/dummystore.cpp | 22 ++++ 6 files changed, 400 insertions(+), 254 deletions(-) create mode 100644 common/modelresult.cpp diff --git a/common/clientapi.cpp b/common/clientapi.cpp index 839e77b..02f8ce6 100644 --- a/common/clientapi.cpp +++ b/common/clientapi.cpp @@ -1,13 +1,40 @@ +/* + * Copyright (C) 2014 Christian Mollekopf + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ #include "clientapi.h" + +#include +#include +#include +#include +#include +#include + #include "resourceaccess.h" #include "commands.h" #include "resourcefacade.h" #include "log.h" #include "definitions.h" #include "resourceconfig.h" -#include -#include +#include "facadefactory.h" +#include "log.h" #define ASYNCINTHREAD @@ -69,6 +96,114 @@ QList Store::getResources(const QList &resourceFilter, c return resources; } +template +QSharedPointer > Store::load(Query query) +{ + auto resultSet = QSharedPointer >::create(); + + //Execute the search in a thread. + //We must guarantee that the emitter is returned before the first result is emitted. + //The result provider must be threadsafe. + async::run([query, resultSet](){ + QEventLoop eventLoop; + resultSet->onDone([&eventLoop](){ + eventLoop.quit(); + }); + // Query all resources and aggregate results + KAsync::iterate(getResources(query.resources, ApplicationDomain::getTypeName())) + .template each([query, resultSet](const QByteArray &resource, KAsync::Future &future) { + if (auto facade = FacadeFactory::instance().getFacade(resourceName(resource), resource)) { + facade->load(query, *resultSet).template then([&future](){future.setFinished();}).exec(); + //Keep the facade alive for the lifetime of the resultSet. + resultSet->setFacade(facade); + } else { + //Ignore the error and carry on + future.setFinished(); + } + }).template then([query, resultSet]() { + resultSet->initialResultSetComplete(); + if (!query.liveQuery) { + resultSet->complete(); + } + }).exec(); + + //Keep the thread alive until the result is ready + if (!resultSet->isDone()) { + eventLoop.exec(); + } + }); + return resultSet->emitter(); +} + +template +QSharedPointer Store::loadModel(Query query) +{ + auto model = QSharedPointer >::create(query, query.requestedProperties.toList()); + auto resultProvider = std::make_shared >(model); + //Keep the resultprovider alive for as long as the model lives + model->setProperty("resultProvider", QVariant::fromValue(std::shared_ptr(resultProvider))); + + // Query all resources and aggregate results + KAsync::iterate(getResources(query.resources, ApplicationDomain::getTypeName())) + .template each([query, resultProvider](const QByteArray &resource, KAsync::Future &future) { + auto facade = FacadeFactory::instance().getFacade(resourceName(resource), resource); + if (facade) { + facade->load(query, *resultProvider).template then([&future](){future.setFinished();}).exec(); + //Keep the facade alive for the lifetime of the resultSet. + //FIXME this would have to become a list + resultProvider->setFacade(facade); + } else { + //Ignore the error and carry on + future.setFinished(); + } + }).template then([query, resultProvider]() { + resultProvider->initialResultSetComplete(); + if (!query.liveQuery) { + resultProvider->complete(); + } + }).exec(); + + return model; +} + +template +static std::shared_ptr > getFacade(const QByteArray &resourceInstanceIdentifier) +{ + if (auto facade = FacadeFactory::instance().getFacade(resourceName(resourceInstanceIdentifier), resourceInstanceIdentifier)) { + return facade; + } + return std::make_shared >(); +} + +template +KAsync::Job Store::create(const DomainType &domainObject) { + //Potentially move to separate thread as well + auto facade = getFacade(domainObject.resourceInstanceIdentifier()); + return facade->create(domainObject).template then([facade](){}, [](int errorCode, const QString &error) { + Warning() << "Failed to create"; + }); +} + +template +KAsync::Job Store::modify(const DomainType &domainObject) +{ + //Potentially move to separate thread as well + auto facade = getFacade(domainObject.resourceInstanceIdentifier()); + return facade->modify(domainObject).template then([facade](){}, [](int errorCode, const QString &error) { + Warning() << "Failed to modify"; + }); +} + +template +KAsync::Job Store::remove(const DomainType &domainObject) +{ + //Potentially move to separate thread as well + auto facade = getFacade(domainObject.resourceInstanceIdentifier()); + return facade->remove(domainObject).template then([facade](){}, [](int errorCode, const QString &error) { + Warning() << "Failed to remove"; + }); +} + KAsync::Job Store::shutdown(const QByteArray &identifier) { Trace() << "shutdown"; @@ -103,4 +238,16 @@ KAsync::Job Store::synchronize(const Akonadi2::Query &query) .template then([](){}); } +#define REGISTER_TYPE(T) template KAsync::Job Store::remove(const T &domainObject); \ + template KAsync::Job Store::create(const T &domainObject); \ + template KAsync::Job Store::modify(const T &domainObject); \ + template QSharedPointer > Store::load(Query query); \ + template QSharedPointer Store::loadModel(Query query); \ + +REGISTER_TYPE(ApplicationDomain::Event); +REGISTER_TYPE(ApplicationDomain::Mail); +REGISTER_TYPE(ApplicationDomain::Folder); +REGISTER_TYPE(ApplicationDomain::AkonadiResource); + } // namespace Akonadi2 + diff --git a/common/clientapi.h b/common/clientapi.h index 6b11ad5..c48c6e9 100644 --- a/common/clientapi.h +++ b/common/clientapi.h @@ -22,21 +22,17 @@ #include #include -#include -#include -#include -#include #include #include "query.h" #include "resultprovider.h" #include "applicationdomaintype.h" -#include "facadefactory.h" -#include "log.h" Q_DECLARE_METATYPE(std::shared_ptr); +class QAbstractItemModel; + namespace async { //This should abstract if we execute from eventloop or in thread. //It supposed to allow the caller to finish the current method before executing the runner. @@ -62,43 +58,7 @@ public: * Asynchronusly load a dataset */ template - static QSharedPointer > load(Query query) - { - auto resultSet = QSharedPointer >::create(); - - //Execute the search in a thread. - //We must guarantee that the emitter is returned before the first result is emitted. - //The result provider must be threadsafe. - async::run([query, resultSet](){ - QEventLoop eventLoop; - resultSet->onDone([&eventLoop](){ - eventLoop.quit(); - }); - // Query all resources and aggregate results - KAsync::iterate(getResources(query.resources, ApplicationDomain::getTypeName())) - .template each([query, resultSet](const QByteArray &resource, KAsync::Future &future) { - if (auto facade = FacadeFactory::instance().getFacade(resourceName(resource), resource)) { - facade->load(query, *resultSet).template then([&future](){future.setFinished();}).exec(); - //Keep the facade alive for the lifetime of the resultSet. - resultSet->setFacade(facade); - } else { - //Ignore the error and carry on - future.setFinished(); - } - }).template then([query, resultSet]() { - resultSet->initialResultSetComplete(); - if (!query.liveQuery) { - resultSet->complete(); - } - }).exec(); - - //Keep the thread alive until the result is ready - if (!resultSet->isDone()) { - eventLoop.exec(); - } - }); - return resultSet->emitter(); - } + static QSharedPointer > load(Query query); enum Roles { DomainObjectRole = Qt::UserRole + 1 //Must be the same as in ModelResult @@ -108,56 +68,13 @@ public: * Asynchronusly load a dataset with tree structure information */ template - static QSharedPointer loadModel(Query query) - { - auto model = QSharedPointer >::create(query, query.requestedProperties.toList()); - auto resultProvider = std::make_shared >(model); - //Keep the resultprovider alive for as long as the model lives - model->setProperty("resultProvider", QVariant::fromValue(std::shared_ptr(resultProvider))); - - // Query all resources and aggregate results - KAsync::iterate(getResources(query.resources, ApplicationDomain::getTypeName())) - .template each([query, resultProvider](const QByteArray &resource, KAsync::Future &future) { - auto facade = FacadeFactory::instance().getFacade(resourceName(resource), resource); - if (facade) { - facade->load(query, *resultProvider).template then([&future](){future.setFinished();}).exec(); - //Keep the facade alive for the lifetime of the resultSet. - //FIXME this would have to become a list - resultProvider->setFacade(facade); - } else { - //Ignore the error and carry on - future.setFinished(); - } - }).template then([query, resultProvider]() { - resultProvider->initialResultSetComplete(); - if (!query.liveQuery) { - resultProvider->complete(); - } - }).exec(); - - return model; - } - - template - static std::shared_ptr > getFacade(const QByteArray &resourceInstanceIdentifier) - { - if (auto facade = FacadeFactory::instance().getFacade(resourceName(resourceInstanceIdentifier), resourceInstanceIdentifier)) { - return facade; - } - return std::make_shared >(); - } + static QSharedPointer loadModel(Query query); /** * Create a new entity. */ template - static KAsync::Job create(const DomainType &domainObject) { - //Potentially move to separate thread as well - auto facade = getFacade(domainObject.resourceInstanceIdentifier()); - return facade->create(domainObject).template then([facade](){}, [](int errorCode, const QString &error) { - Warning() << "Failed to create"; - }); - } + static KAsync::Job create(const DomainType &domainObject); /** * Modify an entity. @@ -165,25 +82,13 @@ public: * This includes moving etc. since these are also simple settings on a property. */ template - static KAsync::Job modify(const DomainType &domainObject) { - //Potentially move to separate thread as well - auto facade = getFacade(domainObject.resourceInstanceIdentifier()); - return facade->modify(domainObject).template then([facade](){}, [](int errorCode, const QString &error) { - Warning() << "Failed to modify"; - }); - } + static KAsync::Job modify(const DomainType &domainObject); /** * Remove an entity. */ template - static KAsync::Job remove(const DomainType &domainObject) { - //Potentially move to separate thread as well - auto facade = getFacade(domainObject.resourceInstanceIdentifier()); - return facade->remove(domainObject).template then([facade](){}, [](int errorCode, const QString &error) { - Warning() << "Failed to remove"; - }); - } + static KAsync::Job remove(const DomainType &domainObject); /** * Shutdown resource. diff --git a/common/domain/applicationdomaintype.h b/common/domain/applicationdomaintype.h index b4cf8c4..227ab4d 100644 --- a/common/domain/applicationdomaintype.h +++ b/common/domain/applicationdomaintype.h @@ -162,3 +162,5 @@ Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Mail) Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Mail::Ptr) Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Folder) Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Folder::Ptr) +Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::AkonadiResource) +Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::AkonadiResource::Ptr) diff --git a/common/modelresult.cpp b/common/modelresult.cpp new file mode 100644 index 0000000..1abcc62 --- /dev/null +++ b/common/modelresult.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2014 Christian Mollekopf + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ +#include "modelresult.h" + +#include + +#include "domain/folder.h" +#include "log.h" + +template +ModelResult::ModelResult(const Akonadi2::Query &query, const QList &propertyColumns) + :QAbstractItemModel(), + mPropertyColumns(propertyColumns) +{ +} + +static qint64 getIdentifier(const QModelIndex &idx) +{ + if (!idx.isValid()) { + return 0; + } + return idx.internalId(); +} + +template +qint64 ModelResult::parentId(const Ptr &value) +{ + return qHash(value->getProperty("parent").toByteArray()); +} + +template +int ModelResult::rowCount(const QModelIndex &parent) const +{ + return mTree[getIdentifier(parent)].size(); +} + +template +int ModelResult::columnCount(const QModelIndex &parent) const +{ + return mPropertyColumns.size(); +} + +template +QVariant ModelResult::data(const QModelIndex &index, int role) const +{ + if (role == DomainObjectRole) { + Q_ASSERT(mEntities.contains(index.internalId())); + return QVariant::fromValue(mEntities.value(index.internalId())); + } + if (role == Qt::DisplayRole) { + if (index.column() < mPropertyColumns.size()) { + Q_ASSERT(mEntities.contains(index.internalId())); + auto entity = mEntities.value(index.internalId()); + return entity->getProperty(mPropertyColumns.at(index.column())).toString(); + } else { + return "No data available"; + } + } + return QVariant(); +} + +template +QModelIndex ModelResult::index(int row, int column, const QModelIndex &parent) const +{ + auto id = getIdentifier(parent); + auto childId = mTree.value(id).at(row); + return createIndex(row, column, childId); +} + +template +QModelIndex ModelResult::createIndexFromId(const qint64 &id) const +{ + auto grandParentId = mParents.value(id, 0); + auto row = mTree.value(grandParentId).indexOf(id); + return createIndex(row, 0, id); +} + +template +QModelIndex ModelResult::parent(const QModelIndex &index) const +{ + auto id = getIdentifier(index); + auto parentId = mParents.value(id); + return createIndexFromId(parentId); +} + +template +bool ModelResult::canFetchMore(const QModelIndex &parent) const +{ + qDebug() << "Can fetch more: " << parent << mEntityChildrenFetched.value(parent.internalId()); + return mEntityChildrenFetched.value(parent.internalId()); +} + +template +void ModelResult::fetchMore(const QModelIndex &parent) +{ + qDebug() << "Fetch more: " << parent; + fetchEntities(parent); +} + +template +void ModelResult::add(const Ptr &value) +{ + auto childId = qHash(value->identifier()); + auto id = parentId(value); + //Ignore updates we get before the initial fetch is done + if (!mEntityChildrenFetched[id]) { + return; + } + auto parent = createIndexFromId(id); + qDebug() << "Added entity " << childId << value->identifier() << id; + const auto keys = mTree[id]; + int index = 0; + for (; index < keys.size(); index++) { + if (childId < keys.at(index)) { + break; + } + } + if (mEntities.contains(childId)) { + qWarning() << "Entity already in model " << value->identifier(); + return; + } + qDebug() << "Inserting rows " << index << parent; + beginInsertRows(QModelIndex(), index, index); + mEntities.insert(childId, value); + mTree[id].insert(index, childId); + mParents.insert(childId, id); + endInsertRows(); + qDebug() << "Inserted rows " << mTree[id].size(); +} + + +template +void ModelResult::remove(const Ptr &value) +{ + auto childId = qHash(value->identifier()); + auto id = parentId(value); + auto parent = createIndexFromId(id); + qDebug() << "Removed entity" << childId; + auto index = mTree[id].indexOf(qHash(value->identifier())); + beginRemoveRows(parent, index, index); + mEntities.remove(childId); + mTree[id].removeAll(childId); + mParents.remove(childId); + //TODO remove children + endRemoveRows(); +} + +template +void ModelResult::fetchEntities(const QModelIndex &parent) +{ + const auto id = getIdentifier(parent); + mEntityChildrenFetched[id] = true; + Trace() << "Loading child entities"; + loadEntities(parent.data(DomainObjectRole).template value()); +} + +template +void ModelResult::setFetcher(const std::function &fetcher) +{ + Trace() << "Setting fetcher"; + loadEntities = fetcher; +} + +template +void ModelResult::modify(const Ptr &value) +{ + auto childId = qHash(value->identifier()); + auto id = parentId(value); + //Ignore updates we get before the initial fetch is done + if (!mEntityChildrenFetched[id]) { + return; + } + auto parent = createIndexFromId(id); + qDebug() << "Modified entity" << childId; + auto i = mTree[id].indexOf(childId); + mEntities.remove(childId); + mEntities.insert(childId, value); + //TODO check for change of parents + auto idx = index(i, 0, parent); + emit dataChanged(idx, idx); +} + +template class ModelResult; +template class ModelResult; +template class ModelResult; +template class ModelResult; diff --git a/common/modelresult.h b/common/modelresult.h index 26f96d8..40a9d9d 100644 --- a/common/modelresult.h +++ b/common/modelresult.h @@ -23,10 +23,9 @@ #include #include #include +#include #include "query.h" -#include "resultprovider.h" - template class ModelResult : public QAbstractItemModel { @@ -36,160 +35,28 @@ public: DomainObjectRole = Qt::UserRole + 1 }; - ModelResult(const Akonadi2::Query &query, const QList &propertyColumns) - :QAbstractItemModel(), - mPropertyColumns(propertyColumns) - { - } - - static qint64 getIdentifier(const QModelIndex &idx) - { - if (!idx.isValid()) { - return 0; - } - return idx.internalId(); - } - - int rowCount(const QModelIndex &parent = QModelIndex()) const - { - return mTree[getIdentifier(parent)].size(); - } - - int columnCount(const QModelIndex &parent = QModelIndex()) const - { - return mPropertyColumns.size(); - } - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const - { - if (role == DomainObjectRole) { - Q_ASSERT(mEntities.contains(index.internalId())); - return QVariant::fromValue(mEntities.value(index.internalId())); - } - if (role == Qt::DisplayRole) { - if (index.column() < mPropertyColumns.size()) { - Q_ASSERT(mEntities.contains(index.internalId())); - auto entity = mEntities.value(index.internalId()); - return entity->getProperty(mPropertyColumns.at(index.column())).toString(); - } else { - return "No data available"; - } - } - return QVariant(); - } - - QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const - { - auto id = getIdentifier(parent); - auto childId = mTree.value(id).at(row); - return createIndex(row, column, childId); - } - - QModelIndex createIndexFromId(const qint64 &id) const - { - auto grandParentId = mParents.value(id, 0); - auto row = mTree.value(grandParentId).indexOf(id); - return createIndex(row, 0, id); - } + ModelResult(const Akonadi2::Query &query, const QList &propertyColumns); - QModelIndex parent(const QModelIndex &index) const - { - auto id = getIdentifier(index); - auto parentId = mParents.value(id); - return createIndexFromId(parentId); - } + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; - bool canFetchMore(const QModelIndex &parent) const - { - return mEntityChildrenFetched.value(parent.internalId()); - } + bool canFetchMore(const QModelIndex &parent) const; + void fetchMore(const QModelIndex &parent); - void fetchMore(const QModelIndex &parent) - { - fetchEntities(parent); - } + void add(const Ptr &value); + void modify(const Ptr &value); + void remove(const Ptr &value); - qint64 parentId(const Ptr &value) - { - return qHash(value->getProperty("parent").toByteArray()); - } - - void add(const Ptr &value) - { - auto childId = qHash(value->identifier()); - auto id = parentId(value); - //Ignore updates we get before the initial fetch is done - if (!mEntityChildrenFetched[id]) { - return; - } - auto parent = createIndexFromId(id); - qDebug() << "Added entity " << childId << value->identifier(); - const auto keys = mTree[id]; - int index = 0; - for (; index < keys.size(); index++) { - if (childId < keys.at(index)) { - break; - } - } - if (mEntities.contains(childId)) { - qWarning() << "Entity already in model " << value->identifier(); - return; - } - beginInsertRows(QModelIndex(), index, index); - mEntities.insert(childId, value); - mTree[id].insert(index, childId); - mParents.insert(childId, id); - endInsertRows(); - } - - void modify(const Ptr &value) - { - auto childId = qHash(value->identifier()); - auto id = parentId(value); - //Ignore updates we get before the initial fetch is done - if (!mEntityChildrenFetched[id]) { - return; - } - auto parent = createIndexFromId(id); - qDebug() << "Modified entity" << childId; - auto i = mTree[id].indexOf(childId); - mEntities.remove(childId); - mEntities.insert(childId, value); - //TODO check for change of parents - auto idx = index(i, 0, parent); - emit dataChanged(idx, idx); - } - - void remove(const Ptr &value) - { - auto childId = qHash(value->identifier()); - auto id = parentId(value); - auto parent = createIndexFromId(id); - qDebug() << "Removed entity" << childId; - auto index = mTree[id].indexOf(qHash(value->identifier())); - beginRemoveRows(parent, index, index); - mEntities.remove(childId); - mTree[id].removeAll(childId); - mParents.remove(childId); - //TODO remove children - endRemoveRows(); - } - - void fetchEntities(const QModelIndex &parent) - { - const auto id = getIdentifier(parent); - mEntityChildrenFetched[id] = true; - Trace() << "Loading child entities"; - loadEntities(parent.data(DomainObjectRole).template value()); - } - - void setFetcher(const std::function &fetcher) - { - Trace() << "Setting fetcher"; - loadEntities = fetcher; - } + void setFetcher(const std::function &fetcher); private: + static qint64 parentId(const Ptr &value); + QModelIndex createIndexFromId(const qint64 &id) const; + void fetchEntities(const QModelIndex &parent); + //TODO we should be able to directly use T as index, with an appropriate hash function, and thus have a QMap and QList QMap mEntities; QMap /* child entity id*/> mTree; diff --git a/examples/dummyresource/dummystore.cpp b/examples/dummyresource/dummystore.cpp index 0356ec0..39ecfe4 100644 --- a/examples/dummyresource/dummystore.cpp +++ b/examples/dummyresource/dummystore.cpp @@ -36,6 +36,13 @@ static QMap createMail(int i) return mail; } +static QMap createFolder(int i) +{ + QMap folder; + folder.insert("name", QString("folder%1").arg(i)); + return folder; +} + QMap > populateEvents() { QMap> content; @@ -54,8 +61,18 @@ QMap > populateMails() return content; } +QMap > populateFolders() +{ + QMap> content; + for (int i = 0; i < 5000; i++) { + content.insert(QString("key%1").arg(i), createFolder(i)); + } + return content; +} + static QMap > s_eventSource = populateEvents(); static QMap > s_mailSource = populateMails(); +static QMap > s_folderSource = populateFolders(); QMap > DummyStore::events() const { @@ -66,3 +83,8 @@ QMap > DummyStore::mails() const { return s_mailSource; } + +QMap > DummyStore::folders() const +{ + return s_folderSource; +} -- cgit v1.2.3