diff options
author | Aaron Seigo <aseigo@kde.org> | 2014-12-16 08:25:14 +0100 |
---|---|---|
committer | Aaron Seigo <aseigo@kde.org> | 2014-12-16 08:25:14 +0100 |
commit | ed7fdbcf6beb40960cf91555ae1d506278b852d8 (patch) | |
tree | 466b262e1d9c2efd63acb44170bdd1add1b98469 /common/clientapi.h | |
parent | 52787e9469cc1892e9e221b877d0ffd81d7a817a (diff) | |
download | sink-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.h | 465 |
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 | |||
34 | namespace 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 | |||
168 | namespace 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 | */ | ||
180 | namespace Domain { | ||
181 | |||
182 | class AkonadiDomainType { | ||
183 | public: | ||
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 | |||
193 | private: | ||
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 | |||
202 | class Event : public AkonadiDomainType { | ||
203 | public: | ||
204 | typedef QSharedPointer<Event> Ptr; | ||
205 | Event(const QString &resource, const QString &identifier, qint64 revision):AkonadiDomainType(resource, identifier, revision){}; | ||
206 | |||
207 | }; | ||
208 | |||
209 | class Todo : public AkonadiDomainType { | ||
210 | public: | ||
211 | typedef QSharedPointer<Todo> Ptr; | ||
212 | }; | ||
213 | |||
214 | class Calendar : public AkonadiDomainType { | ||
215 | public: | ||
216 | typedef QSharedPointer<Calendar> Ptr; | ||
217 | }; | ||
218 | |||
219 | class Mail : public AkonadiDomainType { | ||
220 | }; | ||
221 | |||
222 | class 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 | |||
231 | template<class DomainType> | ||
232 | QString getTypeName(); | ||
233 | |||
234 | template<> | ||
235 | QString getTypeName<Event>() | ||
236 | { | ||
237 | return "event"; | ||
238 | } | ||
239 | |||
240 | template<> | ||
241 | QString getTypeName<Todo>() | ||
242 | { | ||
243 | return "todo"; | ||
244 | } | ||
245 | |||
246 | } | ||
247 | |||
248 | using 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 | */ | ||
263 | class Query | ||
264 | { | ||
265 | public: | ||
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 | */ | ||
285 | template<class DomainType> | ||
286 | class StoreFacade { | ||
287 | public: | ||
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 | |||
305 | class FacadeFactory { | ||
306 | public: | ||
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 | |||
354 | private: | ||
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 | */ | ||
363 | class Store { | ||
364 | public: | ||
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 | |||
438 | class Resource | ||
439 | { | ||
440 | public: | ||
441 | Resource(); | ||
442 | virtual ~Resource(); | ||
443 | |||
444 | private: | ||
445 | class Private; | ||
446 | Private * const d; | ||
447 | }; | ||
448 | |||
449 | class ResourceFactory : public QObject | ||
450 | { | ||
451 | public: | ||
452 | ResourceFactory(QObject *parent); | ||
453 | virtual ~ResourceFactory(); | ||
454 | |||
455 | virtual Resource *createResource() = 0; | ||
456 | virtual void registerFacade(FacadeFactory &factory) = 0; | ||
457 | |||
458 | private: | ||
459 | class Private; | ||
460 | Private * const d; | ||
461 | }; | ||
462 | } | ||
463 | |||
464 | Q_DECLARE_INTERFACE(Akonadi2::ResourceFactory, "org.kde.akonadi2.resourcefactory") | ||
465 | |||