summaryrefslogtreecommitdiffstats
path: root/common/clientapi.h
diff options
context:
space:
mode:
authorAaron Seigo <aseigo@kde.org>2014-12-16 08:25:14 +0100
committerAaron Seigo <aseigo@kde.org>2014-12-16 08:25:14 +0100
commited7fdbcf6beb40960cf91555ae1d506278b852d8 (patch)
tree466b262e1d9c2efd63acb44170bdd1add1b98469 /common/clientapi.h
parent52787e9469cc1892e9e221b877d0ffd81d7a817a (diff)
downloadsink-ed7fdbcf6beb40960cf91555ae1d506278b852d8.tar.gz
sink-ed7fdbcf6beb40960cf91555ae1d506278b852d8.zip
move client classes into akonadi2common and add the base class for resource plugins
we can divide up libakonadi2common later once we have a full collection of classes this makes writing code a bit simpler now as we don't have to figuer out which libraries to link against or how class dependencies should look. when we have more infrastructure in place this will mostly become self-evident
Diffstat (limited to 'common/clientapi.h')
-rw-r--r--common/clientapi.h465
1 files changed, 465 insertions, 0 deletions
diff --git a/common/clientapi.h b/common/clientapi.h
new file mode 100644
index 0000000..3531462
--- /dev/null
+++ b/common/clientapi.h
@@ -0,0 +1,465 @@
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 <QString>
24#include <QSet>
25#include <QSharedPointer>
26#include <QStandardPaths>
27#include <QTimer>
28#include <QDebug>
29#include <QEventLoop>
30#include <QtConcurrent/QtConcurrentRun>
31#include <functional>
32#include "threadboundary.h"
33
34namespace async {
35 //This should abstract if we execute from eventloop or in thread.
36 //It supposed to allow the caller to finish the current method before executing the runner.
37 void run(const std::function<void()> &runner) {
38 QtConcurrent::run(runner);
39
40 // //FIXME we should be using a Job instead of a timer
41 // auto timer = new QTimer;
42 // timer->setSingleShot(true);
43 // QObject::connect(timer, &QTimer::timeout, runner);
44 // QObject::connect(timer, &QTimer::timeout, timer, &QObject::deleteLater);
45 // timer->start(0);
46 };
47
48 /**
49 * Query result set
50 */
51
52 template<class T>
53 class ResultEmitter;
54
55 /*
56 * The promise side for the result emitter
57 */
58 template<class T>
59 class ResultProvider {
60 public:
61 //Called from worker thread
62 void add(const T &value)
63 {
64 //We use the eventloop to call the addHandler directly from the main eventloop.
65 //That way the result emitter implementation doesn't have to care about threadsafety at all.
66 //The alternative would be to make all handlers of the emitter threadsafe.
67 auto emitter = mResultEmitter;
68 mResultEmitter->mThreadBoundary.callInMainThread([emitter, value]() {
69 if (emitter) {
70 emitter->addHandler(value);
71 }
72 });
73 }
74
75 //Called from worker thread
76 void complete()
77 {
78 auto emitter = mResultEmitter;
79 mResultEmitter->mThreadBoundary.callInMainThread([emitter]() {
80 if (emitter) {
81 emitter->completeHandler();
82 }
83 });
84 }
85
86 QSharedPointer<ResultEmitter<T> > emitter()
87 {
88 mResultEmitter = QSharedPointer<ResultEmitter<T> >(new ResultEmitter<T>());
89 return mResultEmitter;
90 }
91
92 private:
93 QSharedPointer<ResultEmitter<T> > mResultEmitter;
94 };
95
96 /*
97 * The future side for the client.
98 *
99 * It does not directly hold the state.
100 *
101 * The advantage of this is that we can specialize it to:
102 * * do inline transformations to the data
103 * * directly store the state in a suitable datastructure: QList, QSet, std::list, QVector, ...
104 * * build async interfaces with signals
105 * * build sync interfaces that block when accessing the value
106 *
107 * TODO: This should probably be merged with daniels futurebase used in Async
108 */
109 template<class DomainType>
110 class ResultEmitter {
111 public:
112 void onAdded(const std::function<void(const DomainType&)> &handler)
113 {
114 addHandler = handler;
115 }
116 // void onRemoved(const std::function<void(const T&)> &handler);
117 void onComplete(const std::function<void(void)> &handler)
118 {
119 completeHandler = handler;
120 }
121
122 private:
123 friend class ResultProvider<DomainType>;
124 std::function<void(const DomainType&)> addHandler;
125 // std::function<void(const T&)> removeHandler;
126 std::function<void(void)> completeHandler;
127 ThreadBoundary mThreadBoundary;
128 };
129
130
131 /*
132 * A result set specialization that provides a syncronous list
133 */
134 template<class T>
135 class SyncListResult : public QList<T> {
136 public:
137 SyncListResult(const QSharedPointer<ResultEmitter<T> > &emitter)
138 :QList<T>(),
139 mComplete(false),
140 mEmitter(emitter)
141 {
142 emitter->onAdded([this](const T &value) {
143 this->append(value);
144 });
145 emitter->onComplete([this]() {
146 mComplete = true;
147 auto loop = mWaitLoop.toStrongRef();
148 if (loop) {
149 loop->quit();
150 }
151 });
152 }
153
154 void exec()
155 {
156 auto loop = QSharedPointer<QEventLoop>::create();
157 mWaitLoop = loop;
158 loop->exec(QEventLoop::ExcludeUserInputEvents);
159 }
160
161 private:
162 bool mComplete;
163 QWeakPointer<QEventLoop> mWaitLoop;
164 QSharedPointer<ResultEmitter<T> > mEmitter;
165 };
166}
167
168namespace Akonadi2 {
169
170/**
171 * Standardized Domain Types
172 *
173 * The don't adhere to any standard and can be freely extended
174 * Their sole purpose is providing a standardized interface to access data.
175 *
176 * This is necessary to decouple resource-backends from application domain containers (otherwise each resource would have to provide a faceade for each application domain container).
177 *
178 * These types will be frequently modified (for every new feature that should be exposed to the any client)
179 */
180namespace Domain {
181
182class AkonadiDomainType {
183public:
184 AkonadiDomainType(const QString &resourceName, const QString &identifier, qint64 revision)
185 : mResourceName(resourceName),
186 mIdentifier(identifier),
187 mRevision(revision)
188 {
189 }
190
191 virtual QVariant getProperty(const QString &key){ return QVariant(); }
192
193private:
194 /*
195 * Each domain object needs to store the resource, identifier, revision triple so we can link back to the storage location.
196 */
197 QString mResourceName;
198 QString mIdentifier;
199 qint64 mRevision;
200};
201
202class Event : public AkonadiDomainType {
203public:
204 typedef QSharedPointer<Event> Ptr;
205 Event(const QString &resource, const QString &identifier, qint64 revision):AkonadiDomainType(resource, identifier, revision){};
206
207};
208
209class Todo : public AkonadiDomainType {
210public:
211 typedef QSharedPointer<Todo> Ptr;
212};
213
214class Calendar : public AkonadiDomainType {
215public:
216 typedef QSharedPointer<Calendar> Ptr;
217};
218
219class Mail : public AkonadiDomainType {
220};
221
222class Folder : public AkonadiDomainType {
223};
224
225/**
226 * All types need to be registered here an MUST return a different name.
227 *
228 * Do not store these types to disk, they may change over time.
229 */
230
231template<class DomainType>
232QString getTypeName();
233
234template<>
235QString getTypeName<Event>()
236{
237 return "event";
238}
239
240template<>
241QString getTypeName<Todo>()
242{
243 return "todo";
244}
245
246}
247
248using namespace async;
249
250/**
251 * A query that matches a set of objects
252 *
253 * The query will have to be updated regularly similary to the domain objects.
254 * It probably also makes sense to have a domain specific part of the query,
255 * such as what properties we're interested in (necessary information for on-demand
256 * loading of data).
257 *
258 * The query defines:
259 * * what resources to search
260 * * filters on various properties (parent collection, startDate range, ....)
261 * * properties we need (for on-demand querying)
262 */
263class Query
264{
265public:
266 //Could also be a propertyFilter
267 QStringList resources;
268 //Could also be a propertyFilter
269 QStringList ids;
270 //Filters to apply
271 QHash<QString, QVariant> propertyFilter;
272 //Properties to retrieve
273 QSet<QString> requestedProperties;
274};
275
276
277/**
278 * Interface for the store facade.
279 *
280 * All methods are synchronous.
281 * Facades are stateful (they hold connections to resources and database).
282 *
283 * TODO: would it make sense to split the write, read and notification parts? (we could potentially save some connections)
284 */
285template<class DomainType>
286class StoreFacade {
287public:
288 virtual ~StoreFacade(){};
289 virtual void create(const DomainType &domainObject) = 0;
290 virtual void modify(const DomainType &domainObject) = 0;
291 virtual void remove(const DomainType &domainObject) = 0;
292 virtual void load(const Query &query, const std::function<void(const typename DomainType::Ptr &)> &resultCallback) = 0;
293};
294
295
296/**
297 * Facade factory that returns a store facade implementation, by loading a plugin and providing the relevant implementation.
298 *
299 * If we were to provide default implementations for certain capabilities. Here would be the place to do so.
300 *
301 * TODO: pluginmechansims for resources to provide their implementations.
302 * * We may want a way to recycle facades to avoid recreating socket connections all the time?
303 */
304
305class FacadeFactory {
306public:
307 //FIXME: proper singleton implementation
308 static FacadeFactory &instance()
309 {
310 static FacadeFactory factory;
311 return factory;
312 }
313
314 static QString key(const QString &resource, const QString &type)
315 {
316 return resource + type;
317 }
318
319 template<class DomainType, class Facade>
320 void registerFacade(const QString &resource)
321 {
322 const QString typeName = Domain::getTypeName<DomainType>();
323 mFacadeRegistry.insert(key(resource, typeName), [](){ return new Facade; });
324 }
325
326 /*
327 * Allows the registrar to register a specific instance.
328 *
329 * Primarily for testing.
330 * The facade factory takes ovnership of the poniter and typically deletes the instance via shared pointer.
331 * Supplied factory functions should therefore always return a new pointer (i.e. via clone())
332 *
333 * FIXME the factory function should really be returning QSharedPointer<void>, which doesn't work (std::shared_pointer<void> would though). That way i.e. a test could keep the object alive until it's done.
334 */
335 template<class DomainType, class Facade>
336 void registerFacade(const QString &resource, const std::function<void*(void)> &customFactoryFunction)
337 {
338 const QString typeName = Domain::getTypeName<DomainType>();
339 mFacadeRegistry.insert(key(resource, typeName), customFactoryFunction);
340 }
341
342 template<class DomainType>
343 QSharedPointer<StoreFacade<DomainType> > getFacade(const QString &resource)
344 {
345 const QString typeName = Domain::getTypeName<DomainType>();
346 auto factoryFunction = mFacadeRegistry.value(key(resource, typeName));
347 if (factoryFunction) {
348 return QSharedPointer<StoreFacade<DomainType> >(static_cast<StoreFacade<DomainType>* >(factoryFunction()));
349 }
350 qWarning() << "Failed to find facade for resource: " << resource << " and type: " << typeName;
351 return QSharedPointer<StoreFacade<DomainType> >();
352 }
353
354private:
355 QHash<QString, std::function<void*(void)> > mFacadeRegistry;
356};
357
358/**
359 * Store interface used in the client API.
360 *
361 * TODO: For testing we need to be able to inject dummy StoreFacades. Should we work with a store instance, or a singleton factory?
362 */
363class Store {
364public:
365 static QString storageLocation()
366 {
367 return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/akonadi2";
368 }
369
370 /**
371 * Asynchronusly load a dataset
372 */
373 template <class DomainType>
374 static QSharedPointer<ResultEmitter<typename DomainType::Ptr> > load(Query query)
375 {
376 QSharedPointer<ResultProvider<typename DomainType::Ptr> > resultSet(new ResultProvider<typename DomainType::Ptr>);
377
378 //Execute the search in a thread.
379 //We must guarantee that the emitter is returned before the first result is emitted.
380 //The result provider must be threadsafe.
381 async::run([resultSet, query](){
382 // Query all resources and aggregate results
383 // query tells us in which resources we're interested
384 // TODO: queries to individual resources could be parallelized
385 for(const QString &resource : query.resources) {
386 auto facade = FacadeFactory::instance().getFacade<DomainType>(resource);
387 //We have to bind an instance to the function callback. Since we use a shared pointer this keeps the result provider instance (and thus also the emitter) alive.
388 std::function<void(const typename DomainType::Ptr &)> addCallback = std::bind(&ResultProvider<typename DomainType::Ptr>::add, resultSet, std::placeholders::_1);
389 facade->load(query, addCallback);
390 }
391 resultSet->complete();
392 });
393 return resultSet->emitter();
394 }
395
396 /**
397 * Asynchronusly load a dataset with tree structure information
398 */
399 // template <class DomainType>
400 // static TreeSet<DomainType> loadTree(Query)
401 // {
402
403 // }
404
405 /**
406 * Create a new entity.
407 */
408 template <class DomainType>
409 static void create(const DomainType &domainObject, const QString &resourceIdentifier) {
410 //Potentially move to separate thread as well
411 auto facade = FacadeFactory::instance().getFacade<DomainType>(resourceIdentifier);
412 facade.create(domainObject);
413 }
414
415 /**
416 * Modify an entity.
417 *
418 * This includes moving etc. since these are also simple settings on a property.
419 */
420 template <class DomainType>
421 static void modify(const DomainType &domainObject, const QString &resourceIdentifier) {
422 //Potentially move to separate thread as well
423 auto facade = FacadeFactory::instance().getFacade<DomainType>(resourceIdentifier);
424 facade.modify(domainObject);
425 }
426
427 /**
428 * Remove an entity.
429 */
430 template <class DomainType>
431 static void remove(const DomainType &domainObject, const QString &resourceIdentifier) {
432 //Potentially move to separate thread as well
433 auto facade = FacadeFactory::instance().getFacade<DomainType>(resourceIdentifier);
434 facade.remove(domainObject);
435 }
436};
437
438class Resource
439{
440public:
441 Resource();
442 virtual ~Resource();
443
444private:
445 class Private;
446 Private * const d;
447};
448
449class ResourceFactory : public QObject
450{
451public:
452 ResourceFactory(QObject *parent);
453 virtual ~ResourceFactory();
454
455 virtual Resource *createResource() = 0;
456 virtual void registerFacade(FacadeFactory &factory) = 0;
457
458private:
459 class Private;
460 Private * const d;
461};
462}
463
464Q_DECLARE_INTERFACE(Akonadi2::ResourceFactory, "org.kde.akonadi2.resourcefactory")
465