diff options
-rw-r--r-- | common/clientapi.h | 19 | ||||
-rw-r--r-- | common/pipeline.cpp | 7 | ||||
-rw-r--r-- | common/resourceaccess.cpp | 20 | ||||
-rw-r--r-- | common/resourceaccess.h | 1 | ||||
-rw-r--r-- | common/storage.h | 5 | ||||
-rw-r--r-- | common/storage_common.cpp | 19 | ||||
-rw-r--r-- | common/storage_lmdb.cpp | 16 | ||||
-rw-r--r-- | common/test/clientapitest.cpp | 3 | ||||
-rw-r--r-- | dummyresource/dummycalendar.fbs | 1 | ||||
-rw-r--r-- | dummyresource/facade.cpp | 63 | ||||
-rw-r--r-- | dummyresource/facade.h | 9 | ||||
-rw-r--r-- | dummyresource/resourcefactory.cpp | 87 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/dummyresourcetest.cpp | 35 |
14 files changed, 244 insertions, 43 deletions
diff --git a/common/clientapi.h b/common/clientapi.h index 6054130..d2757e7 100644 --- a/common/clientapi.h +++ b/common/clientapi.h | |||
@@ -278,7 +278,7 @@ public: | |||
278 | virtual void create(const DomainType &domainObject) = 0; | 278 | virtual void create(const DomainType &domainObject) = 0; |
279 | virtual void modify(const DomainType &domainObject) = 0; | 279 | virtual void modify(const DomainType &domainObject) = 0; |
280 | virtual void remove(const DomainType &domainObject) = 0; | 280 | virtual void remove(const DomainType &domainObject) = 0; |
281 | virtual void load(const Query &query, const std::function<void(const typename DomainType::Ptr &)> &resultCallback) = 0; | 281 | virtual void load(const Query &query, const std::function<void(const typename DomainType::Ptr &)> &resultCallback, const std::function<void()> &completeCallback) = 0; |
282 | }; | 282 | }; |
283 | 283 | ||
284 | 284 | ||
@@ -353,7 +353,7 @@ class Store { | |||
353 | public: | 353 | public: |
354 | static QString storageLocation() | 354 | static QString storageLocation() |
355 | { | 355 | { |
356 | return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/akonadi2"; | 356 | return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/akonadi2/storage"; |
357 | } | 357 | } |
358 | 358 | ||
359 | /** | 359 | /** |
@@ -371,13 +371,24 @@ public: | |||
371 | // Query all resources and aggregate results | 371 | // Query all resources and aggregate results |
372 | // query tells us in which resources we're interested | 372 | // query tells us in which resources we're interested |
373 | // TODO: queries to individual resources could be parallelized | 373 | // TODO: queries to individual resources could be parallelized |
374 | auto eventloop = QSharedPointer<QEventLoop>::create(); | ||
375 | int completeCounter = 0; | ||
374 | for(const QString &resource : query.resources) { | 376 | for(const QString &resource : query.resources) { |
375 | auto facade = FacadeFactory::instance().getFacade<DomainType>(resource); | 377 | auto facade = FacadeFactory::instance().getFacade<DomainType>(resource); |
376 | //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. | 378 | //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. |
377 | std::function<void(const typename DomainType::Ptr &)> addCallback = std::bind(&ResultProvider<typename DomainType::Ptr>::add, resultSet, std::placeholders::_1); | 379 | std::function<void(const typename DomainType::Ptr &)> addCallback = std::bind(&ResultProvider<typename DomainType::Ptr>::add, resultSet, std::placeholders::_1); |
378 | facade->load(query, addCallback); | 380 | //We copy the facade pointer to keep it alive |
381 | facade->load(query, addCallback, [&completeCounter, &query, resultSet, facade, eventloop]() { | ||
382 | //TODO use jobs instead of this counter | ||
383 | completeCounter++; | ||
384 | if (completeCounter == query.resources.size()) { | ||
385 | resultSet->complete(); | ||
386 | eventloop->quit(); | ||
387 | } | ||
388 | }); | ||
379 | } | 389 | } |
380 | resultSet->complete(); | 390 | //The thread contains no eventloop, so execute one here |
391 | eventloop->exec(QEventLoop::ExcludeUserInputEvents); | ||
381 | }); | 392 | }); |
382 | return resultSet->emitter(); | 393 | return resultSet->emitter(); |
383 | } | 394 | } |
diff --git a/common/pipeline.cpp b/common/pipeline.cpp index cf508c5..739909d 100644 --- a/common/pipeline.cpp +++ b/common/pipeline.cpp | |||
@@ -31,7 +31,7 @@ class Pipeline::Private | |||
31 | { | 31 | { |
32 | public: | 32 | public: |
33 | Private(const QString &resourceName) | 33 | Private(const QString &resourceName) |
34 | : storage(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/akonadi2/storage", resourceName), | 34 | : storage(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/akonadi2/storage", resourceName, Storage::ReadWrite), |
35 | stepScheduled(false) | 35 | stepScheduled(false) |
36 | { | 36 | { |
37 | } | 37 | } |
@@ -71,6 +71,11 @@ void Pipeline::null() | |||
71 | 71 | ||
72 | void Pipeline::newEntity(const QByteArray &key, flatbuffers::FlatBufferBuilder &entity) | 72 | void Pipeline::newEntity(const QByteArray &key, flatbuffers::FlatBufferBuilder &entity) |
73 | { | 73 | { |
74 | const qint64 newRevision = storage().maxRevision() + 1; | ||
75 | //FIXME this should go into a preprocessor | ||
76 | storage().write(key, key.size(), reinterpret_cast<char*>(entity.GetBufferPointer()), entity.GetSize()); | ||
77 | storage().setMaxRevision(newRevision); | ||
78 | |||
74 | PipelineState state(this, NewPipeline, key, d->newPipeline); | 79 | PipelineState state(this, NewPipeline, key, d->newPipeline); |
75 | d->activePipelines << state; | 80 | d->activePipelines << state; |
76 | state.step(); | 81 | state.step(); |
diff --git a/common/resourceaccess.cpp b/common/resourceaccess.cpp index a7e14f5..1706ac4 100644 --- a/common/resourceaccess.cpp +++ b/common/resourceaccess.cpp | |||
@@ -58,7 +58,7 @@ public: | |||
58 | 58 | ||
59 | void write(QIODevice *device, uint messageId) | 59 | void write(QIODevice *device, uint messageId) |
60 | { | 60 | { |
61 | Console::main()->log(QString("\tSending queued command %1").arg(m_commandId)); | 61 | // Console::main()->log(QString("\tSending queued command %1").arg(m_commandId)); |
62 | Commands::write(device, messageId, m_commandId, m_buffer, m_bufferSize); | 62 | Commands::write(device, messageId, m_commandId, m_buffer, m_bufferSize); |
63 | } | 63 | } |
64 | 64 | ||
@@ -82,6 +82,7 @@ public: | |||
82 | QByteArray partialMessageBuffer; | 82 | QByteArray partialMessageBuffer; |
83 | flatbuffers::FlatBufferBuilder fbb; | 83 | flatbuffers::FlatBufferBuilder fbb; |
84 | QVector<QueuedCommand *> commandQueue; | 84 | QVector<QueuedCommand *> commandQueue; |
85 | QVector<std::function<void()> > synchronizeResultHandler; | ||
85 | uint messageId; | 86 | uint messageId; |
86 | }; | 87 | }; |
87 | 88 | ||
@@ -149,6 +150,13 @@ void ResourceAccess::sendCommand(int commandId, flatbuffers::FlatBufferBuilder & | |||
149 | } | 150 | } |
150 | } | 151 | } |
151 | 152 | ||
153 | void ResourceAccess::synchronizeResource(const std::function<void()> &resultHandler) | ||
154 | { | ||
155 | sendCommand(Commands::SynchronizeCommand); | ||
156 | //TODO: this should be implemented as a job, so we don't need to store the result handler as member | ||
157 | d->synchronizeResultHandler << resultHandler; | ||
158 | } | ||
159 | |||
152 | void ResourceAccess::open() | 160 | void ResourceAccess::open() |
153 | { | 161 | { |
154 | if (d->socket->isValid()) { | 162 | if (d->socket->isValid()) { |
@@ -262,6 +270,13 @@ bool ResourceAccess::processMessageBuffer() | |||
262 | auto buffer = GetRevisionUpdate(d->partialMessageBuffer.constData() + headerSize); | 270 | auto buffer = GetRevisionUpdate(d->partialMessageBuffer.constData() + headerSize); |
263 | log(QString("Revision updated to: %1").arg(buffer->revision())); | 271 | log(QString("Revision updated to: %1").arg(buffer->revision())); |
264 | emit revisionChanged(buffer->revision()); | 272 | emit revisionChanged(buffer->revision()); |
273 | |||
274 | //FIXME: The result handler should be called on completion of the synchronize command, and not upon arbitrary revision updates. | ||
275 | for(auto handler : d->synchronizeResultHandler) { | ||
276 | //FIXME: we should associate the handler with a buffer->id() to avoid prematurely triggering the result handler from a delayed synchronized response (this is relevant for on-demand syncing). | ||
277 | handler(); | ||
278 | } | ||
279 | d->synchronizeResultHandler.clear(); | ||
265 | break; | 280 | break; |
266 | } | 281 | } |
267 | case Commands::CommandCompletion: { | 282 | case Commands::CommandCompletion: { |
@@ -280,7 +295,8 @@ bool ResourceAccess::processMessageBuffer() | |||
280 | 295 | ||
281 | void ResourceAccess::log(const QString &message) | 296 | void ResourceAccess::log(const QString &message) |
282 | { | 297 | { |
283 | Console::main()->log(d->resourceName + ": " + message); | 298 | qDebug() << d->resourceName + ": " + message; |
299 | // Console::main()->log(d->resourceName + ": " + message); | ||
284 | } | 300 | } |
285 | 301 | ||
286 | } | 302 | } |
diff --git a/common/resourceaccess.h b/common/resourceaccess.h index 3a35af6..7416b25 100644 --- a/common/resourceaccess.h +++ b/common/resourceaccess.h | |||
@@ -42,6 +42,7 @@ public: | |||
42 | 42 | ||
43 | void sendCommand(int commandId); | 43 | void sendCommand(int commandId); |
44 | void sendCommand(int commandId, flatbuffers::FlatBufferBuilder &fbb); | 44 | void sendCommand(int commandId, flatbuffers::FlatBufferBuilder &fbb); |
45 | void synchronizeResource(const std::function<void()> &resultHandler); | ||
45 | 46 | ||
46 | public Q_SLOTS: | 47 | public Q_SLOTS: |
47 | void open(); | 48 | void open(); |
diff --git a/common/storage.h b/common/storage.h index 57ee56c..6cfa3d6 100644 --- a/common/storage.h +++ b/common/storage.h | |||
@@ -71,6 +71,11 @@ public: | |||
71 | static std::function<void(const Storage::Error &error)> basicErrorHandler(); | 71 | static std::function<void(const Storage::Error &error)> basicErrorHandler(); |
72 | qint64 diskUsage() const; | 72 | qint64 diskUsage() const; |
73 | void removeFromDisk() const; | 73 | void removeFromDisk() const; |
74 | |||
75 | qint64 maxRevision(); | ||
76 | void setMaxRevision(qint64 revision); | ||
77 | |||
78 | bool exists() const; | ||
74 | private: | 79 | private: |
75 | class Private; | 80 | class Private; |
76 | Private * const d; | 81 | Private * const d; |
diff --git a/common/storage_common.cpp b/common/storage_common.cpp index 8f465fc..bdae9dd 100644 --- a/common/storage_common.cpp +++ b/common/storage_common.cpp | |||
@@ -52,4 +52,23 @@ void Storage::scan(const std::string &sKey, const std::function<bool(void *keyPt | |||
52 | scan(sKey.data(), sKey.size(), resultHandler, &errorHandler); | 52 | scan(sKey.data(), sKey.size(), resultHandler, &errorHandler); |
53 | } | 53 | } |
54 | 54 | ||
55 | void Storage::setMaxRevision(qint64 revision) | ||
56 | { | ||
57 | write("__internal_maxRevision", QString::number(revision).toStdString()); | ||
58 | } | ||
59 | |||
60 | qint64 Storage::maxRevision() | ||
61 | { | ||
62 | qint64 r = 0; | ||
63 | read(std::string("__internal_maxRevision"), [&](const std::string &revision) -> bool { | ||
64 | r = QString::fromStdString(revision).toLongLong(); | ||
65 | return false; | ||
66 | }, | ||
67 | [](const Storage::Error &error) { | ||
68 | //Ignore the error in case we don't find the value | ||
69 | //TODO only ignore value not found errors | ||
70 | }); | ||
71 | return r; | ||
72 | } | ||
73 | |||
55 | } // namespace Akonadi2 | 74 | } // namespace Akonadi2 |
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp index a8ec378..eeb0045 100644 --- a/common/storage_lmdb.cpp +++ b/common/storage_lmdb.cpp | |||
@@ -59,6 +59,7 @@ QMutex Storage::Private::sMutex; | |||
59 | Storage::Private::Private(const QString &s, const QString &n, AccessMode m) | 59 | Storage::Private::Private(const QString &s, const QString &n, AccessMode m) |
60 | : storageRoot(s), | 60 | : storageRoot(s), |
61 | name(n), | 61 | name(n), |
62 | env(0), | ||
62 | transaction(0), | 63 | transaction(0), |
63 | mode(m), | 64 | mode(m), |
64 | readTransaction(false), | 65 | readTransaction(false), |
@@ -66,7 +67,7 @@ Storage::Private::Private(const QString &s, const QString &n, AccessMode m) | |||
66 | { | 67 | { |
67 | const QString fullPath(storageRoot + '/' + name); | 68 | const QString fullPath(storageRoot + '/' + name); |
68 | QDir dir; | 69 | QDir dir; |
69 | dir.mkdir(storageRoot); | 70 | dir.mkpath(storageRoot); |
70 | dir.mkdir(fullPath); | 71 | dir.mkdir(fullPath); |
71 | 72 | ||
72 | //This seems to resolve threading related issues, not sure why though | 73 | //This seems to resolve threading related issues, not sure why though |
@@ -97,8 +98,10 @@ Storage::Private::~Private() | |||
97 | } | 98 | } |
98 | 99 | ||
99 | // it is still there and still unused, so we can shut it down | 100 | // it is still there and still unused, so we can shut it down |
100 | mdb_dbi_close(env, dbi); | 101 | if (env) { |
101 | mdb_env_close(env); | 102 | mdb_dbi_close(env, dbi); |
103 | mdb_env_close(env); | ||
104 | } | ||
102 | } | 105 | } |
103 | 106 | ||
104 | Storage::Storage(const QString &storageRoot, const QString &name, AccessMode mode) | 107 | Storage::Storage(const QString &storageRoot, const QString &name, AccessMode mode) |
@@ -111,6 +114,10 @@ Storage::~Storage() | |||
111 | delete d; | 114 | delete d; |
112 | } | 115 | } |
113 | 116 | ||
117 | bool Storage::exists() const | ||
118 | { | ||
119 | return (d->env != 0); | ||
120 | } | ||
114 | bool Storage::isInTransaction() const | 121 | bool Storage::isInTransaction() const |
115 | { | 122 | { |
116 | return d->transaction; | 123 | return d->transaction; |
@@ -313,12 +320,9 @@ void Storage::scan(const char *keyData, uint keySize, | |||
313 | errorHandler(error); | 320 | errorHandler(error); |
314 | } | 321 | } |
315 | 322 | ||
316 | /** | ||
317 | we don't abort the transaction since we need it for reading the values | ||
318 | if (implicitTransaction) { | 323 | if (implicitTransaction) { |
319 | abortTransaction(); | 324 | abortTransaction(); |
320 | } | 325 | } |
321 | */ | ||
322 | } | 326 | } |
323 | 327 | ||
324 | qint64 Storage::diskUsage() const | 328 | qint64 Storage::diskUsage() const |
diff --git a/common/test/clientapitest.cpp b/common/test/clientapitest.cpp index 2d1c238..dd634f1 100644 --- a/common/test/clientapitest.cpp +++ b/common/test/clientapitest.cpp | |||
@@ -11,12 +11,13 @@ public: | |||
11 | virtual void create(const Akonadi2::Domain::Event &domainObject){}; | 11 | virtual void create(const Akonadi2::Domain::Event &domainObject){}; |
12 | virtual void modify(const Akonadi2::Domain::Event &domainObject){}; | 12 | virtual void modify(const Akonadi2::Domain::Event &domainObject){}; |
13 | virtual void remove(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) | 14 | virtual void load(const Akonadi2::Query &query, const std::function<void(const Akonadi2::Domain::Event::Ptr &)> &resultCallback, const std::function<void()> &completeCallback) |
15 | { | 15 | { |
16 | qDebug() << "load called"; | 16 | qDebug() << "load called"; |
17 | for(const auto &result : results) { | 17 | for(const auto &result : results) { |
18 | resultCallback(result); | 18 | resultCallback(result); |
19 | } | 19 | } |
20 | completeCallback(); | ||
20 | } | 21 | } |
21 | 22 | ||
22 | QList<Akonadi2::Domain::Event::Ptr> results; | 23 | QList<Akonadi2::Domain::Event::Ptr> results; |
diff --git a/dummyresource/dummycalendar.fbs b/dummyresource/dummycalendar.fbs index 5a217b5..643c9b2 100644 --- a/dummyresource/dummycalendar.fbs +++ b/dummyresource/dummycalendar.fbs | |||
@@ -6,6 +6,7 @@ table DummyEvent { | |||
6 | summary:string; | 6 | summary:string; |
7 | description:string; | 7 | description:string; |
8 | attachment:[ubyte]; | 8 | attachment:[ubyte]; |
9 | remoteId:string; | ||
9 | } | 10 | } |
10 | 11 | ||
11 | root_type DummyEvent; | 12 | root_type DummyEvent; |
diff --git a/dummyresource/facade.cpp b/dummyresource/facade.cpp index 0d47010..c2871bb 100644 --- a/dummyresource/facade.cpp +++ b/dummyresource/facade.cpp | |||
@@ -23,6 +23,7 @@ | |||
23 | #include <functional> | 23 | #include <functional> |
24 | 24 | ||
25 | #include "common/resourceaccess.h" | 25 | #include "common/resourceaccess.h" |
26 | #include "common/commands.h" | ||
26 | #include "dummycalendar_generated.h" | 27 | #include "dummycalendar_generated.h" |
27 | 28 | ||
28 | using namespace DummyCalendar; | 29 | using namespace DummyCalendar; |
@@ -30,7 +31,7 @@ using namespace flatbuffers; | |||
30 | 31 | ||
31 | DummyResourceFacade::DummyResourceFacade() | 32 | DummyResourceFacade::DummyResourceFacade() |
32 | : Akonadi2::StoreFacade<Akonadi2::Domain::Event>(), | 33 | : Akonadi2::StoreFacade<Akonadi2::Domain::Event>(), |
33 | mResourceAccess(/* new ResourceAccess("dummyresource") */) | 34 | mResourceAccess(new Akonadi2::ResourceAccess("org.kde.dummy")) |
34 | { | 35 | { |
35 | // connect(mResourceAccess.data(), &ResourceAccess::ready, this, onReadyChanged); | 36 | // connect(mResourceAccess.data(), &ResourceAccess::ready, this, onReadyChanged); |
36 | } | 37 | } |
@@ -95,11 +96,8 @@ public: | |||
95 | QSharedPointer<Akonadi2::Storage> storage; | 96 | QSharedPointer<Akonadi2::Storage> storage; |
96 | }; | 97 | }; |
97 | 98 | ||
98 | void DummyResourceFacade::load(const Akonadi2::Query &query, const std::function<void(const Akonadi2::Domain::Event::Ptr &)> &resultCallback) | 99 | static std::function<bool(const std::string &key, DummyEvent const *buffer)> prepareQuery(const Akonadi2::Query &query) |
99 | { | 100 | { |
100 | qDebug() << "load called"; | ||
101 | auto storage = QSharedPointer<Akonadi2::Storage>::create(Akonadi2::Store::storageLocation(), "dummyresource"); | ||
102 | |||
103 | //Compose some functions to make query matching fast. | 101 | //Compose some functions to make query matching fast. |
104 | //This way we can process the query once, and convert all values into something that can be compared quickly | 102 | //This way we can process the query once, and convert all values into something that can be compared quickly |
105 | std::function<bool(const std::string &key, DummyEvent const *buffer)> preparedQuery; | 103 | std::function<bool(const std::string &key, DummyEvent const *buffer)> preparedQuery; |
@@ -125,21 +123,48 @@ void DummyResourceFacade::load(const Akonadi2::Query &query, const std::function | |||
125 | return true; | 123 | return true; |
126 | }; | 124 | }; |
127 | } | 125 | } |
126 | return preparedQuery; | ||
127 | } | ||
128 | 128 | ||
129 | //Because we have no indexes yet, we always do a full scan | 129 | void DummyResourceFacade::synchronizeResource(const std::function<void()> &continuation) |
130 | storage->scan("", [=](void *keyValue, int keySize, void *dataValue, int dataSize) -> bool { | 130 | { |
131 | //TODO read second buffer as well | 131 | //TODO check if a sync is necessary |
132 | auto eventBuffer = GetDummyEvent(dataValue); | 132 | //TODO Only sync what was requested |
133 | if (preparedQuery && preparedQuery(std::string(static_cast<char*>(keyValue), keySize), eventBuffer)) { | 133 | //TODO timeout |
134 | //TODO read the revision from the generic portion of the buffer | 134 | mResourceAccess->open(); |
135 | auto event = QSharedPointer<DummyEventAdaptor>::create("dummyresource", QString::fromUtf8(static_cast<char*>(keyValue), keySize), 0); | 135 | mResourceAccess->synchronizeResource(continuation); |
136 | event->buffer = eventBuffer; | 136 | } |
137 | event->storage = storage; | 137 | |
138 | resultCallback(event); | 138 | void DummyResourceFacade::load(const Akonadi2::Query &query, const std::function<void(const Akonadi2::Domain::Event::Ptr &)> &resultCallback, const std::function<void()> &completeCallback) |
139 | } | 139 | { |
140 | return true; | 140 | qDebug() << "load called"; |
141 | |||
142 | synchronizeResource([=]() { | ||
143 | //Now that the sync is complete we can execute the query | ||
144 | const auto preparedQuery = prepareQuery(query); | ||
145 | |||
146 | auto storage = QSharedPointer<Akonadi2::Storage>::create(Akonadi2::Store::storageLocation(), "org.kde.dummy"); | ||
147 | |||
148 | qDebug() << "executing query"; | ||
149 | //We start a transaction explicitly that we'll leave open so the values can be read. | ||
150 | //The transaction will be closed automatically once the storage object is destroyed. | ||
151 | storage->startTransaction(Akonadi2::Storage::ReadOnly); | ||
152 | //Because we have no indexes yet, we always do a full scan | ||
153 | storage->scan("", [=](void *keyValue, int keySize, void *dataValue, int dataSize) -> bool { | ||
154 | //TODO read the three buffers | ||
155 | qDebug() << QString::fromStdString(std::string(static_cast<char*>(keyValue), keySize)); | ||
156 | auto eventBuffer = GetDummyEvent(dataValue); | ||
157 | if (preparedQuery && preparedQuery(std::string(static_cast<char*>(keyValue), keySize), eventBuffer)) { | ||
158 | //TODO set proper revision | ||
159 | qint64 revision = 0; | ||
160 | auto event = QSharedPointer<DummyEventAdaptor>::create("org.kde.dummy", QString::fromUtf8(static_cast<char*>(keyValue), keySize), revision); | ||
161 | event->buffer = eventBuffer; | ||
162 | event->storage = storage; | ||
163 | resultCallback(event); | ||
164 | } | ||
165 | return true; | ||
166 | }); | ||
167 | completeCallback(); | ||
141 | }); | 168 | }); |
142 | } | 169 | } |
143 | 170 | ||
144 | //TODO call in plugin loader | ||
145 | // Akonadi2::FacadeFactory::instance().registerFacade<Akonadi2::Domain::Event, DummyResourceFacade>("dummyresource", [facade](){ return new DummyResourceFacade(facade); }); | ||
diff --git a/dummyresource/facade.h b/dummyresource/facade.h index f179c06..c76e62c 100644 --- a/dummyresource/facade.h +++ b/dummyresource/facade.h | |||
@@ -22,7 +22,9 @@ | |||
22 | #include "common/clientapi.h" | 22 | #include "common/clientapi.h" |
23 | #include "common/storage.h" | 23 | #include "common/storage.h" |
24 | 24 | ||
25 | class ResourceAccess; | 25 | namespace Akonadi2 { |
26 | class ResourceAccess; | ||
27 | } | ||
26 | 28 | ||
27 | class DummyResourceFacade : public Akonadi2::StoreFacade<Akonadi2::Domain::Event> | 29 | class DummyResourceFacade : public Akonadi2::StoreFacade<Akonadi2::Domain::Event> |
28 | { | 30 | { |
@@ -32,8 +34,9 @@ public: | |||
32 | virtual void create(const Akonadi2::Domain::Event &domainObject); | 34 | virtual void create(const Akonadi2::Domain::Event &domainObject); |
33 | virtual void modify(const Akonadi2::Domain::Event &domainObject); | 35 | virtual void modify(const Akonadi2::Domain::Event &domainObject); |
34 | virtual void remove(const Akonadi2::Domain::Event &domainObject); | 36 | virtual void remove(const Akonadi2::Domain::Event &domainObject); |
35 | virtual void load(const Akonadi2::Query &query, const std::function<void(const Akonadi2::Domain::Event::Ptr &)> &resultCallback); | 37 | virtual void load(const Akonadi2::Query &query, const std::function<void(const Akonadi2::Domain::Event::Ptr &)> &resultCallback, const std::function<void()> &completeCallback); |
36 | 38 | ||
37 | private: | 39 | private: |
38 | QSharedPointer<ResourceAccess> mResourceAccess; | 40 | void synchronizeResource(const std::function<void()> &continuation); |
41 | QSharedPointer<Akonadi2::ResourceAccess> mResourceAccess; | ||
39 | }; | 42 | }; |
diff --git a/dummyresource/resourcefactory.cpp b/dummyresource/resourcefactory.cpp index bd85b4f..2c43981 100644 --- a/dummyresource/resourcefactory.cpp +++ b/dummyresource/resourcefactory.cpp | |||
@@ -20,22 +20,95 @@ | |||
20 | #include "resourcefactory.h" | 20 | #include "resourcefactory.h" |
21 | #include "facade.h" | 21 | #include "facade.h" |
22 | #include "dummycalendar_generated.h" | 22 | #include "dummycalendar_generated.h" |
23 | #include <QUuid> | ||
24 | |||
25 | static std::string createEvent() | ||
26 | { | ||
27 | static const size_t attachmentSize = 1024*2; // 2KB | ||
28 | static uint8_t rawData[attachmentSize]; | ||
29 | static flatbuffers::FlatBufferBuilder fbb; | ||
30 | fbb.Clear(); | ||
31 | { | ||
32 | auto summary = fbb.CreateString("summary"); | ||
33 | auto data = fbb.CreateUninitializedVector<uint8_t>(attachmentSize); | ||
34 | DummyCalendar::DummyEventBuilder eventBuilder(fbb); | ||
35 | eventBuilder.add_summary(summary); | ||
36 | eventBuilder.add_attachment(data); | ||
37 | auto eventLocation = eventBuilder.Finish(); | ||
38 | DummyCalendar::FinishDummyEventBuffer(fbb, eventLocation); | ||
39 | memcpy((void*)DummyCalendar::GetDummyEvent(fbb.GetBufferPointer())->attachment()->Data(), rawData, attachmentSize); | ||
40 | } | ||
41 | |||
42 | return std::string(reinterpret_cast<const char *>(fbb.GetBufferPointer()), fbb.GetSize()); | ||
43 | } | ||
44 | |||
45 | QMap<QString, QString> populate() | ||
46 | { | ||
47 | QMap<QString, QString> content; | ||
48 | for (int i = 0; i < 2; i++) { | ||
49 | auto event = createEvent(); | ||
50 | content.insert(QString("key%1").arg(i), QString::fromStdString(event)); | ||
51 | } | ||
52 | return content; | ||
53 | } | ||
54 | |||
55 | static QMap<QString, QString> s_dataSource = populate(); | ||
23 | 56 | ||
24 | DummyResource::DummyResource() | 57 | DummyResource::DummyResource() |
25 | : Akonadi2::Resource() | 58 | : Akonadi2::Resource() |
26 | { | 59 | { |
60 | } | ||
27 | 61 | ||
62 | void findByRemoteId(QSharedPointer<Akonadi2::Storage> storage, const QString &rid, std::function<void(void *keyValue, int keySize, void *dataValue, int dataSize)> callback) | ||
63 | { | ||
64 | //TODO lookup in rid index instead of doing a full scan | ||
65 | const std::string ridString = rid.toStdString(); | ||
66 | storage->scan("", [&](void *keyValue, int keySize, void *dataValue, int dataSize) -> bool { | ||
67 | auto eventBuffer = DummyCalendar::GetDummyEvent(dataValue); | ||
68 | if (std::string(eventBuffer->remoteId()->c_str(), eventBuffer->remoteId()->size()) == ridString) { | ||
69 | callback(keyValue, keySize, dataValue, dataSize); | ||
70 | } | ||
71 | return true; | ||
72 | }); | ||
28 | } | 73 | } |
29 | 74 | ||
30 | void DummyResource::synchronizeWithSource(Akonadi2::Pipeline *pipeline) | 75 | void DummyResource::synchronizeWithSource(Akonadi2::Pipeline *pipeline) |
31 | { | 76 | { |
32 | // TODO actually populate the storage with new items | 77 | //TODO use a read-only transaction during the complete sync to sync against a defined revision |
33 | auto builder = DummyCalendar::DummyEventBuilder(m_fbb); | 78 | |
34 | builder .add_summary(m_fbb.CreateString("summary summary!")); | 79 | qDebug() << "synchronize with source"; |
35 | auto buffer = builder.Finish(); | 80 | |
36 | DummyCalendar::FinishDummyEventBuffer(m_fbb, buffer); | 81 | auto storage = QSharedPointer<Akonadi2::Storage>::create(Akonadi2::Store::storageLocation(), "org.kde.dummy"); |
37 | pipeline->newEntity("fakekey", m_fbb); | 82 | for (auto it = s_dataSource.constBegin(); it != s_dataSource.constEnd(); it++) { |
38 | m_fbb.Clear(); | 83 | bool isNew = true; |
84 | if (storage->exists()) { | ||
85 | findByRemoteId(storage, it.key(), [&](void *keyValue, int keySize, void *dataValue, int dataSize) { | ||
86 | isNew = false; | ||
87 | }); | ||
88 | } | ||
89 | |||
90 | if (isNew) { | ||
91 | //TODO: perhaps it would be more convenient to populate the domain types? | ||
92 | //Resource specific parts are not accessible that way, but then we would only have to implement the property mapping in one place | ||
93 | const QByteArray data = it.value().toUtf8(); | ||
94 | auto eventBuffer = DummyCalendar::GetDummyEvent(data.data()); | ||
95 | |||
96 | //Map the source format to the buffer format (which happens to be an exact copy here) | ||
97 | auto builder = DummyCalendar::DummyEventBuilder(m_fbb); | ||
98 | builder.add_summary(m_fbb.CreateString(eventBuffer->summary()->c_str())); | ||
99 | auto buffer = builder.Finish(); | ||
100 | DummyCalendar::FinishDummyEventBuffer(m_fbb, buffer); | ||
101 | |||
102 | //TODO toRFC4122 would probably be more efficient, but results in non-printable keys. | ||
103 | const auto key = QUuid::createUuid().toString().toUtf8(); | ||
104 | //TODO can we really just start populating the buffer and pass the buffer builder? | ||
105 | qDebug() << "new event"; | ||
106 | pipeline->newEntity(key, m_fbb); | ||
107 | } else { //modification | ||
108 | //TODO diff and create modification if necessary | ||
109 | } | ||
110 | } | ||
111 | //TODO find items to remove | ||
39 | } | 112 | } |
40 | 113 | ||
41 | void DummyResource::processCommand(int commandId, const QByteArray &data, uint size, Akonadi2::Pipeline *pipeline) | 114 | void DummyResource::processCommand(int commandId, const QByteArray &data, uint size, Akonadi2::Pipeline *pipeline) |
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4c288e9..dcf2f21 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt | |||
@@ -17,7 +17,9 @@ manual_tests ( | |||
17 | storagebenchmark | 17 | storagebenchmark |
18 | storagetest | 18 | storagetest |
19 | dummyresourcefacadetest | 19 | dummyresourcefacadetest |
20 | dummyresourcetest | ||
20 | ) | 21 | ) |
21 | 22 | ||
22 | target_link_libraries(dummyresourcefacadetest akonadi2_resource_dummy) | 23 | target_link_libraries(dummyresourcefacadetest akonadi2_resource_dummy) |
24 | target_link_libraries(dummyresourcetest akonadi2_resource_dummy) | ||
23 | 25 | ||
diff --git a/tests/dummyresourcetest.cpp b/tests/dummyresourcetest.cpp new file mode 100644 index 0000000..75d29de --- /dev/null +++ b/tests/dummyresourcetest.cpp | |||
@@ -0,0 +1,35 @@ | |||
1 | #include <QtTest> | ||
2 | |||
3 | #include <QString> | ||
4 | |||
5 | #include "common/resource.h" | ||
6 | #include "clientapi.h" | ||
7 | |||
8 | class DummyResourceTest : public QObject | ||
9 | { | ||
10 | Q_OBJECT | ||
11 | private Q_SLOTS: | ||
12 | void initTestCase() | ||
13 | { | ||
14 | auto factory = Akonadi2::ResourceFactory::load("org.kde.dummy"); | ||
15 | QVERIFY(factory); | ||
16 | } | ||
17 | |||
18 | void cleanupTestCase() | ||
19 | { | ||
20 | } | ||
21 | |||
22 | void testSync() | ||
23 | { | ||
24 | Akonadi2::Query query; | ||
25 | query.resources << "org.kde.dummy"; | ||
26 | |||
27 | async::SyncListResult<Akonadi2::Domain::Event::Ptr> result(Akonadi2::Store::load<Akonadi2::Domain::Event>(query)); | ||
28 | result.exec(); | ||
29 | QVERIFY(!result.isEmpty()); | ||
30 | } | ||
31 | |||
32 | }; | ||
33 | |||
34 | QTEST_MAIN(DummyResourceTest) | ||
35 | #include "dummyresourcetest.moc" | ||