summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/CMakeLists.txt12
-rw-r--r--common/clientapi.cpp117
-rw-r--r--common/clientapi.h97
-rw-r--r--common/domain/applicationdomaintype.cpp9
-rw-r--r--common/domain/applicationdomaintype.h4
-rw-r--r--common/domain/dummy.fbs7
-rw-r--r--common/domain/event.cpp1
-rw-r--r--common/domain/folder.cpp100
-rw-r--r--common/domain/folder.fbs9
-rw-r--r--common/domain/folder.h56
-rw-r--r--common/domainadaptor.h13
-rw-r--r--common/domaintypeadaptorfactoryinterface.h42
-rw-r--r--common/entitystorage.cpp74
-rw-r--r--common/entitystorage.h126
-rw-r--r--common/facade.cpp81
-rw-r--r--common/facade.h262
-rw-r--r--common/facadeinterface.h29
-rw-r--r--common/modelresult.cpp251
-rw-r--r--common/modelresult.h78
-rw-r--r--common/query.h1
-rw-r--r--common/queryrunner.cpp312
-rw-r--r--common/queryrunner.h108
-rw-r--r--common/resourceaccess.cpp3
-rw-r--r--common/resourceaccess.h2
-rw-r--r--common/resourcefacade.cpp13
-rw-r--r--common/resourcefacade.h3
-rw-r--r--common/resultprovider.h114
-rw-r--r--common/storage_lmdb.cpp71
-rw-r--r--common/threadboundary.cpp5
-rw-r--r--examples/client/main.cpp87
-rw-r--r--examples/dummyresource/CMakeLists.txt2
-rw-r--r--examples/dummyresource/domainadaptor.cpp6
-rw-r--r--examples/dummyresource/domainadaptor.h12
-rw-r--r--examples/dummyresource/dummystore.cpp22
-rw-r--r--examples/dummyresource/dummystore.h2
-rw-r--r--examples/dummyresource/facade.cpp22
-rw-r--r--examples/dummyresource/facade.h6
-rw-r--r--examples/dummyresource/resourcefacade.cpp84
-rw-r--r--examples/dummyresource/resourcefacade.h49
-rw-r--r--examples/dummyresource/resourcefactory.cpp77
-rw-r--r--examples/dummyresource/resourcefactory.h1
-rw-r--r--tests/CMakeLists.txt10
-rw-r--r--tests/clientapitest.cpp218
-rw-r--r--tests/dummyresourcebenchmark.cpp40
-rw-r--r--tests/dummyresourcetest.cpp105
-rw-r--r--tests/genericfacadebenchmark.cpp6
-rw-r--r--tests/genericfacadetest.cpp47
-rw-r--r--tests/pipelinetest.cpp2
-rw-r--r--tests/querytest.cpp161
-rw-r--r--tests/storagetest.cpp30
-rw-r--r--tests/testimplementations.h31
51 files changed, 2050 insertions, 970 deletions
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index b4a4703..e56ece9 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -12,10 +12,10 @@ else (STORAGE_unqlite)
12endif (STORAGE_unqlite) 12endif (STORAGE_unqlite)
13 13
14set(command_SRCS 14set(command_SRCS
15 modelresult.cpp
15 definitions.cpp 16 definitions.cpp
16 log.cpp 17 log.cpp
17 entitybuffer.cpp 18 entitybuffer.cpp
18 entitystorage.cpp
19 clientapi.cpp 19 clientapi.cpp
20 facadefactory.cpp 20 facadefactory.cpp
21 commands.cpp 21 commands.cpp
@@ -26,6 +26,7 @@ set(command_SRCS
26 resource.cpp 26 resource.cpp
27 genericresource.cpp 27 genericresource.cpp
28 resourceaccess.cpp 28 resourceaccess.cpp
29 queryrunner.cpp
29 listener.cpp 30 listener.cpp
30 storage_common.cpp 31 storage_common.cpp
31 threadboundary.cpp 32 threadboundary.cpp
@@ -36,6 +37,7 @@ set(command_SRCS
36 domain/applicationdomaintype.cpp 37 domain/applicationdomaintype.cpp
37 domain/event.cpp 38 domain/event.cpp
38 domain/mail.cpp 39 domain/mail.cpp
40 domain/folder.cpp
39 ${storage_SRCS}) 41 ${storage_SRCS})
40 42
41add_library(${PROJECT_NAME} SHARED ${command_SRCS}) 43add_library(${PROJECT_NAME} SHARED ${command_SRCS})
@@ -55,6 +57,8 @@ generate_flatbuffers(
55 commands/revisionreplayed 57 commands/revisionreplayed
56 domain/event 58 domain/event
57 domain/mail 59 domain/mail
60 domain/folder
61 domain/dummy
58 entity 62 entity
59 metadata 63 metadata
60 queuedcommand 64 queuedcommand
@@ -70,12 +74,6 @@ install(FILES
70 clientapi.h 74 clientapi.h
71 domain/applicationdomaintype.h 75 domain/applicationdomaintype.h
72 query.h 76 query.h
73 threadboundary.h
74 resultprovider.h
75 facadefactory.h
76 log.h
77 listmodelresult.h
78 bufferadaptor.h 77 bufferadaptor.h
79 facadeinterface.h
80 DESTINATION ${INCLUDE_INSTALL_DIR}/${PROJECT_NAME} COMPONENT Devel 78 DESTINATION ${INCLUDE_INSTALL_DIR}/${PROJECT_NAME} COMPONENT Devel
81) 79)
diff --git a/common/clientapi.cpp b/common/clientapi.cpp
index f99ebb8..29b7e68 100644
--- a/common/clientapi.cpp
+++ b/common/clientapi.cpp
@@ -1,19 +1,47 @@
1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) version 3, or any
8 * later version accepted by the membership of KDE e.V. (or its
9 * successor approved by the membership of KDE e.V.), which shall
10 * act as a proxy defined in Section 6 of version 3 of the license.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
1 20
2#include "clientapi.h" 21#include "clientapi.h"
22
23#include <QtConcurrent/QtConcurrentRun>
24#include <QTimer>
25#include <QEventLoop>
26#include <QAbstractItemModel>
27#include <functional>
28#include <memory>
29
3#include "resourceaccess.h" 30#include "resourceaccess.h"
4#include "commands.h" 31#include "commands.h"
5#include "resourcefacade.h" 32#include "resourcefacade.h"
6#include "log.h" 33#include "log.h"
7#include "definitions.h" 34#include "definitions.h"
8#include "resourceconfig.h" 35#include "resourceconfig.h"
9#include <QtConcurrent/QtConcurrentRun> 36#include "facadefactory.h"
10#include <QTimer> 37#include "modelresult.h"
38#include "log.h"
11 39
12#define ASYNCINTHREAD 40#define ASYNCINTHREAD
13 41
14namespace async 42namespace async
15{ 43{
16 void run(const std::function<void()> &runner) { 44 static void run(const std::function<void()> &runner) {
17 auto timer = new QTimer(); 45 auto timer = new QTimer();
18 timer->setSingleShot(true); 46 timer->setSingleShot(true);
19 QObject::connect(timer, &QTimer::timeout, [runner, timer]() { 47 QObject::connect(timer, &QTimer::timeout, [runner, timer]() {
@@ -69,6 +97,76 @@ QList<QByteArray> Store::getResources(const QList<QByteArray> &resourceFilter, c
69 return resources; 97 return resources;
70} 98}
71 99
100template <class DomainType>
101QSharedPointer<QAbstractItemModel> Store::loadModel(Query query)
102{
103 auto model = QSharedPointer<ModelResult<DomainType, typename DomainType::Ptr> >::create(query, query.requestedProperties.toList());
104
105 //* Client defines lifetime of model
106 //* The model lifetime defines the duration of live-queries
107 //* The facade needs to life for the duration of any calls being made (assuming we get rid of any internal callbacks
108 //* The emitter needs to live or the duration of query (respectively, the model)
109 //* The result provider needs to live for as long as results are provided (until the last thread exits).
110
111 // Query all resources and aggregate results
112 KAsync::iterate(getResources(query.resources, ApplicationDomain::getTypeName<DomainType>()))
113 .template each<void, QByteArray>([query, model](const QByteArray &resource, KAsync::Future<void> &future) {
114 auto facade = FacadeFactory::instance().getFacade<DomainType>(resourceName(resource), resource);
115 if (facade) {
116 Trace() << "Trying to fetch from resource";
117 auto result = facade->load(query);
118 auto emitter = result.second;
119 //TODO use aggregating emitter instead
120 model->setEmitter(emitter);
121 model->fetchMore(QModelIndex());
122 result.first.template then<void>([&future](){future.setFinished();}).exec();
123 } else {
124 //Ignore the error and carry on
125 future.setFinished();
126 }
127 }).exec();
128
129 return model;
130}
131
132template <class DomainType>
133static std::shared_ptr<StoreFacade<DomainType> > getFacade(const QByteArray &resourceInstanceIdentifier)
134{
135 if (auto facade = FacadeFactory::instance().getFacade<DomainType>(resourceName(resourceInstanceIdentifier), resourceInstanceIdentifier)) {
136 return facade;
137 }
138 return std::make_shared<NullFacade<DomainType> >();
139}
140
141template <class DomainType>
142KAsync::Job<void> Store::create(const DomainType &domainObject) {
143 //Potentially move to separate thread as well
144 auto facade = getFacade<DomainType>(domainObject.resourceInstanceIdentifier());
145 return facade->create(domainObject).template then<void>([facade](){}, [](int errorCode, const QString &error) {
146 Warning() << "Failed to create";
147 });
148}
149
150template <class DomainType>
151KAsync::Job<void> Store::modify(const DomainType &domainObject)
152{
153 //Potentially move to separate thread as well
154 auto facade = getFacade<DomainType>(domainObject.resourceInstanceIdentifier());
155 return facade->modify(domainObject).template then<void>([facade](){}, [](int errorCode, const QString &error) {
156 Warning() << "Failed to modify";
157 });
158}
159
160template <class DomainType>
161KAsync::Job<void> Store::remove(const DomainType &domainObject)
162{
163 //Potentially move to separate thread as well
164 auto facade = getFacade<DomainType>(domainObject.resourceInstanceIdentifier());
165 return facade->remove(domainObject).template then<void>([facade](){}, [](int errorCode, const QString &error) {
166 Warning() << "Failed to remove";
167 });
168}
169
72KAsync::Job<void> Store::shutdown(const QByteArray &identifier) 170KAsync::Job<void> Store::shutdown(const QByteArray &identifier)
73{ 171{
74 Trace() << "shutdown"; 172 Trace() << "shutdown";
@@ -95,7 +193,7 @@ KAsync::Job<void> Store::synchronize(const Akonadi2::Query &query)
95 .template each<void, QByteArray>([query](const QByteArray &resource, KAsync::Future<void> &future) { 193 .template each<void, QByteArray>([query](const QByteArray &resource, KAsync::Future<void> &future) {
96 auto resourceAccess = QSharedPointer<Akonadi2::ResourceAccess>::create(resource); 194 auto resourceAccess = QSharedPointer<Akonadi2::ResourceAccess>::create(resource);
97 resourceAccess->open(); 195 resourceAccess->open();
98 resourceAccess->synchronizeResource(true, false).then<void>([&future]() { 196 resourceAccess->synchronizeResource(query.syncOnDemand, query.processAll).then<void>([&future, resourceAccess]() {
99 future.setFinished(); 197 future.setFinished();
100 }).exec(); 198 }).exec();
101 }) 199 })
@@ -103,4 +201,15 @@ KAsync::Job<void> Store::synchronize(const Akonadi2::Query &query)
103 .template then<void>([](){}); 201 .template then<void>([](){});
104} 202}
105 203
204#define REGISTER_TYPE(T) template KAsync::Job<void> Store::remove<T>(const T &domainObject); \
205 template KAsync::Job<void> Store::create<T>(const T &domainObject); \
206 template KAsync::Job<void> Store::modify<T>(const T &domainObject); \
207 template QSharedPointer<QAbstractItemModel> Store::loadModel<T>(Query query); \
208
209REGISTER_TYPE(ApplicationDomain::Event);
210REGISTER_TYPE(ApplicationDomain::Mail);
211REGISTER_TYPE(ApplicationDomain::Folder);
212REGISTER_TYPE(ApplicationDomain::AkonadiResource);
213
106} // namespace Akonadi2 214} // namespace Akonadi2
215
diff --git a/common/clientapi.h b/common/clientapi.h
index 9a32188..8f87562 100644
--- a/common/clientapi.h
+++ b/common/clientapi.h
@@ -22,28 +22,16 @@
22 22
23#include <QString> 23#include <QString>
24#include <QSharedPointer> 24#include <QSharedPointer>
25#include <QEventLoop>
26#include <functional>
27#include <memory>
28 25
29#include <Async/Async> 26#include <Async/Async>
30 27
31#include "query.h" 28#include "query.h"
32#include "resultprovider.h"
33#include "applicationdomaintype.h" 29#include "applicationdomaintype.h"
34#include "facadefactory.h"
35#include "log.h"
36 30
37namespace async { 31class QAbstractItemModel;
38 //This should abstract if we execute from eventloop or in thread.
39 //It supposed to allow the caller to finish the current method before executing the runner.
40 void run(const std::function<void()> &runner);
41}
42 32
43namespace Akonadi2 { 33namespace Akonadi2 {
44 34
45using namespace async;
46
47/** 35/**
48 * Store interface used in the client API. 36 * Store interface used in the client API.
49 */ 37 */
@@ -55,77 +43,22 @@ public:
55 static QString storageLocation(); 43 static QString storageLocation();
56 static QByteArray resourceName(const QByteArray &instanceIdentifier); 44 static QByteArray resourceName(const QByteArray &instanceIdentifier);
57 45
58 /** 46 enum Roles {
59 * Asynchronusly load a dataset 47 DomainObjectRole = Qt::UserRole + 1, //Must be the same as in ModelResult
60 */ 48 ChildrenFetchedRole
61 template <class DomainType> 49 };
62 static QSharedPointer<ResultEmitter<typename DomainType::Ptr> > load(Query query)
63 {
64 auto resultSet = QSharedPointer<ResultProvider<typename DomainType::Ptr> >::create();
65
66 //Execute the search in a thread.
67 //We must guarantee that the emitter is returned before the first result is emitted.
68 //The result provider must be threadsafe.
69 async::run([query, resultSet](){
70 QEventLoop eventLoop;
71 resultSet->onDone([&eventLoop](){
72 eventLoop.quit();
73 });
74 // Query all resources and aggregate results
75 KAsync::iterate(getResources(query.resources, ApplicationDomain::getTypeName<DomainType>()))
76 .template each<void, QByteArray>([query, resultSet](const QByteArray &resource, KAsync::Future<void> &future) {
77 auto facade = FacadeFactory::instance().getFacade<DomainType>(resourceName(resource), resource);
78 if (facade) {
79 facade->load(query, resultSet).template then<void>([&future](){future.setFinished();}).exec();
80 //Keep the facade alive for the lifetime of the resultSet.
81 resultSet->setFacade(facade);
82 } else {
83 //Ignore the error and carry on
84 future.setFinished();
85 }
86 }).template then<void>([query, resultSet]() {
87 resultSet->initialResultSetComplete();
88 if (!query.liveQuery) {
89 resultSet->complete();
90 }
91 }).exec();
92
93 //Keep the thread alive until the result is ready
94 if (!resultSet->isDone()) {
95 eventLoop.exec();
96 }
97 });
98 return resultSet->emitter();
99 }
100 50
101 /** 51 /**
102 * Asynchronusly load a dataset with tree structure information 52 * Asynchronusly load a dataset with tree structure information
103 */ 53 */
104 // template <class DomainType>
105 // static TreeSet<DomainType> loadTree(Query)
106 // {
107
108 // }
109 template <class DomainType> 54 template <class DomainType>
110 static std::shared_ptr<StoreFacade<DomainType> > getFacade(const QByteArray &resourceInstanceIdentifier) 55 static QSharedPointer<QAbstractItemModel> loadModel(Query query);
111 {
112 if (auto facade = FacadeFactory::instance().getFacade<DomainType>(resourceName(resourceInstanceIdentifier), resourceInstanceIdentifier)) {
113 return facade;
114 }
115 return std::make_shared<NullFacade<DomainType> >();
116 }
117 56
118 /** 57 /**
119 * Create a new entity. 58 * Create a new entity.
120 */ 59 */
121 template <class DomainType> 60 template <class DomainType>
122 static KAsync::Job<void> create(const DomainType &domainObject) { 61 static KAsync::Job<void> create(const DomainType &domainObject);
123 //Potentially move to separate thread as well
124 auto facade = getFacade<DomainType>(domainObject.resourceInstanceIdentifier());
125 return facade->create(domainObject).template then<void>([facade](){}, [](int errorCode, const QString &error) {
126 Warning() << "Failed to create";
127 });
128 }
129 62
130 /** 63 /**
131 * Modify an entity. 64 * Modify an entity.
@@ -133,25 +66,13 @@ public:
133 * This includes moving etc. since these are also simple settings on a property. 66 * This includes moving etc. since these are also simple settings on a property.
134 */ 67 */
135 template <class DomainType> 68 template <class DomainType>
136 static KAsync::Job<void> modify(const DomainType &domainObject) { 69 static KAsync::Job<void> modify(const DomainType &domainObject);
137 //Potentially move to separate thread as well
138 auto facade = getFacade<DomainType>(domainObject.resourceInstanceIdentifier());
139 return facade->modify(domainObject).template then<void>([facade](){}, [](int errorCode, const QString &error) {
140 Warning() << "Failed to modify";
141 });
142 }
143 70
144 /** 71 /**
145 * Remove an entity. 72 * Remove an entity.
146 */ 73 */
147 template <class DomainType> 74 template <class DomainType>
148 static KAsync::Job<void> remove(const DomainType &domainObject) { 75 static KAsync::Job<void> remove(const DomainType &domainObject);
149 //Potentially move to separate thread as well
150 auto facade = getFacade<DomainType>(domainObject.resourceInstanceIdentifier());
151 return facade->remove(domainObject).template then<void>([facade](){}, [](int errorCode, const QString &error) {
152 Warning() << "Failed to remove";
153 });
154 }
155 76
156 /** 77 /**
157 * Shutdown resource. 78 * Shutdown resource.
diff --git a/common/domain/applicationdomaintype.cpp b/common/domain/applicationdomaintype.cpp
index 1b5d870..c9a8bba 100644
--- a/common/domain/applicationdomaintype.cpp
+++ b/common/domain/applicationdomaintype.cpp
@@ -60,10 +60,13 @@ ApplicationDomainType& ApplicationDomainType::operator=(const ApplicationDomainT
60 return *this; 60 return *this;
61} 61}
62 62
63ApplicationDomainType::~ApplicationDomainType() {} 63ApplicationDomainType::~ApplicationDomainType()
64{
65}
64 66
65QVariant ApplicationDomainType::getProperty(const QByteArray &key) const 67QVariant ApplicationDomainType::getProperty(const QByteArray &key) const
66{ 68{
69 Q_ASSERT(mAdaptor);
67 if (!mAdaptor->availableProperties().contains(key)) { 70 if (!mAdaptor->availableProperties().contains(key)) {
68 Warning() << "No such property available " << key; 71 Warning() << "No such property available " << key;
69 } 72 }
@@ -72,7 +75,9 @@ QVariant ApplicationDomainType::getProperty(const QByteArray &key) const
72 75
73void ApplicationDomainType::setProperty(const QByteArray &key, const QVariant &value) 76void ApplicationDomainType::setProperty(const QByteArray &key, const QVariant &value)
74{ 77{
75 mChangeSet.insert(key, value); mAdaptor->setProperty(key, value); 78 Q_ASSERT(mAdaptor);
79 mChangeSet.insert(key, value);
80 mAdaptor->setProperty(key, value);
76} 81}
77 82
78QByteArrayList ApplicationDomainType::changedProperties() const 83QByteArrayList ApplicationDomainType::changedProperties() const
diff --git a/common/domain/applicationdomaintype.h b/common/domain/applicationdomaintype.h
index 5514d26..227ab4d 100644
--- a/common/domain/applicationdomaintype.h
+++ b/common/domain/applicationdomaintype.h
@@ -160,3 +160,7 @@ Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Event)
160Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Event::Ptr) 160Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Event::Ptr)
161Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Mail) 161Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Mail)
162Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Mail::Ptr) 162Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Mail::Ptr)
163Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Folder)
164Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::Folder::Ptr)
165Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::AkonadiResource)
166Q_DECLARE_METATYPE(Akonadi2::ApplicationDomain::AkonadiResource::Ptr)
diff --git a/common/domain/dummy.fbs b/common/domain/dummy.fbs
new file mode 100644
index 0000000..8816b09
--- /dev/null
+++ b/common/domain/dummy.fbs
@@ -0,0 +1,7 @@
1namespace Akonadi2.ApplicationDomain.Buffer;
2
3table Dummy {
4}
5
6root_type Dummy;
7file_identifier "AKFB";
diff --git a/common/domain/event.cpp b/common/domain/event.cpp
index 87e13bc..42c13e2 100644
--- a/common/domain/event.cpp
+++ b/common/domain/event.cpp
@@ -47,6 +47,7 @@ ResultSet TypeImplementation<Event>::queryIndexes(const Akonadi2::Query &query,
47 }); 47 });
48 appliedFilters << "uid"; 48 appliedFilters << "uid";
49 } 49 }
50 Trace() << "Index lookup found " << keys.size() << " keys.";
50 return ResultSet(keys); 51 return ResultSet(keys);
51} 52}
52 53
diff --git a/common/domain/folder.cpp b/common/domain/folder.cpp
new file mode 100644
index 0000000..989d2c4
--- /dev/null
+++ b/common/domain/folder.cpp
@@ -0,0 +1,100 @@
1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastfolder.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19#include "folder.h"
20
21#include <QVector>
22#include <QByteArray>
23#include <QString>
24
25#include "../resultset.h"
26#include "../index.h"
27#include "../storage.h"
28#include "../log.h"
29#include "../propertymapper.h"
30#include "../query.h"
31#include "../definitions.h"
32
33#include "folder_generated.h"
34
35using namespace Akonadi2::ApplicationDomain;
36
37ResultSet TypeImplementation<Folder>::queryIndexes(const Akonadi2::Query &query, const QByteArray &resourceInstanceIdentifier, QSet<QByteArray> &appliedFilters, Akonadi2::Storage::Transaction &transaction)
38{
39 QVector<QByteArray> keys;
40 if (query.propertyFilter.contains("parent")) {
41 Index index("folder.index.parent", transaction);
42 auto lookupKey = query.propertyFilter.value("parent").toByteArray();
43 if (lookupKey.isEmpty()) {
44 lookupKey = "toplevel";
45 }
46 index.lookup(lookupKey, [&](const QByteArray &value) {
47 keys << value;
48 },
49 [](const Index::Error &error) {
50 Warning() << "Error in uid index: " << error.message;
51 });
52 appliedFilters << "parent";
53 }
54 Trace() << "Index lookup found " << keys.size() << " keys.";
55 return ResultSet(keys);
56}
57
58void TypeImplementation<Folder>::index(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Akonadi2::Storage::Transaction &transaction)
59{
60 const auto parent = bufferAdaptor.getProperty("parent");
61 Trace() << "indexing " << identifier << " with parent " << parent.toByteArray();
62 if (parent.isValid()) {
63 Q_ASSERT(!parent.toByteArray().isEmpty());
64 Index("folder.index.parent", transaction).add(parent.toByteArray(), identifier);
65 } else {
66 Index("folder.index.parent", transaction).add("toplevel", identifier);
67 }
68}
69
70void TypeImplementation<Folder>::removeIndex(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Akonadi2::Storage::Transaction &transaction)
71{
72 const auto parent = bufferAdaptor.getProperty("parent");
73 if (parent.isValid()) {
74 Index("folder.index.parent", transaction).remove(parent.toByteArray(), identifier);
75 } else {
76 Index("folder.index.parent", transaction).remove("toplevel", identifier);
77 }
78}
79
80QSharedPointer<ReadPropertyMapper<TypeImplementation<Folder>::Buffer> > TypeImplementation<Folder>::initializeReadPropertyMapper()
81{
82 auto propertyMapper = QSharedPointer<ReadPropertyMapper<Buffer> >::create();
83 propertyMapper->addMapping<QString, Buffer>("parent", &Buffer::parent);
84 propertyMapper->addMapping<QString, Buffer>("name", &Buffer::name);
85 return propertyMapper;
86}
87
88QSharedPointer<WritePropertyMapper<TypeImplementation<Folder>::BufferBuilder> > TypeImplementation<Folder>::initializeWritePropertyMapper()
89{
90 auto propertyMapper = QSharedPointer<WritePropertyMapper<BufferBuilder> >::create();
91 propertyMapper->addMapping("parent", [](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function<void(BufferBuilder &)> {
92 auto offset = variantToProperty<QString>(value, fbb);
93 return [offset](BufferBuilder &builder) { builder.add_parent(offset); };
94 });
95 propertyMapper->addMapping("name", [](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function<void(BufferBuilder &)> {
96 auto offset = variantToProperty<QString>(value, fbb);
97 return [offset](BufferBuilder &builder) { builder.add_name(offset); };
98 });
99 return propertyMapper;
100}
diff --git a/common/domain/folder.fbs b/common/domain/folder.fbs
new file mode 100644
index 0000000..3476d58
--- /dev/null
+++ b/common/domain/folder.fbs
@@ -0,0 +1,9 @@
1namespace Akonadi2.ApplicationDomain.Buffer;
2
3table Folder {
4 name:string;
5 parent:string;
6}
7
8root_type Folder;
9file_identifier "AKFB";
diff --git a/common/domain/folder.h b/common/domain/folder.h
new file mode 100644
index 0000000..545836f
--- /dev/null
+++ b/common/domain/folder.h
@@ -0,0 +1,56 @@
1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19#pragma once
20
21#include "applicationdomaintype.h"
22
23#include "storage.h"
24
25class ResultSet;
26class QByteArray;
27
28template<typename T>
29class ReadPropertyMapper;
30template<typename T>
31class WritePropertyMapper;
32
33namespace Akonadi2 {
34 class Query;
35
36namespace ApplicationDomain {
37 namespace Buffer {
38 struct Folder;
39 struct FolderBuilder;
40 }
41
42template<>
43class TypeImplementation<Akonadi2::ApplicationDomain::Folder> {
44public:
45 typedef Akonadi2::ApplicationDomain::Buffer::Folder Buffer;
46 typedef Akonadi2::ApplicationDomain::Buffer::FolderBuilder BufferBuilder;
47 static QSet<QByteArray> indexedProperties();
48 static ResultSet queryIndexes(const Akonadi2::Query &query, const QByteArray &resourceInstanceIdentifier, QSet<QByteArray> &appliedFilters, Akonadi2::Storage::Transaction &transaction);
49 static void index(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Akonadi2::Storage::Transaction &transaction);
50 static void removeIndex(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Akonadi2::Storage::Transaction &transaction);
51 static QSharedPointer<ReadPropertyMapper<Buffer> > initializeReadPropertyMapper();
52 static QSharedPointer<WritePropertyMapper<BufferBuilder> > initializeWritePropertyMapper();
53};
54
55}
56}
diff --git a/common/domainadaptor.h b/common/domainadaptor.h
index b14fbcd..b541e23 100644
--- a/common/domainadaptor.h
+++ b/common/domainadaptor.h
@@ -1,5 +1,5 @@
1/* 1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm> 2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
3 * 3 *
4 * This program is free software; you can redistribute it and/or modify 4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by 5 * it under the terms of the GNU General Public License as published by
@@ -23,9 +23,11 @@
23#include <QByteArray> 23#include <QByteArray>
24#include <functional> 24#include <functional>
25 25
26#include "domaintypeadaptorfactoryinterface.h"
26#include "domain/applicationdomaintype.h" 27#include "domain/applicationdomaintype.h"
27#include "domain/event.h" 28#include "domain/event.h"
28#include "domain/mail.h" 29#include "domain/mail.h"
30#include "domain/folder.h"
29#include "bufferadaptor.h" 31#include "bufferadaptor.h"
30#include "entity_generated.h" 32#include "entity_generated.h"
31#include "metadata_generated.h" 33#include "metadata_generated.h"
@@ -123,15 +125,6 @@ public:
123 QSharedPointer<ReadPropertyMapper<ResourceBuffer> > mResourceMapper; 125 QSharedPointer<ReadPropertyMapper<ResourceBuffer> > mResourceMapper;
124}; 126};
125 127
126class DomainTypeAdaptorFactoryInterface
127{
128public:
129 typedef QSharedPointer<DomainTypeAdaptorFactoryInterface> Ptr;
130 virtual ~DomainTypeAdaptorFactoryInterface() {};
131 virtual QSharedPointer<Akonadi2::ApplicationDomain::BufferAdaptor> createAdaptor(const Akonadi2::Entity &entity) = 0;
132 virtual void createBuffer(const Akonadi2::ApplicationDomain::ApplicationDomainType &domainType, flatbuffers::FlatBufferBuilder &fbb, void const *metadataData = 0, size_t metadataSize = 0) = 0;
133};
134
135/** 128/**
136 * The factory should define how to go from an entitybuffer (local + resource buffer), to a domain type adapter. 129 * The factory should define how to go from an entitybuffer (local + resource buffer), to a domain type adapter.
137 * It defines how values are split accross local and resource buffer. 130 * It defines how values are split accross local and resource buffer.
diff --git a/common/domaintypeadaptorfactoryinterface.h b/common/domaintypeadaptorfactoryinterface.h
new file mode 100644
index 0000000..8c99aa1
--- /dev/null
+++ b/common/domaintypeadaptorfactoryinterface.h
@@ -0,0 +1,42 @@
1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19#pragma once
20
21#include <QSharedPointer>
22
23namespace Akonadi2 {
24 namespace ApplicationDomain {
25 class BufferAdaptor;
26 class ApplicationDomainType;
27 }
28 struct Entity;
29}
30
31namespace flatbuffers {
32 class FlatBufferBuilder;
33}
34
35class DomainTypeAdaptorFactoryInterface
36{
37public:
38 typedef QSharedPointer<DomainTypeAdaptorFactoryInterface> Ptr;
39 virtual ~DomainTypeAdaptorFactoryInterface() {};
40 virtual QSharedPointer<Akonadi2::ApplicationDomain::BufferAdaptor> createAdaptor(const Akonadi2::Entity &entity) = 0;
41 virtual void createBuffer(const Akonadi2::ApplicationDomain::ApplicationDomainType &domainType, flatbuffers::FlatBufferBuilder &fbb, void const *metadataData = 0, size_t metadataSize = 0) = 0;
42};
diff --git a/common/entitystorage.cpp b/common/entitystorage.cpp
deleted file mode 100644
index 5d4df9f..0000000
--- a/common/entitystorage.cpp
+++ /dev/null
@@ -1,74 +0,0 @@
1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "entitystorage.h"
21
22ResultSet EntityStorageBase::filteredSet(const ResultSet &resultSet, const std::function<bool(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject)> &filter, const Akonadi2::Storage::Transaction &transaction, bool initialQuery)
23{
24 auto resultSetPtr = QSharedPointer<ResultSet>::create(resultSet);
25
26 //Read through the source values and return whatever matches the filter
27 std::function<bool(std::function<void(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &, Akonadi2::Operation)>)> generator = [this, resultSetPtr, &transaction, filter, initialQuery](std::function<void(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &, Akonadi2::Operation)> callback) -> bool {
28 while (resultSetPtr->next()) {
29 readEntity(transaction, resultSetPtr->id(), [this, filter, callback, initialQuery](const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject, Akonadi2::Operation operation) {
30 //Always remove removals, they probably don't match due to non-available properties
31 if (filter(domainObject) || operation == Akonadi2::Operation_Removal) {
32 if (initialQuery) {
33 //We're not interested in removals during the initial query
34 if (operation != Akonadi2::Operation_Removal) {
35 callback(domainObject, Akonadi2::Operation_Creation);
36 }
37 } else {
38 callback(domainObject, operation);
39 }
40 }
41 });
42 }
43 return false;
44 };
45 return ResultSet(generator);
46}
47
48
49ResultSet EntityStorageBase::getResultSet(const Akonadi2::Query &query, Akonadi2::Storage::Transaction &transaction, qint64 baseRevision)
50{
51 QSet<QByteArray> remainingFilters = query.propertyFilter.keys().toSet();
52 ResultSet resultSet;
53 const bool initialQuery = (baseRevision == 1);
54 if (initialQuery) {
55 Trace() << "Initial result set update";
56 resultSet = loadInitialResultSet(query, transaction, remainingFilters);
57 } else {
58 //TODO fallback in case the old revision is no longer available to clear + redo complete initial scan
59 Trace() << "Incremental result set update" << baseRevision;
60 resultSet = loadIncrementalResultSet(baseRevision, query, transaction, remainingFilters);
61 }
62
63 auto filter = [remainingFilters, query, baseRevision](const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject) -> bool {
64 for (const auto &filterProperty : remainingFilters) {
65 //TODO implement other comparison operators than equality
66 if (domainObject->getProperty(filterProperty) != query.propertyFilter.value(filterProperty)) {
67 return false;
68 }
69 }
70 return true;
71 };
72
73 return filteredSet(resultSet, filter, transaction, initialQuery);
74}
diff --git a/common/entitystorage.h b/common/entitystorage.h
deleted file mode 100644
index 8e73083..0000000
--- a/common/entitystorage.h
+++ /dev/null
@@ -1,126 +0,0 @@
1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19#pragma once
20
21#include <QByteArray>
22
23#include "query.h"
24#include "domainadaptor.h"
25#include "entitybuffer.h"
26#include "log.h"
27#include "storage.h"
28#include "resultset.h"
29#include "resultprovider.h"
30#include "definitions.h"
31
32/**
33 * Wraps storage, entity adaptor factory and indexes into one.
34 *
35 */
36class EntityStorageBase
37{
38public:
39 typedef std::function<ResultSet (const Akonadi2::Query &query, Akonadi2::Storage::Transaction &transaction, QSet<QByteArray> &remainingFilters)> InitialResultLoader;
40 typedef std::function<ResultSet (qint64 baseRevision, const Akonadi2::Query &query, Akonadi2::Storage::Transaction &transaction, QSet<QByteArray> &remainingFilters)> IncrementalResultLoader;
41 typedef std::function<void(const Akonadi2::Storage::Transaction &transaction, const QByteArray &key, const std::function<void(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &, Akonadi2::Operation)> &resultCallback)> EntityReader;
42
43 /**
44 * Returns the initial result set that still needs to be filtered.
45 *
46 * To make this efficient indexes should be chosen that are as selective as possible.
47 */
48 InitialResultLoader loadInitialResultSet;
49 /**
50 * Returns the incremental result set that still needs to be filtered.
51 */
52 IncrementalResultLoader loadIncrementalResultSet;
53
54 /**
55 * Loads a single entity by uid from storage.
56 */
57 EntityReader readEntity;
58
59protected:
60 EntityStorageBase(const QByteArray &instanceIdentifier)
61 : mResourceInstanceIdentifier(instanceIdentifier)
62 {
63
64 }
65
66 virtual Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr copy(const Akonadi2::ApplicationDomain::ApplicationDomainType &) = 0;
67
68 ResultSet getResultSet(const Akonadi2::Query &query, Akonadi2::Storage::Transaction &transaction, qint64 baseRevision);
69
70 QByteArray mResourceInstanceIdentifier;
71
72private:
73 ResultSet filteredSet(const ResultSet &resultSet, const std::function<bool(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject)> &filter, const Akonadi2::Storage::Transaction &transaction, bool isInitialQuery);
74};
75
76template<typename DomainType>
77class EntityStorage : public EntityStorageBase
78{
79
80public:
81
82 EntityStorage(const QByteArray &instanceIdentifier)
83 : EntityStorageBase(instanceIdentifier)
84 {
85 }
86
87protected:
88 Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr copy(const Akonadi2::ApplicationDomain::ApplicationDomainType &object) Q_DECL_OVERRIDE
89 {
90 return Akonadi2::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<DomainType>(object);
91 }
92
93public:
94
95 virtual qint64 read(const Akonadi2::Query &query, qint64 baseRevision, const QSharedPointer<Akonadi2::ResultProvider<typename DomainType::Ptr> > &resultProvider)
96 {
97 Akonadi2::Storage storage(Akonadi2::storageLocation(), mResourceInstanceIdentifier);
98 storage.setDefaultErrorHandler([](const Akonadi2::Storage::Error &error) {
99 Warning() << "Error during query: " << error.store << error.message;
100 });
101
102 auto transaction = storage.createTransaction(Akonadi2::Storage::ReadOnly);
103
104 Log() << "Querying" << baseRevision << Akonadi2::Storage::maxRevision(transaction);
105 auto resultSet = getResultSet(query, transaction, baseRevision);
106 while(resultSet.next([this, resultProvider](const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &value, Akonadi2::Operation operation) -> bool {
107 switch (operation) {
108 case Akonadi2::Operation_Creation:
109 Trace() << "Got creation";
110 resultProvider->add(copy(*value).template staticCast<DomainType>());
111 break;
112 case Akonadi2::Operation_Modification:
113 Trace() << "Got modification";
114 resultProvider->modify(copy(*value).template staticCast<DomainType>());
115 break;
116 case Akonadi2::Operation_Removal:
117 Trace() << "Got removal";
118 resultProvider->remove(copy(*value).template staticCast<DomainType>());
119 break;
120 }
121 return true;
122 })){};
123 return Akonadi2::Storage::maxRevision(transaction);
124 }
125
126};
diff --git a/common/facade.cpp b/common/facade.cpp
index e51b32a..1d6b9a7 100644
--- a/common/facade.cpp
+++ b/common/facade.cpp
@@ -1,5 +1,5 @@
1/* 1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm> 2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm>
3 * 3 *
4 * This program is free software; you can redistribute it and/or modify 4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by 5 * it under the terms of the GNU General Public License as published by
@@ -18,3 +18,82 @@
18 */ 18 */
19 19
20#include "facade.h" 20#include "facade.h"
21
22#include "commands.h"
23#include "log.h"
24#include "storage.h"
25#include "definitions.h"
26#include "domainadaptor.h"
27#include "queryrunner.h"
28
29using namespace Akonadi2;
30
31
32template<class DomainType>
33GenericFacade<DomainType>::GenericFacade(const QByteArray &resourceIdentifier, const DomainTypeAdaptorFactoryInterface::Ptr &adaptorFactory , const QSharedPointer<Akonadi2::ResourceAccessInterface> resourceAccess)
34 : Akonadi2::StoreFacade<DomainType>(),
35 mResourceAccess(resourceAccess),
36 mDomainTypeAdaptorFactory(adaptorFactory),
37 mResourceInstanceIdentifier(resourceIdentifier)
38{
39 if (!mResourceAccess) {
40 mResourceAccess = QSharedPointer<Akonadi2::ResourceAccess>::create(resourceIdentifier);
41 }
42}
43
44template<class DomainType>
45GenericFacade<DomainType>::~GenericFacade()
46{
47}
48
49template<class DomainType>
50QByteArray GenericFacade<DomainType>::bufferTypeForDomainType()
51{
52 //We happen to have a one to one mapping
53 return Akonadi2::ApplicationDomain::getTypeName<DomainType>();
54}
55
56template<class DomainType>
57KAsync::Job<void> GenericFacade<DomainType>::create(const DomainType &domainObject)
58{
59 if (!mDomainTypeAdaptorFactory) {
60 Warning() << "No domain type adaptor factory available";
61 return KAsync::error<void>();
62 }
63 flatbuffers::FlatBufferBuilder entityFbb;
64 mDomainTypeAdaptorFactory->createBuffer(domainObject, entityFbb);
65 return mResourceAccess->sendCreateCommand(bufferTypeForDomainType(), QByteArray::fromRawData(reinterpret_cast<const char*>(entityFbb.GetBufferPointer()), entityFbb.GetSize()));
66}
67
68template<class DomainType>
69KAsync::Job<void> GenericFacade<DomainType>::modify(const DomainType &domainObject)
70{
71 if (!mDomainTypeAdaptorFactory) {
72 Warning() << "No domain type adaptor factory available";
73 return KAsync::error<void>();
74 }
75 flatbuffers::FlatBufferBuilder entityFbb;
76 mDomainTypeAdaptorFactory->createBuffer(domainObject, entityFbb);
77 return mResourceAccess->sendModifyCommand(domainObject.identifier(), domainObject.revision(), bufferTypeForDomainType(), QByteArrayList(), QByteArray::fromRawData(reinterpret_cast<const char*>(entityFbb.GetBufferPointer()), entityFbb.GetSize()));
78}
79
80template<class DomainType>
81KAsync::Job<void> GenericFacade<DomainType>::remove(const DomainType &domainObject)
82{
83 return mResourceAccess->sendDeleteCommand(domainObject.identifier(), domainObject.revision(), bufferTypeForDomainType());
84}
85
86template<class DomainType>
87QPair<KAsync::Job<void>, typename ResultEmitter<typename DomainType::Ptr>::Ptr> GenericFacade<DomainType>::load(const Akonadi2::Query &query)
88{
89 //The runner lives for the lifetime of the query
90 auto runner = new QueryRunner<DomainType>(query, mResourceAccess, mResourceInstanceIdentifier, mDomainTypeAdaptorFactory, bufferTypeForDomainType());
91 return qMakePair(KAsync::null<void>(), runner->emitter());
92}
93
94
95template class Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Folder>;
96template class Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Mail>;
97template class Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Event>;
98
99#include "facade.moc"
diff --git a/common/facade.h b/common/facade.h
index 643ebec..de67e05 100644
--- a/common/facade.h
+++ b/common/facade.h
@@ -1,5 +1,5 @@
1/* 1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm> 2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
3 * 3 *
4 * This program is free software; you can redistribute it and/or modify 4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by 5 * it under the terms of the GNU General Public License as published by
@@ -25,85 +25,12 @@
25#include <Async/Async> 25#include <Async/Async>
26 26
27#include "resourceaccess.h" 27#include "resourceaccess.h"
28#include "commands.h"
29#include "domainadaptor.h"
30#include "log.h"
31#include "resultset.h" 28#include "resultset.h"
32#include "entitystorage.h" 29#include "domaintypeadaptorfactoryinterface.h"
33 30#include "storage.h"
34/**
35 * A QueryRunner runs a query and updates the corresponding result set.
36 *
37 * The lifetime of the QueryRunner is defined by the resut set (otherwise it's doing useless work),
38 * and by how long a result set must be updated. If the query is one off the runner dies after the execution,
39 * otherwise it lives on the react to changes and updates the corresponding result set.
40 *
41 * QueryRunner has to keep ResourceAccess alive in order to keep getting updates.
42 */
43class QueryRunner : public QObject
44{
45 Q_OBJECT
46public:
47 typedef std::function<KAsync::Job<qint64>(qint64 oldRevision)> QueryFunction;
48
49 QueryRunner(const Akonadi2::Query &query) : mLatestRevision(0) {};
50 /**
51 * Starts query
52 */
53 KAsync::Job<void> run(qint64 newRevision = 0)
54 {
55 //TODO: JOBAPI: that last empty .then should not be necessary
56 //TODO: remove newRevision
57 return queryFunction(mLatestRevision + 1).then<void, qint64>([this](qint64 revision) {
58 mLatestRevision = revision;
59 }).then<void>([](){});
60 }
61
62 /**
63 * Set the query to run
64 */
65 void setQuery(const QueryFunction &query)
66 {
67 queryFunction = query;
68 }
69
70public slots:
71 /**
72 * Rerun query with new revision
73 */
74 void revisionChanged(qint64 newRevision)
75 {
76 Trace() << "New revision: " << newRevision;
77 run(newRevision).exec();
78 }
79
80private:
81 QueryFunction queryFunction;
82 qint64 mLatestRevision;
83};
84
85static inline ResultSet fullScan(const Akonadi2::Storage::Transaction &transaction, const QByteArray &bufferType)
86{
87 //TODO use a result set with an iterator, to read values on demand
88 QVector<QByteArray> keys;
89 transaction.openDatabase(bufferType + ".main").scan(QByteArray(), [&](const QByteArray &key, const QByteArray &value) -> bool {
90 //Skip internals
91 if (Akonadi2::Storage::isInternalKey(key)) {
92 return true;
93 }
94 keys << Akonadi2::Storage::uidFromKey(key);
95 return true;
96 },
97 [](const Akonadi2::Storage::Error &error) {
98 qWarning() << "Error during query: " << error.message;
99 });
100
101 Trace() << "Full scan found " << keys.size() << " results";
102 return ResultSet(keys);
103}
104
105 31
106namespace Akonadi2 { 32namespace Akonadi2 {
33
107/** 34/**
108 * Default facade implementation for resources that are implemented in a separate process using the ResourceAccess class. 35 * Default facade implementation for resources that are implemented in a separate process using the ResourceAccess class.
109 * 36 *
@@ -125,185 +52,18 @@ public:
125 * @param resourceIdentifier is the identifier of the resource instance 52 * @param resourceIdentifier is the identifier of the resource instance
126 * @param adaptorFactory is the adaptor factory used to generate the mappings from domain to resource types and vice versa 53 * @param adaptorFactory is the adaptor factory used to generate the mappings from domain to resource types and vice versa
127 */ 54 */
128 GenericFacade(const QByteArray &resourceIdentifier, const DomainTypeAdaptorFactoryInterface::Ptr &adaptorFactory = DomainTypeAdaptorFactoryInterface::Ptr(), const QSharedPointer<EntityStorage<DomainType> > storage = QSharedPointer<EntityStorage<DomainType> >(), const QSharedPointer<Akonadi2::ResourceAccessInterface> resourceAccess = QSharedPointer<Akonadi2::ResourceAccessInterface>()) 55 GenericFacade(const QByteArray &resourceIdentifier, const DomainTypeAdaptorFactoryInterface::Ptr &adaptorFactory = DomainTypeAdaptorFactoryInterface::Ptr(), const QSharedPointer<Akonadi2::ResourceAccessInterface> resourceAccess = QSharedPointer<Akonadi2::ResourceAccessInterface>());
129 : Akonadi2::StoreFacade<DomainType>(), 56 ~GenericFacade();
130 mResourceAccess(resourceAccess),
131 mStorage(storage),
132 mDomainTypeAdaptorFactory(adaptorFactory),
133 mResourceInstanceIdentifier(resourceIdentifier)
134 {
135 if (!mResourceAccess) {
136 mResourceAccess = QSharedPointer<Akonadi2::ResourceAccess>::create(resourceIdentifier);
137 }
138 if (!mStorage) {
139 mStorage = QSharedPointer<EntityStorage<DomainType> >::create(resourceIdentifier);
140 const auto bufferType = bufferTypeForDomainType();
141
142 mStorage->readEntity = [bufferType, this] (const Akonadi2::Storage::Transaction &transaction, const QByteArray &key, const std::function<void(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &, Akonadi2::Operation)> &resultCallback)
143 {
144 //This only works for a 1:1 mapping of resource to domain types.
145 //Not i.e. for tags that are stored as flags in each entity of an imap store.
146 //additional properties that don't have a 1:1 mapping (such as separately stored tags),
147 //could be added to the adaptor.
148 transaction.openDatabase(bufferType + ".main").findLatest(key, [=](const QByteArray &key, const QByteArray &value) -> bool {
149 Akonadi2::EntityBuffer buffer(value.data(), value.size());
150 const Akonadi2::Entity &entity = buffer.entity();
151 const auto metadataBuffer = Akonadi2::EntityBuffer::readBuffer<Akonadi2::Metadata>(entity.metadata());
152 Q_ASSERT(metadataBuffer);
153 const qint64 revision = metadataBuffer ? metadataBuffer->revision() : -1;
154 resultCallback(DomainType::Ptr::create(mResourceInstanceIdentifier, Akonadi2::Storage::uidFromKey(key), revision, mDomainTypeAdaptorFactory->createAdaptor(entity)), metadataBuffer->operation());
155 return false;
156 },
157 [](const Akonadi2::Storage::Error &error) {
158 qWarning() << "Error during query: " << error.message;
159 });
160 };
161
162 mStorage->loadInitialResultSet = [bufferType, this] (const Akonadi2::Query &query, Akonadi2::Storage::Transaction &transaction, QSet<QByteArray> &remainingFilters) -> ResultSet
163 {
164 QSet<QByteArray> appliedFilters;
165 auto resultSet = Akonadi2::ApplicationDomain::TypeImplementation<DomainType>::queryIndexes(query, mResourceInstanceIdentifier, appliedFilters, transaction);
166 remainingFilters = query.propertyFilter.keys().toSet() - appliedFilters;
167
168 //We do a full scan if there were no indexes available to create the initial set.
169 if (appliedFilters.isEmpty()) {
170 //TODO this should be replaced by an index lookup as well
171 return fullScan(transaction, bufferType);
172 }
173 return resultSet;
174 };
175
176 mStorage->loadIncrementalResultSet = [bufferType, this] (qint64 baseRevision, const Akonadi2::Query &query, Akonadi2::Storage::Transaction &transaction, QSet<QByteArray> &remainingFilters) -> ResultSet
177 {
178 auto revisionCounter = QSharedPointer<qint64>::create(baseRevision);
179 return ResultSet([bufferType, revisionCounter, &transaction, this]() -> QByteArray {
180 const qint64 topRevision = Akonadi2::Storage::maxRevision(transaction);
181 //Spit out the revision keys one by one.
182 while (*revisionCounter <= topRevision) {
183 const auto uid = Akonadi2::Storage::getUidFromRevision(transaction, *revisionCounter);
184 const auto type = Akonadi2::Storage::getTypeFromRevision(transaction, *revisionCounter);
185 Trace() << "Revision" << *revisionCounter << type << uid;
186 if (type != bufferType) {
187 //Skip revision
188 *revisionCounter += 1;
189 continue;
190 }
191 const auto key = Akonadi2::Storage::assembleKey(uid, *revisionCounter);
192 *revisionCounter += 1;
193 return key;
194 }
195 //We're done
196 return QByteArray();
197 });
198 };
199 }
200 }
201
202 ~GenericFacade()
203 {
204 }
205
206 static QByteArray bufferTypeForDomainType()
207 {
208 //We happen to have a one to one mapping
209 return Akonadi2::ApplicationDomain::getTypeName<DomainType>();
210 }
211
212 KAsync::Job<void> create(const DomainType &domainObject) Q_DECL_OVERRIDE
213 {
214 if (!mDomainTypeAdaptorFactory) {
215 Warning() << "No domain type adaptor factory available";
216 return KAsync::error<void>();
217 }
218 flatbuffers::FlatBufferBuilder entityFbb;
219 mDomainTypeAdaptorFactory->createBuffer(domainObject, entityFbb);
220 return mResourceAccess->sendCreateCommand(bufferTypeForDomainType(), QByteArray::fromRawData(reinterpret_cast<const char*>(entityFbb.GetBufferPointer()), entityFbb.GetSize()));
221 }
222
223 KAsync::Job<void> modify(const DomainType &domainObject) Q_DECL_OVERRIDE
224 {
225 if (!mDomainTypeAdaptorFactory) {
226 Warning() << "No domain type adaptor factory available";
227 return KAsync::error<void>();
228 }
229 flatbuffers::FlatBufferBuilder entityFbb;
230 mDomainTypeAdaptorFactory->createBuffer(domainObject, entityFbb);
231 return mResourceAccess->sendModifyCommand(domainObject.identifier(), domainObject.revision(), bufferTypeForDomainType(), QByteArrayList(), QByteArray::fromRawData(reinterpret_cast<const char*>(entityFbb.GetBufferPointer()), entityFbb.GetSize()));
232 }
233
234 KAsync::Job<void> remove(const DomainType &domainObject) Q_DECL_OVERRIDE
235 {
236 return mResourceAccess->sendDeleteCommand(domainObject.identifier(), domainObject.revision(), bufferTypeForDomainType());
237 }
238
239 //TODO JOBAPI return job from sync continuation to execute it as subjob?
240 KAsync::Job<void> load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProvider<typename DomainType::Ptr> > &resultProvider) Q_DECL_OVERRIDE
241 {
242 auto runner = QSharedPointer<QueryRunner>::create(query);
243 QWeakPointer<Akonadi2::ResultProvider<typename DomainType::Ptr> > weakResultProvider = resultProvider;
244 runner->setQuery([this, weakResultProvider, query] (qint64 oldRevision) -> KAsync::Job<qint64> {
245 return KAsync::start<qint64>([this, weakResultProvider, query, oldRevision](KAsync::Future<qint64> &future) {
246 Trace() << "Executing query " << oldRevision;
247 auto resultProvider = weakResultProvider.toStrongRef();
248 if (!resultProvider) {
249 Warning() << "Tried executing query after result provider is already gone";
250 future.setError(0, QString());
251 future.setFinished();
252 return;
253 }
254 load(query, resultProvider, oldRevision).template then<void, qint64>([&future, this](qint64 queriedRevision) {
255 //TODO set revision in result provider?
256 //TODO update all existing results with new revision
257 mResourceAccess->sendRevisionReplayedCommand(queriedRevision);
258 future.setValue(queriedRevision);
259 future.setFinished();
260 }).exec();
261 });
262 });
263
264 //In case of a live query we keep the runner for as long alive as the result provider exists
265 if (query.liveQuery) {
266 resultProvider->setQueryRunner(runner);
267 //Ensure the connection is open, if it wasn't already opened
268 //TODO If we are not connected already, we have to check for the latest revision once connected, otherwise we could miss some updates
269 mResourceAccess->open();
270 QObject::connect(mResourceAccess.data(), &Akonadi2::ResourceAccess::revisionChanged, runner.data(), &QueryRunner::revisionChanged);
271 }
272
273 //We have to capture the runner to keep it alive
274 return synchronizeResource(query).template then<void>([runner](KAsync::Future<void> &future) {
275 runner->run().then<void>([&future]() {
276 future.setFinished();
277 }).exec();
278 },
279 [](int error, const QString &errorString) {
280 Warning() << "Error during sync " << error << errorString;
281 });
282 }
283
284private:
285 KAsync::Job<void> synchronizeResource(const Akonadi2::Query &query)
286 {
287 //TODO check if a sync is necessary
288 //TODO Only sync what was requested
289 //TODO timeout
290 if (query.syncOnDemand || query.processAll) {
291 return mResourceAccess->synchronizeResource(query.syncOnDemand, query.processAll);
292 }
293 return KAsync::null<void>();
294 }
295 57
296 virtual KAsync::Job<qint64> load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProvider<typename DomainType::Ptr> > &resultProvider, qint64 oldRevision) 58 static QByteArray bufferTypeForDomainType();
297 { 59 KAsync::Job<void> create(const DomainType &domainObject) Q_DECL_OVERRIDE;
298 return KAsync::start<qint64>([=]() -> qint64 { 60 KAsync::Job<void> modify(const DomainType &domainObject) Q_DECL_OVERRIDE;
299 return mStorage->read(query, oldRevision, resultProvider); 61 KAsync::Job<void> remove(const DomainType &domainObject) Q_DECL_OVERRIDE;
300 }); 62 QPair<KAsync::Job<void>, typename ResultEmitter<typename DomainType::Ptr>::Ptr> load(const Akonadi2::Query &query) Q_DECL_OVERRIDE;
301 }
302 63
303protected: 64protected:
304 //TODO use one resource access instance per application & per resource 65 //TODO use one resource access instance per application & per resource
305 QSharedPointer<Akonadi2::ResourceAccessInterface> mResourceAccess; 66 QSharedPointer<Akonadi2::ResourceAccessInterface> mResourceAccess;
306 QSharedPointer<EntityStorage<DomainType> > mStorage;
307 DomainTypeAdaptorFactoryInterface::Ptr mDomainTypeAdaptorFactory; 67 DomainTypeAdaptorFactoryInterface::Ptr mDomainTypeAdaptorFactory;
308 QByteArray mResourceInstanceIdentifier; 68 QByteArray mResourceInstanceIdentifier;
309}; 69};
diff --git a/common/facadeinterface.h b/common/facadeinterface.h
index 3a38db8..318abf3 100644
--- a/common/facadeinterface.h
+++ b/common/facadeinterface.h
@@ -23,6 +23,7 @@
23#include <Async/Async> 23#include <Async/Async>
24#include <QByteArray> 24#include <QByteArray>
25#include <QSharedPointer> 25#include <QSharedPointer>
26#include <QPair>
26#include "applicationdomaintype.h" 27#include "applicationdomaintype.h"
27#include "resultprovider.h" 28#include "resultprovider.h"
28 29
@@ -42,10 +43,32 @@ class StoreFacade {
42public: 43public:
43 virtual ~StoreFacade(){}; 44 virtual ~StoreFacade(){};
44 QByteArray type() const { return ApplicationDomain::getTypeName<DomainType>(); } 45 QByteArray type() const { return ApplicationDomain::getTypeName<DomainType>(); }
46
47 /**
48 * Create an entity in the store.
49 *
50 * The job returns succefully once the task has been successfully placed in the queue
51 */
45 virtual KAsync::Job<void> create(const DomainType &domainObject) = 0; 52 virtual KAsync::Job<void> create(const DomainType &domainObject) = 0;
53
54 /**
55 * Modify an entity in the store.
56 *
57 * The job returns succefully once the task has been successfully placed in the queue
58 */
46 virtual KAsync::Job<void> modify(const DomainType &domainObject) = 0; 59 virtual KAsync::Job<void> modify(const DomainType &domainObject) = 0;
60
61 /**
62 * Remove an entity from the store.
63 *
64 * The job returns succefully once the task has been successfully placed in the queue
65 */
47 virtual KAsync::Job<void> remove(const DomainType &domainObject) = 0; 66 virtual KAsync::Job<void> remove(const DomainType &domainObject) = 0;
48 virtual KAsync::Job<void> load(const Query &query, const QSharedPointer<Akonadi2::ResultProvider<typename DomainType::Ptr> > &resultProvider) = 0; 67
68 /**
69 * Load entities from the store.
70 */
71 virtual QPair<KAsync::Job<void>, typename Akonadi2::ResultEmitter<typename DomainType::Ptr>::Ptr > load(const Query &query) = 0;
49}; 72};
50 73
51template<class DomainType> 74template<class DomainType>
@@ -67,9 +90,9 @@ public:
67 return KAsync::error<void>(-1, "Failed to create a facade"); 90 return KAsync::error<void>(-1, "Failed to create a facade");
68 } 91 }
69 92
70 KAsync::Job<void> load(const Query &query, const QSharedPointer<Akonadi2::ResultProvider<typename DomainType::Ptr> > &resultProvider) 93 QPair<KAsync::Job<void>, typename Akonadi2::ResultEmitter<typename DomainType::Ptr>::Ptr > load(const Query &query)
71 { 94 {
72 return KAsync::error<void>(-1, "Failed to create a facade"); 95 return qMakePair(KAsync::null<void>(), typename Akonadi2::ResultEmitter<typename DomainType::Ptr>::Ptr());
73 } 96 }
74}; 97};
75 98
diff --git a/common/modelresult.cpp b/common/modelresult.cpp
new file mode 100644
index 0000000..c7fcd49
--- /dev/null
+++ b/common/modelresult.cpp
@@ -0,0 +1,251 @@
1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) version 3, or any
8 * later version accepted by the membership of KDE e.V. (or its
9 * successor approved by the membership of KDE e.V.), which shall
10 * act as a proxy defined in Section 6 of version 3 of the license.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20#include "modelresult.h"
21
22#include <QDebug>
23
24#include "domain/folder.h"
25#include "log.h"
26
27template<class T, class Ptr>
28ModelResult<T, Ptr>::ModelResult(const Akonadi2::Query &query, const QList<QByteArray> &propertyColumns)
29 :QAbstractItemModel(),
30 mPropertyColumns(propertyColumns),
31 mQuery(query)
32{
33}
34
35static qint64 getIdentifier(const QModelIndex &idx)
36{
37 if (!idx.isValid()) {
38 return 0;
39 }
40 return idx.internalId();
41}
42
43template<class T, class Ptr>
44qint64 ModelResult<T, Ptr>::parentId(const Ptr &value)
45{
46 if (!mQuery.parentProperty.isEmpty()) {
47 const auto property = value->getProperty(mQuery.parentProperty).toByteArray();
48 if (!property.isEmpty()) {
49 return qHash(property);
50 }
51 }
52 return 0;
53}
54
55template<class T, class Ptr>
56int ModelResult<T, Ptr>::rowCount(const QModelIndex &parent) const
57{
58 return mTree[getIdentifier(parent)].size();
59}
60
61template<class T, class Ptr>
62int ModelResult<T, Ptr>::columnCount(const QModelIndex &parent) const
63{
64 return mPropertyColumns.size();
65}
66
67template<class T, class Ptr>
68QVariant ModelResult<T, Ptr>::data(const QModelIndex &index, int role) const
69{
70 if (role == DomainObjectRole) {
71 Q_ASSERT(mEntities.contains(index.internalId()));
72 return QVariant::fromValue(mEntities.value(index.internalId()));
73 }
74 if (role == ChildrenFetchedRole) {
75 return childrenFetched(index);
76 }
77 if (role == Qt::DisplayRole) {
78 if (index.column() < mPropertyColumns.size()) {
79 Q_ASSERT(mEntities.contains(index.internalId()));
80 auto entity = mEntities.value(index.internalId());
81 return entity->getProperty(mPropertyColumns.at(index.column())).toString();
82 } else {
83 return "No data available";
84 }
85 }
86 return QVariant();
87}
88
89template<class T, class Ptr>
90QModelIndex ModelResult<T, Ptr>::index(int row, int column, const QModelIndex &parent) const
91{
92 const auto id = getIdentifier(parent);
93 const auto childId = mTree.value(id).at(row);
94 return createIndex(row, column, childId);
95}
96
97template<class T, class Ptr>
98QModelIndex ModelResult<T, Ptr>::createIndexFromId(const qint64 &id) const
99{
100 if (id == 0) {
101 return QModelIndex();
102 }
103 auto grandParentId = mParents.value(id, 0);
104 auto row = mTree.value(grandParentId).indexOf(id);
105 return createIndex(row, 0, id);
106}
107
108template<class T, class Ptr>
109QModelIndex ModelResult<T, Ptr>::parent(const QModelIndex &index) const
110{
111 auto id = getIdentifier(index);
112 auto parentId = mParents.value(id);
113 return createIndexFromId(parentId);
114}
115
116template<class T, class Ptr>
117bool ModelResult<T, Ptr>::hasChildren(const QModelIndex &parent) const
118{
119 if (mQuery.parentProperty.isEmpty() && parent.isValid()) {
120 return false;
121 }
122 return QAbstractItemModel::hasChildren(parent);
123}
124
125template<class T, class Ptr>
126bool ModelResult<T, Ptr>::canFetchMore(const QModelIndex &parent) const
127{
128 return !mEntityChildrenFetched.contains(parent.internalId());
129}
130
131template<class T, class Ptr>
132void ModelResult<T, Ptr>::fetchMore(const QModelIndex &parent)
133{
134 fetchEntities(parent);
135}
136
137template<class T, class Ptr>
138void ModelResult<T, Ptr>::add(const Ptr &value)
139{
140 const auto childId = qHash(value->identifier());
141 const auto id = parentId(value);
142 //Ignore updates we get before the initial fetch is done
143 if (!mEntityChildrenFetched.contains(id)) {
144 return;
145 }
146 auto parent = createIndexFromId(id);
147 // qDebug() << "Added entity " << childId << value->identifier() << id;
148 const auto keys = mTree[id];
149 int index = 0;
150 for (; index < keys.size(); index++) {
151 if (childId < keys.at(index)) {
152 break;
153 }
154 }
155 if (mEntities.contains(childId)) {
156 Warning() << "Entity already in model " << value->identifier();
157 return;
158 }
159 // qDebug() << "Inserting rows " << index << parent;
160 beginInsertRows(parent, index, index);
161 mEntities.insert(childId, value);
162 mTree[id].insert(index, childId);
163 mParents.insert(childId, id);
164 endInsertRows();
165 // qDebug() << "Inserted rows " << mTree[id].size();
166}
167
168
169template<class T, class Ptr>
170void ModelResult<T, Ptr>::remove(const Ptr &value)
171{
172 auto childId = qHash(value->identifier());
173 auto id = parentId(value);
174 auto parent = createIndexFromId(id);
175 // qDebug() << "Removed entity" << childId;
176 auto index = mTree[id].indexOf(qHash(value->identifier()));
177 beginRemoveRows(parent, index, index);
178 mEntities.remove(childId);
179 mTree[id].removeAll(childId);
180 mParents.remove(childId);
181 //TODO remove children
182 endRemoveRows();
183}
184
185template<class T, class Ptr>
186void ModelResult<T, Ptr>::fetchEntities(const QModelIndex &parent)
187{
188 const auto id = getIdentifier(parent);
189 mEntityChildrenFetched.insert(id);
190 Trace() << "Loading child entities";
191 loadEntities(parent.data(DomainObjectRole).template value<Ptr>());
192}
193
194template<class T, class Ptr>
195void ModelResult<T, Ptr>::setFetcher(const std::function<void(const Ptr &parent)> &fetcher)
196{
197 Trace() << "Setting fetcher";
198 loadEntities = fetcher;
199}
200
201template<class T, class Ptr>
202void ModelResult<T, Ptr>::setEmitter(const typename Akonadi2::ResultEmitter<Ptr>::Ptr &emitter)
203{
204 setFetcher(emitter->mFetcher);
205 emitter->onAdded([this](const Ptr &value) {
206 this->add(value);
207 });
208 emitter->onModified([this](const Ptr &value) {
209 this->modify(value);
210 });
211 emitter->onRemoved([this](const Ptr &value) {
212 this->remove(value);
213 });
214 emitter->onInitialResultSetComplete([this](const Ptr &parent) {
215 const qint64 parentId = parent ? qHash(parent->identifier()) : 0;
216 const auto parentIndex = createIndexFromId(parentId);
217 mEntityChildrenFetchComplete.insert(parentId);
218 emit dataChanged(parentIndex, parentIndex, QVector<int>() << ChildrenFetchedRole);
219 });
220 mEmitter = emitter;
221}
222
223template<class T, class Ptr>
224bool ModelResult<T, Ptr>::childrenFetched(const QModelIndex &index) const
225{
226 return mEntityChildrenFetchComplete.contains(getIdentifier(index));
227}
228
229template<class T, class Ptr>
230void ModelResult<T, Ptr>::modify(const Ptr &value)
231{
232 auto childId = qHash(value->identifier());
233 auto id = parentId(value);
234 //Ignore updates we get before the initial fetch is done
235 if (!mEntityChildrenFetched.contains(id)) {
236 return;
237 }
238 auto parent = createIndexFromId(id);
239 // qDebug() << "Modified entity" << childId;
240 auto i = mTree[id].indexOf(childId);
241 mEntities.remove(childId);
242 mEntities.insert(childId, value);
243 //TODO check for change of parents
244 auto idx = index(i, 0, parent);
245 emit dataChanged(idx, idx);
246}
247
248template class ModelResult<Akonadi2::ApplicationDomain::Folder, Akonadi2::ApplicationDomain::Folder::Ptr>;
249template class ModelResult<Akonadi2::ApplicationDomain::Mail, Akonadi2::ApplicationDomain::Mail::Ptr>;
250template class ModelResult<Akonadi2::ApplicationDomain::Event, Akonadi2::ApplicationDomain::Event::Ptr>;
251template class ModelResult<Akonadi2::ApplicationDomain::AkonadiResource, Akonadi2::ApplicationDomain::AkonadiResource::Ptr>;
diff --git a/common/modelresult.h b/common/modelresult.h
new file mode 100644
index 0000000..700064b
--- /dev/null
+++ b/common/modelresult.h
@@ -0,0 +1,78 @@
1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) version 3, or any
8 * later version accepted by the membership of KDE e.V. (or its
9 * successor approved by the membership of KDE e.V.), which shall
10 * act as a proxy defined in Section 6 of version 3 of the license.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#pragma once
22
23#include <QAbstractItemModel>
24#include <QModelIndex>
25#include <QDebug>
26#include <QSharedPointer>
27#include <functional>
28#include "query.h"
29#include "resultprovider.h"
30
31template<class T, class Ptr>
32class ModelResult : public QAbstractItemModel
33{
34public:
35 enum Roles {
36 DomainObjectRole = Qt::UserRole + 1,
37 ChildrenFetchedRole
38 };
39
40 ModelResult(const Akonadi2::Query &query, const QList<QByteArray> &propertyColumns);
41
42 void setEmitter(const typename Akonadi2::ResultEmitter<Ptr>::Ptr &);
43
44 int rowCount(const QModelIndex &parent = QModelIndex()) const;
45 int columnCount(const QModelIndex &parent = QModelIndex()) const;
46 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
47 QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const;
48 QModelIndex parent(const QModelIndex &index) const;
49 bool hasChildren(const QModelIndex &parent = QModelIndex()) const;
50
51 bool canFetchMore(const QModelIndex &parent) const;
52 void fetchMore(const QModelIndex &parent);
53
54 void add(const Ptr &value);
55 void modify(const Ptr &value);
56 void remove(const Ptr &value);
57
58 void setFetcher(const std::function<void(const Ptr &parent)> &fetcher);
59
60 bool childrenFetched(const QModelIndex &) const;
61
62private:
63 qint64 parentId(const Ptr &value);
64 QModelIndex createIndexFromId(const qint64 &id) const;
65 void fetchEntities(const QModelIndex &parent);
66
67 //TODO we should be able to directly use T as index, with an appropriate hash function, and thus have a QMap<T, T> and QList<T>
68 QMap<qint64 /* entity id */, Ptr> mEntities;
69 QMap<qint64 /* parent entity id */, QList<qint64> /* child entity id*/> mTree;
70 QMap<qint64 /* child entity id */, qint64 /* parent entity id*/> mParents;
71 QSet<qint64 /* entity id */> mEntityChildrenFetched;
72 QSet<qint64 /* entity id */> mEntityChildrenFetchComplete;
73 QList<QByteArray> mPropertyColumns;
74 Akonadi2::Query mQuery;
75 std::function<void(const Ptr &)> loadEntities;
76 typename Akonadi2::ResultEmitter<Ptr>::Ptr mEmitter;
77};
78
diff --git a/common/query.h b/common/query.h
index 0cad9fb..5313fa9 100644
--- a/common/query.h
+++ b/common/query.h
@@ -53,6 +53,7 @@ public:
53 QHash<QByteArray, QVariant> propertyFilter; 53 QHash<QByteArray, QVariant> propertyFilter;
54 //Properties to retrieve 54 //Properties to retrieve
55 QSet<QByteArray> requestedProperties; 55 QSet<QByteArray> requestedProperties;
56 QByteArray parentProperty;
56 bool syncOnDemand; 57 bool syncOnDemand;
57 bool processAll; 58 bool processAll;
58 //If live query is false, this query will not continuously be updated 59 //If live query is false, this query will not continuously be updated
diff --git a/common/queryrunner.cpp b/common/queryrunner.cpp
new file mode 100644
index 0000000..e365cfc
--- /dev/null
+++ b/common/queryrunner.cpp
@@ -0,0 +1,312 @@
1/*
2 Copyright (c) 2015 Christian Mollekopf <mollekopf@kolabsys.com>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19#include "queryrunner.h"
20
21#include <QtConcurrent/QtConcurrentRun>
22#include <QFuture>
23#include <QFutureWatcher>
24#include <QTime>
25#include "commands.h"
26#include "log.h"
27#include "storage.h"
28#include "definitions.h"
29#include "domainadaptor.h"
30
31using namespace Akonadi2;
32
33static inline ResultSet fullScan(const Akonadi2::Storage::Transaction &transaction, const QByteArray &bufferType)
34{
35 //TODO use a result set with an iterator, to read values on demand
36 QVector<QByteArray> keys;
37 transaction.openDatabase(bufferType + ".main").scan(QByteArray(), [&](const QByteArray &key, const QByteArray &value) -> bool {
38 //Skip internals
39 if (Akonadi2::Storage::isInternalKey(key)) {
40 return true;
41 }
42 keys << Akonadi2::Storage::uidFromKey(key);
43 return true;
44 },
45 [](const Akonadi2::Storage::Error &error) {
46 qWarning() << "Error during query: " << error.message;
47 });
48
49 Trace() << "Full scan on " << bufferType << " found " << keys.size() << " results";
50 return ResultSet(keys);
51}
52
53template<class DomainType>
54QueryRunner<DomainType>::QueryRunner(const Akonadi2::Query &query, const Akonadi2::ResourceAccessInterface::Ptr &resourceAccess, const QByteArray &instanceIdentifier, const DomainTypeAdaptorFactoryInterface::Ptr &factory, const QByteArray &bufferType)
55 : QueryRunnerBase(),
56 mResourceAccess(resourceAccess),
57 mResultProvider(new ResultProvider<typename DomainType::Ptr>),
58 mDomainTypeAdaptorFactory(factory),
59 mQuery(query),
60 mResourceInstanceIdentifier(instanceIdentifier),
61 mBufferType(bufferType)
62{
63 Trace() << "Starting query";
64 //We delegate loading of initial data to the result provider, os it can decide for itself what it needs to load.
65 mResultProvider->setFetcher([this, query](const typename DomainType::Ptr &parent) {
66 Trace() << "Running fetcher";
67
68 // auto watcher = new QFutureWatcher<qint64>;
69 // QObject::connect(watcher, &QFutureWatcher::finished, [](qint64 newRevision) {
70 // mResourceAccess->sendRevisionReplayedCommand(newRevision);
71 // });
72 // auto future = QtConcurrent::run([&resultProvider]() -> qint64 {
73 // const qint64 newRevision = executeInitialQuery(query, parent, resultProvider);
74 // return newRevision;
75 // });
76 // watcher->setFuture(future);
77 const qint64 newRevision = executeInitialQuery(query, parent, *mResultProvider);
78 mResourceAccess->sendRevisionReplayedCommand(newRevision);
79 });
80
81
82 //In case of a live query we keep the runner for as long alive as the result provider exists
83 if (query.liveQuery) {
84 //Incremental updates are always loaded directly, leaving it up to the result to discard the changes if they are not interesting
85 setQuery([this, query] () -> KAsync::Job<void> {
86 return KAsync::start<void>([this, query](KAsync::Future<void> &future) {
87 //TODO execute in thread
88 const qint64 newRevision = executeIncrementalQuery(query, *mResultProvider);
89 mResourceAccess->sendRevisionReplayedCommand(newRevision);
90 future.setFinished();
91 });
92 });
93 //Ensure the connection is open, if it wasn't already opened
94 //TODO If we are not connected already, we have to check for the latest revision once connected, otherwise we could miss some updates
95 mResourceAccess->open();
96 QObject::connect(mResourceAccess.data(), &Akonadi2::ResourceAccess::revisionChanged, this, &QueryRunner::revisionChanged);
97 }
98}
99
100template<class DomainType>
101QueryRunner<DomainType>::~QueryRunner()
102{
103 Trace() << "Stopped query";
104}
105
106template<class DomainType>
107typename Akonadi2::ResultEmitter<typename DomainType::Ptr>::Ptr QueryRunner<DomainType>::emitter()
108{
109 return mResultProvider->emitter();
110}
111
112//TODO move into result provider?
113template<class DomainType>
114void QueryRunner<DomainType>::replaySet(ResultSet &resultSet, Akonadi2::ResultProviderInterface<typename DomainType::Ptr> &resultProvider)
115{
116 // Trace() << "Replay set";
117 int counter = 0;
118 while (resultSet.next([&resultProvider, &counter](const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &value, Akonadi2::Operation operation) -> bool {
119 counter++;
120 switch (operation) {
121 case Akonadi2::Operation_Creation:
122 // Trace() << "Got creation";
123 resultProvider.add(Akonadi2::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<DomainType>(*value).template staticCast<DomainType>());
124 break;
125 case Akonadi2::Operation_Modification:
126 // Trace() << "Got modification";
127 resultProvider.modify(Akonadi2::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<DomainType>(*value).template staticCast<DomainType>());
128 break;
129 case Akonadi2::Operation_Removal:
130 // Trace() << "Got removal";
131 resultProvider.remove(Akonadi2::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<DomainType>(*value).template staticCast<DomainType>());
132 break;
133 }
134 return true;
135 })){};
136 Trace() << "Replayed " << counter << " results";
137}
138
139template<class DomainType>
140void QueryRunner<DomainType>::readEntity(const Akonadi2::Storage::NamedDatabase &db, const QByteArray &key, const std::function<void(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &, Akonadi2::Operation)> &resultCallback)
141{
142 //This only works for a 1:1 mapping of resource to domain types.
143 //Not i.e. for tags that are stored as flags in each entity of an imap store.
144 //additional properties that don't have a 1:1 mapping (such as separately stored tags),
145 //could be added to the adaptor.
146 db.findLatest(key, [=](const QByteArray &key, const QByteArray &value) -> bool {
147 Akonadi2::EntityBuffer buffer(value.data(), value.size());
148 const Akonadi2::Entity &entity = buffer.entity();
149 const auto metadataBuffer = Akonadi2::EntityBuffer::readBuffer<Akonadi2::Metadata>(entity.metadata());
150 Q_ASSERT(metadataBuffer);
151 const qint64 revision = metadataBuffer ? metadataBuffer->revision() : -1;
152 resultCallback(DomainType::Ptr::create(mResourceInstanceIdentifier, Akonadi2::Storage::uidFromKey(key), revision, mDomainTypeAdaptorFactory->createAdaptor(entity)), metadataBuffer->operation());
153 return false;
154 },
155 [](const Akonadi2::Storage::Error &error) {
156 qWarning() << "Error during query: " << error.message;
157 });
158}
159
160template<class DomainType>
161ResultSet QueryRunner<DomainType>::loadInitialResultSet(const Akonadi2::Query &query, Akonadi2::Storage::Transaction &transaction, QSet<QByteArray> &remainingFilters)
162{
163 QSet<QByteArray> appliedFilters;
164 auto resultSet = Akonadi2::ApplicationDomain::TypeImplementation<DomainType>::queryIndexes(query, mResourceInstanceIdentifier, appliedFilters, transaction);
165 remainingFilters = query.propertyFilter.keys().toSet() - appliedFilters;
166
167 //We do a full scan if there were no indexes available to create the initial set.
168 if (appliedFilters.isEmpty()) {
169 //TODO this should be replaced by an index lookup as well
170 resultSet = fullScan(transaction, mBufferType);
171 }
172 return resultSet;
173}
174
175template<class DomainType>
176ResultSet QueryRunner<DomainType>::loadIncrementalResultSet(qint64 baseRevision, const Akonadi2::Query &query, Akonadi2::Storage::Transaction &transaction, QSet<QByteArray> &remainingFilters)
177{
178 const auto bufferType = mBufferType;
179 auto revisionCounter = QSharedPointer<qint64>::create(baseRevision);
180 remainingFilters = query.propertyFilter.keys().toSet();
181 return ResultSet([bufferType, revisionCounter, &transaction]() -> QByteArray {
182 const qint64 topRevision = Akonadi2::Storage::maxRevision(transaction);
183 //Spit out the revision keys one by one.
184 while (*revisionCounter <= topRevision) {
185 const auto uid = Akonadi2::Storage::getUidFromRevision(transaction, *revisionCounter);
186 const auto type = Akonadi2::Storage::getTypeFromRevision(transaction, *revisionCounter);
187 // Trace() << "Revision" << *revisionCounter << type << uid;
188 if (type != bufferType) {
189 //Skip revision
190 *revisionCounter += 1;
191 continue;
192 }
193 const auto key = Akonadi2::Storage::assembleKey(uid, *revisionCounter);
194 *revisionCounter += 1;
195 return key;
196 }
197 Trace() << "Finished reading incremental result set:" << *revisionCounter;
198 //We're done
199 return QByteArray();
200 });
201}
202
203template<class DomainType>
204ResultSet QueryRunner<DomainType>::filterSet(const ResultSet &resultSet, const std::function<bool(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject)> &filter, const Akonadi2::Storage::NamedDatabase &db, bool initialQuery)
205{
206 auto resultSetPtr = QSharedPointer<ResultSet>::create(resultSet);
207
208 //Read through the source values and return whatever matches the filter
209 std::function<bool(std::function<void(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &, Akonadi2::Operation)>)> generator = [this, resultSetPtr, &db, filter, initialQuery](std::function<void(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &, Akonadi2::Operation)> callback) -> bool {
210 while (resultSetPtr->next()) {
211 //readEntity is only necessary if we actually want to filter or know the operation type (but not a big deal if we do it always I guess)
212 readEntity(db, resultSetPtr->id(), [this, filter, callback, initialQuery](const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject, Akonadi2::Operation operation) {
213 //Always remove removals, they probably don't match due to non-available properties
214 if (filter(domainObject) || operation == Akonadi2::Operation_Removal) {
215 if (initialQuery) {
216 //We're not interested in removals during the initial query
217 if (operation != Akonadi2::Operation_Removal) {
218 callback(domainObject, Akonadi2::Operation_Creation);
219 }
220 } else {
221 callback(domainObject, operation);
222 }
223 }
224 });
225 }
226 return false;
227 };
228 return ResultSet(generator);
229}
230
231
232template<class DomainType>
233std::function<bool(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject)> QueryRunner<DomainType>::getFilter(const QSet<QByteArray> remainingFilters, const Akonadi2::Query &query)
234{
235 return [remainingFilters, query](const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject) -> bool {
236 for (const auto &filterProperty : remainingFilters) {
237 const auto property = domainObject->getProperty(filterProperty);
238 if (property.isValid()) {
239 //TODO implement other comparison operators than equality
240 if (property != query.propertyFilter.value(filterProperty)) {
241 Trace() << "Filtering entity due to property mismatch: " << domainObject->getProperty(filterProperty);
242 return false;
243 }
244 } else {
245 Warning() << "Ignored property filter because value is invalid: " << filterProperty;
246 }
247 }
248 return true;
249 };
250}
251
252template<class DomainType>
253qint64 QueryRunner<DomainType>::load(const Akonadi2::Query &query, const std::function<ResultSet(Akonadi2::Storage::Transaction &, QSet<QByteArray> &)> &baseSetRetriever, Akonadi2::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, bool initialQuery)
254{
255 Akonadi2::Storage storage(Akonadi2::storageLocation(), mResourceInstanceIdentifier);
256 storage.setDefaultErrorHandler([](const Akonadi2::Storage::Error &error) {
257 Warning() << "Error during query: " << error.store << error.message;
258 });
259 auto transaction = storage.createTransaction(Akonadi2::Storage::ReadOnly);
260 auto db = transaction.openDatabase(mBufferType + ".main");
261
262 QSet<QByteArray> remainingFilters;
263 auto resultSet = baseSetRetriever(transaction, remainingFilters);
264 auto filteredSet = filterSet(resultSet, getFilter(remainingFilters, query), db, initialQuery);
265 replaySet(filteredSet, resultProvider);
266 resultProvider.setRevision(Akonadi2::Storage::maxRevision(transaction));
267 return Akonadi2::Storage::maxRevision(transaction);
268}
269
270
271template<class DomainType>
272qint64 QueryRunner<DomainType>::executeIncrementalQuery(const Akonadi2::Query &query, Akonadi2::ResultProviderInterface<typename DomainType::Ptr> &resultProvider)
273{
274 QTime time;
275 time.start();
276
277 const qint64 baseRevision = resultProvider.revision() + 1;
278 Trace() << "Running incremental query " << baseRevision;
279 auto revision = load(query, [&](Akonadi2::Storage::Transaction &transaction, QSet<QByteArray> &remainingFilters) -> ResultSet {
280 return loadIncrementalResultSet(baseRevision, query, transaction, remainingFilters);
281 }, resultProvider, false);
282 Trace() << "Incremental query took: " << time.elapsed() << " ms";
283 return revision;
284}
285
286template<class DomainType>
287qint64 QueryRunner<DomainType>::executeInitialQuery(const Akonadi2::Query &query, const typename DomainType::Ptr &parent, Akonadi2::ResultProviderInterface<typename DomainType::Ptr> &resultProvider)
288{
289 QTime time;
290 time.start();
291
292 auto modifiedQuery = query;
293 if (!query.parentProperty.isEmpty()) {
294 if (parent) {
295 Trace() << "Running initial query for parent:" << parent->identifier();
296 modifiedQuery.propertyFilter.insert(query.parentProperty, parent->identifier());
297 } else {
298 Trace() << "Running initial query for toplevel";
299 modifiedQuery.propertyFilter.insert(query.parentProperty, QVariant());
300 }
301 }
302 auto revision = load(modifiedQuery, [&](Akonadi2::Storage::Transaction &transaction, QSet<QByteArray> &remainingFilters) -> ResultSet {
303 return loadInitialResultSet(modifiedQuery, transaction, remainingFilters);
304 }, resultProvider, true);
305 Trace() << "Initial query took: " << time.elapsed() << " ms";
306 resultProvider.initialResultSetComplete(parent);
307 return revision;
308}
309
310template class QueryRunner<Akonadi2::ApplicationDomain::Folder>;
311template class QueryRunner<Akonadi2::ApplicationDomain::Mail>;
312template class QueryRunner<Akonadi2::ApplicationDomain::Event>;
diff --git a/common/queryrunner.h b/common/queryrunner.h
new file mode 100644
index 0000000..c918dcb
--- /dev/null
+++ b/common/queryrunner.h
@@ -0,0 +1,108 @@
1/*
2 Copyright (c) 2015 Christian Mollekopf <mollekopf@kolabsys.com>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#pragma once
21
22#include <QObject>
23#include "facadeinterface.h"
24#include "resourceaccess.h"
25#include "resultprovider.h"
26#include "domaintypeadaptorfactoryinterface.h"
27#include "storage.h"
28#include "query.h"
29
30/**
31 * A QueryRunner runs a query and updates the corresponding result set.
32 *
33 * The lifetime of the QueryRunner is defined by the resut set (otherwise it's doing useless work),
34 * and by how long a result set must be updated. If the query is one off the runner dies after the execution,
35 * otherwise it lives on the react to changes and updates the corresponding result set.
36 *
37 * QueryRunner has to keep ResourceAccess alive in order to keep getting updates.
38 */
39
40class QueryRunnerBase : public QObject
41{
42 Q_OBJECT
43protected:
44 typedef std::function<KAsync::Job<void>()> QueryFunction;
45
46 /**
47 * Set the query to run
48 */
49 void setQuery(const QueryFunction &query)
50 {
51 queryFunction = query;
52 }
53
54
55protected slots:
56 /**
57 * Rerun query with new revision
58 */
59 void revisionChanged(qint64 newRevision)
60 {
61 Trace() << "New revision: " << newRevision;
62 run().exec();
63 }
64
65private:
66 /**
67 * Starts query
68 */
69 KAsync::Job<void> run(qint64 newRevision = 0)
70 {
71 return queryFunction();
72 }
73
74 QueryFunction queryFunction;
75};
76
77template<typename DomainType>
78class QueryRunner : public QueryRunnerBase
79{
80public:
81 QueryRunner(const Akonadi2::Query &query, const Akonadi2::ResourceAccessInterface::Ptr &, const QByteArray &instanceIdentifier, const DomainTypeAdaptorFactoryInterface::Ptr &, const QByteArray &bufferType);
82 virtual ~QueryRunner();
83
84 typename Akonadi2::ResultEmitter<typename DomainType::Ptr>::Ptr emitter();
85
86private:
87 static void replaySet(ResultSet &resultSet, Akonadi2::ResultProviderInterface<typename DomainType::Ptr> &resultProvider);
88
89 void readEntity(const Akonadi2::Storage::NamedDatabase &db, const QByteArray &key, const std::function<void(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &, Akonadi2::Operation)> &resultCallback);
90
91 ResultSet loadInitialResultSet(const Akonadi2::Query &query, Akonadi2::Storage::Transaction &transaction, QSet<QByteArray> &remainingFilters);
92 ResultSet loadIncrementalResultSet(qint64 baseRevision, const Akonadi2::Query &query, Akonadi2::Storage::Transaction &transaction, QSet<QByteArray> &remainingFilters);
93
94 ResultSet filterSet(const ResultSet &resultSet, const std::function<bool(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject)> &filter, const Akonadi2::Storage::NamedDatabase &db, bool initialQuery);
95 std::function<bool(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject)> getFilter(const QSet<QByteArray> remainingFilters, const Akonadi2::Query &query);
96 qint64 load(const Akonadi2::Query &query, const std::function<ResultSet(Akonadi2::Storage::Transaction &, QSet<QByteArray> &)> &baseSetRetriever, Akonadi2::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, bool initialQuery);
97 qint64 executeIncrementalQuery(const Akonadi2::Query &query, Akonadi2::ResultProviderInterface<typename DomainType::Ptr> &resultProvider);
98 qint64 executeInitialQuery(const Akonadi2::Query &query, const typename DomainType::Ptr &parent, Akonadi2::ResultProviderInterface<typename DomainType::Ptr> &resultProvider);
99
100private:
101 QSharedPointer<Akonadi2::ResultProvider<typename DomainType::Ptr> > mResultProvider;
102 QSharedPointer<Akonadi2::ResourceAccessInterface> mResourceAccess;
103 DomainTypeAdaptorFactoryInterface::Ptr mDomainTypeAdaptorFactory;
104 QByteArray mResourceInstanceIdentifier;
105 QByteArray mBufferType;
106 Akonadi2::Query mQuery;
107};
108
diff --git a/common/resourceaccess.cpp b/common/resourceaccess.cpp
index bd9e2c9..be25533 100644
--- a/common/resourceaccess.cpp
+++ b/common/resourceaccess.cpp
@@ -282,6 +282,7 @@ KAsync::Job<void> ResourceAccess::sendCommand(int commandId, flatbuffers::FlatB
282 282
283KAsync::Job<void> ResourceAccess::synchronizeResource(bool sourceSync, bool localSync) 283KAsync::Job<void> ResourceAccess::synchronizeResource(bool sourceSync, bool localSync)
284{ 284{
285 Trace() << "Sending synchronize command: " << sourceSync << localSync;
285 flatbuffers::FlatBufferBuilder fbb; 286 flatbuffers::FlatBufferBuilder fbb;
286 auto command = Akonadi2::CreateSynchronize(fbb, sourceSync, localSync); 287 auto command = Akonadi2::CreateSynchronize(fbb, sourceSync, localSync);
287 Akonadi2::FinishSynchronizeBuffer(fbb, command); 288 Akonadi2::FinishSynchronizeBuffer(fbb, command);
@@ -340,7 +341,7 @@ KAsync::Job<void> ResourceAccess::sendRevisionReplayedCommand(qint64 revision)
340void ResourceAccess::open() 341void ResourceAccess::open()
341{ 342{
342 if (d->socket && d->socket->isValid()) { 343 if (d->socket && d->socket->isValid()) {
343 log("Socket valid, so not opening again"); 344 // Trace() << "Socket valid, so not opening again";
344 return; 345 return;
345 } 346 }
346 if (d->openingSocket) { 347 if (d->openingSocket) {
diff --git a/common/resourceaccess.h b/common/resourceaccess.h
index 8e27054..e87a1f7 100644
--- a/common/resourceaccess.h
+++ b/common/resourceaccess.h
@@ -37,6 +37,8 @@ class ResourceAccessInterface : public QObject
37{ 37{
38 Q_OBJECT 38 Q_OBJECT
39public: 39public:
40 typedef QSharedPointer<ResourceAccessInterface> Ptr;
41
40 ResourceAccessInterface() {} 42 ResourceAccessInterface() {}
41 virtual ~ResourceAccessInterface() {} 43 virtual ~ResourceAccessInterface() {}
42 virtual KAsync::Job<void> sendCommand(int commandId) = 0; 44 virtual KAsync::Job<void> sendCommand(int commandId) = 0;
diff --git a/common/resourcefacade.cpp b/common/resourcefacade.cpp
index 54185f8..6510c90 100644
--- a/common/resourcefacade.cpp
+++ b/common/resourcefacade.cpp
@@ -54,9 +54,15 @@ KAsync::Job<void> ResourceFacade::remove(const Akonadi2::ApplicationDomain::Akon
54 }); 54 });
55} 55}
56 56
57KAsync::Job<void> ResourceFacade::load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProvider<typename Akonadi2::ApplicationDomain::AkonadiResource::Ptr> > &resultProvider) 57QPair<KAsync::Job<void>, typename Akonadi2::ResultEmitter<Akonadi2::ApplicationDomain::AkonadiResource::Ptr>::Ptr > ResourceFacade::load(const Akonadi2::Query &query)
58{ 58{
59 return KAsync::start<void>([query, resultProvider]() { 59 auto resultProvider = new Akonadi2::ResultProvider<typename Akonadi2::ApplicationDomain::AkonadiResource::Ptr>();
60 auto emitter = resultProvider->emitter();
61 resultProvider->setFetcher([](const QSharedPointer<Akonadi2::ApplicationDomain::AkonadiResource> &) {});
62 resultProvider->onDone([resultProvider]() {
63 delete resultProvider;
64 });
65 auto job = KAsync::start<void>([query, resultProvider]() {
60 const auto configuredResources = ResourceConfig::getResources(); 66 const auto configuredResources = ResourceConfig::getResources();
61 for (const auto &res : configuredResources.keys()) { 67 for (const auto &res : configuredResources.keys()) {
62 const auto type = configuredResources.value(res); 68 const auto type = configuredResources.value(res);
@@ -68,8 +74,9 @@ KAsync::Job<void> ResourceFacade::load(const Akonadi2::Query &query, const QShar
68 } 74 }
69 } 75 }
70 //TODO initialResultSetComplete should be implicit 76 //TODO initialResultSetComplete should be implicit
71 resultProvider->initialResultSetComplete(); 77 resultProvider->initialResultSetComplete(Akonadi2::ApplicationDomain::AkonadiResource::Ptr());
72 resultProvider->complete(); 78 resultProvider->complete();
73 }); 79 });
80 return qMakePair(job, emitter);
74} 81}
75 82
diff --git a/common/resourcefacade.h b/common/resourcefacade.h
index 437ff75..38e0c0e 100644
--- a/common/resourcefacade.h
+++ b/common/resourcefacade.h
@@ -37,5 +37,6 @@ public:
37 KAsync::Job<void> create(const Akonadi2::ApplicationDomain::AkonadiResource &resource) Q_DECL_OVERRIDE; 37 KAsync::Job<void> create(const Akonadi2::ApplicationDomain::AkonadiResource &resource) Q_DECL_OVERRIDE;
38 KAsync::Job<void> modify(const Akonadi2::ApplicationDomain::AkonadiResource &resource) Q_DECL_OVERRIDE; 38 KAsync::Job<void> modify(const Akonadi2::ApplicationDomain::AkonadiResource &resource) Q_DECL_OVERRIDE;
39 KAsync::Job<void> remove(const Akonadi2::ApplicationDomain::AkonadiResource &resource) Q_DECL_OVERRIDE; 39 KAsync::Job<void> remove(const Akonadi2::ApplicationDomain::AkonadiResource &resource) Q_DECL_OVERRIDE;
40 KAsync::Job<void> load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProvider<typename Akonadi2::ApplicationDomain::AkonadiResource::Ptr> > &resultProvider) Q_DECL_OVERRIDE; 40 QPair<KAsync::Job<void>, typename Akonadi2::ResultEmitter<Akonadi2::ApplicationDomain::AkonadiResource::Ptr>::Ptr > load(const Akonadi2::Query &query) Q_DECL_OVERRIDE;
41}; 41};
42
diff --git a/common/resultprovider.h b/common/resultprovider.h
index bc03152..d50f3f6 100644
--- a/common/resultprovider.h
+++ b/common/resultprovider.h
@@ -20,9 +20,12 @@
20 20
21#pragma once 21#pragma once
22 22
23#include <QThread>
23#include <functional> 24#include <functional>
24#include <memory> 25#include <memory>
25#include "threadboundary.h" 26#include "threadboundary.h"
27#include "resultset.h"
28#include "log.h"
26 29
27using namespace async; 30using namespace async;
28 31
@@ -34,11 +37,43 @@ namespace Akonadi2 {
34template<class T> 37template<class T>
35class ResultEmitter; 38class ResultEmitter;
36 39
40template<class T>
41class ResultProviderInterface
42{
43public:
44 ResultProviderInterface()
45 : mRevision(0)
46 {
47
48 }
49
50 virtual void add(const T &value) = 0;
51 virtual void modify(const T &value) = 0;
52 virtual void remove(const T &value) = 0;
53 virtual void initialResultSetComplete(const T &parent) = 0;
54 virtual void complete() = 0;
55 virtual void clear() = 0;
56 virtual void setFetcher(const std::function<void(const T &parent)> &fetcher) = 0;
57
58 void setRevision(qint64 revision)
59 {
60 mRevision = revision;
61 }
62
63 qint64 revision() const
64 {
65 return mRevision;
66 }
67
68private:
69 qint64 mRevision;
70};
71
37/* 72/*
38* The promise side for the result emitter 73* The promise side for the result emitter
39*/ 74*/
40template<class T> 75template<class T>
41class ResultProvider { 76class ResultProvider : public ResultProviderInterface<T> {
42private: 77private:
43 void callInMainThreadOnEmitter(void (ResultEmitter<T>::*f)()) 78 void callInMainThreadOnEmitter(void (ResultEmitter<T>::*f)())
44 { 79 {
@@ -69,6 +104,12 @@ private:
69 } 104 }
70 105
71public: 106public:
107 typedef QSharedPointer<ResultProvider<T> > Ptr;
108
109 virtual ~ResultProvider()
110 {
111 }
112
72 //Called from worker thread 113 //Called from worker thread
73 void add(const T &value) 114 void add(const T &value)
74 { 115 {
@@ -103,9 +144,15 @@ public:
103 }); 144 });
104 } 145 }
105 146
106 void initialResultSetComplete() 147 void initialResultSetComplete(const T &parent)
107 { 148 {
108 callInMainThreadOnEmitter(&ResultEmitter<T>::initialResultSetComplete); 149 //Because I don't know how to use bind
150 auto weakEmitter = mResultEmitter;
151 callInMainThreadOnEmitter([weakEmitter, parent](){
152 if (auto strongRef = weakEmitter.toStrongRef()) {
153 strongRef->initialResultSetComplete(parent);
154 }
155 });
109 } 156 }
110 157
111 //Called from worker thread 158 //Called from worker thread
@@ -126,30 +173,16 @@ public:
126 //We have to go over a separate var and return that, otherwise we'd delete the emitter immediately again 173 //We have to go over a separate var and return that, otherwise we'd delete the emitter immediately again
127 auto sharedPtr = QSharedPointer<ResultEmitter<T> >(new ResultEmitter<T>, [this](ResultEmitter<T> *emitter){ mThreadBoundary->callInMainThread([this]() {done();}); delete emitter; }); 174 auto sharedPtr = QSharedPointer<ResultEmitter<T> >(new ResultEmitter<T>, [this](ResultEmitter<T> *emitter){ mThreadBoundary->callInMainThread([this]() {done();}); delete emitter; });
128 mResultEmitter = sharedPtr; 175 mResultEmitter = sharedPtr;
176 sharedPtr->setFetcher([this](const T &parent) {
177 Q_ASSERT(mFetcher);
178 mFetcher(parent);
179 });
129 return sharedPtr; 180 return sharedPtr;
130 } 181 }
131 182
132 return mResultEmitter.toStrongRef(); 183 return mResultEmitter.toStrongRef();
133 } 184 }
134 185
135 /**
136 * For lifetimemanagement only.
137 * We keep the runner alive as long as the result provider exists.
138 */
139 void setQueryRunner(const QSharedPointer<QObject> &runner)
140 {
141 mQueryRunner = runner;
142 }
143
144 /**
145 * For lifetimemanagement only.
146 * We keep the runner alive as long as the result provider exists.
147 */
148 void setFacade(const std::shared_ptr<void> &facade)
149 {
150 mFacade = facade;
151 }
152
153 void onDone(const std::function<void()> &callback) 186 void onDone(const std::function<void()> &callback)
154 { 187 {
155 mThreadBoundary = QSharedPointer<ThreadBoundary>::create(); 188 mThreadBoundary = QSharedPointer<ThreadBoundary>::create();
@@ -162,21 +195,27 @@ public:
162 return mResultEmitter.toStrongRef().isNull(); 195 return mResultEmitter.toStrongRef().isNull();
163 } 196 }
164 197
198 void setFetcher(const std::function<void(const T &parent)> &fetcher)
199 {
200 mFetcher = fetcher;
201 }
202
165private: 203private:
166 void done() 204 void done()
167 { 205 {
168 qWarning() << "done"; 206 qWarning() << "done";
169 if (mOnDoneCallback) { 207 if (mOnDoneCallback) {
170 mOnDoneCallback(); 208 auto callback = mOnDoneCallback;
171 mOnDoneCallback = std::function<void()>(); 209 mOnDoneCallback = std::function<void()>();
210 //This may delete this object
211 callback();
172 } 212 }
173 } 213 }
174 214
175 QWeakPointer<ResultEmitter<T> > mResultEmitter; 215 QWeakPointer<ResultEmitter<T> > mResultEmitter;
176 QSharedPointer<QObject> mQueryRunner;
177 std::shared_ptr<void> mFacade;
178 std::function<void()> mOnDoneCallback; 216 std::function<void()> mOnDoneCallback;
179 QSharedPointer<ThreadBoundary> mThreadBoundary; 217 QSharedPointer<ThreadBoundary> mThreadBoundary;
218 std::function<void(const T &parent)> mFetcher;
180}; 219};
181 220
182/* 221/*
@@ -194,6 +233,8 @@ private:
194template<class DomainType> 233template<class DomainType>
195class ResultEmitter { 234class ResultEmitter {
196public: 235public:
236 typedef QSharedPointer<ResultEmitter<DomainType> > Ptr;
237
197 void onAdded(const std::function<void(const DomainType&)> &handler) 238 void onAdded(const std::function<void(const DomainType&)> &handler)
198 { 239 {
199 addHandler = handler; 240 addHandler = handler;
@@ -209,7 +250,7 @@ public:
209 removeHandler = handler; 250 removeHandler = handler;
210 } 251 }
211 252
212 void onInitialResultSetComplete(const std::function<void(void)> &handler) 253 void onInitialResultSetComplete(const std::function<void(const DomainType&)> &handler)
213 { 254 {
214 initialResultSetCompleteHandler = handler; 255 initialResultSetCompleteHandler = handler;
215 } 256 }
@@ -239,28 +280,41 @@ public:
239 removeHandler(value); 280 removeHandler(value);
240 } 281 }
241 282
242 void initialResultSetComplete() 283 void initialResultSetComplete(const DomainType &parent)
243 { 284 {
244 initialResultSetCompleteHandler(); 285 if (initialResultSetCompleteHandler) {
286 initialResultSetCompleteHandler(parent);
287 }
245 } 288 }
246 289
247 void complete() 290 void complete()
248 { 291 {
249 completeHandler(); 292 if (completeHandler) {
293 completeHandler();
294 }
250 } 295 }
251 296
252 void clear() 297 void clear()
253 { 298 {
254 clearHandler(); 299 if (clearHandler) {
300 clearHandler();
301 }
255 } 302 }
256 303
304 void setFetcher(const std::function<void(const DomainType &parent)> &fetcher)
305 {
306 mFetcher = fetcher;
307 }
308
309 std::function<void(const DomainType &parent)> mFetcher;
310
257private: 311private:
258 friend class ResultProvider<DomainType>; 312 friend class ResultProvider<DomainType>;
259 313
260 std::function<void(const DomainType&)> addHandler; 314 std::function<void(const DomainType&)> addHandler;
261 std::function<void(const DomainType&)> modifyHandler; 315 std::function<void(const DomainType&)> modifyHandler;
262 std::function<void(const DomainType&)> removeHandler; 316 std::function<void(const DomainType&)> removeHandler;
263 std::function<void(void)> initialResultSetCompleteHandler; 317 std::function<void(const DomainType&)> initialResultSetCompleteHandler;
264 std::function<void(void)> completeHandler; 318 std::function<void(void)> completeHandler;
265 std::function<void(void)> clearHandler; 319 std::function<void(void)> clearHandler;
266 ThreadBoundary mThreadBoundary; 320 ThreadBoundary mThreadBoundary;
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp
index 1516e69..a247e38 100644
--- a/common/storage_lmdb.cpp
+++ b/common/storage_lmdb.cpp
@@ -253,22 +253,69 @@ int Storage::NamedDatabase::scan(const QByteArray &k,
253 253
254 return numberOfRetrievedValues; 254 return numberOfRetrievedValues;
255} 255}
256void Storage::NamedDatabase::findLatest(const QByteArray &uid, 256
257void Storage::NamedDatabase::findLatest(const QByteArray &k,
257 const std::function<void(const QByteArray &key, const QByteArray &value)> &resultHandler, 258 const std::function<void(const QByteArray &key, const QByteArray &value)> &resultHandler,
258 const std::function<void(const Storage::Error &error)> &errorHandler) const 259 const std::function<void(const Storage::Error &error)> &errorHandler) const
259{ 260{
260 QByteArray latestKey; 261 if (!d || !d->transaction) {
261 scan(uid, [&](const QByteArray &key, const QByteArray &value) -> bool { 262 //Not an error. We rely on this to read nothing from non-existing databases.
262 latestKey = key; 263 return;
263 return true; 264 }
264 },
265 errorHandler, true);
266 265
267 scan(latestKey, [=](const QByteArray &key, const QByteArray &value) -> bool { 266 int rc;
268 resultHandler(key, value); 267 MDB_val key;
269 return false; 268 MDB_val data;
270 }, 269 MDB_cursor *cursor;
271 errorHandler); 270
271 key.mv_data = (void*)k.constData();
272 key.mv_size = k.size();
273
274 rc = mdb_cursor_open(d->transaction, d->dbi, &cursor);
275 if (rc) {
276 Error error(d->name.toLatin1(), getErrorCode(rc), QByteArray("Error during mdb_cursor open: ") + QByteArray(mdb_strerror(rc)));
277 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
278 return;
279 }
280
281 MDB_cursor_op op = MDB_SET_RANGE;
282 if ((rc = mdb_cursor_get(cursor, &key, &data, op)) == 0) {
283 //The first lookup will find a key that is equal or greather than our key
284 if (QByteArray::fromRawData((char*)key.mv_data, key.mv_size).startsWith(k)) {
285 bool advanced = false;
286 while (QByteArray::fromRawData((char*)key.mv_data, key.mv_size).startsWith(k)) {
287 advanced = true;
288 MDB_cursor_op nextOp = MDB_NEXT;
289 rc = mdb_cursor_get(cursor, &key, &data, nextOp);
290 if (rc) {
291 break;
292 }
293 }
294 if (advanced) {
295 MDB_cursor_op prefOp = MDB_PREV;
296 //We read past the end above, just take the last value
297 if (rc == MDB_NOTFOUND) {
298 prefOp = MDB_LAST;
299 }
300 rc = mdb_cursor_get(cursor, &key, &data, prefOp);
301 resultHandler(QByteArray::fromRawData((char*)key.mv_data, key.mv_size), QByteArray::fromRawData((char*)data.mv_data, data.mv_size));
302 }
303 }
304 }
305
306 //We never find the last value
307 if (rc == MDB_NOTFOUND) {
308 rc = 0;
309 }
310
311 mdb_cursor_close(cursor);
312
313 if (rc) {
314 Error error(d->name.toLatin1(), getErrorCode(rc), QByteArray("Key: ") + k + " : " + QByteArray(mdb_strerror(rc)));
315 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
316 }
317
318 return;
272} 319}
273 320
274 321
diff --git a/common/threadboundary.cpp b/common/threadboundary.cpp
index 47ec508..48fd11a 100644
--- a/common/threadboundary.cpp
+++ b/common/threadboundary.cpp
@@ -24,6 +24,9 @@ Q_DECLARE_METATYPE(std::function<void()>);
24 24
25namespace async { 25namespace async {
26ThreadBoundary::ThreadBoundary(): QObject() { qRegisterMetaType<std::function<void()> >("std::function<void()>"); } 26ThreadBoundary::ThreadBoundary(): QObject() { qRegisterMetaType<std::function<void()> >("std::function<void()>"); }
27ThreadBoundary:: ~ThreadBoundary() {} 27ThreadBoundary:: ~ThreadBoundary()
28{
29}
30
28} 31}
29 32
diff --git a/examples/client/main.cpp b/examples/client/main.cpp
index 0a1a725..2aeb328 100644
--- a/examples/client/main.cpp
+++ b/examples/client/main.cpp
@@ -20,21 +20,24 @@
20#include <QApplication> 20#include <QApplication>
21#include <QCommandLineParser> 21#include <QCommandLineParser>
22#include <QCommandLineOption> 22#include <QCommandLineOption>
23#include <QElapsedTimer>
23 24
24#include "common/clientapi.h" 25#include "common/clientapi.h"
25#include "common/resource.h" 26#include "common/resource.h"
26#include "common/listmodelresult.h"
27#include "common/storage.h" 27#include "common/storage.h"
28#include "common/domain/event.h" 28#include "common/domain/event.h"
29#include "common/domain/folder.h"
29#include "common/resourceconfig.h" 30#include "common/resourceconfig.h"
31#include "common/log.h"
30#include "console.h" 32#include "console.h"
31 33
32#include <QWidget> 34#include <QWidget>
33#include <QListView> 35#include <QTreeView>
34#include <QVBoxLayout> 36#include <QVBoxLayout>
35#include <QLabel> 37#include <QLabel>
36#include <QPushButton> 38#include <QPushButton>
37#include <QItemSelectionModel> 39#include <QItemSelectionModel>
40#include <iostream>
38 41
39template <typename T> 42template <typename T>
40class View : public QWidget 43class View : public QWidget
@@ -43,8 +46,8 @@ public:
43 View(QAbstractItemModel *model) 46 View(QAbstractItemModel *model)
44 : QWidget() 47 : QWidget()
45 { 48 {
46 auto listView = new QListView(this); 49 auto modelView = new QTreeView(this);
47 listView->setModel(model); 50 modelView->setModel(model);
48 resize(1000, 1500); 51 resize(1000, 1500);
49 52
50 auto topLayout = new QVBoxLayout(this); 53 auto topLayout = new QVBoxLayout(this);
@@ -61,36 +64,59 @@ public:
61 QObject::connect(syncButton, &QPushButton::pressed, []() { 64 QObject::connect(syncButton, &QPushButton::pressed, []() {
62 Akonadi2::Query query; 65 Akonadi2::Query query;
63 query.resources << "org.kde.dummy.instance1"; 66 query.resources << "org.kde.dummy.instance1";
64 Akonadi2::Store::synchronize(query); 67 query.syncOnDemand = true;
68 Akonadi2::Store::synchronize(query).exec();
65 }); 69 });
66 70
67 auto removeButton = new QPushButton(this); 71 auto removeButton = new QPushButton(this);
68 removeButton->setText("Remove"); 72 removeButton->setText("Remove");
69 QObject::connect(removeButton, &QPushButton::pressed, [listView]() { 73 QObject::connect(removeButton, &QPushButton::pressed, [modelView]() {
70 for (auto index :listView->selectionModel()->selectedIndexes()) { 74 for (auto index : modelView->selectionModel()->selectedIndexes()) {
71 auto object = index.data(DomainObjectRole).value<typename T::Ptr>(); 75 auto object = index.data(Akonadi2::Store::DomainObjectRole).value<typename T::Ptr>();
72 Akonadi2::Store::remove(*object).exec(); 76 Akonadi2::Store::remove(*object).exec();
73 } 77 }
74 }); 78 });
75 79
76 topLayout->addWidget(titleLabel); 80 topLayout->addWidget(titleLabel);
77 topLayout->addWidget(syncButton); 81 topLayout->addWidget(syncButton);
78 topLayout->addWidget(listView, 10); 82 topLayout->addWidget(modelView, 10);
79 83
80 show(); 84 show();
81 } 85 }
82 86
83}; 87};
84 88
89
90class MyApplication : public QApplication
91{
92 QElapsedTimer t;
93public:
94 MyApplication(int& argc, char ** argv) : QApplication(argc, argv) { }
95 virtual ~MyApplication() { }
96
97 virtual bool notify(QObject* receiver, QEvent* event)
98 {
99 t.start();
100 bool ret = QApplication::notify(receiver, event);
101 if(t.elapsed() > 3)
102 qDebug("processing event type %d for object %s took %dms",
103 (int)event->type(), receiver->objectName().toLocal8Bit().data(),
104 (int)t.elapsed());
105 return ret;
106 }
107};
108
85int main(int argc, char *argv[]) 109int main(int argc, char *argv[])
86{ 110{
87 QApplication app(argc, argv); 111 MyApplication app(argc, argv);
88 112
89 QCommandLineParser cliOptions; 113 QCommandLineParser cliOptions;
90 cliOptions.addPositionalArgument(QObject::tr("[resource]"), 114 cliOptions.addPositionalArgument(QObject::tr("[resource]"),
91 QObject::tr("A resource to connect to")); 115 QObject::tr("A resource to connect to"));
92 cliOptions.addOption(QCommandLineOption("clear")); 116 cliOptions.addOption(QCommandLineOption("clear"));
93 cliOptions.addOption(QCommandLineOption("debuglevel")); 117 cliOptions.addOption(QCommandLineOption("debuglevel"));
118 cliOptions.addOption(QCommandLineOption("list"));
119 cliOptions.addOption(QCommandLineOption("count"));
94 cliOptions.addHelpOption(); 120 cliOptions.addHelpOption();
95 cliOptions.process(app); 121 cliOptions.process(app);
96 QStringList resources = cliOptions.positionalArguments(); 122 QStringList resources = cliOptions.positionalArguments();
@@ -122,10 +148,43 @@ int main(int argc, char *argv[])
122 } 148 }
123 query.syncOnDemand = false; 149 query.syncOnDemand = false;
124 query.processAll = false; 150 query.processAll = false;
151 query.requestedProperties << "name";
125 query.liveQuery = true; 152 query.liveQuery = true;
126 153
127 auto model = QSharedPointer<ListModelResult<Akonadi2::ApplicationDomain::Event::Ptr> >::create(Akonadi2::Store::load<Akonadi2::ApplicationDomain::Event>(query), QList<QByteArray>() << "summary" << "uid"); 154 QTime time;
128 auto view = QSharedPointer<View<Akonadi2::ApplicationDomain::Event> >::create(model.data()); 155 time.start();
129 156 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query);
130 return app.exec(); 157 qDebug() << "Loaded model in " << time.elapsed() << " ms";
158
159 if (cliOptions.isSet("list")) {
160 query.liveQuery = false;
161 qDebug() << "Listing";
162 QObject::connect(model.data(), &QAbstractItemModel::rowsInserted, [model](const QModelIndex &index, int start, int end) {
163 for (int i = start; i <= end; i++) {
164 std::cout << "\tRow " << model->rowCount() << ": " << model->data(model->index(i, 0, index)).toString().toStdString() << std::endl;
165 }
166 });
167 QObject::connect(model.data(), &QAbstractItemModel::dataChanged, [model, &app](const QModelIndex &, const QModelIndex &, const QVector<int> &roles) {
168 if (roles.contains(Akonadi2::Store::ChildrenFetchedRole)) {
169 app.quit();
170 }
171 });
172 if (!model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool()) {
173 return app.exec();
174 }
175 } else if (cliOptions.isSet("count")) {
176 query.liveQuery = false;
177 QObject::connect(model.data(), &QAbstractItemModel::dataChanged, [model, &app](const QModelIndex &, const QModelIndex &, const QVector<int> &roles) {
178 if (roles.contains(Akonadi2::Store::ChildrenFetchedRole)) {
179 std::cout << "\tCounted results " << model->rowCount(QModelIndex());
180 app.quit();
181 }
182 });
183 return app.exec();
184 } else {
185 query.liveQuery = true;
186 auto view = QSharedPointer<View<Akonadi2::ApplicationDomain::Folder> >::create(model.data());
187 return app.exec();
188 }
189 return 0;
131} 190}
diff --git a/examples/dummyresource/CMakeLists.txt b/examples/dummyresource/CMakeLists.txt
index e4b51dd..1e80f81 100644
--- a/examples/dummyresource/CMakeLists.txt
+++ b/examples/dummyresource/CMakeLists.txt
@@ -4,7 +4,7 @@ add_definitions(-DQT_PLUGIN)
4include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) 4include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
5 5
6 6
7add_library(${PROJECT_NAME} SHARED facade.cpp resourcefactory.cpp domainadaptor.cpp resourcefacade.cpp dummystore.cpp) 7add_library(${PROJECT_NAME} SHARED facade.cpp resourcefactory.cpp domainadaptor.cpp dummystore.cpp)
8generate_flatbuffers(${PROJECT_NAME} dummycalendar) 8generate_flatbuffers(${PROJECT_NAME} dummycalendar)
9qt5_use_modules(${PROJECT_NAME} Core Network) 9qt5_use_modules(${PROJECT_NAME} Core Network)
10target_link_libraries(${PROJECT_NAME} akonadi2common) 10target_link_libraries(${PROJECT_NAME} akonadi2common)
diff --git a/examples/dummyresource/domainadaptor.cpp b/examples/dummyresource/domainadaptor.cpp
index d08a783..74b170d 100644
--- a/examples/dummyresource/domainadaptor.cpp
+++ b/examples/dummyresource/domainadaptor.cpp
@@ -51,3 +51,9 @@ DummyMailAdaptorFactory::DummyMailAdaptorFactory()
51 51
52} 52}
53 53
54DummyFolderAdaptorFactory::DummyFolderAdaptorFactory()
55 : DomainTypeAdaptorFactory()
56{
57
58}
59
diff --git a/examples/dummyresource/domainadaptor.h b/examples/dummyresource/domainadaptor.h
index add3e8e..e5856f8 100644
--- a/examples/dummyresource/domainadaptor.h
+++ b/examples/dummyresource/domainadaptor.h
@@ -21,6 +21,8 @@
21#include "common/domainadaptor.h" 21#include "common/domainadaptor.h"
22#include "event_generated.h" 22#include "event_generated.h"
23#include "mail_generated.h" 23#include "mail_generated.h"
24#include "folder_generated.h"
25#include "dummy_generated.h"
24#include "dummycalendar_generated.h" 26#include "dummycalendar_generated.h"
25#include "entity_generated.h" 27#include "entity_generated.h"
26 28
@@ -31,10 +33,16 @@ public:
31 virtual ~DummyEventAdaptorFactory() {}; 33 virtual ~DummyEventAdaptorFactory() {};
32}; 34};
33 35
34//TODO replace the resource specific event class by a mail class or a dummy class if no resource type is required. 36class DummyMailAdaptorFactory : public DomainTypeAdaptorFactory<Akonadi2::ApplicationDomain::Mail, Akonadi2::ApplicationDomain::Buffer::Dummy, Akonadi2::ApplicationDomain::Buffer::DummyBuilder>
35class DummyMailAdaptorFactory : public DomainTypeAdaptorFactory<Akonadi2::ApplicationDomain::Mail, DummyCalendar::DummyEvent, DummyCalendar::DummyEventBuilder>
36{ 37{
37public: 38public:
38 DummyMailAdaptorFactory(); 39 DummyMailAdaptorFactory();
39 virtual ~DummyMailAdaptorFactory() {}; 40 virtual ~DummyMailAdaptorFactory() {};
40}; 41};
42
43class DummyFolderAdaptorFactory : public DomainTypeAdaptorFactory<Akonadi2::ApplicationDomain::Folder, Akonadi2::ApplicationDomain::Buffer::Dummy, Akonadi2::ApplicationDomain::Buffer::DummyBuilder>
44{
45public:
46 DummyFolderAdaptorFactory();
47 virtual ~DummyFolderAdaptorFactory() {};
48};
diff --git a/examples/dummyresource/dummystore.cpp b/examples/dummyresource/dummystore.cpp
index 0356ec0..458695f 100644
--- a/examples/dummyresource/dummystore.cpp
+++ b/examples/dummyresource/dummystore.cpp
@@ -36,6 +36,13 @@ static QMap<QString, QVariant> createMail(int i)
36 return mail; 36 return mail;
37} 37}
38 38
39static QMap<QString, QVariant> createFolder(int i)
40{
41 QMap<QString, QVariant> folder;
42 folder.insert("name", QString("folder%1").arg(i));
43 return folder;
44}
45
39QMap<QString, QMap<QString, QVariant> > populateEvents() 46QMap<QString, QMap<QString, QVariant> > populateEvents()
40{ 47{
41 QMap<QString, QMap<QString, QVariant>> content; 48 QMap<QString, QMap<QString, QVariant>> content;
@@ -54,8 +61,18 @@ QMap<QString, QMap<QString, QVariant> > populateMails()
54 return content; 61 return content;
55} 62}
56 63
64QMap<QString, QMap<QString, QVariant> > populateFolders()
65{
66 QMap<QString, QMap<QString, QVariant>> content;
67 for (int i = 0; i < 5; i++) {
68 content.insert(QString("key%1").arg(i), createFolder(i));
69 }
70 return content;
71}
72
57static QMap<QString, QMap<QString, QVariant> > s_eventSource = populateEvents(); 73static QMap<QString, QMap<QString, QVariant> > s_eventSource = populateEvents();
58static QMap<QString, QMap<QString, QVariant> > s_mailSource = populateMails(); 74static QMap<QString, QMap<QString, QVariant> > s_mailSource = populateMails();
75static QMap<QString, QMap<QString, QVariant> > s_folderSource = populateFolders();
59 76
60QMap<QString, QMap<QString, QVariant> > DummyStore::events() const 77QMap<QString, QMap<QString, QVariant> > DummyStore::events() const
61{ 78{
@@ -66,3 +83,8 @@ QMap<QString, QMap<QString, QVariant> > DummyStore::mails() const
66{ 83{
67 return s_mailSource; 84 return s_mailSource;
68} 85}
86
87QMap<QString, QMap<QString, QVariant> > DummyStore::folders() const
88{
89 return s_folderSource;
90}
diff --git a/examples/dummyresource/dummystore.h b/examples/dummyresource/dummystore.h
index 4ecd5ea..c730118 100644
--- a/examples/dummyresource/dummystore.h
+++ b/examples/dummyresource/dummystore.h
@@ -23,7 +23,6 @@
23class DummyStore 23class DummyStore
24{ 24{
25public: 25public:
26 //TODO proper singleton
27 static DummyStore &instance() 26 static DummyStore &instance()
28 { 27 {
29 static DummyStore instance; 28 static DummyStore instance;
@@ -32,4 +31,5 @@ public:
32 31
33 QMap<QString, QMap<QString, QVariant> > events() const; 32 QMap<QString, QMap<QString, QVariant> > events() const;
34 QMap<QString, QMap<QString, QVariant> > mails() const; 33 QMap<QString, QMap<QString, QVariant> > mails() const;
34 QMap<QString, QMap<QString, QVariant> > folders() const;
35}; 35};
diff --git a/examples/dummyresource/facade.cpp b/examples/dummyresource/facade.cpp
index 5a9d722..f337bdc 100644
--- a/examples/dummyresource/facade.cpp
+++ b/examples/dummyresource/facade.cpp
@@ -30,6 +30,7 @@ DummyResourceFacade::~DummyResourceFacade()
30{ 30{
31} 31}
32 32
33
33DummyResourceMailFacade::DummyResourceMailFacade(const QByteArray &instanceIdentifier) 34DummyResourceMailFacade::DummyResourceMailFacade(const QByteArray &instanceIdentifier)
34 : Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Mail>(instanceIdentifier, QSharedPointer<DummyMailAdaptorFactory>::create()) 35 : Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Mail>(instanceIdentifier, QSharedPointer<DummyMailAdaptorFactory>::create())
35{ 36{
@@ -39,25 +40,12 @@ DummyResourceMailFacade::~DummyResourceMailFacade()
39{ 40{
40} 41}
41 42
42static void addFolder(const QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Folder::Ptr> > &resultProvider, QByteArray uid, QString name, QString icon) 43
44DummyResourceFolderFacade::DummyResourceFolderFacade(const QByteArray &instanceIdentifier)
45 : Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Folder>(instanceIdentifier, QSharedPointer<DummyFolderAdaptorFactory>::create())
43{ 46{
44 auto folder = Akonadi2::ApplicationDomain::Folder::Ptr::create();
45 folder->setProperty("name", name);
46 folder->setProperty("uid", uid);
47 resultProvider->add(folder);
48} 47}
49 48
50KAsync::Job<void> load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Folder::Ptr> > &resultProvider) 49DummyResourceFolderFacade::~DummyResourceFolderFacade()
51{ 50{
52 //Dummy implementation for a fixed set of folders
53 addFolder(resultProvider, "inbox", "INBOX", "mail-folder-inbox");
54 addFolder(resultProvider, "sent", "Sent", "mail-folder-sent");
55 addFolder(resultProvider, "trash", "Trash", "user-trash");
56 addFolder(resultProvider, "drafts", "Drafts", "document-edit");
57 addFolder(resultProvider, "1", "dragons", "folder");
58 addFolder(resultProvider, "1", "super mega long tailed dragons", "folder");
59 resultProvider->initialResultSetComplete();
60 resultProvider->complete();
61 return KAsync::null<void>();
62} 51}
63
diff --git a/examples/dummyresource/facade.h b/examples/dummyresource/facade.h
index 948116b..b00e1d7 100644
--- a/examples/dummyresource/facade.h
+++ b/examples/dummyresource/facade.h
@@ -36,13 +36,9 @@ public:
36 virtual ~DummyResourceMailFacade(); 36 virtual ~DummyResourceMailFacade();
37}; 37};
38 38
39class DummyResourceFolderFacade : public Akonadi2::StoreFacade<Akonadi2::ApplicationDomain::Folder> 39class DummyResourceFolderFacade : public Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Folder>
40{ 40{
41public: 41public:
42 DummyResourceFolderFacade(const QByteArray &instanceIdentifier); 42 DummyResourceFolderFacade(const QByteArray &instanceIdentifier);
43 virtual ~DummyResourceFolderFacade(); 43 virtual ~DummyResourceFolderFacade();
44 virtual KAsync::Job<void> create(const Akonadi2::ApplicationDomain::Folder &domainObject) { return KAsync::null<void>(); };
45 virtual KAsync::Job<void> modify(const Akonadi2::ApplicationDomain::Folder &domainObject) { return KAsync::null<void>(); };
46 virtual KAsync::Job<void> remove(const Akonadi2::ApplicationDomain::Folder &domainObject) { return KAsync::null<void>(); };
47 virtual KAsync::Job<void> load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Folder::Ptr> > &resultProvider);
48}; 44};
diff --git a/examples/dummyresource/resourcefacade.cpp b/examples/dummyresource/resourcefacade.cpp
deleted file mode 100644
index df805e4..0000000
--- a/examples/dummyresource/resourcefacade.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "resourcefacade.h"
21
22#include <QSettings>
23#include <QStandardPaths>
24
25DummyResourceConfigFacade::DummyResourceConfigFacade()
26 : Akonadi2::StoreFacade<Akonadi2::ApplicationDomain::AkonadiResource>()
27{
28
29}
30
31DummyResourceConfigFacade::~DummyResourceConfigFacade()
32{
33
34}
35
36QSharedPointer<QSettings> DummyResourceConfigFacade::getSettings()
37{
38 //FIXME deal with resource instances
39 const QString instanceIdentifier = "dummyresource.instance1";
40 //FIXME Use config location
41 return QSharedPointer<QSettings>::create(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/akonadi2/" + "org.kde." + instanceIdentifier + "/settings.ini", QSettings::IniFormat);
42}
43
44KAsync::Job<void> DummyResourceConfigFacade::create(const Akonadi2::ApplicationDomain::AkonadiResource &domainObject)
45{
46 //TODO create resource instance
47 //This can be generalized in a base implementation
48 return KAsync::null<void>();
49}
50
51KAsync::Job<void> DummyResourceConfigFacade::modify(const Akonadi2::ApplicationDomain::AkonadiResource &domainObject)
52{
53 //modify configuration
54 //This part is likely resource specific, but could be partially generalized
55 return KAsync::start<void>([domainObject, this]() {
56 auto settings = getSettings();
57 //TODO Write properties to file
58 });
59}
60
61KAsync::Job<void> DummyResourceConfigFacade::remove(const Akonadi2::ApplicationDomain::AkonadiResource &domainObject)
62{
63 //TODO remove resource instance
64 //This can be generalized in a base implementation
65 return KAsync::null<void>();
66}
67
68KAsync::Job<void> DummyResourceConfigFacade::load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProvider<typename Akonadi2::ApplicationDomain::AkonadiResource::Ptr> > &resultProvider)
69{
70 //Read configuration and list all available instances.
71 //This includes runtime information about runing instances etc.
72 //Part of this is generic, and part is accessing the resource specific configuration.
73 //FIXME this currently does not support live queries (because we're not inheriting from GenericFacade)
74 //FIXME only read what was requested in the query?
75 return KAsync::start<void>([resultProvider, this]() {
76 auto settings = getSettings();
77 auto memoryAdaptor = QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create();
78 //TODO copy settings to adaptor
79 //
80 //TODO use correct instance identifier
81 //TODO key == instance identifier ?
82 resultProvider->add(QSharedPointer<Akonadi2::ApplicationDomain::AkonadiResource>::create("org.kde.dummy.instance1", "org.kde.dummy.config", 0, memoryAdaptor));
83 });
84}
diff --git a/examples/dummyresource/resourcefacade.h b/examples/dummyresource/resourcefacade.h
deleted file mode 100644
index 5a5f46b..0000000
--- a/examples/dummyresource/resourcefacade.h
+++ /dev/null
@@ -1,49 +0,0 @@
1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#pragma once
21
22#include <Async/Async>
23#include <common/domain/applicationdomaintype.h>
24#include <common/resultprovider.h>
25#include <common/facadeinterface.h>
26
27namespace Akonadi2 {
28 class Query;
29}
30
31class QSettings;
32
33class DummyResourceConfigFacade : public Akonadi2::StoreFacade<Akonadi2::ApplicationDomain::AkonadiResource>
34{
35public:
36 DummyResourceConfigFacade();
37 ~DummyResourceConfigFacade();
38 //Create an instance
39 KAsync::Job<void> create(const Akonadi2::ApplicationDomain::AkonadiResource &domainObject) Q_DECL_OVERRIDE;
40 //Modify configuration
41 KAsync::Job<void> modify(const Akonadi2::ApplicationDomain::AkonadiResource &domainObject) Q_DECL_OVERRIDE;
42 //Remove instance
43 KAsync::Job<void> remove(const Akonadi2::ApplicationDomain::AkonadiResource &domainObject) Q_DECL_OVERRIDE;
44 //Read configuration and available instances
45 KAsync::Job<void> load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProvider<typename Akonadi2::ApplicationDomain::AkonadiResource::Ptr> > &resultProvider) Q_DECL_OVERRIDE;
46
47private:
48 QSharedPointer<QSettings> getSettings();
49};
diff --git a/examples/dummyresource/resourcefactory.cpp b/examples/dummyresource/resourcefactory.cpp
index 397ca5f..e524c3f 100644
--- a/examples/dummyresource/resourcefactory.cpp
+++ b/examples/dummyresource/resourcefactory.cpp
@@ -40,6 +40,7 @@
40//This is the resources entity type, and not the domain type 40//This is the resources entity type, and not the domain type
41#define ENTITY_TYPE_EVENT "event" 41#define ENTITY_TYPE_EVENT "event"
42#define ENTITY_TYPE_MAIL "mail" 42#define ENTITY_TYPE_MAIL "mail"
43#define ENTITY_TYPE_FOLDER "folder"
43 44
44/** 45/**
45 * Index types: 46 * Index types:
@@ -51,7 +52,6 @@
51 * * range indexes like what date range an event affects. 52 * * range indexes like what date range an event affects.
52 * * group indexes like tree hierarchies as nested sets 53 * * group indexes like tree hierarchies as nested sets
53 */ 54 */
54template<typename DomainType>
55class IndexUpdater : public Akonadi2::Preprocessor { 55class IndexUpdater : public Akonadi2::Preprocessor {
56public: 56public:
57 IndexUpdater(const QByteArray &index, const QByteArray &type) 57 IndexUpdater(const QByteArray &index, const QByteArray &type)
@@ -63,21 +63,17 @@ public:
63 63
64 void newEntity(const QByteArray &uid, qint64 revision, const Akonadi2::ApplicationDomain::BufferAdaptor &newEntity, Akonadi2::Storage::Transaction &transaction) Q_DECL_OVERRIDE 64 void newEntity(const QByteArray &uid, qint64 revision, const Akonadi2::ApplicationDomain::BufferAdaptor &newEntity, Akonadi2::Storage::Transaction &transaction) Q_DECL_OVERRIDE
65 { 65 {
66 Akonadi2::ApplicationDomain::TypeImplementation<DomainType>::index(uid, newEntity, transaction);
67 add(newEntity.getProperty("remoteId"), uid, transaction); 66 add(newEntity.getProperty("remoteId"), uid, transaction);
68 } 67 }
69 68
70 void modifiedEntity(const QByteArray &uid, qint64 revision, const Akonadi2::ApplicationDomain::BufferAdaptor &oldEntity, const Akonadi2::ApplicationDomain::BufferAdaptor &newEntity, Akonadi2::Storage::Transaction &transaction) Q_DECL_OVERRIDE 69 void modifiedEntity(const QByteArray &uid, qint64 revision, const Akonadi2::ApplicationDomain::BufferAdaptor &oldEntity, const Akonadi2::ApplicationDomain::BufferAdaptor &newEntity, Akonadi2::Storage::Transaction &transaction) Q_DECL_OVERRIDE
71 { 70 {
72 Akonadi2::ApplicationDomain::TypeImplementation<DomainType>::removeIndex(uid, oldEntity, transaction);
73 Akonadi2::ApplicationDomain::TypeImplementation<DomainType>::index(uid, newEntity, transaction);
74 remove(oldEntity.getProperty("remoteId"), uid, transaction); 71 remove(oldEntity.getProperty("remoteId"), uid, transaction);
75 add(newEntity.getProperty("remoteId"), uid, transaction); 72 add(newEntity.getProperty("remoteId"), uid, transaction);
76 } 73 }
77 74
78 void deletedEntity(const QByteArray &uid, qint64 revision, const Akonadi2::ApplicationDomain::BufferAdaptor &oldEntity, Akonadi2::Storage::Transaction &transaction) Q_DECL_OVERRIDE 75 void deletedEntity(const QByteArray &uid, qint64 revision, const Akonadi2::ApplicationDomain::BufferAdaptor &oldEntity, Akonadi2::Storage::Transaction &transaction) Q_DECL_OVERRIDE
79 { 76 {
80 Akonadi2::ApplicationDomain::TypeImplementation<DomainType>::removeIndex(uid, oldEntity, transaction);
81 remove(oldEntity.getProperty("remoteId"), uid, transaction); 77 remove(oldEntity.getProperty("remoteId"), uid, transaction);
82 } 78 }
83 79
@@ -91,6 +87,7 @@ private:
91 87
92 void remove(const QVariant &value, const QByteArray &uid, Akonadi2::Storage::Transaction &transaction) 88 void remove(const QVariant &value, const QByteArray &uid, Akonadi2::Storage::Transaction &transaction)
93 { 89 {
90 //TODO hide notfound error
94 Index(mIndexIdentifier, transaction).remove(value.toByteArray(), uid); 91 Index(mIndexIdentifier, transaction).remove(value.toByteArray(), uid);
95 } 92 }
96 93
@@ -98,20 +95,49 @@ private:
98 QByteArray mBufferType; 95 QByteArray mBufferType;
99}; 96};
100 97
98template<typename DomainType>
99class DefaultIndexUpdater : public Akonadi2::Preprocessor {
100public:
101 void newEntity(const QByteArray &uid, qint64 revision, const Akonadi2::ApplicationDomain::BufferAdaptor &newEntity, Akonadi2::Storage::Transaction &transaction) Q_DECL_OVERRIDE
102 {
103 Akonadi2::ApplicationDomain::TypeImplementation<DomainType>::index(uid, newEntity, transaction);
104 }
105
106 void modifiedEntity(const QByteArray &uid, qint64 revision, const Akonadi2::ApplicationDomain::BufferAdaptor &oldEntity, const Akonadi2::ApplicationDomain::BufferAdaptor &newEntity, Akonadi2::Storage::Transaction &transaction) Q_DECL_OVERRIDE
107 {
108 Akonadi2::ApplicationDomain::TypeImplementation<DomainType>::removeIndex(uid, oldEntity, transaction);
109 Akonadi2::ApplicationDomain::TypeImplementation<DomainType>::index(uid, newEntity, transaction);
110 }
111
112 void deletedEntity(const QByteArray &uid, qint64 revision, const Akonadi2::ApplicationDomain::BufferAdaptor &oldEntity, Akonadi2::Storage::Transaction &transaction) Q_DECL_OVERRIDE
113 {
114 Akonadi2::ApplicationDomain::TypeImplementation<DomainType>::removeIndex(uid, oldEntity, transaction);
115 }
116};
117
101DummyResource::DummyResource(const QByteArray &instanceIdentifier, const QSharedPointer<Akonadi2::Pipeline> &pipeline) 118DummyResource::DummyResource(const QByteArray &instanceIdentifier, const QSharedPointer<Akonadi2::Pipeline> &pipeline)
102 : Akonadi2::GenericResource(instanceIdentifier, pipeline) 119 : Akonadi2::GenericResource(instanceIdentifier, pipeline)
103{ 120{
104 { 121 {
105 auto eventFactory = QSharedPointer<DummyEventAdaptorFactory>::create(); 122 QVector<Akonadi2::Preprocessor*> eventPreprocessors;
106 auto eventIndexer = new IndexUpdater<Akonadi2::ApplicationDomain::Event>("event.index.rid", ENTITY_TYPE_EVENT); 123 eventPreprocessors << new DefaultIndexUpdater<Akonadi2::ApplicationDomain::Event>;
107 mPipeline->setPreprocessors(ENTITY_TYPE_EVENT, QVector<Akonadi2::Preprocessor*>() << eventIndexer); 124 eventPreprocessors << new IndexUpdater("event.index.rid", ENTITY_TYPE_EVENT);
108 mPipeline->setAdaptorFactory(ENTITY_TYPE_EVENT, eventFactory); 125 mPipeline->setPreprocessors(ENTITY_TYPE_EVENT, eventPreprocessors);
126 mPipeline->setAdaptorFactory(ENTITY_TYPE_EVENT, QSharedPointer<DummyEventAdaptorFactory>::create());
127 }
128 {
129 QVector<Akonadi2::Preprocessor*> mailPreprocessors;
130 mailPreprocessors << new DefaultIndexUpdater<Akonadi2::ApplicationDomain::Mail>;
131 mailPreprocessors << new IndexUpdater("mail.index.rid", ENTITY_TYPE_MAIL);
132 mPipeline->setPreprocessors(ENTITY_TYPE_MAIL, mailPreprocessors);
133 mPipeline->setAdaptorFactory(ENTITY_TYPE_MAIL, QSharedPointer<DummyMailAdaptorFactory>::create());
109 } 134 }
110 { 135 {
111 auto mailFactory = QSharedPointer<DummyMailAdaptorFactory>::create(); 136 QVector<Akonadi2::Preprocessor*> folderPreprocessors;
112 auto mailIndexer = new IndexUpdater<Akonadi2::ApplicationDomain::Mail>("mail.index.rid", ENTITY_TYPE_MAIL); 137 folderPreprocessors << new DefaultIndexUpdater<Akonadi2::ApplicationDomain::Folder>;
113 mPipeline->setPreprocessors(ENTITY_TYPE_MAIL, QVector<Akonadi2::Preprocessor*>() << mailIndexer); 138 folderPreprocessors << new IndexUpdater("folder.index.rid", ENTITY_TYPE_FOLDER);
114 mPipeline->setAdaptorFactory(ENTITY_TYPE_MAIL, mailFactory); 139 mPipeline->setPreprocessors(ENTITY_TYPE_FOLDER, folderPreprocessors);
140 mPipeline->setAdaptorFactory(ENTITY_TYPE_FOLDER, QSharedPointer<DummyFolderAdaptorFactory>::create());
115 } 141 }
116} 142}
117 143
@@ -156,6 +182,27 @@ void DummyResource::createMail(const QByteArray &ridBuffer, const QMap<QString,
156 Akonadi2::EntityBuffer::assembleEntityBuffer(entityFbb, 0, 0, 0, 0, m_fbb.GetBufferPointer(), m_fbb.GetSize()); 182 Akonadi2::EntityBuffer::assembleEntityBuffer(entityFbb, 0, 0, 0, 0, m_fbb.GetBufferPointer(), m_fbb.GetSize());
157} 183}
158 184
185void DummyResource::createFolder(const QByteArray &ridBuffer, const QMap<QString, QVariant> &data, flatbuffers::FlatBufferBuilder &entityFbb)
186{
187 //Map the source format to the buffer format (which happens to be an exact copy here)
188 auto name = m_fbb.CreateString(data.value("name").toString().toStdString());
189 flatbuffers::Offset<flatbuffers::String> parent;
190 bool hasParent = false;
191 if (!data.value("parent").toString().isEmpty()) {
192 hasParent = true;
193 parent = m_fbb.CreateString(data.value("parent").toString().toStdString());
194 }
195
196 auto builder = Akonadi2::ApplicationDomain::Buffer::FolderBuilder(m_fbb);
197 builder.add_name(name);
198 if (hasParent) {
199 builder.add_parent(parent);
200 }
201 auto buffer = builder.Finish();
202 Akonadi2::ApplicationDomain::Buffer::FinishFolderBuffer(m_fbb, buffer);
203 Akonadi2::EntityBuffer::assembleEntityBuffer(entityFbb, 0, 0, 0, 0, m_fbb.GetBufferPointer(), m_fbb.GetSize());
204}
205
159void DummyResource::synchronize(const QString &bufferType, const QMap<QString, QMap<QString, QVariant> > &data, Akonadi2::Storage::Transaction &transaction, std::function<void(const QByteArray &ridBuffer, const QMap<QString, QVariant> &data, flatbuffers::FlatBufferBuilder &entityFbb)> createEntity) 206void DummyResource::synchronize(const QString &bufferType, const QMap<QString, QMap<QString, QVariant> > &data, Akonadi2::Storage::Transaction &transaction, std::function<void(const QByteArray &ridBuffer, const QMap<QString, QVariant> &data, flatbuffers::FlatBufferBuilder &entityFbb)> createEntity)
160{ 207{
161 Index uidIndex("index.uid", transaction); 208 Index uidIndex("index.uid", transaction);
@@ -202,6 +249,9 @@ KAsync::Job<void> DummyResource::synchronizeWithSource()
202 synchronize(ENTITY_TYPE_MAIL, DummyStore::instance().mails(), transaction, [this](const QByteArray &ridBuffer, const QMap<QString, QVariant> &data, flatbuffers::FlatBufferBuilder &entityFbb) { 249 synchronize(ENTITY_TYPE_MAIL, DummyStore::instance().mails(), transaction, [this](const QByteArray &ridBuffer, const QMap<QString, QVariant> &data, flatbuffers::FlatBufferBuilder &entityFbb) {
203 createMail(ridBuffer, data, entityFbb); 250 createMail(ridBuffer, data, entityFbb);
204 }); 251 });
252 synchronize(ENTITY_TYPE_FOLDER, DummyStore::instance().folders(), transaction, [this](const QByteArray &ridBuffer, const QMap<QString, QVariant> &data, flatbuffers::FlatBufferBuilder &entityFbb) {
253 createFolder(ridBuffer, data, entityFbb);
254 });
205 255
206 f.setFinished(); 256 f.setFinished();
207 }); 257 });
@@ -228,5 +278,6 @@ void DummyResourceFactory::registerFacades(Akonadi2::FacadeFactory &factory)
228{ 278{
229 factory.registerFacade<Akonadi2::ApplicationDomain::Event, DummyResourceFacade>(PLUGIN_NAME); 279 factory.registerFacade<Akonadi2::ApplicationDomain::Event, DummyResourceFacade>(PLUGIN_NAME);
230 factory.registerFacade<Akonadi2::ApplicationDomain::Mail, DummyResourceMailFacade>(PLUGIN_NAME); 280 factory.registerFacade<Akonadi2::ApplicationDomain::Mail, DummyResourceMailFacade>(PLUGIN_NAME);
281 factory.registerFacade<Akonadi2::ApplicationDomain::Folder, DummyResourceFolderFacade>(PLUGIN_NAME);
231} 282}
232 283
diff --git a/examples/dummyresource/resourcefactory.h b/examples/dummyresource/resourcefactory.h
index 196d29a..67681ae 100644
--- a/examples/dummyresource/resourcefactory.h
+++ b/examples/dummyresource/resourcefactory.h
@@ -38,6 +38,7 @@ public:
38private: 38private:
39 void createEvent(const QByteArray &rid, const QMap<QString, QVariant> &data, flatbuffers::FlatBufferBuilder &entityFbb); 39 void createEvent(const QByteArray &rid, const QMap<QString, QVariant> &data, flatbuffers::FlatBufferBuilder &entityFbb);
40 void createMail(const QByteArray &rid, const QMap<QString, QVariant> &data, flatbuffers::FlatBufferBuilder &entityFbb); 40 void createMail(const QByteArray &rid, const QMap<QString, QVariant> &data, flatbuffers::FlatBufferBuilder &entityFbb);
41 void createFolder(const QByteArray &rid, const QMap<QString, QVariant> &data, flatbuffers::FlatBufferBuilder &entityFbb);
41 void synchronize(const QString &bufferType, const QMap<QString, QMap<QString, QVariant> > &data, Akonadi2::Storage::Transaction &transaction, std::function<void(const QByteArray &ridBuffer, const QMap<QString, QVariant> &data, flatbuffers::FlatBufferBuilder &entityFbb)> createEntity); 42 void synchronize(const QString &bufferType, const QMap<QString, QMap<QString, QVariant> > &data, Akonadi2::Storage::Transaction &transaction, std::function<void(const QByteArray &ridBuffer, const QMap<QString, QVariant> &data, flatbuffers::FlatBufferBuilder &entityFbb)> createEntity);
42}; 43};
43 44
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 5629cdb..11fe415 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -30,8 +30,8 @@ endmacro(auto_tests)
30manual_tests ( 30manual_tests (
31 storagebenchmark 31 storagebenchmark
32 dummyresourcebenchmark 32 dummyresourcebenchmark
33 genericresourcebenchmark 33# genericresourcebenchmark
34 genericfacadebenchmark 34# genericfacadebenchmark
35) 35)
36 36
37auto_tests ( 37auto_tests (
@@ -41,12 +41,14 @@ auto_tests (
41 domainadaptortest 41 domainadaptortest
42 messagequeuetest 42 messagequeuetest
43 indextest 43 indextest
44 genericresourcetest 44 # genericresourcetest
45 genericfacadetest 45 # genericfacadetest
46 resourcecommunicationtest 46 resourcecommunicationtest
47 pipelinetest 47 pipelinetest
48 querytest
48) 49)
49 50
50target_link_libraries(dummyresourcetest akonadi2_resource_dummy) 51target_link_libraries(dummyresourcetest akonadi2_resource_dummy)
51target_link_libraries(dummyresourcebenchmark akonadi2_resource_dummy) 52target_link_libraries(dummyresourcebenchmark akonadi2_resource_dummy)
53target_link_libraries(querytest akonadi2_resource_dummy)
52 54
diff --git a/tests/clientapitest.cpp b/tests/clientapitest.cpp
index 665d29b..8f956ab 100644
--- a/tests/clientapitest.cpp
+++ b/tests/clientapitest.cpp
@@ -4,30 +4,60 @@
4 4
5#include "clientapi.h" 5#include "clientapi.h"
6#include "facade.h" 6#include "facade.h"
7#include "synclistresult.h"
8#include "resourceconfig.h" 7#include "resourceconfig.h"
8#include "modelresult.h"
9#include "resultprovider.h"
10#include "facadefactory.h"
9 11
10class DummyResourceFacade : public Akonadi2::StoreFacade<Akonadi2::ApplicationDomain::Event> 12template <typename T>
13class DummyResourceFacade : public Akonadi2::StoreFacade<T>
11{ 14{
12public: 15public:
16 static std::shared_ptr<DummyResourceFacade<T> > registerFacade()
17 {
18 auto facade = std::make_shared<DummyResourceFacade<T> >();
19 Akonadi2::FacadeFactory::instance().registerFacade<T, DummyResourceFacade<T> >("dummyresource",
20 [facade](const QByteArray &instanceIdentifier) {
21 return facade;
22 }
23 );
24 return facade;
25 }
13 ~DummyResourceFacade(){}; 26 ~DummyResourceFacade(){};
14 KAsync::Job<void> create(const Akonadi2::ApplicationDomain::Event &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); }; 27 KAsync::Job<void> create(const T &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); };
15 KAsync::Job<void> modify(const Akonadi2::ApplicationDomain::Event &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); }; 28 KAsync::Job<void> modify(const T &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); };
16 KAsync::Job<void> remove(const Akonadi2::ApplicationDomain::Event &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); }; 29 KAsync::Job<void> remove(const T &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); };
17 KAsync::Job<void> load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Event::Ptr> > &resultProvider) Q_DECL_OVERRIDE 30 QPair<KAsync::Job<void>, typename Akonadi2::ResultEmitter<typename T::Ptr>::Ptr > load(const Akonadi2::Query &query) Q_DECL_OVERRIDE
18 { 31 {
19 capturedResultProvider = resultProvider; 32 auto resultProvider = new Akonadi2::ResultProvider<typename T::Ptr>();
20 return KAsync::start<void>([this, resultProvider, query]() { 33 resultProvider->onDone([resultProvider]() {
34 Trace() << "Result provider is done";
35 delete resultProvider;
36 });
37 //We have to do it this way, otherwise we're not setting the fetcher right
38 auto emitter = resultProvider->emitter();
39
40 resultProvider->setFetcher([query, resultProvider, this](const typename T::Ptr &parent) {
41 Trace() << "Running the fetcher";
21 for (const auto &res : results) { 42 for (const auto &res : results) {
22 resultProvider->add(res); 43 qDebug() << "Parent filter " << query.propertyFilter.value("parent").toByteArray() << res->identifier();
44 if (!query.propertyFilter.contains("parent") || query.propertyFilter.value("parent").toByteArray() == res->getProperty("parent").toByteArray()) {
45 resultProvider->add(res);
46 }
23 } 47 }
48 resultProvider->initialResultSetComplete(parent);
49 });
50 auto job = KAsync::start<void>([query, resultProvider]() {
24 }); 51 });
52 mResultProvider = resultProvider;
53 return qMakePair(job, emitter);
25 } 54 }
26 55
27 QList<Akonadi2::ApplicationDomain::Event::Ptr> results; 56 QList<typename T::Ptr> results;
28 QWeakPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Event::Ptr> > capturedResultProvider; 57 Akonadi2::ResultProviderInterface<typename T::Ptr> *mResultProvider;
29}; 58};
30 59
60
31/** 61/**
32 * Test of the client api implementation. 62 * Test of the client api implementation.
33 * 63 *
@@ -38,56 +68,26 @@ class ClientAPITest : public QObject
38 Q_OBJECT 68 Q_OBJECT
39private Q_SLOTS: 69private Q_SLOTS:
40 70
41 static std::shared_ptr<DummyResourceFacade> registerDummyFacade()
42 {
43 auto facade = std::make_shared<DummyResourceFacade>();
44 Akonadi2::FacadeFactory::instance().registerFacade<Akonadi2::ApplicationDomain::Event, DummyResourceFacade>("dummyresource",
45 [facade](const QByteArray &instanceIdentifier) {
46 return facade;
47 }
48 );
49 return facade;
50 }
51
52 void initTestCase() 71 void initTestCase()
53 { 72 {
54 Akonadi2::FacadeFactory::instance().resetFactory(); 73 Akonadi2::FacadeFactory::instance().resetFactory();
55 ResourceConfig::clear(); 74 ResourceConfig::clear();
75 Akonadi2::Log::setDebugOutputLevel(Akonadi2::Log::Trace);
56 } 76 }
57 77
58 void testLoad() 78 void testLoad()
59 { 79 {
60 auto facade = registerDummyFacade(); 80 auto facade = DummyResourceFacade<Akonadi2::ApplicationDomain::Event>::registerFacade();
61 facade->results << QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("resource", "id", 0, QSharedPointer<Akonadi2::ApplicationDomain::BufferAdaptor>()); 81 facade->results << QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("resource", "id", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
62 ResourceConfig::addResource("dummyresource.instance1", "dummyresource"); 82 ResourceConfig::addResource("dummyresource.instance1", "dummyresource");
63 83
64 Akonadi2::Query query; 84 Akonadi2::Query query;
65 query.resources << "dummyresource.instance1"; 85 query.resources << "dummyresource.instance1";
66 query.liveQuery = false; 86 query.liveQuery = false;
67 87
68 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(Akonadi2::Store::load<Akonadi2::ApplicationDomain::Event>(query)); 88 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query);
69 result.exec(); 89 QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool());
70 QCOMPARE(result.size(), 1); 90 QCOMPARE(model->rowCount(QModelIndex()), 1);
71 }
72
73 //The query provider is supposed to delete itself
74 void testQueryLifetime()
75 {
76 auto facade = registerDummyFacade();
77 facade->results << QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("resource", "id", 0, QSharedPointer<Akonadi2::ApplicationDomain::BufferAdaptor>());
78 ResourceConfig::addResource("dummyresource.instance1", "dummyresource");
79
80 Akonadi2::Query query;
81 query.resources << "dummyresource.instance1";
82 query.liveQuery = true;
83
84 {
85 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(Akonadi2::Store::load<Akonadi2::ApplicationDomain::Event>(query));
86 result.exec();
87 QCOMPARE(result.size(), 1);
88 }
89 //It's running in a separate thread, so we have to wait for a moment until the query provider deletes itself.
90 QTRY_VERIFY(!facade->capturedResultProvider);
91 } 91 }
92 92
93 //TODO: This test doesn't belong to this testsuite 93 //TODO: This test doesn't belong to this testsuite
@@ -104,21 +104,133 @@ private Q_SLOTS:
104 { 104 {
105 Akonadi2::Query query; 105 Akonadi2::Query query;
106 query.propertyFilter.insert("type", "dummyresource"); 106 query.propertyFilter.insert("type", "dummyresource");
107 async::SyncListResult<Akonadi2::ApplicationDomain::AkonadiResource::Ptr> result(Akonadi2::Store::load<Akonadi2::ApplicationDomain::AkonadiResource>(query)); 107 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::AkonadiResource>(query);
108 result.exec(); 108 QTRY_COMPARE(model->rowCount(QModelIndex()), 1);
109 QCOMPARE(result.size(), 1);
110 } 109 }
111 110
112 Akonadi2::Store::remove(res).exec().waitForFinished(); 111 Akonadi2::Store::remove(res).exec().waitForFinished();
113 { 112 {
114 Akonadi2::Query query; 113 Akonadi2::Query query;
115 query.propertyFilter.insert("type", "dummyresource"); 114 query.propertyFilter.insert("type", "dummyresource");
116 async::SyncListResult<Akonadi2::ApplicationDomain::AkonadiResource::Ptr> result(Akonadi2::Store::load<Akonadi2::ApplicationDomain::AkonadiResource>(query)); 115 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::AkonadiResource>(query);
117 result.exec(); 116 QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool());
118 QCOMPARE(result.size(), 0); 117 QCOMPARE(model->rowCount(QModelIndex()), 0);
119 } 118 }
120 } 119 }
121 120
121 void testModelSingle()
122 {
123 auto facade = DummyResourceFacade<Akonadi2::ApplicationDomain::Folder>::registerFacade();
124 facade->results << QSharedPointer<Akonadi2::ApplicationDomain::Folder>::create("resource", "id", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
125 ResourceConfig::addResource("dummyresource.instance1", "dummyresource");
126
127 Akonadi2::Query query;
128 query.resources << "dummyresource.instance1";
129 query.liveQuery = false;
130
131 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query);
132 QTRY_COMPARE(model->rowCount(), 1);
133 }
134
135 void testModelNested()
136 {
137 auto facade = DummyResourceFacade<Akonadi2::ApplicationDomain::Folder>::registerFacade();
138 auto folder = QSharedPointer<Akonadi2::ApplicationDomain::Folder>::create("resource", "id", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
139 auto subfolder = QSharedPointer<Akonadi2::ApplicationDomain::Folder>::create("resource", "subId", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
140 subfolder->setProperty("parent", "id");
141 facade->results << folder << subfolder;
142 ResourceConfig::addResource("dummyresource.instance1", "dummyresource");
143
144 //Test
145 Akonadi2::Query query;
146 query.resources << "dummyresource.instance1";
147 query.liveQuery = false;
148 query.parentProperty = "parent";
149
150 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query);
151 QTRY_VERIFY(model->data(QModelIndex(), Akonadi2::Store::ChildrenFetchedRole).toBool());
152 QCOMPARE(model->rowCount(), 1);
153 model->fetchMore(model->index(0, 0));
154 QTRY_VERIFY(model->data(model->index(0, 0), Akonadi2::Store::ChildrenFetchedRole).toBool());
155 QCOMPARE(model->rowCount(model->index(0, 0)), 1);
156 }
157
158 void testModelSignals()
159 {
160 auto facade = DummyResourceFacade<Akonadi2::ApplicationDomain::Folder>::registerFacade();
161 auto folder = QSharedPointer<Akonadi2::ApplicationDomain::Folder>::create("resource", "id", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
162 auto subfolder = QSharedPointer<Akonadi2::ApplicationDomain::Folder>::create("resource", "subId", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
163 subfolder->setProperty("parent", "id");
164 facade->results << folder << subfolder;
165 ResourceConfig::addResource("dummyresource.instance1", "dummyresource");
166
167 //Test
168 Akonadi2::Query query;
169 query.resources << "dummyresource.instance1";
170 query.liveQuery = false;
171 query.parentProperty = "parent";
172
173 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query);
174 QSignalSpy spy(model.data(), SIGNAL(rowsInserted(const QModelIndex &, int, int)));
175 QVERIFY(spy.isValid());
176 model->fetchMore(model->index(0, 0));
177 QTRY_VERIFY(spy.count() >= 1);
178 }
179
180 void testModelNestedLive()
181 {
182 auto facade = DummyResourceFacade<Akonadi2::ApplicationDomain::Folder>::registerFacade();
183 auto folder = QSharedPointer<Akonadi2::ApplicationDomain::Folder>::create("resource", "id", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
184 auto subfolder = QSharedPointer<Akonadi2::ApplicationDomain::Folder>::create("resource", "subId", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
185 subfolder->setProperty("parent", "id");
186 facade->results << folder << subfolder;
187 ResourceConfig::addResource("dummyresource.instance1", "dummyresource");
188
189 //Test
190 Akonadi2::Query query;
191 query.resources << "dummyresource.instance1";
192 query.liveQuery = true;
193 query.parentProperty = "parent";
194
195 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query);
196 QTRY_COMPARE(model->rowCount(), 1);
197 model->fetchMore(model->index(0, 0));
198 QTRY_COMPARE(model->rowCount(model->index(0, 0)), 1);
199
200 auto resultProvider = facade->mResultProvider;
201
202 //Test new toplevel folder
203 {
204 QSignalSpy rowsInsertedSpy(model.data(), SIGNAL(rowsInserted(const QModelIndex &, int, int)));
205 auto folder2 = QSharedPointer<Akonadi2::ApplicationDomain::Folder>::create("resource", "id2", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
206 resultProvider->add(folder2);
207 QTRY_COMPARE(model->rowCount(), 2);
208 QTRY_COMPARE(rowsInsertedSpy.count(), 1);
209 QCOMPARE(rowsInsertedSpy.at(0).at(0).value<QModelIndex>(), QModelIndex());
210 }
211
212 //Test changed name
213 {
214 QSignalSpy dataChanged(model.data(), SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &, const QVector<int> &)));
215 folder->setProperty("subject", "modifiedSubject");
216 resultProvider->modify(folder);
217 QTRY_COMPARE(model->rowCount(), 2);
218 QTRY_COMPARE(dataChanged.count(), 1);
219 }
220
221 //Test removal
222 {
223 QSignalSpy rowsRemovedSpy(model.data(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)));
224 folder->setProperty("subject", "modifiedSubject");
225 resultProvider->remove(subfolder);
226 QTRY_COMPARE(model->rowCount(model->index(0, 0)), 0);
227 QTRY_COMPARE(rowsRemovedSpy.count(), 1);
228 }
229
230 //TODO: A modification can also be a move
231 }
232
233
122}; 234};
123 235
124QTEST_MAIN(ClientAPITest) 236QTEST_MAIN(ClientAPITest)
diff --git a/tests/dummyresourcebenchmark.cpp b/tests/dummyresourcebenchmark.cpp
index 242ac76..6eaf065 100644
--- a/tests/dummyresourcebenchmark.cpp
+++ b/tests/dummyresourcebenchmark.cpp
@@ -7,9 +7,9 @@
7#include "clientapi.h" 7#include "clientapi.h"
8#include "commands.h" 8#include "commands.h"
9#include "entitybuffer.h" 9#include "entitybuffer.h"
10#include "synclistresult.h"
11#include "pipeline.h" 10#include "pipeline.h"
12#include "log.h" 11#include "log.h"
12#include "resourceconfig.h"
13 13
14#include "event_generated.h" 14#include "event_generated.h"
15#include "entity_generated.h" 15#include "entity_generated.h"
@@ -24,18 +24,20 @@
24class DummyResourceBenchmark : public QObject 24class DummyResourceBenchmark : public QObject
25{ 25{
26 Q_OBJECT 26 Q_OBJECT
27private:
28 int num;
27private Q_SLOTS: 29private Q_SLOTS:
28 void initTestCase() 30 void initTestCase()
29 { 31 {
30 Akonadi2::Log::setDebugOutputLevel(Akonadi2::Log::Warning); 32 Akonadi2::Log::setDebugOutputLevel(Akonadi2::Log::Warning);
31 auto factory = Akonadi2::ResourceFactory::load("org.kde.dummy"); 33 auto factory = Akonadi2::ResourceFactory::load("org.kde.dummy");
32 QVERIFY(factory); 34 QVERIFY(factory);
33 DummyResource::removeFromDisk("org.kde.dummy.instance1"); 35 ResourceConfig::addResource("org.kde.dummy.instance1", "org.kde.dummy");
36 num = 5000;
34 } 37 }
35 38
36 void cleanup() 39 void cleanup()
37 { 40 {
38 DummyResource::removeFromDisk("org.kde.dummy.instance1");
39 } 41 }
40 42
41 static KAsync::Job<void> waitForCompletion(QList<KAsync::Future<void> > &futures) 43 static KAsync::Job<void> waitForCompletion(QList<KAsync::Future<void> > &futures)
@@ -68,11 +70,12 @@ private Q_SLOTS:
68 }); 70 });
69 } 71 }
70 72
71 void testWriteToFacadeAndQueryByUid() 73 void testWriteToFacade()
72 { 74 {
75 DummyResource::removeFromDisk("org.kde.dummy.instance1");
76
73 QTime time; 77 QTime time;
74 time.start(); 78 time.start();
75 int num = 100;
76 QList<KAsync::Future<void> > waitCondition; 79 QList<KAsync::Future<void> > waitCondition;
77 for (int i = 0; i < num; i++) { 80 for (int i = 0; i < num; i++) {
78 Akonadi2::ApplicationDomain::Event event("org.kde.dummy.instance1"); 81 Akonadi2::ApplicationDomain::Event event("org.kde.dummy.instance1");
@@ -90,13 +93,17 @@ private Q_SLOTS:
90 query.resources << "org.kde.dummy.instance1"; 93 query.resources << "org.kde.dummy.instance1";
91 query.syncOnDemand = false; 94 query.syncOnDemand = false;
92 query.processAll = true; 95 query.processAll = true;
93 96 Akonadi2::Store::synchronize(query).exec().waitForFinished();
94 query.propertyFilter.insert("uid", "nonexistantuid");
95 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(Akonadi2::Store::load<Akonadi2::ApplicationDomain::Event>(query));
96 result.exec();
97 } 97 }
98 auto allProcessedTime = time.elapsed(); 98 auto allProcessedTime = time.elapsed();
99 qDebug() << "Append to messagequeue " << appendTime;
100 qDebug() << "All processed: " << allProcessedTime << "/sec " << num*1000/allProcessedTime;
101 }
99 102
103 void testQueryByUid()
104 {
105 QTime time;
106 time.start();
100 //Measure query 107 //Measure query
101 { 108 {
102 time.start(); 109 time.start();
@@ -106,20 +113,17 @@ private Q_SLOTS:
106 query.processAll = false; 113 query.processAll = false;
107 114
108 query.propertyFilter.insert("uid", "testuid"); 115 query.propertyFilter.insert("uid", "testuid");
109 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(Akonadi2::Store::load<Akonadi2::ApplicationDomain::Event>(query)); 116 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query);
110 result.exec(); 117 QTRY_COMPARE(model->rowCount(QModelIndex()), num);
111 QCOMPARE(result.size(), num);
112 } 118 }
113 qDebug() << "Append to messagequeue " << appendTime;
114 qDebug() << "All processed: " << allProcessedTime << "/sec " << num*1000/allProcessedTime;
115 qDebug() << "Query Time: " << time.elapsed() << "/sec " << num*1000/time.elapsed(); 119 qDebug() << "Query Time: " << time.elapsed() << "/sec " << num*1000/time.elapsed();
116 } 120 }
117 121
118 void testWriteInProcess() 122 void testWriteInProcess()
119 { 123 {
124 DummyResource::removeFromDisk("org.kde.dummy.instance1");
120 QTime time; 125 QTime time;
121 time.start(); 126 time.start();
122 int num = 100;
123 127
124 auto pipeline = QSharedPointer<Akonadi2::Pipeline>::create("org.kde.dummy.instance1"); 128 auto pipeline = QSharedPointer<Akonadi2::Pipeline>::create("org.kde.dummy.instance1");
125 DummyResource resource("org.kde.dummy.instance1", pipeline); 129 DummyResource resource("org.kde.dummy.instance1", pipeline);
@@ -191,6 +195,12 @@ private Q_SLOTS:
191 Akonadi2::Commands::FinishCreateEntityBuffer(fbb, location); 195 Akonadi2::Commands::FinishCreateEntityBuffer(fbb, location);
192 } 196 }
193 } 197 }
198
199 //This allows to run individual parts without doing a cleanup, but still cleaning up normally
200 void testCleanupForCompleteTest()
201 {
202 DummyResource::removeFromDisk("org.kde.dummy.instance1");
203 }
194}; 204};
195 205
196QTEST_MAIN(DummyResourceBenchmark) 206QTEST_MAIN(DummyResourceBenchmark)
diff --git a/tests/dummyresourcetest.cpp b/tests/dummyresourcetest.cpp
index d027266..20c725f 100644
--- a/tests/dummyresourcetest.cpp
+++ b/tests/dummyresourcetest.cpp
@@ -4,10 +4,10 @@
4 4
5#include "dummyresource/resourcefactory.h" 5#include "dummyresource/resourcefactory.h"
6#include "clientapi.h" 6#include "clientapi.h"
7#include "synclistresult.h"
8#include "commands.h" 7#include "commands.h"
9#include "entitybuffer.h" 8#include "entitybuffer.h"
10#include "resourceconfig.h" 9#include "resourceconfig.h"
10#include "modelresult.h"
11#include "pipeline.h" 11#include "pipeline.h"
12#include "log.h" 12#include "log.h"
13 13
@@ -64,11 +64,13 @@ private Q_SLOTS:
64 query.syncOnDemand = false; 64 query.syncOnDemand = false;
65 query.processAll = true; 65 query.processAll = true;
66 66
67 //Ensure all local data is processed
68 Akonadi2::Store::synchronize(query).exec().waitForFinished();
69
67 query.propertyFilter.insert("uid", "testuid"); 70 query.propertyFilter.insert("uid", "testuid");
68 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(Akonadi2::Store::load<Akonadi2::ApplicationDomain::Event>(query)); 71 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query);
69 result.exec(); 72 QTRY_COMPARE(model->rowCount(QModelIndex()), 1);
70 QCOMPARE(result.size(), 1); 73 auto value = model->index(0, 0, QModelIndex()).data(Akonadi2::Store::DomainObjectRole).value<Akonadi2::ApplicationDomain::Event::Ptr>();
71 auto value = result.first();
72 QCOMPARE(value->getProperty("uid").toByteArray(), QByteArray("testuid")); 74 QCOMPARE(value->getProperty("uid").toByteArray(), QByteArray("testuid"));
73 } 75 }
74 76
@@ -88,11 +90,15 @@ private Q_SLOTS:
88 query.syncOnDemand = false; 90 query.syncOnDemand = false;
89 query.processAll = true; 91 query.processAll = true;
90 92
93 //Ensure all local data is processed
94 Akonadi2::Store::synchronize(query).exec().waitForFinished();
95
91 query.propertyFilter.insert("uid", "testuid"); 96 query.propertyFilter.insert("uid", "testuid");
92 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(Akonadi2::Store::load<Akonadi2::ApplicationDomain::Event>(query)); 97
93 result.exec(); 98 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query);
94 QCOMPARE(result.size(), 1); 99 QTRY_COMPARE(model->rowCount(QModelIndex()), 1);
95 auto value = result.first(); 100 auto value = model->index(0, 0, QModelIndex()).data(Akonadi2::Store::DomainObjectRole).value<Akonadi2::ApplicationDomain::Event::Ptr>();
101
96 qDebug() << value->getProperty("uid").toByteArray(); 102 qDebug() << value->getProperty("uid").toByteArray();
97 QCOMPARE(value->getProperty("uid").toByteArray(), QByteArray("testuid")); 103 QCOMPARE(value->getProperty("uid").toByteArray(), QByteArray("testuid"));
98 } 104 }
@@ -114,11 +120,15 @@ private Q_SLOTS:
114 query.syncOnDemand = false; 120 query.syncOnDemand = false;
115 query.processAll = true; 121 query.processAll = true;
116 122
123 //Ensure all local data is processed
124 Akonadi2::Store::synchronize(query).exec().waitForFinished();
125
117 query.propertyFilter.insert("summary", "summaryValue2"); 126 query.propertyFilter.insert("summary", "summaryValue2");
118 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(Akonadi2::Store::load<Akonadi2::ApplicationDomain::Event>(query)); 127
119 result.exec(); 128 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query);
120 QCOMPARE(result.size(), 1); 129 QTRY_COMPARE(model->rowCount(QModelIndex()), 1);
121 auto value = result.first(); 130 auto value = model->index(0, 0, QModelIndex()).data(Akonadi2::Store::DomainObjectRole).value<Akonadi2::ApplicationDomain::Event::Ptr>();
131
122 qDebug() << value->getProperty("uid").toByteArray(); 132 qDebug() << value->getProperty("uid").toByteArray();
123 QCOMPARE(value->getProperty("uid").toByteArray(), QByteArray("testuid2")); 133 QCOMPARE(value->getProperty("uid").toByteArray(), QByteArray("testuid2"));
124 } 134 }
@@ -145,10 +155,13 @@ private Q_SLOTS:
145 query.syncOnDemand = true; 155 query.syncOnDemand = true;
146 query.processAll = true; 156 query.processAll = true;
147 157
148 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(Akonadi2::Store::load<Akonadi2::ApplicationDomain::Event>(query)); 158 //Ensure all local data is processed
149 result.exec(); 159 Akonadi2::Store::synchronize(query).exec().waitForFinished();
150 QVERIFY(!result.isEmpty()); 160
151 auto value = result.first(); 161 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query);
162 QTRY_VERIFY(model->rowCount(QModelIndex()) >= 1);
163 auto value = model->index(0, 0, QModelIndex()).data(Akonadi2::Store::DomainObjectRole).value<Akonadi2::ApplicationDomain::Event::Ptr>();
164
152 QVERIFY(!value->getProperty("summary").toString().isEmpty()); 165 QVERIFY(!value->getProperty("summary").toString().isEmpty());
153 qDebug() << value->getProperty("summary").toString(); 166 qDebug() << value->getProperty("summary").toString();
154 } 167 }
@@ -160,10 +173,13 @@ private Q_SLOTS:
160 query.syncOnDemand = true; 173 query.syncOnDemand = true;
161 query.processAll = true; 174 query.processAll = true;
162 175
163 async::SyncListResult<Akonadi2::ApplicationDomain::Mail::Ptr> result(Akonadi2::Store::load<Akonadi2::ApplicationDomain::Mail>(query)); 176 //Ensure all local data is processed
164 result.exec(); 177 Akonadi2::Store::synchronize(query).exec().waitForFinished();
165 QVERIFY(!result.isEmpty()); 178
166 auto value = result.first(); 179 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query);
180 QTRY_VERIFY(model->rowCount(QModelIndex()) >= 1);
181 auto value = model->index(0, 0, QModelIndex()).data(Akonadi2::Store::DomainObjectRole).value<Akonadi2::ApplicationDomain::Mail::Ptr>();
182
167 QVERIFY(!value->getProperty("subject").toString().isEmpty()); 183 QVERIFY(!value->getProperty("subject").toString().isEmpty());
168 qDebug() << value->getProperty("subject").toString(); 184 qDebug() << value->getProperty("subject").toString();
169 } 185 }
@@ -182,13 +198,16 @@ private Q_SLOTS:
182 query.processAll = true; 198 query.processAll = true;
183 query.propertyFilter.insert("uid", "testuid"); 199 query.propertyFilter.insert("uid", "testuid");
184 200
201 //Ensure all local data is processed
202 Akonadi2::Store::synchronize(query).exec().waitForFinished();
203
185 //Test create 204 //Test create
186 Akonadi2::ApplicationDomain::Event event2; 205 Akonadi2::ApplicationDomain::Event event2;
187 { 206 {
188 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(Akonadi2::Store::load<Akonadi2::ApplicationDomain::Event>(query)); 207 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query);
189 result.exec(); 208 QTRY_COMPARE(model->rowCount(QModelIndex()), 1);
190 QCOMPARE(result.size(), 1); 209 auto value = model->index(0, 0, QModelIndex()).data(Akonadi2::Store::DomainObjectRole).value<Akonadi2::ApplicationDomain::Event::Ptr>();
191 auto value = result.first(); 210
192 QCOMPARE(value->getProperty("uid").toByteArray(), QByteArray("testuid")); 211 QCOMPARE(value->getProperty("uid").toByteArray(), QByteArray("testuid"));
193 QCOMPARE(value->getProperty("summary").toByteArray(), QByteArray("summaryValue")); 212 QCOMPARE(value->getProperty("summary").toByteArray(), QByteArray("summaryValue"));
194 event2 = *value; 213 event2 = *value;
@@ -198,23 +217,29 @@ private Q_SLOTS:
198 event2.setProperty("summary", "summaryValue2"); 217 event2.setProperty("summary", "summaryValue2");
199 Akonadi2::Store::modify<Akonadi2::ApplicationDomain::Event>(event2).exec().waitForFinished(); 218 Akonadi2::Store::modify<Akonadi2::ApplicationDomain::Event>(event2).exec().waitForFinished();
200 219
220 //Ensure all local data is processed
221 Akonadi2::Store::synchronize(query).exec().waitForFinished();
222
201 //Test modify 223 //Test modify
202 { 224 {
203 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(Akonadi2::Store::load<Akonadi2::ApplicationDomain::Event>(query)); 225 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query);
204 result.exec(); 226 QTRY_COMPARE(model->rowCount(QModelIndex()), 1);
205 QCOMPARE(result.size(), 1); 227 auto value = model->index(0, 0, QModelIndex()).data(Akonadi2::Store::DomainObjectRole).value<Akonadi2::ApplicationDomain::Event::Ptr>();
206 auto value = result.first(); 228
207 QCOMPARE(value->getProperty("uid").toByteArray(), QByteArray("testuid")); 229 QCOMPARE(value->getProperty("uid").toByteArray(), QByteArray("testuid"));
208 QCOMPARE(value->getProperty("summary").toByteArray(), QByteArray("summaryValue2")); 230 QCOMPARE(value->getProperty("summary").toByteArray(), QByteArray("summaryValue2"));
209 } 231 }
210 232
211 Akonadi2::Store::remove<Akonadi2::ApplicationDomain::Event>(event2).exec().waitForFinished(); 233 Akonadi2::Store::remove<Akonadi2::ApplicationDomain::Event>(event2).exec().waitForFinished();
212 234
235 //Ensure all local data is processed
236 Akonadi2::Store::synchronize(query).exec().waitForFinished();
237
213 //Test remove 238 //Test remove
214 { 239 {
215 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(Akonadi2::Store::load<Akonadi2::ApplicationDomain::Event>(query)); 240 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query);
216 result.exec(); 241 //TODO ensure the initial query is done
217 QTRY_COMPARE(result.size(), 0); 242 QTRY_COMPARE(model->rowCount(QModelIndex()), 0);
218 } 243 }
219 } 244 }
220 245
@@ -228,9 +253,8 @@ private Q_SLOTS:
228 query.liveQuery = true; 253 query.liveQuery = true;
229 query.propertyFilter.insert("uid", "testuid"); 254 query.propertyFilter.insert("uid", "testuid");
230 255
231 256 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Event>(query);
232 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(Akonadi2::Store::load<Akonadi2::ApplicationDomain::Event>(query)); 257 //TODO ensure the initial query is done
233 result.exec();
234 258
235 Akonadi2::ApplicationDomain::Event event("org.kde.dummy.instance1"); 259 Akonadi2::ApplicationDomain::Event event("org.kde.dummy.instance1");
236 event.setProperty("uid", "testuid"); 260 event.setProperty("uid", "testuid");
@@ -241,8 +265,8 @@ private Q_SLOTS:
241 //Test create 265 //Test create
242 Akonadi2::ApplicationDomain::Event event2; 266 Akonadi2::ApplicationDomain::Event event2;
243 { 267 {
244 QTRY_COMPARE(result.size(), 1); 268 QTRY_COMPARE(model->rowCount(QModelIndex()), 1);
245 auto value = result.first(); 269 auto value = model->index(0, 0, QModelIndex()).data(Akonadi2::Store::DomainObjectRole).value<Akonadi2::ApplicationDomain::Event::Ptr>();
246 QCOMPARE(value->getProperty("uid").toByteArray(), QByteArray("testuid")); 270 QCOMPARE(value->getProperty("uid").toByteArray(), QByteArray("testuid"));
247 QCOMPARE(value->getProperty("summary").toByteArray(), QByteArray("summaryValue")); 271 QCOMPARE(value->getProperty("summary").toByteArray(), QByteArray("summaryValue"));
248 event2 = *value; 272 event2 = *value;
@@ -254,8 +278,9 @@ private Q_SLOTS:
254 278
255 //Test modify 279 //Test modify
256 { 280 {
257 QTRY_COMPARE(result.size(), 1); 281 //TODO wait for a change signal
258 auto value = result.first(); 282 QTRY_COMPARE(model->rowCount(QModelIndex()), 1);
283 auto value = model->index(0, 0, QModelIndex()).data(Akonadi2::Store::DomainObjectRole).value<Akonadi2::ApplicationDomain::Event::Ptr>();
259 QCOMPARE(value->getProperty("uid").toByteArray(), QByteArray("testuid")); 284 QCOMPARE(value->getProperty("uid").toByteArray(), QByteArray("testuid"));
260 QCOMPARE(value->getProperty("summary").toByteArray(), QByteArray("summaryValue2")); 285 QCOMPARE(value->getProperty("summary").toByteArray(), QByteArray("summaryValue2"));
261 } 286 }
@@ -264,7 +289,7 @@ private Q_SLOTS:
264 289
265 //Test remove 290 //Test remove
266 { 291 {
267 QTRY_COMPARE(result.size(), 0); 292 QTRY_COMPARE(model->rowCount(QModelIndex()), 0);
268 } 293 }
269 } 294 }
270 295
diff --git a/tests/genericfacadebenchmark.cpp b/tests/genericfacadebenchmark.cpp
index 29c91d7..703acd1 100644
--- a/tests/genericfacadebenchmark.cpp
+++ b/tests/genericfacadebenchmark.cpp
@@ -8,6 +8,7 @@
8#include <common/domainadaptor.h> 8#include <common/domainadaptor.h>
9#include <common/resultprovider.h> 9#include <common/resultprovider.h>
10#include <common/synclistresult.h> 10#include <common/synclistresult.h>
11#include <common/definitions.h>
11 12
12#include "event_generated.h" 13#include "event_generated.h"
13 14
@@ -56,12 +57,11 @@ private Q_SLOTS:
56 QBENCHMARK { 57 QBENCHMARK {
57 auto resultSet = QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Event::Ptr> >::create(); 58 auto resultSet = QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Event::Ptr> >::create();
58 auto resourceAccess = QSharedPointer<TestResourceAccess>::create(); 59 auto resourceAccess = QSharedPointer<TestResourceAccess>::create();
59 auto storage = QSharedPointer<EntityStorage<Akonadi2::ApplicationDomain::Event> >::create("identifier"); 60 TestResourceFacade facade(identifier, resourceAccess);
60 TestResourceFacade facade(identifier, storage, resourceAccess);
61 61
62 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(resultSet->emitter()); 62 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(resultSet->emitter());
63 63
64 facade.load(query, resultSet).exec().waitForFinished(); 64 facade.load(query, *resultSet).exec().waitForFinished();
65 resultSet->initialResultSetComplete(); 65 resultSet->initialResultSetComplete();
66 66
67 //We have to wait for the events that deliver the results to be processed by the eventloop 67 //We have to wait for the events that deliver the results to be processed by the eventloop
diff --git a/tests/genericfacadetest.cpp b/tests/genericfacadetest.cpp
index 67320c3..bb95f4e 100644
--- a/tests/genericfacadetest.cpp
+++ b/tests/genericfacadetest.cpp
@@ -17,6 +17,7 @@
17 * Test for the generic facade implementation. 17 * Test for the generic facade implementation.
18 * 18 *
19 * This test doesn't use the actual storage and thus only tests the update logic of the facade. 19 * This test doesn't use the actual storage and thus only tests the update logic of the facade.
20 * //FIXME this now uses the actual storage
20 */ 21 */
21class GenericFacadeTest : public QObject 22class GenericFacadeTest : public QObject
22{ 23{
@@ -34,14 +35,13 @@ private Q_SLOTS:
34 query.liveQuery = false; 35 query.liveQuery = false;
35 36
36 auto resultSet = QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Event::Ptr> >::create(); 37 auto resultSet = QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Event::Ptr> >::create();
37 auto storage = QSharedPointer<TestEntityStorage>::create("identifier");
38 auto resourceAccess = QSharedPointer<TestResourceAccess>::create(); 38 auto resourceAccess = QSharedPointer<TestResourceAccess>::create();
39 storage->mResults << Akonadi2::ApplicationDomain::Event::Ptr::create(); 39 // storage->mResults << Akonadi2::ApplicationDomain::Event::Ptr::create();
40 TestResourceFacade facade("identifier", storage, resourceAccess); 40 TestResourceFacade facade("identifier", resourceAccess);
41 41
42 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(resultSet->emitter()); 42 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(resultSet->emitter());
43 43
44 facade.load(query, resultSet).exec().waitForFinished(); 44 facade.load(query, *resultSet).exec().waitForFinished();
45 resultSet->initialResultSetComplete(); 45 resultSet->initialResultSetComplete();
46 46
47 //We have to wait for the events that deliver the results to be processed by the eventloop 47 //We have to wait for the events that deliver the results to be processed by the eventloop
@@ -56,23 +56,22 @@ private Q_SLOTS:
56 query.liveQuery = true; 56 query.liveQuery = true;
57 57
58 auto resultSet = QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Event::Ptr> >::create(); 58 auto resultSet = QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Event::Ptr> >::create();
59 auto storage = QSharedPointer<TestEntityStorage>::create("identifier");
60 auto resourceAccess = QSharedPointer<TestResourceAccess>::create(); 59 auto resourceAccess = QSharedPointer<TestResourceAccess>::create();
61 storage->mResults << Akonadi2::ApplicationDomain::Event::Ptr::create(); 60 // storage->mResults << Akonadi2::ApplicationDomain::Event::Ptr::create();
62 TestResourceFacade facade("identifier", storage, resourceAccess); 61 TestResourceFacade facade("identifier", resourceAccess);
63 62
64 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(resultSet->emitter()); 63 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(resultSet->emitter());
65 64
66 facade.load(query, resultSet).exec().waitForFinished(); 65 facade.load(query, *resultSet).exec().waitForFinished();
67 resultSet->initialResultSetComplete(); 66 resultSet->initialResultSetComplete();
68 67
69 result.exec(); 68 result.exec();
70 QCOMPARE(result.size(), 1); 69 QCOMPARE(result.size(), 1);
71 70
72 //Enter a second result 71 //Enter a second result
73 storage->mResults.clear(); 72 // storage->mResults.clear();
74 storage->mResults << QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("resource", "id2", 0, QSharedPointer<Akonadi2::ApplicationDomain::BufferAdaptor>()); 73 // storage->mResults << QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("resource", "id2", 0, QSharedPointer<Akonadi2::ApplicationDomain::BufferAdaptor>());
75 storage->mLatestRevision = 2; 74 // storage->mLatestRevision = 2;
76 resourceAccess->emit revisionChanged(2); 75 resourceAccess->emit revisionChanged(2);
77 76
78 //Hack to get event loop in synclistresult to abort again 77 //Hack to get event loop in synclistresult to abort again
@@ -88,27 +87,26 @@ private Q_SLOTS:
88 query.liveQuery = true; 87 query.liveQuery = true;
89 88
90 auto resultSet = QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Event::Ptr> >::create(); 89 auto resultSet = QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Event::Ptr> >::create();
91 auto storage = QSharedPointer<TestEntityStorage>::create("identifier");
92 auto resourceAccess = QSharedPointer<TestResourceAccess>::create(); 90 auto resourceAccess = QSharedPointer<TestResourceAccess>::create();
93 auto entity = QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("resource", "id2", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create()); 91 auto entity = QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("resource", "id2", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
94 entity->setProperty("test", "test1"); 92 entity->setProperty("test", "test1");
95 storage->mResults << entity; 93 // storage->mResults << entity;
96 TestResourceFacade facade("identifier", storage, resourceAccess); 94 TestResourceFacade facade("identifier", resourceAccess);
97 95
98 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(resultSet->emitter()); 96 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(resultSet->emitter());
99 97
100 facade.load(query, resultSet).exec().waitForFinished(); 98 facade.load(query, *resultSet).exec().waitForFinished();
101 resultSet->initialResultSetComplete(); 99 resultSet->initialResultSetComplete();
102 100
103 result.exec(); 101 result.exec();
104 QCOMPARE(result.size(), 1); 102 QCOMPARE(result.size(), 1);
105 103
106 //Modify the entity again 104 //Modify the entity again
107 storage->mResults.clear(); 105 // storage->mResults.clear();
108 entity = QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("resource", "id2", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create()); 106 entity = QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("resource", "id2", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
109 entity->setProperty("test", "test2"); 107 entity->setProperty("test", "test2");
110 storage->mModifications << entity; 108 // storage->mModifications << entity;
111 storage->mLatestRevision = 2; 109 // storage->mLatestRevision = 2;
112 resourceAccess->emit revisionChanged(2); 110 resourceAccess->emit revisionChanged(2);
113 111
114 //Hack to get event loop in synclistresult to abort again 112 //Hack to get event loop in synclistresult to abort again
@@ -125,24 +123,23 @@ private Q_SLOTS:
125 query.liveQuery = true; 123 query.liveQuery = true;
126 124
127 auto resultSet = QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Event::Ptr> >::create(); 125 auto resultSet = QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Event::Ptr> >::create();
128 auto storage = QSharedPointer<TestEntityStorage>::create("identifier");
129 auto resourceAccess = QSharedPointer<TestResourceAccess>::create(); 126 auto resourceAccess = QSharedPointer<TestResourceAccess>::create();
130 auto entity = QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("resource", "id2", 0, QSharedPointer<Akonadi2::ApplicationDomain::BufferAdaptor>()); 127 auto entity = QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("resource", "id2", 0, QSharedPointer<Akonadi2::ApplicationDomain::BufferAdaptor>());
131 storage->mResults << entity; 128 // storage->mResults << entity;
132 TestResourceFacade facade("identifier", storage, resourceAccess); 129 TestResourceFacade facade("identifier", resourceAccess);
133 130
134 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(resultSet->emitter()); 131 async::SyncListResult<Akonadi2::ApplicationDomain::Event::Ptr> result(resultSet->emitter());
135 132
136 facade.load(query, resultSet).exec().waitForFinished(); 133 facade.load(query, *resultSet).exec().waitForFinished();
137 resultSet->initialResultSetComplete(); 134 resultSet->initialResultSetComplete();
138 135
139 result.exec(); 136 result.exec();
140 QCOMPARE(result.size(), 1); 137 QCOMPARE(result.size(), 1);
141 138
142 //Remove the entity again 139 //Remove the entity again
143 storage->mResults.clear(); 140 // storage->mResults.clear();
144 storage->mRemovals << entity; 141 // storage->mRemovals << entity;
145 storage->mLatestRevision = 2; 142 // storage->mLatestRevision = 2;
146 resourceAccess->emit revisionChanged(2); 143 resourceAccess->emit revisionChanged(2);
147 144
148 //Hack to get event loop in synclistresult to abort again 145 //Hack to get event loop in synclistresult to abort again
diff --git a/tests/pipelinetest.cpp b/tests/pipelinetest.cpp
index 47090a8..f0fd1a4 100644
--- a/tests/pipelinetest.cpp
+++ b/tests/pipelinetest.cpp
@@ -12,13 +12,13 @@
12#include "deleteentity_generated.h" 12#include "deleteentity_generated.h"
13#include "dummyresource/resourcefactory.h" 13#include "dummyresource/resourcefactory.h"
14#include "clientapi.h" 14#include "clientapi.h"
15#include "synclistresult.h"
16#include "commands.h" 15#include "commands.h"
17#include "entitybuffer.h" 16#include "entitybuffer.h"
18#include "resourceconfig.h" 17#include "resourceconfig.h"
19#include "pipeline.h" 18#include "pipeline.h"
20#include "log.h" 19#include "log.h"
21#include "domainadaptor.h" 20#include "domainadaptor.h"
21#include "definitions.h"
22 22
23static void removeFromDisk(const QString &name) 23static void removeFromDisk(const QString &name)
24{ 24{
diff --git a/tests/querytest.cpp b/tests/querytest.cpp
new file mode 100644
index 0000000..669bf58
--- /dev/null
+++ b/tests/querytest.cpp
@@ -0,0 +1,161 @@
1#include <QtTest>
2
3#include <QString>
4
5#include "dummyresource/resourcefactory.h"
6#include "clientapi.h"
7#include "commands.h"
8#include "resourceconfig.h"
9#include "log.h"
10#include "modelresult.h"
11
12/**
13 * Test of the query system using the dummy resource.
14 *
15 * This test requires the dummy resource installed.
16 */
17class QueryTest : public QObject
18{
19 Q_OBJECT
20private Q_SLOTS:
21 void initTestCase()
22 {
23 Akonadi2::Log::setDebugOutputLevel(Akonadi2::Log::Trace);
24 auto factory = Akonadi2::ResourceFactory::load("org.kde.dummy");
25 QVERIFY(factory);
26 DummyResource::removeFromDisk("org.kde.dummy.instance1");
27 ResourceConfig::addResource("org.kde.dummy.instance1", "org.kde.dummy");
28 }
29
30 void cleanup()
31 {
32 Akonadi2::Store::shutdown(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished();
33 DummyResource::removeFromDisk("org.kde.dummy.instance1");
34 auto factory = Akonadi2::ResourceFactory::load("org.kde.dummy");
35 QVERIFY(factory);
36 }
37
38 void init()
39 {
40 qDebug();
41 qDebug() << "-----------------------------------------";
42 qDebug();
43 }
44
45 void testSingle()
46 {
47 //Setup
48 {
49 Akonadi2::ApplicationDomain::Mail mail("org.kde.dummy.instance1");
50 Akonadi2::Store::create<Akonadi2::ApplicationDomain::Mail>(mail).exec().waitForFinished();
51 }
52
53 //Test
54 Akonadi2::Query query;
55 query.resources << "org.kde.dummy.instance1";
56 query.syncOnDemand = false;
57 query.processAll = false;
58 query.liveQuery = true;
59
60 //We fetch before the data is available and rely on the live query mechanism to deliver the actual data
61 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query);
62 model->fetchMore(QModelIndex());
63 QTRY_COMPARE(model->rowCount(), 1);
64 }
65
66 void testSingleWithDelay()
67 {
68 //Setup
69 {
70 Akonadi2::ApplicationDomain::Mail mail("org.kde.dummy.instance1");
71 Akonadi2::Store::create<Akonadi2::ApplicationDomain::Mail>(mail).exec().waitForFinished();
72 }
73
74 //Test
75 Akonadi2::Query query;
76 query.resources << "org.kde.dummy.instance1";
77 query.syncOnDemand = false;
78 query.processAll = true;
79 query.liveQuery = false;
80
81 //We fetch after the data is available and don't rely on the live query mechanism to deliver the actual data
82 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Mail>(query);
83
84 //Ensure all local data is processed
85 Akonadi2::Store::synchronize(query).exec().waitForFinished();
86
87 model->fetchMore(QModelIndex());
88 QVERIFY(model->rowCount() < 2);
89 QTRY_COMPARE(model->rowCount(), 1);
90 }
91
92 void testFolder()
93 {
94 //Setup
95 {
96 Akonadi2::ApplicationDomain::Folder folder("org.kde.dummy.instance1");
97 Akonadi2::Store::create<Akonadi2::ApplicationDomain::Folder>(folder).exec().waitForFinished();
98 }
99
100 //Test
101 Akonadi2::Query query;
102 query.resources << "org.kde.dummy.instance1";
103 query.syncOnDemand = false;
104 query.processAll = false;
105 query.liveQuery = true;
106
107 //We fetch before the data is available and rely on the live query mechanism to deliver the actual data
108 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query);
109 model->fetchMore(QModelIndex());
110 QTRY_COMPARE(model->rowCount(), 1);
111 auto folderEntity = model->index(0, 0).data(Akonadi2::Store::DomainObjectRole).value<Akonadi2::ApplicationDomain::Folder::Ptr>();
112 QVERIFY(!folderEntity->identifier().isEmpty());
113 }
114
115 void testFolderTree()
116 {
117 //Setup
118 {
119 Akonadi2::ApplicationDomain::Folder folder("org.kde.dummy.instance1");
120 Akonadi2::Store::create<Akonadi2::ApplicationDomain::Folder>(folder).exec().waitForFinished();
121
122 Akonadi2::Query query;
123 query.resources << "org.kde.dummy.instance1";
124 query.syncOnDemand = false;
125 query.processAll = true;
126
127 //Ensure all local data is processed
128 Akonadi2::Store::synchronize(query).exec().waitForFinished();
129
130 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query);
131 QTRY_COMPARE(model->rowCount(), 1);
132
133 auto folderEntity = model->index(0, 0).data(Akonadi2::Store::DomainObjectRole).value<Akonadi2::ApplicationDomain::Folder::Ptr>();
134 QVERIFY(!folderEntity->identifier().isEmpty());
135
136 Akonadi2::ApplicationDomain::Folder subfolder("org.kde.dummy.instance1");
137 subfolder.setProperty("parent", folderEntity->identifier());
138 Akonadi2::Store::create<Akonadi2::ApplicationDomain::Folder>(subfolder).exec().waitForFinished();
139 }
140
141 //Test
142 Akonadi2::Query query;
143 query.resources << "org.kde.dummy.instance1";
144 query.syncOnDemand = false;
145 query.processAll = true;
146 query.parentProperty = "parent";
147
148 //Ensure all local data is processed
149 Akonadi2::Store::synchronize(query).exec().waitForFinished();
150
151 //We fetch after the data is available and don't rely on the live query mechanism to deliver the actual data
152 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query);
153 model->fetchMore(QModelIndex());
154 QTRY_COMPARE(model->rowCount(), 1);
155 model->fetchMore(model->index(0, 0));
156 QTRY_COMPARE(model->rowCount(model->index(0, 0)), 1);
157 }
158};
159
160QTEST_MAIN(QueryTest)
161#include "querytest.moc"
diff --git a/tests/storagetest.cpp b/tests/storagetest.cpp
index bef8755..d950961 100644
--- a/tests/storagetest.cpp
+++ b/tests/storagetest.cpp
@@ -376,6 +376,7 @@ private Q_SLOTS:
376 db.write("sub1","value1"); 376 db.write("sub1","value1");
377 db.write("sub2","value2"); 377 db.write("sub2","value2");
378 db.write("wub3","value3"); 378 db.write("wub3","value3");
379 db.write("wub4","value4");
379 QByteArray result; 380 QByteArray result;
380 db.findLatest("sub", [&](const QByteArray &key, const QByteArray &value) { 381 db.findLatest("sub", [&](const QByteArray &key, const QByteArray &value) {
381 result = value; 382 result = value;
@@ -384,6 +385,35 @@ private Q_SLOTS:
384 QCOMPARE(result, QByteArray("value2")); 385 QCOMPARE(result, QByteArray("value2"));
385 } 386 }
386 387
388 void testFindLatestInSingle()
389 {
390 Akonadi2::Storage store(testDataPath, dbName, Akonadi2::Storage::ReadWrite);
391 auto transaction = store.createTransaction(Akonadi2::Storage::ReadWrite);
392 auto db = transaction.openDatabase("test", nullptr, false);
393 db.write("sub2","value2");
394 QByteArray result;
395 db.findLatest("sub", [&](const QByteArray &key, const QByteArray &value) {
396 result = value;
397 });
398
399 QCOMPARE(result, QByteArray("value2"));
400 }
401
402 void testFindLast()
403 {
404 Akonadi2::Storage store(testDataPath, dbName, Akonadi2::Storage::ReadWrite);
405 auto transaction = store.createTransaction(Akonadi2::Storage::ReadWrite);
406 auto db = transaction.openDatabase("test", nullptr, false);
407 db.write("sub2","value2");
408 db.write("wub3","value3");
409 QByteArray result;
410 db.findLatest("wub", [&](const QByteArray &key, const QByteArray &value) {
411 result = value;
412 });
413
414 QCOMPARE(result, QByteArray("value3"));
415 }
416
387 void testRecordRevision() 417 void testRecordRevision()
388 { 418 {
389 Akonadi2::Storage store(testDataPath, dbName, Akonadi2::Storage::ReadWrite); 419 Akonadi2::Storage store(testDataPath, dbName, Akonadi2::Storage::ReadWrite);
diff --git a/tests/testimplementations.h b/tests/testimplementations.h
index eee78b0..a47a775 100644
--- a/tests/testimplementations.h
+++ b/tests/testimplementations.h
@@ -21,13 +21,10 @@
21 21
22#include <Async/Async> 22#include <Async/Async>
23 23
24#include <common/entitystorage.h>
25#include <common/domainadaptor.h> 24#include <common/domainadaptor.h>
26#include <common/resultprovider.h> 25#include <common/resultprovider.h>
27#include <common/synclistresult.h>
28#include <common/resourceaccess.h> 26#include <common/resourceaccess.h>
29#include <common/facade.h> 27#include <common/facade.h>
30#include <common/entitystorage.h>
31#include <common/genericresource.h> 28#include <common/genericresource.h>
32 29
33//Replace with something different 30//Replace with something different
@@ -44,30 +41,6 @@ public:
44 virtual ~TestEventAdaptorFactory() {}; 41 virtual ~TestEventAdaptorFactory() {};
45}; 42};
46 43
47class TestEntityStorage : public EntityStorage<Akonadi2::ApplicationDomain::Event>
48{
49public:
50 using EntityStorage::EntityStorage;
51 virtual qint64 read(const Akonadi2::Query &query, qint64 oldRevision, const QSharedPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Event::Ptr> > &resultProvider) Q_DECL_OVERRIDE
52 {
53 for (const auto &res : mResults) {
54 resultProvider->add(res);
55 }
56 for (const auto &res : mModifications) {
57 resultProvider->modify(res);
58 }
59 for (const auto &res : mRemovals) {
60 resultProvider->remove(res);
61 }
62 return mLatestRevision;
63 }
64
65 QList<Akonadi2::ApplicationDomain::Event::Ptr> mResults;
66 QList<Akonadi2::ApplicationDomain::Event::Ptr> mModifications;
67 QList<Akonadi2::ApplicationDomain::Event::Ptr> mRemovals;
68 qint64 mLatestRevision;
69};
70
71class TestResourceAccess : public Akonadi2::ResourceAccessInterface 44class TestResourceAccess : public Akonadi2::ResourceAccessInterface
72{ 45{
73 Q_OBJECT 46 Q_OBJECT
@@ -85,8 +58,8 @@ public Q_SLOTS:
85class TestResourceFacade : public Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Event> 58class TestResourceFacade : public Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Event>
86{ 59{
87public: 60public:
88 TestResourceFacade(const QByteArray &instanceIdentifier, const QSharedPointer<EntityStorage<Akonadi2::ApplicationDomain::Event> > storage, const QSharedPointer<Akonadi2::ResourceAccessInterface> resourceAccess) 61 TestResourceFacade(const QByteArray &instanceIdentifier, const QSharedPointer<Akonadi2::ResourceAccessInterface> resourceAccess)
89 : Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Event>(instanceIdentifier, QSharedPointer<TestEventAdaptorFactory>::create(), storage, resourceAccess) 62 : Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Event>(instanceIdentifier, QSharedPointer<TestEventAdaptorFactory>::create(), resourceAccess)
90 { 63 {
91 64
92 } 65 }