diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2014-11-27 16:42:33 +0100 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2014-11-27 16:42:33 +0100 |
commit | 0bcfc57f24adf8ce8dfb2fad33b294b5f0110a89 (patch) | |
tree | bcc9572b698b694875cc2454aa5559244f4869af | |
parent | 3d2359416b09aaf363a8e1f959c087fded51765f (diff) | |
download | sink-0bcfc57f24adf8ce8dfb2fad33b294b5f0110a89.tar.gz sink-0bcfc57f24adf8ce8dfb2fad33b294b5f0110a89.zip |
Updated ClientAPI draft.
-rw-r--r-- | client/clientapi.h | 242 |
1 files changed, 190 insertions, 52 deletions
diff --git a/client/clientapi.h b/client/clientapi.h index b035708..8924328 100644 --- a/client/clientapi.h +++ b/client/clientapi.h | |||
@@ -2,20 +2,45 @@ | |||
2 | 2 | ||
3 | #include <QString> | 3 | #include <QString> |
4 | #include <QSet> | 4 | #include <QSet> |
5 | #include <QSharedPointer> | ||
6 | #include <functional> | ||
7 | #include "store/database.h" | ||
5 | 8 | ||
6 | namespace ClientAPI { | 9 | namespace ClientAPI { |
7 | 10 | ||
8 | template<class T> | 11 | /** |
9 | class Set { | 12 | * Standardized Domain Types |
13 | * | ||
14 | * The don't adhere to any standard and can be freely extended | ||
15 | * Their sole purpose is providing a standardized interface to access data. | ||
16 | * | ||
17 | * 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). | ||
18 | * | ||
19 | * These types will be frequently modified (for every new feature that should be exposed to the any client) | ||
20 | */ | ||
10 | 21 | ||
22 | class AkonadiDomainType { | ||
23 | /* | ||
24 | * Each domain object needs to store the resource, identifier, revision triple so we can link back to the storage location. | ||
25 | */ | ||
26 | QString identifier; | ||
27 | QString resource; | ||
28 | qint64 revision; | ||
11 | }; | 29 | }; |
12 | 30 | ||
13 | template<class T> | 31 | class Event : public AkonadiDomainType { |
14 | class TreeSet : public Set<T> { | ||
15 | 32 | ||
16 | }; | 33 | }; |
34 | class Todo : public AkonadiDomainType { | ||
17 | 35 | ||
18 | class DomainObject { | 36 | }; |
37 | class Calendar : public AkonadiDomainType { | ||
38 | |||
39 | }; | ||
40 | class Mail : public AkonadiDomainType { | ||
41 | |||
42 | }; | ||
43 | class Folder : public AkonadiDomainType { | ||
19 | 44 | ||
20 | }; | 45 | }; |
21 | 46 | ||
@@ -26,7 +51,7 @@ class DomainObject { | |||
26 | * ** dummy domain object that is a wrapper? | 51 | * ** dummy domain object that is a wrapper? |
27 | * ** domain adapter has an accessor for the domain object to hide subclassing | 52 | * ** domain adapter has an accessor for the domain object to hide subclassing |
28 | */ | 53 | */ |
29 | class DomainAdapter : public DomainObject { | 54 | class EventDomainAdapter : public Event { |
30 | // virtual void setFoo(const QString &value) | 55 | // virtual void setFoo(const QString &value) |
31 | // { | 56 | // { |
32 | // mBuffer.setFoo(value); | 57 | // mBuffer.setFoo(value); |
@@ -41,17 +66,86 @@ class DomainAdapter : public DomainObject { | |||
41 | }; | 66 | }; |
42 | 67 | ||
43 | 68 | ||
69 | |||
70 | /** | ||
71 | * Query result set | ||
72 | * | ||
73 | * This should probably become part of a generic kasync library. | ||
74 | * | ||
75 | * Functional is nice because we don't have to store data in the emitter | ||
76 | * Non functional and storing may be the right thing because we want an in-memory representation of the set | ||
77 | * non-functional also allows us to batch move data across thread boundaries. | ||
78 | */ | ||
79 | |||
80 | template<class T> | ||
81 | class ResultEmitter; | ||
82 | |||
83 | /* | ||
84 | * The promise side for the result provider | ||
85 | */ | ||
86 | template<class T> | ||
87 | class ResultProvider { | ||
88 | public: | ||
89 | void add(const T &value) | ||
90 | { | ||
91 | //the handler will be called in the other thread, protect | ||
92 | mResultEmitter->addHandler(value); | ||
93 | } | ||
94 | |||
95 | QSharedPointer<ResultEmitter<T> > emitter() | ||
96 | { | ||
97 | mResultEmitter = QSharedPointer<ResultEmitter<T> >(new ResultEmitter<T>()); | ||
98 | return emitter; | ||
99 | } | ||
100 | |||
101 | private: | ||
102 | QSharedPointer<ResultEmitter<T> > mResultEmitter; | ||
103 | }; | ||
104 | |||
105 | /* | ||
106 | * The future side for the client. | ||
107 | * | ||
108 | * It does not directly hold the state. | ||
109 | */ | ||
110 | template<class T> | ||
111 | class ResultEmitter { | ||
112 | public: | ||
113 | void onAdded(const std::function<void(const T&)> &handler); | ||
114 | // void onRemoved(const std::function<void(const T&)> &handler); | ||
115 | |||
116 | private: | ||
117 | friend class SetSource; | ||
118 | std::function<void(const T&)> addHandler; | ||
119 | // std::function<void(const T&)> removeHandler; | ||
120 | }; | ||
121 | |||
122 | // template<class T> | ||
123 | // class TreeSet : public Set<T> { | ||
124 | // | ||
125 | // }; | ||
126 | |||
127 | |||
128 | |||
44 | /** | 129 | /** |
45 | * A query that matches a set of objects | 130 | * A query that matches a set of objects |
131 | * | ||
132 | * The query will have to be updated regularly similary to the domain objects. | ||
133 | * It probably also makes sense to have a domain specific part of the query, | ||
134 | * such as what properties we're interested in (necessary information for on-demand | ||
135 | * loading of data). | ||
46 | */ | 136 | */ |
47 | class Query | 137 | class Query |
48 | { | 138 | { |
49 | public: | 139 | public: |
140 | //Resources to search | ||
50 | QSet<QString> resources() const { return QSet<QString>(); } | 141 | QSet<QString> resources() const { return QSet<QString>(); } |
51 | }; | 142 | }; |
52 | 143 | ||
144 | |||
53 | /** | 145 | /** |
54 | * Interface for the store facade | 146 | * Interface for the store facade |
147 | * | ||
148 | * All methods are synchronous. | ||
55 | */ | 149 | */ |
56 | template<class DomainType> | 150 | template<class DomainType> |
57 | class StoreFacade { | 151 | class StoreFacade { |
@@ -59,14 +153,10 @@ public: | |||
59 | virtual void create(const DomainType &domainObject) = 0; | 153 | virtual void create(const DomainType &domainObject) = 0; |
60 | virtual void modify(const DomainType &domainObject) = 0; | 154 | virtual void modify(const DomainType &domainObject) = 0; |
61 | virtual void remove(const DomainType &domainObject) = 0; | 155 | virtual void remove(const DomainType &domainObject) = 0; |
62 | virtual void load(const Query &query) = 0; | 156 | virtual void load(const Query &query, const std::function<void(const DomainType &)> &resultCallback) = 0; |
63 | }; | 157 | }; |
64 | 158 | ||
65 | 159 | ||
66 | class ResourceImpl { | ||
67 | |||
68 | }; | ||
69 | |||
70 | /** | 160 | /** |
71 | * Actual implementation of the store facade that is provided by the resource plugin. | 161 | * Actual implementation of the store facade that is provided by the resource plugin. |
72 | * | 162 | * |
@@ -74,48 +164,78 @@ class ResourceImpl { | |||
74 | * | 164 | * |
75 | * A resource must provide this facade for each domain type it knows. | 165 | * A resource must provide this facade for each domain type it knows. |
76 | * => is reimplemented a lot | 166 | * => is reimplemented a lot |
167 | * => we should have a base implementation | ||
77 | * | 168 | * |
78 | * This interface should be executed in a thread so we can synchronously retrieve data from the store. | 169 | * This interface should be executed in a thread so we can synchronously retrieve data from the store. |
170 | * | ||
171 | * TODO: perhaps we should also allow async access and leave the thread/non-thread decision up to the implementation? | ||
79 | */ | 172 | */ |
80 | template<class DomainType> | 173 | template<typename DomainType> |
81 | class StoreFacadeImpl : public StoreFacade<DomainType> { | 174 | class StoreFacadeImpl : public StoreFacade<Event> { |
175 | }; | ||
176 | |||
177 | template<> | ||
178 | class StoreFacadeImpl<Event> : public StoreFacade<Event> { | ||
82 | public: | 179 | public: |
83 | void create(const DomainType &domainObject) { | 180 | void create(const Event &domainObject) { |
84 | //FIXME here we would need to cast to DomainAdapter | 181 | //FIXME here we would need to cast to DomainAdapter |
85 | //Do actual work | 182 | //Do actual work |
183 | //transformFromDomainType(domainObject); | ||
184 | //Ideally we have an adapter | ||
185 | //getAdater(domainObject).buffer(); | ||
186 | //domainObject.key(); => The domain object needs to provide the id | ||
187 | //writeToDb(); | ||
86 | } | 188 | } |
87 | 189 | ||
88 | void modify(const DomainType &domainObject) { | 190 | void modify(const Event &domainObject) { |
89 | //Do actual work | 191 | //Do actual work |
90 | } | 192 | } |
91 | 193 | ||
92 | void remove(const DomainType &domainObject) { | 194 | void remove(const Event &domainObject) { |
93 | //Do actual work | 195 | //Do actual work |
94 | } | 196 | } |
95 | 197 | ||
96 | Set<DomainType> load(Query) { | 198 | class EventBuffer { |
97 | Set<DomainType> resultSet; | 199 | QString value; |
98 | 200 | }; | |
99 | //retrieve results from store and fill into result set | 201 | |
100 | 202 | static Event transformToDomainType(const EventBuffer &buffer) { | |
101 | resultSet << | 203 | //We may want to avoid copies here |
102 | 204 | Event event; | |
103 | return resultSet; | 205 | // //Ideally we don't have to copy and can use an adaptor instead |
206 | // return DomainAdaptor | ||
207 | return event; | ||
208 | }; | ||
209 | |||
210 | void load(const Query &query, const std::function<void(const Event &)> &resultCallback) { | ||
211 | //retrieve buffers from storage | ||
212 | QList<EventBuffer> queryresult; | ||
213 | foreach(const EventBuffer &buffer, queryresult) { | ||
214 | resultCallback(transformToDomainType(buffer)); | ||
215 | } | ||
104 | } | 216 | } |
105 | 217 | ||
106 | private: | 218 | private: |
219 | //Dummy implementation | ||
220 | class ResourceImpl {}; | ||
107 | ResourceImpl resource; | 221 | ResourceImpl resource; |
222 | Database mDb; | ||
108 | }; | 223 | }; |
109 | 224 | ||
110 | /** | 225 | /** |
111 | * Facade factory that returns a store facade implementation, by loading a plugin and providing the relevant implementation. | 226 | * Facade factory that returns a store facade implementation, by loading a plugin and providing the relevant implementation. |
227 | * | ||
228 | * If we were to provide default implementations for certain capabilities. Here would be the place to do so. | ||
229 | * | ||
230 | * TODO: pluginmechansims for resources to provide their implementations. | ||
112 | */ | 231 | */ |
113 | class FacadeFactory { | 232 | class FacadeFactory { |
114 | public: | 233 | public: |
115 | template<class DomainType> | 234 | template<class DomainType> |
116 | static StoreFacade<DomainType> getFacade(const QString &resource) | 235 | static StoreFacade<DomainType> getFacade(const QString &resource) |
117 | { | 236 | { |
118 | if (resource == "resourceX") { | 237 | //TODO errorhandling in case the resource doesn't support the domain type |
238 | if (resource == "dummyresource") { | ||
119 | return StoreFacadeImpl<DomainType>(); | 239 | return StoreFacadeImpl<DomainType>(); |
120 | } | 240 | } |
121 | return StoreFacadeImpl<DomainType>(); | 241 | return StoreFacadeImpl<DomainType>(); |
@@ -127,48 +247,66 @@ public: | |||
127 | */ | 247 | */ |
128 | class Store { | 248 | class Store { |
129 | public: | 249 | public: |
250 | /** | ||
251 | * Asynchronusly load a dataset | ||
252 | */ | ||
130 | template <class DomainType> | 253 | template <class DomainType> |
131 | static Set<DomainType> load(Query) | 254 | static QSharedPointer<ResultEmitter<DomainType> > load(Query query) |
132 | { | 255 | { |
133 | //Query all resources and aggregate results | 256 | QSharedPointer<ResultProvider<DomainType> > resultSet(new ResultProvider<DomainType>); |
134 | //Query tells us in which resources we're interested | 257 | |
135 | Set<DomainType> resultSet; | 258 | //Create a job that executes the search function. |
136 | 259 | //We must guarantee that the emitter is returned before the first result is emitted. | |
137 | //FIXME this should run in a thread. | 260 | //The thread boundary handling is implemented in the result provider. |
138 | //The result set is immediately returned and a "promise"/"resultprovider", | 261 | // QtConcurrent::run([provider, resultSet](){ |
139 | //is passed to the actual query. The resultset is threadsafe so the query thread can safely move data | 262 | // // Query all resources and aggregate results |
140 | //via the promise to the mainthread. | 263 | // // query tells us in which resources we're interested |
141 | for(auto resource, query.resources()) { | 264 | // for(const auto &resource, query.resources()) { |
142 | auto facade = FacadeFactory::getFacade(resource); | 265 | // auto facade = FacadeFactory::getFacade(resource); |
143 | resultSet += facade.load<DomainType>(query); | 266 | // facade.load<DomainType>(query, resultSet.add); |
144 | } | 267 | // } |
145 | return resultSet; | 268 | // }); |
269 | return resultSet->emitter(); | ||
146 | } | 270 | } |
147 | 271 | ||
148 | //Future load(id); => Set with single value | 272 | /** |
149 | 273 | * Asynchronusly load a dataset with tree structure information | |
150 | template <class DomainType> | 274 | */ |
151 | static TreeSet<DomainType> loadTree(Query) | 275 | // template <class DomainType> |
152 | { | 276 | // static TreeSet<DomainType> loadTree(Query) |
277 | // { | ||
153 | 278 | ||
154 | } | 279 | // } |
155 | 280 | ||
156 | //Sync methods for modifications | 281 | /** |
282 | * Create a new entity. | ||
283 | */ | ||
157 | template <class DomainType> | 284 | template <class DomainType> |
158 | static void create(const DomainType &domainObject) { | 285 | static void create(const DomainType &domainObject, const QString &resourceIdentifier) { |
159 | auto facade = FacadeFactory::getFacade(domainObject.resource()); | 286 | //Potentially move to separate thread as well |
287 | auto facade = FacadeFactory::getFacade<DomainType>(resourceIdentifier); | ||
160 | facade.create(domainObject); | 288 | facade.create(domainObject); |
161 | } | 289 | } |
162 | 290 | ||
291 | /** | ||
292 | * Modify an entity. | ||
293 | * | ||
294 | * This includes moving etc. since these are also simple settings on a property. | ||
295 | */ | ||
163 | template <class DomainType> | 296 | template <class DomainType> |
164 | static void modify(const DomainType &domainObject) { | 297 | static void modify(const DomainType &domainObject, const QString &resourceIdentifier) { |
165 | auto facade = FacadeFactory::getFacade(domainObject.resource()); | 298 | //Potentially move to separate thread as well |
299 | auto facade = FacadeFactory::getFacade<DomainType>(resourceIdentifier); | ||
166 | facade.modify(domainObject); | 300 | facade.modify(domainObject); |
167 | } | 301 | } |
168 | 302 | ||
303 | /** | ||
304 | * Remove an entity. | ||
305 | */ | ||
169 | template <class DomainType> | 306 | template <class DomainType> |
170 | static void remove(const DomainType &domainObject) { | 307 | static void remove(const DomainType &domainObject, const QString &resourceIdentifier) { |
171 | auto facade = FacadeFactory::getFacade(domainObject.resource()); | 308 | //Potentially move to separate thread as well |
309 | auto facade = FacadeFactory::getFacade<DomainType>(resourceIdentifier); | ||
172 | facade.remove(domainObject); | 310 | facade.remove(domainObject); |
173 | } | 311 | } |
174 | }; | 312 | }; |