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