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