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