diff options
Diffstat (limited to 'client/clientapi.h')
-rw-r--r-- | client/clientapi.h | 387 |
1 files changed, 236 insertions, 151 deletions
diff --git a/client/clientapi.h b/client/clientapi.h index 4e98d05..4ec90e2 100644 --- a/client/clientapi.h +++ b/client/clientapi.h | |||
@@ -4,7 +4,6 @@ | |||
4 | #include <QSet> | 4 | #include <QSet> |
5 | #include <QSharedPointer> | 5 | #include <QSharedPointer> |
6 | #include <functional> | 6 | #include <functional> |
7 | #include "store/database.h" | ||
8 | 7 | ||
9 | namespace async { | 8 | namespace async { |
10 | //This should abstract if we execute from eventloop or in thread. | 9 | //This should abstract if we execute from eventloop or in thread. |
@@ -18,9 +17,74 @@ namespace async { | |||
18 | timer->start(0); | 17 | timer->start(0); |
19 | }; | 18 | }; |
20 | 19 | ||
20 | /** | ||
21 | * Query result set | ||
22 | * | ||
23 | * This should probably become part of a generic kasync library. | ||
24 | * | ||
25 | * Functional is nice because we don't have to store data in the emitter | ||
26 | * Non functional and storing may be the right thing because we want an in-memory representation of the set | ||
27 | * non-functional also allows us to batch move data across thread boundaries. | ||
28 | */ | ||
29 | |||
30 | template<class T> | ||
31 | class ResultEmitter; | ||
32 | |||
33 | /* | ||
34 | * The promise side for the result provider | ||
35 | */ | ||
36 | template<class T> | ||
37 | class ResultProvider { | ||
38 | public: | ||
39 | void add(const T &value) | ||
40 | { | ||
41 | //the handler will be called in the other thread, protect | ||
42 | mResultEmitter->addHandler(value); | ||
43 | } | ||
44 | |||
45 | void complete() | ||
46 | { | ||
47 | mResultEmitter->completeHandler(); | ||
48 | } | ||
49 | |||
50 | QSharedPointer<ResultEmitter<T> > emitter() | ||
51 | { | ||
52 | mResultEmitter = QSharedPointer<ResultEmitter<T> >(new ResultEmitter<T>()); | ||
53 | return mResultEmitter; | ||
54 | } | ||
55 | |||
56 | private: | ||
57 | QSharedPointer<ResultEmitter<T> > mResultEmitter; | ||
58 | }; | ||
59 | |||
60 | /* | ||
61 | * The future side for the client. | ||
62 | * | ||
63 | * It does not directly hold the state. | ||
64 | */ | ||
65 | template<class DomainType> | ||
66 | class ResultEmitter { | ||
67 | public: | ||
68 | void onAdded(const std::function<void(const DomainType&)> &handler) | ||
69 | { | ||
70 | addHandler = handler; | ||
71 | } | ||
72 | // void onRemoved(const std::function<void(const T&)> &handler); | ||
73 | void onComplete(const std::function<void(void)> &handler) | ||
74 | { | ||
75 | completeHandler = handler; | ||
76 | } | ||
77 | |||
78 | private: | ||
79 | friend class ResultProvider<DomainType>; | ||
80 | std::function<void(const DomainType&)> addHandler; | ||
81 | // std::function<void(const T&)> removeHandler; | ||
82 | std::function<void(void)> completeHandler; | ||
83 | }; | ||
84 | |||
21 | } | 85 | } |
22 | 86 | ||
23 | namespace ClientAPI { | 87 | namespace Akonadi2 { |
24 | 88 | ||
25 | /** | 89 | /** |
26 | * Standardized Domain Types | 90 | * Standardized Domain Types |
@@ -32,8 +96,10 @@ namespace ClientAPI { | |||
32 | * | 96 | * |
33 | * These types will be frequently modified (for every new feature that should be exposed to the any client) | 97 | * These types will be frequently modified (for every new feature that should be exposed to the any client) |
34 | */ | 98 | */ |
99 | namespace Domain { | ||
35 | 100 | ||
36 | class AkonadiDomainType { | 101 | class AkonadiDomainType { |
102 | public: | ||
37 | /* | 103 | /* |
38 | * Each domain object needs to store the resource, identifier, revision triple so we can link back to the storage location. | 104 | * Each domain object needs to store the resource, identifier, revision triple so we can link back to the storage location. |
39 | */ | 105 | */ |
@@ -58,87 +124,30 @@ class Folder : public AkonadiDomainType { | |||
58 | 124 | ||
59 | }; | 125 | }; |
60 | 126 | ||
61 | /* | ||
62 | * Resource and domain object specific | ||
63 | * FIXME: should we hardcode the requirement that the domain adapter is a subclass for the domain object? | ||
64 | * * how do we allow copying of domain objects? | ||
65 | * ** dummy domain object that is a wrapper? | ||
66 | * ** domain adapter has an accessor for the domain object to hide subclassing | ||
67 | */ | ||
68 | class EventDomainAdapter : public Event { | ||
69 | // virtual void setFoo(const QString &value) | ||
70 | // { | ||
71 | // mBuffer.setFoo(value); | ||
72 | // } | ||
73 | |||
74 | // virtual QString foo() const | ||
75 | // { | ||
76 | // return mBuffer.foo(); | ||
77 | // } | ||
78 | |||
79 | // MessageBuffer mBuffer; | ||
80 | }; | ||
81 | |||
82 | |||
83 | |||
84 | /** | 127 | /** |
85 | * Query result set | 128 | * All types need to be registered here an MUST return a different name. |
86 | * | 129 | * |
87 | * This should probably become part of a generic kasync library. | 130 | * Do not store these types to disk, they may change over time. |
88 | * | ||
89 | * Functional is nice because we don't have to store data in the emitter | ||
90 | * Non functional and storing may be the right thing because we want an in-memory representation of the set | ||
91 | * non-functional also allows us to batch move data across thread boundaries. | ||
92 | */ | ||
93 | |||
94 | template<class T> | ||
95 | class ResultEmitter; | ||
96 | |||
97 | /* | ||
98 | * The promise side for the result provider | ||
99 | */ | 131 | */ |
100 | template<class T> | ||
101 | class ResultProvider { | ||
102 | public: | ||
103 | void add(const T &value) | ||
104 | { | ||
105 | //the handler will be called in the other thread, protect | ||
106 | mResultEmitter->addHandler(value); | ||
107 | } | ||
108 | |||
109 | QSharedPointer<ResultEmitter<T> > emitter() | ||
110 | { | ||
111 | mResultEmitter = QSharedPointer<ResultEmitter<T> >(new ResultEmitter<T>()); | ||
112 | return emitter; | ||
113 | } | ||
114 | 132 | ||
115 | private: | 133 | template<class DomainType> |
116 | QSharedPointer<ResultEmitter<T> > mResultEmitter; | 134 | QString getTypeName(); |
117 | }; | ||
118 | |||
119 | /* | ||
120 | * The future side for the client. | ||
121 | * | ||
122 | * It does not directly hold the state. | ||
123 | */ | ||
124 | template<class T> | ||
125 | class ResultEmitter { | ||
126 | public: | ||
127 | void onAdded(const std::function<void(const T&)> &handler); | ||
128 | // void onRemoved(const std::function<void(const T&)> &handler); | ||
129 | 135 | ||
130 | private: | 136 | template<> |
131 | friend class SetSource; | 137 | QString getTypeName<Event>() |
132 | std::function<void(const T&)> addHandler; | 138 | { |
133 | // std::function<void(const T&)> removeHandler; | 139 | return "event"; |
134 | }; | 140 | } |
135 | 141 | ||
136 | // template<class T> | 142 | template<> |
137 | // class TreeSet : public Set<T> { | 143 | QString getTypeName<Todo>() |
138 | // | 144 | { |
139 | // }; | 145 | return "todo"; |
146 | } | ||
140 | 147 | ||
148 | } | ||
141 | 149 | ||
150 | using namespace async; | ||
142 | 151 | ||
143 | /** | 152 | /** |
144 | * A query that matches a set of objects | 153 | * A query that matches a set of objects |
@@ -157,18 +166,22 @@ class Query | |||
157 | { | 166 | { |
158 | public: | 167 | public: |
159 | //Resources to search | 168 | //Resources to search |
160 | QSet<QString> resources() const { return QSet<QString>(); } | 169 | QSet<QString> resources; |
161 | }; | 170 | }; |
162 | 171 | ||
163 | 172 | ||
164 | /** | 173 | /** |
165 | * Interface for the store facade | 174 | * Interface for the store facade. |
166 | * | 175 | * |
167 | * All methods are synchronous. | 176 | * All methods are synchronous. |
177 | * Facades are stateful (they hold connections to resources and database). | ||
178 | * | ||
179 | * TODO: would it make sense to split the write, read and notification parts? (we could potentially save some connections) | ||
168 | */ | 180 | */ |
169 | template<class DomainType> | 181 | template<class DomainType> |
170 | class StoreFacade { | 182 | class StoreFacade { |
171 | public: | 183 | public: |
184 | virtual ~StoreFacade(){}; | ||
172 | virtual void create(const DomainType &domainObject) = 0; | 185 | virtual void create(const DomainType &domainObject) = 0; |
173 | virtual void modify(const DomainType &domainObject) = 0; | 186 | virtual void modify(const DomainType &domainObject) = 0; |
174 | virtual void remove(const DomainType &domainObject) = 0; | 187 | virtual void remove(const DomainType &domainObject) = 0; |
@@ -177,94 +190,71 @@ public: | |||
177 | 190 | ||
178 | 191 | ||
179 | /** | 192 | /** |
180 | * Actual implementation of the store facade that is provided by the resource plugin. | 193 | * Facade factory that returns a store facade implementation, by loading a plugin and providing the relevant implementation. |
181 | * | ||
182 | * It knows the buffer type used by the resource as well as the actual store used. | ||
183 | * | 194 | * |
184 | * A resource must provide this facade for each domain type it knows. | 195 | * If we were to provide default implementations for certain capabilities. Here would be the place to do so. |
185 | * => is reimplemented a lot | ||
186 | * => we should have a base implementation | ||
187 | * | 196 | * |
188 | * This interface should be executed in a thread so we can synchronously retrieve data from the store. | 197 | * TODO: pluginmechansims for resources to provide their implementations. |
189 | * | 198 | * * We may want a way to recycle facades to avoid recreating socket connections all the time? |
190 | * TODO: perhaps we should also allow async access and leave the thread/non-thread decision up to the implementation? | ||
191 | */ | 199 | */ |
192 | template<typename DomainType> | ||
193 | class StoreFacadeImpl : public StoreFacade<Event> { | ||
194 | }; | ||
195 | 200 | ||
196 | template<> | 201 | class FacadeFactory { |
197 | class StoreFacadeImpl<Event> : public StoreFacade<Event> { | ||
198 | public: | 202 | public: |
199 | void create(const Event &domainObject) { | 203 | //FIXME: proper singleton implementation |
200 | //FIXME here we would need to cast to DomainAdapter | 204 | static FacadeFactory &instance() |
201 | //Do actual work | 205 | { |
202 | //transformFromDomainType(domainObject); | 206 | static FacadeFactory factory; |
203 | //Ideally we have an adapter | 207 | return factory; |
204 | //getAdater(domainObject).buffer(); | ||
205 | //domainObject.key(); => The domain object needs to provide the id | ||
206 | //writeToDb(); | ||
207 | } | 208 | } |
208 | 209 | ||
209 | void modify(const Event &domainObject) { | 210 | static QString key(const QString &resource, const QString &type) |
210 | //Do actual work | 211 | { |
212 | return resource + type; | ||
211 | } | 213 | } |
212 | 214 | ||
213 | void remove(const Event &domainObject) { | 215 | template<class DomainType, class Facade> |
214 | //Do actual work | 216 | void registerFacade(const QString &resource) |
217 | { | ||
218 | const QString typeName = Domain::getTypeName<DomainType>(); | ||
219 | mFacadeRegistry.insert(key(resource, typeName), [](){ return new Facade; }); | ||
215 | } | 220 | } |
216 | 221 | ||
217 | class EventBuffer { | 222 | /* |
218 | QString value; | 223 | * Allows the registrar to register a specific instance. |
219 | }; | 224 | * |
220 | 225 | * Primarily for testing. | |
221 | static Event transformToDomainType(const EventBuffer &buffer) { | 226 | * The facade factory takes ovnership of the poniter and typically deletes the instance via shared pointer. |
222 | //We may want to avoid copies here | 227 | * Supplied factory functions should therefore always return a new pointer (i.e. via clone()) |
223 | Event event; | 228 | * |
224 | // //Ideally we don't have to copy and can use an adaptor instead | 229 | * 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. |
225 | // return DomainAdaptor | 230 | */ |
226 | return event; | 231 | template<class DomainType, class Facade> |
227 | }; | 232 | void registerFacade(const QString &resource, const std::function<void*(void)> &customFactoryFunction) |
228 | 233 | { | |
229 | void load(const Query &query, const std::function<void(const Event &)> &resultCallback) { | 234 | const QString typeName = Domain::getTypeName<DomainType>(); |
230 | //retrieve buffers from storage | 235 | mFacadeRegistry.insert(key(resource, typeName), customFactoryFunction); |
231 | QList<EventBuffer> queryresult; | ||
232 | for(const EventBuffer &buffer : queryresult) { | ||
233 | resultCallback(transformToDomainType(buffer)); | ||
234 | } | ||
235 | } | 236 | } |
236 | 237 | ||
237 | private: | ||
238 | //Dummy implementation | ||
239 | class ResourceImpl {}; | ||
240 | ResourceImpl resource; | ||
241 | Database mDb; | ||
242 | }; | ||
243 | |||
244 | /** | ||
245 | * Facade factory that returns a store facade implementation, by loading a plugin and providing the relevant implementation. | ||
246 | * | ||
247 | * If we were to provide default implementations for certain capabilities. Here would be the place to do so. | ||
248 | * | ||
249 | * TODO: pluginmechansims for resources to provide their implementations. | ||
250 | */ | ||
251 | class FacadeFactory { | ||
252 | public: | ||
253 | template<class DomainType> | 238 | template<class DomainType> |
254 | static StoreFacade<DomainType> getFacade(const QString &resource) | 239 | QSharedPointer<StoreFacade<DomainType> > getFacade(const QString &resource) |
255 | { | 240 | { |
256 | //TODO errorhandling in case the resource doesn't support the domain type | 241 | const QString typeName = Domain::getTypeName<DomainType>(); |
257 | if (resource == "dummyresource") { | 242 | auto factoryFunction = mFacadeRegistry.value(key(resource, typeName)); |
258 | return StoreFacadeImpl<DomainType>(); | 243 | if (factoryFunction) { |
244 | return QSharedPointer<StoreFacade<DomainType> >(static_cast<StoreFacade<DomainType>* >(factoryFunction())); | ||
259 | } | 245 | } |
260 | return StoreFacadeImpl<DomainType>(); | 246 | qWarning() << "Failed to find facade for resource: " << resource << " and type: " << typeName; |
247 | return QSharedPointer<StoreFacade<DomainType> >(); | ||
261 | } | 248 | } |
249 | |||
250 | private: | ||
251 | QHash<QString, std::function<void*(void)> > mFacadeRegistry; | ||
262 | }; | 252 | }; |
263 | 253 | ||
264 | /** | 254 | /** |
265 | * Store interface used in the client API | 255 | * Store interface used in the client API. |
266 | * | 256 | * |
267 | * TODO: For testing we need to be able to inject dummy StoreFacades. | 257 | * TODO: For testing we need to be able to inject dummy StoreFacades. Should we work with a store instance, or a singleton factory? |
268 | */ | 258 | */ |
269 | class Store { | 259 | class Store { |
270 | public: | 260 | public: |
@@ -282,10 +272,13 @@ public: | |||
282 | async::run([resultSet, query](){ | 272 | async::run([resultSet, query](){ |
283 | // Query all resources and aggregate results | 273 | // Query all resources and aggregate results |
284 | // query tells us in which resources we're interested | 274 | // query tells us in which resources we're interested |
285 | for(const QString &resource : query.resources()) { | 275 | for(const QString &resource : query.resources) { |
286 | auto facade = FacadeFactory::getFacade<DomainType>(resource); | 276 | auto facade = FacadeFactory::instance().getFacade<DomainType>(resource); |
287 | facade.load(query, resultSet.add); | 277 | //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. |
278 | std::function<void(const DomainType &)> addCallback = std::bind(&ResultProvider<DomainType>::add, resultSet, std::placeholders::_1); | ||
279 | facade->load(query, addCallback); | ||
288 | } | 280 | } |
281 | resultSet->complete(); | ||
289 | }); | 282 | }); |
290 | return resultSet->emitter(); | 283 | return resultSet->emitter(); |
291 | } | 284 | } |
@@ -305,7 +298,7 @@ public: | |||
305 | template <class DomainType> | 298 | template <class DomainType> |
306 | static void create(const DomainType &domainObject, const QString &resourceIdentifier) { | 299 | static void create(const DomainType &domainObject, const QString &resourceIdentifier) { |
307 | //Potentially move to separate thread as well | 300 | //Potentially move to separate thread as well |
308 | auto facade = FacadeFactory::getFacade<DomainType>(resourceIdentifier); | 301 | auto facade = FacadeFactory::instance().getFacade<DomainType>(resourceIdentifier); |
309 | facade.create(domainObject); | 302 | facade.create(domainObject); |
310 | } | 303 | } |
311 | 304 | ||
@@ -317,7 +310,7 @@ public: | |||
317 | template <class DomainType> | 310 | template <class DomainType> |
318 | static void modify(const DomainType &domainObject, const QString &resourceIdentifier) { | 311 | static void modify(const DomainType &domainObject, const QString &resourceIdentifier) { |
319 | //Potentially move to separate thread as well | 312 | //Potentially move to separate thread as well |
320 | auto facade = FacadeFactory::getFacade<DomainType>(resourceIdentifier); | 313 | auto facade = FacadeFactory::instance().getFacade<DomainType>(resourceIdentifier); |
321 | facade.modify(domainObject); | 314 | facade.modify(domainObject); |
322 | } | 315 | } |
323 | 316 | ||
@@ -327,9 +320,101 @@ public: | |||
327 | template <class DomainType> | 320 | template <class DomainType> |
328 | static void remove(const DomainType &domainObject, const QString &resourceIdentifier) { | 321 | static void remove(const DomainType &domainObject, const QString &resourceIdentifier) { |
329 | //Potentially move to separate thread as well | 322 | //Potentially move to separate thread as well |
330 | auto facade = FacadeFactory::getFacade<DomainType>(resourceIdentifier); | 323 | auto facade = FacadeFactory::instance().getFacade<DomainType>(resourceIdentifier); |
331 | facade.remove(domainObject); | 324 | facade.remove(domainObject); |
332 | } | 325 | } |
333 | }; | 326 | }; |
334 | 327 | ||
335 | } | 328 | } |
329 | |||
330 | //Example implementations | ||
331 | /* | ||
332 | * Resource and domain object specific | ||
333 | * FIXME: should we hardcode the requirement that the domain adapter is a subclass for the domain object? | ||
334 | * * how do we allow copying of domain objects? | ||
335 | * ** dummy domain object that is a wrapper? | ||
336 | * ** domain adapter has an accessor for the domain object to hide subclassing | ||
337 | */ | ||
338 | class EventDomainAdapter : public Akonadi2::Domain::Event { | ||
339 | // virtual void setFoo(const QString &value) | ||
340 | // { | ||
341 | // mBuffer.setFoo(value); | ||
342 | // } | ||
343 | |||
344 | // virtual QString foo() const | ||
345 | // { | ||
346 | // return mBuffer.foo(); | ||
347 | // } | ||
348 | |||
349 | // MessageBuffer mBuffer; | ||
350 | }; | ||
351 | |||
352 | |||
353 | /** | ||
354 | * Actual implementation of the store facade that is provided by the resource plugin. | ||
355 | * | ||
356 | * It knows the buffer type used by the resource as well as the actual store used. | ||
357 | * | ||
358 | * A resource must provide this facade for each domain type it knows. | ||
359 | * => is reimplemented a lot | ||
360 | * => we should have a base implementation | ||
361 | * | ||
362 | * This interface should be executed in a thread so we can synchronously retrieve data from the store. | ||
363 | * | ||
364 | * TODO: perhaps we should also allow async access and leave the thread/non-thread decision up to the implementation? | ||
365 | */ | ||
366 | template<typename DomainType> | ||
367 | class StoreFacadeImpl : public Akonadi2::StoreFacade<Akonadi2::Domain::Event> { | ||
368 | }; | ||
369 | |||
370 | template<> | ||
371 | class StoreFacadeImpl<Akonadi2::Domain::Event> : public Akonadi2::StoreFacade<Akonadi2::Domain::Event> { | ||
372 | public: | ||
373 | StoreFacadeImpl():StoreFacade() {}; | ||
374 | |||
375 | void create(const Akonadi2::Domain::Event &domainObject) { | ||
376 | //FIXME here we would need to cast to DomainAdapter | ||
377 | //Do actual work | ||
378 | //transformFromDomainType(domainObject); | ||
379 | //Ideally we have an adapter | ||
380 | //getAdater(domainObject).buffer(); | ||
381 | //domainObject.key(); => The domain object needs to provide the id | ||
382 | //writeToDb(); | ||
383 | } | ||
384 | |||
385 | void modify(const Akonadi2::Domain::Event &domainObject) { | ||
386 | //Do actual work | ||
387 | } | ||
388 | |||
389 | void remove(const Akonadi2::Domain::Event &domainObject) { | ||
390 | //Do actual work | ||
391 | } | ||
392 | |||
393 | class EventBuffer { | ||
394 | QString value; | ||
395 | }; | ||
396 | |||
397 | static Akonadi2::Domain::Event transformToDomainType(const EventBuffer &buffer) { | ||
398 | //We may want to avoid copies here | ||
399 | Akonadi2::Domain::Event event; | ||
400 | // //Ideally we don't have to copy and can use an adaptor instead | ||
401 | // return DomainAdaptor | ||
402 | return event; | ||
403 | }; | ||
404 | |||
405 | void load(const Akonadi2::Query &query, const std::function<void(const Akonadi2::Domain::Event &)> &resultCallback) { | ||
406 | //retrieve buffers from storage | ||
407 | QList<EventBuffer> queryresult; | ||
408 | for(const EventBuffer &buffer : queryresult) { | ||
409 | resultCallback(transformToDomainType(buffer)); | ||
410 | } | ||
411 | } | ||
412 | |||
413 | private: | ||
414 | //Dummy implementation | ||
415 | class ResourceImpl {}; | ||
416 | ResourceImpl resource; | ||
417 | class DatabaseImpl {}; | ||
418 | DatabaseImpl mDb; | ||
419 | }; | ||
420 | |||