summaryrefslogtreecommitdiffstats
path: root/client/clientapi.h
diff options
context:
space:
mode:
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