summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2017-07-03 14:02:27 +0200
committerChristian Mollekopf <chrigi_1@fastmail.fm>2017-07-03 14:02:27 +0200
commit55fe06979ceebe67553135b43aa47e70d931304b (patch)
tree16b10a744879cc1872d6c07624b59ae64469ddbf
parent56fae95f49a1ca8ca614bd9f89b0ea5f872765e9 (diff)
parent288946f1694c2abe1d2c5800c87339d1e8780e4b (diff)
downloadsink-55fe06979ceebe67553135b43aa47e70d931304b.tar.gz
sink-55fe06979ceebe67553135b43aa47e70d931304b.zip
Merge branch 'develop'
-rw-r--r--CMakeLists.txt6
-rw-r--r--common/bufferadaptor.h3
-rw-r--r--common/bufferutils.h2
-rw-r--r--common/changereplay.cpp57
-rw-r--r--common/changereplay.h3
-rw-r--r--common/commands.cpp22
-rw-r--r--common/commands.h8
-rw-r--r--common/commands/notification.fbs2
-rw-r--r--common/configstore.cpp2
-rw-r--r--common/contactpreprocessor.cpp2
-rw-r--r--common/datastorequery.cpp41
-rw-r--r--common/datastorequery.h1
-rw-r--r--common/definitions.cpp41
-rw-r--r--common/definitions.h1
-rw-r--r--common/domain/applicationdomaintype.cpp3
-rw-r--r--common/domain/applicationdomaintype.h1
-rw-r--r--common/domain/contact.fbs1
-rw-r--r--common/domain/typeimplementations.cpp193
-rw-r--r--common/domain/typeimplementations.h25
-rw-r--r--common/domain/typeimplementations_p.h154
-rw-r--r--common/domainadaptor.h63
-rw-r--r--common/facade.cpp1
-rw-r--r--common/facade.h1
-rw-r--r--common/genericresource.h2
-rw-r--r--common/listener.cpp3
-rw-r--r--common/listener.h2
-rw-r--r--common/log.cpp87
-rw-r--r--common/log.h43
-rw-r--r--common/mail/threadindexer.cpp84
-rw-r--r--common/mail/threadindexer.h1
-rw-r--r--common/mailpreprocessor.cpp2
-rw-r--r--common/messagequeue.cpp2
-rw-r--r--common/modelresult.cpp1
-rw-r--r--common/notification.cpp4
-rw-r--r--common/notification.h2
-rw-r--r--common/pipeline.cpp10
-rw-r--r--common/propertymapper.cpp29
-rw-r--r--common/propertymapper.h164
-rw-r--r--common/queryrunner.cpp6
-rw-r--r--common/queryrunner.h2
-rw-r--r--common/resourceaccess.cpp4
-rw-r--r--common/resourceaccess.h2
-rw-r--r--common/resourcecontrol.cpp2
-rw-r--r--common/resourcefacade.cpp53
-rw-r--r--common/resourcefacade.h18
-rw-r--r--common/specialpurposepreprocessor.cpp2
-rw-r--r--common/storage.h11
-rw-r--r--common/storage/entitystore.cpp121
-rw-r--r--common/storage_common.cpp23
-rw-r--r--common/storage_lmdb.cpp153
-rw-r--r--common/store.cpp15
-rw-r--r--common/store.h12
-rw-r--r--common/synchronizer.cpp47
-rw-r--r--common/synchronizer.h12
-rw-r--r--common/synchronizerstore.cpp2
-rw-r--r--common/test.cpp3
-rw-r--r--common/typeindex.cpp40
-rw-r--r--common/typeindex.h9
-rw-r--r--dist/sink.spec12
-rw-r--r--examples/davresource/CMakeLists.txt4
-rw-r--r--examples/davresource/davresource.cpp47
-rw-r--r--examples/davresource/davresource.h4
-rw-r--r--examples/dummyresource/domainadaptor.cpp3
-rw-r--r--examples/dummyresource/domainadaptor.h8
-rw-r--r--examples/dummyresource/resourcefactory.cpp2
-rw-r--r--examples/imapresource/CMakeLists.txt2
-rw-r--r--examples/imapresource/imapresource.cpp126
-rw-r--r--examples/imapresource/imapserverproxy.cpp88
-rw-r--r--examples/imapresource/imapserverproxy.h30
-rw-r--r--examples/imapresource/tests/imapmailsyncbenchmark.cpp2
-rw-r--r--examples/imapresource/tests/imapserverproxytest.cpp2
-rw-r--r--examples/imapresource/tests/populatemailbox.sh11
-rw-r--r--examples/imapresource/tests/resetmailbox.sh3
-rw-r--r--examples/maildirresource/maildirresource.cpp26
-rw-r--r--examples/mailtransportresource/mailtransport.cpp2
-rw-r--r--examples/mailtransportresource/mailtransportresource.cpp2
-rw-r--r--examples/mailtransportresource/tests/mailtransporttest.cpp4
-rw-r--r--hawd_defs/mail_query_threadleader8
-rw-r--r--sinksh/CMakeLists.txt1
-rw-r--r--sinksh/state.cpp2
-rw-r--r--sinksh/syntax_modules/sink_upgrade.cpp47
-rw-r--r--synchronizer/CMakeLists.txt2
-rw-r--r--synchronizer/main.cpp2
-rw-r--r--tests/CMakeLists.txt3
-rw-r--r--tests/dbwriter.cpp45
-rw-r--r--tests/domainadaptortest.cpp63
-rw-r--r--tests/hawd/state.cpp2
-rw-r--r--tests/interresourcemovetest.cpp2
-rw-r--r--tests/mailquerybenchmark.cpp8
-rw-r--r--tests/mailsynctest.cpp51
-rw-r--r--tests/mailtest.cpp20
-rw-r--r--tests/mailthreadtest.cpp6
-rw-r--r--tests/messagequeuetest.cpp2
-rw-r--r--tests/notificationtest.cpp21
-rw-r--r--tests/querytest.cpp138
-rw-r--r--tests/storagetest.cpp55
-rw-r--r--tests/testimplementations.h4
97 files changed, 1550 insertions, 916 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 53ae5df..6690c4c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,10 +3,10 @@ cmake_minimum_required(VERSION 3.0)
3cmake_policy(SET CMP0048 NEW) 3cmake_policy(SET CMP0048 NEW)
4cmake_policy(SET CMP0028 NEW) 4cmake_policy(SET CMP0028 NEW)
5 5
6project(sink VERSION 0.2.0) 6project(sink VERSION 0.3.0)
7 7
8option(BUILD_MAILDIR "BUILD_MAILDIR" ON) 8option(BUILD_MAILDIR "BUILD_MAILDIR" ON)
9option(BUILD_DAV "BUILD_DAV" OFF) 9option(BUILD_DAV "BUILD_DAV" ON)
10option(AVOID_BINDING_REBUILD "AVOID_BINDING_REBUILD" OFF) 10option(AVOID_BINDING_REBUILD "AVOID_BINDING_REBUILD" OFF)
11option(CATCH_ERRORS "CATCH_ERRORS" OFF) 11option(CATCH_ERRORS "CATCH_ERRORS" OFF)
12option(ENABLE_MEMCHECK "Build valgrind tests" OFF) 12option(ENABLE_MEMCHECK "Build valgrind tests" OFF)
@@ -28,7 +28,7 @@ include(KDEInstallDirs)
28find_package(Qt5 COMPONENTS REQUIRED Core Network Gui) 28find_package(Qt5 COMPONENTS REQUIRED Core Network Gui)
29find_package(KF5 COMPONENTS REQUIRED Mime Contacts) 29find_package(KF5 COMPONENTS REQUIRED Mime Contacts)
30find_package(FlatBuffers REQUIRED 1.4.0) 30find_package(FlatBuffers REQUIRED 1.4.0)
31find_package(KAsync REQUIRED 0.1.0) 31find_package(KAsync REQUIRED 0.1.2)
32find_package(LMDB REQUIRED 0.9) 32find_package(LMDB REQUIRED 0.9)
33 33
34find_program(MEMORYCHECK_COMMAND valgrind) 34find_program(MEMORYCHECK_COMMAND valgrind)
diff --git a/common/bufferadaptor.h b/common/bufferadaptor.h
index fd4809b..f393388 100644
--- a/common/bufferadaptor.h
+++ b/common/bufferadaptor.h
@@ -66,8 +66,7 @@ public:
66 virtual QVariant getProperty(const QByteArray &key) const 66 virtual QVariant getProperty(const QByteArray &key) const
67 { 67 {
68 if (!mValues.contains(key)) { 68 if (!mValues.contains(key)) {
69 qWarning() << "Tried to read value that is not available; Did you forget to call Query::request? Property: " << key; 69 return {};
70 return QVariant{};
71 } 70 }
72 return mValues.value(key); 71 return mValues.value(key);
73 } 72 }
diff --git a/common/bufferutils.h b/common/bufferutils.h
index 6763ced..f0460b7 100644
--- a/common/bufferutils.h
+++ b/common/bufferutils.h
@@ -38,7 +38,7 @@ static flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<flatbuffers::
38{ 38{
39 std::vector<flatbuffers::Offset<flatbuffers::String>> modifiedPropertiesList; 39 std::vector<flatbuffers::Offset<flatbuffers::String>> modifiedPropertiesList;
40 for (const auto &change : list) { 40 for (const auto &change : list) {
41 auto s = fbb.CreateString(change); 41 auto s = fbb.CreateString(change.toStdString());
42 modifiedPropertiesList.push_back(s); 42 modifiedPropertiesList.push_back(s);
43 } 43 }
44 return fbb.CreateVector(modifiedPropertiesList); 44 return fbb.CreateVector(modifiedPropertiesList);
diff --git a/common/changereplay.cpp b/common/changereplay.cpp
index 7895b66..0adbd78 100644
--- a/common/changereplay.cpp
+++ b/common/changereplay.cpp
@@ -30,8 +30,7 @@ using namespace Sink;
30using namespace Sink::Storage; 30using namespace Sink::Storage;
31 31
32ChangeReplay::ChangeReplay(const ResourceContext &resourceContext, const Sink::Log::Context &ctx) 32ChangeReplay::ChangeReplay(const ResourceContext &resourceContext, const Sink::Log::Context &ctx)
33 : mStorage(storageLocation(), resourceContext.instanceId(), DataStore::ReadOnly), mChangeReplayStore(storageLocation(), resourceContext.instanceId() + ".changereplay", DataStore::ReadWrite), mReplayInProgress(false), mLogCtx{ctx.subContext("changereplay")}, 33 : mStorage(storageLocation(), resourceContext.instanceId(), DataStore::ReadOnly), mChangeReplayStore(storageLocation(), resourceContext.instanceId() + ".changereplay", DataStore::ReadWrite), mReplayInProgress(false), mLogCtx{ctx.subContext("changereplay")}
34 mGuard{new QObject}
35{ 34{
36} 35}
37 36
@@ -84,6 +83,8 @@ KAsync::Job<void> ChangeReplay::replayNextRevision()
84 auto replayStoreTransaction = mChangeReplayStore.createTransaction(DataStore::ReadOnly, [this](const DataStore::Error &error) { 83 auto replayStoreTransaction = mChangeReplayStore.createTransaction(DataStore::ReadOnly, [this](const DataStore::Error &error) {
85 SinkWarningCtx(mLogCtx) << error.message; 84 SinkWarningCtx(mLogCtx) << error.message;
86 }); 85 });
86 Q_ASSERT(mMainStoreTransaction);
87 Q_ASSERT(replayStoreTransaction);
87 replayStoreTransaction.openDatabase().scan("lastReplayedRevision", 88 replayStoreTransaction.openDatabase().scan("lastReplayedRevision",
88 [lastReplayedRevision](const QByteArray &key, const QByteArray &value) -> bool { 89 [lastReplayedRevision](const QByteArray &key, const QByteArray &value) -> bool {
89 *lastReplayedRevision = value.toLongLong(); 90 *lastReplayedRevision = value.toLongLong();
@@ -98,16 +99,13 @@ KAsync::Job<void> ChangeReplay::replayNextRevision()
98 SinkTraceCtx(mLogCtx) << "Changereplay from " << *lastReplayedRevision << " to " << *topRevision; 99 SinkTraceCtx(mLogCtx) << "Changereplay from " << *lastReplayedRevision << " to " << *topRevision;
99 return KAsync::doWhile( 100 return KAsync::doWhile(
100 [this, lastReplayedRevision, topRevision]() -> KAsync::Job<KAsync::ControlFlowFlag> { 101 [this, lastReplayedRevision, topRevision]() -> KAsync::Job<KAsync::ControlFlowFlag> {
101 if (!mGuard) {
102 SinkTraceCtx(mLogCtx) << "Exit due to guard";
103 return KAsync::value(KAsync::Break);
104 }
105 if (*lastReplayedRevision >= *topRevision) { 102 if (*lastReplayedRevision >= *topRevision) {
106 SinkTraceCtx(mLogCtx) << "Done replaying" << *lastReplayedRevision << *topRevision; 103 SinkTraceCtx(mLogCtx) << "Done replaying" << *lastReplayedRevision << *topRevision;
107 return KAsync::value(KAsync::Break); 104 return KAsync::value(KAsync::Break);
108 } 105 }
106 Q_ASSERT(mMainStoreTransaction);
109 107
110 KAsync::Job<void> replayJob = KAsync::null<void>(); 108 auto replayJob = KAsync::null();
111 qint64 revision = *lastReplayedRevision + 1; 109 qint64 revision = *lastReplayedRevision + 1;
112 while (revision <= *topRevision) { 110 while (revision <= *topRevision) {
113 const auto uid = DataStore::getUidFromRevision(mMainStoreTransaction, revision); 111 const auto uid = DataStore::getUidFromRevision(mMainStoreTransaction, revision);
@@ -131,14 +129,14 @@ KAsync::Job<void> ChangeReplay::replayNextRevision()
131 if (canReplay(type, key, entityBuffer)) { 129 if (canReplay(type, key, entityBuffer)) {
132 SinkTraceCtx(mLogCtx) << "Replaying " << key; 130 SinkTraceCtx(mLogCtx) << "Replaying " << key;
133 replayJob = replay(type, key, entityBuffer); 131 replayJob = replay(type, key, entityBuffer);
132 //Set the last revision we tried to replay
133 *lastReplayedRevision = revision;
134 //Execute replay job and commit
135 break;
134 } else { 136 } else {
135 SinkTraceCtx(mLogCtx) << "Cannot replay " << key; 137 SinkTraceCtx(mLogCtx) << "Not replaying " << key;
136 //We silently skip over revisions that cannot be replayed, as this is not an error. 138 //We silently skip over revisions that cannot be replayed, as this is not an error.
137 replayJob = KAsync::null();
138 } 139 }
139 //Set the last revision we tried to replay
140 *lastReplayedRevision = revision;
141 break;
142 } 140 }
143 } 141 }
144 //Bump the revision if we failed to even attempt to replay. This will simply skip over those revisions, as we can't recover from those situations. 142 //Bump the revision if we failed to even attempt to replay. This will simply skip over those revisions, as we can't recover from those situations.
@@ -149,22 +147,22 @@ KAsync::Job<void> ChangeReplay::replayNextRevision()
149 if (error) { 147 if (error) {
150 SinkWarningCtx(mLogCtx) << "Change replay failed: " << error << "Last replayed revision: " << *lastReplayedRevision; 148 SinkWarningCtx(mLogCtx) << "Change replay failed: " << error << "Last replayed revision: " << *lastReplayedRevision;
151 //We're probably not online or so, so postpone retrying 149 //We're probably not online or so, so postpone retrying
152 return KAsync::value(KAsync::Break); 150 return KAsync::value(KAsync::Break).then(KAsync::error<KAsync::ControlFlowFlag>(error));
151 }
152 SinkTraceCtx(mLogCtx) << "Replayed until: " << *lastReplayedRevision;
153
154 recordReplayedRevision(*lastReplayedRevision);
155 reportProgress(*lastReplayedRevision, *topRevision);
156
157 const bool gotMoreToReplay = (*lastReplayedRevision < *topRevision);
158 if (gotMoreToReplay) {
159 SinkTraceCtx(mLogCtx) << "Replaying some more...";
160 //Replay more if we have more
161 return KAsync::wait(0).then(KAsync::value(KAsync::Continue));
153 } else { 162 } else {
154 SinkTraceCtx(mLogCtx) << "Replayed until: " << *lastReplayedRevision; 163 return KAsync::value(KAsync::Break);
155 recordReplayedRevision(*lastReplayedRevision);
156 if (*lastReplayedRevision < *topRevision) {
157 SinkTraceCtx(mLogCtx) << "Replaying some more...";
158 //Replay more if we have more
159 return KAsync::wait(0).then(KAsync::value(KAsync::Continue));
160 } else {
161 return KAsync::value(KAsync::Break);
162 }
163 } 164 }
164 //We shouldn't ever get here 165 }).guard(&mGuard);
165 Q_ASSERT(false);
166 return KAsync::value(KAsync::Break);
167 });
168 }); 166 });
169 }) 167 })
170 .then([this](const KAsync::Error &error) { 168 .then([this](const KAsync::Error &error) {
@@ -181,7 +179,12 @@ KAsync::Job<void> ChangeReplay::replayNextRevision()
181 emit changesReplayed(); 179 emit changesReplayed();
182 } 180 }
183 } 181 }
184 }); 182 if (error) {
183 return KAsync::error(error);
184 } else {
185 return KAsync::null();
186 }
187 }).guard(&mGuard);
185} 188}
186 189
187void ChangeReplay::revisionChanged() 190void ChangeReplay::revisionChanged()
diff --git a/common/changereplay.h b/common/changereplay.h
index edc4462..22e26a5 100644
--- a/common/changereplay.h
+++ b/common/changereplay.h
@@ -54,6 +54,7 @@ public slots:
54protected: 54protected:
55 virtual KAsync::Job<void> replay(const QByteArray &type, const QByteArray &key, const QByteArray &value) = 0; 55 virtual KAsync::Job<void> replay(const QByteArray &type, const QByteArray &key, const QByteArray &value) = 0;
56 virtual bool canReplay(const QByteArray &type, const QByteArray &key, const QByteArray &value) = 0; 56 virtual bool canReplay(const QByteArray &type, const QByteArray &key, const QByteArray &value) = 0;
57 virtual void reportProgress(int progress, int total, const QByteArrayList &applicableEntities = {}){};
57 Sink::Storage::DataStore mStorage; 58 Sink::Storage::DataStore mStorage;
58 KAsync::Job<void> replayNextRevision(); 59 KAsync::Job<void> replayNextRevision();
59 60
@@ -63,7 +64,7 @@ private:
63 bool mReplayInProgress; 64 bool mReplayInProgress;
64 Sink::Storage::DataStore::Transaction mMainStoreTransaction; 65 Sink::Storage::DataStore::Transaction mMainStoreTransaction;
65 Sink::Log::Context mLogCtx; 66 Sink::Log::Context mLogCtx;
66 QSharedPointer<QObject> mGuard; 67 QObject mGuard;
67}; 68};
68 69
69class NullChangeReplay : public ChangeReplay 70class NullChangeReplay : public ChangeReplay
diff --git a/common/commands.cpp b/common/commands.cpp
index eeb7f08..24f2017 100644
--- a/common/commands.cpp
+++ b/common/commands.cpp
@@ -1,5 +1,6 @@
1/* 1/*
2 * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org> 2 * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org>
3 * Copyright (C) 2016 Christian Mollekopf <mollekopf@kolabsys.com>
3 * 4 *
4 * This library is free software; you can redistribute it and/or 5 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public 6 * modify it under the terms of the GNU Lesser General Public
@@ -20,7 +21,7 @@
20 21
21#include "commands.h" 22#include "commands.h"
22 23
23#include <QIODevice> 24#include <QLocalSocket>
24#include <log.h> 25#include <log.h>
25 26
26namespace Sink { 27namespace Sink {
@@ -73,19 +74,19 @@ int headerSize()
73 return sizeof(int) + (sizeof(uint) * 2); 74 return sizeof(int) + (sizeof(uint) * 2);
74} 75}
75 76
76void write(QIODevice *device, int messageId, int commandId) 77void write(QLocalSocket *device, int messageId, int commandId)
77{ 78{
78 write(device, messageId, commandId, 0, 0); 79 write(device, messageId, commandId, 0, 0);
79} 80}
80 81
81static void write(QIODevice *device, const char *buffer, uint size) 82static void write(QLocalSocket *device, const char *buffer, uint size)
82{ 83{
83 if (device->write(buffer, size) < 0) { 84 if (device->write(buffer, size) < 0) {
84 SinkWarningCtx(Sink::Log::Context{"commands"}) << "Error while writing " << device->errorString(); 85 SinkWarningCtx(Sink::Log::Context{"commands"}) << "Error while writing " << device->errorString();
85 } 86 }
86} 87}
87 88
88void write(QIODevice *device, int messageId, int commandId, const char *buffer, uint size) 89void write(QLocalSocket *device, int messageId, int commandId, const char *buffer, uint size)
89{ 90{
90 if (size > 0 && !buffer) { 91 if (size > 0 && !buffer) {
91 size = 0; 92 size = 0;
@@ -97,15 +98,16 @@ void write(QIODevice *device, int messageId, int commandId, const char *buffer,
97 if (buffer) { 98 if (buffer) {
98 write(device, buffer, size); 99 write(device, buffer, size);
99 } 100 }
101 //The default implementation will happily buffer 200k bytes before sending it out which doesn't make the sytem exactly responsive.
102 //1k is arbitrary, but fits a bunch of messages at least.
103 if (device->bytesToWrite() > 1000) {
104 device->flush();
105 }
100} 106}
101 107
102void write(QIODevice *device, int messageId, int commandId, flatbuffers::FlatBufferBuilder &fbb) 108void write(QLocalSocket *device, int messageId, int commandId, flatbuffers::FlatBufferBuilder &fbb)
103{ 109{
104 const int dataSize = fbb.GetSize(); 110 write(device, messageId, commandId, (const char *)fbb.GetBufferPointer(), fbb.GetSize());
105 write(device, (const char *)&messageId, sizeof(int));
106 write(device, (const char *)&commandId, sizeof(int));
107 write(device, (const char *)&dataSize, sizeof(int));
108 write(device, (const char *)fbb.GetBufferPointer(), dataSize);
109} 111}
110 112
111} // namespace Commands 113} // namespace Commands
diff --git a/common/commands.h b/common/commands.h
index 6d5d39b..1548eac 100644
--- a/common/commands.h
+++ b/common/commands.h
@@ -24,7 +24,7 @@
24#include <flatbuffers/flatbuffers.h> 24#include <flatbuffers/flatbuffers.h>
25#include <QByteArray> 25#include <QByteArray>
26 26
27class QIODevice; 27class QLocalSocket;
28 28
29namespace Sink { 29namespace Sink {
30 30
@@ -55,9 +55,9 @@ enum CommandIds
55QByteArray name(int commandId); 55QByteArray name(int commandId);
56 56
57int SINK_EXPORT headerSize(); 57int SINK_EXPORT headerSize();
58void SINK_EXPORT write(QIODevice *device, int messageId, int commandId); 58void SINK_EXPORT write(QLocalSocket *device, int messageId, int commandId);
59void SINK_EXPORT write(QIODevice *device, int messageId, int commandId, const char *buffer, uint size); 59void SINK_EXPORT write(QLocalSocket *device, int messageId, int commandId, const char *buffer, uint size);
60void SINK_EXPORT write(QIODevice *device, int messageId, int commandId, flatbuffers::FlatBufferBuilder &fbb); 60void SINK_EXPORT write(QLocalSocket *device, int messageId, int commandId, flatbuffers::FlatBufferBuilder &fbb);
61} 61}
62 62
63} // namespace Sink 63} // namespace Sink
diff --git a/common/commands/notification.fbs b/common/commands/notification.fbs
index 517111c..7ced666 100644
--- a/common/commands/notification.fbs
+++ b/common/commands/notification.fbs
@@ -5,6 +5,8 @@ table Notification {
5 identifier: string; //An identifier that links back to the something related to the notification (e.g. a command id) 5 identifier: string; //An identifier that links back to the something related to the notification (e.g. a command id)
6 message: string; 6 message: string;
7 code: int = 0; //See notification.h 7 code: int = 0; //See notification.h
8 progress: int = 0; //See notification.h
9 total: int = 0; //See notification.h
8 entities: [string]; //A list of entities this applies to 10 entities: [string]; //A list of entities this applies to
9} 11}
10 12
diff --git a/common/configstore.cpp b/common/configstore.cpp
index 1ae9da8..6751907 100644
--- a/common/configstore.cpp
+++ b/common/configstore.cpp
@@ -24,8 +24,6 @@
24#include <log.h> 24#include <log.h>
25#include <definitions.h> 25#include <definitions.h>
26 26
27SINK_DEBUG_AREA("configstore")
28
29static QSharedPointer<QSettings> getConfig(const QByteArray &identifier) 27static QSharedPointer<QSettings> getConfig(const QByteArray &identifier)
30{ 28{
31 return QSharedPointer<QSettings>::create(Sink::configLocation() + "/" + identifier + ".ini", QSettings::IniFormat); 29 return QSharedPointer<QSettings>::create(Sink::configLocation() + "/" + identifier + ".ini", QSettings::IniFormat);
diff --git a/common/contactpreprocessor.cpp b/common/contactpreprocessor.cpp
index ac2c3bc..d331421 100644
--- a/common/contactpreprocessor.cpp
+++ b/common/contactpreprocessor.cpp
@@ -35,6 +35,8 @@ void updatedProperties(Sink::ApplicationDomain::Contact &contact, const KContact
35 emails << Sink::ApplicationDomain::Contact::Email{Sink::ApplicationDomain::Contact::Email::Undefined, email}; 35 emails << Sink::ApplicationDomain::Contact::Email{Sink::ApplicationDomain::Contact::Email::Undefined, email};
36 } 36 }
37 contact.setEmails(emails); 37 contact.setEmails(emails);
38
39 contact.setPhoto(addressee.photo().rawData());
38} 40}
39 41
40ContactPropertyExtractor::~ContactPropertyExtractor() 42ContactPropertyExtractor::~ContactPropertyExtractor()
diff --git a/common/datastorequery.cpp b/common/datastorequery.cpp
index 2e0c348..4c95606 100644
--- a/common/datastorequery.cpp
+++ b/common/datastorequery.cpp
@@ -43,6 +43,8 @@ class Source : public FilterBase {
43 43
44 QVector<QByteArray> mIds; 44 QVector<QByteArray> mIds;
45 QVector<QByteArray>::ConstIterator mIt; 45 QVector<QByteArray>::ConstIterator mIt;
46 QVector<QByteArray> mIncrementalIds;
47 QVector<QByteArray>::ConstIterator mIncrementalIt;
46 48
47 Source (const QVector<QByteArray> &ids, DataStoreQuery *store) 49 Source (const QVector<QByteArray> &ids, DataStoreQuery *store)
48 : FilterBase(store), 50 : FilterBase(store),
@@ -63,21 +65,36 @@ class Source : public FilterBase {
63 65
64 void add(const QVector<QByteArray> &ids) 66 void add(const QVector<QByteArray> &ids)
65 { 67 {
66 mIds = ids; 68 mIncrementalIds = ids;
67 mIt = mIds.constBegin(); 69 mIncrementalIt = mIncrementalIds.constBegin();
68 } 70 }
69 71
70 bool next(const std::function<void(const ResultSet::Result &result)> &callback) Q_DECL_OVERRIDE 72 bool next(const std::function<void(const ResultSet::Result &result)> &callback) Q_DECL_OVERRIDE
71 { 73 {
72 if (mIt == mIds.constEnd()) { 74 if (!mIncrementalIds.isEmpty()) {
73 return false; 75 if (mIncrementalIt == mIncrementalIds.constEnd()) {
76 return false;
77 }
78 readEntity(*mIncrementalIt, [this, callback](const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Operation operation) {
79 SinkTraceCtx(mDatastore->mLogCtx) << "Source: Read entity: " << entity.identifier() << operationName(operation);
80 callback({entity, operation});
81 });
82 mIncrementalIt++;
83 if (mIncrementalIt == mIncrementalIds.constEnd()) {
84 return false;
85 }
86 return true;
87 } else {
88 if (mIt == mIds.constEnd()) {
89 return false;
90 }
91 readEntity(*mIt, [this, callback](const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Operation operation) {
92 SinkTraceCtx(mDatastore->mLogCtx) << "Source: Read entity: " << entity.identifier() << operationName(operation);
93 callback({entity, operation});
94 });
95 mIt++;
96 return mIt != mIds.constEnd();
74 } 97 }
75 readEntity(*mIt, [this, callback](const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Operation operation) {
76 SinkTraceCtx(mDatastore->mLogCtx) << "Source: Read entity: " << entity.identifier() << operationName(operation);
77 callback({entity, operation});
78 });
79 mIt++;
80 return mIt != mIds.constEnd();
81 } 98 }
82}; 99};
83 100
@@ -599,6 +616,10 @@ ResultSet DataStoreQuery::update(qint64 baseRevision)
599 return ResultSet(generator, [this]() { mCollector->skip(); }); 616 return ResultSet(generator, [this]() { mCollector->skip(); });
600} 617}
601 618
619void DataStoreQuery::updateComplete()
620{
621 mSource->mIncrementalIds.clear();
622}
602 623
603ResultSet DataStoreQuery::execute() 624ResultSet DataStoreQuery::execute()
604{ 625{
diff --git a/common/datastorequery.h b/common/datastorequery.h
index ee5f99e..de4ae26 100644
--- a/common/datastorequery.h
+++ b/common/datastorequery.h
@@ -50,6 +50,7 @@ public:
50 ~DataStoreQuery(); 50 ~DataStoreQuery();
51 ResultSet execute(); 51 ResultSet execute();
52 ResultSet update(qint64 baseRevision); 52 ResultSet update(qint64 baseRevision);
53 void updateComplete();
53 54
54 State::Ptr getState(); 55 State::Ptr getState();
55 56
diff --git a/common/definitions.cpp b/common/definitions.cpp
index 3fc4700..17977bc 100644
--- a/common/definitions.cpp
+++ b/common/definitions.cpp
@@ -23,6 +23,17 @@
23#include <QStandardPaths> 23#include <QStandardPaths>
24#include <QDir> 24#include <QDir>
25 25
26static bool rereadDataLocation = true;
27static bool rereadConfigLocation = true;
28static bool rereadTemporaryFileLocation = true;
29
30void Sink::clearLocationCache()
31{
32 rereadDataLocation = true;
33 rereadConfigLocation = true;
34 rereadTemporaryFileLocation = true;
35}
36
26QString Sink::storageLocation() 37QString Sink::storageLocation()
27{ 38{
28 return dataLocation() + "/storage"; 39 return dataLocation() + "/storage";
@@ -30,21 +41,37 @@ QString Sink::storageLocation()
30 41
31QString Sink::dataLocation() 42QString Sink::dataLocation()
32{ 43{
33 return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/sink"; 44 static QString location;
45 if (rereadDataLocation) {
46 location = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/sink";
47 rereadDataLocation = false;
48 }
49 return location;
34} 50}
35 51
36QString Sink::configLocation() 52QString Sink::configLocation()
37{ 53{
38 return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/sink"; 54 static QString location;
55 if (rereadConfigLocation) {
56 location = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/sink";
57 rereadConfigLocation = false;
58 }
59 return location;
39} 60}
40 61
41QString Sink::temporaryFileLocation() 62QString Sink::temporaryFileLocation()
42{ 63{
43 auto path = dataLocation() + "/temporaryFiles"; 64 static QString location;
44 //FIXME create in a singleton on startup? 65 static bool dirCreated = false;
45 QDir dir; 66 if (rereadTemporaryFileLocation) {
46 dir.mkpath(path); 67 location = dataLocation() + "/temporaryFiles";
47 return path; 68 dirCreated = QDir{}.mkpath(location);
69 rereadTemporaryFileLocation = false;
70 }
71 if (!dirCreated && QDir{}.mkpath(location)) {
72 dirCreated = true;
73 }
74 return location;
48} 75}
49 76
50QString Sink::resourceStorageLocation(const QByteArray &resourceInstanceIdentifier) 77QString Sink::resourceStorageLocation(const QByteArray &resourceInstanceIdentifier)
diff --git a/common/definitions.h b/common/definitions.h
index e8cd45e..ce9e794 100644
--- a/common/definitions.h
+++ b/common/definitions.h
@@ -25,6 +25,7 @@
25#include <QByteArray> 25#include <QByteArray>
26 26
27namespace Sink { 27namespace Sink {
28void SINK_EXPORT clearLocationCache();
28QString SINK_EXPORT storageLocation(); 29QString SINK_EXPORT storageLocation();
29QString SINK_EXPORT dataLocation(); 30QString SINK_EXPORT dataLocation();
30QString SINK_EXPORT configLocation(); 31QString SINK_EXPORT configLocation();
diff --git a/common/domain/applicationdomaintype.cpp b/common/domain/applicationdomaintype.cpp
index 67d463f..3718f77 100644
--- a/common/domain/applicationdomaintype.cpp
+++ b/common/domain/applicationdomaintype.cpp
@@ -25,8 +25,6 @@
25#include "storage.h" //for generateUid() 25#include "storage.h" //for generateUid()
26#include <QFile> 26#include <QFile>
27 27
28SINK_DEBUG_AREA("applicationdomaintype");
29
30QDebug Sink::ApplicationDomain::operator<< (QDebug d, const Sink::ApplicationDomain::Mail::Contact &c) 28QDebug Sink::ApplicationDomain::operator<< (QDebug d, const Sink::ApplicationDomain::Mail::Contact &c)
31{ 29{
32 d << "Contact(" << c.name << ", " << c.emailAddress << ")"; 30 d << "Contact(" << c.name << ", " << c.emailAddress << ")";
@@ -114,6 +112,7 @@ SINK_REGISTER_PROPERTY(Contact, Lastname);
114SINK_REGISTER_PROPERTY(Contact, Emails); 112SINK_REGISTER_PROPERTY(Contact, Emails);
115SINK_REGISTER_PROPERTY(Contact, Vcard); 113SINK_REGISTER_PROPERTY(Contact, Vcard);
116SINK_REGISTER_PROPERTY(Contact, Addressbook); 114SINK_REGISTER_PROPERTY(Contact, Addressbook);
115SINK_REGISTER_PROPERTY(Contact, Photo);
117 116
118SINK_REGISTER_ENTITY(Addressbook); 117SINK_REGISTER_ENTITY(Addressbook);
119SINK_REGISTER_PROPERTY(Addressbook, Name); 118SINK_REGISTER_PROPERTY(Addressbook, Name);
diff --git a/common/domain/applicationdomaintype.h b/common/domain/applicationdomaintype.h
index e5aa46e..602d54c 100644
--- a/common/domain/applicationdomaintype.h
+++ b/common/domain/applicationdomaintype.h
@@ -377,6 +377,7 @@ struct SINK_EXPORT Contact : public Entity {
377 SINK_PROPERTY(QString, Lastname, lastname); 377 SINK_PROPERTY(QString, Lastname, lastname);
378 SINK_PROPERTY(QList<Email>, Emails, emails); 378 SINK_PROPERTY(QList<Email>, Emails, emails);
379 SINK_PROPERTY(QByteArray, Vcard, vcard); 379 SINK_PROPERTY(QByteArray, Vcard, vcard);
380 SINK_PROPERTY(QByteArray, Photo, photo);
380 SINK_REFERENCE_PROPERTY(Addressbook, Addressbook, addressbook); 381 SINK_REFERENCE_PROPERTY(Addressbook, Addressbook, addressbook);
381}; 382};
382 383
diff --git a/common/domain/contact.fbs b/common/domain/contact.fbs
index d941d5a..e689756 100644
--- a/common/domain/contact.fbs
+++ b/common/domain/contact.fbs
@@ -13,6 +13,7 @@ table Contact {
13 addressbook:string; 13 addressbook:string;
14 emails: [ContactEmail]; 14 emails: [ContactEmail];
15 vcard: string; 15 vcard: string;
16 photo: string;
16} 17}
17 18
18root_type Contact; 19root_type Contact;
diff --git a/common/domain/typeimplementations.cpp b/common/domain/typeimplementations.cpp
index eb3851e..47a9cf7 100644
--- a/common/domain/typeimplementations.cpp
+++ b/common/domain/typeimplementations.cpp
@@ -28,25 +28,52 @@
28#include "entity_generated.h" 28#include "entity_generated.h"
29#include "mail/threadindexer.h" 29#include "mail/threadindexer.h"
30#include "domainadaptor.h" 30#include "domainadaptor.h"
31#include "typeimplementations_p.h"
31 32
32using namespace Sink; 33using namespace Sink;
33using namespace Sink::ApplicationDomain; 34using namespace Sink::ApplicationDomain;
34 35
36#define SINK_REGISTER_SERIALIZER(MAPPER, ENTITYTYPE, PROPERTY, LOWERCASEPROPERTY) \
37 MAPPER.addMapping<ENTITYTYPE::PROPERTY, Sink::ApplicationDomain::Buffer::ENTITYTYPE, Sink::ApplicationDomain::Buffer::ENTITYTYPE##Builder>(&Sink::ApplicationDomain::Buffer::ENTITYTYPE::LOWERCASEPROPERTY, &Sink::ApplicationDomain::Buffer::ENTITYTYPE##Builder::add_##LOWERCASEPROPERTY);
38
39typedef IndexConfig<Mail,
40 ValueIndex<Mail::Date>,
41 ValueIndex<Mail::Folder>,
42 ValueIndex<Mail::ParentMessageId>,
43 ValueIndex<Mail::MessageId>,
44 ValueIndex<Mail::Draft>,
45 SortedIndex<Mail::Folder, Mail::Date>,
46 SecondaryIndex<Mail::MessageId, Mail::ThreadId>,
47 SecondaryIndex<Mail::ThreadId, Mail::MessageId>,
48 CustomSecondaryIndex<Mail::MessageId, Mail::ThreadId, ThreadIndexer>
49 > MailIndexConfig;
50
51typedef IndexConfig<Folder,
52 ValueIndex<Folder::Name>,
53 ValueIndex<Folder::Parent>
54 > FolderIndexConfig;
55
56typedef IndexConfig<Contact,
57 ValueIndex<Contact::Uid>
58 > ContactIndexConfig;
59
60typedef IndexConfig<Addressbook,
61 ValueIndex<Addressbook::Parent>
62 > AddressbookIndexConfig;
63
64typedef IndexConfig<Event,
65 ValueIndex<Event::Uid>
66 > EventIndexConfig;
67
68
35void TypeImplementation<Mail>::configure(TypeIndex &index) 69void TypeImplementation<Mail>::configure(TypeIndex &index)
36{ 70{
37 // index.addProperty<Mail::Sender>(); 71 MailIndexConfig::configure(index);
38 /* index.addProperty<QByteArray>(Mail::SenderName::name); */ 72}
39 /* index->addProperty<QString>(Mail::Subject::name); */
40 /* index->addFulltextProperty<QString>(Mail::Subject::name); */
41 index.addProperty<Mail::Date>();
42 index.addProperty<Mail::Folder>();
43 index.addPropertyWithSorting<Mail::Folder, Mail::Date>();
44 index.addProperty<Mail::ParentMessageId>();
45 index.addProperty<Mail::MessageId>();
46 73
47 index.addSecondaryPropertyIndexer<Mail::MessageId, Mail::ThreadId, ThreadIndexer>(); 74QMap<QByteArray, int> TypeImplementation<Mail>::typeDatabases()
48 index.addSecondaryProperty<Mail::MessageId, Mail::ThreadId>(); 75{
49 index.addSecondaryProperty<Mail::ThreadId, Mail::MessageId>(); 76 return merge(QMap<QByteArray, int>{{QByteArray{Mail::name} + ".main", 0}}, MailIndexConfig::databases());
50} 77}
51 78
52void TypeImplementation<Mail>::configure(IndexPropertyMapper &indexPropertyMapper) 79void TypeImplementation<Mail>::configure(IndexPropertyMapper &indexPropertyMapper)
@@ -61,69 +88,44 @@ void TypeImplementation<Mail>::configure(IndexPropertyMapper &indexPropertyMappe
61 }); 88 });
62} 89}
63 90
64void TypeImplementation<Mail>::configure(ReadPropertyMapper<Buffer> &propertyMapper) 91void TypeImplementation<Mail>::configure(PropertyMapper &propertyMapper)
65{ 92{
66 propertyMapper.addMapping<Mail::Sender, Buffer>(&Buffer::sender); 93 SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Sender, sender);
67 propertyMapper.addMapping<Mail::To, Buffer>(&Buffer::to); 94 SINK_REGISTER_SERIALIZER(propertyMapper, Mail, To, to);
68 propertyMapper.addMapping<Mail::Cc, Buffer>(&Buffer::cc); 95 SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Cc, cc);
69 propertyMapper.addMapping<Mail::Bcc, Buffer>(&Buffer::bcc); 96 SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Bcc, bcc);
70 propertyMapper.addMapping<Mail::Subject, Buffer>(&Buffer::subject); 97 SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Subject, subject);
71 propertyMapper.addMapping<Mail::Date, Buffer>(&Buffer::date); 98 SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Date, date);
72 propertyMapper.addMapping<Mail::Unread, Buffer>(&Buffer::unread); 99 SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Unread, unread);
73 propertyMapper.addMapping<Mail::Important, Buffer>(&Buffer::important); 100 SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Important, important);
74 propertyMapper.addMapping<Mail::Folder, Buffer>(&Buffer::folder); 101 SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Folder, folder);
75 propertyMapper.addMapping<Mail::MimeMessage, Buffer>(&Buffer::mimeMessage); 102 SINK_REGISTER_SERIALIZER(propertyMapper, Mail, MimeMessage, mimeMessage);
76 propertyMapper.addMapping<Mail::FullPayloadAvailable, Buffer>(&Buffer::fullPayloadAvailable); 103 SINK_REGISTER_SERIALIZER(propertyMapper, Mail, FullPayloadAvailable, fullPayloadAvailable);
77 propertyMapper.addMapping<Mail::Draft, Buffer>(&Buffer::draft); 104 SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Draft, draft);
78 propertyMapper.addMapping<Mail::Trash, Buffer>(&Buffer::trash); 105 SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Trash, trash);
79 propertyMapper.addMapping<Mail::Sent, Buffer>(&Buffer::sent); 106 SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Sent, sent);
80 propertyMapper.addMapping<Mail::MessageId, Buffer>(&Buffer::messageId); 107 SINK_REGISTER_SERIALIZER(propertyMapper, Mail, MessageId, messageId);
81 propertyMapper.addMapping<Mail::ParentMessageId, Buffer>(&Buffer::parentMessageId); 108 SINK_REGISTER_SERIALIZER(propertyMapper, Mail, ParentMessageId, parentMessageId);
82}
83
84void TypeImplementation<Mail>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper)
85{
86 propertyMapper.addMapping<Mail::Sender>(&BufferBuilder::add_sender);
87 propertyMapper.addMapping<Mail::To>(&BufferBuilder::add_to);
88 propertyMapper.addMapping<Mail::Cc>(&BufferBuilder::add_cc);
89 propertyMapper.addMapping<Mail::Bcc>(&BufferBuilder::add_bcc);
90 propertyMapper.addMapping<Mail::Subject>(&BufferBuilder::add_subject);
91 propertyMapper.addMapping<Mail::Date>(&BufferBuilder::add_date);
92 propertyMapper.addMapping<Mail::Unread>(&BufferBuilder::add_unread);
93 propertyMapper.addMapping<Mail::Important>(&BufferBuilder::add_important);
94 propertyMapper.addMapping<Mail::Folder>(&BufferBuilder::add_folder);
95 propertyMapper.addMapping<Mail::MimeMessage>(&BufferBuilder::add_mimeMessage);
96 propertyMapper.addMapping<Mail::FullPayloadAvailable>(&BufferBuilder::add_fullPayloadAvailable);
97 propertyMapper.addMapping<Mail::Draft>(&BufferBuilder::add_draft);
98 propertyMapper.addMapping<Mail::Trash>(&BufferBuilder::add_trash);
99 propertyMapper.addMapping<Mail::Sent>(&BufferBuilder::add_sent);
100 propertyMapper.addMapping<Mail::MessageId>(&BufferBuilder::add_messageId);
101 propertyMapper.addMapping<Mail::ParentMessageId>(&BufferBuilder::add_parentMessageId);
102} 109}
103 110
104 111
105void TypeImplementation<Folder>::configure(TypeIndex &index) 112void TypeImplementation<Folder>::configure(TypeIndex &index)
106{ 113{
107 index.addProperty<QByteArray>(Folder::Parent::name); 114 FolderIndexConfig::configure(index);
108 index.addProperty<QString>(Folder::Name::name);
109} 115}
110 116
111void TypeImplementation<Folder>::configure(ReadPropertyMapper<Buffer> &propertyMapper) 117QMap<QByteArray, int> TypeImplementation<Folder>::typeDatabases()
112{ 118{
113 propertyMapper.addMapping<Folder::Parent, Buffer>(&Buffer::parent); 119 return merge(QMap<QByteArray, int>{{QByteArray{Folder::name} + ".main", 0}}, FolderIndexConfig::databases());
114 propertyMapper.addMapping<Folder::Name, Buffer>(&Buffer::name);
115 propertyMapper.addMapping<Folder::Icon, Buffer>(&Buffer::icon);
116 propertyMapper.addMapping<Folder::SpecialPurpose, Buffer>(&Buffer::specialpurpose);
117 propertyMapper.addMapping<Folder::Enabled, Buffer>(&Buffer::enabled);
118} 120}
119 121
120void TypeImplementation<Folder>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper) 122void TypeImplementation<Folder>::configure(PropertyMapper &propertyMapper)
121{ 123{
122 propertyMapper.addMapping<Folder::Parent>(&BufferBuilder::add_parent); 124 SINK_REGISTER_SERIALIZER(propertyMapper, Folder, Parent, parent);
123 propertyMapper.addMapping<Folder::Name>(&BufferBuilder::add_name); 125 SINK_REGISTER_SERIALIZER(propertyMapper, Folder, Name, name);
124 propertyMapper.addMapping<Folder::Icon>(&BufferBuilder::add_icon); 126 SINK_REGISTER_SERIALIZER(propertyMapper, Folder, Icon, icon);
125 propertyMapper.addMapping<Folder::SpecialPurpose>(&BufferBuilder::add_specialpurpose); 127 SINK_REGISTER_SERIALIZER(propertyMapper, Folder, SpecialPurpose, specialpurpose);
126 propertyMapper.addMapping<Folder::Enabled>(&BufferBuilder::add_enabled); 128 SINK_REGISTER_SERIALIZER(propertyMapper, Folder, Enabled, enabled);
127} 129}
128 130
129void TypeImplementation<Folder>::configure(IndexPropertyMapper &) 131void TypeImplementation<Folder>::configure(IndexPropertyMapper &)
@@ -134,29 +136,24 @@ void TypeImplementation<Folder>::configure(IndexPropertyMapper &)
134 136
135void TypeImplementation<Contact>::configure(TypeIndex &index) 137void TypeImplementation<Contact>::configure(TypeIndex &index)
136{ 138{
137 index.addProperty<QByteArray>(Contact::Uid::name); 139 ContactIndexConfig::configure(index);
138} 140}
139 141
140void TypeImplementation<Contact>::configure(ReadPropertyMapper<Buffer> &propertyMapper) 142QMap<QByteArray, int> TypeImplementation<Contact>::typeDatabases()
141{ 143{
142 propertyMapper.addMapping<Contact::Uid, Buffer>(&Buffer::uid); 144 return merge(QMap<QByteArray, int>{{QByteArray{Contact::name} + ".main", 0}}, ContactIndexConfig::databases());
143 propertyMapper.addMapping<Contact::Fn, Buffer>(&Buffer::fn);
144 propertyMapper.addMapping<Contact::Emails, Buffer>(&Buffer::emails);
145 propertyMapper.addMapping<Contact::Vcard, Buffer>(&Buffer::vcard);
146 propertyMapper.addMapping<Contact::Addressbook, Buffer>(&Buffer::addressbook);
147 propertyMapper.addMapping<Contact::Firstname, Buffer>(&Buffer::firstname);
148 propertyMapper.addMapping<Contact::Lastname, Buffer>(&Buffer::lastname);
149} 145}
150 146
151void TypeImplementation<Contact>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper) 147void TypeImplementation<Contact>::configure(PropertyMapper &propertyMapper)
152{ 148{
153 propertyMapper.addMapping<Contact::Uid>(&BufferBuilder::add_uid); 149 SINK_REGISTER_SERIALIZER(propertyMapper, Contact, Uid, uid);
154 propertyMapper.addMapping<Contact::Fn>(&BufferBuilder::add_fn); 150 SINK_REGISTER_SERIALIZER(propertyMapper, Contact, Fn, fn);
155 propertyMapper.addMapping<Contact::Emails>(&BufferBuilder::add_emails); 151 SINK_REGISTER_SERIALIZER(propertyMapper, Contact, Emails, emails);
156 propertyMapper.addMapping<Contact::Vcard>(&BufferBuilder::add_vcard); 152 SINK_REGISTER_SERIALIZER(propertyMapper, Contact, Vcard, vcard);
157 propertyMapper.addMapping<Contact::Addressbook>(&BufferBuilder::add_addressbook); 153 SINK_REGISTER_SERIALIZER(propertyMapper, Contact, Addressbook, addressbook);
158 propertyMapper.addMapping<Contact::Firstname>(&BufferBuilder::add_firstname); 154 SINK_REGISTER_SERIALIZER(propertyMapper, Contact, Firstname, firstname);
159 propertyMapper.addMapping<Contact::Lastname>(&BufferBuilder::add_lastname); 155 SINK_REGISTER_SERIALIZER(propertyMapper, Contact, Lastname, lastname);
156 SINK_REGISTER_SERIALIZER(propertyMapper, Contact, Photo, photo);
160} 157}
161 158
162void TypeImplementation<Contact>::configure(IndexPropertyMapper &) 159void TypeImplementation<Contact>::configure(IndexPropertyMapper &)
@@ -167,20 +164,18 @@ void TypeImplementation<Contact>::configure(IndexPropertyMapper &)
167 164
168void TypeImplementation<Addressbook>::configure(TypeIndex &index) 165void TypeImplementation<Addressbook>::configure(TypeIndex &index)
169{ 166{
170 index.addProperty<QByteArray>(Addressbook::Parent::name); 167 AddressbookIndexConfig::configure(index);
171 index.addProperty<QString>(Addressbook::Name::name);
172} 168}
173 169
174void TypeImplementation<Addressbook>::configure(ReadPropertyMapper<Buffer> &propertyMapper) 170QMap<QByteArray, int> TypeImplementation<Addressbook>::typeDatabases()
175{ 171{
176 propertyMapper.addMapping<Addressbook::Parent, Buffer>(&Buffer::parent); 172 return merge(QMap<QByteArray, int>{{QByteArray{Addressbook::name} + ".main", 0}}, AddressbookIndexConfig::databases());
177 propertyMapper.addMapping<Addressbook::Name, Buffer>(&Buffer::name);
178} 173}
179 174
180void TypeImplementation<Addressbook>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper) 175void TypeImplementation<Addressbook>::configure(PropertyMapper &propertyMapper)
181{ 176{
182 propertyMapper.addMapping<Addressbook::Parent>(&BufferBuilder::add_parent); 177 SINK_REGISTER_SERIALIZER(propertyMapper, Addressbook, Parent, parent);
183 propertyMapper.addMapping<Addressbook::Name>(&BufferBuilder::add_name); 178 SINK_REGISTER_SERIALIZER(propertyMapper, Addressbook, Name, name);
184} 179}
185 180
186void TypeImplementation<Addressbook>::configure(IndexPropertyMapper &) 181void TypeImplementation<Addressbook>::configure(IndexPropertyMapper &)
@@ -191,26 +186,24 @@ void TypeImplementation<Addressbook>::configure(IndexPropertyMapper &)
191 186
192void TypeImplementation<Event>::configure(TypeIndex &index) 187void TypeImplementation<Event>::configure(TypeIndex &index)
193{ 188{
194 index.addProperty<QByteArray>(Event::Uid::name); 189 EventIndexConfig::configure(index);
195} 190}
196 191
197void TypeImplementation<Event>::configure(ReadPropertyMapper<Buffer> &propertyMapper) 192QMap<QByteArray, int> TypeImplementation<Event>::typeDatabases()
198{ 193{
199 propertyMapper.addMapping<Event::Summary, Buffer>(&Buffer::summary); 194 return merge(QMap<QByteArray, int>{{QByteArray{Event::name} + ".main", 0}}, EventIndexConfig::databases());
200 propertyMapper.addMapping<Event::Description, Buffer>(&Buffer::description);
201 propertyMapper.addMapping<Event::Uid, Buffer>(&Buffer::uid);
202 propertyMapper.addMapping<Event::Attachment, Buffer>(&Buffer::attachment);
203} 195}
204 196
205void TypeImplementation<Event>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper) 197void TypeImplementation<Event>::configure(PropertyMapper &propertyMapper)
206{ 198{
207 propertyMapper.addMapping<Event::Summary>(&BufferBuilder::add_summary); 199 SINK_REGISTER_SERIALIZER(propertyMapper, Event, Summary, summary);
208 propertyMapper.addMapping<Event::Description>(&BufferBuilder::add_description); 200 SINK_REGISTER_SERIALIZER(propertyMapper, Event, Description, description);
209 propertyMapper.addMapping<Event::Uid>(&BufferBuilder::add_uid); 201 SINK_REGISTER_SERIALIZER(propertyMapper, Event, Uid, uid);
210 propertyMapper.addMapping<Event::Attachment>(&BufferBuilder::add_attachment); 202 SINK_REGISTER_SERIALIZER(propertyMapper, Event, Attachment, attachment);
211} 203}
212 204
213void TypeImplementation<Event>::configure(IndexPropertyMapper &) 205void TypeImplementation<Event>::configure(IndexPropertyMapper &)
214{ 206{
215 207
216} 208}
209
diff --git a/common/domain/typeimplementations.h b/common/domain/typeimplementations.h
index 37d6ca9..d36dfc1 100644
--- a/common/domain/typeimplementations.h
+++ b/common/domain/typeimplementations.h
@@ -26,10 +26,7 @@
26#include "contact_generated.h" 26#include "contact_generated.h"
27#include "addressbook_generated.h" 27#include "addressbook_generated.h"
28 28
29template<typename T> 29class PropertyMapper;
30class ReadPropertyMapper;
31template<typename T>
32class WritePropertyMapper;
33class IndexPropertyMapper; 30class IndexPropertyMapper;
34 31
35class TypeIndex; 32class TypeIndex;
@@ -48,9 +45,9 @@ public:
48 typedef Sink::ApplicationDomain::Buffer::Mail Buffer; 45 typedef Sink::ApplicationDomain::Buffer::Mail Buffer;
49 typedef Sink::ApplicationDomain::Buffer::MailBuilder BufferBuilder; 46 typedef Sink::ApplicationDomain::Buffer::MailBuilder BufferBuilder;
50 static void configure(TypeIndex &index); 47 static void configure(TypeIndex &index);
51 static void configure(ReadPropertyMapper<Buffer> &propertyMapper); 48 static void configure(PropertyMapper &propertyMapper);
52 static void configure(WritePropertyMapper<BufferBuilder> &propertyMapper);
53 static void configure(IndexPropertyMapper &indexPropertyMapper); 49 static void configure(IndexPropertyMapper &indexPropertyMapper);
50 static QMap<QByteArray, int> typeDatabases();
54}; 51};
55 52
56template<> 53template<>
@@ -59,9 +56,9 @@ public:
59 typedef Sink::ApplicationDomain::Buffer::Folder Buffer; 56 typedef Sink::ApplicationDomain::Buffer::Folder Buffer;
60 typedef Sink::ApplicationDomain::Buffer::FolderBuilder BufferBuilder; 57 typedef Sink::ApplicationDomain::Buffer::FolderBuilder BufferBuilder;
61 static void configure(TypeIndex &); 58 static void configure(TypeIndex &);
62 static void configure(ReadPropertyMapper<Buffer> &); 59 static void configure(PropertyMapper &);
63 static void configure(WritePropertyMapper<BufferBuilder> &);
64 static void configure(IndexPropertyMapper &indexPropertyMapper); 60 static void configure(IndexPropertyMapper &indexPropertyMapper);
61 static QMap<QByteArray, int> typeDatabases();
65}; 62};
66 63
67template<> 64template<>
@@ -70,9 +67,9 @@ public:
70 typedef Sink::ApplicationDomain::Buffer::Contact Buffer; 67 typedef Sink::ApplicationDomain::Buffer::Contact Buffer;
71 typedef Sink::ApplicationDomain::Buffer::ContactBuilder BufferBuilder; 68 typedef Sink::ApplicationDomain::Buffer::ContactBuilder BufferBuilder;
72 static void configure(TypeIndex &); 69 static void configure(TypeIndex &);
73 static void configure(ReadPropertyMapper<Buffer> &); 70 static void configure(PropertyMapper &);
74 static void configure(WritePropertyMapper<BufferBuilder> &);
75 static void configure(IndexPropertyMapper &indexPropertyMapper); 71 static void configure(IndexPropertyMapper &indexPropertyMapper);
72 static QMap<QByteArray, int> typeDatabases();
76}; 73};
77 74
78template<> 75template<>
@@ -81,9 +78,9 @@ public:
81 typedef Sink::ApplicationDomain::Buffer::Addressbook Buffer; 78 typedef Sink::ApplicationDomain::Buffer::Addressbook Buffer;
82 typedef Sink::ApplicationDomain::Buffer::AddressbookBuilder BufferBuilder; 79 typedef Sink::ApplicationDomain::Buffer::AddressbookBuilder BufferBuilder;
83 static void configure(TypeIndex &); 80 static void configure(TypeIndex &);
84 static void configure(ReadPropertyMapper<Buffer> &); 81 static void configure(PropertyMapper &);
85 static void configure(WritePropertyMapper<BufferBuilder> &);
86 static void configure(IndexPropertyMapper &indexPropertyMapper); 82 static void configure(IndexPropertyMapper &indexPropertyMapper);
83 static QMap<QByteArray, int> typeDatabases();
87}; 84};
88 85
89template<> 86template<>
@@ -92,9 +89,9 @@ public:
92 typedef Sink::ApplicationDomain::Buffer::Event Buffer; 89 typedef Sink::ApplicationDomain::Buffer::Event Buffer;
93 typedef Sink::ApplicationDomain::Buffer::EventBuilder BufferBuilder; 90 typedef Sink::ApplicationDomain::Buffer::EventBuilder BufferBuilder;
94 static void configure(TypeIndex &); 91 static void configure(TypeIndex &);
95 static void configure(ReadPropertyMapper<Buffer> &); 92 static void configure(PropertyMapper &);
96 static void configure(WritePropertyMapper<BufferBuilder> &);
97 static void configure(IndexPropertyMapper &indexPropertyMapper); 93 static void configure(IndexPropertyMapper &indexPropertyMapper);
94 static QMap<QByteArray, int> typeDatabases();
98}; 95};
99 96
100} 97}
diff --git a/common/domain/typeimplementations_p.h b/common/domain/typeimplementations_p.h
new file mode 100644
index 0000000..6f77a2d
--- /dev/null
+++ b/common/domain/typeimplementations_p.h
@@ -0,0 +1,154 @@
1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "typeindex.h"
21#include <QMap>
22
23template <typename T, typename First>
24void mergeImpl(T &map, First f)
25{
26 for (auto it = f.constBegin(); it != f.constEnd(); it++) {
27 map.insert(it.key(), it.value());
28 }
29}
30
31template <typename T, typename First, typename ... Tail>
32void mergeImpl(T &map, First f, Tail ...maps)
33{
34 for (auto it = f.constBegin(); it != f.constEnd(); it++) {
35 map.insert(it.key(), it.value());
36 }
37 mergeImpl<T, Tail...>(map, maps...);
38}
39
40template <typename First, typename ... Tail>
41First merge(First f, Tail ...maps)
42{
43 First map;
44 mergeImpl(map, f, maps...);
45 return map;
46}
47
48template <typename Property>
49class ValueIndex
50{
51public:
52 static void configure(TypeIndex &index)
53 {
54 index.addProperty<Property>();
55 }
56
57 template <typename EntityType>
58 static QMap<QByteArray, int> databases()
59 {
60 return {{QByteArray{EntityType::name} +".index." + Property::name, 1}};
61 }
62};
63
64
65template <typename Property, typename SortProperty>
66class SortedIndex
67{
68public:
69 static void configure(TypeIndex &index)
70 {
71 index.addPropertyWithSorting<Property, SortProperty>();
72 }
73
74 template <typename EntityType>
75 static QMap<QByteArray, int> databases()
76 {
77 return {{QByteArray{EntityType::name} +".index." + Property::name + ".sort." + SortProperty::name, 1}};
78 }
79};
80
81template <typename Property, typename SecondaryProperty>
82class SecondaryIndex
83{
84public:
85 static void configure(TypeIndex &index)
86 {
87 index.addSecondaryProperty<Property, SecondaryProperty>();
88 }
89
90 template <typename EntityType>
91 static QMap<QByteArray, int> databases()
92 {
93 return {{QByteArray{EntityType::name} +".index." + Property::name + SecondaryProperty::name, 1}};
94 }
95};
96
97template <typename Property, typename SecondaryProperty, typename Indexer>
98class CustomSecondaryIndex
99{
100public:
101 static void configure(TypeIndex &index)
102 {
103 index.addSecondaryPropertyIndexer<Property, SecondaryProperty, Indexer>();
104 }
105
106 template <typename EntityType>
107 static QMap<QByteArray, int> databases()
108 {
109 return Indexer::databases();
110 }
111};
112
113template <typename EntityType, typename ... Indexes>
114class IndexConfig
115{
116 template <typename T>
117 static void applyIndex(TypeIndex &index)
118 {
119 T::configure(index);
120 }
121
122 ///Apply recursively for parameter pack
123 template <typename First, typename Second, typename ... Tail>
124 static void applyIndex(TypeIndex &index)
125 {
126 applyIndex<First>(index);
127 applyIndex<Second, Tail...>(index);
128 }
129
130 template <typename T>
131 static QMap<QByteArray, int> getDbs()
132 {
133 return T::template databases<EntityType>();
134 }
135
136 template <typename First, typename Second, typename ... Tail>
137 static QMap<QByteArray, int> getDbs()
138 {
139 return merge(getDbs<First>(), getDbs<Second, Tail...>());
140 }
141
142public:
143 static void configure(TypeIndex &index)
144 {
145 applyIndex<Indexes...>(index);
146 }
147
148 static QMap<QByteArray, int> databases()
149 {
150 return getDbs<Indexes...>();
151 }
152
153};
154
diff --git a/common/domainadaptor.h b/common/domainadaptor.h
index af5d5fc..f981a1f 100644
--- a/common/domainadaptor.h
+++ b/common/domainadaptor.h
@@ -29,21 +29,19 @@
29#include "domain/typeimplementations.h" 29#include "domain/typeimplementations.h"
30#include "bufferadaptor.h" 30#include "bufferadaptor.h"
31#include "entity_generated.h" 31#include "entity_generated.h"
32#include "metadata_generated.h"
33#include "entitybuffer.h" 32#include "entitybuffer.h"
34#include "propertymapper.h" 33#include "propertymapper.h"
35#include "log.h" 34#include "log.h"
36#include "dummy_generated.h"
37 35
38/** 36/**
39 * Create a buffer from a domain object using the provided mappings 37 * Create a buffer from a domain object using the provided mappings
40 */ 38 */
41template <class Builder, class Buffer> 39template <class Builder, class Buffer>
42flatbuffers::Offset<Buffer> 40flatbuffers::Offset<Buffer>
43createBufferPart(const Sink::ApplicationDomain::ApplicationDomainType &domainObject, flatbuffers::FlatBufferBuilder &fbb, const WritePropertyMapper<Builder> &mapper) 41createBufferPart(const Sink::ApplicationDomain::ApplicationDomainType &domainObject, flatbuffers::FlatBufferBuilder &fbb, const PropertyMapper &mapper)
44{ 42{
45 // First create a primitives such as strings using the mappings 43 // First create a primitives such as strings using the mappings
46 QList<std::function<void(Builder &)>> propertiesToAddToResource; 44 QList<std::function<void(void *builder)>> propertiesToAddToResource;
47 for (const auto &property : domainObject.changedProperties()) { 45 for (const auto &property : domainObject.changedProperties()) {
48 // SinkTrace() << "copying property " << property; 46 // SinkTrace() << "copying property " << property;
49 const auto value = domainObject.getProperty(property); 47 const auto value = domainObject.getProperty(property);
@@ -57,7 +55,7 @@ createBufferPart(const Sink::ApplicationDomain::ApplicationDomainType &domainObj
57 // Then create all porperties using the above generated builderCalls 55 // Then create all porperties using the above generated builderCalls
58 Builder builder(fbb); 56 Builder builder(fbb);
59 for (auto propertyBuilder : propertiesToAddToResource) { 57 for (auto propertyBuilder : propertiesToAddToResource) {
60 propertyBuilder(builder); 58 propertyBuilder(&builder);
61 } 59 }
62 return builder.Finish(); 60 return builder.Finish();
63} 61}
@@ -68,7 +66,7 @@ createBufferPart(const Sink::ApplicationDomain::ApplicationDomainType &domainObj
68 * After this the buffer can be extracted from the FlatBufferBuilder object. 66 * After this the buffer can be extracted from the FlatBufferBuilder object.
69 */ 67 */
70template <typename Buffer, typename BufferBuilder> 68template <typename Buffer, typename BufferBuilder>
71static void createBufferPartBuffer(const Sink::ApplicationDomain::ApplicationDomainType &domainObject, flatbuffers::FlatBufferBuilder &fbb, WritePropertyMapper<BufferBuilder> &mapper) 69static void createBufferPartBuffer(const Sink::ApplicationDomain::ApplicationDomainType &domainObject, flatbuffers::FlatBufferBuilder &fbb, PropertyMapper &mapper)
72{ 70{
73 auto pos = createBufferPart<BufferBuilder, Buffer>(domainObject, fbb, mapper); 71 auto pos = createBufferPart<BufferBuilder, Buffer>(domainObject, fbb, mapper);
74 // Because we cannot template the following call 72 // Because we cannot template the following call
@@ -120,10 +118,8 @@ private:
120/** 118/**
121 * A generic adaptor implementation that uses a property mapper to read/write values. 119 * A generic adaptor implementation that uses a property mapper to read/write values.
122 */ 120 */
123template <class LocalBuffer, class ResourceBuffer>
124class DatastoreBufferAdaptor : public Sink::ApplicationDomain::BufferAdaptor 121class DatastoreBufferAdaptor : public Sink::ApplicationDomain::BufferAdaptor
125{ 122{
126 SINK_DEBUG_AREA("bufferadaptor")
127public: 123public:
128 DatastoreBufferAdaptor() : BufferAdaptor() 124 DatastoreBufferAdaptor() : BufferAdaptor()
129 { 125 {
@@ -137,9 +133,7 @@ public:
137 133
138 virtual QVariant getProperty(const QByteArray &key) const Q_DECL_OVERRIDE 134 virtual QVariant getProperty(const QByteArray &key) const Q_DECL_OVERRIDE
139 { 135 {
140 if (mResourceBuffer && mResourceMapper->hasMapping(key)) { 136 if (mLocalBuffer && mLocalMapper->hasMapping(key)) {
141 return mResourceMapper->getProperty(key, mResourceBuffer);
142 } else if (mLocalBuffer && mLocalMapper->hasMapping(key)) {
143 return mLocalMapper->getProperty(key, mLocalBuffer); 137 return mLocalMapper->getProperty(key, mLocalBuffer);
144 } else if (mIndex && mIndexMapper->hasMapping(key)) { 138 } else if (mIndex && mIndexMapper->hasMapping(key)) {
145 return mIndexMapper->getProperty(key, *mIndex, *this); 139 return mIndexMapper->getProperty(key, *mIndex, *this);
@@ -152,23 +146,21 @@ public:
152 */ 146 */
153 virtual QList<QByteArray> availableProperties() const Q_DECL_OVERRIDE 147 virtual QList<QByteArray> availableProperties() const Q_DECL_OVERRIDE
154 { 148 {
155 return mResourceMapper->availableProperties() + mLocalMapper->availableProperties() + mIndexMapper->availableProperties(); 149 return mLocalMapper->availableProperties() + mIndexMapper->availableProperties();
156 } 150 }
157 151
158 LocalBuffer const *mLocalBuffer; 152 void const *mLocalBuffer;
159 ResourceBuffer const *mResourceBuffer; 153 QSharedPointer<PropertyMapper> mLocalMapper;
160 QSharedPointer<ReadPropertyMapper<LocalBuffer>> mLocalMapper;
161 QSharedPointer<ReadPropertyMapper<ResourceBuffer>> mResourceMapper;
162 QSharedPointer<IndexPropertyMapper> mIndexMapper; 154 QSharedPointer<IndexPropertyMapper> mIndexMapper;
163 TypeIndex *mIndex; 155 TypeIndex *mIndex;
164}; 156};
165 157
166/** 158/**
167 * The factory should define how to go from an entitybuffer (local + resource buffer), to a domain type adapter. 159 * The factory should define how to go from an entitybuffer (local buffer), to a domain type adapter.
168 * It defines how values are split accross local and resource buffer. 160 * It defines how values are split accross local and resource buffer.
169 * This is required by the facade the read the value, and by the pipeline preprocessors to access the domain values in a generic way. 161 * This is required by the facade the read the value, and by the pipeline preprocessors to access the domain values in a generic way.
170 */ 162 */
171template <typename DomainType, typename ResourceBuffer = Sink::ApplicationDomain::Buffer::Dummy, typename ResourceBuilder = Sink::ApplicationDomain::Buffer::DummyBuilder> 163template <typename DomainType>
172class SINK_EXPORT DomainTypeAdaptorFactory : public DomainTypeAdaptorFactoryInterface 164class SINK_EXPORT DomainTypeAdaptorFactory : public DomainTypeAdaptorFactoryInterface
173{ 165{
174 typedef typename Sink::ApplicationDomain::TypeImplementation<DomainType>::Buffer LocalBuffer; 166 typedef typename Sink::ApplicationDomain::TypeImplementation<DomainType>::Buffer LocalBuffer;
@@ -176,31 +168,25 @@ class SINK_EXPORT DomainTypeAdaptorFactory : public DomainTypeAdaptorFactoryInte
176 168
177public: 169public:
178 DomainTypeAdaptorFactory() 170 DomainTypeAdaptorFactory()
179 : mLocalMapper(QSharedPointer<ReadPropertyMapper<LocalBuffer>>::create()), 171 : mPropertyMapper(QSharedPointer<PropertyMapper>::create()),
180 mResourceMapper(QSharedPointer<ReadPropertyMapper<ResourceBuffer>>::create()),
181 mLocalWriteMapper(QSharedPointer<WritePropertyMapper<LocalBuilder>>::create()),
182 mResourceWriteMapper(QSharedPointer<WritePropertyMapper<ResourceBuilder>>::create()),
183 mIndexMapper(QSharedPointer<IndexPropertyMapper>::create()) 172 mIndexMapper(QSharedPointer<IndexPropertyMapper>::create())
184 { 173 {
185 Sink::ApplicationDomain::TypeImplementation<DomainType>::configure(*mLocalMapper); 174 Sink::ApplicationDomain::TypeImplementation<DomainType>::configure(*mPropertyMapper);
186 Sink::ApplicationDomain::TypeImplementation<DomainType>::configure(*mLocalWriteMapper);
187 Sink::ApplicationDomain::TypeImplementation<DomainType>::configure(*mIndexMapper); 175 Sink::ApplicationDomain::TypeImplementation<DomainType>::configure(*mIndexMapper);
188 } 176 }
189 177
190 virtual ~DomainTypeAdaptorFactory(){}; 178 virtual ~DomainTypeAdaptorFactory(){};
191 179
192 /** 180 /**
193 * Creates an adaptor for the given domain and resource types. 181 * Creates an adaptor for the given domain types.
194 * 182 *
195 * This returns by default a DatastoreBufferAdaptor initialized with the corresponding property mappers. 183 * This returns by default a DatastoreBufferAdaptor initialized with the corresponding property mappers.
196 */ 184 */
197 virtual QSharedPointer<Sink::ApplicationDomain::BufferAdaptor> createAdaptor(const Sink::Entity &entity, TypeIndex *index = nullptr) Q_DECL_OVERRIDE 185 virtual QSharedPointer<Sink::ApplicationDomain::BufferAdaptor> createAdaptor(const Sink::Entity &entity, TypeIndex *index = nullptr) Q_DECL_OVERRIDE
198 { 186 {
199 auto adaptor = QSharedPointer<DatastoreBufferAdaptor<LocalBuffer, ResourceBuffer>>::create(); 187 auto adaptor = QSharedPointer<DatastoreBufferAdaptor>::create();
200 adaptor->mLocalBuffer = Sink::EntityBuffer::readBuffer<LocalBuffer>(entity.local()); 188 adaptor->mLocalBuffer = Sink::EntityBuffer::readBuffer<LocalBuffer>(entity.local());
201 adaptor->mLocalMapper = mLocalMapper; 189 adaptor->mLocalMapper = mPropertyMapper;
202 adaptor->mResourceBuffer = Sink::EntityBuffer::readBuffer<ResourceBuffer>(entity.resource());
203 adaptor->mResourceMapper = mResourceMapper;
204 adaptor->mIndexMapper = mIndexMapper; 190 adaptor->mIndexMapper = mIndexMapper;
205 adaptor->mIndex = index; 191 adaptor->mIndex = index;
206 return adaptor; 192 return adaptor;
@@ -210,18 +196,8 @@ public:
210 createBuffer(const Sink::ApplicationDomain::ApplicationDomainType &domainObject, flatbuffers::FlatBufferBuilder &fbb, void const *metadataData = 0, size_t metadataSize = 0) Q_DECL_OVERRIDE 196 createBuffer(const Sink::ApplicationDomain::ApplicationDomainType &domainObject, flatbuffers::FlatBufferBuilder &fbb, void const *metadataData = 0, size_t metadataSize = 0) Q_DECL_OVERRIDE
211 { 197 {
212 flatbuffers::FlatBufferBuilder localFbb; 198 flatbuffers::FlatBufferBuilder localFbb;
213 if (mLocalWriteMapper) { 199 createBufferPartBuffer<LocalBuffer, LocalBuilder>(domainObject, localFbb, *mPropertyMapper);
214 // SinkTrace() << "Creating local buffer part"; 200 Sink::EntityBuffer::assembleEntityBuffer(fbb, metadataData, metadataSize, 0, 0, localFbb.GetBufferPointer(), localFbb.GetSize());
215 createBufferPartBuffer<LocalBuffer, LocalBuilder>(domainObject, localFbb, *mLocalWriteMapper);
216 }
217
218 flatbuffers::FlatBufferBuilder resFbb;
219 if (mResourceWriteMapper) {
220 // SinkTrace() << "Creating resouce buffer part";
221 createBufferPartBuffer<ResourceBuffer, ResourceBuilder>(domainObject, resFbb, *mResourceWriteMapper);
222 }
223
224 Sink::EntityBuffer::assembleEntityBuffer(fbb, metadataData, metadataSize, resFbb.GetBufferPointer(), resFbb.GetSize(), localFbb.GetBufferPointer(), localFbb.GetSize());
225 return true; 201 return true;
226 } 202 }
227 203
@@ -236,10 +212,7 @@ public:
236 212
237 213
238protected: 214protected:
239 QSharedPointer<ReadPropertyMapper<LocalBuffer>> mLocalMapper; 215 QSharedPointer<PropertyMapper> mPropertyMapper;
240 QSharedPointer<ReadPropertyMapper<ResourceBuffer>> mResourceMapper;
241 QSharedPointer<WritePropertyMapper<LocalBuilder>> mLocalWriteMapper;
242 QSharedPointer<WritePropertyMapper<ResourceBuilder>> mResourceWriteMapper;
243 QSharedPointer<IndexPropertyMapper> mIndexMapper; 216 QSharedPointer<IndexPropertyMapper> mIndexMapper;
244}; 217};
245 218
diff --git a/common/facade.cpp b/common/facade.cpp
index 38ec1dc..9c14a23 100644
--- a/common/facade.cpp
+++ b/common/facade.cpp
@@ -104,6 +104,7 @@ KAsync::Job<void> GenericFacade<DomainType>::remove(const DomainType &domainObje
104template <class DomainType> 104template <class DomainType>
105QPair<KAsync::Job<void>, typename ResultEmitter<typename DomainType::Ptr>::Ptr> GenericFacade<DomainType>::load(const Sink::Query &query, const Log::Context &ctx) 105QPair<KAsync::Job<void>, typename ResultEmitter<typename DomainType::Ptr>::Ptr> GenericFacade<DomainType>::load(const Sink::Query &query, const Log::Context &ctx)
106{ 106{
107 Q_ASSERT(DomainType::name == query.type() || query.type().isEmpty());
107 // The runner lives for the lifetime of the query 108 // The runner lives for the lifetime of the query
108 auto runner = new QueryRunner<DomainType>(query, mResourceContext, bufferTypeForDomainType(), ctx); 109 auto runner = new QueryRunner<DomainType>(query, mResourceContext, bufferTypeForDomainType(), ctx);
109 runner->setResultTransformation(mResultTransformation); 110 runner->setResultTransformation(mResultTransformation);
diff --git a/common/facade.h b/common/facade.h
index 45c718b..0ec2e59 100644
--- a/common/facade.h
+++ b/common/facade.h
@@ -48,7 +48,6 @@ template <typename DomainType>
48class SINK_EXPORT GenericFacade : public Sink::StoreFacade<DomainType> 48class SINK_EXPORT GenericFacade : public Sink::StoreFacade<DomainType>
49{ 49{
50protected: 50protected:
51 SINK_DEBUG_AREA("facade")
52 SINK_DEBUG_COMPONENT(mResourceContext.resourceInstanceIdentifier) 51 SINK_DEBUG_COMPONENT(mResourceContext.resourceInstanceIdentifier)
53public: 52public:
54 /** 53 /**
diff --git a/common/genericresource.h b/common/genericresource.h
index 558145c..bffc697 100644
--- a/common/genericresource.h
+++ b/common/genericresource.h
@@ -35,8 +35,6 @@ class CommandProcessor;
35 */ 35 */
36class SINK_EXPORT GenericResource : public Resource 36class SINK_EXPORT GenericResource : public Resource
37{ 37{
38protected:
39 SINK_DEBUG_AREA("resource")
40public: 38public:
41 GenericResource(const Sink::ResourceContext &context, const QSharedPointer<Pipeline> &pipeline = QSharedPointer<Pipeline>()); 39 GenericResource(const Sink::ResourceContext &context, const QSharedPointer<Pipeline> &pipeline = QSharedPointer<Pipeline>());
42 virtual ~GenericResource(); 40 virtual ~GenericResource();
diff --git a/common/listener.cpp b/common/listener.cpp
index 983e438..c9fd9d3 100644
--- a/common/listener.cpp
+++ b/common/listener.cpp
@@ -405,6 +405,7 @@ void Listener::updateClientsWithRevision(qint64 revision)
405 405
406 SinkTrace() << "Sending revision update for " << client.name << revision; 406 SinkTrace() << "Sending revision update for " << client.name << revision;
407 Sink::Commands::write(client.socket, ++m_messageId, Sink::Commands::RevisionUpdateCommand, m_fbb); 407 Sink::Commands::write(client.socket, ++m_messageId, Sink::Commands::RevisionUpdateCommand, m_fbb);
408 client.socket->flush();
408 } 409 }
409 m_fbb.Clear(); 410 m_fbb.Clear();
410} 411}
@@ -420,6 +421,8 @@ void Listener::notify(const Sink::Notification &notification)
420 builder.add_identifier(idString); 421 builder.add_identifier(idString);
421 builder.add_message(messageString); 422 builder.add_message(messageString);
422 builder.add_entities(entities); 423 builder.add_entities(entities);
424 builder.add_progress(notification.progress);
425 builder.add_total(notification.total);
423 auto command = builder.Finish(); 426 auto command = builder.Finish();
424 Sink::Commands::FinishNotificationBuffer(m_fbb, command); 427 Sink::Commands::FinishNotificationBuffer(m_fbb, command);
425 for (Client &client : m_connections) { 428 for (Client &client : m_connections) {
diff --git a/common/listener.h b/common/listener.h
index 8d8b529..f29130d 100644
--- a/common/listener.h
+++ b/common/listener.h
@@ -55,8 +55,6 @@ public:
55class SINK_EXPORT Listener : public QObject 55class SINK_EXPORT Listener : public QObject
56{ 56{
57 Q_OBJECT 57 Q_OBJECT
58 SINK_DEBUG_AREA("communication")
59
60public: 58public:
61 Listener(const QByteArray &resourceName, const QByteArray &resourceType, QObject *parent = 0); 59 Listener(const QByteArray &resourceName, const QByteArray &resourceType, QObject *parent = 0);
62 ~Listener(); 60 ~Listener();
diff --git a/common/log.cpp b/common/log.cpp
index 045b3cc..5dfb872 100644
--- a/common/log.cpp
+++ b/common/log.cpp
@@ -1,6 +1,7 @@
1#include "log.h" 1#include "log.h"
2 2
3#include <QString> 3#include <QString>
4#include <QDir>
4#include <QIODevice> 5#include <QIODevice>
5#include <QCoreApplication> 6#include <QCoreApplication>
6#include <QSettings> 7#include <QSettings>
@@ -12,12 +13,18 @@
12#include <memory> 13#include <memory>
13#include <atomic> 14#include <atomic>
14#include <definitions.h> 15#include <definitions.h>
16#include <QThreadStorage>
17#include <QStringBuilder>
15 18
16using namespace Sink::Log; 19using namespace Sink::Log;
17 20
18static QSharedPointer<QSettings> config() 21static QThreadStorage<QSharedPointer<QSettings>> sSettings;
22static QSettings &config()
19{ 23{
20 return QSharedPointer<QSettings>::create(Sink::configLocation() + "/log.ini", QSettings::IniFormat); 24 if (!sSettings.hasLocalData()) {
25 sSettings.setLocalData(QSharedPointer<QSettings>::create(Sink::configLocation() + "/log.ini", QSettings::IniFormat));
26 }
27 return *sSettings.localData();
21} 28}
22 29
23static QByteArray sPrimaryComponent; 30static QByteArray sPrimaryComponent;
@@ -173,22 +180,22 @@ DebugLevel Sink::Log::debugLevelFromName(const QByteArray &name)
173 180
174void Sink::Log::setDebugOutputLevel(DebugLevel debugLevel) 181void Sink::Log::setDebugOutputLevel(DebugLevel debugLevel)
175{ 182{
176 config()->setValue("level", debugLevel); 183 config().setValue("level", debugLevel);
177} 184}
178 185
179Sink::Log::DebugLevel Sink::Log::debugOutputLevel() 186Sink::Log::DebugLevel Sink::Log::debugOutputLevel()
180{ 187{
181 return static_cast<Sink::Log::DebugLevel>(config()->value("level", Sink::Log::Log).toInt()); 188 return static_cast<Sink::Log::DebugLevel>(config().value("level", Sink::Log::Log).toInt());
182} 189}
183 190
184void Sink::Log::setDebugOutputFilter(FilterType type, const QByteArrayList &filter) 191void Sink::Log::setDebugOutputFilter(FilterType type, const QByteArrayList &filter)
185{ 192{
186 switch (type) { 193 switch (type) {
187 case ApplicationName: 194 case ApplicationName:
188 config()->setValue("applicationfilter", QVariant::fromValue(filter)); 195 config().setValue("applicationfilter", QVariant::fromValue(filter));
189 break; 196 break;
190 case Area: 197 case Area:
191 config()->setValue("areafilter", QVariant::fromValue(filter)); 198 config().setValue("areafilter", QVariant::fromValue(filter));
192 break; 199 break;
193 } 200 }
194} 201}
@@ -197,9 +204,9 @@ QByteArrayList Sink::Log::debugOutputFilter(FilterType type)
197{ 204{
198 switch (type) { 205 switch (type) {
199 case ApplicationName: 206 case ApplicationName:
200 return config()->value("applicationfilter").value<QByteArrayList>(); 207 return config().value("applicationfilter").value<QByteArrayList>();
201 case Area: 208 case Area:
202 return config()->value("areafilter").value<QByteArrayList>(); 209 return config().value("areafilter").value<QByteArrayList>();
203 default: 210 default:
204 return QByteArrayList(); 211 return QByteArrayList();
205 } 212 }
@@ -207,12 +214,12 @@ QByteArrayList Sink::Log::debugOutputFilter(FilterType type)
207 214
208void Sink::Log::setDebugOutputFields(const QByteArrayList &output) 215void Sink::Log::setDebugOutputFields(const QByteArrayList &output)
209{ 216{
210 config()->setValue("outputfields", QVariant::fromValue(output)); 217 config().setValue("outputfields", QVariant::fromValue(output));
211} 218}
212 219
213QByteArrayList Sink::Log::debugOutputFields() 220QByteArrayList Sink::Log::debugOutputFields()
214{ 221{
215 return config()->value("outputfields").value<QByteArrayList>(); 222 return config().value("outputfields").value<QByteArrayList>();
216} 223}
217 224
218static QByteArray getProgramName() 225static QByteArray getProgramName()
@@ -277,14 +284,16 @@ static bool containsItemStartingWith(const QByteArray &pattern, const QByteArray
277 for (const auto &item : list) { 284 for (const auto &item : list) {
278 if (item.startsWith('*')) { 285 if (item.startsWith('*')) {
279 auto stripped = item.mid(1); 286 auto stripped = item.mid(1);
280 stripped.endsWith('*'); 287 if (stripped.endsWith('*')) {
281 stripped.chop(1); 288 stripped.chop(1);
289 }
282 if (pattern.contains(stripped)) { 290 if (pattern.contains(stripped)) {
283 return true; 291 return true;
284 } 292 }
285 } 293 } else {
286 if (pattern.startsWith(item)) { 294 if (pattern.contains(item)) {
287 return true; 295 return true;
296 }
288 } 297 }
289 } 298 }
290 return false; 299 return false;
@@ -300,25 +309,51 @@ static bool caseInsensitiveContains(const QByteArray &pattern, const QByteArrayL
300 return false; 309 return false;
301} 310}
302 311
303QDebug Sink::Log::debugStream(DebugLevel debugLevel, int line, const char *file, const char *function, const char *debugArea, const char *debugComponent) 312static QByteArray getFileName(const char *file)
304{ 313{
305 static NullStream nullstream; 314 static char sep = QDir::separator().toLatin1();
306 if (debugLevel < debugOutputLevel()) { 315 auto filename = QByteArray(file).split(sep).last();
307 return QDebug(&nullstream); 316 return filename.split('.').first();
308 } 317}
309 318
319static QString assembleDebugArea(const char *debugArea, const char *debugComponent, const char *file)
320{
310 if (sPrimaryComponent.isEmpty()) { 321 if (sPrimaryComponent.isEmpty()) {
311 sPrimaryComponent = getProgramName(); 322 sPrimaryComponent = getProgramName();
312 } 323 }
313 QString fullDebugArea = sPrimaryComponent + "." + (debugComponent ? (QString::fromLatin1(debugComponent) + ".") : "") + (debugArea ? QString::fromLatin1(debugArea) : ""); 324 //Using stringbuilder for fewer allocations
325 return QLatin1String{sPrimaryComponent} % QLatin1String{"."} %
326 (debugComponent ? (QLatin1String{debugComponent} + QLatin1String{"."}) : QLatin1String{""}) %
327 (debugArea ? QLatin1String{debugArea} : QLatin1String{getFileName(file)});
328}
329
330static bool isFiltered(DebugLevel debugLevel, const QByteArray &fullDebugArea)
331{
332 if (debugLevel < debugOutputLevel()) {
333 return true;
334 }
335 const auto areas = debugOutputFilter(Sink::Log::Area);
336 if ((debugLevel <= Sink::Log::Trace) && !areas.isEmpty()) {
337 if (!containsItemStartingWith(fullDebugArea, areas)) {
338 return true;
339 }
340 }
341 return false;
342}
314 343
344bool Sink::Log::isFiltered(DebugLevel debugLevel, const char *debugArea, const char *debugComponent, const char *file)
345{
346 return isFiltered(debugLevel, assembleDebugArea(debugArea, debugComponent, file).toLatin1());
347}
348
349QDebug Sink::Log::debugStream(DebugLevel debugLevel, int line, const char *file, const char *function, const char *debugArea, const char *debugComponent)
350{
351 const auto fullDebugArea = assembleDebugArea(debugArea, debugComponent, file);
315 collectDebugArea(fullDebugArea); 352 collectDebugArea(fullDebugArea);
316 353
317 auto areas = debugOutputFilter(Sink::Log::Area); 354 static NullStream nullstream;
318 if (debugLevel <= Sink::Log::Trace && !areas.isEmpty()) { 355 if (isFiltered(debugLevel, fullDebugArea.toLatin1())) {
319 if (!containsItemStartingWith(fullDebugArea.toUtf8(), areas)) { 356 return QDebug(&nullstream);
320 return QDebug(&nullstream);
321 }
322 } 357 }
323 358
324 QString prefix; 359 QString prefix;
diff --git a/common/log.h b/common/log.h
index e85856a..8266fdb 100644
--- a/common/log.h
+++ b/common/log.h
@@ -85,30 +85,35 @@ SINK_EXPORT inline QDebug operator<<(QDebug d, const TraceTime &time)
85 d << time.time << "[ms]"; 85 d << time.time << "[ms]";
86 return d; 86 return d;
87} 87}
88
89SINK_EXPORT bool isFiltered(DebugLevel debugLevel, const char *debugArea, const char *debugComponent, const char *file);
90
88} 91}
89} 92}
90 93
91static const char *getComponentName() { return nullptr; } 94static const char *getComponentName() { return nullptr; }
92 95
93#define Trace_area(AREA) Sink::Log::debugStream(Sink::Log::DebugLevel::Trace, __LINE__, __FILE__, Q_FUNC_INFO, AREA) 96#define SINK_DEBUG_STREAM_IMPL(LEVEL, AREA, COMPONENT) if (!Sink::Log::isFiltered(LEVEL, AREA, COMPONENT, __FILE__)) Sink::Log::debugStream(LEVEL, __LINE__, __FILE__, Q_FUNC_INFO, AREA, COMPONENT)
94#define Log_area(AREA) Sink::Log::debugStream(Sink::Log::DebugLevel::Log, __LINE__, __FILE__, Q_FUNC_INFO, AREA) 97
95#define Warning_area(AREA) Sink::Log::debugStream(Sink::Log::DebugLevel::Warning, __LINE__, __FILE__, Q_FUNC_INFO, AREA) 98#define Trace_area(AREA) SINK_DEBUG_STREAM_IMPL(Sink::Log::DebugLevel::Trace, AREA, nullptr)
96#define Error_area(AREA) Sink::Log::debugStream(Sink::Log::DebugLevel::Error, __LINE__, __FILE__, Q_FUNC_INFO, AREA) 99#define Log_area(AREA) SINK_DEBUG_STREAM_IMPL(Sink::Log::DebugLevel::Log, AREA, nullptr)
97 100#define Warning_area(AREA) SINK_DEBUG_STREAM_IMPL(Sink::Log::DebugLevel::Warning, AREA, nullptr)
98#define SinkTrace_(COMPONENT, AREA) Sink::Log::debugStream(Sink::Log::DebugLevel::Trace, __LINE__, __FILE__, Q_FUNC_INFO, AREA, COMPONENT) 101#define Error_area(AREA) SINK_DEBUG_STREAM_IMPL(Sink::Log::DebugLevel::Error, AREA, nullptr)
99#define SinkLog_(COMPONENT, AREA) Sink::Log::debugStream(Sink::Log::DebugLevel::Log, __LINE__, __FILE__, Q_FUNC_INFO, AREA, COMPONENT) 102
100#define SinkWarning_(COMPONENT, AREA) Sink::Log::debugStream(Sink::Log::DebugLevel::Warning, __LINE__, __FILE__, Q_FUNC_INFO, AREA, COMPONENT) 103#define SinkTrace_(COMPONENT, AREA) SINK_DEBUG_STREAM_IMPL(Sink::Log::DebugLevel::Trace, AREA, COMPONENT)
101#define SinkError_(COMPONENT, AREA) Sink::Log::debugStream(Sink::Log::DebugLevel::Error, __LINE__, __FILE__, Q_FUNC_INFO, AREA, COMPONENT) 104#define SinkLog_(COMPONENT, AREA) SINK_DEBUG_STREAM_IMPL(Sink::Log::DebugLevel::Log, AREA, COMPONENT)
102 105#define SinkWarning_(COMPONENT, AREA) SINK_DEBUG_STREAM_IMPL(Sink::Log::DebugLevel::Warning, AREA, COMPONENT)
103#define SinkTrace() Sink::Log::debugStream(Sink::Log::DebugLevel::Trace, __LINE__, __FILE__, Q_FUNC_INFO, s_sinkDebugArea, getComponentName()) 106#define SinkError_(COMPONENT, AREA) SINK_DEBUG_STREAM_IMPL(Sink::Log::DebugLevel::Error, AREA, COMPONENT)
104#define SinkLog() Sink::Log::debugStream(Sink::Log::DebugLevel::Log, __LINE__, __FILE__, Q_FUNC_INFO, s_sinkDebugArea, getComponentName()) 107
105#define SinkWarning() Sink::Log::debugStream(Sink::Log::DebugLevel::Warning, __LINE__, __FILE__, Q_FUNC_INFO, s_sinkDebugArea, getComponentName()) 108#define SinkTrace() SINK_DEBUG_STREAM_IMPL(Sink::Log::DebugLevel::Trace, nullptr, getComponentName())
106#define SinkError() Sink::Log::debugStream(Sink::Log::DebugLevel::Error, __LINE__, __FILE__, Q_FUNC_INFO, s_sinkDebugArea, getComponentName()) 109#define SinkLog() SINK_DEBUG_STREAM_IMPL(Sink::Log::DebugLevel::Log, nullptr, getComponentName())
107 110#define SinkWarning() SINK_DEBUG_STREAM_IMPL(Sink::Log::DebugLevel::Warning, nullptr, getComponentName())
108#define SinkTraceCtx(CTX) Sink::Log::debugStream(Sink::Log::DebugLevel::Trace, __LINE__, __FILE__, Q_FUNC_INFO, CTX.name) 111#define SinkError() SINK_DEBUG_STREAM_IMPL(Sink::Log::DebugLevel::Error, nullptr, getComponentName())
109#define SinkLogCtx(CTX) Sink::Log::debugStream(Sink::Log::DebugLevel::Log, __LINE__, __FILE__, Q_FUNC_INFO, CTX.name) 112
110#define SinkWarningCtx(CTX) Sink::Log::debugStream(Sink::Log::DebugLevel::Warning, __LINE__, __FILE__, Q_FUNC_INFO, CTX.name) 113#define SinkTraceCtx(CTX) SINK_DEBUG_STREAM_IMPL(Sink::Log::DebugLevel::Trace, CTX.name, nullptr)
111#define SinkErrorCtx(CTX) Sink::Log::debugStream(Sink::Log::DebugLevel::Error, __LINE__, __FILE__, Q_FUNC_INFO, CTX.name) 114#define SinkLogCtx(CTX) SINK_DEBUG_STREAM_IMPL(Sink::Log::DebugLevel::Log, CTX.name, nullptr)
115#define SinkWarningCtx(CTX) SINK_DEBUG_STREAM_IMPL(Sink::Log::DebugLevel::Warning, CTX.name, nullptr)
116#define SinkErrorCtx(CTX) SINK_DEBUG_STREAM_IMPL(Sink::Log::DebugLevel::Error, CTX.name, nullptr)
112 117
113#define SINK_DEBUG_AREA(AREA) static constexpr const char* s_sinkDebugArea{AREA}; 118#define SINK_DEBUG_AREA(AREA) static constexpr const char* s_sinkDebugArea{AREA};
114#define SINK_DEBUG_COMPONENT(COMPONENT) const char* getComponentName() const { return COMPONENT; }; 119#define SINK_DEBUG_COMPONENT(COMPONENT) const char* getComponentName() const { return COMPONENT; };
diff --git a/common/mail/threadindexer.cpp b/common/mail/threadindexer.cpp
index d91ab5f..4171d85 100644
--- a/common/mail/threadindexer.cpp
+++ b/common/mail/threadindexer.cpp
@@ -21,74 +21,15 @@
21#include "typeindex.h" 21#include "typeindex.h"
22#include "log.h" 22#include "log.h"
23 23
24SINK_DEBUG_AREA("threadindex")
25
26using namespace Sink; 24using namespace Sink;
27using namespace Sink::ApplicationDomain; 25using namespace Sink::ApplicationDomain;
28 26
29static QString stripOffPrefixes(const QString &subject)
30{
31 //TODO this hardcoded list is probably not good enough (especially regarding internationalization)
32 //TODO this whole routine, including internationalized re/fwd ... should go into some library.
33 //We'll require the same for generating reply/forward subjects in kube
34 static QStringList defaultReplyPrefixes = QStringList() << QLatin1String("Re\\s*:")
35 << QLatin1String("Re\\[\\d+\\]:")
36 << QLatin1String("Re\\d+:");
37
38 static QStringList defaultForwardPrefixes = QStringList() << QLatin1String("Fwd:")
39 << QLatin1String("FW:");
40
41 QStringList replyPrefixes; // = GlobalSettings::self()->replyPrefixes();
42 if (replyPrefixes.isEmpty()) {
43 replyPrefixes = defaultReplyPrefixes;
44 }
45
46 QStringList forwardPrefixes; // = GlobalSettings::self()->forwardPrefixes();
47 if (forwardPrefixes.isEmpty()) {
48 forwardPrefixes = defaultReplyPrefixes;
49 }
50
51 const QStringList prefixRegExps = replyPrefixes + forwardPrefixes;
52
53 // construct a big regexp that
54 // 1. is anchored to the beginning of str (sans whitespace)
55 // 2. matches at least one of the part regexps in prefixRegExps
56 const QString bigRegExp = QString::fromLatin1("^(?:\\s+|(?:%1))+\\s*").arg(prefixRegExps.join(QLatin1String(")|(?:")));
57
58 static QString regExpPattern;
59 static QRegExp regExp;
60
61 regExp.setCaseSensitivity(Qt::CaseInsensitive);
62 if (regExpPattern != bigRegExp) {
63 // the prefixes have changed, so update the regexp
64 regExpPattern = bigRegExp;
65 regExp.setPattern(regExpPattern);
66 }
67
68 if(regExp.isValid()) {
69 QString tmp = subject;
70 if (regExp.indexIn( tmp ) == 0) {
71 return tmp.remove(0, regExp.matchedLength());
72 }
73 } else {
74 SinkWarning() << "bigRegExp = \""
75 << bigRegExp << "\"\n"
76 << "prefix regexp is invalid!";
77 }
78
79 return subject;
80}
81
82
83void ThreadIndexer::updateThreadingIndex(const QByteArray &identifier, const ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction) 27void ThreadIndexer::updateThreadingIndex(const QByteArray &identifier, const ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction)
84{ 28{
85 auto messageId = entity.getProperty(Mail::MessageId::name); 29 auto messageId = entity.getProperty(Mail::MessageId::name);
86 auto parentMessageId = entity.getProperty(Mail::ParentMessageId::name); 30 auto parentMessageId = entity.getProperty(Mail::ParentMessageId::name);
87 const auto subject = entity.getProperty(Mail::Subject::name);
88 const auto normalizedSubject = stripOffPrefixes(subject.toString()).toUtf8();
89 if (messageId.toByteArray().isEmpty()) { 31 if (messageId.toByteArray().isEmpty()) {
90 SinkWarning() << "Found an email without messageId. This is illegal and threading will break. Entity id: " << identifier; 32 SinkWarning() << "Found an email without messageId. This is illegal and threading will break. Entity id: " << identifier;
91 SinkWarning() << "Subject: " << subject;
92 } 33 }
93 34
94 QVector<QByteArray> thread; 35 QVector<QByteArray> thread;
@@ -101,18 +42,9 @@ void ThreadIndexer::updateThreadingIndex(const QByteArray &identifier, const App
101 thread = index().secondaryLookup<Mail::MessageId, Mail::ThreadId>(parentMessageId); 42 thread = index().secondaryLookup<Mail::MessageId, Mail::ThreadId>(parentMessageId);
102 SinkTrace() << "Found parent: " << thread; 43 SinkTrace() << "Found parent: " << thread;
103 } 44 }
104
105 if (thread.isEmpty()) { 45 if (thread.isEmpty()) {
106 //Try to lookup the thread by subject if not empty 46 thread << QUuid::createUuid().toByteArray();
107 if ( !normalizedSubject.isEmpty()) { 47 SinkTrace() << "Created a new thread: " << thread;
108 thread = index().secondaryLookup<Mail::Subject, Mail::ThreadId>(normalizedSubject);
109 }
110 if (thread.isEmpty()) {
111 thread << QUuid::createUuid().toByteArray();
112 SinkTrace() << "Created a new thread: " << thread;
113 } else {
114 SinkTrace() << "Found thread by subject: " << thread;
115 }
116 } 48 }
117 49
118 Q_ASSERT(!thread.isEmpty()); 50 Q_ASSERT(!thread.isEmpty());
@@ -124,9 +56,6 @@ void ThreadIndexer::updateThreadingIndex(const QByteArray &identifier, const App
124 } 56 }
125 index().index<Mail::MessageId, Mail::ThreadId>(messageId, thread.first(), transaction); 57 index().index<Mail::MessageId, Mail::ThreadId>(messageId, thread.first(), transaction);
126 index().index<Mail::ThreadId, Mail::MessageId>(thread.first(), messageId, transaction); 58 index().index<Mail::ThreadId, Mail::MessageId>(thread.first(), messageId, transaction);
127 if (!normalizedSubject.isEmpty()) {
128 index().index<Mail::Subject, Mail::ThreadId>(normalizedSubject, thread.first(), transaction);
129 }
130} 59}
131 60
132 61
@@ -142,6 +71,15 @@ void ThreadIndexer::modify(const ApplicationDomain::ApplicationDomainType &old,
142 71
143void ThreadIndexer::remove(const ApplicationDomain::ApplicationDomainType &entity) 72void ThreadIndexer::remove(const ApplicationDomain::ApplicationDomainType &entity)
144{ 73{
74 auto messageId = entity.getProperty(Mail::MessageId::name);
75 auto thread = index().secondaryLookup<Mail::MessageId, Mail::ThreadId>(messageId);
76 index().unindex<Mail::MessageId, Mail::ThreadId>(messageId.toByteArray(), thread.first(), transaction());
77 index().unindex<Mail::ThreadId, Mail::MessageId>(thread.first(), messageId.toByteArray(), transaction());
78}
145 79
80QMap<QByteArray, int> ThreadIndexer::databases()
81{
82 return {{"mail.index.messageIdthreadId", 1},
83 {"mail.index.threadIdmessageId", 1}};
146} 84}
147 85
diff --git a/common/mail/threadindexer.h b/common/mail/threadindexer.h
index 064ae71..60d0863 100644
--- a/common/mail/threadindexer.h
+++ b/common/mail/threadindexer.h
@@ -29,6 +29,7 @@ public:
29 virtual void add(const ApplicationDomain::ApplicationDomainType &entity) Q_DECL_OVERRIDE; 29 virtual void add(const ApplicationDomain::ApplicationDomainType &entity) Q_DECL_OVERRIDE;
30 virtual void modify(const ApplicationDomain::ApplicationDomainType &old, const ApplicationDomain::ApplicationDomainType &entity) Q_DECL_OVERRIDE; 30 virtual void modify(const ApplicationDomain::ApplicationDomainType &old, const ApplicationDomain::ApplicationDomainType &entity) Q_DECL_OVERRIDE;
31 virtual void remove(const ApplicationDomain::ApplicationDomainType &entity) Q_DECL_OVERRIDE; 31 virtual void remove(const ApplicationDomain::ApplicationDomainType &entity) Q_DECL_OVERRIDE;
32 static QMap<QByteArray, int> databases();
32private: 33private:
33 void updateThreadingIndex(const QByteArray &identifier, const ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction); 34 void updateThreadingIndex(const QByteArray &identifier, const ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction);
34}; 35};
diff --git a/common/mailpreprocessor.cpp b/common/mailpreprocessor.cpp
index dff3b3d..5c54fbc 100644
--- a/common/mailpreprocessor.cpp
+++ b/common/mailpreprocessor.cpp
@@ -29,8 +29,6 @@
29 29
30using namespace Sink; 30using namespace Sink;
31 31
32SINK_DEBUG_AREA("mailpreprocessor")
33
34QString MailPropertyExtractor::getFilePathFromMimeMessagePath(const QString &s) const 32QString MailPropertyExtractor::getFilePathFromMimeMessagePath(const QString &s) const
35{ 33{
36 return s; 34 return s;
diff --git a/common/messagequeue.cpp b/common/messagequeue.cpp
index 6e79d89..362ddfd 100644
--- a/common/messagequeue.cpp
+++ b/common/messagequeue.cpp
@@ -3,8 +3,6 @@
3#include <QDebug> 3#include <QDebug>
4#include <log.h> 4#include <log.h>
5 5
6SINK_DEBUG_AREA("messagequeue")
7
8MessageQueue::MessageQueue(const QString &storageRoot, const QString &name) : mStorage(storageRoot, name, Sink::Storage::DataStore::ReadWrite) 6MessageQueue::MessageQueue(const QString &storageRoot, const QString &name) : mStorage(storageRoot, name, Sink::Storage::DataStore::ReadWrite)
9{ 7{
10} 8}
diff --git a/common/modelresult.cpp b/common/modelresult.cpp
index b12216b..58703ab 100644
--- a/common/modelresult.cpp
+++ b/common/modelresult.cpp
@@ -224,7 +224,6 @@ QModelIndex ModelResult<T, Ptr>::index(int row, int column, const QModelIndex &p
224 return createIndex(row, column, childId); 224 return createIndex(row, column, childId);
225 } 225 }
226 SinkWarningCtx(mLogCtx) << "Index not available " << row << column << parent; 226 SinkWarningCtx(mLogCtx) << "Index not available " << row << column << parent;
227 Q_ASSERT(false);
228 return QModelIndex(); 227 return QModelIndex();
229} 228}
230 229
diff --git a/common/notification.cpp b/common/notification.cpp
index e688b6d..da31e20 100644
--- a/common/notification.cpp
+++ b/common/notification.cpp
@@ -48,8 +48,8 @@ static QByteArray name(int type)
48 48
49QDebug operator<<(QDebug dbg, const Sink::Notification &n) 49QDebug operator<<(QDebug dbg, const Sink::Notification &n)
50{ 50{
51 dbg << "Notification(Type: " << name(n.type) << "Id, : " << n.id << ", Code: "; 51 dbg << "Notification(Type:" << name(n.type) << "Id, :" << n.id << ", Code:";
52 dbg << n.code; 52 dbg << n.code;
53 dbg << ", Message: " << n.message << ", Entities: " << n.entities << ")"; 53 dbg << ", Message:" << n.message << ", Entities:" << n.entities << ")";
54 return dbg.space(); 54 return dbg.space();
55} 55}
diff --git a/common/notification.h b/common/notification.h
index f5379fd..30e240c 100644
--- a/common/notification.h
+++ b/common/notification.h
@@ -56,6 +56,8 @@ public:
56 QString message; 56 QString message;
57 //A return code. Zero typically indicates success. 57 //A return code. Zero typically indicates success.
58 int code = 0; 58 int code = 0;
59 int progress = 0;
60 int total = 0;
59 QByteArray resource; 61 QByteArray resource;
60}; 62};
61} 63}
diff --git a/common/pipeline.cpp b/common/pipeline.cpp
index f5cf995..f29c7db 100644
--- a/common/pipeline.cpp
+++ b/common/pipeline.cpp
@@ -283,6 +283,8 @@ KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size)
283 case Preprocessor::DropModification: 283 case Preprocessor::DropModification:
284 SinkTraceCtx(d->logCtx) << "Dropping modification"; 284 SinkTraceCtx(d->logCtx) << "Dropping modification";
285 return KAsync::error<qint64>(0); 285 return KAsync::error<qint64>(0);
286 case Preprocessor::NoAction:
287 case Preprocessor::DeleteEntity:
286 default: 288 default:
287 break; 289 break;
288 } 290 }
@@ -301,8 +303,8 @@ KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size)
301 SinkTraceCtx(d->logCtx) << "Move of " << current.identifier() << "was successfull"; 303 SinkTraceCtx(d->logCtx) << "Move of " << current.identifier() << "was successfull";
302 if (isMove) { 304 if (isMove) {
303 flatbuffers::FlatBufferBuilder fbb; 305 flatbuffers::FlatBufferBuilder fbb;
304 auto entityId = fbb.CreateString(current.identifier()); 306 auto entityId = fbb.CreateString(current.identifier().toStdString());
305 auto type = fbb.CreateString(bufferType); 307 auto type = fbb.CreateString(bufferType.toStdString());
306 auto location = Sink::Commands::CreateDeleteEntity(fbb, current.revision(), entityId, type, true); 308 auto location = Sink::Commands::CreateDeleteEntity(fbb, current.revision(), entityId, type, true);
307 Sink::Commands::FinishDeleteEntityBuffer(fbb, location); 309 Sink::Commands::FinishDeleteEntityBuffer(fbb, location);
308 const auto data = BufferUtils::extractBuffer(fbb); 310 const auto data = BufferUtils::extractBuffer(fbb);
@@ -448,8 +450,8 @@ void Preprocessor::createEntity(const Sink::ApplicationDomain::ApplicationDomain
448 const auto entityBuffer = BufferUtils::extractBuffer(entityFbb); 450 const auto entityBuffer = BufferUtils::extractBuffer(entityFbb);
449 451
450 flatbuffers::FlatBufferBuilder fbb; 452 flatbuffers::FlatBufferBuilder fbb;
451 auto entityId = fbb.CreateString(entity.identifier()); 453 auto entityId = fbb.CreateString(entity.identifier().toStdString());
452 auto type = fbb.CreateString(typeName); 454 auto type = fbb.CreateString(typeName.toStdString());
453 auto delta = Sink::EntityBuffer::appendAsVector(fbb, entityBuffer.constData(), entityBuffer.size()); 455 auto delta = Sink::EntityBuffer::appendAsVector(fbb, entityBuffer.constData(), entityBuffer.size());
454 auto location = Sink::Commands::CreateCreateEntity(fbb, entityId, type, delta); 456 auto location = Sink::Commands::CreateCreateEntity(fbb, entityId, type, delta);
455 Sink::Commands::FinishCreateEntityBuffer(fbb, location); 457 Sink::Commands::FinishCreateEntityBuffer(fbb, location);
diff --git a/common/propertymapper.cpp b/common/propertymapper.cpp
index c72cf31..dbf93a3 100644
--- a/common/propertymapper.cpp
+++ b/common/propertymapper.cpp
@@ -21,6 +21,7 @@
21 21
22#include "applicationdomaintype.h" 22#include "applicationdomaintype.h"
23#include <QDateTime> 23#include <QDateTime>
24#include <QDataStream>
24#include "mail_generated.h" 25#include "mail_generated.h"
25#include "contact_generated.h" 26#include "contact_generated.h"
26 27
@@ -57,7 +58,9 @@ template <>
57flatbuffers::uoffset_t variantToProperty<QByteArray>(const QVariant &property, flatbuffers::FlatBufferBuilder &fbb) 58flatbuffers::uoffset_t variantToProperty<QByteArray>(const QVariant &property, flatbuffers::FlatBufferBuilder &fbb)
58{ 59{
59 if (property.isValid()) { 60 if (property.isValid()) {
60 return fbb.CreateString(property.toByteArray().toStdString()).o; 61 const auto ba = property.toByteArray();
62 const auto s = fbb.CreateString(ba.constData(), ba.size());
63 return s.o;
61 } 64 }
62 return 0; 65 return 0;
63} 66}
@@ -66,7 +69,10 @@ template <>
66flatbuffers::uoffset_t variantToProperty<QDateTime>(const QVariant &property, flatbuffers::FlatBufferBuilder &fbb) 69flatbuffers::uoffset_t variantToProperty<QDateTime>(const QVariant &property, flatbuffers::FlatBufferBuilder &fbb)
67{ 70{
68 if (property.isValid()) { 71 if (property.isValid()) {
69 return fbb.CreateString(property.toDateTime().toString().toStdString()).o; 72 QByteArray ba;
73 QDataStream ds(&ba, QIODevice::WriteOnly);
74 ds << property.toDateTime();
75 return fbb.CreateString(ba.toStdString()).o;
70 } 76 }
71 return 0; 77 return 0;
72} 78}
@@ -131,7 +137,7 @@ QString propertyToString(const flatbuffers::String *property)
131{ 137{
132 if (property) { 138 if (property) {
133 // We have to copy the memory, otherwise it would become eventually invalid 139 // We have to copy the memory, otherwise it would become eventually invalid
134 return QString::fromStdString(property->c_str()); 140 return QString::fromStdString(property->str());
135 } 141 }
136 return QString(); 142 return QString();
137} 143}
@@ -141,7 +147,7 @@ QVariant propertyToVariant<QString>(const flatbuffers::String *property)
141{ 147{
142 if (property) { 148 if (property) {
143 // We have to copy the memory, otherwise it would become eventually invalid 149 // We have to copy the memory, otherwise it would become eventually invalid
144 return QString::fromStdString(property->c_str()); 150 return QString::fromStdString(property->str());
145 } 151 }
146 return QVariant(); 152 return QVariant();
147} 153}
@@ -151,7 +157,7 @@ QVariant propertyToVariant<Sink::ApplicationDomain::BLOB>(const flatbuffers::Str
151{ 157{
152 if (property) { 158 if (property) {
153 // We have to copy the memory, otherwise it would become eventually invalid 159 // We have to copy the memory, otherwise it would become eventually invalid
154 auto s = QString::fromStdString(property->c_str()); 160 auto s = QString::fromStdString(property->str());
155 auto ext = s.endsWith(":ext"); 161 auto ext = s.endsWith(":ext");
156 s.chop(4); 162 s.chop(4);
157 163
@@ -167,7 +173,7 @@ QVariant propertyToVariant<Sink::ApplicationDomain::Reference>(const flatbuffers
167{ 173{
168 if (property) { 174 if (property) {
169 // We have to copy the memory, otherwise it would become eventually invalid 175 // We have to copy the memory, otherwise it would become eventually invalid
170 return QVariant::fromValue(Sink::ApplicationDomain::Reference{QString::fromStdString(property->c_str()).toUtf8()}); 176 return QVariant::fromValue(Sink::ApplicationDomain::Reference{QString::fromStdString(property->str()).toUtf8()});
171 } 177 }
172 return QVariant(); 178 return QVariant();
173} 179}
@@ -177,7 +183,7 @@ QVariant propertyToVariant<QByteArray>(const flatbuffers::String *property)
177{ 183{
178 if (property) { 184 if (property) {
179 // We have to copy the memory, otherwise it would become eventually invalid 185 // We have to copy the memory, otherwise it would become eventually invalid
180 return QString::fromStdString(property->c_str()).toUtf8(); 186 return QByteArray(property->c_str(), property->Length());
181 } 187 }
182 return QVariant(); 188 return QVariant();
183} 189}
@@ -199,7 +205,7 @@ QVariant propertyToVariant<QByteArrayList>(const flatbuffers::Vector<flatbuffers
199 QByteArrayList list; 205 QByteArrayList list;
200 for (auto it = property->begin(); it != property->end();) { 206 for (auto it = property->begin(); it != property->end();) {
201 // We have to copy the memory, otherwise it would become eventually invalid 207 // We have to copy the memory, otherwise it would become eventually invalid
202 list << QString::fromStdString((*it)->c_str()).toUtf8(); 208 list << QString::fromStdString((*it)->str()).toUtf8();
203 it.operator++(); 209 it.operator++();
204 } 210 }
205 return QVariant::fromValue(list); 211 return QVariant::fromValue(list);
@@ -256,8 +262,11 @@ template <>
256QVariant propertyToVariant<QDateTime>(const flatbuffers::String *property) 262QVariant propertyToVariant<QDateTime>(const flatbuffers::String *property)
257{ 263{
258 if (property) { 264 if (property) {
259 // We have to copy the memory, otherwise it would become eventually invalid 265 auto ba = QByteArray::fromRawData(property->c_str(), property->size());
260 return QDateTime::fromString(QString::fromStdString(property->c_str())); 266 QDateTime dt;
267 QDataStream ds(&ba, QIODevice::ReadOnly);
268 ds >> dt;
269 return dt;
261 } 270 }
262 return QVariant(); 271 return QVariant();
263} 272}
diff --git a/common/propertymapper.h b/common/propertymapper.h
index 9ea0b73..fd24278 100644
--- a/common/propertymapper.h
+++ b/common/propertymapper.h
@@ -65,13 +65,19 @@ QVariant SINK_EXPORT propertyToVariant(const flatbuffers::Vector<flatbuffers::Of
65 * a virtual method per property, the property mapper can be filled with accessors 65 * a virtual method per property, the property mapper can be filled with accessors
66 * that extract the properties from resource types. 66 * that extract the properties from resource types.
67 */ 67 */
68template <typename BufferType> 68class PropertyMapper
69class ReadPropertyMapper
70{ 69{
71public: 70public:
72 virtual ~ReadPropertyMapper(){}; 71 virtual ~PropertyMapper(){};
73 72
74 virtual QVariant getProperty(const QByteArray &key, BufferType const *buffer) const 73 template <typename T, typename Buffer, typename BufferBuilder, typename FunctionReturnValue, typename Arg>
74 void addMapping(FunctionReturnValue (Buffer::*f)() const, void (BufferBuilder::*f2)(Arg))
75 {
76 addReadMapping<T, Buffer, FunctionReturnValue>(f);
77 addWriteMapping<T, BufferBuilder>(f2);
78 }
79
80 virtual QVariant getProperty(const QByteArray &key, void const *buffer) const
75 { 81 {
76 if (mReadAccessors.contains(key)) { 82 if (mReadAccessors.contains(key)) {
77 auto accessor = mReadAccessors.value(key); 83 auto accessor = mReadAccessors.value(key);
@@ -79,163 +85,69 @@ public:
79 } 85 }
80 return QVariant(); 86 return QVariant();
81 } 87 }
82 bool hasMapping(const QByteArray &key) const
83 {
84 return mReadAccessors.contains(key);
85 }
86 QList<QByteArray> availableProperties() const
87 {
88 return mReadAccessors.keys();
89 }
90 void addMapping(const QByteArray &property, const std::function<QVariant(BufferType const *)> &mapping)
91 {
92 mReadAccessors.insert(property, mapping);
93 }
94
95 template <typename T, typename Buffer>
96 void addMapping(const flatbuffers::String *(Buffer::*f)() const)
97 {
98 addMapping(T::name, [f](Buffer const *buffer) -> QVariant { return propertyToVariant<typename T::Type>((buffer->*f)()); });
99 }
100
101 template <typename T, typename Buffer>
102 void addMapping(uint8_t (Buffer::*f)() const)
103 {
104 addMapping(T::name, [f](Buffer const *buffer) -> QVariant { return propertyToVariant<typename T::Type>((buffer->*f)()); });
105 }
106
107 template <typename T, typename Buffer>
108 void addMapping(bool (Buffer::*f)() const)
109 {
110 addMapping(T::name, [f](Buffer const *buffer) -> QVariant { return propertyToVariant<typename T::Type>((buffer->*f)()); });
111 }
112
113 template <typename T, typename Buffer>
114 void addMapping(const flatbuffers::Vector<uint8_t> *(Buffer::*f)() const)
115 {
116 addMapping(T::name, [f](Buffer const *buffer) -> QVariant { return propertyToVariant<typename T::Type>((buffer->*f)()); });
117 }
118
119 template <typename T, typename Buffer>
120 void addMapping(const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *(Buffer::*f)() const)
121 {
122 addMapping(T::name, [f](Buffer const *buffer) -> QVariant { return propertyToVariant<typename T::Type>((buffer->*f)()); });
123 }
124
125 template <typename T, typename Buffer>
126 void addMapping(const Sink::ApplicationDomain::Buffer::MailContact *(Buffer::*f)() const)
127 {
128 addMapping(T::name, [f](Buffer const *buffer) -> QVariant { return propertyToVariant<typename T::Type>((buffer->*f)()); });
129 }
130
131 template <typename T, typename Buffer>
132 void addMapping(const flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::MailContact>> *(Buffer::*f)() const)
133 {
134 addMapping(T::name, [f](Buffer const *buffer) -> QVariant { return propertyToVariant<typename T::Type>((buffer->*f)()); });
135 }
136 88
137 template <typename T, typename Buffer> 89 virtual void setProperty(const QByteArray &key, const QVariant &value, QList<std::function<void(void *builder)>> &builderCalls, flatbuffers::FlatBufferBuilder &fbb) const
138 void addMapping(const flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::ContactEmail>> *(Buffer::*f)() const)
139 {
140 addMapping(T::name, [f](Buffer const *buffer) -> QVariant { return propertyToVariant<typename T::Type>((buffer->*f)()); });
141 }
142
143private:
144 QHash<QByteArray, std::function<QVariant(BufferType const *)>> mReadAccessors;
145};
146
147template <typename BufferBuilder>
148class WritePropertyMapper
149{
150public:
151 virtual ~WritePropertyMapper(){};
152
153 virtual void setProperty(const QByteArray &key, const QVariant &value, QList<std::function<void(BufferBuilder &)>> &builderCalls, flatbuffers::FlatBufferBuilder &fbb) const
154 { 90 {
155 if (mWriteAccessors.contains(key)) { 91 if (mWriteAccessors.contains(key)) {
156 auto accessor = mWriteAccessors.value(key); 92 auto accessor = mWriteAccessors.value(key);
157 builderCalls << accessor(value, fbb); 93 builderCalls << accessor(value, fbb);
158 } 94 }
159 } 95 }
96
160 bool hasMapping(const QByteArray &key) const 97 bool hasMapping(const QByteArray &key) const
161 { 98 {
162 return mWriteAccessors.contains(key); 99 return mReadAccessors.contains(key);
163 }
164 void addMapping(const QByteArray &property, const std::function<std::function<void(BufferBuilder &)>(const QVariant &, flatbuffers::FlatBufferBuilder &)> &mapping)
165 {
166 mWriteAccessors.insert(property, mapping);
167 } 100 }
168 101
169 template <typename T> 102 QList<QByteArray> availableProperties() const
170 void addMapping(void (BufferBuilder::*f)(uint8_t))
171 { 103 {
172 addMapping(T::name, [f](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function<void(BufferBuilder &)> { 104 return mReadAccessors.keys();
173 return [value, f](BufferBuilder &builder) { (builder.*f)(value.value<typename T::Type>()); };
174 });
175 } 105 }
176 106
177 template <typename T> 107private:
178 void addMapping(void (BufferBuilder::*f)(bool)) 108 void addReadMapping(const QByteArray &property, const std::function<QVariant(void const *)> &mapping)
179 { 109 {
180 addMapping(T::name, [f](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function<void(BufferBuilder &)> { 110 mReadAccessors.insert(property, mapping);
181 return [value, f](BufferBuilder &builder) { (builder.*f)(value.value<typename T::Type>()); };
182 });
183 } 111 }
184 112
185 template <typename T> 113 template <typename T, typename Buffer, typename FunctionReturnValue>
186 void addMapping(void (BufferBuilder::*f)(flatbuffers::Offset<flatbuffers::String>)) 114 void addReadMapping(FunctionReturnValue (Buffer::*f)() const)
187 { 115 {
188 addMapping(T::name, [f](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function<void(BufferBuilder &)> { 116 addReadMapping(T::name, [f](void const *buffer) -> QVariant { return propertyToVariant<typename T::Type>((static_cast<const Buffer*>(buffer)->*f)()); });
189 auto offset = variantToProperty<typename T::Type>(value, fbb);
190 return [offset, f](BufferBuilder &builder) { (builder.*f)(offset); };
191 });
192 } 117 }
193 118
194 template <typename T>
195 void addMapping(void (BufferBuilder::*f)(flatbuffers::Offset<flatbuffers::Vector<uint8_t>>))
196 {
197 addMapping(T::name, [f](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function<void(BufferBuilder &)> {
198 auto offset = variantToProperty<typename T::Type>(value, fbb);
199 return [offset, f](BufferBuilder &builder) { (builder.*f)(offset); };
200 });
201 }
202 119
203 template <typename T> 120 void addWriteMapping(const QByteArray &property, const std::function<std::function<void(void *builder)>(const QVariant &, flatbuffers::FlatBufferBuilder &)> &mapping)
204 void addMapping(void (BufferBuilder::*f)(flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>))
205 { 121 {
206 addMapping(T::name, [f](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function<void(BufferBuilder &)> { 122 mWriteAccessors.insert(property, mapping);
207 auto offset = variantToProperty<typename T::Type>(value, fbb);
208 return [offset, f](BufferBuilder &builder) { (builder.*f)(offset); };
209 });
210 } 123 }
211 124
212 template <typename T> 125 template <typename T, typename BufferBuilder>
213 void addMapping(void (BufferBuilder::*f)(flatbuffers::Offset<Sink::ApplicationDomain::Buffer::MailContact>)) 126 void addWriteMapping(void (BufferBuilder::*f)(uint8_t))
214 { 127 {
215 addMapping(T::name, [f](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function<void(BufferBuilder &)> { 128 addWriteMapping(T::name, [f](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function<void(void *builder)> {
216 auto offset = variantToProperty<typename T::Type>(value, fbb); 129 return [value, f](void *builder) { (static_cast<BufferBuilder*>(builder)->*f)(value.value<typename T::Type>()); };
217 return [offset, f](BufferBuilder &builder) { (builder.*f)(offset); };
218 }); 130 });
219 } 131 }
220 132
221 template <typename T> 133 template <typename T, typename BufferBuilder>
222 void addMapping(void (BufferBuilder::*f)(flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::MailContact>>>)) 134 void addWriteMapping(void (BufferBuilder::*f)(bool))
223 { 135 {
224 addMapping(T::name, [f](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function<void(BufferBuilder &)> { 136 addWriteMapping(T::name, [f](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function<void(void *builder)> {
225 auto offset = variantToProperty<typename T::Type>(value, fbb); 137 return [value, f](void *builder) { (static_cast<BufferBuilder*>(builder)->*f)(value.value<typename T::Type>()); };
226 return [offset, f](BufferBuilder &builder) { (builder.*f)(offset); };
227 }); 138 });
228 } 139 }
229 140
230 template <typename T> 141 template <typename T, typename BufferBuilder, typename Arg>
231 void addMapping(void (BufferBuilder::*f)(flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::ContactEmail>>>)) 142 void addWriteMapping(void (BufferBuilder::*f)(flatbuffers::Offset<Arg>))
232 { 143 {
233 addMapping(T::name, [f](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function<void(BufferBuilder &)> { 144 addWriteMapping(T::name, [f](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function<void(void *builder)> {
234 auto offset = variantToProperty<typename T::Type>(value, fbb); 145 auto offset = variantToProperty<typename T::Type>(value, fbb);
235 return [offset, f](BufferBuilder &builder) { (builder.*f)(offset); }; 146 return [offset, f](void *builder) { (static_cast<BufferBuilder*>(builder)->*f)(offset); };
236 }); 147 });
237 } 148 }
238 149
239private: 150 QHash<QByteArray, std::function<QVariant(void const *)>> mReadAccessors;
240 QHash<QByteArray, std::function<std::function<void(BufferBuilder &)>(const QVariant &, flatbuffers::FlatBufferBuilder &)>> mWriteAccessors; 151 QHash<QByteArray, std::function<std::function<void(void *builder)>(const QVariant &, flatbuffers::FlatBufferBuilder &)>> mWriteAccessors;
241}; 152};
153
diff --git a/common/queryrunner.cpp b/common/queryrunner.cpp
index 43f48c0..f196965 100644
--- a/common/queryrunner.cpp
+++ b/common/queryrunner.cpp
@@ -231,11 +231,11 @@ ReplayResult QueryWorker<DomainType>::executeIncrementalQuery(const Sink::Query
231 auto replayResult = resultSet.replaySet(0, 0, [this, query, &resultProvider](const ResultSet::Result &result) { 231 auto replayResult = resultSet.replaySet(0, 0, [this, query, &resultProvider](const ResultSet::Result &result) {
232 resultProviderCallback(query, resultProvider, result); 232 resultProviderCallback(query, resultProvider, result);
233 }); 233 });
234 234 preparedQuery.updateComplete();
235 SinkTraceCtx(mLogCtx) << "Replayed " << replayResult.replayedEntities << " results.\n" 235 SinkTraceCtx(mLogCtx) << "Replayed " << replayResult.replayedEntities << " results.\n"
236 << (replayResult.replayedAll ? "Replayed all available results.\n" : "") 236 << (replayResult.replayedAll ? "Replayed all available results.\n" : "")
237 << "Incremental query took: " << Log::TraceTime(time.elapsed()); 237 << "Incremental query took: " << Log::TraceTime(time.elapsed());
238 return {entityStore.maxRevision(), replayResult.replayedEntities, replayResult.replayedAll, preparedQuery.getState()}; 238 return {entityStore.maxRevision(), replayResult.replayedEntities, false, preparedQuery.getState()};
239} 239}
240 240
241template <class DomainType> 241template <class DomainType>
@@ -264,7 +264,7 @@ ReplayResult QueryWorker<DomainType>::executeInitialQuery(
264 return DataStoreQuery{modifiedQuery, ApplicationDomain::getTypeName<DomainType>(), entityStore}; 264 return DataStoreQuery{modifiedQuery, ApplicationDomain::getTypeName<DomainType>(), entityStore};
265 } 265 }
266 }(); 266 }();
267 auto resultSet = preparedQuery.execute();; 267 auto resultSet = preparedQuery.execute();
268 268
269 SinkTraceCtx(mLogCtx) << "Filtered set retrieved." << Log::TraceTime(time.elapsed()); 269 SinkTraceCtx(mLogCtx) << "Filtered set retrieved." << Log::TraceTime(time.elapsed());
270 auto replayResult = resultSet.replaySet(0, batchsize, [this, query, &resultProvider](const ResultSet::Result &result) { 270 auto replayResult = resultSet.replaySet(0, batchsize, [this, query, &resultProvider](const ResultSet::Result &result) {
diff --git a/common/queryrunner.h b/common/queryrunner.h
index 5308eac..11a302f 100644
--- a/common/queryrunner.h
+++ b/common/queryrunner.h
@@ -32,8 +32,6 @@
32class QueryRunnerBase : public QObject 32class QueryRunnerBase : public QObject
33{ 33{
34 Q_OBJECT 34 Q_OBJECT
35protected:
36 SINK_DEBUG_AREA("queryrunner")
37public: 35public:
38 typedef std::function<void(Sink::ApplicationDomain::ApplicationDomainType &domainObject)> ResultTransformation; 36 typedef std::function<void(Sink::ApplicationDomain::ApplicationDomainType &domainObject)> ResultTransformation;
39 37
diff --git a/common/resourceaccess.cpp b/common/resourceaccess.cpp
index ad8cae9..808d892 100644
--- a/common/resourceaccess.cpp
+++ b/common/resourceaccess.cpp
@@ -477,7 +477,7 @@ void ResourceAccess::connected()
477 477
478 { 478 {
479 flatbuffers::FlatBufferBuilder fbb; 479 flatbuffers::FlatBufferBuilder fbb;
480 auto name = fbb.CreateString(QString("PID: %1 ResourceAccess: %2").arg(QCoreApplication::applicationPid()).arg(reinterpret_cast<qlonglong>(this)).toLatin1()); 480 auto name = fbb.CreateString(QString("PID: %1 ResourceAccess: %2").arg(QCoreApplication::applicationPid()).arg(reinterpret_cast<qlonglong>(this)).toLatin1().toStdString());
481 auto command = Sink::Commands::CreateHandshake(fbb, name); 481 auto command = Sink::Commands::CreateHandshake(fbb, name);
482 Sink::Commands::FinishHandshakeBuffer(fbb, command); 482 Sink::Commands::FinishHandshakeBuffer(fbb, command);
483 Commands::write(d->socket.data(), ++d->messageId, Commands::HandshakeCommand, fbb); 483 Commands::write(d->socket.data(), ++d->messageId, Commands::HandshakeCommand, fbb);
@@ -547,6 +547,8 @@ static Sink::Notification getNotification(const Sink::Commands::Notification *bu
547 } 547 }
548 n.type = buffer->type(); 548 n.type = buffer->type();
549 n.code = buffer->code(); 549 n.code = buffer->code();
550 n.progress = buffer->progress();
551 n.total = buffer->total();
550 n.entities = BufferUtils::fromVector(*buffer->entities()); 552 n.entities = BufferUtils::fromVector(*buffer->entities());
551 return n; 553 return n;
552} 554}
diff --git a/common/resourceaccess.h b/common/resourceaccess.h
index c32566b..b6a0b34 100644
--- a/common/resourceaccess.h
+++ b/common/resourceaccess.h
@@ -101,7 +101,6 @@ protected:
101class SINK_EXPORT ResourceAccess : public ResourceAccessInterface 101class SINK_EXPORT ResourceAccess : public ResourceAccessInterface
102{ 102{
103 Q_OBJECT 103 Q_OBJECT
104 SINK_DEBUG_AREA("communication")
105public: 104public:
106 typedef QSharedPointer<ResourceAccess> Ptr; 105 typedef QSharedPointer<ResourceAccess> Ptr;
107 106
@@ -158,7 +157,6 @@ private:
158 */ 157 */
159class SINK_EXPORT ResourceAccessFactory 158class SINK_EXPORT ResourceAccessFactory
160{ 159{
161 SINK_DEBUG_AREA("ResourceAccessFactory")
162public: 160public:
163 static ResourceAccessFactory &instance(); 161 static ResourceAccessFactory &instance();
164 Sink::ResourceAccess::Ptr getAccess(const QByteArray &instanceIdentifier, const QByteArray resourceType); 162 Sink::ResourceAccess::Ptr getAccess(const QByteArray &instanceIdentifier, const QByteArray resourceType);
diff --git a/common/resourcecontrol.cpp b/common/resourcecontrol.cpp
index 70a3f7d..b6a4c0b 100644
--- a/common/resourcecontrol.cpp
+++ b/common/resourcecontrol.cpp
@@ -30,8 +30,6 @@
30#include "log.h" 30#include "log.h"
31#include "notifier.h" 31#include "notifier.h"
32 32
33SINK_DEBUG_AREA("resourcecontrol")
34
35namespace Sink { 33namespace Sink {
36 34
37KAsync::Job<void> ResourceControl::shutdown(const QByteArray &identifier) 35KAsync::Job<void> ResourceControl::shutdown(const QByteArray &identifier)
diff --git a/common/resourcefacade.cpp b/common/resourcefacade.cpp
index dee0711..dab6aed 100644
--- a/common/resourcefacade.cpp
+++ b/common/resourcefacade.cpp
@@ -21,16 +21,12 @@
21#include "resourceconfig.h" 21#include "resourceconfig.h"
22#include "query.h" 22#include "query.h"
23#include "definitions.h" 23#include "definitions.h"
24#include "storage.h"
25#include "store.h" 24#include "store.h"
26#include "resourceaccess.h" 25#include "resourceaccess.h"
27#include "resource.h" 26#include "resource.h"
28#include <QDir>
29 27
30using namespace Sink; 28using namespace Sink;
31 29
32SINK_DEBUG_AREA("ResourceFacade")
33
34template<typename DomainType> 30template<typename DomainType>
35ConfigNotifier LocalStorageFacade<DomainType>::sConfigNotifier; 31ConfigNotifier LocalStorageFacade<DomainType>::sConfigNotifier;
36 32
@@ -100,17 +96,24 @@ template<typename DomainType>
100LocalStorageQueryRunner<DomainType>::LocalStorageQueryRunner(const Query &query, const QByteArray &identifier, const QByteArray &typeName, ConfigNotifier &configNotifier, const Sink::Log::Context &ctx) 96LocalStorageQueryRunner<DomainType>::LocalStorageQueryRunner(const Query &query, const QByteArray &identifier, const QByteArray &typeName, ConfigNotifier &configNotifier, const Sink::Log::Context &ctx)
101 : mResultProvider(new ResultProvider<typename DomainType::Ptr>), mConfigStore(identifier, typeName), mGuard(new QObject), mLogCtx(ctx.subContext("config")) 97 : mResultProvider(new ResultProvider<typename DomainType::Ptr>), mConfigStore(identifier, typeName), mGuard(new QObject), mLogCtx(ctx.subContext("config"))
102{ 98{
99
100 auto matchesTypeAndIds = [query, this] (const QByteArray &type, const QByteArray &id) {
101 if (query.hasFilter(ApplicationDomain::SinkResource::ResourceType::name) && query.getFilter(ApplicationDomain::SinkResource::ResourceType::name).value.toByteArray() != type) {
102 SinkTraceCtx(mLogCtx) << "Skipping due to type.";
103 return false;
104 }
105 if (!query.ids().isEmpty() && !query.ids().contains(id)) {
106 return false;
107 }
108 return true;
109 };
110
103 QObject *guard = new QObject; 111 QObject *guard = new QObject;
104 mResultProvider->setFetcher([this, query, guard, &configNotifier](const QSharedPointer<DomainType> &) { 112 mResultProvider->setFetcher([this, query, guard, &configNotifier, matchesTypeAndIds](const QSharedPointer<DomainType> &) {
105 const auto entries = mConfigStore.getEntries(); 113 const auto entries = mConfigStore.getEntries();
106 for (const auto &res : entries.keys()) { 114 for (const auto &res : entries.keys()) {
107 const auto type = entries.value(res); 115 const auto type = entries.value(res);
108 116 if (!matchesTypeAndIds(type, res)){
109 if (query.hasFilter(ApplicationDomain::SinkResource::ResourceType::name) && query.getFilter(ApplicationDomain::SinkResource::ResourceType::name).value.toByteArray() != type) {
110 SinkTraceCtx(mLogCtx) << "Skipping due to type.";
111 continue;
112 }
113 if (!query.ids().isEmpty() && !query.ids().contains(res)) {
114 continue; 117 continue;
115 } 118 }
116 auto entity = readFromConfig<DomainType>(mConfigStore, res, type, query.requestedProperties); 119 auto entity = readFromConfig<DomainType>(mConfigStore, res, type, query.requestedProperties);
@@ -128,8 +131,14 @@ LocalStorageQueryRunner<DomainType>::LocalStorageQueryRunner(const Query &query,
128 }); 131 });
129 if (query.liveQuery()) { 132 if (query.liveQuery()) {
130 { 133 {
131 auto ret = QObject::connect(&configNotifier, &ConfigNotifier::added, guard, [this](const ApplicationDomain::ApplicationDomainType::Ptr &entry) { 134 auto ret = QObject::connect(&configNotifier, &ConfigNotifier::added, guard, [this, query, matchesTypeAndIds](const ApplicationDomain::ApplicationDomainType::Ptr &entry, const QByteArray &type) {
132 auto entity = entry.staticCast<DomainType>(); 135 auto entity = entry.staticCast<DomainType>();
136 if (!matchesTypeAndIds(type, entity->identifier())){
137 return;
138 }
139 if (!matchesFilter(query.getBaseFilters(), *entity)){
140 return;
141 }
133 SinkTraceCtx(mLogCtx) << "A new resource has been added: " << entity->identifier(); 142 SinkTraceCtx(mLogCtx) << "A new resource has been added: " << entity->identifier();
134 updateStatus(*entity); 143 updateStatus(*entity);
135 mResultProvider->add(entity); 144 mResultProvider->add(entity);
@@ -137,8 +146,14 @@ LocalStorageQueryRunner<DomainType>::LocalStorageQueryRunner(const Query &query,
137 Q_ASSERT(ret); 146 Q_ASSERT(ret);
138 } 147 }
139 { 148 {
140 auto ret = QObject::connect(&configNotifier, &ConfigNotifier::modified, guard, [this](const ApplicationDomain::ApplicationDomainType::Ptr &entry) { 149 auto ret = QObject::connect(&configNotifier, &ConfigNotifier::modified, guard, [this, query, matchesTypeAndIds](const ApplicationDomain::ApplicationDomainType::Ptr &entry, const QByteArray &type) {
141 auto entity = entry.staticCast<DomainType>(); 150 auto entity = entry.staticCast<DomainType>();
151 if (!matchesTypeAndIds(type, entity->identifier())){
152 return;
153 }
154 if (!matchesFilter(query.getBaseFilters(), *entity)){
155 return;
156 }
142 updateStatus(*entity); 157 updateStatus(*entity);
143 mResultProvider->modify(entity); 158 mResultProvider->modify(entity);
144 }); 159 });
@@ -222,7 +237,7 @@ KAsync::Job<void> LocalStorageFacade<DomainType>::create(const DomainType &domai
222 } 237 }
223 configStore.modify(identifier, configurationValues); 238 configStore.modify(identifier, configurationValues);
224 } 239 }
225 sConfigNotifier.add(::readFromConfig<DomainType>(configStore, identifier, type, QByteArrayList{})); 240 sConfigNotifier.add(::readFromConfig<DomainType>(configStore, identifier, type, QByteArrayList{}), type);
226 }); 241 });
227} 242}
228 243
@@ -251,7 +266,7 @@ KAsync::Job<void> LocalStorageFacade<DomainType>::modify(const DomainType &domai
251 } 266 }
252 267
253 const auto type = configStore.getEntries().value(identifier); 268 const auto type = configStore.getEntries().value(identifier);
254 sConfigNotifier.modify(::readFromConfig<DomainType>(configStore, identifier, type, QByteArrayList{})); 269 sConfigNotifier.modify(::readFromConfig<DomainType>(configStore, identifier, type, QByteArrayList{}), type);
255 }); 270 });
256} 271}
257 272
@@ -281,7 +296,7 @@ KAsync::Job<void> LocalStorageFacade<DomainType>::remove(const DomainType &domai
281 SinkTrace() << "Removing: " << identifier; 296 SinkTrace() << "Removing: " << identifier;
282 auto configStore = ConfigStore(configStoreIdentifier, typeName); 297 auto configStore = ConfigStore(configStoreIdentifier, typeName);
283 configStore.remove(identifier); 298 configStore.remove(identifier);
284 sConfigNotifier.remove(QSharedPointer<DomainType>::create(domainObject)); 299 sConfigNotifier.remove(QSharedPointer<DomainType>::create(domainObject), typeName);
285 }); 300 });
286} 301}
287 302
@@ -373,10 +388,10 @@ QPair<KAsync::Job<void>, typename Sink::ResultEmitter<typename ApplicationDomain
373 if (states.contains(ApplicationDomain::BusyStatus)) { 388 if (states.contains(ApplicationDomain::BusyStatus)) {
374 return ApplicationDomain::BusyStatus; 389 return ApplicationDomain::BusyStatus;
375 } 390 }
376 if (states.contains(ApplicationDomain::ConnectedStatus)) { 391 if (states.contains(ApplicationDomain::OfflineStatus)) {
377 return ApplicationDomain::ConnectedStatus; 392 return ApplicationDomain::OfflineStatus;
378 } 393 }
379 return ApplicationDomain::OfflineStatus; 394 return ApplicationDomain::ConnectedStatus;
380 }(); 395 }();
381 account.setStatusStatus(status); 396 account.setStatusStatus(status);
382 }); 397 });
diff --git a/common/resourcefacade.h b/common/resourcefacade.h
index 1cc075c..76fadce 100644
--- a/common/resourcefacade.h
+++ b/common/resourcefacade.h
@@ -36,24 +36,24 @@ class ConfigNotifier : public QObject
36{ 36{
37 Q_OBJECT 37 Q_OBJECT
38public: 38public:
39 void add(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account) 39 void add(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account, const QByteArray &type)
40 { 40 {
41 emit added(account); 41 emit added(account, type);
42 } 42 }
43 43
44 void remove(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account) 44 void remove(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account, const QByteArray &type)
45 { 45 {
46 emit removed(account); 46 emit removed(account, type);
47 } 47 }
48 48
49 void modify(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account) 49 void modify(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account, const QByteArray &type)
50 { 50 {
51 emit modified(account); 51 emit modified(account, type);
52 } 52 }
53signals: 53signals:
54 void added(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account); 54 void added(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account, const QByteArray &type);
55 void removed(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account); 55 void removed(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account, const QByteArray &type);
56 void modified(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account); 56 void modified(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &account, const QByteArray &type);
57}; 57};
58 58
59template <typename DomainType> 59template <typename DomainType>
diff --git a/common/specialpurposepreprocessor.cpp b/common/specialpurposepreprocessor.cpp
index 25a6d1a..54f7f46 100644
--- a/common/specialpurposepreprocessor.cpp
+++ b/common/specialpurposepreprocessor.cpp
@@ -5,8 +5,6 @@
5 5
6using namespace Sink; 6using namespace Sink;
7 7
8SINK_DEBUG_AREA("SpecialPurposeProcessor")
9
10static QHash<QByteArray, QString> specialPurposeFolders() 8static QHash<QByteArray, QString> specialPurposeFolders()
11{ 9{
12 QHash<QByteArray, QString> hash; 10 QHash<QByteArray, QString> hash;
diff --git a/common/storage.h b/common/storage.h
index 71e9401..8c129df 100644
--- a/common/storage.h
+++ b/common/storage.h
@@ -25,10 +25,19 @@
25#include <string> 25#include <string>
26#include <functional> 26#include <functional>
27#include <QString> 27#include <QString>
28#include <QMap>
28 29
29namespace Sink { 30namespace Sink {
30namespace Storage { 31namespace Storage {
31 32
33struct SINK_EXPORT DbLayout {
34 typedef QMap<QByteArray, int> Databases;
35 DbLayout();
36 DbLayout(const QByteArray &, const Databases &);
37 QByteArray name;
38 Databases tables;
39};
40
32class SINK_EXPORT DataStore 41class SINK_EXPORT DataStore
33{ 42{
34public: 43public:
@@ -132,7 +141,6 @@ public:
132 void abort(); 141 void abort();
133 142
134 QList<QByteArray> getDatabaseNames() const; 143 QList<QByteArray> getDatabaseNames() const;
135 bool validateNamedDatabases();
136 144
137 NamedDatabase openDatabase(const QByteArray &name = {"default"}, 145 NamedDatabase openDatabase(const QByteArray &name = {"default"},
138 const std::function<void(const DataStore::Error &error)> &errorHandler = {}, bool allowDuplicates = false) const; 146 const std::function<void(const DataStore::Error &error)> &errorHandler = {}, bool allowDuplicates = false) const;
@@ -152,6 +160,7 @@ public:
152 }; 160 };
153 161
154 DataStore(const QString &storageRoot, const QString &name, AccessMode mode = ReadOnly); 162 DataStore(const QString &storageRoot, const QString &name, AccessMode mode = ReadOnly);
163 DataStore(const QString &storageRoot, const DbLayout &layout, AccessMode mode = ReadOnly);
155 ~DataStore(); 164 ~DataStore();
156 165
157 Transaction createTransaction(AccessMode mode = ReadWrite, const std::function<void(const DataStore::Error &error)> &errorHandler = std::function<void(const DataStore::Error &error)>()); 166 Transaction createTransaction(AccessMode mode = ReadWrite, const std::function<void(const DataStore::Error &error)> &errorHandler = std::function<void(const DataStore::Error &error)>());
diff --git a/common/storage/entitystore.cpp b/common/storage/entitystore.cpp
index b7309ab..22e5ae3 100644
--- a/common/storage/entitystore.cpp
+++ b/common/storage/entitystore.cpp
@@ -36,9 +36,75 @@
36using namespace Sink; 36using namespace Sink;
37using namespace Sink::Storage; 37using namespace Sink::Storage;
38 38
39static QMap<QByteArray, int> baseDbs()
40{
41 return {{"revisionType", 0},
42 {"revisions", 0},
43 {"uids", 0},
44 {"default", 0},
45 {"__flagtable", 0}};
46}
47
48template <typename T, typename First>
49void mergeImpl(T &map, First f)
50{
51 for (auto it = f.constBegin(); it != f.constEnd(); it++) {
52 map.insert(it.key(), it.value());
53 }
54}
55
56template <typename T, typename First, typename ... Tail>
57void mergeImpl(T &map, First f, Tail ...maps)
58{
59 for (auto it = f.constBegin(); it != f.constEnd(); it++) {
60 map.insert(it.key(), it.value());
61 }
62 mergeImpl<T, Tail...>(map, maps...);
63}
64
65template <typename First, typename ... Tail>
66First merge(First f, Tail ...maps)
67{
68 First map;
69 mergeImpl(map, f, maps...);
70 return map;
71}
72
73template <class T>
74struct DbLayoutHelper {
75 void operator()(QMap<QByteArray, int> map) const {
76 mergeImpl(map, ApplicationDomain::TypeImplementation<T>::typeDatabases());
77 }
78};
79
80static Sink::Storage::DbLayout dbLayout(const QByteArray &instanceId)
81{
82 static auto databases = [] {
83 QMap<QByteArray, int> map;
84 mergeImpl(map, ApplicationDomain::TypeImplementation<ApplicationDomain::Mail>::typeDatabases());
85 mergeImpl(map, ApplicationDomain::TypeImplementation<ApplicationDomain::Folder>::typeDatabases());
86 mergeImpl(map, ApplicationDomain::TypeImplementation<ApplicationDomain::Contact>::typeDatabases());
87 mergeImpl(map, ApplicationDomain::TypeImplementation<ApplicationDomain::Addressbook>::typeDatabases());
88 mergeImpl(map, ApplicationDomain::TypeImplementation<ApplicationDomain::Event>::typeDatabases());
89 return merge(baseDbs(), map);
90 }();
91 return {instanceId, databases};
92}
93
94
39class EntityStore::Private { 95class EntityStore::Private {
40public: 96public:
41 Private(const ResourceContext &context, const Sink::Log::Context &ctx) : resourceContext(context), logCtx(ctx.subContext("entitystore")) {} 97 Private(const ResourceContext &context, const Sink::Log::Context &ctx) : resourceContext(context), logCtx(ctx.subContext("entitystore"))
98 {
99 static bool initialized = false;
100 if (!initialized) {
101 if (QDir{}.mkpath(entityBlobStorageDir())) {
102 initialized = true;
103 } else {
104 SinkWarningCtx(logCtx) << "Failed to create the directory: " << entityBlobStorageDir();
105 }
106 }
107 }
42 108
43 ResourceContext resourceContext; 109 ResourceContext resourceContext;
44 DataStore::Transaction transaction; 110 DataStore::Transaction transaction;
@@ -56,7 +122,7 @@ public:
56 return transaction; 122 return transaction;
57 } 123 }
58 124
59 Sink::Storage::DataStore store(Sink::storageLocation(), resourceContext.instanceId(), DataStore::ReadOnly); 125 Sink::Storage::DataStore store(Sink::storageLocation(), dbLayout(resourceContext.instanceId()), DataStore::ReadOnly);
60 transaction = store.createTransaction(DataStore::ReadOnly); 126 transaction = store.createTransaction(DataStore::ReadOnly);
61 return transaction; 127 return transaction;
62 } 128 }
@@ -93,9 +159,14 @@ public:
93 return ApplicationDomain::ApplicationDomainType{resourceContext.instanceId(), uid, revision, adaptor}; 159 return ApplicationDomain::ApplicationDomainType{resourceContext.instanceId(), uid, revision, adaptor};
94 } 160 }
95 161
162 QString entityBlobStorageDir()
163 {
164 return Sink::resourceStorageLocation(resourceContext.instanceId()) + "/blob";
165 }
166
96 QString entityBlobStoragePath(const QByteArray &id) 167 QString entityBlobStoragePath(const QByteArray &id)
97 { 168 {
98 return Sink::resourceStorageLocation(resourceContext.instanceId()) + "/blob/" + id; 169 return entityBlobStorageDir() +"/"+ id;
99 } 170 }
100 171
101}; 172};
@@ -110,9 +181,8 @@ void EntityStore::startTransaction(Sink::Storage::DataStore::AccessMode accessMo
110{ 181{
111 SinkTraceCtx(d->logCtx) << "Starting transaction: " << accessMode; 182 SinkTraceCtx(d->logCtx) << "Starting transaction: " << accessMode;
112 Q_ASSERT(!d->transaction); 183 Q_ASSERT(!d->transaction);
113 Sink::Storage::DataStore store(Sink::storageLocation(), d->resourceContext.instanceId(), accessMode); 184 Sink::Storage::DataStore store(Sink::storageLocation(), dbLayout(d->resourceContext.instanceId()), accessMode);
114 d->transaction = store.createTransaction(accessMode); 185 d->transaction = store.createTransaction(accessMode);
115 Q_ASSERT(d->transaction.validateNamedDatabases());
116} 186}
117 187
118void EntityStore::commitTransaction() 188void EntityStore::commitTransaction()
@@ -138,9 +208,6 @@ bool EntityStore::hasTransaction() const
138void EntityStore::copyBlobs(ApplicationDomain::ApplicationDomainType &entity, qint64 newRevision) 208void EntityStore::copyBlobs(ApplicationDomain::ApplicationDomainType &entity, qint64 newRevision)
139{ 209{
140 const auto directory = d->entityBlobStoragePath(entity.identifier()); 210 const auto directory = d->entityBlobStoragePath(entity.identifier());
141 if (!QDir().mkpath(directory)) {
142 SinkWarningCtx(d->logCtx) << "Failed to create the directory: " << directory;
143 }
144 211
145 for (const auto &property : entity.changedProperties()) { 212 for (const auto &property : entity.changedProperties()) {
146 const auto value = entity.getProperty(property); 213 const auto value = entity.getProperty(property);
@@ -149,7 +216,7 @@ void EntityStore::copyBlobs(ApplicationDomain::ApplicationDomainType &entity, qi
149 //Any blob that is not part of the storage yet has to be moved there. 216 //Any blob that is not part of the storage yet has to be moved there.
150 if (blob.isExternal) { 217 if (blob.isExternal) {
151 auto oldPath = blob.value; 218 auto oldPath = blob.value;
152 auto filePath = directory + QString("/%1%2.blob").arg(QString::number(newRevision)).arg(QString::fromLatin1(property)); 219 auto filePath = directory + QString("_%1%2.blob").arg(QString::number(newRevision)).arg(QString::fromLatin1(property));
153 //In case we hit the same revision again due to a rollback. 220 //In case we hit the same revision again due to a rollback.
154 QFile::remove(filePath); 221 QFile::remove(filePath);
155 QFile origFile(oldPath); 222 QFile origFile(oldPath);
@@ -320,6 +387,11 @@ void EntityStore::cleanupEntityRevisionsUntil(qint64 revision)
320{ 387{
321 const auto uid = DataStore::getUidFromRevision(d->transaction, revision); 388 const auto uid = DataStore::getUidFromRevision(d->transaction, revision);
322 const auto bufferType = DataStore::getTypeFromRevision(d->transaction, revision); 389 const auto bufferType = DataStore::getTypeFromRevision(d->transaction, revision);
390 if (bufferType.isEmpty() || uid.isEmpty()) {
391 SinkErrorCtx(d->logCtx) << "Failed to find revision during cleanup: " << revision;
392 Q_ASSERT(false);
393 return;
394 }
323 SinkTraceCtx(d->logCtx) << "Cleaning up revision " << revision << uid << bufferType; 395 SinkTraceCtx(d->logCtx) << "Cleaning up revision " << revision << uid << bufferType;
324 DataStore::mainDatabase(d->transaction, bufferType) 396 DataStore::mainDatabase(d->transaction, bufferType)
325 .scan(uid, 397 .scan(uid,
@@ -337,10 +409,10 @@ void EntityStore::cleanupEntityRevisionsUntil(qint64 revision)
337 DataStore::mainDatabase(d->transaction, bufferType).remove(key); 409 DataStore::mainDatabase(d->transaction, bufferType).remove(key);
338 } 410 }
339 if (isRemoval) { 411 if (isRemoval) {
340 const auto directory = d->entityBlobStoragePath(uid); 412 QDir dir{d->entityBlobStorageDir()};
341 QDir dir(directory); 413 const auto infoList = dir.entryInfoList(QStringList{} << QString{uid + "*"});
342 if (!dir.removeRecursively()) { 414 for (const auto &fileInfo : infoList) {
343 SinkErrorCtx(d->logCtx) << "Failed to cleanup: " << directory; 415 QFile::remove(fileInfo.filePath());
344 } 416 }
345 } 417 }
346 //Don't cleanup more than specified 418 //Don't cleanup more than specified
@@ -614,29 +686,6 @@ qint64 EntityStore::maxRevision()
614 return DataStore::maxRevision(d->getTransaction()); 686 return DataStore::maxRevision(d->getTransaction());
615} 687}
616 688
617/* DataStore::Transaction getTransaction() */
618/* { */
619/* Sink::Storage::DataStore::Transaction transaction; */
620/* { */
621/* Sink::Storage::DataStore storage(Sink::storageLocation(), mResourceInstanceIdentifier); */
622/* if (!storage.exists()) { */
623/* //This is not an error if the resource wasn't started before */
624/* SinkLogCtx(d->logCtx) << "Store doesn't exist: " << mResourceInstanceIdentifier; */
625/* return Sink::Storage::DataStore::Transaction(); */
626/* } */
627/* storage.setDefaultErrorHandler([this](const Sink::Storage::DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Error during query: " << error.store << error.message; }); */
628/* transaction = storage.createTransaction(Sink::Storage::DataStore::ReadOnly); */
629/* } */
630
631/* //FIXME this is a temporary measure to recover from a failure to open the named databases correctly. */
632/* //Once the actual problem is fixed it will be enough to simply crash if we open the wrong database (which we check in openDatabase already). */
633/* while (!transaction.validateNamedDatabases()) { */
634/* Sink::Storage::DataStore storage(Sink::storageLocation(), mResourceInstanceIdentifier); */
635/* transaction = storage.createTransaction(Sink::Storage::DataStore::ReadOnly); */
636/* } */
637/* return transaction; */
638/* } */
639
640Sink::Log::Context EntityStore::logContext() const 689Sink::Log::Context EntityStore::logContext() const
641{ 690{
642 return d->logCtx; 691 return d->logCtx;
diff --git a/common/storage_common.cpp b/common/storage_common.cpp
index 81a38c7..8603787 100644
--- a/common/storage_common.cpp
+++ b/common/storage_common.cpp
@@ -24,8 +24,6 @@
24#include "log.h" 24#include "log.h"
25#include <QUuid> 25#include <QUuid>
26 26
27SINK_DEBUG_AREA("storage")
28
29QDebug& operator<<(QDebug &dbg, const Sink::Storage::DataStore::Error &error) 27QDebug& operator<<(QDebug &dbg, const Sink::Storage::DataStore::Error &error)
30{ 28{
31 dbg << error.message << "Code: " << error.code << "Db: " << error.store; 29 dbg << error.message << "Code: " << error.code << "Db: " << error.store;
@@ -38,6 +36,18 @@ namespace Storage {
38static const char *s_internalPrefix = "__internal"; 36static const char *s_internalPrefix = "__internal";
39static const int s_internalPrefixSize = strlen(s_internalPrefix); 37static const int s_internalPrefixSize = strlen(s_internalPrefix);
40 38
39DbLayout::DbLayout()
40{
41
42}
43
44DbLayout::DbLayout(const QByteArray &n, const Databases &t)
45 : name(n),
46 tables(t)
47{
48
49}
50
41void errorHandler(const DataStore::Error &error) 51void errorHandler(const DataStore::Error &error)
42{ 52{
43 if (error.code == DataStore::TransactionError) { 53 if (error.code == DataStore::TransactionError) {
@@ -113,7 +123,7 @@ QByteArray DataStore::getUidFromRevision(const DataStore::Transaction &transacti
113 transaction.openDatabase("revisions") 123 transaction.openDatabase("revisions")
114 .scan(QByteArray::number(revision), 124 .scan(QByteArray::number(revision),
115 [&](const QByteArray &, const QByteArray &value) -> bool { 125 [&](const QByteArray &, const QByteArray &value) -> bool {
116 uid = value; 126 uid = QByteArray{value.constData(), value.size()};
117 return false; 127 return false;
118 }, 128 },
119 [revision](const Error &error) { SinkWarning() << "Couldn't find uid for revision: " << revision << error.message; }); 129 [revision](const Error &error) { SinkWarning() << "Couldn't find uid for revision: " << revision << error.message; });
@@ -126,7 +136,7 @@ QByteArray DataStore::getTypeFromRevision(const DataStore::Transaction &transact
126 transaction.openDatabase("revisionType") 136 transaction.openDatabase("revisionType")
127 .scan(QByteArray::number(revision), 137 .scan(QByteArray::number(revision),
128 [&](const QByteArray &, const QByteArray &value) -> bool { 138 [&](const QByteArray &, const QByteArray &value) -> bool {
129 type = value; 139 type = QByteArray{value.constData(), value.size()};
130 return false; 140 return false;
131 }, 141 },
132 [revision](const Error &error) { SinkWarning() << "Couldn't find type for revision " << revision; }); 142 [revision](const Error &error) { SinkWarning() << "Couldn't find type for revision " << revision; });
@@ -207,6 +217,11 @@ QByteArray DataStore::generateUid()
207 217
208DataStore::NamedDatabase DataStore::mainDatabase(const DataStore::Transaction &t, const QByteArray &type) 218DataStore::NamedDatabase DataStore::mainDatabase(const DataStore::Transaction &t, const QByteArray &type)
209{ 219{
220 if (type.isEmpty()) {
221 SinkError() << "Tried to open main database for empty type.";
222 Q_ASSERT(false);
223 return {};
224 }
210 return t.openDatabase(type + ".main"); 225 return t.openDatabase(type + ".main");
211} 226}
212 227
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp
index 08eea37..f7999d1 100644
--- a/common/storage_lmdb.cpp
+++ b/common/storage_lmdb.cpp
@@ -35,9 +35,6 @@
35#include <lmdb.h> 35#include <lmdb.h>
36#include "log.h" 36#include "log.h"
37 37
38SINK_DEBUG_AREA("storage")
39// SINK_DEBUG_COMPONENT(d->storageRoot.toLatin1() + '/' + d->name.toLatin1())
40
41namespace Sink { 38namespace Sink {
42namespace Storage { 39namespace Storage {
43 40
@@ -169,6 +166,27 @@ public:
169 if (const int rc = mdb_dbi_open(transaction, db.constData(), flags, &dbi)) { 166 if (const int rc = mdb_dbi_open(transaction, db.constData(), flags, &dbi)) {
170 //Create the db if it is not existing already 167 //Create the db if it is not existing already
171 if (rc == MDB_NOTFOUND && !readOnly) { 168 if (rc == MDB_NOTFOUND && !readOnly) {
169 //Sanity check db name
170 {
171 auto parts = db.split('.');
172 for (const auto &p : parts) {
173 auto containsSpecialCharacter = [] (const QByteArray &p) {
174 for (int i = 0; i < p.size(); i++) {
175 const auto c = p.at(i);
176 //Between 0 and z in the ascii table. Essentially ensures that the name is printable and doesn't contain special chars
177 if (c < 0x30 || c > 0x7A) {
178 return true;
179 }
180 }
181 return false;
182 };
183 if (p.isEmpty() || containsSpecialCharacter(p)) {
184 SinkError() << "Tried to create a db with an invalid name. Hex:" << db.toHex() << " ASCII:" << db;
185 Q_ASSERT(false);
186 throw std::runtime_error("Fatal error while creating db.");
187 }
188 }
189 }
172 if (const int rc = mdb_dbi_open(transaction, db.constData(), flags | MDB_CREATE, &dbi)) { 190 if (const int rc = mdb_dbi_open(transaction, db.constData(), flags | MDB_CREATE, &dbi)) {
173 SinkWarning() << "Failed to create db " << QByteArray(mdb_strerror(rc)); 191 SinkWarning() << "Failed to create db " << QByteArray(mdb_strerror(rc));
174 Error error(name.toLatin1(), ErrorCodes::GenericError, "Error while creating database: " + QByteArray(mdb_strerror(rc))); 192 Error error(name.toLatin1(), ErrorCodes::GenericError, "Error while creating database: " + QByteArray(mdb_strerror(rc)));
@@ -542,6 +560,7 @@ DataStore::Transaction::Transaction(Transaction &&other) : d(nullptr)
542DataStore::Transaction &DataStore::Transaction::operator=(DataStore::Transaction &&other) 560DataStore::Transaction &DataStore::Transaction::operator=(DataStore::Transaction &&other)
543{ 561{
544 if (&other != this) { 562 if (&other != this) {
563 abort();
545 delete d; 564 delete d;
546 d = other.d; 565 d = other.d;
547 other.d = nullptr; 566 other.d = nullptr;
@@ -639,11 +658,6 @@ static bool ensureCorrectDb(DataStore::NamedDatabase &database, const QByteArray
639 return !openedTheWrongDatabase; 658 return !openedTheWrongDatabase;
640} 659}
641 660
642bool DataStore::Transaction::validateNamedDatabases()
643{
644 return true;
645}
646
647DataStore::NamedDatabase DataStore::Transaction::openDatabase(const QByteArray &db, const std::function<void(const DataStore::Error &error)> &errorHandler, bool allowDuplicates) const 661DataStore::NamedDatabase DataStore::Transaction::openDatabase(const QByteArray &db, const std::function<void(const DataStore::Error &error)> &errorHandler, bool allowDuplicates) const
648{ 662{
649 if (!d) { 663 if (!d) {
@@ -694,7 +708,7 @@ QList<QByteArray> DataStore::Transaction::getDatabaseNames() const
694class DataStore::Private 708class DataStore::Private
695{ 709{
696public: 710public:
697 Private(const QString &s, const QString &n, AccessMode m); 711 Private(const QString &s, const QString &n, AccessMode m, const DbLayout &layout = {});
698 ~Private(); 712 ~Private();
699 713
700 QString storageRoot; 714 QString storageRoot;
@@ -702,68 +716,85 @@ public:
702 716
703 MDB_env *env; 717 MDB_env *env;
704 AccessMode mode; 718 AccessMode mode;
719 Sink::Log::Context logCtx;
720
721 void initEnvironment(const QString &fullPath, const DbLayout &layout)
722 {
723 // Ensure the environment is only created once, and that we only have one environment per process
724 if (!(env = sEnvironments.value(fullPath))) {
725 QMutexLocker locker(&sMutex);
726 if (!(env = sEnvironments.value(fullPath))) {
727 int rc = 0;
728 if ((rc = mdb_env_create(&env))) {
729 SinkWarningCtx(logCtx) << "mdb_env_create: " << rc << " " << mdb_strerror(rc);
730 qCritical() << "mdb_env_create: " << rc << " " << mdb_strerror(rc);
731 } else {
732 //Limit large enough to accomodate all our named dbs. This only starts to matter if the number gets large, otherwise it's just a bunch of extra entries in the main table.
733 mdb_env_set_maxdbs(env, 50);
734 const bool readOnly = (mode == ReadOnly);
735 unsigned int flags = MDB_NOTLS;
736 if (readOnly) {
737 flags |= MDB_RDONLY;
738 }
739 if ((rc = mdb_env_open(env, fullPath.toStdString().data(), flags, 0664))) {
740 SinkWarningCtx(logCtx) << "mdb_env_open: " << rc << ":" << mdb_strerror(rc);
741 mdb_env_close(env);
742 env = 0;
743 } else {
744 if (RUNNING_ON_VALGRIND) {
745 // In order to run valgrind this size must be smaller than half your available RAM
746 // https://github.com/BVLC/caffe/issues/2404
747 mdb_env_set_mapsize(env, (size_t)10485760 * (size_t)1000); // 1MB * 1000
748 } else {
749 //This is the maximum size of the db (but will not be used directly), so we make it large enough that we hopefully never run into the limit.
750 mdb_env_set_mapsize(env, (size_t)10485760 * (size_t)100000); // 1MB * 1000
751 }
752 Q_ASSERT(env);
753 sEnvironments.insert(fullPath, env);
754 //Open all available dbi's
755 bool noLock = true;
756 auto t = Transaction(new Transaction::Private(readOnly, nullptr, name, env, noLock));
757 if (!layout.tables.isEmpty()) {
758
759 //TODO upgrade db if the layout has changed:
760 //* read existing layout
761 //* if layout is not the same create new layout
762 //If the db is read only, abort if the db is not yet existing.
763 //If the db is not read-only but is not existing, ensure we have a layout and create all tables.
764
765 for (auto it = layout.tables.constBegin(); it != layout.tables.constEnd(); it++) {
766 bool allowDuplicates = it.value();
767 t.openDatabase(it.key(), {}, allowDuplicates);
768 }
769 } else {
770 for (const auto &db : t.getDatabaseNames()) {
771 //Get dbi to store for future use.
772 t.openDatabase(db);
773 }
774 }
775 //To persist the dbis (this is also necessary for read-only transactions)
776 t.commit();
777 }
778 }
779 }
780 }
781 }
782
705}; 783};
706 784
707DataStore::Private::Private(const QString &s, const QString &n, AccessMode m) : storageRoot(s), name(n), env(0), mode(m) 785DataStore::Private::Private(const QString &s, const QString &n, AccessMode m, const DbLayout &layout) : storageRoot(s), name(n), env(0), mode(m), logCtx(n.toLatin1())
708{ 786{
787
709 const QString fullPath(storageRoot + '/' + name); 788 const QString fullPath(storageRoot + '/' + name);
710 QFileInfo dirInfo(fullPath); 789 QFileInfo dirInfo(fullPath);
711 if (!dirInfo.exists() && mode == ReadWrite) { 790 if (!dirInfo.exists() && mode == ReadWrite) {
712 QDir().mkpath(fullPath); 791 QDir().mkpath(fullPath);
713 dirInfo.refresh(); 792 dirInfo.refresh();
714 } 793 }
715 Sink::Log::Context logCtx{n.toLatin1()};
716 if (mode == ReadWrite && !dirInfo.permission(QFile::WriteOwner)) { 794 if (mode == ReadWrite && !dirInfo.permission(QFile::WriteOwner)) {
717 qCritical() << fullPath << "does not have write permissions. Aborting"; 795 qCritical() << fullPath << "does not have write permissions. Aborting";
718 } else if (dirInfo.exists()) { 796 } else if (dirInfo.exists()) {
719 // Ensure the environment is only created once 797 initEnvironment(fullPath, layout);
720 QMutexLocker locker(&sMutex);
721
722 /*
723 * It seems we can only ever have one environment open in the process.
724 * Otherwise multi-threading breaks.
725 */
726 env = sEnvironments.value(fullPath);
727 if (!env) {
728 int rc = 0;
729 if ((rc = mdb_env_create(&env))) {
730 // TODO: handle error
731 SinkWarningCtx(logCtx) << "mdb_env_create: " << rc << " " << mdb_strerror(rc);
732 } else {
733 mdb_env_set_maxdbs(env, 50);
734 unsigned int flags = MDB_NOTLS;
735 if (mode == ReadOnly) {
736 flags |= MDB_RDONLY;
737 }
738 if ((rc = mdb_env_open(env, fullPath.toStdString().data(), flags, 0664))) {
739 SinkWarningCtx(logCtx) << "mdb_env_open: " << rc << ":" << mdb_strerror(rc);
740 mdb_env_close(env);
741 env = 0;
742 } else {
743 if (RUNNING_ON_VALGRIND) {
744 // In order to run valgrind this size must be smaller than half your available RAM
745 // https://github.com/BVLC/caffe/issues/2404
746 const size_t dbSize = (size_t)10485760 * (size_t)1000; // 1MB * 1000
747 mdb_env_set_mapsize(env, dbSize);
748 } else {
749 // FIXME: dynamic resize
750 const size_t dbSize = (size_t)10485760 * (size_t)8000; // 1MB * 8000
751 mdb_env_set_mapsize(env, dbSize);
752 }
753 sEnvironments.insert(fullPath, env);
754 //Open all available dbi's
755 bool noLock = true;
756 bool requestedRead = m == ReadOnly;
757 auto t = Transaction(new Transaction::Private(requestedRead, nullptr, name, env, noLock));
758 for (const auto &db : t.getDatabaseNames()) {
759 //Get dbi to store for future use.
760 t.openDatabase(db);
761 }
762 //To persist the dbis (this is also necessary for read-only transactions)
763 t.commit();
764 }
765 }
766 }
767 } 798 }
768} 799}
769 800
@@ -777,6 +808,10 @@ DataStore::DataStore(const QString &storageRoot, const QString &name, AccessMode
777{ 808{
778} 809}
779 810
811DataStore::DataStore(const QString &storageRoot, const DbLayout &dbLayout, AccessMode mode) : d(new Private(storageRoot, dbLayout.name, mode, dbLayout))
812{
813}
814
780DataStore::~DataStore() 815DataStore::~DataStore()
781{ 816{
782 delete d; 817 delete d;
diff --git a/common/store.cpp b/common/store.cpp
index d266098..4735113 100644
--- a/common/store.cpp
+++ b/common/store.cpp
@@ -36,8 +36,6 @@
36#include "storage.h" 36#include "storage.h"
37#include "log.h" 37#include "log.h"
38 38
39SINK_DEBUG_AREA("store")
40
41Q_DECLARE_METATYPE(QSharedPointer<Sink::ResultEmitter<Sink::ApplicationDomain::SinkResource::Ptr>>) 39Q_DECLARE_METATYPE(QSharedPointer<Sink::ResultEmitter<Sink::ApplicationDomain::SinkResource::Ptr>>)
42Q_DECLARE_METATYPE(QSharedPointer<Sink::ResourceAccessInterface>); 40Q_DECLARE_METATYPE(QSharedPointer<Sink::ResourceAccessInterface>);
43Q_DECLARE_METATYPE(std::shared_ptr<void>); 41Q_DECLARE_METATYPE(std::shared_ptr<void>);
@@ -277,6 +275,19 @@ KAsync::Job<void> Store::removeDataFromDisk(const QByteArray &identifier)
277 }); 275 });
278} 276}
279 277
278KAsync::Job<void> Store::upgrade()
279{
280 SinkLog() << "Upgrading...";
281 return fetchAll<ApplicationDomain::SinkResource>({})
282 .template each([](const ApplicationDomain::SinkResource::Ptr &resource) -> KAsync::Job<void> {
283 SinkLog() << "Removing caches for " << resource->identifier();
284 return removeDataFromDisk(resource->identifier());
285 })
286 .then([] {
287 SinkLog() << "Upgrade complete.";
288 });
289}
290
280static KAsync::Job<void> synchronize(const QByteArray &resource, const Sink::SyncScope &scope) 291static KAsync::Job<void> synchronize(const QByteArray &resource, const Sink::SyncScope &scope)
281{ 292{
282 SinkLog() << "Synchronizing " << resource << scope; 293 SinkLog() << "Synchronizing " << resource << scope;
diff --git a/common/store.h b/common/store.h
index fae76e5..34e14df 100644
--- a/common/store.h
+++ b/common/store.h
@@ -122,6 +122,18 @@ KAsync::Job<void> SINK_EXPORT synchronize(const Sink::SyncScope &query);
122 */ 122 */
123KAsync::Job<void> SINK_EXPORT removeDataFromDisk(const QByteArray &resourceIdentifier); 123KAsync::Job<void> SINK_EXPORT removeDataFromDisk(const QByteArray &resourceIdentifier);
124 124
125/**
126 * Run upgrade jobs.
127 *
128 * Run this to upgrade your local database to a new version.
129 * Note that this may:
130 * * take a while
131 * * remove some/all of your local caches
132 *
133 * Note: The initial implementation simply calls removeDataFromDisk for all resources.
134 */
135KAsync::Job<void> SINK_EXPORT upgrade();
136
125template <class DomainType> 137template <class DomainType>
126KAsync::Job<DomainType> SINK_EXPORT fetchOne(const Sink::Query &query); 138KAsync::Job<DomainType> SINK_EXPORT fetchOne(const Sink::Query &query);
127 139
diff --git a/common/synchronizer.cpp b/common/synchronizer.cpp
index 3e7bd30..3b32e68 100644
--- a/common/synchronizer.cpp
+++ b/common/synchronizer.cpp
@@ -304,9 +304,29 @@ void Synchronizer::emitNotification(Notification::NoticationType type, int code,
304 emit notify(n); 304 emit notify(n);
305} 305}
306 306
307void Synchronizer::reportProgress(int progress, int total) 307void Synchronizer::emitProgressNotification(Notification::NoticationType type, int progress, int total, const QByteArray &id, const QByteArrayList &entities)
308{ 308{
309 SinkLogCtx(mLogCtx) << "Progress: " << progress << " out of " << total; 309 Sink::Notification n;
310 n.id = id;
311 n.type = type;
312 n.progress = progress;
313 n.total = total;
314 n.entities = entities;
315 emit notify(n);
316}
317
318void Synchronizer::reportProgress(int progress, int total, const QByteArrayList &entities)
319{
320 if (progress > 0 && total > 0) {
321 SinkLogCtx(mLogCtx) << "Progress: " << progress << " out of " << total << mCurrentRequest.requestId << mCurrentRequest.applicableEntities;
322 const auto applicableEntities = [&] {
323 if (entities.isEmpty()) {
324 return mCurrentRequest.applicableEntities;
325 }
326 return entities;
327 }();
328 emitProgressNotification(Notification::Progress, progress, total, mCurrentRequest.requestId, applicableEntities);
329 }
310} 330}
311 331
312void Synchronizer::setStatusFromResult(const KAsync::Error &error, const QString &s, const QByteArray &requestId) 332void Synchronizer::setStatusFromResult(const KAsync::Error &error, const QString &s, const QByteArray &requestId)
@@ -315,6 +335,9 @@ void Synchronizer::setStatusFromResult(const KAsync::Error &error, const QString
315 if (error.errorCode == ApplicationDomain::ConnectionError) { 335 if (error.errorCode == ApplicationDomain::ConnectionError) {
316 //Couldn't connect, so we assume we don't have a network connection. 336 //Couldn't connect, so we assume we don't have a network connection.
317 setStatus(ApplicationDomain::OfflineStatus, s, requestId); 337 setStatus(ApplicationDomain::OfflineStatus, s, requestId);
338 } else if (error.errorCode == ApplicationDomain::NoServerError) {
339 //Failed to contact the server.
340 setStatus(ApplicationDomain::OfflineStatus, s, requestId);
318 } else if (error.errorCode == ApplicationDomain::ConfigurationError) { 341 } else if (error.errorCode == ApplicationDomain::ConfigurationError) {
319 //There is an error with the configuration. 342 //There is an error with the configuration.
320 setStatus(ApplicationDomain::ErrorStatus, s, requestId); 343 setStatus(ApplicationDomain::ErrorStatus, s, requestId);
@@ -354,6 +377,7 @@ KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request)
354 } else if (request.requestType == Synchronizer::SyncRequest::Synchronization) { 377 } else if (request.requestType == Synchronizer::SyncRequest::Synchronization) {
355 return KAsync::start([this, request] { 378 return KAsync::start([this, request] {
356 SinkLogCtx(mLogCtx) << "Synchronizing: " << request.query; 379 SinkLogCtx(mLogCtx) << "Synchronizing: " << request.query;
380 setBusy(true, "Synchronization has started.", request.requestId);
357 emitNotification(Notification::Info, ApplicationDomain::SyncInProgress, {}, {}, request.applicableEntities); 381 emitNotification(Notification::Info, ApplicationDomain::SyncInProgress, {}, {}, request.applicableEntities);
358 }).then(synchronizeWithSource(request.query)).then([this] { 382 }).then(synchronizeWithSource(request.query)).then([this] {
359 //Commit after every request, so implementations only have to commit more if they add a lot of data. 383 //Commit after every request, so implementations only have to commit more if they add a lot of data.
@@ -391,11 +415,12 @@ KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request)
391 return KAsync::null(); 415 return KAsync::null();
392 } else { 416 } else {
393 return KAsync::start([this, request] { 417 return KAsync::start([this, request] {
418 setBusy(true, "ChangeReplay has started.", request.requestId);
394 SinkLogCtx(mLogCtx) << "Replaying changes."; 419 SinkLogCtx(mLogCtx) << "Replaying changes.";
395 }) 420 })
396 .then(replayNextRevision()) 421 .then(replayNextRevision())
397 .then<void>([this, request](const KAsync::Error &error) { 422 .then<void>([this, request](const KAsync::Error &error) {
398 setStatusFromResult(error, "Changereplay has ended.", "changereplay"); 423 setStatusFromResult(error, "Changereplay has ended.", request.requestId);
399 if (error) { 424 if (error) {
400 SinkWarningCtx(mLogCtx) << "Changereplay failed: " << error.errorMessage; 425 SinkWarningCtx(mLogCtx) << "Changereplay failed: " << error.errorMessage;
401 return KAsync::error(error); 426 return KAsync::error(error);
@@ -462,16 +487,13 @@ KAsync::Job<void> Synchronizer::processSyncQueue()
462 mMessageQueue->startTransaction(); 487 mMessageQueue->startTransaction();
463 mEntityStore->startTransaction(Sink::Storage::DataStore::ReadOnly); 488 mEntityStore->startTransaction(Sink::Storage::DataStore::ReadOnly);
464 mSyncInProgress = true; 489 mSyncInProgress = true;
465 if (request.requestType == Synchronizer::SyncRequest::Synchronization) { 490 mCurrentRequest = request;
466 setBusy(true, "Synchronization has started.", request.requestId);
467 } else if (request.requestType == Synchronizer::SyncRequest::ChangeReplay) {
468 setBusy(true, "ChangeReplay has started.", "changereplay");
469 }
470 }) 491 })
471 .then(processRequest(request)) 492 .then(processRequest(request))
472 .then<void>([this, request](const KAsync::Error &error) { 493 .then<void>([this, request](const KAsync::Error &error) {
473 SinkTraceCtx(mLogCtx) << "Sync request processed"; 494 SinkTraceCtx(mLogCtx) << "Sync request processed";
474 setBusy(false, {}, request.requestId); 495 setBusy(false, {}, request.requestId);
496 mCurrentRequest = {};
475 mEntityStore->abortTransaction(); 497 mEntityStore->abortTransaction();
476 mSyncTransaction.abort(); 498 mSyncTransaction.abort();
477 mMessageQueue->commit(); 499 mMessageQueue->commit();
@@ -516,7 +538,7 @@ void Synchronizer::revisionChanged()
516 return; 538 return;
517 } 539 }
518 } 540 }
519 mSyncRequestQueue << Synchronizer::SyncRequest{Synchronizer::SyncRequest::ChangeReplay}; 541 mSyncRequestQueue << Synchronizer::SyncRequest{Synchronizer::SyncRequest::ChangeReplay, "changereplay"};
520 processSyncQueue().exec(); 542 processSyncQueue().exec();
521} 543}
522 544
@@ -607,11 +629,14 @@ KAsync::Job<void> Synchronizer::replay(const QByteArray &type, const QByteArray
607 } 629 }
608 }) 630 })
609 .then([this](const KAsync::Error &error) { 631 .then([this](const KAsync::Error &error) {
632 //We need to commit here otherwise the next change-replay step will abort the transaction
633 mSyncStore.clear();
634 mSyncTransaction.commit();
610 if (error) { 635 if (error) {
611 SinkWarningCtx(mLogCtx) << "Failed to replay change: " << error.errorMessage; 636 SinkWarningCtx(mLogCtx) << "Failed to replay change: " << error.errorMessage;
637 return KAsync::error(error);
612 } 638 }
613 mSyncStore.clear(); 639 return KAsync::null();
614 mSyncTransaction.commit();
615 }); 640 });
616} 641}
617 642
diff --git a/common/synchronizer.h b/common/synchronizer.h
index b1ee122..cc082be 100644
--- a/common/synchronizer.h
+++ b/common/synchronizer.h
@@ -131,6 +131,8 @@ protected:
131 RequestFlush 131 RequestFlush
132 }; 132 };
133 133
134 SyncRequest() = default;
135
134 SyncRequest(const Sink::QueryBase &q, const QByteArray &requestId_ = QByteArray(), RequestOptions o = NoOptions) 136 SyncRequest(const Sink::QueryBase &q, const QByteArray &requestId_ = QByteArray(), RequestOptions o = NoOptions)
135 : requestId(requestId_), 137 : requestId(requestId_),
136 requestType(Synchronization), 138 requestType(Synchronization),
@@ -145,6 +147,12 @@ protected:
145 { 147 {
146 } 148 }
147 149
150 SyncRequest(RequestType type, const QByteArray &requestId_)
151 : requestId(requestId_),
152 requestType(type)
153 {
154 }
155
148 SyncRequest(RequestType type, int flushType_, const QByteArray &requestId_) 156 SyncRequest(RequestType type, int flushType_, const QByteArray &requestId_)
149 : flushType(flushType_), 157 : flushType(flushType_),
150 requestId(requestId_), 158 requestId(requestId_),
@@ -184,11 +192,12 @@ protected:
184 virtual void mergeIntoQueue(const Synchronizer::SyncRequest &request, QList<Synchronizer::SyncRequest> &queue); 192 virtual void mergeIntoQueue(const Synchronizer::SyncRequest &request, QList<Synchronizer::SyncRequest> &queue);
185 193
186 void emitNotification(Notification::NoticationType type, int code, const QString &message, const QByteArray &id = QByteArray{}, const QByteArrayList &entiteis = QByteArrayList{}); 194 void emitNotification(Notification::NoticationType type, int code, const QString &message, const QByteArray &id = QByteArray{}, const QByteArrayList &entiteis = QByteArrayList{});
195 void emitProgressNotification(Notification::NoticationType type, int progress, int total, const QByteArray &id, const QByteArrayList &entities);
187 196
188 /** 197 /**
189 * Report progress for current task 198 * Report progress for current task
190 */ 199 */
191 void reportProgress(int progress, int total); 200 virtual void reportProgress(int progress, int total, const QByteArrayList &entities = {}) Q_DECL_OVERRIDE;
192 201
193protected: 202protected:
194 Sink::Log::Context mLogCtx; 203 Sink::Log::Context mLogCtx;
@@ -211,6 +220,7 @@ private:
211 Sink::Storage::DataStore::Transaction mSyncTransaction; 220 Sink::Storage::DataStore::Transaction mSyncTransaction;
212 std::function<void(int commandId, const QByteArray &data)> mEnqueue; 221 std::function<void(int commandId, const QByteArray &data)> mEnqueue;
213 QList<SyncRequest> mSyncRequestQueue; 222 QList<SyncRequest> mSyncRequestQueue;
223 SyncRequest mCurrentRequest;
214 MessageQueue *mMessageQueue; 224 MessageQueue *mMessageQueue;
215 bool mSyncInProgress; 225 bool mSyncInProgress;
216 QMultiHash<QByteArray, SyncRequest> mPendingSyncRequests; 226 QMultiHash<QByteArray, SyncRequest> mPendingSyncRequests;
diff --git a/common/synchronizerstore.cpp b/common/synchronizerstore.cpp
index 5364094..79cd920 100644
--- a/common/synchronizerstore.cpp
+++ b/common/synchronizerstore.cpp
@@ -25,8 +25,6 @@
25 25
26using namespace Sink; 26using namespace Sink;
27 27
28SINK_DEBUG_AREA("synchronizerstore")
29
30SynchronizerStore::SynchronizerStore(Sink::Storage::DataStore::Transaction &transaction) 28SynchronizerStore::SynchronizerStore(Sink::Storage::DataStore::Transaction &transaction)
31 : mTransaction(transaction) 29 : mTransaction(transaction)
32{ 30{
diff --git a/common/test.cpp b/common/test.cpp
index 90586ba..237d3bb 100644
--- a/common/test.cpp
+++ b/common/test.cpp
@@ -29,8 +29,6 @@
29#include "resourceconfig.h" 29#include "resourceconfig.h"
30#include "definitions.h" 30#include "definitions.h"
31 31
32SINK_DEBUG_AREA("test")
33
34using namespace Sink; 32using namespace Sink;
35 33
36void Sink::Test::initTest() 34void Sink::Test::initTest()
@@ -83,6 +81,7 @@ void Sink::Test::initTest()
83void Sink::Test::setTestModeEnabled(bool enabled) 81void Sink::Test::setTestModeEnabled(bool enabled)
84{ 82{
85 QStandardPaths::setTestModeEnabled(enabled); 83 QStandardPaths::setTestModeEnabled(enabled);
84 Sink::clearLocationCache();
86 if (enabled) { 85 if (enabled) {
87 qputenv("SINK_TESTMODE", "TRUE"); 86 qputenv("SINK_TESTMODE", "TRUE");
88 } else { 87 } else {
diff --git a/common/typeindex.cpp b/common/typeindex.cpp
index 153aa43..5a19839 100644
--- a/common/typeindex.cpp
+++ b/common/typeindex.cpp
@@ -21,18 +21,20 @@
21#include "log.h" 21#include "log.h"
22#include "index.h" 22#include "index.h"
23#include <QDateTime> 23#include <QDateTime>
24 24#include <QDataStream>
25SINK_DEBUG_AREA("typeindex")
26 25
27using namespace Sink; 26using namespace Sink;
28 27
29static QByteArray getByteArray(const QVariant &value) 28static QByteArray getByteArray(const QVariant &value)
30{ 29{
31 if (value.type() == QVariant::DateTime) { 30 if (value.type() == QVariant::DateTime) {
32 const auto result = value.toDateTime().toString().toLatin1(); 31 QByteArray result;
33 if (result.isEmpty()) { 32 QDataStream ds(&result, QIODevice::WriteOnly);
34 return "nodate"; 33 ds << value.toDateTime();
35 } 34 return result;
35 }
36 if (value.type() == QVariant::Bool) {
37 return value.toBool() ? "t" : "f";
36 } 38 }
37 if (value.canConvert<Sink::ApplicationDomain::Reference>()) { 39 if (value.canConvert<Sink::ApplicationDomain::Reference>()) {
38 const auto ba = value.value<Sink::ApplicationDomain::Reference>().value; 40 const auto ba = value.value<Sink::ApplicationDomain::Reference>().value;
@@ -85,6 +87,20 @@ void TypeIndex::addProperty<QByteArray>(const QByteArray &property)
85} 87}
86 88
87template <> 89template <>
90void TypeIndex::addProperty<bool>(const QByteArray &property)
91{
92 auto indexer = [this, property](bool add, const QByteArray &identifier, const QVariant &value, Sink::Storage::DataStore::Transaction &transaction) {
93 if (add) {
94 Index(indexName(property), transaction).add(getByteArray(value), identifier);
95 } else {
96 Index(indexName(property), transaction).remove(getByteArray(value), identifier);
97 }
98 };
99 mIndexer.insert(property, indexer);
100 mProperties << property;
101}
102
103template <>
88void TypeIndex::addProperty<QString>(const QByteArray &property) 104void TypeIndex::addProperty<QString>(const QByteArray &property)
89{ 105{
90 auto indexer = [this, property](bool add, const QByteArray &identifier, const QVariant &value, Sink::Storage::DataStore::Transaction &transaction) { 106 auto indexer = [this, property](bool add, const QByteArray &identifier, const QVariant &value, Sink::Storage::DataStore::Transaction &transaction) {
@@ -266,6 +282,18 @@ void TypeIndex::index<QString, QByteArray>(const QByteArray &leftName, const QBy
266} 282}
267 283
268template <> 284template <>
285void TypeIndex::unindex<QByteArray, QByteArray>(const QByteArray &leftName, const QByteArray &rightName, const QVariant &leftValue, const QVariant &rightValue, Sink::Storage::DataStore::Transaction &transaction)
286{
287 Index(indexName(leftName + rightName), transaction).remove(getByteArray(leftValue), getByteArray(rightValue));
288}
289
290template <>
291void TypeIndex::unindex<QString, QByteArray>(const QByteArray &leftName, const QByteArray &rightName, const QVariant &leftValue, const QVariant &rightValue, Sink::Storage::DataStore::Transaction &transaction)
292{
293 Index(indexName(leftName + rightName), transaction).remove(getByteArray(leftValue), getByteArray(rightValue));
294}
295
296template <>
269QVector<QByteArray> TypeIndex::secondaryLookup<QByteArray>(const QByteArray &leftName, const QByteArray &rightName, const QVariant &value) 297QVector<QByteArray> TypeIndex::secondaryLookup<QByteArray>(const QByteArray &leftName, const QByteArray &rightName, const QVariant &value)
270{ 298{
271 QVector<QByteArray> keys; 299 QVector<QByteArray> keys;
diff --git a/common/typeindex.h b/common/typeindex.h
index 1f216a7..890c3db 100644
--- a/common/typeindex.h
+++ b/common/typeindex.h
@@ -95,6 +95,15 @@ public:
95 template <typename LeftType, typename RightType> 95 template <typename LeftType, typename RightType>
96 void index(const QByteArray &leftName, const QByteArray &rightName, const QVariant &leftValue, const QVariant &rightValue, Sink::Storage::DataStore::Transaction &transaction); 96 void index(const QByteArray &leftName, const QByteArray &rightName, const QVariant &leftValue, const QVariant &rightValue, Sink::Storage::DataStore::Transaction &transaction);
97 97
98 template <typename Left, typename Right>
99 void unindex(const QVariant &leftValue, const QVariant &rightValue, Sink::Storage::DataStore::Transaction &transaction)
100 {
101 index<typename Left::Type, typename Right::Type>(Left::name, Right::name, leftValue, rightValue, transaction);
102 }
103
104 template <typename LeftType, typename RightType>
105 void unindex(const QByteArray &leftName, const QByteArray &rightName, const QVariant &leftValue, const QVariant &rightValue, Sink::Storage::DataStore::Transaction &transaction);
106
98 107
99private: 108private:
100 friend class Sink::Storage::EntityStore; 109 friend class Sink::Storage::EntityStore;
diff --git a/dist/sink.spec b/dist/sink.spec
index 60015f7..f5132b7 100644
--- a/dist/sink.spec
+++ b/dist/sink.spec
@@ -1,7 +1,7 @@
1 1
2Name: sink 2Name: sink
3Version: 0.2.0 3Version: 0.3
4Release: 0%{?dist} 4Release: 10%{?dist}
5Summary: sink 5Summary: sink
6 6
7Group: Applications/Desktop 7Group: Applications/Desktop
@@ -18,6 +18,7 @@ BuildRequires: kf5-kcoreaddons-devel
18BuildRequires: kf5-kcontacts-devel 18BuildRequires: kf5-kcontacts-devel
19BuildRequires: kf5-kmime-devel 19BuildRequires: kf5-kmime-devel
20BuildRequires: kimap2-devel 20BuildRequires: kimap2-devel
21BuildRequires: kdav2-devel
21BuildRequires: libcurl-devel 22BuildRequires: libcurl-devel
22BuildRequires: libgit2-devel 23BuildRequires: libgit2-devel
23BuildRequires: lmdb-devel 24BuildRequires: lmdb-devel
@@ -37,17 +38,11 @@ Development headers for sink
37%prep 38%prep
38%setup -q 39%setup -q
39 40
40sed -i \
41 -e '/inspectiontest/d' \
42 -e '/maildirresourcetest/d' \
43 tests/CMakeLists.txt
44
45%build 41%build
46mkdir -p build/ 42mkdir -p build/
47pushd build 43pushd build
48%{cmake} \ 44%{cmake} \
49 -DQT_PLUGIN_INSTALL_DIR:PATH=%{_libdir}/qt5/plugins/ \ 45 -DQT_PLUGIN_INSTALL_DIR:PATH=%{_libdir}/qt5/plugins/ \
50 -DBUILD_DAV=OFF \
51 .. 46 ..
52 47
53make %{?_smp_mflags} 48make %{?_smp_mflags}
@@ -58,7 +53,6 @@ pushd build
58%make_install 53%make_install
59popd 54popd
60 55
61#rm -rf %{buildroot}%{_prefix}/mkspecs/modules/qt_KMime.pri
62rm %{buildroot}%{_prefix}/bin/resetmailbox.sh 56rm %{buildroot}%{_prefix}/bin/resetmailbox.sh
63rm %{buildroot}%{_prefix}/bin/populatemailbox.sh 57rm %{buildroot}%{_prefix}/bin/populatemailbox.sh
64rm %{buildroot}%{_prefix}/bin/sink_smtp_test 58rm %{buildroot}%{_prefix}/bin/sink_smtp_test
diff --git a/examples/davresource/CMakeLists.txt b/examples/davresource/CMakeLists.txt
index 28829d5..7091edc 100644
--- a/examples/davresource/CMakeLists.txt
+++ b/examples/davresource/CMakeLists.txt
@@ -3,10 +3,10 @@ project(sink_resource_dav)
3add_definitions(-DQT_PLUGIN) 3add_definitions(-DQT_PLUGIN)
4include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) 4include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
5 5
6find_package(KPimKDAV REQUIRED) 6find_package(KPimKDAV2 REQUIRED)
7 7
8add_library(${PROJECT_NAME} SHARED davresource.cpp) 8add_library(${PROJECT_NAME} SHARED davresource.cpp)
9qt5_use_modules(${PROJECT_NAME} Core Network) 9qt5_use_modules(${PROJECT_NAME} Core Network)
10target_link_libraries(${PROJECT_NAME} sink KPim::KDAV) 10target_link_libraries(${PROJECT_NAME} sink KPim::KDAV2)
11 11
12install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH}) 12install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH})
diff --git a/examples/davresource/davresource.cpp b/examples/davresource/davresource.cpp
index 50471ed..465220f 100644
--- a/examples/davresource/davresource.cpp
+++ b/examples/davresource/davresource.cpp
@@ -31,19 +31,17 @@
31 31
32#include "contactpreprocessor.h" 32#include "contactpreprocessor.h"
33 33
34#include <KDAV/DavCollection> 34#include <KDAV2/DavCollection>
35#include <KDAV/DavCollectionsFetchJob> 35#include <KDAV2/DavCollectionsFetchJob>
36#include <KDAV/DavItem> 36#include <KDAV2/DavItem>
37#include <KDAV/DavItemsListJob> 37#include <KDAV2/DavItemsListJob>
38#include <KDAV/DavItemFetchJob> 38#include <KDAV2/DavItemFetchJob>
39#include <KDAV/EtagCache> 39#include <KDAV2/EtagCache>
40 40
41//This is the resources entity type, and not the domain type 41//This is the resources entity type, and not the domain type
42#define ENTITY_TYPE_CONTACT "contact" 42#define ENTITY_TYPE_CONTACT "contact"
43#define ENTITY_TYPE_ADDRESSBOOK "addressbook" 43#define ENTITY_TYPE_ADDRESSBOOK "addressbook"
44 44
45SINK_DEBUG_AREA("davresource")
46
47using namespace Sink; 45using namespace Sink;
48 46
49static KAsync::Job<void> runJob(KJob *job) 47static KAsync::Job<void> runJob(KJob *job)
@@ -87,7 +85,7 @@ public:
87 return remoteId; 85 return remoteId;
88 } 86 }
89 87
90 void synchronizeAddressbooks(const KDAV::DavCollection::List &addressbookList) 88 void synchronizeAddressbooks(const KDAV2::DavCollection::List &addressbookList)
91 { 89 {
92 const QByteArray bufferType = ENTITY_TYPE_ADDRESSBOOK; 90 const QByteArray bufferType = ENTITY_TYPE_ADDRESSBOOK;
93 SinkTrace() << "Found addressbooks " << addressbookList.size(); 91 SinkTrace() << "Found addressbooks " << addressbookList.size();
@@ -121,12 +119,12 @@ public:
121 return list; 119 return list;
122 } 120 }
123 121
124 static QByteArray getRid(const KDAV::DavItem &item) 122 static QByteArray getRid(const KDAV2::DavItem &item)
125 { 123 {
126 return item.url().toDisplayString().toUtf8(); 124 return item.url().toDisplayString().toUtf8();
127 } 125 }
128 126
129 static QByteArray getRid(const KDAV::DavCollection &item) 127 static QByteArray getRid(const KDAV2::DavCollection &item)
130 { 128 {
131 return item.url().toDisplayString().toUtf8(); 129 return item.url().toDisplayString().toUtf8();
132 } 130 }
@@ -135,7 +133,7 @@ public:
135 { 133 {
136 if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Addressbook>()) { 134 if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Addressbook>()) {
137 SinkLogCtx(mLogCtx) << "Synchronizing addressbooks:" << mResourceUrl.url(); 135 SinkLogCtx(mLogCtx) << "Synchronizing addressbooks:" << mResourceUrl.url();
138 auto collectionsFetchJob = new KDAV::DavCollectionsFetchJob(mResourceUrl); 136 auto collectionsFetchJob = new KDAV2::DavCollectionsFetchJob(mResourceUrl);
139 auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob] (const KAsync::Error &error) { 137 auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob] (const KAsync::Error &error) {
140 if (error) { 138 if (error) {
141 SinkWarningCtx(mLogCtx) << "Failed to synchronize addressbooks." << collectionsFetchJob->errorString(); 139 SinkWarningCtx(mLogCtx) << "Failed to synchronize addressbooks." << collectionsFetchJob->errorString();
@@ -147,29 +145,29 @@ public:
147 } else if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Contact>()) { 145 } else if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Contact>()) {
148 SinkLogCtx(mLogCtx) << "Synchronizing contacts."; 146 SinkLogCtx(mLogCtx) << "Synchronizing contacts.";
149 auto ridList = QSharedPointer<QByteArrayList>::create(); 147 auto ridList = QSharedPointer<QByteArrayList>::create();
150 auto collectionsFetchJob = new KDAV::DavCollectionsFetchJob(mResourceUrl); 148 auto collectionsFetchJob = new KDAV2::DavCollectionsFetchJob(mResourceUrl);
151 auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob] { 149 auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob] {
152 synchronizeAddressbooks(collectionsFetchJob ->collections()); 150 synchronizeAddressbooks(collectionsFetchJob ->collections());
153 return collectionsFetchJob->collections(); 151 return collectionsFetchJob->collections();
154 }) 152 })
155 .serialEach([this, ridList](const KDAV::DavCollection &collection) { 153 .serialEach([this, ridList](const KDAV2::DavCollection &collection) {
156 auto collId = getRid(collection); 154 auto collId = getRid(collection);
157 const auto addressbookLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, collId); 155 const auto addressbookLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, collId);
158 auto ctag = collection.CTag().toLatin1(); 156 auto ctag = collection.CTag().toLatin1();
159 if (ctag != syncStore().readValue(collId + "_ctagXX")) { 157 if (ctag != syncStore().readValue(collId + "_ctagXX")) {
160 SinkTraceCtx(mLogCtx) << "Syncing " << collId; 158 SinkTraceCtx(mLogCtx) << "Syncing " << collId;
161 auto cache = std::shared_ptr<KDAV::EtagCache>(new KDAV::EtagCache()); 159 auto cache = std::shared_ptr<KDAV2::EtagCache>(new KDAV2::EtagCache());
162 auto davItemsListJob = new KDAV::DavItemsListJob(collection.url(), cache); 160 auto davItemsListJob = new KDAV2::DavItemsListJob(collection.url(), cache);
163 const QByteArray bufferType = ENTITY_TYPE_CONTACT; 161 const QByteArray bufferType = ENTITY_TYPE_CONTACT;
164 QHash<QByteArray, Query::Comparator> mergeCriteria; 162 QHash<QByteArray, Query::Comparator> mergeCriteria;
165 auto colljob = runJob(davItemsListJob).then([davItemsListJob] { 163 auto colljob = runJob(davItemsListJob).then([davItemsListJob] {
166 return KAsync::value(davItemsListJob->items()); 164 return KAsync::value(davItemsListJob->items());
167 }) 165 })
168 .serialEach([=] (const KDAV::DavItem &item) { 166 .serialEach([=] (const KDAV2::DavItem &item) {
169 QByteArray rid = getRid(item); 167 QByteArray rid = getRid(item);
170 if (item.etag().toLatin1() != syncStore().readValue(rid + "_etag")){ 168 if (item.etag().toLatin1() != syncStore().readValue(rid + "_etag")){
171 SinkTrace() << "Updating " << rid; 169 SinkTrace() << "Updating " << rid;
172 auto davItemFetchJob = new KDAV::DavItemFetchJob(item); 170 auto davItemFetchJob = new KDAV2::DavItemFetchJob(item);
173 auto itemjob = runJob(davItemFetchJob) 171 auto itemjob = runJob(davItemFetchJob)
174 .then([=] { 172 .then([=] {
175 const auto item = davItemFetchJob->item(); 173 const auto item = davItemFetchJob->item();
@@ -180,7 +178,7 @@ public:
180 createOrModify(bufferType, rid, contact, mergeCriteria); 178 createOrModify(bufferType, rid, contact, mergeCriteria);
181 return item; 179 return item;
182 }) 180 })
183 .then([this, ridList] (const KDAV::DavItem &item) { 181 .then([this, ridList] (const KDAV2::DavItem &item) {
184 const auto rid = getRid(item); 182 const auto rid = getRid(item);
185 syncStore().writeValue(rid + "_etag", item.etag().toLatin1()); 183 syncStore().writeValue(rid + "_etag", item.etag().toLatin1());
186 ridList->append(rid); 184 ridList->append(rid);
@@ -227,24 +225,19 @@ KAsync::Job<QByteArray> replay(const ApplicationDomain::Contact &contact, Sink::
227 } 225 }
228 226
229public: 227public:
230 KDAV::DavUrl mResourceUrl; 228 KDAV2::DavUrl mResourceUrl;
231}; 229};
232 230
233 231
234DavResource::DavResource(const Sink::ResourceContext &resourceContext) 232DavResource::DavResource(const Sink::ResourceContext &resourceContext)
235 : Sink::GenericResource(resourceContext) 233 : Sink::GenericResource(resourceContext)
236{ 234{
237 /*
238 * Fork KIO slaves (used in kdav), instead of starting them via klauncher.
239 * Otherwise we have yet another runtime dependency that will i.e. not work in the docker container.
240 */
241 qputenv("KDE_FORK_SLAVES", "TRUE");
242 auto config = ResourceConfig::getConfiguration(resourceContext.instanceId()); 235 auto config = ResourceConfig::getConfiguration(resourceContext.instanceId());
243 auto resourceUrl = QUrl::fromUserInput(config.value("server").toString()); 236 auto resourceUrl = QUrl::fromUserInput(config.value("server").toString());
244 resourceUrl.setUserName(config.value("username").toString()); 237 resourceUrl.setUserName(config.value("username").toString());
245 resourceUrl.setPassword(config.value("password").toString()); 238 resourceUrl.setPassword(config.value("password").toString());
246 239
247 mResourceUrl = KDAV::DavUrl(resourceUrl, KDAV::CardDav); 240 mResourceUrl = KDAV2::DavUrl(resourceUrl, KDAV2::CardDav);
248 241
249 auto synchronizer = QSharedPointer<ContactSynchronizer>::create(resourceContext); 242 auto synchronizer = QSharedPointer<ContactSynchronizer>::create(resourceContext);
250 synchronizer->mResourceUrl = mResourceUrl; 243 synchronizer->mResourceUrl = mResourceUrl;
@@ -271,7 +264,7 @@ Sink::Resource *DavResourceFactory::createResource(const ResourceContext &contex
271void DavResourceFactory::registerFacades(const QByteArray &name, Sink::FacadeFactory &factory) 264void DavResourceFactory::registerFacades(const QByteArray &name, Sink::FacadeFactory &factory)
272{ 265{
273 factory.registerFacade<ApplicationDomain::Contact, DefaultFacade<ApplicationDomain::Contact>>(name); 266 factory.registerFacade<ApplicationDomain::Contact, DefaultFacade<ApplicationDomain::Contact>>(name);
274 factory.registerFacade<ApplicationDomain::Addressbook, DefaultFacade<ApplicationDomain::Contact>>(name); 267 factory.registerFacade<ApplicationDomain::Addressbook, DefaultFacade<ApplicationDomain::Addressbook>>(name);
275} 268}
276 269
277void DavResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry &registry) 270void DavResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry &registry)
diff --git a/examples/davresource/davresource.h b/examples/davresource/davresource.h
index 1ce66ea..db175a4 100644
--- a/examples/davresource/davresource.h
+++ b/examples/davresource/davresource.h
@@ -21,7 +21,7 @@
21 21
22#include "common/genericresource.h" 22#include "common/genericresource.h"
23 23
24#include <KDAV/DavUrl> 24#include <KDAV2/DavUrl>
25#include <KAsync/Async> 25#include <KAsync/Async>
26 26
27#include <flatbuffers/flatbuffers.h> 27#include <flatbuffers/flatbuffers.h>
@@ -48,7 +48,7 @@ public:
48private: 48private:
49 QStringList listAvailableFolders(); 49 QStringList listAvailableFolders();
50 50
51 KDAV::DavUrl mResourceUrl; 51 KDAV2::DavUrl mResourceUrl;
52}; 52};
53 53
54class DavResourceFactory : public Sink::ResourceFactory 54class DavResourceFactory : public Sink::ResourceFactory
diff --git a/examples/dummyresource/domainadaptor.cpp b/examples/dummyresource/domainadaptor.cpp
index dcc08c7..e7a20da 100644
--- a/examples/dummyresource/domainadaptor.cpp
+++ b/examples/dummyresource/domainadaptor.cpp
@@ -28,9 +28,6 @@ using namespace flatbuffers;
28DummyEventAdaptorFactory::DummyEventAdaptorFactory() 28DummyEventAdaptorFactory::DummyEventAdaptorFactory()
29 : DomainTypeAdaptorFactory() 29 : DomainTypeAdaptorFactory()
30{ 30{
31 //TODO turn this into initializeReadPropertyMapper as well?
32 mResourceMapper->addMapping<Sink::ApplicationDomain::Event::Summary, DummyEvent>(&DummyEvent::summary);
33 mResourceWriteMapper->addMapping<Sink::ApplicationDomain::Event::Summary>(&DummyEventBuilder::add_summary);
34} 31}
35 32
36DummyMailAdaptorFactory::DummyMailAdaptorFactory() 33DummyMailAdaptorFactory::DummyMailAdaptorFactory()
diff --git a/examples/dummyresource/domainadaptor.h b/examples/dummyresource/domainadaptor.h
index e7098e9..3faaa63 100644
--- a/examples/dummyresource/domainadaptor.h
+++ b/examples/dummyresource/domainadaptor.h
@@ -22,25 +22,23 @@
22#include "event_generated.h" 22#include "event_generated.h"
23#include "mail_generated.h" 23#include "mail_generated.h"
24#include "folder_generated.h" 24#include "folder_generated.h"
25#include "dummy_generated.h"
26#include "dummycalendar_generated.h"
27#include "entity_generated.h" 25#include "entity_generated.h"
28 26
29class DummyEventAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Event, DummyCalendar::DummyEvent, DummyCalendar::DummyEventBuilder> 27class DummyEventAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Event>
30{ 28{
31public: 29public:
32 DummyEventAdaptorFactory(); 30 DummyEventAdaptorFactory();
33 virtual ~DummyEventAdaptorFactory() {}; 31 virtual ~DummyEventAdaptorFactory() {};
34}; 32};
35 33
36class DummyMailAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Mail, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder> 34class DummyMailAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Mail>
37{ 35{
38public: 36public:
39 DummyMailAdaptorFactory(); 37 DummyMailAdaptorFactory();
40 virtual ~DummyMailAdaptorFactory() {}; 38 virtual ~DummyMailAdaptorFactory() {};
41}; 39};
42 40
43class DummyFolderAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Folder, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder> 41class DummyFolderAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Folder>
44{ 42{
45public: 43public:
46 DummyFolderAdaptorFactory(); 44 DummyFolderAdaptorFactory();
diff --git a/examples/dummyresource/resourcefactory.cpp b/examples/dummyresource/resourcefactory.cpp
index c1f536e..dffdfc9 100644
--- a/examples/dummyresource/resourcefactory.cpp
+++ b/examples/dummyresource/resourcefactory.cpp
@@ -42,8 +42,6 @@
42#define ENTITY_TYPE_MAIL "mail" 42#define ENTITY_TYPE_MAIL "mail"
43#define ENTITY_TYPE_FOLDER "folder" 43#define ENTITY_TYPE_FOLDER "folder"
44 44
45SINK_DEBUG_AREA("dummyresource")
46
47using namespace Sink; 45using namespace Sink;
48 46
49class DummySynchronizer : public Sink::Synchronizer { 47class DummySynchronizer : public Sink::Synchronizer {
diff --git a/examples/imapresource/CMakeLists.txt b/examples/imapresource/CMakeLists.txt
index 46a8b08..5d2d38b 100644
--- a/examples/imapresource/CMakeLists.txt
+++ b/examples/imapresource/CMakeLists.txt
@@ -4,7 +4,7 @@ add_definitions(-DQT_PLUGIN)
4include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) 4include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
5 5
6find_package(KF5 COMPONENTS REQUIRED Mime) 6find_package(KF5 COMPONENTS REQUIRED Mime)
7find_package(KIMAP2 0.0.1 REQUIRED) 7find_package(KIMAP2 0.2 REQUIRED)
8 8
9include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) 9include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
10 10
diff --git a/examples/imapresource/imapresource.cpp b/examples/imapresource/imapresource.cpp
index 0579dae..81c808b 100644
--- a/examples/imapresource/imapresource.cpp
+++ b/examples/imapresource/imapresource.cpp
@@ -46,8 +46,6 @@
46#define ENTITY_TYPE_MAIL "mail" 46#define ENTITY_TYPE_MAIL "mail"
47#define ENTITY_TYPE_FOLDER "folder" 47#define ENTITY_TYPE_FOLDER "folder"
48 48
49SINK_DEBUG_AREA("imapresource")
50
51Q_DECLARE_METATYPE(QSharedPointer<Imap::ImapServerProxy>) 49Q_DECLARE_METATYPE(QSharedPointer<Imap::ImapServerProxy>)
52 50
53using namespace Imap; 51using namespace Imap;
@@ -87,6 +85,25 @@ static QByteArray parentRid(const Imap::Folder &folder)
87 return folder.parentPath().toUtf8(); 85 return folder.parentPath().toUtf8();
88} 86}
89 87
88static QByteArray getSpecialPurposeType(const QByteArrayList &flags)
89{
90 if (Imap::flagsContain(Imap::FolderFlags::Trash, flags)) {
91 return ApplicationDomain::SpecialPurpose::Mail::trash;
92 }
93 if (Imap::flagsContain(Imap::FolderFlags::Drafts, flags)) {
94 return ApplicationDomain::SpecialPurpose::Mail::drafts;
95 }
96 if (Imap::flagsContain(Imap::FolderFlags::Sent, flags)) {
97 return ApplicationDomain::SpecialPurpose::Mail::sent;
98 }
99 return {};
100}
101
102static bool hasSpecialPurposeFlag(const QByteArrayList &flags)
103{
104 return !getSpecialPurposeType(flags).isEmpty();
105}
106
90 107
91class ImapSynchronizer : public Sink::Synchronizer { 108class ImapSynchronizer : public Sink::Synchronizer {
92 Q_OBJECT 109 Q_OBJECT
@@ -100,22 +117,29 @@ public:
100 QByteArray createFolder(const Imap::Folder &f) 117 QByteArray createFolder(const Imap::Folder &f)
101 { 118 {
102 const auto parentFolderRid = parentRid(f); 119 const auto parentFolderRid = parentRid(f);
103 SinkTraceCtx(mLogCtx) << "Creating folder: " << f.name() << parentFolderRid; 120 bool isToplevel = parentFolderRid.isEmpty();
121
122 SinkTraceCtx(mLogCtx) << "Creating folder: " << f.name() << parentFolderRid << f.flags;
104 123
105 const auto remoteId = folderRid(f); 124 const auto remoteId = folderRid(f);
106 Sink::ApplicationDomain::Folder folder; 125 Sink::ApplicationDomain::Folder folder;
107 folder.setName(f.name()); 126 folder.setName(f.name());
108 folder.setIcon("folder"); 127 folder.setIcon("folder");
109 folder.setEnabled(f.subscribed); 128 folder.setEnabled(f.subscribed);
110 QHash<QByteArray, Query::Comparator> mergeCriteria; 129 auto specialPurpose = [&] {
111 if (SpecialPurpose::isSpecialPurposeFolderName(f.name()) && parentFolderRid.isEmpty()) { 130 if (hasSpecialPurposeFlag(f.flags)) {
112 auto type = SpecialPurpose::getSpecialPurposeType(f.name()); 131 return getSpecialPurposeType(f.flags);
113 folder.setSpecialPurpose(QByteArrayList() << type); 132 } else if (SpecialPurpose::isSpecialPurposeFolderName(f.name()) && isToplevel) {
114 mergeCriteria.insert(ApplicationDomain::Folder::SpecialPurpose::name, Query::Comparator(type, Query::Comparator::Contains)); 133 return SpecialPurpose::getSpecialPurposeType(f.name());
134 }
135 return QByteArray{};
136 }();
137 if (!specialPurpose.isEmpty()) {
138 folder.setSpecialPurpose(QByteArrayList() << specialPurpose);
115 } 139 }
116 140
117 if (!parentFolderRid.isEmpty()) { 141 if (!isToplevel) {
118 folder.setParent(syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, parentFolderRid)); 142 folder.setParent(syncStore().resolveRemoteId(ApplicationDomain::Folder::name, parentFolderRid));
119 } 143 }
120 createOrModify(ApplicationDomain::getTypeName<ApplicationDomain::Folder>(), remoteId, folder); 144 createOrModify(ApplicationDomain::getTypeName<ApplicationDomain::Folder>(), remoteId, folder);
121 return remoteId; 145 return remoteId;
@@ -160,22 +184,20 @@ public:
160 return flags; 184 return flags;
161 } 185 }
162 186
163 void synchronizeMails(const QByteArray &folderRid, const Message &message) 187 void synchronizeMails(const QByteArray &folderRid, const QByteArray &folderLocalId, const Message &message)
164 { 188 {
165 auto time = QSharedPointer<QTime>::create(); 189 auto time = QSharedPointer<QTime>::create();
166 time->start(); 190 time->start();
167 SinkTraceCtx(mLogCtx) << "Importing new mail." << folderRid; 191 SinkTraceCtx(mLogCtx) << "Importing new mail." << folderRid;
168 192
169 const auto folderLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, folderRid);
170
171 const auto remoteId = assembleMailRid(folderLocalId, message.uid); 193 const auto remoteId = assembleMailRid(folderLocalId, message.uid);
172 194
173 Q_ASSERT(message.msg); 195 Q_ASSERT(message.msg);
174 SinkTraceCtx(mLogCtx) << "Found a mail " << remoteId << message.msg->subject(true)->asUnicodeString() << message.flags; 196 SinkTraceCtx(mLogCtx) << "Found a mail " << remoteId << message.flags;
175 197
176 auto mail = Sink::ApplicationDomain::Mail::create(mResourceInstanceIdentifier); 198 auto mail = Sink::ApplicationDomain::Mail::create(mResourceInstanceIdentifier);
177 mail.setFolder(folderLocalId); 199 mail.setFolder(folderLocalId);
178 mail.setMimeMessage(message.msg->encodedContent()); 200 mail.setMimeMessage(message.msg->encodedContent(true));
179 mail.setExtractedFullPayloadAvailable(message.fullPayload); 201 mail.setExtractedFullPayloadAvailable(message.fullPayload);
180 setFlags(mail, message.flags); 202 setFlags(mail, message.flags);
181 203
@@ -291,14 +313,15 @@ public:
291 SinkTraceCtx(mLogCtx) << "Uids to fetch: " << filteredAndSorted; 313 SinkTraceCtx(mLogCtx) << "Uids to fetch: " << filteredAndSorted;
292 314
293 bool headersOnly = false; 315 bool headersOnly = false;
316 const auto folderLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, folderRemoteId);
294 return imap->fetchMessages(folder, filteredAndSorted, headersOnly, [=](const Message &m) { 317 return imap->fetchMessages(folder, filteredAndSorted, headersOnly, [=](const Message &m) {
295 if (*maxUid < m.uid) { 318 if (*maxUid < m.uid) {
296 *maxUid = m.uid; 319 *maxUid = m.uid;
297 } 320 }
298 synchronizeMails(folderRemoteId, m); 321 synchronizeMails(folderRemoteId, folderLocalId, m);
299 }, 322 },
300 [this, maxUid, folder](int progress, int total) { 323 [=](int progress, int total) {
301 SinkLog() << "Progress: " << progress << " out of " << total; 324 reportProgress(progress, total, QByteArrayList{} << folderLocalId);
302 //commit every 10 messages 325 //commit every 10 messages
303 if ((progress % 10) == 0) { 326 if ((progress % 10) == 0) {
304 commit(); 327 commit();
@@ -335,11 +358,12 @@ public:
335 SinkLogCtx(mLogCtx) << "Fetching headers for: " << toFetch; 358 SinkLogCtx(mLogCtx) << "Fetching headers for: " << toFetch;
336 359
337 bool headersOnly = true; 360 bool headersOnly = true;
361 const auto folderLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, folderRemoteId);
338 return imap->fetchMessages(folder, toFetch, headersOnly, [=](const Message &m) { 362 return imap->fetchMessages(folder, toFetch, headersOnly, [=](const Message &m) {
339 synchronizeMails(folderRemoteId, m); 363 synchronizeMails(folderRemoteId, folderLocalId, m);
340 }, 364 },
341 [=](int progress, int total) { 365 [=](int progress, int total) {
342 SinkLogCtx(mLogCtx) << "Progress: " << progress << " out of " << total; 366 reportProgress(progress, total, QByteArrayList{} << folderLocalId);
343 //commit every 100 messages 367 //commit every 100 messages
344 if ((progress % 100) == 0) { 368 if ((progress % 100) == 0) {
345 commit(); 369 commit();
@@ -466,7 +490,7 @@ public:
466 //Otherwise fetch full payload for daterange 490 //Otherwise fetch full payload for daterange
467 auto folderList = QSharedPointer<QVector<Folder>>::create(); 491 auto folderList = QSharedPointer<QVector<Folder>>::create();
468 return imap->fetchFolders([folderList](const Folder &folder) { 492 return imap->fetchFolders([folderList](const Folder &folder) {
469 if (!folder.noselect) { 493 if (!folder.noselect && folder.subscribed) {
470 *folderList << folder; 494 *folderList << folder;
471 } 495 }
472 }) 496 })
@@ -480,12 +504,16 @@ public:
480 KAsync::Error getError(const KAsync::Error &error) 504 KAsync::Error getError(const KAsync::Error &error)
481 { 505 {
482 if (error) { 506 if (error) {
483 if (error.errorCode == Imap::CouldNotConnectError) { 507 switch(error.errorCode) {
484 return {ApplicationDomain::ConnectionError, error.errorMessage}; 508 case Imap::CouldNotConnectError:
485 } else if (error.errorCode == Imap::SslHandshakeError) { 509 return {ApplicationDomain::ConnectionError, error.errorMessage};
486 return {ApplicationDomain::LoginError, error.errorMessage}; 510 case Imap::SslHandshakeError:
511 return {ApplicationDomain::LoginError, error.errorMessage};
512 case Imap::HostNotFoundError:
513 return {ApplicationDomain::NoServerError, error.errorMessage};
514 default:
515 return {ApplicationDomain::UnknownError, error.errorMessage};
487 } 516 }
488 return {ApplicationDomain::UnknownError, error.errorMessage};
489 } 517 }
490 return {}; 518 return {};
491 } 519 }
@@ -539,11 +567,12 @@ public:
539 } 567 }
540 SinkLog() << "Fetching messages: " << toFetch << folderRemoteId; 568 SinkLog() << "Fetching messages: " << toFetch << folderRemoteId;
541 bool headersOnly = false; 569 bool headersOnly = false;
570 const auto folderLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, folderRemoteId);
542 return imap->fetchMessages(Folder{folderRemoteId}, toFetch, headersOnly, [=](const Message &m) { 571 return imap->fetchMessages(Folder{folderRemoteId}, toFetch, headersOnly, [=](const Message &m) {
543 synchronizeMails(folderRemoteId, m); 572 synchronizeMails(folderRemoteId, folderLocalId, m);
544 }, 573 },
545 [=](int progress, int total) { 574 [=](int progress, int total) {
546 reportProgress(progress, total); 575 reportProgress(progress, total, QByteArrayList{} << folderLocalId);
547 //commit every 100 messages 576 //commit every 100 messages
548 if ((progress % 100) == 0) { 577 if ((progress % 100) == 0) {
549 commit(); 578 commit();
@@ -554,10 +583,7 @@ public:
554 bool syncHeaders = query.hasFilter<ApplicationDomain::Mail::Folder>(); 583 bool syncHeaders = query.hasFilter<ApplicationDomain::Mail::Folder>();
555 //FIXME If we were able to to flush in between we could just query the local store for the folder list. 584 //FIXME If we were able to to flush in between we could just query the local store for the folder list.
556 return getFolderList(imap, query) 585 return getFolderList(imap, query)
557 .then([=] (const QVector<Folder> &folders) { 586 .serialEach([=](const Folder &folder) {
558 //Synchronize folders
559 return KAsync::value(folders)
560 .serialEach<void>([=](const Folder &folder) {
561 SinkLog() << "Syncing folder " << folder.path(); 587 SinkLog() << "Syncing folder " << folder.path();
562 //Emit notification that the folder is being synced. 588 //Emit notification that the folder is being synced.
563 //The synchronizer can't do that because it has no concept of the folder filter on a mail sync scope meaning that the folder is being synchronized. 589 //The synchronizer can't do that because it has no concept of the folder filter on a mail sync scope meaning that the folder is being synchronized.
@@ -572,7 +598,6 @@ public:
572 SinkWarning() << "Failed to sync folder: " << folder.path() << "Error: " << error.errorMessage; 598 SinkWarning() << "Failed to sync folder: " << folder.path() << "Error: " << error.errorMessage;
573 }); 599 });
574 }); 600 });
575 });
576 } 601 }
577 }) 602 })
578 .then([=] (const KAsync::Error &error) { 603 .then([=] (const KAsync::Error &error) {
@@ -582,6 +607,15 @@ public:
582 } 607 }
583 return KAsync::error<void>("Nothing to do"); 608 return KAsync::error<void>("Nothing to do");
584 } 609 }
610 static QByteArray ensureCRLF(const QByteArray &data) {
611 auto index = data.indexOf('\n');
612 if (index > 0 && data.at(index - 1) == '\r') { //First line is LF-only terminated
613 //Convert back and forth in case there's a mix. We don't want to expand CRLF into CRCRLF.
614 return KMime::LFtoCRLF(KMime::CRLFtoLF(data));
615 } else {
616 return data;
617 }
618 }
585 619
586 KAsync::Job<QByteArray> replay(const ApplicationDomain::Mail &mail, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE 620 KAsync::Job<QByteArray> replay(const ApplicationDomain::Mail &mail, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE
587 { 621 {
@@ -589,10 +623,10 @@ public:
589 auto login = imap->login(mUser, mPassword); 623 auto login = imap->login(mUser, mPassword);
590 KAsync::Job<QByteArray> job = KAsync::null<QByteArray>(); 624 KAsync::Job<QByteArray> job = KAsync::null<QByteArray>();
591 if (operation == Sink::Operation_Creation) { 625 if (operation == Sink::Operation_Creation) {
592 QString mailbox = syncStore().resolveLocalId(ENTITY_TYPE_FOLDER, mail.getFolder()); 626 const QString mailbox = syncStore().resolveLocalId(ENTITY_TYPE_FOLDER, mail.getFolder());
593 QByteArray content = KMime::LFtoCRLF(mail.getMimeMessage()); 627 const auto content = ensureCRLF(mail.getMimeMessage());
594 auto flags = getFlags(mail); 628 const auto flags = getFlags(mail);
595 QDateTime internalDate = mail.getDate(); 629 const QDateTime internalDate = mail.getDate();
596 job = login.then(imap->append(mailbox, content, flags, internalDate)) 630 job = login.then(imap->append(mailbox, content, flags, internalDate))
597 .addToContext(imap) 631 .addToContext(imap)
598 .then([mail](qint64 uid) { 632 .then([mail](qint64 uid) {
@@ -623,11 +657,11 @@ public:
623 const bool messageMoved = changedProperties.contains(ApplicationDomain::Mail::Folder::name); 657 const bool messageMoved = changedProperties.contains(ApplicationDomain::Mail::Folder::name);
624 const bool messageChanged = changedProperties.contains(ApplicationDomain::Mail::MimeMessage::name); 658 const bool messageChanged = changedProperties.contains(ApplicationDomain::Mail::MimeMessage::name);
625 if (messageChanged || messageMoved) { 659 if (messageChanged || messageMoved) {
626 SinkTrace() << "Replacing message.";
627 const auto folderId = folderIdFromMailRid(oldRemoteId); 660 const auto folderId = folderIdFromMailRid(oldRemoteId);
628 const QString oldMailbox = syncStore().resolveLocalId(ENTITY_TYPE_FOLDER, folderId); 661 const QString oldMailbox = syncStore().resolveLocalId(ENTITY_TYPE_FOLDER, folderId);
629 QByteArray content = KMime::LFtoCRLF(mail.getMimeMessage()); 662 const auto content = ensureCRLF(mail.getMimeMessage());
630 QDateTime internalDate = mail.getDate(); 663 const QDateTime internalDate = mail.getDate();
664 SinkTrace() << "Replacing message. Old mailbox: " << oldMailbox << "New mailbox: " << mailbox << "Flags: " << flags << "Content: " << content;
631 KIMAP2::ImapSet set; 665 KIMAP2::ImapSet set;
632 set.add(uid); 666 set.add(uid);
633 job = login.then(imap->append(mailbox, content, flags, internalDate)) 667 job = login.then(imap->append(mailbox, content, flags, internalDate))
@@ -655,7 +689,7 @@ public:
655 if (error) { 689 if (error) {
656 SinkWarning() << "Error during changereplay: " << error.errorMessage; 690 SinkWarning() << "Error during changereplay: " << error.errorMessage;
657 return imap->logout() 691 return imap->logout()
658 .then(KAsync::error<QByteArray>(error)); 692 .then(KAsync::error<QByteArray>(getError(error)));
659 } 693 }
660 return imap->logout() 694 return imap->logout()
661 .then(KAsync::value(remoteId)); 695 .then(KAsync::value(remoteId));
@@ -664,6 +698,12 @@ public:
664 698
665 KAsync::Job<QByteArray> replay(const ApplicationDomain::Folder &folder, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE 699 KAsync::Job<QByteArray> replay(const ApplicationDomain::Folder &folder, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE
666 { 700 {
701 if (operation != Sink::Operation_Creation) {
702 if(oldRemoteId.isEmpty()) {
703 Q_ASSERT(false);
704 return KAsync::error<QByteArray>("Tried to replay modification without old remoteId.");
705 }
706 }
667 auto imap = QSharedPointer<ImapServerProxy>::create(mServer, mPort, &mSessionCache); 707 auto imap = QSharedPointer<ImapServerProxy>::create(mServer, mPort, &mSessionCache);
668 auto login = imap->login(mUser, mPassword); 708 auto login = imap->login(mUser, mPassword);
669 if (operation == Sink::Operation_Creation) { 709 if (operation == Sink::Operation_Creation) {
@@ -793,6 +833,10 @@ protected:
793 .then(imap->select(folderRemoteId)) 833 .then(imap->select(folderRemoteId))
794 .then([](Imap::SelectResult){}) 834 .then([](Imap::SelectResult){})
795 .then(imap->fetch(set, scope, [imap, messageByUid](const Imap::Message &message) { 835 .then(imap->fetch(set, scope, [imap, messageByUid](const Imap::Message &message) {
836 //We avoid parsing normally, so we have to do it explicitly here
837 if (message.msg) {
838 message.msg->parse();
839 }
796 messageByUid->insert(message.uid, message); 840 messageByUid->insert(message.uid, message);
797 })); 841 }));
798 842
diff --git a/examples/imapresource/imapserverproxy.cpp b/examples/imapresource/imapserverproxy.cpp
index 0cc43b8..538105c 100644
--- a/examples/imapresource/imapserverproxy.cpp
+++ b/examples/imapresource/imapserverproxy.cpp
@@ -37,8 +37,6 @@
37#include "log.h" 37#include "log.h"
38#include "test.h" 38#include "test.h"
39 39
40SINK_DEBUG_AREA("imapserverproxy")
41
42using namespace Imap; 40using namespace Imap;
43 41
44const char* Imap::Flags::Seen = "\\Seen"; 42const char* Imap::Flags::Seen = "\\Seen";
@@ -57,6 +55,7 @@ const char* Imap::FolderFlags::Trash = "\\Trash";
57const char* Imap::FolderFlags::Archive = "\\Archive"; 55const char* Imap::FolderFlags::Archive = "\\Archive";
58const char* Imap::FolderFlags::Junk = "\\Junk"; 56const char* Imap::FolderFlags::Junk = "\\Junk";
59const char* Imap::FolderFlags::Flagged = "\\Flagged"; 57const char* Imap::FolderFlags::Flagged = "\\Flagged";
58const char* Imap::FolderFlags::Drafts = "\\Drafts";
60 59
61const char* Imap::Capabilities::Namespace = "NAMESPACE"; 60const char* Imap::Capabilities::Namespace = "NAMESPACE";
62const char* Imap::Capabilities::Uidplus = "UIDPLUS"; 61const char* Imap::Capabilities::Uidplus = "UIDPLUS";
@@ -98,17 +97,25 @@ static KAsync::Job<void> runJob(KJob *job)
98 }); 97 });
99} 98}
100 99
101ImapServerProxy::ImapServerProxy(const QString &serverUrl, int port, SessionCache *sessionCache) : mSession(new KIMAP2::Session(serverUrl, qint16(port))), mSessionCache(sessionCache) 100KIMAP2::Session *createNewSession(const QString &serverUrl, int port)
102{ 101{
103 QObject::connect(mSession, &KIMAP2::Session::sslErrors, [this](const QList<QSslError> &errors) { 102 auto newSession = new KIMAP2::Session(serverUrl, qint16(port));
103 if (Sink::Test::testModeEnabled()) {
104 newSession->setTimeout(1);
105 } else {
106 newSession->setTimeout(40);
107 }
108 QObject::connect(newSession, &KIMAP2::Session::sslErrors, [=](const QList<QSslError> &errors) {
104 SinkLog() << "Received ssl error: " << errors; 109 SinkLog() << "Received ssl error: " << errors;
105 mSession->ignoreErrors(errors); 110 newSession->ignoreErrors(errors);
106 }); 111 });
112 return newSession;
113}
107 114
108 if (Sink::Test::testModeEnabled()) { 115ImapServerProxy::ImapServerProxy(const QString &serverUrl, int port, SessionCache *sessionCache) : mSessionCache(sessionCache), mSession(nullptr)
109 mSession->setTimeout(1); 116{
110 } else { 117 if (!mSessionCache || mSessionCache->isEmpty()) {
111 mSession->setTimeout(40); 118 mSession = createNewSession(serverUrl, port);
112 } 119 }
113} 120}
114 121
@@ -161,12 +168,16 @@ KAsync::Job<void> ImapServerProxy::login(const QString &username, const QString
161 // SinkTrace() << "Found user namespaces: " << mNamespaces.user; 168 // SinkTrace() << "Found user namespaces: " << mNamespaces.user;
162 }).then([=] (const KAsync::Error &error) { 169 }).then([=] (const KAsync::Error &error) {
163 if (error) { 170 if (error) {
164 if (error.errorCode == KIMAP2::LoginJob::ErrorCode::ERR_COULD_NOT_CONNECT) { 171 switch (error.errorCode) {
172 case KIMAP2::LoginJob::ErrorCode::ERR_HOST_NOT_FOUND:
173 return KAsync::error(HostNotFoundError, "Host not found: " + error.errorMessage);
174 case KIMAP2::LoginJob::ErrorCode::ERR_COULD_NOT_CONNECT:
165 return KAsync::error(CouldNotConnectError, "Failed to connect: " + error.errorMessage); 175 return KAsync::error(CouldNotConnectError, "Failed to connect: " + error.errorMessage);
166 } else if (error.errorCode == KIMAP2::LoginJob::ErrorCode::ERR_SSL_HANDSHAKE_FAILED) { 176 case KIMAP2::LoginJob::ErrorCode::ERR_SSL_HANDSHAKE_FAILED:
167 return KAsync::error(SslHandshakeError, "Ssl handshake failed: " + error.errorMessage); 177 return KAsync::error(SslHandshakeError, "Ssl handshake failed: " + error.errorMessage);
178 default:
179 return KAsync::error(error);
168 } 180 }
169 return KAsync::error(error);
170 } 181 }
171 return KAsync::null(); 182 return KAsync::null();
172 }); 183 });
@@ -188,6 +199,12 @@ KAsync::Job<void> ImapServerProxy::logout()
188 } 199 }
189} 200}
190 201
202bool ImapServerProxy::isGmail() const
203{
204 //Magic capability that only gmail has
205 return mCapabilities.contains("X-GM-EXT-1");
206}
207
191KAsync::Job<SelectResult> ImapServerProxy::select(const QString &mailbox) 208KAsync::Job<SelectResult> ImapServerProxy::select(const QString &mailbox)
192{ 209{
193 auto select = new KIMAP2::SelectJob(mSession); 210 auto select = new KIMAP2::SelectJob(mSession);
@@ -297,6 +314,7 @@ KAsync::Job<void> ImapServerProxy::fetch(const KIMAP2::ImapSet &set, KIMAP2::Fet
297 fetch->setSequenceSet(set); 314 fetch->setSequenceSet(set);
298 fetch->setUidBased(true); 315 fetch->setUidBased(true);
299 fetch->setScope(scope); 316 fetch->setScope(scope);
317 fetch->setAvoidParsing(true);
300 QObject::connect(fetch, &KIMAP2::FetchJob::resultReceived, callback); 318 QObject::connect(fetch, &KIMAP2::FetchJob::resultReceived, callback);
301 return runJob(fetch); 319 return runJob(fetch);
302} 320}
@@ -437,18 +455,60 @@ QString ImapServerProxy::getNamespace(const QString &name)
437 return ns.name; 455 return ns.name;
438} 456}
439 457
458static bool caseInsensitiveContains(const QByteArray &f, const QByteArrayList &list) {
459 return list.contains(f) || list.contains(f.toLower());
460}
461
462bool Imap::flagsContain(const QByteArray &f, const QByteArrayList &flags)
463{
464 return caseInsensitiveContains(f, flags);
465}
466
467static void reportFolder(const Folder &f, QSharedPointer<QSet<QString>> reportedList, std::function<void(const Folder &)> callback) {
468 if (!reportedList->contains(f.path())) {
469 reportedList->insert(f.path());
470 auto c = f;
471 c.noselect = true;
472 callback(c);
473 if (!f.parentPath().isEmpty()){
474 reportFolder(f.parentFolder(), reportedList, callback);
475 }
476 }
477}
478
440KAsync::Job<void> ImapServerProxy::fetchFolders(std::function<void(const Folder &)> callback) 479KAsync::Job<void> ImapServerProxy::fetchFolders(std::function<void(const Folder &)> callback)
441{ 480{
442 SinkTrace() << "Fetching folders"; 481 SinkTrace() << "Fetching folders";
443 auto subscribedList = QSharedPointer<QSet<QString>>::create() ; 482 auto subscribedList = QSharedPointer<QSet<QString>>::create() ;
483 auto reportedList = QSharedPointer<QSet<QString>>::create() ;
444 return list(KIMAP2::ListJob::NoOption, [=](const KIMAP2::MailBoxDescriptor &mailbox, const QList<QByteArray> &){ 484 return list(KIMAP2::ListJob::NoOption, [=](const KIMAP2::MailBoxDescriptor &mailbox, const QList<QByteArray> &){
445 *subscribedList << mailbox.name; 485 *subscribedList << mailbox.name;
446 }).then(list(KIMAP2::ListJob::IncludeUnsubscribed, [=](const KIMAP2::MailBoxDescriptor &mailbox, const QList<QByteArray> &flags) { 486 }).then(list(KIMAP2::ListJob::IncludeUnsubscribed, [=](const KIMAP2::MailBoxDescriptor &mailbox, const QList<QByteArray> &flags) {
447 bool noselect = flags.contains(QByteArray(FolderFlags::Noselect).toLower()) || flags.contains(QByteArray(FolderFlags::Noselect)); 487 bool noselect = caseInsensitiveContains(FolderFlags::Noselect, flags);
448 bool subscribed = subscribedList->contains(mailbox.name); 488 bool subscribed = subscribedList->contains(mailbox.name);
489 if (isGmail()) {
490 bool inbox = mailbox.name.toLower() == "inbox";
491 bool sent = caseInsensitiveContains(FolderFlags::Sent, flags);
492 bool drafts = caseInsensitiveContains(FolderFlags::Drafts, flags);
493 bool trash = caseInsensitiveContains(FolderFlags::Trash, flags);
494 /**
495 * Because gmail duplicates messages all over the place we only support a few selected folders for now that should be mostly exclusive.
496 */
497 if (!(inbox || sent || drafts || trash)) {
498 return;
499 }
500 }
449 SinkLog() << "Found mailbox: " << mailbox.name << flags << FolderFlags::Noselect << noselect << " sub: " << subscribed; 501 SinkLog() << "Found mailbox: " << mailbox.name << flags << FolderFlags::Noselect << noselect << " sub: " << subscribed;
450 auto ns = getNamespace(mailbox.name); 502 auto ns = getNamespace(mailbox.name);
451 callback(Folder{mailbox.name, ns, mailbox.separator, noselect, subscribed, flags}); 503 auto folder = Folder{mailbox.name, ns, mailbox.separator, noselect, subscribed, flags};
504
505 //call callback for parents if that didn't already happen.
506 //This is necessary because we can have missing bits in the hierarchy in IMAP, but this will not work in sink because we'd end up with an incomplete tree.
507 if (!folder.parentPath().isEmpty() && !reportedList->contains(folder.parentPath())) {
508 reportFolder(folder.parentFolder(), reportedList, callback);
509 }
510 reportedList->insert(folder.path());
511 callback(folder);
452 })); 512 }));
453} 513}
454 514
diff --git a/examples/imapresource/imapserverproxy.h b/examples/imapresource/imapserverproxy.h
index 872f032..82f4f58 100644
--- a/examples/imapresource/imapserverproxy.h
+++ b/examples/imapresource/imapserverproxy.h
@@ -31,6 +31,7 @@ namespace Imap {
31 31
32enum ErrorCode { 32enum ErrorCode {
33 NoError, 33 NoError,
34 HostNotFoundError,
34 CouldNotConnectError, 35 CouldNotConnectError,
35 SslHandshakeError 36 SslHandshakeError
36}; 37};
@@ -60,6 +61,7 @@ namespace FolderFlags
60 extern const char* Junk; 61 extern const char* Junk;
61 extern const char* Flagged; 62 extern const char* Flagged;
62 extern const char* All; 63 extern const char* All;
64 extern const char* Drafts;
63} 65}
64 66
65namespace Capabilities 67namespace Capabilities
@@ -78,6 +80,8 @@ struct Message {
78 bool fullPayload; 80 bool fullPayload;
79}; 81};
80 82
83bool flagsContain(const QByteArray &f, const QByteArrayList &flags);
84
81struct Folder { 85struct Folder {
82 Folder() = default; 86 Folder() = default;
83 Folder(const QString &path, const QString &ns, const QChar &separator, bool noselect_, bool subscribed_, const QByteArrayList &flags_) 87 Folder(const QString &path, const QString &ns, const QChar &separator, bool noselect_, bool subscribed_, const QByteArrayList &flags_)
@@ -114,6 +118,15 @@ struct Folder {
114 return parentPath; 118 return parentPath;
115 } 119 }
116 120
121 Folder parentFolder() const
122 {
123 Folder parent;
124 parent.mPath = parentPath();
125 parent.mNamespace = mNamespace;
126 parent.mSeparator = mSeparator;
127 return parent;
128 }
129
117 QString name() const 130 QString name() const
118 { 131 {
119 auto pathParts = mPath.split(mSeparator); 132 auto pathParts = mPath.split(mSeparator);
@@ -218,18 +231,18 @@ public:
218 return session; 231 return session;
219 } 232 }
220 } 233 }
221 return CachedSession{}; 234 return {};
235 }
236
237 bool isEmpty() const
238 {
239 return mSessions.isEmpty();
222 } 240 }
223private: 241private:
224 QList<CachedSession> mSessions; 242 QList<CachedSession> mSessions;
225}; 243};
226 244
227class ImapServerProxy { 245class ImapServerProxy {
228 KIMAP2::Session *mSession;
229 QStringList mCapabilities;
230 Namespaces mNamespaces;
231
232
233public: 246public:
234 ImapServerProxy(const QString &serverUrl, int port, SessionCache *sessionCache = nullptr); 247 ImapServerProxy(const QString &serverUrl, int port, SessionCache *sessionCache = nullptr);
235 248
@@ -279,9 +292,14 @@ public:
279 KAsync::Job<QVector<qint64>> fetchUids(const Folder &folder); 292 KAsync::Job<QVector<qint64>> fetchUids(const Folder &folder);
280 293
281private: 294private:
295 bool isGmail() const;
296
282 QString getNamespace(const QString &name); 297 QString getNamespace(const QString &name);
283 QObject mGuard; 298 QObject mGuard;
284 SessionCache *mSessionCache; 299 SessionCache *mSessionCache;
300 KIMAP2::Session *mSession;
301 QStringList mCapabilities;
302 Namespaces mNamespaces;
285}; 303};
286 304
287} 305}
diff --git a/examples/imapresource/tests/imapmailsyncbenchmark.cpp b/examples/imapresource/tests/imapmailsyncbenchmark.cpp
index a53c148..814e325 100644
--- a/examples/imapresource/tests/imapmailsyncbenchmark.cpp
+++ b/examples/imapresource/tests/imapmailsyncbenchmark.cpp
@@ -31,8 +31,6 @@
31using namespace Sink; 31using namespace Sink;
32using namespace Sink::ApplicationDomain; 32using namespace Sink::ApplicationDomain;
33 33
34SINK_DEBUG_AREA("ImapMailSyncBenchmark")
35
36/** 34/**
37 * Test of complete system using the imap resource. 35 * Test of complete system using the imap resource.
38 * 36 *
diff --git a/examples/imapresource/tests/imapserverproxytest.cpp b/examples/imapresource/tests/imapserverproxytest.cpp
index 476066d..271b3d9 100644
--- a/examples/imapresource/tests/imapserverproxytest.cpp
+++ b/examples/imapresource/tests/imapserverproxytest.cpp
@@ -12,8 +12,6 @@
12 12
13using namespace Imap; 13using namespace Imap;
14 14
15// SINK_DEBUG_AREA("imapserverproxytest")
16
17/** 15/**
18 */ 16 */
19class ImapServerProxyTest : public QObject 17class ImapServerProxyTest : public QObject
diff --git a/examples/imapresource/tests/populatemailbox.sh b/examples/imapresource/tests/populatemailbox.sh
index a435df7..800e2e7 100644
--- a/examples/imapresource/tests/populatemailbox.sh
+++ b/examples/imapresource/tests/populatemailbox.sh
@@ -1,12 +1,23 @@
1#!/bin/bash 1#!/bin/bash
2 2
3sudo echo "sam user.doe.* cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost 3sudo echo "sam user.doe.* cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost
4#Delete all mailboxes
4sudo echo "dm user.doe.*" | cyradm --auth PLAIN -u cyrus -w admin localhost 5sudo echo "dm user.doe.*" | cyradm --auth PLAIN -u cyrus -w admin localhost
6#Create mailboxes
5sudo echo "cm user.doe.test" | cyradm --auth PLAIN -u cyrus -w admin localhost 7sudo echo "cm user.doe.test" | cyradm --auth PLAIN -u cyrus -w admin localhost
6sudo echo "cm user.doe.Drafts" | cyradm --auth PLAIN -u cyrus -w admin localhost 8sudo echo "cm user.doe.Drafts" | cyradm --auth PLAIN -u cyrus -w admin localhost
7sudo echo "cm user.doe.Trash" | cyradm --auth PLAIN -u cyrus -w admin localhost 9sudo echo "cm user.doe.Trash" | cyradm --auth PLAIN -u cyrus -w admin localhost
10
11#Set acls so we can create in INBOX
8sudo echo "sam user.doe cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost 12sudo echo "sam user.doe cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost
9 13
14#Subscribe to mailboxes
15sudo echo "sub INBOX" | cyradm --auth PLAIN -u doe -w doe localhost
16sudo echo "sub INBOX.test" | cyradm --auth PLAIN -u doe -w doe localhost
17sudo echo "sub INBOX.Drafts" | cyradm --auth PLAIN -u doe -w doe localhost
18sudo echo "sub INBOX.Trash" | cyradm --auth PLAIN -u doe -w doe localhost
19
20#Create a bunch of test messages in the test folder
10# for i in `seq 1 5000`; 21# for i in `seq 1 5000`;
11# do 22# do
12# # sudo cp /work/source/Sink/examples/imapresource/tests/data/1365777830.R28.localhost.localdomain\:2\,S /var/spool/imap/d/user/doe/test/$i. 23# # sudo cp /work/source/Sink/examples/imapresource/tests/data/1365777830.R28.localhost.localdomain\:2\,S /var/spool/imap/d/user/doe/test/$i.
diff --git a/examples/imapresource/tests/resetmailbox.sh b/examples/imapresource/tests/resetmailbox.sh
index 6ed198e..63d3478 100644
--- a/examples/imapresource/tests/resetmailbox.sh
+++ b/examples/imapresource/tests/resetmailbox.sh
@@ -3,8 +3,11 @@
3sudo echo "sam user.doe.* cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost 3sudo echo "sam user.doe.* cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost
4sudo echo "dm user.doe.*" | cyradm --auth PLAIN -u cyrus -w admin localhost 4sudo echo "dm user.doe.*" | cyradm --auth PLAIN -u cyrus -w admin localhost
5sudo echo "cm user.doe.test" | cyradm --auth PLAIN -u cyrus -w admin localhost 5sudo echo "cm user.doe.test" | cyradm --auth PLAIN -u cyrus -w admin localhost
6sudo echo "subscribe INBOX.test" | cyradm --auth PLAIN -u doe -w doe localhost
6sudo echo "cm user.doe.Drafts" | cyradm --auth PLAIN -u cyrus -w admin localhost 7sudo echo "cm user.doe.Drafts" | cyradm --auth PLAIN -u cyrus -w admin localhost
8sudo echo "subscribe INBOX.Drafts" | cyradm --auth PLAIN -u doe -w doe localhost
7sudo echo "cm user.doe.Trash" | cyradm --auth PLAIN -u cyrus -w admin localhost 9sudo echo "cm user.doe.Trash" | cyradm --auth PLAIN -u cyrus -w admin localhost
10sudo echo "subscribe INBOX.Trash" | cyradm --auth PLAIN -u doe -w doe localhost
8sudo echo "sam user.doe cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost 11sudo echo "sam user.doe cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost
9sudo cp /work/source/Sink/examples/imapresource/tests/data/1365777830.R28.localhost.localdomain\:2\,S /var/spool/imap/d/user/doe/test/1. 12sudo cp /work/source/Sink/examples/imapresource/tests/data/1365777830.R28.localhost.localdomain\:2\,S /var/spool/imap/d/user/doe/test/1.
10sudo chown cyrus:mail /var/spool/imap/d/user/doe/test/1. 13sudo chown cyrus:mail /var/spool/imap/d/user/doe/test/1.
diff --git a/examples/maildirresource/maildirresource.cpp b/examples/maildirresource/maildirresource.cpp
index 40bab37..b406f63 100644
--- a/examples/maildirresource/maildirresource.cpp
+++ b/examples/maildirresource/maildirresource.cpp
@@ -43,8 +43,6 @@
43#define ENTITY_TYPE_MAIL "mail" 43#define ENTITY_TYPE_MAIL "mail"
44#define ENTITY_TYPE_FOLDER "folder" 44#define ENTITY_TYPE_FOLDER "folder"
45 45
46SINK_DEBUG_AREA("maildirresource")
47
48using namespace Sink; 46using namespace Sink;
49 47
50static QString getFilePathFromMimeMessagePath(const QString &mimeMessagePath) 48static QString getFilePathFromMimeMessagePath(const QString &mimeMessagePath)
@@ -555,18 +553,20 @@ MaildirResource::MaildirResource(const Sink::ResourceContext &resourceContext)
555 setupPreprocessors(ENTITY_TYPE_FOLDER, QVector<Sink::Preprocessor*>() << new FolderPreprocessor(mMaildirPath)); 553 setupPreprocessors(ENTITY_TYPE_FOLDER, QVector<Sink::Preprocessor*>() << new FolderPreprocessor(mMaildirPath));
556 554
557 KPIM::Maildir dir(mMaildirPath, true); 555 KPIM::Maildir dir(mMaildirPath, true);
558 SinkTrace() << "Started maildir resource for maildir: " << mMaildirPath; 556 if (dir.isValid(false)) {
559 { 557 {
560 auto draftsFolder = dir.addSubFolder("Drafts"); 558 auto draftsFolder = dir.addSubFolder("Drafts");
561 auto remoteId = synchronizer->createFolder(draftsFolder, "folder", QByteArrayList() << "drafts"); 559 auto remoteId = synchronizer->createFolder(draftsFolder, "folder", QByteArrayList() << "drafts");
562 auto draftsFolderLocalId = synchronizer->syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, remoteId); 560 auto draftsFolderLocalId = synchronizer->syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, remoteId);
563 } 561 }
564 { 562 {
565 auto trashFolder = dir.addSubFolder("Trash"); 563 auto trashFolder = dir.addSubFolder("Trash");
566 auto remoteId = synchronizer->createFolder(trashFolder, "folder", QByteArrayList() << "trash"); 564 auto remoteId = synchronizer->createFolder(trashFolder, "folder", QByteArrayList() << "trash");
567 auto trashFolderLocalId = synchronizer->syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, remoteId); 565 auto trashFolderLocalId = synchronizer->syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, remoteId);
566 }
567 synchronizer->commit();
568 } 568 }
569 synchronizer->commit(); 569 SinkTrace() << "Started maildir resource for maildir: " << mMaildirPath;
570} 570}
571 571
572 572
diff --git a/examples/mailtransportresource/mailtransport.cpp b/examples/mailtransportresource/mailtransport.cpp
index 84c1556..afe0257 100644
--- a/examples/mailtransportresource/mailtransport.cpp
+++ b/examples/mailtransportresource/mailtransport.cpp
@@ -23,8 +23,6 @@
23#include <QDebug> 23#include <QDebug>
24#include <common/log.h> 24#include <common/log.h>
25 25
26SINK_DEBUG_AREA("mailtransport")
27
28extern "C" { 26extern "C" {
29 27
30#include <stdio.h> 28#include <stdio.h>
diff --git a/examples/mailtransportresource/mailtransportresource.cpp b/examples/mailtransportresource/mailtransportresource.cpp
index c73219f..3d6f8e4 100644
--- a/examples/mailtransportresource/mailtransportresource.cpp
+++ b/examples/mailtransportresource/mailtransportresource.cpp
@@ -39,8 +39,6 @@
39 39
40#define ENTITY_TYPE_MAIL "mail" 40#define ENTITY_TYPE_MAIL "mail"
41 41
42SINK_DEBUG_AREA("mailtransportresource")
43
44using namespace Sink; 42using namespace Sink;
45 43
46class MailtransportPreprocessor : public Sink::Preprocessor 44class MailtransportPreprocessor : public Sink::Preprocessor
diff --git a/examples/mailtransportresource/tests/mailtransporttest.cpp b/examples/mailtransportresource/tests/mailtransporttest.cpp
index e4cc447..2a831ed 100644
--- a/examples/mailtransportresource/tests/mailtransporttest.cpp
+++ b/examples/mailtransportresource/tests/mailtransporttest.cpp
@@ -64,7 +64,7 @@ private slots:
64 message->assemble(); 64 message->assemble();
65 65
66 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier); 66 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier);
67 mail.setMimeMessage(message->encodedContent()); 67 mail.setMimeMessage(message->encodedContent(true));
68 68
69 VERIFYEXEC(Store::create(mail)); 69 VERIFYEXEC(Store::create(mail));
70 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier)); 70 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));
@@ -92,7 +92,7 @@ private slots:
92 message->assemble(); 92 message->assemble();
93 93
94 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier); 94 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier);
95 mail.setMimeMessage(message->encodedContent()); 95 mail.setMimeMessage(message->encodedContent(true));
96 96
97 VERIFYEXEC(Store::create(mail)); 97 VERIFYEXEC(Store::create(mail));
98 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier)); 98 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));
diff --git a/hawd_defs/mail_query_threadleader b/hawd_defs/mail_query_threadleader
new file mode 100644
index 0000000..c555997
--- /dev/null
+++ b/hawd_defs/mail_query_threadleader
@@ -0,0 +1,8 @@
1{
2 "name": "Mail Query performance",
3 "description": "Measures performance of mail queries",
4 "columns": [
5 { "name": "rows", "type": "int" },
6 { "name": "queryResultPerMs", "type": "float", "unit": "result/ms" }
7 ]
8}
diff --git a/sinksh/CMakeLists.txt b/sinksh/CMakeLists.txt
index 82839ab..bc487c1 100644
--- a/sinksh/CMakeLists.txt
+++ b/sinksh/CMakeLists.txt
@@ -18,6 +18,7 @@ set(sink_cli_SRCS
18 syntax_modules/sink_trace.cpp 18 syntax_modules/sink_trace.cpp
19 syntax_modules/sink_inspect.cpp 19 syntax_modules/sink_inspect.cpp
20 syntax_modules/sink_drop.cpp 20 syntax_modules/sink_drop.cpp
21 syntax_modules/sink_upgrade.cpp
21 sinksh_utils.cpp 22 sinksh_utils.cpp
22 repl/repl.cpp 23 repl/repl.cpp
23 repl/replStates.cpp 24 repl/replStates.cpp
diff --git a/sinksh/state.cpp b/sinksh/state.cpp
index 8f4a675..7e04d28 100644
--- a/sinksh/state.cpp
+++ b/sinksh/state.cpp
@@ -26,8 +26,6 @@
26 26
27#include "common/log.h" 27#include "common/log.h"
28 28
29SINK_DEBUG_AREA("state")
30
31static bool s_hasEventLoop = false; 29static bool s_hasEventLoop = false;
32 30
33class State::Private 31class State::Private
diff --git a/sinksh/syntax_modules/sink_upgrade.cpp b/sinksh/syntax_modules/sink_upgrade.cpp
new file mode 100644
index 0000000..c399048
--- /dev/null
+++ b/sinksh/syntax_modules/sink_upgrade.cpp
@@ -0,0 +1,47 @@
1/*
2 * Copyright (C) 2017 Christian Mollekopf <mollekopf@kolabsys.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include <QDebug>
21#include <QObject> // tr()
22#include <QTimer>
23
24#include "common/store.h"
25
26#include "state.h"
27#include "syntaxtree.h"
28
29namespace SinkUpgrade
30{
31
32bool upgrade(const QStringList &args, State &state)
33{
34 state.print(QObject::tr("Upgrading..."));
35 Sink::Store::upgrade().exec().waitForFinished();
36 state.printLine(QObject::tr("done"));
37 return true;
38}
39
40Syntax::List syntax()
41{
42 return Syntax::List() << Syntax{"upgrade", QObject::tr("Upgrades your storage to the latest version (be careful!)"), &SinkUpgrade::upgrade, Syntax::NotInteractive};
43}
44
45REGISTER_SYNTAX(SinkUpgrade)
46
47}
diff --git a/synchronizer/CMakeLists.txt b/synchronizer/CMakeLists.txt
index ff9ec86..e049d64 100644
--- a/synchronizer/CMakeLists.txt
+++ b/synchronizer/CMakeLists.txt
@@ -8,5 +8,5 @@ set(sinksynchronizer_SRCS
8 8
9add_executable(${PROJECT_NAME} ${sinksynchronizer_SRCS}) 9add_executable(${PROJECT_NAME} ${sinksynchronizer_SRCS})
10target_link_libraries(${PROJECT_NAME} sink KAsync ${CMAKE_DL_LIBS}) 10target_link_libraries(${PROJECT_NAME} sink KAsync ${CMAKE_DL_LIBS})
11qt5_use_modules(${PROJECT_NAME} Network) 11qt5_use_modules(${PROJECT_NAME} Core Network)
12install(TARGETS ${PROJECT_NAME} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) 12install(TARGETS ${PROJECT_NAME} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/synchronizer/main.cpp b/synchronizer/main.cpp
index c66a2fb..3c41c67 100644
--- a/synchronizer/main.cpp
+++ b/synchronizer/main.cpp
@@ -39,8 +39,6 @@
39#include "test.h" 39#include "test.h"
40#include "definitions.h" 40#include "definitions.h"
41 41
42SINK_DEBUG_AREA("main")
43
44static Listener *listener = nullptr; 42static Listener *listener = nullptr;
45 43
46//Print a demangled stacktrace 44//Print a demangled stacktrace
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index c77a736..2b3e7b1 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -16,6 +16,9 @@ add_library(sink_test SHARED testimplementations.cpp getrssusage.cpp mailtest.cp
16qt5_use_modules(sink_test Core Test Concurrent) 16qt5_use_modules(sink_test Core Test Concurrent)
17target_link_libraries(sink_test sink libhawd KF5::Mime) 17target_link_libraries(sink_test sink libhawd KF5::Mime)
18 18
19add_executable(dbwriter dbwriter.cpp)
20target_link_libraries(dbwriter sink)
21
19include(SinkTest) 22include(SinkTest)
20 23
21manual_tests ( 24manual_tests (
diff --git a/tests/dbwriter.cpp b/tests/dbwriter.cpp
new file mode 100644
index 0000000..902a607
--- /dev/null
+++ b/tests/dbwriter.cpp
@@ -0,0 +1,45 @@
1#include <QByteArrayList>
2#include <QDebug>
3#include <storage.h>
4
5int main(int argc, char *argv[])
6{
7
8 QByteArrayList arguments;
9 for (int i = 0; i < argc; i++) {
10 arguments << argv[i];
11 }
12 auto testDataPath = arguments.value(1);
13 auto dbName = arguments.value(2);
14 auto count = arguments.value(3).toInt();
15
16 if (Sink::Storage::DataStore(testDataPath, dbName, Sink::Storage::DataStore::ReadOnly).exists()) {
17 Sink::Storage::DataStore(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite).removeFromDisk();
18 }
19
20 qWarning() << "Creating db: " << testDataPath << dbName << count;
21 Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite);
22 auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite);
23 for (int i = 0; i < count; i++) {
24 if (!transaction) {
25 qWarning() << "No valid transaction";
26 return -1;
27 }
28 transaction.openDatabase("a", nullptr, false).write(QByteArray::number(i), "a");
29 transaction.openDatabase("b", nullptr, false).write(QByteArray::number(i), "b");
30 transaction.openDatabase("c", nullptr, false).write(QByteArray::number(i), "c");
31 transaction.openDatabase("p", nullptr, false).write(QByteArray::number(i), "c");
32 transaction.openDatabase("q", nullptr, false).write(QByteArray::number(i), "c");
33 if (i > (count/2)) {
34 for (int d = 0; d < 40; d++) {
35 transaction.openDatabase("db" + QByteArray::number(d), nullptr, false).write(QByteArray::number(i), "a");
36 }
37 }
38 if ((i % 1000) == 0) {
39 transaction.commit();
40 transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite);
41 }
42 }
43 qWarning() << "Creating db done.";
44 return 0;
45}
diff --git a/tests/domainadaptortest.cpp b/tests/domainadaptortest.cpp
index a17152e..2aed0a9 100644
--- a/tests/domainadaptortest.cpp
+++ b/tests/domainadaptortest.cpp
@@ -14,24 +14,22 @@
14#include "metadata_generated.h" 14#include "metadata_generated.h"
15#include "entity_generated.h" 15#include "entity_generated.h"
16 16
17class TestFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Event, Sink::ApplicationDomain::Buffer::Event, Sink::ApplicationDomain::Buffer::EventBuilder> 17class TestFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Event>
18{ 18{
19public: 19public:
20 TestFactory() 20 TestFactory() = default;
21 {
22 mResourceWriteMapper = QSharedPointer<WritePropertyMapper<Sink::ApplicationDomain::Buffer::EventBuilder>>::create();
23 Sink::ApplicationDomain::TypeImplementation<Sink::ApplicationDomain::Event>::configure(*mResourceWriteMapper);
24 }
25}; 21};
26 22
27class TestMailFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Mail, Sink::ApplicationDomain::Buffer::Mail, Sink::ApplicationDomain::Buffer::MailBuilder> 23class TestMailFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Mail>
28{ 24{
29public: 25public:
30 TestMailFactory() 26 TestMailFactory() = default;
31 { 27};
32 mResourceWriteMapper = QSharedPointer<WritePropertyMapper<Sink::ApplicationDomain::Buffer::MailBuilder>>::create(); 28
33 Sink::ApplicationDomain::TypeImplementation<Sink::ApplicationDomain::Mail>::configure(*mResourceWriteMapper); 29class TestContactFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Contact>
34 } 30{
31public:
32 TestContactFactory() = default;
35}; 33};
36 34
37/** 35/**
@@ -51,7 +49,7 @@ private slots:
51 49
52 void testCreateBufferPart() 50 void testCreateBufferPart()
53 { 51 {
54 auto writeMapper = QSharedPointer<WritePropertyMapper<Sink::ApplicationDomain::Buffer::EventBuilder>>::create(); 52 auto writeMapper = QSharedPointer<PropertyMapper>::create();
55 Sink::ApplicationDomain::TypeImplementation<Sink::ApplicationDomain::Event>::configure(*writeMapper); 53 Sink::ApplicationDomain::TypeImplementation<Sink::ApplicationDomain::Event>::configure(*writeMapper);
56 54
57 Sink::ApplicationDomain::Event event; 55 Sink::ApplicationDomain::Event event;
@@ -104,7 +102,7 @@ private slots:
104 102
105 void testMail() 103 void testMail()
106 { 104 {
107 auto writeMapper = QSharedPointer<WritePropertyMapper<Sink::ApplicationDomain::Buffer::MailBuilder>>::create(); 105 auto writeMapper = QSharedPointer<PropertyMapper>::create();
108 Sink::ApplicationDomain::TypeImplementation<Sink::ApplicationDomain::Mail>::configure(*writeMapper); 106 Sink::ApplicationDomain::TypeImplementation<Sink::ApplicationDomain::Mail>::configure(*writeMapper);
109 107
110 Sink::ApplicationDomain::Mail mail; 108 Sink::ApplicationDomain::Mail mail;
@@ -139,6 +137,43 @@ private slots:
139 } 137 }
140 138
141 } 139 }
140
141 void testContact()
142 {
143 auto writeMapper = QSharedPointer<PropertyMapper>::create();
144 Sink::ApplicationDomain::TypeImplementation<Sink::ApplicationDomain::Contact>::configure(*writeMapper);
145
146 auto binaryData = QByteArray::fromRawData("\xEF\xBF\xBD\x00\xEF\xBF", 5);
147
148 Sink::ApplicationDomain::Contact contact;
149 contact.setPhoto(binaryData);
150 QVERIFY(!contact.getPhoto().isEmpty());
151
152 flatbuffers::FlatBufferBuilder metadataFbb;
153 auto metadataBuilder = Sink::MetadataBuilder(metadataFbb);
154 metadataBuilder.add_revision(1);
155 auto metadataBuffer = metadataBuilder.Finish();
156 Sink::FinishMetadataBuffer(metadataFbb, metadataBuffer);
157
158 flatbuffers::FlatBufferBuilder mailFbb;
159 auto pos = createBufferPart<Sink::ApplicationDomain::Buffer::ContactBuilder, Sink::ApplicationDomain::Buffer::Contact>(contact, mailFbb, *writeMapper);
160 Sink::ApplicationDomain::Buffer::FinishContactBuffer(mailFbb, pos);
161
162 flatbuffers::FlatBufferBuilder fbb;
163 Sink::EntityBuffer::assembleEntityBuffer(
164 fbb, metadataFbb.GetBufferPointer(), metadataFbb.GetSize(), mailFbb.GetBufferPointer(), mailFbb.GetSize(), mailFbb.GetBufferPointer(), mailFbb.GetSize());
165
166 {
167 std::string data(reinterpret_cast<const char *>(fbb.GetBufferPointer()), fbb.GetSize());
168 Sink::EntityBuffer buffer((void *)(data.data()), data.size());
169
170 TestContactFactory factory;
171 auto adaptor = factory.createAdaptor(buffer.entity());
172 Sink::ApplicationDomain::Contact readContact{QByteArray{}, QByteArray{}, 0, adaptor};
173 QCOMPARE(readContact.getPhoto(), contact.getPhoto());
174 }
175
176 }
142}; 177};
143 178
144QTEST_MAIN(DomainAdaptorTest) 179QTEST_MAIN(DomainAdaptorTest)
diff --git a/tests/hawd/state.cpp b/tests/hawd/state.cpp
index dfeef41..a994718 100644
--- a/tests/hawd/state.cpp
+++ b/tests/hawd/state.cpp
@@ -117,7 +117,7 @@ void State::findGitHash()
117{ 117{
118#ifdef HAVE_LIBGIT2 118#ifdef HAVE_LIBGIT2
119 git_libgit2_init(); 119 git_libgit2_init();
120 git_buf root = {0}; 120 git_buf root = GIT_BUF_INIT_CONST(0, 0);
121 int error = git_repository_discover(&root, projectPath().toStdString().data(), 0, NULL); 121 int error = git_repository_discover(&root, projectPath().toStdString().data(), 0, NULL);
122 if (!error) { 122 if (!error) {
123 git_repository *repo = NULL; 123 git_repository *repo = NULL;
diff --git a/tests/interresourcemovetest.cpp b/tests/interresourcemovetest.cpp
index 3ac6ad4..5438bc7 100644
--- a/tests/interresourcemovetest.cpp
+++ b/tests/interresourcemovetest.cpp
@@ -48,7 +48,7 @@ class InterResourceMoveTest : public QObject
48 m.subject(true)->fromUnicodeString(subject, "utf8"); 48 m.subject(true)->fromUnicodeString(subject, "utf8");
49 m.messageID(true)->setIdentifier(uid); 49 m.messageID(true)->setIdentifier(uid);
50 m.assemble(); 50 m.assemble();
51 return m.encodedContent(); 51 return m.encodedContent(true);
52 } 52 }
53 53
54private slots: 54private slots:
diff --git a/tests/mailquerybenchmark.cpp b/tests/mailquerybenchmark.cpp
index b15c8d6..00c156d 100644
--- a/tests/mailquerybenchmark.cpp
+++ b/tests/mailquerybenchmark.cpp
@@ -78,7 +78,7 @@ class MailQueryBenchmark : public QObject
78 entityStore.commitTransaction(); 78 entityStore.commitTransaction();
79 } 79 }
80 80
81 void testLoad(const Sink::Query &query, int count, int expectedSize) 81 void testLoad(const QByteArray &name, const Sink::Query &query, int count, int expectedSize)
82 { 82 {
83 const auto startingRss = getCurrentRSS(); 83 const auto startingRss = getCurrentRSS();
84 84
@@ -124,7 +124,7 @@ class MailQueryBenchmark : public QObject
124 std::cout << "Rss without db [kb]: " << rssWithoutDb / 1024 << std::endl; 124 std::cout << "Rss without db [kb]: " << rssWithoutDb / 1024 << std::endl;
125 std::cout << "Percentage error: " << percentageRssError << std::endl; 125 std::cout << "Percentage error: " << percentageRssError << std::endl;
126 126
127 HAWD::Dataset dataset("mail_query", mHawdState); 127 HAWD::Dataset dataset(QString{"mail_query"} + name, mHawdState);
128 HAWD::Dataset::Row row = dataset.row(); 128 HAWD::Dataset::Row row = dataset.row();
129 row.setValue("rows", list.size()); 129 row.setValue("rows", list.size());
130 row.setValue("queryResultPerMs", (qreal)list.size() / elapsed); 130 row.setValue("queryResultPerMs", (qreal)list.size() / elapsed);
@@ -159,7 +159,7 @@ private slots:
159 query.limit(1000); 159 query.limit(1000);
160 160
161 populateDatabase(50000); 161 populateDatabase(50000);
162 testLoad(query, 50000, query.limit()); 162 testLoad({}, query, 50000, query.limit());
163 } 163 }
164 164
165 void test50kThreadleader() 165 void test50kThreadleader()
@@ -176,7 +176,7 @@ private slots:
176 176
177 int count = 50000; 177 int count = 50000;
178 populateDatabase(count, mailsPerFolder); 178 populateDatabase(count, mailsPerFolder);
179 testLoad(query, count, query.limit()); 179 testLoad("_threadleader", query, count, query.limit());
180 } 180 }
181}; 181};
182 182
diff --git a/tests/mailsynctest.cpp b/tests/mailsynctest.cpp
index c8ba9f1..75454c0 100644
--- a/tests/mailsynctest.cpp
+++ b/tests/mailsynctest.cpp
@@ -33,8 +33,6 @@
33using namespace Sink; 33using namespace Sink;
34using namespace Sink::ApplicationDomain; 34using namespace Sink::ApplicationDomain;
35 35
36SINK_DEBUG_AREA("mailsynctest")
37
38void MailSyncTest::initTestCase() 36void MailSyncTest::initTestCase()
39{ 37{
40 Test::initTest(); 38 Test::initTest();
@@ -226,7 +224,8 @@ void MailSyncTest::testListNewSubFolder()
226 for (const auto &folder : folders) { 224 for (const auto &folder : folders) {
227 names << folder->getName(); 225 names << folder->getName();
228 } 226 }
229 QVERIFY(names.contains("sub1")); 227 ASYNCVERIFY(names.contains("sub1"));
228 return KAsync::null();
230 }); 229 });
231 VERIFYEXEC(job); 230 VERIFYEXEC(job);
232} 231}
@@ -254,7 +253,8 @@ void MailSyncTest::testListRemovedSubFolder()
254 for (const auto &folder : folders) { 253 for (const auto &folder : folders) {
255 names << folder->getName(); 254 names << folder->getName();
256 } 255 }
257 QVERIFY(!names.contains("sub1")); 256 ASYNCVERIFY(!names.contains("sub1"));
257 return KAsync::null();
258 }); 258 });
259 VERIFYEXEC(job); 259 VERIFYEXEC(job);
260} 260}
@@ -270,18 +270,19 @@ void MailSyncTest::testListMails()
270 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier)); 270 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));
271 271
272 auto job = Store::fetchAll<Mail>(query).then([](const QList<Mail::Ptr> &mails) { 272 auto job = Store::fetchAll<Mail>(query).then([](const QList<Mail::Ptr> &mails) {
273 QCOMPARE(mails.size(), 1); 273 ASYNCCOMPARE(mails.size(), 1);
274 auto mail = mails.first(); 274 auto mail = mails.first();
275 QVERIFY(mail->getSubject().startsWith(QString("[Nepomuk] Jenkins build is still unstable"))); 275 ASYNCVERIFY(mail->getSubject().startsWith(QString("[Nepomuk] Jenkins build is still unstable")));
276 const auto data = mail->getMimeMessage(); 276 const auto data = mail->getMimeMessage();
277 QVERIFY(!data.isEmpty()); 277 ASYNCVERIFY(!data.isEmpty());
278 278
279 KMime::Message m; 279 KMime::Message m;
280 m.setContent(data); 280 m.setContent(KMime::CRLFtoLF(data));
281 m.parse(); 281 m.parse();
282 QCOMPARE(mail->getSubject(), m.subject(true)->asUnicodeString()); 282 ASYNCCOMPARE(mail->getSubject(), m.subject(true)->asUnicodeString());
283 QVERIFY(!mail->getFolder().isEmpty()); 283 ASYNCVERIFY(!mail->getFolder().isEmpty());
284 QVERIFY(mail->getDate().isValid()); 284 ASYNCVERIFY(mail->getDate().isValid());
285 return KAsync::null();
285 }); 286 });
286 VERIFYEXEC(job); 287 VERIFYEXEC(job);
287} 288}
@@ -302,10 +303,11 @@ void MailSyncTest::testResyncMails()
302 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier)); 303 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));
303 304
304 auto job = Store::fetchAll<Mail>(query).then([](const QList<Mail::Ptr> &mails) { 305 auto job = Store::fetchAll<Mail>(query).then([](const QList<Mail::Ptr> &mails) {
305 QCOMPARE(mails.size(), 1); 306 ASYNCCOMPARE(mails.size(), 1);
306 auto mail = mails.first(); 307 auto mail = mails.first();
307 QVERIFY(!mail->getSubject().isEmpty()); 308 ASYNCVERIFY(!mail->getSubject().isEmpty());
308 QVERIFY(!mail->getMimeMessagePath().isEmpty()); 309 ASYNCVERIFY(!mail->getMimeMessagePath().isEmpty());
310 return KAsync::null();
309 }); 311 });
310 VERIFYEXEC(job); 312 VERIFYEXEC(job);
311} 313}
@@ -330,7 +332,8 @@ void MailSyncTest::testFetchNewRemovedMessages()
330 332
331 { 333 {
332 auto job = Store::fetchAll<Mail>(query).then([](const QList<Mail::Ptr> &mails) { 334 auto job = Store::fetchAll<Mail>(query).then([](const QList<Mail::Ptr> &mails) {
333 QCOMPARE(mails.size(), 2); 335 ASYNCCOMPARE(mails.size(), 2);
336 return KAsync::null();
334 }); 337 });
335 VERIFYEXEC(job); 338 VERIFYEXEC(job);
336 } 339 }
@@ -342,7 +345,8 @@ void MailSyncTest::testFetchNewRemovedMessages()
342 345
343 { 346 {
344 auto job = Store::fetchAll<Mail>(query).then([](const QList<Mail::Ptr> &mails) { 347 auto job = Store::fetchAll<Mail>(query).then([](const QList<Mail::Ptr> &mails) {
345 QCOMPARE(mails.size(), 1); 348 ASYNCCOMPARE(mails.size(), 1);
349 return KAsync::null();
346 }); 350 });
347 VERIFYEXEC(job); 351 VERIFYEXEC(job);
348 } 352 }
@@ -365,7 +369,8 @@ void MailSyncTest::testFlagChange()
365 369
366 { 370 {
367 auto job = Store::fetchAll<Mail>(query).then([](const QList<Mail::Ptr> &mails) { 371 auto job = Store::fetchAll<Mail>(query).then([](const QList<Mail::Ptr> &mails) {
368 QCOMPARE(mails.size(), 0); 372 ASYNCCOMPARE(mails.size(), 0);
373 return KAsync::null();
369 }); 374 });
370 VERIFYEXEC(job); 375 VERIFYEXEC(job);
371 } 376 }
@@ -378,8 +383,9 @@ void MailSyncTest::testFlagChange()
378 383
379 { 384 {
380 auto job = Store::fetchAll<Mail>(query).then([](const QList<Mail::Ptr> &mails) { 385 auto job = Store::fetchAll<Mail>(query).then([](const QList<Mail::Ptr> &mails) {
381 QCOMPARE(mails.size(), 1); 386 ASYNCCOMPARE(mails.size(), 1);
382 QVERIFY(mails.first()->getImportant()); 387 ASYNCVERIFY(mails.first()->getImportant());
388 return KAsync::null();
383 }); 389 });
384 VERIFYEXEC(job); 390 VERIFYEXEC(job);
385 } 391 }
@@ -394,8 +400,9 @@ void MailSyncTest::testSyncSingleFolder()
394 Folder::Ptr folder; 400 Folder::Ptr folder;
395 { 401 {
396 auto job = Store::fetchAll<Folder>(Sink::Query{}.resourceFilter(mResourceInstanceIdentifier).filter<Folder::Name>("test")).template then([&](const QList<Folder::Ptr> &folders) { 402 auto job = Store::fetchAll<Folder>(Sink::Query{}.resourceFilter(mResourceInstanceIdentifier).filter<Folder::Name>("test")).template then([&](const QList<Folder::Ptr> &folders) {
397 QCOMPARE(folders.size(), 1); 403 ASYNCCOMPARE(folders.size(), 1);
398 folder = folders.first(); 404 folder = folders.first();
405 return KAsync::null();
399 }); 406 });
400 VERIFYEXEC(job); 407 VERIFYEXEC(job);
401 } 408 }
@@ -419,11 +426,13 @@ void MailSyncTest::testSyncSingleMail()
419 Mail::Ptr mail; 426 Mail::Ptr mail;
420 { 427 {
421 auto job = Store::fetchAll<Mail>(Sink::Query{}.resourceFilter(mResourceInstanceIdentifier)).template then([&](const QList<Mail::Ptr> &mails) { 428 auto job = Store::fetchAll<Mail>(Sink::Query{}.resourceFilter(mResourceInstanceIdentifier)).template then([&](const QList<Mail::Ptr> &mails) {
422 QVERIFY(mails.size() >= 1); 429 ASYNCVERIFY(mails.size() >= 1);
423 mail = mails.first(); 430 mail = mails.first();
431 return KAsync::null();
424 }); 432 });
425 VERIFYEXEC(job); 433 VERIFYEXEC(job);
426 } 434 }
435 QVERIFY(mail);
427 436
428 auto syncScope = Sink::SyncScope{ApplicationDomain::getTypeName<Mail>()}; 437 auto syncScope = Sink::SyncScope{ApplicationDomain::getTypeName<Mail>()};
429 syncScope.resourceFilter(mResourceInstanceIdentifier); 438 syncScope.resourceFilter(mResourceInstanceIdentifier);
diff --git a/tests/mailtest.cpp b/tests/mailtest.cpp
index c51fc56..cbb56d5 100644
--- a/tests/mailtest.cpp
+++ b/tests/mailtest.cpp
@@ -31,8 +31,6 @@
31using namespace Sink; 31using namespace Sink;
32using namespace Sink::ApplicationDomain; 32using namespace Sink::ApplicationDomain;
33 33
34SINK_DEBUG_AREA("mailtest")
35
36void MailTest::initTestCase() 34void MailTest::initTestCase()
37{ 35{
38 Test::initTest(); 36 Test::initTest();
@@ -154,7 +152,7 @@ void MailTest::testCreateModifyDeleteMail()
154 message->assemble(); 152 message->assemble();
155 153
156 auto mail = Mail::create(mResourceInstanceIdentifier); 154 auto mail = Mail::create(mResourceInstanceIdentifier);
157 mail.setMimeMessage(message->encodedContent()); 155 mail.setMimeMessage(message->encodedContent(true));
158 mail.setFolder(folder); 156 mail.setFolder(folder);
159 157
160 VERIFYEXEC(Store::create(mail)); 158 VERIFYEXEC(Store::create(mail));
@@ -168,7 +166,7 @@ void MailTest::testCreateModifyDeleteMail()
168 QCOMPARE(mail.getFolder(), folder.identifier()); 166 QCOMPARE(mail.getFolder(), folder.identifier());
169 QVERIFY(QFile(mail.getMimeMessagePath()).exists()); 167 QVERIFY(QFile(mail.getMimeMessagePath()).exists());
170 KMime::Message m; 168 KMime::Message m;
171 m.setContent(mail.getMimeMessage()); 169 m.setContent(KMime::CRLFtoLF(mail.getMimeMessage()));
172 m.parse(); 170 m.parse();
173 QCOMPARE(m.subject(true)->asUnicodeString(), subject); 171 QCOMPARE(m.subject(true)->asUnicodeString(), subject);
174 }); 172 });
@@ -184,7 +182,7 @@ void MailTest::testCreateModifyDeleteMail()
184 auto message2 = KMime::Message::Ptr::create(); 182 auto message2 = KMime::Message::Ptr::create();
185 message2->subject(true)->fromUnicodeString(subject2, "utf8"); 183 message2->subject(true)->fromUnicodeString(subject2, "utf8");
186 message2->assemble(); 184 message2->assemble();
187 mail.setMimeMessage(message2->encodedContent()); 185 mail.setMimeMessage(message2->encodedContent(true));
188 186
189 VERIFYEXEC(Store::modify(mail)); 187 VERIFYEXEC(Store::modify(mail));
190 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier)); 188 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));
@@ -197,7 +195,7 @@ void MailTest::testCreateModifyDeleteMail()
197 QCOMPARE(mail.getFolder(), folder.identifier()); 195 QCOMPARE(mail.getFolder(), folder.identifier());
198 QVERIFY(QFile(mail.getMimeMessagePath()).exists()); 196 QVERIFY(QFile(mail.getMimeMessagePath()).exists());
199 KMime::Message m; 197 KMime::Message m;
200 m.setContent(mail.getMimeMessage()); 198 m.setContent(KMime::CRLFtoLF(mail.getMimeMessage()));
201 m.parse(); 199 m.parse();
202 QCOMPARE(m.subject(true)->asUnicodeString(), subject2); 200 QCOMPARE(m.subject(true)->asUnicodeString(), subject2);
203 }); 201 });
@@ -239,7 +237,7 @@ void MailTest::testMoveMail()
239 message->assemble(); 237 message->assemble();
240 238
241 auto mail = Mail::create(mResourceInstanceIdentifier); 239 auto mail = Mail::create(mResourceInstanceIdentifier);
242 mail.setMimeMessage(message->encodedContent()); 240 mail.setMimeMessage(message->encodedContent(true));
243 mail.setFolder(folder); 241 mail.setFolder(folder);
244 242
245 VERIFYEXEC(Store::create(mail)); 243 VERIFYEXEC(Store::create(mail));
@@ -291,7 +289,7 @@ void MailTest::testMarkMailAsRead()
291 message->assemble(); 289 message->assemble();
292 290
293 auto mail = Mail::create(mResourceInstanceIdentifier); 291 auto mail = Mail::create(mResourceInstanceIdentifier);
294 mail.setMimeMessage(message->encodedContent()); 292 mail.setMimeMessage(message->encodedContent(true));
295 mail.setFolder(folder); 293 mail.setFolder(folder);
296 mail.setUnread(true); 294 mail.setUnread(true);
297 VERIFYEXEC(Store::create(mail)); 295 VERIFYEXEC(Store::create(mail));
@@ -343,7 +341,7 @@ void MailTest::testCreateDraft()
343 message->assemble(); 341 message->assemble();
344 342
345 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier); 343 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier);
346 mail.setMimeMessage(message->encodedContent()); 344 mail.setMimeMessage(message->encodedContent(true));
347 mail.setDraft(true); 345 mail.setDraft(true);
348 346
349 VERIFYEXEC(Store::create(mail)); 347 VERIFYEXEC(Store::create(mail));
@@ -393,7 +391,7 @@ void MailTest::testModifyMailToDraft()
393 message->assemble(); 391 message->assemble();
394 392
395 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier); 393 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier);
396 mail.setMimeMessage(message->encodedContent()); 394 mail.setMimeMessage(message->encodedContent(true));
397 mail.setDraft(false); 395 mail.setDraft(false);
398 mail.setFolder(folder); 396 mail.setFolder(folder);
399 397
@@ -440,7 +438,7 @@ void MailTest::testModifyMailToTrash()
440 message->assemble(); 438 message->assemble();
441 439
442 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier); 440 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier);
443 mail.setMimeMessage(message->encodedContent()); 441 mail.setMimeMessage(message->encodedContent(true));
444 mail.setTrash(false); 442 mail.setTrash(false);
445 mail.setFolder(folder); 443 mail.setFolder(folder);
446 444
diff --git a/tests/mailthreadtest.cpp b/tests/mailthreadtest.cpp
index 02b29f6..741eb78 100644
--- a/tests/mailthreadtest.cpp
+++ b/tests/mailthreadtest.cpp
@@ -117,7 +117,7 @@ void MailThreadTest::testIndexInMixedOrder()
117 117
118 { 118 {
119 auto mail = Mail::create(mResourceInstanceIdentifier); 119 auto mail = Mail::create(mResourceInstanceIdentifier);
120 mail.setMimeMessage(message3->encodedContent()); 120 mail.setMimeMessage(message3->encodedContent(true));
121 mail.setFolder(folder); 121 mail.setFolder(folder);
122 VERIFYEXEC(Store::create(mail)); 122 VERIFYEXEC(Store::create(mail));
123 } 123 }
@@ -140,7 +140,7 @@ void MailThreadTest::testIndexInMixedOrder()
140 140
141 { 141 {
142 auto mail = Mail::create(mResourceInstanceIdentifier); 142 auto mail = Mail::create(mResourceInstanceIdentifier);
143 mail.setMimeMessage(message2->encodedContent()); 143 mail.setMimeMessage(message2->encodedContent(true));
144 mail.setFolder(folder); 144 mail.setFolder(folder);
145 VERIFYEXEC(Store::create(mail)); 145 VERIFYEXEC(Store::create(mail));
146 } 146 }
@@ -156,7 +156,7 @@ void MailThreadTest::testIndexInMixedOrder()
156 156
157 { 157 {
158 auto mail = Mail::create(mResourceInstanceIdentifier); 158 auto mail = Mail::create(mResourceInstanceIdentifier);
159 mail.setMimeMessage(message1->encodedContent()); 159 mail.setMimeMessage(message1->encodedContent(true));
160 mail.setFolder(folder); 160 mail.setFolder(folder);
161 VERIFYEXEC(Store::create(mail)); 161 VERIFYEXEC(Store::create(mail));
162 } 162 }
diff --git a/tests/messagequeuetest.cpp b/tests/messagequeuetest.cpp
index 83fa23f..2e3bd75 100644
--- a/tests/messagequeuetest.cpp
+++ b/tests/messagequeuetest.cpp
@@ -9,8 +9,6 @@
9#include "log.h" 9#include "log.h"
10#include "test.h" 10#include "test.h"
11 11
12SINK_DEBUG_AREA("messagequeuetest")
13
14/** 12/**
15 * Test of the messagequeue implementation. 13 * Test of the messagequeue implementation.
16 */ 14 */
diff --git a/tests/notificationtest.cpp b/tests/notificationtest.cpp
index a34d325..f0d957e 100644
--- a/tests/notificationtest.cpp
+++ b/tests/notificationtest.cpp
@@ -67,12 +67,21 @@ private slots:
67 VERIFYEXEC(Sink::Store::synchronize(query)); 67 VERIFYEXEC(Sink::Store::synchronize(query));
68 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1")); 68 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
69 69
70 QVERIFY(statusNotifications.size() <= 3); 70 using namespace Sink::ApplicationDomain;
71 QTRY_COMPARE(statusNotifications.size(), 3); 71 {
72 //Sync 72 QList<Status> expected = {
73 QCOMPARE(statusNotifications.at(0).code, static_cast<int>(ApplicationDomain::Status::ConnectedStatus)); 73 Status::ConnectedStatus,
74 QCOMPARE(statusNotifications.at(1).code, static_cast<int>(ApplicationDomain::Status::BusyStatus)); 74 Status::BusyStatus,
75 QCOMPARE(statusNotifications.at(2).code, static_cast<int>(ApplicationDomain::Status::ConnectedStatus)); 75 Status::ConnectedStatus,
76 };
77 qInfo() << "Received notifications " << statusNotifications;
78 QVERIFY2(statusNotifications.size() <= expected.size(), "More notifications than expected.");
79 QTRY_COMPARE(statusNotifications.size(), expected.size());
80 qInfo() << "All received notifications " << statusNotifications;
81 for (auto i = 0; i < statusNotifications.size(); i++) {
82 QCOMPARE(statusNotifications.at(i).code, static_cast<int>(expected.at(i)));
83 }
84 }
76 //Changereplay 85 //Changereplay
77 // It can happen that we get a changereplay notification pair first and then a second one at the end, 86 // It can happen that we get a changereplay notification pair first and then a second one at the end,
78 // we therefore currently filter all changereplay notifications (see above). 87 // we therefore currently filter all changereplay notifications (see above).
diff --git a/tests/querytest.cpp b/tests/querytest.cpp
index f639d94..714e549 100644
--- a/tests/querytest.cpp
+++ b/tests/querytest.cpp
@@ -98,10 +98,7 @@ private slots:
98 void testSingle() 98 void testSingle()
99 { 99 {
100 // Setup 100 // Setup
101 { 101 VERIFYEXEC(Sink::Store::create<Mail>(Mail("sink.dummy.instance1")));
102 Mail mail("sink.dummy.instance1");
103 VERIFYEXEC(Sink::Store::create<Mail>(mail));
104 }
105 102
106 // Test 103 // Test
107 Sink::Query query; 104 Sink::Query query;
@@ -116,10 +113,7 @@ private slots:
116 void testSingleWithDelay() 113 void testSingleWithDelay()
117 { 114 {
118 // Setup 115 // Setup
119 { 116 VERIFYEXEC(Sink::Store::create<Mail>(Mail("sink.dummy.instance1")));
120 Mail mail("sink.dummy.instance1");
121 Sink::Store::create<Mail>(mail).exec().waitForFinished();
122 }
123 117
124 // Test 118 // Test
125 Sink::Query query; 119 Sink::Query query;
@@ -142,13 +136,13 @@ private slots:
142 Mail mail("sink.dummy.instance1"); 136 Mail mail("sink.dummy.instance1");
143 mail.setExtractedMessageId("test1"); 137 mail.setExtractedMessageId("test1");
144 mail.setFolder("folder1"); 138 mail.setFolder("folder1");
145 Sink::Store::create<Mail>(mail).exec().waitForFinished(); 139 VERIFYEXEC(Sink::Store::create<Mail>(mail));
146 } 140 }
147 { 141 {
148 Mail mail("sink.dummy.instance1"); 142 Mail mail("sink.dummy.instance1");
149 mail.setExtractedMessageId("test2"); 143 mail.setExtractedMessageId("test2");
150 mail.setFolder("folder2"); 144 mail.setFolder("folder2");
151 Sink::Store::create<Mail>(mail).exec().waitForFinished(); 145 VERIFYEXEC(Sink::Store::create<Mail>(mail));
152 } 146 }
153 147
154 // Test 148 // Test
@@ -164,13 +158,13 @@ private slots:
164 auto mail = model->index(0, 0, QModelIndex()).data(Sink::Store::DomainObjectRole).value<Mail::Ptr>(); 158 auto mail = model->index(0, 0, QModelIndex()).data(Sink::Store::DomainObjectRole).value<Mail::Ptr>();
165 { 159 {
166 mail->setFolder("folder2"); 160 mail->setFolder("folder2");
167 Sink::Store::modify<Mail>(*mail).exec().waitForFinished(); 161 VERIFYEXEC(Sink::Store::modify<Mail>(*mail));
168 } 162 }
169 QTRY_COMPARE(model->rowCount(), 0); 163 QTRY_COMPARE(model->rowCount(), 0);
170 164
171 { 165 {
172 mail->setFolder("folder1"); 166 mail->setFolder("folder1");
173 Sink::Store::modify<Mail>(*mail).exec().waitForFinished(); 167 VERIFYEXEC(Sink::Store::modify<Mail>(*mail));
174 } 168 }
175 QTRY_COMPARE(model->rowCount(), 1); 169 QTRY_COMPARE(model->rowCount(), 1);
176 } 170 }
@@ -181,8 +175,8 @@ private slots:
181 // Setup 175 // Setup
182 { 176 {
183 Mail mail("sink.dummy.instance1"); 177 Mail mail("sink.dummy.instance1");
184 Sink::Store::create<Mail>(mail).exec().waitForFinished(); 178 VERIFYEXEC(Sink::Store::create<Mail>(mail));
185 Sink::Store::create<Mail>(mail).exec().waitForFinished(); 179 VERIFYEXEC(Sink::Store::create<Mail>(mail));
186 180
187 Sink::Query query; 181 Sink::Query query;
188 query.resourceFilter("sink.dummy.instance1"); 182 query.resourceFilter("sink.dummy.instance1");
@@ -211,7 +205,7 @@ private slots:
211 // Setup 205 // Setup
212 { 206 {
213 Folder folder("sink.dummy.instance1"); 207 Folder folder("sink.dummy.instance1");
214 Sink::Store::create<Folder>(folder).exec().waitForFinished(); 208 VERIFYEXEC(Sink::Store::create<Folder>(folder));
215 } 209 }
216 210
217 // Test 211 // Test
@@ -387,6 +381,7 @@ private slots:
387 { 381 {
388 // Setup 382 // Setup
389 Folder::Ptr folderEntity; 383 Folder::Ptr folderEntity;
384 const auto date = QDateTime(QDate(2015, 7, 7), QTime(12, 0));
390 { 385 {
391 Folder folder("sink.dummy.instance1"); 386 Folder folder("sink.dummy.instance1");
392 Sink::Store::create<Folder>(folder).exec().waitForFinished(); 387 Sink::Store::create<Folder>(folder).exec().waitForFinished();
@@ -404,7 +399,6 @@ private slots:
404 folderEntity = model->index(0, 0).data(Sink::Store::DomainObjectRole).value<Folder::Ptr>(); 399 folderEntity = model->index(0, 0).data(Sink::Store::DomainObjectRole).value<Folder::Ptr>();
405 QVERIFY(!folderEntity->identifier().isEmpty()); 400 QVERIFY(!folderEntity->identifier().isEmpty());
406 401
407 const auto date = QDateTime(QDate(2015, 7, 7), QTime(12, 0));
408 { 402 {
409 Mail mail("sink.dummy.instance1"); 403 Mail mail("sink.dummy.instance1");
410 mail.setExtractedMessageId("testSecond"); 404 mail.setExtractedMessageId("testSecond");
@@ -434,6 +428,11 @@ private slots:
434 query.filter<Mail::Folder>(*folderEntity); 428 query.filter<Mail::Folder>(*folderEntity);
435 query.sort<Mail::Date>(); 429 query.sort<Mail::Date>();
436 query.limit(1); 430 query.limit(1);
431 query.setFlags(Query::LiveQuery);
432 query.reduce<ApplicationDomain::Mail::ThreadId>(Query::Reduce::Selector::max<ApplicationDomain::Mail::Date>())
433 .count("count")
434 .collect<ApplicationDomain::Mail::Unread>("unreadCollected")
435 .collect<ApplicationDomain::Mail::Important>("importantCollected");
437 436
438 // Ensure all local data is processed 437 // Ensure all local data is processed
439 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1")); 438 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
@@ -449,6 +448,26 @@ private slots:
449 QCOMPARE(model->rowCount(), 2); 448 QCOMPARE(model->rowCount(), 2);
450 // We can't make any assumptions about the order of the indexes 449 // We can't make any assumptions about the order of the indexes
451 // QCOMPARE(model->index(1, 0).data(Sink::Store::DomainObjectRole).value<Mail::Ptr>()->getProperty("messageId").toByteArray(), QByteArray("testSecond")); 450 // QCOMPARE(model->index(1, 0).data(Sink::Store::DomainObjectRole).value<Mail::Ptr>()->getProperty("messageId").toByteArray(), QByteArray("testSecond"));
451
452 //New revisions always go through
453 {
454 Mail mail("sink.dummy.instance1");
455 mail.setExtractedMessageId("testInjected");
456 mail.setFolder(folderEntity->identifier());
457 mail.setExtractedDate(date.addDays(-2));
458 Sink::Store::create<Mail>(mail).exec().waitForFinished();
459 }
460 QTRY_COMPARE(model->rowCount(), 3);
461
462 //Ensure we can continue fetching after the incremental update
463 model->fetchMore(QModelIndex());
464 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
465 QCOMPARE(model->rowCount(), 4);
466
467 //Ensure we have fetched all
468 model->fetchMore(QModelIndex());
469 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
470 QCOMPARE(model->rowCount(), 4);
452 } 471 }
453 472
454 void testReactToNewResource() 473 void testReactToNewResource()
@@ -503,12 +522,13 @@ private slots:
503 Folder folder2(resource2.identifier()); 522 Folder folder2(resource2.identifier());
504 VERIFYEXEC(Sink::Store::create<Folder>(folder2)); 523 VERIFYEXEC(Sink::Store::create<Folder>(folder2));
505 } 524 }
525 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << resource1.identifier()));
526 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << resource2.identifier()));
506 527
507 // Test 528 // Test
508 Sink::Query query; 529 Sink::Query query;
509 query.resourceFilter<SinkResource::Account>(account1); 530 query.resourceFilter<SinkResource::Account>(account1);
510 531
511 // We fetch before the data is available and rely on the live query mechanism to deliver the actual data
512 auto folders = Sink::Store::read<Folder>(query); 532 auto folders = Sink::Store::read<Folder>(query);
513 QCOMPARE(folders.size(), 1); 533 QCOMPARE(folders.size(), 1);
514 } 534 }
@@ -621,18 +641,50 @@ private slots:
621 resource2.setResourceType("sink.dummy"); 641 resource2.setResourceType("sink.dummy");
622 VERIFYEXEC(Store::create(resource2)); 642 VERIFYEXEC(Store::create(resource2));
623 643
624 Folder folder1(resource1.identifier()); 644 VERIFYEXEC(Sink::Store::create<Folder>(Folder{resource1.identifier()}));
625 VERIFYEXEC(Sink::Store::create<Folder>(folder1)); 645 VERIFYEXEC(Sink::Store::create<Folder>(Folder{resource2.identifier()}));
626 Folder folder2(resource2.identifier());
627 VERIFYEXEC(Sink::Store::create<Folder>(folder2));
628 646
629 // Test 647 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(resource1.identifier()));
630 Sink::Query query; 648 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(resource2.identifier()));
631 query.resourceContainsFilter<SinkResource::Capabilities>("cap1");
632 649
633 // We fetch before the data is available and rely on the live query mechanism to deliver the actual data 650 // We fetch before the data is available and rely on the live query mechanism to deliver the actual data
634 auto folders = Sink::Store::read<Folder>(query); 651 auto folders = Sink::Store::read<Folder>(Sink::Query{}.resourceContainsFilter<SinkResource::Capabilities>("cap1"));
635 QCOMPARE(folders.size(), 1); 652 QCOMPARE(folders.size(), 1);
653
654 //TODO this should be part of the regular cleanup between tests
655 VERIFYEXEC(Store::remove(resource1));
656 VERIFYEXEC(Store::remove(resource2));
657 }
658
659 void testFilteredLiveResourceSubQuery()
660 {
661 using namespace Sink;
662 using namespace Sink::ApplicationDomain;
663
664 //Setup
665 auto resource1 = ApplicationDomainType::createEntity<SinkResource>();
666 resource1.setResourceType("sink.dummy");
667 resource1.setCapabilities(QByteArrayList() << "cap1");
668 VERIFYEXEC(Store::create(resource1));
669 VERIFYEXEC(Store::create<Folder>(Folder{resource1.identifier()}));
670 VERIFYEXEC(ResourceControl::flushMessageQueue(resource1.identifier()));
671
672 auto model = Sink::Store::loadModel<Folder>(Query{Query::LiveQuery}.resourceContainsFilter<SinkResource::Capabilities>("cap1"));
673 QTRY_COMPARE(model->rowCount(), 1);
674
675 auto resource2 = ApplicationDomainType::createEntity<SinkResource>();
676 resource2.setCapabilities(QByteArrayList() << "cap2");
677 resource2.setResourceType("sink.dummy");
678 VERIFYEXEC(Store::create(resource2));
679 VERIFYEXEC(Store::create<Folder>(Folder{resource2.identifier()}));
680 VERIFYEXEC(ResourceControl::flushMessageQueue(resource2.identifier()));
681
682 //The new resource should be filtered and thus not make it in here
683 QCOMPARE(model->rowCount(), 1);
684
685 //TODO this should be part of the regular cleanup between tests
686 VERIFYEXEC(Store::remove(resource1));
687 VERIFYEXEC(Store::remove(resource2));
636 } 688 }
637 689
638 void testLivequeryUnmatchInThread() 690 void testLivequeryUnmatchInThread()
@@ -1059,30 +1111,18 @@ private slots:
1059 QDateTime now{QDate{2017, 2, 3}, QTime{10, 0, 0}}; 1111 QDateTime now{QDate{2017, 2, 3}, QTime{10, 0, 0}};
1060 QDateTime later{QDate{2017, 2, 3}, QTime{11, 0, 0}}; 1112 QDateTime later{QDate{2017, 2, 3}, QTime{11, 0, 0}};
1061 1113
1062 { 1114 auto createMail = [] (const QByteArray &messageid, const Folder &folder, const QDateTime &date, bool important) {
1063 auto mail1 = Mail::createEntity<Mail>("sink.dummy.instance1"); 1115 auto mail = Mail::createEntity<Mail>("sink.dummy.instance1");
1064 mail1.setExtractedMessageId("mail1"); 1116 mail.setExtractedMessageId(messageid);
1065 mail1.setFolder(folder1); 1117 mail.setFolder(folder);
1066 mail1.setExtractedDate(now); 1118 mail.setExtractedDate(date);
1067 mail1.setImportant(false); 1119 mail.setImportant(important);
1068 VERIFYEXEC(Sink::Store::create(mail1)); 1120 return mail;
1069 } 1121 };
1070 { 1122
1071 auto mail2 = Mail::createEntity<Mail>("sink.dummy.instance1"); 1123 VERIFYEXEC(Sink::Store::create(createMail("mail1", folder1, now, false)));
1072 mail2.setExtractedMessageId("mail2"); 1124 VERIFYEXEC(Sink::Store::create(createMail("mail2", folder1, earlier, false)));
1073 mail2.setFolder(folder1); 1125 VERIFYEXEC(Sink::Store::create(createMail("mail3", folder1, later, true)));
1074 mail2.setExtractedDate(earlier);
1075 mail2.setImportant(false);
1076 VERIFYEXEC(Sink::Store::create(mail2));
1077 }
1078 {
1079 auto mail3 = Mail::createEntity<Mail>("sink.dummy.instance1");
1080 mail3.setExtractedMessageId("mail3");
1081 mail3.setFolder(folder1);
1082 mail3.setExtractedDate(later);
1083 mail3.setImportant(true);
1084 VERIFYEXEC(Sink::Store::create(mail3));
1085 }
1086 1126
1087 // Ensure all local data is processed 1127 // Ensure all local data is processed
1088 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1")); 1128 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
diff --git a/tests/storagetest.cpp b/tests/storagetest.cpp
index 7202628..9e9bad9 100644
--- a/tests/storagetest.cpp
+++ b/tests/storagetest.cpp
@@ -487,6 +487,61 @@ private slots:
487 } 487 }
488 } 488 }
489 489
490 void testCopyTransaction()
491 {
492 Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite);
493 {
494 auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite);
495 transaction.openDatabase("a", nullptr, false);
496 transaction.openDatabase("b", nullptr, false);
497 transaction.openDatabase("c", nullptr, false);
498 transaction.commit();
499 }
500 auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadOnly);
501 for (int i = 0; i < 1000; i++) {
502 transaction.openDatabase("a", nullptr, false);
503 transaction.openDatabase("b", nullptr, false);
504 transaction.openDatabase("c", nullptr, false);
505 transaction = store.createTransaction(Sink::Storage::DataStore::ReadOnly);
506 }
507 }
508
509 /*
510 * This test is meant to find problems with the multi-process architecture and initial database creation.
511 * If we create named databases dynamically (not all up front), it is possilbe that we violate the rule
512 * that mdb_open_dbi may only be used by a single thread at a time.
513 * This test is meant to stress that condition.
514 *
515 * However, it yields absolutely nothing.
516 */
517 void testReadDuringExternalProcessWrite()
518 {
519 QSKIP("Not running multiprocess test");
520
521 QList<QFuture<void>> futures;
522 for (int i = 0; i < 5; i++) {
523 futures << QtConcurrent::run([&]() {
524 QTRY_VERIFY(Sink::Storage::DataStore(testDataPath, dbName, Sink::Storage::DataStore::ReadOnly).exists());
525 Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadOnly);
526 auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadOnly);
527 for (int i = 0; i < 100000; i++) {
528 transaction.openDatabase("a", nullptr, false);
529 transaction.openDatabase("b", nullptr, false);
530 transaction.openDatabase("c", nullptr, false);
531 transaction.openDatabase("p", nullptr, false);
532 transaction.openDatabase("q", nullptr, false);
533 }
534 });
535 }
536
537 //Start writing to the db from a separate process
538 QVERIFY(QProcess::startDetached(QCoreApplication::applicationDirPath() + "/dbwriter", QStringList() << testDataPath << dbName << QString::number(100000)));
539
540 for (auto future : futures) {
541 future.waitForFinished();
542 }
543
544 }
490}; 545};
491 546
492QTEST_MAIN(StorageTest) 547QTEST_MAIN(StorageTest)
diff --git a/tests/testimplementations.h b/tests/testimplementations.h
index a145265..ff9d9b8 100644
--- a/tests/testimplementations.h
+++ b/tests/testimplementations.h
@@ -33,7 +33,7 @@
33#include "mail_generated.h" 33#include "mail_generated.h"
34#include "createentity_generated.h" 34#include "createentity_generated.h"
35 35
36class TestEventAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Event, Sink::ApplicationDomain::Buffer::Event, Sink::ApplicationDomain::Buffer::EventBuilder> 36class TestEventAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Event>
37{ 37{
38public: 38public:
39 TestEventAdaptorFactory() : DomainTypeAdaptorFactory() 39 TestEventAdaptorFactory() : DomainTypeAdaptorFactory()
@@ -43,7 +43,7 @@ public:
43 virtual ~TestEventAdaptorFactory(){}; 43 virtual ~TestEventAdaptorFactory(){};
44}; 44};
45 45
46class TestMailAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Mail, Sink::ApplicationDomain::Buffer::Mail, Sink::ApplicationDomain::Buffer::MailBuilder> 46class TestMailAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Mail>
47{ 47{
48public: 48public:
49 TestMailAdaptorFactory() : DomainTypeAdaptorFactory() 49 TestMailAdaptorFactory() : DomainTypeAdaptorFactory()