summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/modelresult.h180
-rw-r--r--tests/CMakeLists.txt2
-rw-r--r--tests/clientapitest.cpp117
-rw-r--r--tests/querytest.cpp100
4 files changed, 376 insertions, 23 deletions
diff --git a/common/modelresult.h b/common/modelresult.h
new file mode 100644
index 0000000..c23c41e
--- /dev/null
+++ b/common/modelresult.h
@@ -0,0 +1,180 @@
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 "query.h"
27#include "clientapi.h"
28
29#include "resultprovider.h"
30
31template<class T>
32class ModelResult : public QAbstractItemModel
33{
34public:
35
36 enum Roles {
37 DomainObjectRole = Qt::UserRole + 1
38 };
39
40 ModelResult(const Akonadi2::Query &query, const QList<QByteArray> &propertyColumns)
41 :QAbstractItemModel(),
42 mPropertyColumns(propertyColumns)
43 {
44 }
45
46 static qint64 getIdentifier(const QModelIndex &idx)
47 {
48 if (!idx.isValid()) {
49 return 0;
50 }
51 return idx.internalId();
52 }
53
54 int rowCount(const QModelIndex &parent = QModelIndex()) const
55 {
56 return mTree[getIdentifier(parent)].size();
57 }
58
59 int columnCount(const QModelIndex &parent = QModelIndex()) const
60 {
61 return mPropertyColumns.size();
62 }
63
64 virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
65 {
66 if (role == DomainObjectRole) {
67 qWarning() << "trying to get entity " << index.internalId();
68 Q_ASSERT(mEntities.contains(index.internalId()));
69 return QVariant::fromValue(mEntities.value(index.internalId()));
70 }
71 qDebug() << "Invalid role";
72 return QVariant();
73 }
74
75 QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const
76 {
77 auto id = getIdentifier(parent);
78 auto childId = mTree.value(id).at(row);
79 return createIndex(row, column, childId);
80 }
81
82 QModelIndex parent(const QModelIndex &index) const
83 {
84 auto id = getIdentifier(index);
85 auto parentId = mParents.value(id);
86 auto grandParentId = mParents.value(parentId, 0);
87 auto row = mTree.value(grandParentId).indexOf(parentId);
88 return createIndex(row, 0, parentId);
89 }
90
91 bool canFetchMore(const QModelIndex &parent) const
92 {
93 return mEntityChildrenFetched.value(parent.internalId());
94 }
95
96 void fetchMore(const QModelIndex &parent)
97 {
98 fetchEntities(parent);
99 }
100
101 void fetchEntities(const QModelIndex &parent)
102 {
103 qDebug() << "Fetching entities";
104 const auto id = getIdentifier(parent);
105 // beginResetModel();
106 // mEntities.remove(id);
107 mEntityChildrenFetched[id] = true;
108 auto query = mQuery;
109 if (!parent.isValid()) {
110 qDebug() << "no parent";
111 query.propertyFilter.insert("parent", QByteArray());
112 } else {
113 qDebug() << "parent is valid";
114 auto object = parent.data(DomainObjectRole).template value<typename T::Ptr>();
115 Q_ASSERT(object);
116 query.propertyFilter.insert("parent", object->identifier());
117 }
118 auto emitter = Akonadi2::Store::load<T>(query);
119 emitter->onAdded([this, id, parent](const typename T::Ptr &value) {
120 auto childId = qHash(value->identifier());
121 qDebug() << "Added entity " << childId;
122 const auto keys = mTree[id];
123 int index = 0;
124 for (; index < keys.size(); index++) {
125 if (childId < keys.at(index)) {
126 break;
127 }
128 }
129 beginInsertRows(parent, index, index);
130 mEntities.insert(childId, value);
131 mTree[id].insert(index, childId);
132 mParents.insert(childId, id);
133 endInsertRows();
134 });
135 emitter->onModified([this, id, parent](const typename T::Ptr &value) {
136 auto childId = qHash(value->identifier());
137 qDebug() << "Modified entity" << childId;
138 auto i = mTree[id].indexOf(childId);
139 mEntities.remove(childId);
140 mEntities.insert(childId, value);
141 //TODO check for change of parents
142 auto idx = index(i, 0, parent);
143 emit dataChanged(idx, idx);
144 });
145 emitter->onRemoved([this, id, parent](const typename T::Ptr &value) {
146 auto childId = qHash(value->identifier());
147 qDebug() << "Removed entity" << childId;
148 auto index = mTree[id].indexOf(qHash(value->identifier()));
149 beginRemoveRows(parent, index, index);
150 mEntities.remove(childId);
151 mTree[id].removeAll(childId);
152 mParents.remove(childId);
153 //TODO remove children
154 endRemoveRows();
155 });
156 emitter->onInitialResultSetComplete([this]() {
157 });
158 emitter->onComplete([this, id]() {
159 mEmitter[id].clear();
160 });
161 emitter->onClear([this]() {
162 // beginResetModel();
163 // mEntities.clear();
164 // endResetModel();
165 });
166 mEmitter.insert(id, emitter);
167 // endResetModel();
168 }
169
170private:
171 QMap<qint64 /* parent entity id */, QSharedPointer<Akonadi2::ResultEmitter<typename T::Ptr> >> mEmitter;
172 //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>
173 QMap<qint64 /* entity id */, typename T::Ptr> mEntities;
174 QMap<qint64 /* parent entity id */, QList<qint64> /* child entity id*/> mTree;
175 QMap<qint64 /* child entity id */, qint64 /* parent entity id*/> mParents;
176 QMap<qint64 /* entity id */, bool> mEntityChildrenFetched;
177 QList<QByteArray> mPropertyColumns;
178 Akonadi2::Query mQuery;
179};
180
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 5629cdb..9ed5a76 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -45,8 +45,10 @@ auto_tests (
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..5bfad4b 100644
--- a/tests/clientapitest.cpp
+++ b/tests/clientapitest.cpp
@@ -6,28 +6,45 @@
6#include "facade.h" 6#include "facade.h"
7#include "synclistresult.h" 7#include "synclistresult.h"
8#include "resourceconfig.h" 8#include "resourceconfig.h"
9#include "modelresult.h"
10#include "resultprovider.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 KAsync::Job<void> load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProvider<typename T::Ptr> > &resultProvider) Q_DECL_OVERRIDE
18 { 31 {
19 capturedResultProvider = resultProvider; 32 capturedResultProvider = resultProvider;
20 return KAsync::start<void>([this, resultProvider, query]() { 33 return KAsync::start<void>([this, resultProvider, query]() {
21 for (const auto &res : results) { 34 for (const auto &res : results) {
22 resultProvider->add(res); 35 qDebug() << "Parent filter " << query.propertyFilter.value("parent").toByteArray() << res->identifier();
36 if (!query.propertyFilter.contains("parent") || query.propertyFilter.value("parent").toByteArray() == res->getProperty("parent").toByteArray()) {
37 resultProvider->add(res);
38 }
23 } 39 }
24 }); 40 });
25 } 41 }
26 42
27 QList<Akonadi2::ApplicationDomain::Event::Ptr> results; 43 QList<typename T::Ptr> results;
28 QWeakPointer<Akonadi2::ResultProvider<Akonadi2::ApplicationDomain::Event::Ptr> > capturedResultProvider; 44 QWeakPointer<Akonadi2::ResultProvider<typename T::Ptr> > capturedResultProvider;
29}; 45};
30 46
47
31/** 48/**
32 * Test of the client api implementation. 49 * Test of the client api implementation.
33 * 50 *
@@ -38,17 +55,6 @@ class ClientAPITest : public QObject
38 Q_OBJECT 55 Q_OBJECT
39private Q_SLOTS: 56private Q_SLOTS:
40 57
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() 58 void initTestCase()
53 { 59 {
54 Akonadi2::FacadeFactory::instance().resetFactory(); 60 Akonadi2::FacadeFactory::instance().resetFactory();
@@ -57,8 +63,8 @@ private Q_SLOTS:
57 63
58 void testLoad() 64 void testLoad()
59 { 65 {
60 auto facade = registerDummyFacade(); 66 auto facade = DummyResourceFacade<Akonadi2::ApplicationDomain::Event>::registerFacade();
61 facade->results << QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("resource", "id", 0, QSharedPointer<Akonadi2::ApplicationDomain::BufferAdaptor>()); 67 facade->results << QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("resource", "id", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
62 ResourceConfig::addResource("dummyresource.instance1", "dummyresource"); 68 ResourceConfig::addResource("dummyresource.instance1", "dummyresource");
63 69
64 Akonadi2::Query query; 70 Akonadi2::Query query;
@@ -73,8 +79,8 @@ private Q_SLOTS:
73 //The query provider is supposed to delete itself 79 //The query provider is supposed to delete itself
74 void testQueryLifetime() 80 void testQueryLifetime()
75 { 81 {
76 auto facade = registerDummyFacade(); 82 auto facade = DummyResourceFacade<Akonadi2::ApplicationDomain::Event>::registerFacade();
77 facade->results << QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("resource", "id", 0, QSharedPointer<Akonadi2::ApplicationDomain::BufferAdaptor>()); 83 facade->results << QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("resource", "id", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
78 ResourceConfig::addResource("dummyresource.instance1", "dummyresource"); 84 ResourceConfig::addResource("dummyresource.instance1", "dummyresource");
79 85
80 Akonadi2::Query query; 86 Akonadi2::Query query;
@@ -119,6 +125,71 @@ private Q_SLOTS:
119 } 125 }
120 } 126 }
121 127
128 void testModelSingle()
129 {
130 auto facade = DummyResourceFacade<Akonadi2::ApplicationDomain::Folder>::registerFacade();
131 facade->results << QSharedPointer<Akonadi2::ApplicationDomain::Folder>::create("resource", "id", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
132 ResourceConfig::addResource("dummyresource.instance1", "dummyresource");
133
134 Akonadi2::Query query;
135 query.resources << "dummyresource.instance1";
136 query.liveQuery = false;
137
138 auto model = new ModelResult<Akonadi2::ApplicationDomain::Folder>(query, QList<QByteArray>() << "summary" << "uid");
139 model->fetchMore(QModelIndex());
140 QTRY_COMPARE(model->rowCount(), 1);
141 }
142
143 void testModelNested()
144 {
145 auto facade = DummyResourceFacade<Akonadi2::ApplicationDomain::Folder>::registerFacade();
146 auto folder = QSharedPointer<Akonadi2::ApplicationDomain::Folder>::create("resource", "id", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
147 auto subfolder = QSharedPointer<Akonadi2::ApplicationDomain::Folder>::create("resource", "subId", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
148 subfolder->setProperty("parent", "id");
149 facade->results << folder << subfolder;
150 ResourceConfig::addResource("dummyresource.instance1", "dummyresource");
151
152 //Test
153 Akonadi2::Query query;
154 query.resources << "dummyresource.instance1";
155 query.liveQuery = false;
156
157 auto model = new ModelResult<Akonadi2::ApplicationDomain::Folder>(query, QList<QByteArray>() << "summary" << "uid");
158 model->fetchMore(QModelIndex());
159 QTRY_COMPARE(model->rowCount(), 1);
160 model->fetchMore(model->index(0, 0));
161 QTRY_COMPARE(model->rowCount(model->index(0, 0)), 1);
162 }
163
164 // void testModelNestedLive()
165 // {
166 // auto facade = DummyResourceFacade<Akonadi2::ApplicationDomain::Folder>::registerFacade();
167 // auto folder = QSharedPointer<Akonadi2::ApplicationDomain::Folder>::create("resource", "id", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
168 // auto subfolder = QSharedPointer<Akonadi2::ApplicationDomain::Folder>::create("resource", "subId", 0, QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create());
169 // subfolder->setProperty("parent", "id");
170 // facade->results << folder << subfolder;
171 // ResourceConfig::addResource("dummyresource.instance1", "dummyresource");
172 //
173 // //Test
174 // Akonadi2::Query query;
175 // query.resources << "dummyresource.instance1";
176 // query.liveQuery = true
177 //
178 // auto model = new ModelResult<Akonadi2::ApplicationDomain::Folder>(query, QList<QByteArray>() << "summary" << "uid");
179 // model->fetchMore(QModelIndex());
180 // QTRY_COMPARE(model->rowCount(), 1);
181 // model->fetchMore(model->index(0, 0));
182 // QTRY_COMPARE(model->rowCount(model->index(0, 0)), 1);
183 //
184 // auto resultProvider = facade->capturedResultProvider.toStrongRef();
185 //
186 // //A modification can also be a move
187 // // resultProvider->modify();
188 //
189 // // resultProvider->remove();
190 // }
191
192
122}; 193};
123 194
124QTEST_MAIN(ClientAPITest) 195QTEST_MAIN(ClientAPITest)
diff --git a/tests/querytest.cpp b/tests/querytest.cpp
new file mode 100644
index 0000000..9f4b3bb
--- /dev/null
+++ b/tests/querytest.cpp
@@ -0,0 +1,100 @@
1#include <QtTest>
2
3#include <QString>
4
5#include "dummyresource/resourcefactory.h"
6#include "clientapi.h"
7#include "synclistresult.h"
8#include "commands.h"
9#include "resourceconfig.h"
10#include "log.h"
11#include "modelresult.h"
12
13/**
14 * Test of the query system using the dummy resource.
15 *
16 * This test requires the dummy resource installed.
17 */
18class QueryTest : public QObject
19{
20 Q_OBJECT
21private Q_SLOTS:
22 void initTestCase()
23 {
24 Akonadi2::Log::setDebugOutputLevel(Akonadi2::Log::Trace);
25 auto factory = Akonadi2::ResourceFactory::load("org.kde.dummy");
26 QVERIFY(factory);
27 DummyResource::removeFromDisk("org.kde.dummy.instance1");
28 ResourceConfig::addResource("org.kde.dummy.instance1", "org.kde.dummy");
29 }
30
31 void cleanup()
32 {
33 Akonadi2::Store::shutdown(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished();
34 DummyResource::removeFromDisk("org.kde.dummy.instance1");
35 auto factory = Akonadi2::ResourceFactory::load("org.kde.dummy");
36 QVERIFY(factory);
37 }
38
39 void init()
40 {
41 qDebug();
42 qDebug() << "-----------------------------------------";
43 qDebug();
44 }
45
46 void testSingle()
47 {
48 //Setup
49 {
50 Akonadi2::ApplicationDomain::Mail mail("org.kde.dummy.instance1");
51 Akonadi2::Store::create<Akonadi2::ApplicationDomain::Mail>(mail).exec().waitForFinished();
52 }
53
54 //Test
55 Akonadi2::Query query;
56 query.resources << "org.kde.dummy.instance1";
57 query.syncOnDemand = false;
58 query.processAll = true;
59
60 auto model = new ModelResult<Akonadi2::ApplicationDomain::Mail>(query, QList<QByteArray>() << "summary" << "uid");
61 model->fetchMore(QModelIndex());
62 QTRY_COMPARE(model->rowCount(), 1);
63 }
64
65 // void testTree()
66 // {
67 // //Setup
68 // {
69 // Akonadi2::ApplicationDomain::Folder folder("org.kde.dummy.instance1");
70 // Akonadi2::Store::create<Akonadi2::ApplicationDomain::Folder>(folder).exec().waitForFinished();
71 //
72 // Akonadi2::Query query;
73 // query.resources << "org.kde.dummy.instance1";
74 // query.syncOnDemand = false;
75 // query.processAll = true;
76 //
77 // auto model = new ModelResult<Akonadi2::ApplicationDomain::Folder>(query, QList<QByteArray>() << "summary" << "uid");
78 // QTRY_COMPARE(model->rowCount(), 1);
79 //
80 // auto folderEntity = model->index(0, 0).data(ModelResult<Akonadi2::ApplicationDomain::Folder>::DomainObjectRole).value<Akonadi2::ApplicationDomain::Folder>();
81 //
82 // Akonadi2::ApplicationDomain::Folder subfolder("org.kde.dummy.instance1");
83 // subfolder.setProperty("parent", folderEntity.identifier());
84 // Akonadi2::Store::create<Akonadi2::ApplicationDomain::Folder>(subfolder).exec().waitForFinished();
85 // }
86 //
87 // //Test
88 // Akonadi2::Query query;
89 // query.resources << "org.kde.dummy.instance1";
90 // query.syncOnDemand = false;
91 // query.processAll = true;
92 //
93 // auto model = new ModelResult<Akonadi2::ApplicationDomain::Folder>(query, QList<QByteArray>() << "summary" << "uid");
94 // QTRY_COMPARE(model->rowCount(), 1);
95 // QTRY_COMPARE(model->rowCount(model->index(0, 0)), 1);
96 // }
97};
98
99QTEST_MAIN(QueryTest)
100#include "querytest.moc"