summaryrefslogtreecommitdiffstats
path: root/common
diff options
context:
space:
mode:
authorAaron Seigo <aseigo@kde.org>2014-12-16 08:25:14 +0100
committerAaron Seigo <aseigo@kde.org>2014-12-16 08:25:14 +0100
commited7fdbcf6beb40960cf91555ae1d506278b852d8 (patch)
tree466b262e1d9c2efd63acb44170bdd1add1b98469 /common
parent52787e9469cc1892e9e221b877d0ffd81d7a817a (diff)
downloadsink-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')
-rw-r--r--common/CMakeLists.txt6
-rw-r--r--common/clientapi.cpp45
-rw-r--r--common/clientapi.h465
-rw-r--r--common/resourceaccess.cpp181
-rw-r--r--common/resourceaccess.h68
-rw-r--r--common/test/CMakeLists.txt15
-rw-r--r--common/test/clientapitest.cpp48
-rw-r--r--common/threadboundary.cpp29
-rw-r--r--common/threadboundary.h47
9 files changed, 903 insertions, 1 deletions
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index d200635..fab7708 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -13,12 +13,16 @@ endif (STORAGE_unqlite)
13set(command_SRCS 13set(command_SRCS
14 commands.cpp 14 commands.cpp
15 console.cpp 15 console.cpp
16 resourceaccess.cpp
16 storage_common.cpp 17 storage_common.cpp
18 threadboundary.cpp
17 ${storage_SRCS}) 19 ${storage_SRCS})
18 20
19add_library(${PROJECT_NAME} SHARED ${command_SRCS}) 21add_library(${PROJECT_NAME} SHARED ${command_SRCS})
20generate_export_header(${PROJECT_NAME} BASE_NAME Akonadi2Common EXPORT_FILE_NAME akonadi2common_export.h) 22generate_export_header(${PROJECT_NAME} BASE_NAME Akonadi2Common EXPORT_FILE_NAME akonadi2common_export.h)
21SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) 23SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX)
22qt5_use_modules(${PROJECT_NAME} Widgets) 24qt5_use_modules(${PROJECT_NAME} Widgets Network)
23target_link_libraries(${PROJECT_NAME} ${storage_LIBS}) 25target_link_libraries(${PROJECT_NAME} ${storage_LIBS})
24install(TARGETS ${PROJECT_NAME} DESTINATION lib) 26install(TARGETS ${PROJECT_NAME} DESTINATION lib)
27
28add_subdirectory(test)
diff --git a/common/clientapi.cpp b/common/clientapi.cpp
new file mode 100644
index 0000000..88797cc
--- /dev/null
+++ b/common/clientapi.cpp
@@ -0,0 +1,45 @@
1/*
2 * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org>
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#include "clientapi.h"
22
23Resource::Resource()
24 : d(0)
25{
26
27}
28
29Resource::~Resource()
30{
31 //delete d;
32}
33
34ResourceFactory::ResourceFactory(QObject *parent)
35 : QObject(parent),
36 d(0)
37{
38
39}
40
41ResourceFactory::~ResourceFactory()
42{
43 //delete d;
44}
45
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
diff --git a/common/resourceaccess.cpp b/common/resourceaccess.cpp
new file mode 100644
index 0000000..2b58545
--- /dev/null
+++ b/common/resourceaccess.cpp
@@ -0,0 +1,181 @@
1/*
2 * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org>
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#include "resourceaccess.h"
22
23#include "common/console.h"
24#include "common/commands.h"
25#include "common/handshake_generated.h"
26#include "common/revisionupdate_generated.h"
27
28#include <QDebug>
29#include <QProcess>
30
31namespace Akonadi2
32{
33
34ResourceAccess::ResourceAccess(const QString &resourceName, QObject *parent)
35 : QObject(parent),
36 m_resourceName(resourceName),
37 m_socket(new QLocalSocket(this)),
38 m_tryOpenTimer(new QTimer(this)),
39 m_startingProcess(false)
40{
41 m_tryOpenTimer->setInterval(50);
42 m_tryOpenTimer->setSingleShot(true);
43 connect(m_tryOpenTimer, &QTimer::timeout,
44 this, &ResourceAccess::open);
45
46 log("Starting access");
47 connect(m_socket, &QLocalSocket::connected,
48 this, &ResourceAccess::connected);
49 connect(m_socket, &QLocalSocket::disconnected,
50 this, &ResourceAccess::disconnected);
51 connect(m_socket, SIGNAL(error(QLocalSocket::LocalSocketError)),
52 this, SLOT(connectionError(QLocalSocket::LocalSocketError)));
53 connect(m_socket, &QIODevice::readyRead,
54 this, &ResourceAccess::readResourceMessage);
55
56}
57
58ResourceAccess::~ResourceAccess()
59{
60
61}
62
63QString ResourceAccess::resourceName() const
64{
65 return m_resourceName;
66}
67
68bool ResourceAccess::isReady() const
69{
70 return m_socket->isValid();
71}
72
73void ResourceAccess::open()
74{
75 if (m_socket->isValid()) {
76 log("Socket valid, so aborting the open");
77 return;
78 }
79
80 m_socket->setServerName(m_resourceName);
81 log(QString("Opening %1").arg(m_socket->serverName()));
82 //FIXME: race between starting the exec and opening the socket?
83 m_socket->open();
84}
85
86void ResourceAccess::close()
87{
88 log(QString("Closing %1").arg(m_socket->fullServerName()));
89 m_socket->close();
90}
91
92void ResourceAccess::connected()
93{
94 m_startingProcess = false;
95 log(QString("Connected: ").arg(m_socket->fullServerName()));
96
97 {
98 auto name = m_fbb.CreateString(QString::number((long long)this).toLatin1());
99 auto command = Akonadi2::CreateHandshake(m_fbb, name);
100 Akonadi2::FinishHandshakeBuffer(m_fbb, command);
101 Commands::write(m_socket, Commands::HandshakeCommand, m_fbb);
102 m_fbb.Clear();
103 }
104
105 emit ready(true);
106}
107
108void ResourceAccess::disconnected()
109{
110 m_socket->close();
111 log(QString("Disconnected from %1").arg(m_socket->fullServerName()));
112 emit ready(false);
113 open();
114}
115
116void ResourceAccess::connectionError(QLocalSocket::LocalSocketError error)
117{
118 log(QString("Connection error: %2").arg(error));
119 if (m_startingProcess) {
120 if (!m_tryOpenTimer->isActive()) {
121 m_tryOpenTimer->start();
122 }
123 return;
124 }
125
126 m_startingProcess = true;
127 log(QString("Attempting to start resource ") + m_resourceName);
128 QStringList args;
129 args << m_resourceName;
130 if (QProcess::startDetached("akonadi2_synchronizer", args)) {
131 m_socket->open();
132 }
133}
134
135void ResourceAccess::readResourceMessage()
136{
137 if (!m_socket->isValid()) {
138 return;
139 }
140
141 m_partialMessageBuffer += m_socket->readAll();
142
143 // should be scheduled rather than processed all at once
144 while (processMessageBuffer()) {}
145}
146
147bool ResourceAccess::processMessageBuffer()
148{
149 static const int headerSize = (sizeof(int) * 2);
150 if (m_partialMessageBuffer.size() < headerSize) {
151 return false;
152 }
153
154 const int commandId = *(int*)m_partialMessageBuffer.constData();
155 const int size = *(int*)(m_partialMessageBuffer.constData() + sizeof(int));
156
157 if (size > m_partialMessageBuffer.size() - headerSize) {
158 return false;
159 }
160
161 switch (commandId) {
162 case Commands::RevisionUpdateCommand: {
163 auto buffer = Akonadi2::GetRevisionUpdate(m_partialMessageBuffer.constData() + headerSize);
164 log(QString("Revision updated to: %1").arg(buffer->revision()));
165 emit revisionChanged(buffer->revision());
166 break;
167 }
168 default:
169 break;
170 }
171
172 m_partialMessageBuffer.remove(0, headerSize + size);
173 return m_partialMessageBuffer.size() >= headerSize;
174}
175
176void ResourceAccess::log(const QString &message)
177{
178 Console::main()->log(m_resourceName + ": " + message);
179}
180
181}
diff --git a/common/resourceaccess.h b/common/resourceaccess.h
new file mode 100644
index 0000000..f381af1
--- /dev/null
+++ b/common/resourceaccess.h
@@ -0,0 +1,68 @@
1/*
2 * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org>
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 <QLocalSocket>
24#include <QObject>
25#include <QTimer>
26
27#include <flatbuffers/flatbuffers.h>
28
29namespace Akonadi2
30{
31
32class ResourceAccess : public QObject
33{
34 Q_OBJECT
35
36public:
37 ResourceAccess(const QString &resourceName, QObject *parent = 0);
38 ~ResourceAccess();
39
40 QString resourceName() const;
41 bool isReady() const;
42
43public Q_SLOTS:
44 void open();
45 void close();
46
47Q_SIGNALS:
48 void ready(bool isReady);
49 void revisionChanged(unsigned long long revision);
50
51private Q_SLOTS:
52 void connected();
53 void disconnected();
54 void connectionError(QLocalSocket::LocalSocketError error);
55 void readResourceMessage();
56 bool processMessageBuffer();
57
58private:
59 void log(const QString &message);
60 QString m_resourceName;
61 QLocalSocket *m_socket;
62 QTimer *m_tryOpenTimer;
63 bool m_startingProcess;
64 QByteArray m_partialMessageBuffer;
65 flatbuffers::FlatBufferBuilder m_fbb;
66};
67
68}
diff --git a/common/test/CMakeLists.txt b/common/test/CMakeLists.txt
new file mode 100644
index 0000000..98d4ecc
--- /dev/null
+++ b/common/test/CMakeLists.txt
@@ -0,0 +1,15 @@
1set(CMAKE_AUTOMOC ON)
2include_directories(${CMAKE_CURRENT_BINARY_DIR})
3
4macro(auto_tests)
5 foreach(_testname ${ARGN})
6 add_executable(${_testname} ${_testname}.cpp ${store_SRCS})
7 qt5_use_modules(${_testname} Core Test)
8 target_link_libraries(${_testname} akonadi2common)
9 add_test(NAME ${_testname} COMMAND ${_testname})
10 endforeach(_testname)
11endmacro(auto_tests)
12
13auto_tests (
14 clientapitest
15)
diff --git a/common/test/clientapitest.cpp b/common/test/clientapitest.cpp
new file mode 100644
index 0000000..2d1c238
--- /dev/null
+++ b/common/test/clientapitest.cpp
@@ -0,0 +1,48 @@
1#include <QtTest>
2#include <QDebug>
3#include <functional>
4
5#include "../clientapi.h"
6
7class DummyResourceFacade : public Akonadi2::StoreFacade<Akonadi2::Domain::Event>
8{
9public:
10 ~DummyResourceFacade(){};
11 virtual void create(const Akonadi2::Domain::Event &domainObject){};
12 virtual void modify(const Akonadi2::Domain::Event &domainObject){};
13 virtual void remove(const Akonadi2::Domain::Event &domainObject){};
14 virtual void load(const Akonadi2::Query &query, const std::function<void(const Akonadi2::Domain::Event::Ptr &)> &resultCallback)
15 {
16 qDebug() << "load called";
17 for(const auto &result : results) {
18 resultCallback(result);
19 }
20 }
21
22 QList<Akonadi2::Domain::Event::Ptr> results;
23};
24
25class ClientAPITest : public QObject
26{
27 Q_OBJECT
28private Q_SLOTS:
29
30 void testLoad()
31 {
32 DummyResourceFacade facade;
33 facade.results << QSharedPointer<Akonadi2::Domain::Event>::create("resource", "id", 0);
34
35 Akonadi2::FacadeFactory::instance().registerFacade<Akonadi2::Domain::Event, DummyResourceFacade>("dummyresource", [facade](){ return new DummyResourceFacade(facade); });
36
37 Akonadi2::Query query;
38 query.resources << "dummyresource";
39
40 async::SyncListResult<Akonadi2::Domain::Event::Ptr> result(Akonadi2::Store::load<Akonadi2::Domain::Event>(query));
41 result.exec();
42 QCOMPARE(result.size(), 1);
43 }
44
45};
46
47QTEST_MAIN(ClientAPITest)
48#include "clientapitest.moc"
diff --git a/common/threadboundary.cpp b/common/threadboundary.cpp
new file mode 100644
index 0000000..47ec508
--- /dev/null
+++ b/common/threadboundary.cpp
@@ -0,0 +1,29 @@
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#include "threadboundary.h"
22
23Q_DECLARE_METATYPE(std::function<void()>);
24
25namespace async {
26ThreadBoundary::ThreadBoundary(): QObject() { qRegisterMetaType<std::function<void()> >("std::function<void()>"); }
27ThreadBoundary:: ~ThreadBoundary() {}
28}
29
diff --git a/common/threadboundary.h b/common/threadboundary.h
new file mode 100644
index 0000000..9881afa
--- /dev/null
+++ b/common/threadboundary.h
@@ -0,0 +1,47 @@
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 <QObject>
24#include <functional>
25
26namespace async {
27 /*
28 * A helper class to invoke a method in a different thread using the event loop.
29 * The ThreadBoundary object must live in the thread where the function should be called.
30 */
31 class ThreadBoundary : public QObject {
32 Q_OBJECT
33 public:
34 ThreadBoundary();
35 virtual ~ThreadBoundary();
36
37 //Call in worker thread
38 void callInMainThread(std::function<void()> f) {
39 QMetaObject::invokeMethod(this, "addValueInMainThread", Qt::QueuedConnection, QGenericReturnArgument(), Q_ARG(std::function<void()>, f));
40 }
41 public slots:
42 //Get's called in main thread by it's eventloop
43 void addValueInMainThread(std::function<void()> f) {
44 f();
45 }
46 };
47}