summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/clientapi.h32
-rw-r--r--common/facade.h263
-rw-r--r--common/facadeinterface.h4
-rw-r--r--common/modelresult.h139
-rw-r--r--common/resourcefacade.cpp2
-rw-r--r--common/resourcefacade.h2
-rw-r--r--common/resultprovider.h303
-rw-r--r--examples/dummyresource/resourcefacade.cpp2
-rw-r--r--examples/dummyresource/resourcefacade.h2
-rw-r--r--tests/clientapitest.cpp4
10 files changed, 602 insertions, 151 deletions
diff --git a/common/clientapi.h b/common/clientapi.h
index 9a32188..a424424 100644
--- a/common/clientapi.h
+++ b/common/clientapi.h
@@ -23,6 +23,7 @@
23#include <QString> 23#include <QString>
24#include <QSharedPointer> 24#include <QSharedPointer>
25#include <QEventLoop> 25#include <QEventLoop>
26#include <QAbstractItemModel>
26#include <functional> 27#include <functional>
27#include <memory> 28#include <memory>
28 29
@@ -101,11 +102,34 @@ public:
101 /** 102 /**
102 * Asynchronusly load a dataset with tree structure information 103 * Asynchronusly load a dataset with tree structure information
103 */ 104 */
104 // template <class DomainType> 105 template <class DomainType>
105 // static TreeSet<DomainType> loadTree(Query) 106 static QSharedPointer<QAbstractItemModel> loadModel(Query query)
106 // { 107 {
108 auto model = QSharedPointer<ModelResult<DomainType, typename DomainType::Ptr> >::create(query, QList<QByteArray>() << "summary" << "uid");
109 auto resultProvider = QSharedPointer<ModelResultProvider<DomainType, typename DomainType::Ptr> >::create(model);
110
111 // Query all resources and aggregate results
112 KAsync::iterate(getResources(query.resources, ApplicationDomain::getTypeName<DomainType>()))
113 .template each<void, QByteArray>([query, resultProvider](const QByteArray &resource, KAsync::Future<void> &future) {
114 auto facade = FacadeFactory::instance().getFacade<DomainType>(resourceName(resource), resource);
115 if (facade) {
116 facade->load(query, resultProvider).template then<void>([&future](){future.setFinished();}).exec();
117 //Keep the facade alive for the lifetime of the resultSet.
118 resultProvider->setFacade(facade);
119 } else {
120 //Ignore the error and carry on
121 future.setFinished();
122 }
123 }).template then<void>([query, resultProvider]() {
124 resultProvider->initialResultSetComplete();
125 if (!query.liveQuery) {
126 resultProvider->complete();
127 }
128 }).exec();
129
130 return model;
131 }
107 132
108 // }
109 template <class DomainType> 133 template <class DomainType>
110 static std::shared_ptr<StoreFacade<DomainType> > getFacade(const QByteArray &resourceInstanceIdentifier) 134 static std::shared_ptr<StoreFacade<DomainType> > getFacade(const QByteArray &resourceInstanceIdentifier)
111 { 135 {
diff --git a/common/facade.h b/common/facade.h
index 643ebec..eb55c98 100644
--- a/common/facade.h
+++ b/common/facade.h
@@ -135,68 +135,6 @@ public:
135 if (!mResourceAccess) { 135 if (!mResourceAccess) {
136 mResourceAccess = QSharedPointer<Akonadi2::ResourceAccess>::create(resourceIdentifier); 136 mResourceAccess = QSharedPointer<Akonadi2::ResourceAccess>::create(resourceIdentifier);
137 } 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 } 138 }
201 139
202 ~GenericFacade() 140 ~GenericFacade()
@@ -237,13 +175,56 @@ public:
237 } 175 }
238 176
239 //TODO JOBAPI return job from sync continuation to execute it as subjob? 177 //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 178 KAsync::Job<void> load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProviderInterface<typename DomainType::Ptr> > &resultProvider) Q_DECL_OVERRIDE
241 { 179 {
180 {
181 QSet<QByteArray> remainingFilters;
182 auto filter = [remainingFilters, query](const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject) -> bool {
183 for (const auto &filterProperty : remainingFilters) {
184 //TODO implement other comparison operators than equality
185 if (domainObject->getProperty(filterProperty) != query.propertyFilter.value(filterProperty)) {
186 return false;
187 }
188 }
189 return true;
190 };
191
192 auto fetchEntities = [this, query, resultProvider, filter](const QByteArray &parent) {
193 Trace() << "Running fetchEntities" << parent;
194 Akonadi2::Storage storage(Akonadi2::storageLocation(), mResourceInstanceIdentifier);
195 storage.setDefaultErrorHandler([](const Akonadi2::Storage::Error &error) {
196 Warning() << "Error during query: " << error.store << error.message;
197 });
198
199 auto transaction = storage.createTransaction(Akonadi2::Storage::ReadOnly);
200
201 auto modifiedQuery = query;
202 modifiedQuery.propertyFilter.insert("parent", parent);
203 //TODO
204 QSet<QByteArray> appliedFilters;
205 auto resultSet = Akonadi2::ApplicationDomain::TypeImplementation<DomainType>::queryIndexes(modifiedQuery, mResourceInstanceIdentifier, appliedFilters, transaction);
206 QSet<QByteArray> remainingFilters = query.propertyFilter.keys().toSet() - appliedFilters;
207
208 //We do a full scan if there were no indexes available to create the initial set.
209 if (appliedFilters.isEmpty()) {
210 //TODO this should be replaced by an index lookup as well
211 resultSet = fullScan(transaction, bufferTypeForDomainType());
212 }
213 auto filteredSet = filterSet(resultSet, filter, transaction, true);
214 replaySet(filteredSet, resultProvider);
215 resultProvider->setRevision(Akonadi2::Storage::maxRevision(transaction));
216 qint64 newRevision = Akonadi2::Storage::maxRevision(transaction);
217 //TODO send newRevision to resource
218 // mResourceAccess->sendRevisionReplayedCommand(newRevision);
219 };
220 resultProvider->setFetcher(fetchEntities);
221 }
222
242 auto runner = QSharedPointer<QueryRunner>::create(query); 223 auto runner = QSharedPointer<QueryRunner>::create(query);
243 QWeakPointer<Akonadi2::ResultProvider<typename DomainType::Ptr> > weakResultProvider = resultProvider; 224 QWeakPointer<Akonadi2::ResultProviderInterface<typename DomainType::Ptr> > weakResultProvider = resultProvider;
244 runner->setQuery([this, weakResultProvider, query] (qint64 oldRevision) -> KAsync::Job<qint64> { 225 runner->setQuery([this, weakResultProvider, query] () -> KAsync::Job<void> {
245 return KAsync::start<qint64>([this, weakResultProvider, query, oldRevision](KAsync::Future<qint64> &future) { 226 return KAsync::start<void>([this, weakResultProvider, query](KAsync::Future<void> &future) {
246 Trace() << "Executing query " << oldRevision; 227 Trace() << "Executing query ";
247 auto resultProvider = weakResultProvider.toStrongRef(); 228 auto resultProvider = weakResultProvider.toStrongRef();
248 if (!resultProvider) { 229 if (!resultProvider) {
249 Warning() << "Tried executing query after result provider is already gone"; 230 Warning() << "Tried executing query after result provider is already gone";
@@ -251,11 +232,10 @@ public:
251 future.setFinished(); 232 future.setFinished();
252 return; 233 return;
253 } 234 }
254 load(query, resultProvider, oldRevision).template then<void, qint64>([&future, this](qint64 queriedRevision) { 235 executeQuery(query, resultProvider).template then<void, qint64>([&future, this](qint64 queriedRevision) {
255 //TODO set revision in result provider? 236 //TODO set revision in result provider?
256 //TODO update all existing results with new revision 237 //TODO update all existing results with new revision
257 mResourceAccess->sendRevisionReplayedCommand(queriedRevision); 238 mResourceAccess->sendRevisionReplayedCommand(queriedRevision);
258 future.setValue(queriedRevision);
259 future.setFinished(); 239 future.setFinished();
260 }).exec(); 240 }).exec();
261 }); 241 });
@@ -272,9 +252,7 @@ public:
272 252
273 //We have to capture the runner to keep it alive 253 //We have to capture the runner to keep it alive
274 return synchronizeResource(query).template then<void>([runner](KAsync::Future<void> &future) { 254 return synchronizeResource(query).template then<void>([runner](KAsync::Future<void> &future) {
275 runner->run().then<void>([&future]() { 255 future.setFinished();
276 future.setFinished();
277 }).exec();
278 }, 256 },
279 [](int error, const QString &errorString) { 257 [](int error, const QString &errorString) {
280 Warning() << "Error during sync " << error << errorString; 258 Warning() << "Error during sync " << error << errorString;
@@ -293,17 +271,152 @@ private:
293 return KAsync::null<void>(); 271 return KAsync::null<void>();
294 } 272 }
295 273
296 virtual KAsync::Job<qint64> load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProvider<typename DomainType::Ptr> > &resultProvider, qint64 oldRevision) 274 //TODO move into result provider?
275 void replaySet(ResultSet &resultSet, const QSharedPointer<Akonadi2::ResultProviderInterface<typename DomainType::Ptr> > &resultProvider)
276 {
277 while (resultSet.next([this, resultProvider](const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &value, Akonadi2::Operation operation) -> bool {
278 switch (operation) {
279 case Akonadi2::Operation_Creation:
280 Trace() << "Got creation";
281 //TODO Only copy in result provider
282 resultProvider->add(Akonadi2::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<DomainType>(*value).template staticCast<DomainType>());
283 // modelResult->add();
284 break;
285 case Akonadi2::Operation_Modification:
286 Trace() << "Got modification";
287 resultProvider->modify(Akonadi2::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<DomainType>(*value).template staticCast<DomainType>());
288 // modelResult->modify();
289 break;
290 case Akonadi2::Operation_Removal:
291 Trace() << "Got removal";
292 resultProvider->remove(Akonadi2::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<DomainType>(*value).template staticCast<DomainType>());
293 // modelResult->remove();
294 break;
295 }
296 return true;
297 })){};
298 }
299
300 void readEntity(const Akonadi2::Storage::Transaction &transaction, const QByteArray &key, const std::function<void(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &, Akonadi2::Operation)> &resultCallback)
301 {
302 const auto bufferType = bufferTypeForDomainType();
303 //This only works for a 1:1 mapping of resource to domain types.
304 //Not i.e. for tags that are stored as flags in each entity of an imap store.
305 //additional properties that don't have a 1:1 mapping (such as separately stored tags),
306 //could be added to the adaptor.
307 //
308 // Akonadi2::Storage::getLatest(transaction, bufferTye, key);
309 transaction.openDatabase(bufferType + ".main").findLatest(key, [=](const QByteArray &key, const QByteArray &value) -> bool {
310 Akonadi2::EntityBuffer buffer(value.data(), value.size());
311 const Akonadi2::Entity &entity = buffer.entity();
312 const auto metadataBuffer = Akonadi2::EntityBuffer::readBuffer<Akonadi2::Metadata>(entity.metadata());
313 Q_ASSERT(metadataBuffer);
314 const qint64 revision = metadataBuffer ? metadataBuffer->revision() : -1;
315 resultCallback(DomainType::Ptr::create(mResourceInstanceIdentifier, Akonadi2::Storage::uidFromKey(key), revision, mDomainTypeAdaptorFactory->createAdaptor(entity)), metadataBuffer->operation());
316 return false;
317 },
318 [](const Akonadi2::Storage::Error &error) {
319 qWarning() << "Error during query: " << error.message;
320 });
321 }
322
323 ResultSet loadIncrementalResultSet(qint64 baseRevision, const Akonadi2::Query &query, Akonadi2::Storage::Transaction &transaction, QSet<QByteArray> &remainingFilters)
324 {
325
326 const auto bufferType = bufferTypeForDomainType();
327 auto revisionCounter = QSharedPointer<qint64>::create(baseRevision);
328 //TODO apply filter from index
329 return ResultSet([bufferType, revisionCounter, &transaction, this]() -> QByteArray {
330 const qint64 topRevision = Akonadi2::Storage::maxRevision(transaction);
331 //Spit out the revision keys one by one.
332 while (*revisionCounter <= topRevision) {
333 const auto uid = Akonadi2::Storage::getUidFromRevision(transaction, *revisionCounter);
334 const auto type = Akonadi2::Storage::getTypeFromRevision(transaction, *revisionCounter);
335 Trace() << "Revision" << *revisionCounter << type << uid;
336 if (type != bufferType) {
337 //Skip revision
338 *revisionCounter += 1;
339 continue;
340 }
341 const auto key = Akonadi2::Storage::assembleKey(uid, *revisionCounter);
342 *revisionCounter += 1;
343 return key;
344 }
345 //We're done
346 return QByteArray();
347 });
348 }
349
350 ResultSet filterSet(const ResultSet &resultSet, const std::function<bool(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject)> &filter, const Akonadi2::Storage::Transaction &transaction, bool initialQuery)
297 { 351 {
352 auto resultSetPtr = QSharedPointer<ResultSet>::create(resultSet);
353
354 //Read through the source values and return whatever matches the filter
355 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 {
356 while (resultSetPtr->next()) {
357 //TODO only necessary if we actually want to filter or neew the operation type (but not a big deal if we do it always I guess)
358 readEntity(transaction, resultSetPtr->id(), [this, filter, callback, initialQuery](const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject, Akonadi2::Operation operation) {
359 //Always remove removals, they probably don't match due to non-available properties
360 if (filter(domainObject) || operation == Akonadi2::Operation_Removal) {
361 if (initialQuery) {
362 //We're not interested in removals during the initial query
363 if (operation != Akonadi2::Operation_Removal) {
364 callback(domainObject, Akonadi2::Operation_Creation);
365 }
366 } else {
367 callback(domainObject, operation);
368 }
369 }
370 });
371 }
372 return false;
373 };
374 return ResultSet(generator);
375 }
376
377 virtual KAsync::Job<qint64> executeQuery(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProviderInterface<typename DomainType::Ptr> > &resultProvider)
378 {
379 /*
380 * This method gets called initially, and after every revision change.
381 * * We delegate loading of initial data to the result provider, os it can decide for itself what it needs to load.
382 * * Incremental updates are loaded directly, leaving it up to the model to discard the changes if they are not interesting
383 */
384 const qint64 baseRevision = resultProvider->revision() + 1;
385 Trace() << "Running query " << baseRevision;
386 QSet<QByteArray> remainingFilters;
387 auto filter = [remainingFilters, query, baseRevision](const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject) -> bool {
388 for (const auto &filterProperty : remainingFilters) {
389 //TODO implement other comparison operators than equality
390 if (domainObject->getProperty(filterProperty) != query.propertyFilter.value(filterProperty)) {
391 return false;
392 }
393 }
394 return true;
395 };
396 qint64 newRevision = 0;
397
398 Trace() << "Fetching updates";
399 Akonadi2::Storage storage(Akonadi2::storageLocation(), mResourceInstanceIdentifier);
400 storage.setDefaultErrorHandler([](const Akonadi2::Storage::Error &error) {
401 Warning() << "Error during query: " << error.store << error.message;
402 });
403
404 auto transaction = storage.createTransaction(Akonadi2::Storage::ReadOnly);
405
406 auto resultSet = loadIncrementalResultSet(baseRevision, query, transaction, remainingFilters);
407 auto filteredSet = filterSet(resultSet, filter, transaction, false);
408 replaySet(filteredSet, resultProvider);
409 resultProvider->setRevision(Akonadi2::Storage::maxRevision(transaction));
410 newRevision = Akonadi2::Storage::maxRevision(transaction);
411
298 return KAsync::start<qint64>([=]() -> qint64 { 412 return KAsync::start<qint64>([=]() -> qint64 {
299 return mStorage->read(query, oldRevision, resultProvider); 413 return newRevision;
300 }); 414 });
301 } 415 }
302 416
303protected: 417protected:
304 //TODO use one resource access instance per application & per resource 418 //TODO use one resource access instance per application & per resource
305 QSharedPointer<Akonadi2::ResourceAccessInterface> mResourceAccess; 419 QSharedPointer<Akonadi2::ResourceAccessInterface> mResourceAccess;
306 QSharedPointer<EntityStorage<DomainType> > mStorage;
307 DomainTypeAdaptorFactoryInterface::Ptr mDomainTypeAdaptorFactory; 420 DomainTypeAdaptorFactoryInterface::Ptr mDomainTypeAdaptorFactory;
308 QByteArray mResourceInstanceIdentifier; 421 QByteArray mResourceInstanceIdentifier;
309}; 422};
diff --git a/common/facadeinterface.h b/common/facadeinterface.h
index 3a38db8..571a1e8 100644
--- a/common/facadeinterface.h
+++ b/common/facadeinterface.h
@@ -45,7 +45,7 @@ public:
45 virtual KAsync::Job<void> create(const DomainType &domainObject) = 0; 45 virtual KAsync::Job<void> create(const DomainType &domainObject) = 0;
46 virtual KAsync::Job<void> modify(const DomainType &domainObject) = 0; 46 virtual KAsync::Job<void> modify(const DomainType &domainObject) = 0;
47 virtual KAsync::Job<void> remove(const DomainType &domainObject) = 0; 47 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; 48 virtual KAsync::Job<void> load(const Query &query, const QSharedPointer<Akonadi2::ResultProviderInterface<typename DomainType::Ptr> > &resultProvider) = 0;
49}; 49};
50 50
51template<class DomainType> 51template<class DomainType>
@@ -67,7 +67,7 @@ public:
67 return KAsync::error<void>(-1, "Failed to create a facade"); 67 return KAsync::error<void>(-1, "Failed to create a facade");
68 } 68 }
69 69
70 KAsync::Job<void> load(const Query &query, const QSharedPointer<Akonadi2::ResultProvider<typename DomainType::Ptr> > &resultProvider) 70 KAsync::Job<void> load(const Query &query, const QSharedPointer<Akonadi2::ResultProviderInterface<typename DomainType::Ptr> > &resultProvider)
71 { 71 {
72 return KAsync::error<void>(-1, "Failed to create a facade"); 72 return KAsync::error<void>(-1, "Failed to create a facade");
73 } 73 }
diff --git a/common/modelresult.h b/common/modelresult.h
index c23c41e..756f4d6 100644
--- a/common/modelresult.h
+++ b/common/modelresult.h
@@ -24,11 +24,10 @@
24#include <QModelIndex> 24#include <QModelIndex>
25#include <QDebug> 25#include <QDebug>
26#include "query.h" 26#include "query.h"
27#include "clientapi.h"
28 27
29#include "resultprovider.h" 28#include "resultprovider.h"
30 29
31template<class T> 30template<class T, class Ptr>
32class ModelResult : public QAbstractItemModel 31class ModelResult : public QAbstractItemModel
33{ 32{
34public: 33public:
@@ -79,13 +78,18 @@ public:
79 return createIndex(row, column, childId); 78 return createIndex(row, column, childId);
80 } 79 }
81 80
81 QModelIndex createIndexFromId(const qint64 &id) const
82 {
83 auto grandParentId = mParents.value(id, 0);
84 auto row = mTree.value(grandParentId).indexOf(id);
85 return createIndex(row, 0, id);
86 }
87
82 QModelIndex parent(const QModelIndex &index) const 88 QModelIndex parent(const QModelIndex &index) const
83 { 89 {
84 auto id = getIdentifier(index); 90 auto id = getIdentifier(index);
85 auto parentId = mParents.value(id); 91 auto parentId = mParents.value(id);
86 auto grandParentId = mParents.value(parentId, 0); 92 return createIndexFromId(parentId);
87 auto row = mTree.value(grandParentId).indexOf(parentId);
88 return createIndex(row, 0, parentId);
89 } 93 }
90 94
91 bool canFetchMore(const QModelIndex &parent) const 95 bool canFetchMore(const QModelIndex &parent) const
@@ -98,83 +102,92 @@ public:
98 fetchEntities(parent); 102 fetchEntities(parent);
99 } 103 }
100 104
105 qint64 parentId(const Ptr &value)
106 {
107 return qHash(value->getProperty("parent").toByteArray());
108 }
109
110 void add(const Ptr &value)
111 {
112 auto childId = qHash(value->identifier());
113 auto id = parentId(value);
114 auto parent = createIndexFromId(id);
115 qDebug() << "Added entity " << childId;
116 const auto keys = mTree[id];
117 int index = 0;
118 for (; index < keys.size(); index++) {
119 if (childId < keys.at(index)) {
120 break;
121 }
122 }
123 beginInsertRows(parent, index, index);
124 mEntities.insert(childId, value);
125 mTree[id].insert(index, childId);
126 mParents.insert(childId, id);
127 endInsertRows();
128 }
129
130 void modify(const Ptr &value)
131 {
132 auto childId = qHash(value->identifier());
133 auto id = parentId(value);
134 auto parent = createIndexFromId(id);
135 qDebug() << "Modified entity" << childId;
136 auto i = mTree[id].indexOf(childId);
137 mEntities.remove(childId);
138 mEntities.insert(childId, value);
139 //TODO check for change of parents
140 auto idx = index(i, 0, parent);
141 emit dataChanged(idx, idx);
142 }
143
144 void remove(const Ptr &value)
145 {
146 auto childId = qHash(value->identifier());
147 auto id = parentId(value);
148 auto parent = createIndexFromId(id);
149 qDebug() << "Removed entity" << childId;
150 auto index = mTree[id].indexOf(qHash(value->identifier()));
151 beginRemoveRows(parent, index, index);
152 mEntities.remove(childId);
153 mTree[id].removeAll(childId);
154 mParents.remove(childId);
155 //TODO remove children
156 endRemoveRows();
157 }
158
101 void fetchEntities(const QModelIndex &parent) 159 void fetchEntities(const QModelIndex &parent)
102 { 160 {
103 qDebug() << "Fetching entities"; 161 qDebug() << "Fetching entities";
104 const auto id = getIdentifier(parent); 162 const auto id = getIdentifier(parent);
105 // beginResetModel();
106 // mEntities.remove(id);
107 mEntityChildrenFetched[id] = true; 163 mEntityChildrenFetched[id] = true;
108 auto query = mQuery; 164 QByteArray parentIdentifier;
109 if (!parent.isValid()) { 165 if (!parent.isValid()) {
110 qDebug() << "no parent"; 166 qDebug() << "no parent";
111 query.propertyFilter.insert("parent", QByteArray());
112 } else { 167 } else {
113 qDebug() << "parent is valid"; 168 qDebug() << "parent is valid";
114 auto object = parent.data(DomainObjectRole).template value<typename T::Ptr>(); 169 auto object = parent.data(DomainObjectRole).template value<Ptr>();
115 Q_ASSERT(object); 170 Q_ASSERT(object);
116 query.propertyFilter.insert("parent", object->identifier()); 171 parentIdentifier = object->identifier();
117 } 172 }
118 auto emitter = Akonadi2::Store::load<T>(query); 173 Trace() << "Loading entities";
119 emitter->onAdded([this, id, parent](const typename T::Ptr &value) { 174 loadEntities(parentIdentifier);
120 auto childId = qHash(value->identifier()); 175 }
121 qDebug() << "Added entity " << childId; 176
122 const auto keys = mTree[id]; 177 void setFetcher(const std::function<void(const QByteArray &parent)> &fetcher)
123 int index = 0; 178 {
124 for (; index < keys.size(); index++) { 179 Trace() << "Setting fetcher";
125 if (childId < keys.at(index)) { 180 loadEntities = fetcher;
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 } 181 }
169 182
170private: 183private:
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> 184 //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; 185 QMap<qint64 /* entity id */, Ptr> mEntities;
174 QMap<qint64 /* parent entity id */, QList<qint64> /* child entity id*/> mTree; 186 QMap<qint64 /* parent entity id */, QList<qint64> /* child entity id*/> mTree;
175 QMap<qint64 /* child entity id */, qint64 /* parent entity id*/> mParents; 187 QMap<qint64 /* child entity id */, qint64 /* parent entity id*/> mParents;
176 QMap<qint64 /* entity id */, bool> mEntityChildrenFetched; 188 QMap<qint64 /* entity id */, bool> mEntityChildrenFetched;
177 QList<QByteArray> mPropertyColumns; 189 QList<QByteArray> mPropertyColumns;
178 Akonadi2::Query mQuery; 190 Akonadi2::Query mQuery;
191 std::function<void(const QByteArray &)> loadEntities;
179}; 192};
180 193
diff --git a/common/resourcefacade.cpp b/common/resourcefacade.cpp
index 54185f8..0b7c5a3 100644
--- a/common/resourcefacade.cpp
+++ b/common/resourcefacade.cpp
@@ -54,7 +54,7 @@ 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) 57KAsync::Job<void> ResourceFacade::load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProviderInterface<typename Akonadi2::ApplicationDomain::AkonadiResource::Ptr> > &resultProvider)
58{ 58{
59 return KAsync::start<void>([query, resultProvider]() { 59 return KAsync::start<void>([query, resultProvider]() {
60 const auto configuredResources = ResourceConfig::getResources(); 60 const auto configuredResources = ResourceConfig::getResources();
diff --git a/common/resourcefacade.h b/common/resourcefacade.h
index 437ff75..850d380 100644
--- a/common/resourcefacade.h
+++ b/common/resourcefacade.h
@@ -37,5 +37,5 @@ 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 KAsync::Job<void> load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProviderInterface<typename Akonadi2::ApplicationDomain::AkonadiResource::Ptr> > &resultProvider) Q_DECL_OVERRIDE;
41}; 41};
diff --git a/common/resultprovider.h b/common/resultprovider.h
index bc03152..43d21a4 100644
--- a/common/resultprovider.h
+++ b/common/resultprovider.h
@@ -34,11 +34,312 @@ namespace Akonadi2 {
34template<class T> 34template<class T>
35class ResultEmitter; 35class ResultEmitter;
36 36
37template<class T>
38class ResultProviderInterface
39{
40public:
41 ResultProviderInterface()
42 : mRevision(0)
43 {
44
45 }
46
47 virtual void add(const T &value) = 0;
48 virtual void modify(const T &value) = 0;
49 virtual void remove(const T &value) = 0;
50 virtual void initialResultSetComplete() = 0;
51 virtual void complete() = 0;
52 virtual void clear() = 0;
53 virtual void setFetcher(const std::function<void(const QByteArray &parent)> &fetcher)
54 {
55 }
56
57 virtual void setFacade(const std::shared_ptr<void> &facade) = 0;
58 virtual void setQueryRunner(const QSharedPointer<QObject> &runner) = 0;
59
60 void setRevision(qint64 revision)
61 {
62 mRevision = revision;
63 }
64
65 qint64 revision() const
66 {
67 return mRevision;
68 }
69
70private:
71 qint64 mRevision;
72};
73
74template<class T, class Ptr>
75class ModelResultProvider : public ResultProviderInterface<Ptr> {
76public:
77 ModelResultProvider(QWeakPointer<ModelResult<T, Ptr> > model)
78 : ResultProviderInterface<Ptr>(),
79 mModel(model)
80 {
81
82 }
83
84 void add(const Ptr &value)
85 {
86 if (auto model = mModel.toStrongRef()) {
87 model->add(value);
88 }
89 }
90
91 void modify(const Ptr &value)
92 {
93 if (auto model = mModel.toStrongRef()) {
94 model->modify(value);
95 }
96 }
97
98 void remove(const Ptr &value)
99 {
100 if (auto model = mModel.toStrongRef()) {
101 model->remove(value);
102 }
103 }
104
105 void initialResultSetComplete()
106 {
107 // mResultEmitter->initialResultSetComplete();
108 }
109
110 void complete()
111 {
112 // mResultEmitter->complete();
113 }
114
115 void clear()
116 {
117 // mResultEmitter->clear();
118 }
119
120 // QSharedPointer<ResultEmitter<T> > emitter()
121 // {
122 // if (!mResultEmitter) {
123 // //We have to go over a separate var and return that, otherwise we'd delete the emitter immediately again
124 // auto sharedPtr = QSharedPointer<ResultEmitter<T> >(new ResultEmitter<T>, [this](ResultEmitter<T> *emitter){ done(); delete emitter; });
125 // mResultEmitter = sharedPtr;
126 // return sharedPtr;
127 // }
128 //
129 // return mResultEmitter.toStrongRef();
130 // }
131
132 /**
133 * For lifetimemanagement only.
134 * We keep the runner alive as long as the result provider exists.
135 */
136 void setFacade(const std::shared_ptr<void> &facade)
137 {
138 mFacade = facade;
139 }
140
141 void onDone(const std::function<void()> &callback)
142 {
143 mOnDoneCallback = callback;
144 }
145
146 bool isDone() const
147 {
148 //The existance of the emitter currently defines wether we're done or not.
149 // return mResultEmitter.toStrongRef().isNull();
150 return true;
151 }
152
153 void setFetcher(const std::function<void(const QByteArray &parent)> &fetcher)
154 {
155 if (auto model = mModel.toStrongRef()) {
156 model->setFetcher(fetcher);
157 }
158 }
159
160 void setQueryRunner(const QSharedPointer<QObject> &runner)
161 {
162 mQueryRunner = runner;
163 }
164
165 // qint64 fetch(const ResultSet &resultSet)
166 // {
167 // //Fetch a bunch
168 // //
169 // // Akonadi2::Storage storage(Akonadi2::storageLocation(), mResourceInstanceIdentifier);
170 // // storage.setDefaultErrorHandler([](const Akonadi2::Storage::Error &error) {
171 // // Warning() << "Error during query: " << error.store << error.message;
172 // // });
173 // //
174 // // auto transaction = storage.createTransaction(Akonadi2::Storage::ReadOnly);
175 //
176 // // Log() << "Querying" << baseRevision << Akonadi2::Storage::maxRevision(transaction);
177 // // auto resultSet = getResultSet(query, transaction, baseRevision);
178 // while (resultSet.next([this](const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &value, Akonadi2::Operation operation) -> bool {
179 // switch (operation) {
180 // case Akonadi2::Operation_Creation:
181 // Trace() << "Got creation";
182 // //TODO Only copy in result provider
183 // add(Akonadi2::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<T>(*value).template staticCast<T>());
184 // break;
185 // case Akonadi2::Operation_Modification:
186 // Trace() << "Got modification";
187 // modify(Akonadi2::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<T>(*value).template staticCast<T>());
188 // break;
189 // case Akonadi2::Operation_Removal:
190 // Trace() << "Got removal";
191 // remove(Akonadi2::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<T>(*value).template staticCast<T>());
192 // break;
193 // }
194 // return true;
195 // })){};
196 // // return Akonadi2::Storage::maxRevision(transaction);
197 // }
198
199private:
200 void done()
201 {
202 qWarning() << "done";
203 if (mOnDoneCallback) {
204 mOnDoneCallback();
205 mOnDoneCallback = std::function<void()>();
206 }
207 }
208
209 QWeakPointer<ModelResult<T, Ptr> > mModel;
210 QSharedPointer<QObject> mQueryRunner;
211 std::shared_ptr<void> mFacade;
212 std::function<void()> mOnDoneCallback;
213};
214
215
216
217
218
219
220template<class T>
221class SyncResultProvider : public ResultProviderInterface<T> {
222public:
223 void add(const T &value)
224 {
225 mResultEmitter->addHandler(value);
226 }
227
228 void modify(const T &value)
229 {
230 mResultEmitter->modifyHandler(value);
231 }
232
233 void remove(const T &value)
234 {
235 mResultEmitter->removeHandler(value);
236 }
237
238 void initialResultSetComplete()
239 {
240 mResultEmitter->initialResultSetComplete();
241 }
242
243 void complete()
244 {
245 mResultEmitter->complete();
246 }
247
248 void clear()
249 {
250 mResultEmitter->clear();
251 }
252
253 QSharedPointer<ResultEmitter<T> > emitter()
254 {
255 if (!mResultEmitter) {
256 //We have to go over a separate var and return that, otherwise we'd delete the emitter immediately again
257 auto sharedPtr = QSharedPointer<ResultEmitter<T> >(new ResultEmitter<T>, [this](ResultEmitter<T> *emitter){ done(); delete emitter; });
258 mResultEmitter = sharedPtr;
259 return sharedPtr;
260 }
261
262 return mResultEmitter.toStrongRef();
263 }
264
265 /**
266 * For lifetimemanagement only.
267 * We keep the runner alive as long as the result provider exists.
268 */
269 void setFacade(const std::shared_ptr<void> &facade)
270 {
271 mFacade = facade;
272 }
273
274 void onDone(const std::function<void()> &callback)
275 {
276 mOnDoneCallback = callback;
277 }
278
279 bool isDone() const
280 {
281 //The existance of the emitter currently defines wether we're done or not.
282 return mResultEmitter.toStrongRef().isNull();
283 }
284
285 // qint64 fetch(const ResultSet &resultSet)
286 // {
287 // //Fetch a bunch
288 // //
289 // // Akonadi2::Storage storage(Akonadi2::storageLocation(), mResourceInstanceIdentifier);
290 // // storage.setDefaultErrorHandler([](const Akonadi2::Storage::Error &error) {
291 // // Warning() << "Error during query: " << error.store << error.message;
292 // // });
293 // //
294 // // auto transaction = storage.createTransaction(Akonadi2::Storage::ReadOnly);
295 //
296 // // Log() << "Querying" << baseRevision << Akonadi2::Storage::maxRevision(transaction);
297 // // auto resultSet = getResultSet(query, transaction, baseRevision);
298 // while (resultSet.next([this](const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &value, Akonadi2::Operation operation) -> bool {
299 // switch (operation) {
300 // case Akonadi2::Operation_Creation:
301 // Trace() << "Got creation";
302 // //TODO Only copy in result provider
303 // add(Akonadi2::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<T>(*value).template staticCast<T>());
304 // break;
305 // case Akonadi2::Operation_Modification:
306 // Trace() << "Got modification";
307 // modify(Akonadi2::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<T>(*value).template staticCast<T>());
308 // break;
309 // case Akonadi2::Operation_Removal:
310 // Trace() << "Got removal";
311 // remove(Akonadi2::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<T>(*value).template staticCast<T>());
312 // break;
313 // }
314 // return true;
315 // })){};
316 // // return Akonadi2::Storage::maxRevision(transaction);
317 // }
318
319private:
320 void done()
321 {
322 qWarning() << "done";
323 if (mOnDoneCallback) {
324 mOnDoneCallback();
325 mOnDoneCallback = std::function<void()>();
326 }
327 }
328
329 QWeakPointer<ResultEmitter<T> > mResultEmitter;
330 std::shared_ptr<void> mFacade;
331 std::function<void()> mOnDoneCallback;
332 QSharedPointer<ThreadBoundary> mThreadBoundary;
333};
334
335
336
337
37/* 338/*
38* The promise side for the result emitter 339* The promise side for the result emitter
39*/ 340*/
40template<class T> 341template<class T>
41class ResultProvider { 342class ResultProvider : public ResultProviderInterface<T> {
42private: 343private:
43 void callInMainThreadOnEmitter(void (ResultEmitter<T>::*f)()) 344 void callInMainThreadOnEmitter(void (ResultEmitter<T>::*f)())
44 { 345 {
diff --git a/examples/dummyresource/resourcefacade.cpp b/examples/dummyresource/resourcefacade.cpp
index df805e4..1090757 100644
--- a/examples/dummyresource/resourcefacade.cpp
+++ b/examples/dummyresource/resourcefacade.cpp
@@ -65,7 +65,7 @@ KAsync::Job<void> DummyResourceConfigFacade::remove(const Akonadi2::ApplicationD
65 return KAsync::null<void>(); 65 return KAsync::null<void>();
66} 66}
67 67
68KAsync::Job<void> DummyResourceConfigFacade::load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProvider<typename Akonadi2::ApplicationDomain::AkonadiResource::Ptr> > &resultProvider) 68KAsync::Job<void> DummyResourceConfigFacade::load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProviderInterface<typename Akonadi2::ApplicationDomain::AkonadiResource::Ptr> > &resultProvider)
69{ 69{
70 //Read configuration and list all available instances. 70 //Read configuration and list all available instances.
71 //This includes runtime information about runing instances etc. 71 //This includes runtime information about runing instances etc.
diff --git a/examples/dummyresource/resourcefacade.h b/examples/dummyresource/resourcefacade.h
index 5a5f46b..aa2ab05 100644
--- a/examples/dummyresource/resourcefacade.h
+++ b/examples/dummyresource/resourcefacade.h
@@ -42,7 +42,7 @@ public:
42 //Remove instance 42 //Remove instance
43 KAsync::Job<void> remove(const Akonadi2::ApplicationDomain::AkonadiResource &domainObject) Q_DECL_OVERRIDE; 43 KAsync::Job<void> remove(const Akonadi2::ApplicationDomain::AkonadiResource &domainObject) Q_DECL_OVERRIDE;
44 //Read configuration and available instances 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; 45 KAsync::Job<void> load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProviderInterface<typename Akonadi2::ApplicationDomain::AkonadiResource::Ptr> > &resultProvider) Q_DECL_OVERRIDE;
46 46
47private: 47private:
48 QSharedPointer<QSettings> getSettings(); 48 QSharedPointer<QSettings> getSettings();
diff --git a/tests/clientapitest.cpp b/tests/clientapitest.cpp
index 5bfad4b..2b3cc46 100644
--- a/tests/clientapitest.cpp
+++ b/tests/clientapitest.cpp
@@ -27,7 +27,7 @@ public:
27 KAsync::Job<void> create(const T &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); }; 27 KAsync::Job<void> create(const T &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); };
28 KAsync::Job<void> modify(const T &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); }; 28 KAsync::Job<void> modify(const T &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); };
29 KAsync::Job<void> remove(const T &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); }; 29 KAsync::Job<void> remove(const T &domainObject) Q_DECL_OVERRIDE { return KAsync::null<void>(); };
30 KAsync::Job<void> load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProvider<typename T::Ptr> > &resultProvider) Q_DECL_OVERRIDE 30 KAsync::Job<void> load(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::ResultProviderInterface<typename T::Ptr> > &resultProvider) Q_DECL_OVERRIDE
31 { 31 {
32 capturedResultProvider = resultProvider; 32 capturedResultProvider = resultProvider;
33 return KAsync::start<void>([this, resultProvider, query]() { 33 return KAsync::start<void>([this, resultProvider, query]() {
@@ -41,7 +41,7 @@ public:
41 } 41 }
42 42
43 QList<typename T::Ptr> results; 43 QList<typename T::Ptr> results;
44 QWeakPointer<Akonadi2::ResultProvider<typename T::Ptr> > capturedResultProvider; 44 QWeakPointer<Akonadi2::ResultProviderInterface<typename T::Ptr> > capturedResultProvider;
45}; 45};
46 46
47 47