summaryrefslogtreecommitdiffstats
path: root/common
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2017-09-13 12:42:31 +0200
committerChristian Mollekopf <chrigi_1@fastmail.fm>2017-09-13 12:42:31 +0200
commitc12a9a09da59b9e418316dba02e6215cb55e47ee (patch)
tree05498d9a42e399bcca787f40c1fc473fb09e680e /common
parent55fe06979ceebe67553135b43aa47e70d931304b (diff)
parentebdb89b8bb482bbb5ecd544c3d38bef35fc7d820 (diff)
downloadsink-0.4.0.tar.gz
sink-0.4.0.zip
Merge commit 'ebdb89b8bb482bbb5ecd544c3d38bef35fc7d820'v0.4.0
Diffstat (limited to 'common')
-rw-r--r--common/CMakeLists.txt4
-rw-r--r--common/asyncutils.h2
-rw-r--r--common/definitions.cpp18
-rw-r--r--common/definitions.h8
-rw-r--r--common/domain/applicationdomaintype.cpp17
-rw-r--r--common/domain/applicationdomaintype.h15
-rw-r--r--common/domain/applicationdomaintype_p.h2
-rw-r--r--common/log.cpp50
-rw-r--r--common/mail/threadindexer.cpp30
-rw-r--r--common/mailpreprocessor.cpp22
-rw-r--r--common/modelresult.cpp10
-rw-r--r--common/notification.cpp2
-rw-r--r--common/notifier.cpp4
-rw-r--r--common/resource.cpp8
-rw-r--r--common/resourceaccess.cpp2
-rw-r--r--common/resourcefacade.cpp53
-rw-r--r--common/resourcefacade.h1
-rw-r--r--common/storage.h6
-rw-r--r--common/storage/entitystore.cpp12
-rw-r--r--common/storage_common.cpp12
-rw-r--r--common/storage_lmdb.cpp31
-rw-r--r--common/store.cpp44
-rw-r--r--common/store.h3
-rw-r--r--common/synchronizer.cpp19
24 files changed, 271 insertions, 104 deletions
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 001a412..8421fc2 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -116,13 +116,13 @@ SET_TARGET_PROPERTIES(${PROJECT_NAME}
116 EXPORT_NAME ${PROJECT_NAME} 116 EXPORT_NAME ${PROJECT_NAME}
117) 117)
118 118
119qt5_use_modules(${PROJECT_NAME} LINK_PUBLIC Network)
120qt5_use_modules(${PROJECT_NAME} LINK_PRIVATE Gui)
121target_link_libraries(${PROJECT_NAME} 119target_link_libraries(${PROJECT_NAME}
122PUBLIC 120PUBLIC
123 KAsync 121 KAsync
122 Qt5::Network
124PRIVATE 123PRIVATE
125 ${storage_LIBS} 124 ${storage_LIBS}
125 Qt5::Gui
126 KF5::Mime 126 KF5::Mime
127 KF5::Contacts 127 KF5::Contacts
128) 128)
diff --git a/common/asyncutils.h b/common/asyncutils.h
index 67b5928..c80af30 100644
--- a/common/asyncutils.h
+++ b/common/asyncutils.h
@@ -31,12 +31,12 @@ KAsync::Job<T> run(const std::function<T()> &f, bool runAsync = true)
31 return KAsync::start<T>([f](KAsync::Future<T> &future) { 31 return KAsync::start<T>([f](KAsync::Future<T> &future) {
32 auto result = QtConcurrent::run(f); 32 auto result = QtConcurrent::run(f);
33 auto watcher = new QFutureWatcher<T>; 33 auto watcher = new QFutureWatcher<T>;
34 watcher->setFuture(result);
35 QObject::connect(watcher, &QFutureWatcher<T>::finished, watcher, [&future, watcher]() { 34 QObject::connect(watcher, &QFutureWatcher<T>::finished, watcher, [&future, watcher]() {
36 future.setValue(watcher->future().result()); 35 future.setValue(watcher->future().result());
37 delete watcher; 36 delete watcher;
38 future.setFinished(); 37 future.setFinished();
39 }); 38 });
39 watcher->setFuture(result);
40 }); 40 });
41 } else { 41 } else {
42 return KAsync::start<T>([f]() { 42 return KAsync::start<T>([f]() {
diff --git a/common/definitions.cpp b/common/definitions.cpp
index 17977bc..ee18d52 100644
--- a/common/definitions.cpp
+++ b/common/definitions.cpp
@@ -39,11 +39,17 @@ QString Sink::storageLocation()
39 return dataLocation() + "/storage"; 39 return dataLocation() + "/storage";
40} 40}
41 41
42static QString sinkLocation(QStandardPaths::StandardLocation location)
43{
44 return QStandardPaths::writableLocation(location) + "/sink";
45}
46
42QString Sink::dataLocation() 47QString Sink::dataLocation()
43{ 48{
44 static QString location; 49 static QString location = sinkLocation(QStandardPaths::GenericDataLocation);
50 //Warning: This is not threadsafe, but clearLocationCache is only ever used in testcode. The initialization above is required to make at least the initialization threadsafe (relies on C++11 threadsafe initialization).
45 if (rereadDataLocation) { 51 if (rereadDataLocation) {
46 location = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/sink"; 52 location = sinkLocation(QStandardPaths::GenericDataLocation);
47 rereadDataLocation = false; 53 rereadDataLocation = false;
48 } 54 }
49 return location; 55 return location;
@@ -51,9 +57,10 @@ QString Sink::dataLocation()
51 57
52QString Sink::configLocation() 58QString Sink::configLocation()
53{ 59{
54 static QString location; 60 static QString location = sinkLocation(QStandardPaths::GenericConfigLocation);
61 //Warning: This is not threadsafe, but clearLocationCache is only ever used in testcode. The initialization above is required to make at least the initialization threadsafe (relies on C++11 threadsafe initialization).
55 if (rereadConfigLocation) { 62 if (rereadConfigLocation) {
56 location = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/sink"; 63 location = sinkLocation(QStandardPaths::GenericConfigLocation);
57 rereadConfigLocation = false; 64 rereadConfigLocation = false;
58 } 65 }
59 return location; 66 return location;
@@ -61,8 +68,9 @@ QString Sink::configLocation()
61 68
62QString Sink::temporaryFileLocation() 69QString Sink::temporaryFileLocation()
63{ 70{
64 static QString location; 71 static QString location = dataLocation() + "/temporaryFiles";
65 static bool dirCreated = false; 72 static bool dirCreated = false;
73 //Warning: This is not threadsafe, but clearLocationCache is only ever used in testcode. The initialization above is required to make at least the initialization threadsafe (relies on C++11 threadsafe initialization).
66 if (rereadTemporaryFileLocation) { 74 if (rereadTemporaryFileLocation) {
67 location = dataLocation() + "/temporaryFiles"; 75 location = dataLocation() + "/temporaryFiles";
68 dirCreated = QDir{}.mkpath(location); 76 dirCreated = QDir{}.mkpath(location);
diff --git a/common/definitions.h b/common/definitions.h
index ce9e794..7ef215b 100644
--- a/common/definitions.h
+++ b/common/definitions.h
@@ -25,10 +25,16 @@
25#include <QByteArray> 25#include <QByteArray>
26 26
27namespace Sink { 27namespace Sink {
28void SINK_EXPORT clearLocationCache();
29QString SINK_EXPORT storageLocation(); 28QString SINK_EXPORT storageLocation();
30QString SINK_EXPORT dataLocation(); 29QString SINK_EXPORT dataLocation();
31QString SINK_EXPORT configLocation(); 30QString SINK_EXPORT configLocation();
32QString SINK_EXPORT temporaryFileLocation(); 31QString SINK_EXPORT temporaryFileLocation();
33QString SINK_EXPORT resourceStorageLocation(const QByteArray &resourceInstanceIdentifier); 32QString SINK_EXPORT resourceStorageLocation(const QByteArray &resourceInstanceIdentifier);
33
34/**
35 * Clear the location cache and lookup locations again.
36 *
37 * Warning: Calling this results in non-threadsafe initialization, only use it in test-code.
38 */
39void SINK_EXPORT clearLocationCache();
34} 40}
diff --git a/common/domain/applicationdomaintype.cpp b/common/domain/applicationdomaintype.cpp
index 3718f77..ee70c35 100644
--- a/common/domain/applicationdomaintype.cpp
+++ b/common/domain/applicationdomaintype.cpp
@@ -34,7 +34,13 @@ QDebug Sink::ApplicationDomain::operator<< (QDebug d, const Sink::ApplicationDom
34QDebug Sink::ApplicationDomain::operator<< (QDebug d, const Sink::ApplicationDomain::ApplicationDomainType &type) 34QDebug Sink::ApplicationDomain::operator<< (QDebug d, const Sink::ApplicationDomain::ApplicationDomainType &type)
35{ 35{
36 d << "ApplicationDomainType(\n"; 36 d << "ApplicationDomainType(\n";
37 auto properties = type.mAdaptor->availableProperties(); 37 auto properties = [&] {
38 if (!type.changedProperties().isEmpty()) {
39 return type.changedProperties();
40 } else {
41 return type.mAdaptor->availableProperties();
42 }
43 }();
38 std::sort(properties.begin(), properties.end()); 44 std::sort(properties.begin(), properties.end());
39 d << " " << "Id: " << "\t" << type.identifier() << "\n"; 45 d << " " << "Id: " << "\t" << type.identifier() << "\n";
40 d << " " << "Resource: " << "\t" << type.resourceInstanceIdentifier() << "\n"; 46 d << " " << "Resource: " << "\t" << type.resourceInstanceIdentifier() << "\n";
@@ -216,8 +222,13 @@ QVariant ApplicationDomainType::getProperty(const QByteArray &key) const
216void ApplicationDomainType::setProperty(const QByteArray &key, const QVariant &value) 222void ApplicationDomainType::setProperty(const QByteArray &key, const QVariant &value)
217{ 223{
218 Q_ASSERT(mAdaptor); 224 Q_ASSERT(mAdaptor);
219 mChangeSet->insert(key); 225 auto existing = mAdaptor->getProperty(key);
220 mAdaptor->setProperty(key, value); 226 if (existing.isValid() && existing == value) {
227 SinkTrace() << "Tried to set property that is still the same: " << key << value;
228 } else {
229 mChangeSet->insert(key);
230 mAdaptor->setProperty(key, value);
231 }
221} 232}
222 233
223void ApplicationDomainType::setResource(const QByteArray &identifier) 234void ApplicationDomainType::setResource(const QByteArray &identifier)
diff --git a/common/domain/applicationdomaintype.h b/common/domain/applicationdomaintype.h
index 602d54c..f7fd07e 100644
--- a/common/domain/applicationdomaintype.h
+++ b/common/domain/applicationdomaintype.h
@@ -101,6 +101,7 @@ enum SINK_EXPORT ErrorCode {
101 LoginError, 101 LoginError,
102 ConfigurationError, 102 ConfigurationError,
103 TransmissionError, 103 TransmissionError,
104 ConnectionLostError,
104}; 105};
105 106
106enum SINK_EXPORT SuccessCode { 107enum SINK_EXPORT SuccessCode {
@@ -118,12 +119,14 @@ enum SINK_EXPORT SyncStatus {
118 * The status of an account or resource. 119 * The status of an account or resource.
119 * 120 *
120 * It is set as follows: 121 * It is set as follows:
121 * * By default the status is offline. 122 * * By default the status is no status.
123 * * If a connection to the server failed the status is Offline.
122 * * If a connection to the server could be established the status is Connected. 124 * * If a connection to the server could be established the status is Connected.
123 * * If an error occurred that keeps the resource from operating (so non transient), the resource enters the error state. 125 * * If an error occurred that keeps the resource from operating (so non transient), the resource enters the error state.
124 * * If a long running operation is started the resource goes to the busy state (and return to the previous state after that). 126 * * If a long running operation is started the resource goes to the busy state (and return to the previous state after that).
125 */ 127 */
126enum SINK_EXPORT Status { 128enum SINK_EXPORT Status {
129 NoStatus,
127 OfflineStatus, 130 OfflineStatus,
128 ConnectedStatus, 131 ConnectedStatus,
129 BusyStatus, 132 BusyStatus,
@@ -270,7 +273,17 @@ public:
270 bool hasProperty(const QByteArray &key) const; 273 bool hasProperty(const QByteArray &key) const;
271 274
272 QVariant getProperty(const QByteArray &key) const; 275 QVariant getProperty(const QByteArray &key) const;
276
277 /**
278 * Set a property and record a changed property
279 *
280 * If the propery is available and did not change the call will be ignored.
281 */
273 void setProperty(const QByteArray &key, const QVariant &value); 282 void setProperty(const QByteArray &key, const QVariant &value);
283
284 /**
285 * Convenience method to set a reference property.
286 */
274 void setProperty(const QByteArray &key, const ApplicationDomainType &value); 287 void setProperty(const QByteArray &key, const ApplicationDomainType &value);
275 288
276 QByteArray getBlobProperty(const QByteArray &key) const; 289 QByteArray getBlobProperty(const QByteArray &key) const;
diff --git a/common/domain/applicationdomaintype_p.h b/common/domain/applicationdomaintype_p.h
index a5a6b1d..a60df38 100644
--- a/common/domain/applicationdomaintype_p.h
+++ b/common/domain/applicationdomaintype_p.h
@@ -45,5 +45,7 @@ struct TypeHelper {
45 } else { 45 } else {
46 Q_ASSERT(false); 46 Q_ASSERT(false);
47 } 47 }
48 //Silence compiler warning
49 return Func<Sink::ApplicationDomain::Mail>{}(std::forward<Args...>(args...));
48 } 50 }
49}; 51};
diff --git a/common/log.cpp b/common/log.cpp
index 5dfb872..bfc9d5e 100644
--- a/common/log.cpp
+++ b/common/log.cpp
@@ -10,7 +10,6 @@
10#include <QMutexLocker> 10#include <QMutexLocker>
11#include <iostream> 11#include <iostream>
12#include <unistd.h> 12#include <unistd.h>
13#include <memory>
14#include <atomic> 13#include <atomic>
15#include <definitions.h> 14#include <definitions.h>
16#include <QThreadStorage> 15#include <QThreadStorage>
@@ -27,10 +26,13 @@ static QSettings &config()
27 return *sSettings.localData(); 26 return *sSettings.localData();
28} 27}
29 28
30static QByteArray sPrimaryComponent; 29Q_GLOBAL_STATIC(QByteArray, sPrimaryComponent);
30
31void Sink::Log::setPrimaryComponent(const QString &component) 31void Sink::Log::setPrimaryComponent(const QString &component)
32{ 32{
33 sPrimaryComponent = component.toUtf8(); 33 if (!sPrimaryComponent.isDestroyed()) {
34 *sPrimaryComponent = component.toUtf8();
35 }
34} 36}
35 37
36class DebugStream : public QIODevice 38class DebugStream : public QIODevice
@@ -267,16 +269,21 @@ public:
267 QSet<QString> mDebugAreas; 269 QSet<QString> mDebugAreas;
268}; 270};
269 271
270static auto sDebugAreaCollector = std::unique_ptr<DebugAreaCollector>(new DebugAreaCollector); 272Q_GLOBAL_STATIC(DebugAreaCollector, sDebugAreaCollector);
271 273
272QSet<QString> Sink::Log::debugAreas() 274QSet<QString> Sink::Log::debugAreas()
273{ 275{
274 return sDebugAreaCollector->debugAreas(); 276 if (!sDebugAreaCollector.isDestroyed()) {
277 return sDebugAreaCollector->debugAreas();
278 }
279 return {};
275} 280}
276 281
277static void collectDebugArea(const QString &debugArea) 282static void collectDebugArea(const QString &debugArea)
278{ 283{
279 sDebugAreaCollector->add(debugArea); 284 if (!sDebugAreaCollector.isDestroyed()) {
285 sDebugAreaCollector->add(debugArea);
286 }
280} 287}
281 288
282static bool containsItemStartingWith(const QByteArray &pattern, const QByteArrayList &list) 289static bool containsItemStartingWith(const QByteArray &pattern, const QByteArrayList &list)
@@ -318,13 +325,17 @@ static QByteArray getFileName(const char *file)
318 325
319static QString assembleDebugArea(const char *debugArea, const char *debugComponent, const char *file) 326static QString assembleDebugArea(const char *debugArea, const char *debugComponent, const char *file)
320{ 327{
321 if (sPrimaryComponent.isEmpty()) { 328 if (!sPrimaryComponent.isDestroyed() && sPrimaryComponent->isEmpty()) {
322 sPrimaryComponent = getProgramName(); 329 *sPrimaryComponent = getProgramName();
330 }
331 if (!sPrimaryComponent.isDestroyed()) {
332 //Using stringbuilder for fewer allocations
333 return QLatin1String{*sPrimaryComponent} % QLatin1String{"."} %
334 (debugComponent ? (QLatin1String{debugComponent} + QLatin1String{"."}) : QLatin1String{""}) %
335 (debugArea ? QLatin1String{debugArea} : QLatin1String{getFileName(file)});
336 } else {
337 return {};
323 } 338 }
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} 339}
329 340
330static bool isFiltered(DebugLevel debugLevel, const QByteArray &fullDebugArea) 341static bool isFiltered(DebugLevel debugLevel, const QByteArray &fullDebugArea)
@@ -346,14 +357,19 @@ bool Sink::Log::isFiltered(DebugLevel debugLevel, const char *debugArea, const c
346 return isFiltered(debugLevel, assembleDebugArea(debugArea, debugComponent, file).toLatin1()); 357 return isFiltered(debugLevel, assembleDebugArea(debugArea, debugComponent, file).toLatin1());
347} 358}
348 359
360Q_GLOBAL_STATIC(NullStream, sNullStream);
361Q_GLOBAL_STATIC(DebugStream, sDebugStream);
362
349QDebug Sink::Log::debugStream(DebugLevel debugLevel, int line, const char *file, const char *function, const char *debugArea, const char *debugComponent) 363QDebug Sink::Log::debugStream(DebugLevel debugLevel, int line, const char *file, const char *function, const char *debugArea, const char *debugComponent)
350{ 364{
351 const auto fullDebugArea = assembleDebugArea(debugArea, debugComponent, file); 365 const auto fullDebugArea = assembleDebugArea(debugArea, debugComponent, file);
352 collectDebugArea(fullDebugArea); 366 collectDebugArea(fullDebugArea);
353 367
354 static NullStream nullstream;
355 if (isFiltered(debugLevel, fullDebugArea.toLatin1())) { 368 if (isFiltered(debugLevel, fullDebugArea.toLatin1())) {
356 return QDebug(&nullstream); 369 if (!sNullStream.isDestroyed()) {
370 return QDebug(sNullStream);
371 }
372 return QDebug{QtDebugMsg};
357 } 373 }
358 374
359 QString prefix; 375 QString prefix;
@@ -418,8 +434,10 @@ QDebug Sink::Log::debugStream(DebugLevel debugLevel, int line, const char *file,
418 } 434 }
419 output += ":"; 435 output += ":";
420 436
421 static DebugStream stream; 437 if (sDebugStream.isDestroyed()) {
422 QDebug debug(&stream); 438 return QDebug{QtDebugMsg};
439 }
440 QDebug debug(sDebugStream);
423 441
424 debug.noquote().nospace() << output; 442 debug.noquote().nospace() << output;
425 443
diff --git a/common/mail/threadindexer.cpp b/common/mail/threadindexer.cpp
index 4171d85..1401fc8 100644
--- a/common/mail/threadindexer.cpp
+++ b/common/mail/threadindexer.cpp
@@ -34,9 +34,37 @@ void ThreadIndexer::updateThreadingIndex(const QByteArray &identifier, const App
34 34
35 QVector<QByteArray> thread; 35 QVector<QByteArray> thread;
36 36
37 //a child already registered our thread. 37 //check if a child already registered our thread.
38 thread = index().secondaryLookup<Mail::MessageId, Mail::ThreadId>(messageId); 38 thread = index().secondaryLookup<Mail::MessageId, Mail::ThreadId>(messageId);
39 39
40 if (!thread.isEmpty()) {
41 //A child already registered our thread so we merge the childs thread
42 //* check if we have a parent thread, if not just continue as usual
43 //* get all messages that have the same threadid as the child
44 //* switch all to the parents thread
45 auto parentThread = index().secondaryLookup<Mail::MessageId, Mail::ThreadId>(parentMessageId);
46 if (!parentThread.isEmpty()) {
47 auto childThreadId = thread.first();
48 auto parentThreadId = parentThread.first();
49 SinkTrace() << "Merging child thread: " << childThreadId << " into parent thread: " << parentThreadId;
50
51 //Ensure this mail ends up in the correct thread
52 index().unindex<Mail::MessageId, Mail::ThreadId>(messageId, childThreadId, transaction);
53 //We have to copy the id here, otherwise it doesn't survive the subsequent writes
54 thread = QVector<QByteArray>() << QByteArray{parentThreadId.data(), parentThreadId.size()};
55
56 //Merge all child messages into the correct thread
57 auto childThreadMessageIds = index().secondaryLookup<Mail::ThreadId, Mail::MessageId>(childThreadId);
58 for (const auto &msgId : childThreadMessageIds) {
59 SinkTrace() << "Merging child message: " << msgId;
60 index().unindex<Mail::MessageId, Mail::ThreadId>(msgId, childThreadId, transaction);
61 index().unindex<Mail::ThreadId, Mail::MessageId>(childThreadId, msgId, transaction);
62 index().index<Mail::MessageId, Mail::ThreadId>(msgId, parentThreadId, transaction);
63 index().index<Mail::ThreadId, Mail::MessageId>(parentThreadId, msgId, transaction);
64 }
65 }
66 }
67
40 //If parent is already available, add to thread of parent 68 //If parent is already available, add to thread of parent
41 if (thread.isEmpty() && parentMessageId.isValid()) { 69 if (thread.isEmpty() && parentMessageId.isValid()) {
42 thread = index().secondaryLookup<Mail::MessageId, Mail::ThreadId>(parentMessageId); 70 thread = index().secondaryLookup<Mail::MessageId, Mail::ThreadId>(parentMessageId);
diff --git a/common/mailpreprocessor.cpp b/common/mailpreprocessor.cpp
index 5c54fbc..253e8b4 100644
--- a/common/mailpreprocessor.cpp
+++ b/common/mailpreprocessor.cpp
@@ -104,9 +104,6 @@ static void updatedIndexedProperties(Sink::ApplicationDomain::Mail &mail, KMime:
104 mail.setExtractedBcc(getContactList(msg->bcc(true))); 104 mail.setExtractedBcc(getContactList(msg->bcc(true)));
105 mail.setExtractedDate(msg->date(true)->dateTime()); 105 mail.setExtractedDate(msg->date(true)->dateTime());
106 106
107 //The rest should never change, unless we didn't have the headers available initially.
108 auto messageId = msg->messageID(true)->identifier();
109
110 //Ensure the mssageId is unique. 107 //Ensure the mssageId is unique.
111 //If there already is one with the same id we'd have to assign a new message id, which probably doesn't make any sense. 108 //If there already is one with the same id we'd have to assign a new message id, which probably doesn't make any sense.
112 109
@@ -125,12 +122,21 @@ static void updatedIndexedProperties(Sink::ApplicationDomain::Mail &mail, KMime:
125 parentMessageId = inReplyTo.first(); 122 parentMessageId = inReplyTo.first();
126 } 123 }
127 } 124 }
125
126 //The rest should never change, unless we didn't have the headers available initially.
127 auto messageId = msg->messageID(true)->identifier();
128 if (messageId.isEmpty()) { 128 if (messageId.isEmpty()) {
129 auto tmp = KMime::Message::Ptr::create(); 129 //reuse an existing messageis (on modification)
130 auto header = tmp->messageID(true); 130 auto existing = mail.getMessageId();
131 header->generate("kube.kde.org"); 131 if (existing.isEmpty()) {
132 messageId = header->as7BitString(); 132 auto tmp = KMime::Message::Ptr::create();
133 SinkWarning() << "Message id is empty, generating one: " << messageId; 133 auto header = tmp->messageID(true);
134 header->generate("kube.kde.org");
135 messageId = header->as7BitString();
136 SinkWarning() << "Message id is empty, generating one: " << messageId;
137 } else {
138 messageId = existing;
139 }
134 } 140 }
135 141
136 mail.setExtractedMessageId(messageId); 142 mail.setExtractedMessageId(messageId);
diff --git a/common/modelresult.cpp b/common/modelresult.cpp
index 58703ab..95f4643 100644
--- a/common/modelresult.cpp
+++ b/common/modelresult.cpp
@@ -298,16 +298,16 @@ void ModelResult<T, Ptr>::add(const Ptr &value)
298 auto parent = createIndexFromId(id); 298 auto parent = createIndexFromId(id);
299 SinkTraceCtx(mLogCtx) << "Added entity " << childId << "id: " << value->identifier() << "parent: " << id; 299 SinkTraceCtx(mLogCtx) << "Added entity " << childId << "id: " << value->identifier() << "parent: " << id;
300 const auto keys = mTree[id]; 300 const auto keys = mTree[id];
301 int index = 0; 301 int idx = 0;
302 for (; index < keys.size(); index++) { 302 for (; idx < keys.size(); idx++) {
303 if (childId < keys.at(index)) { 303 if (childId < keys.at(idx)) {
304 break; 304 break;
305 } 305 }
306 } 306 }
307 // SinkTraceCtx(mLogCtx) << "Inserting rows " << index << parent; 307 // SinkTraceCtx(mLogCtx) << "Inserting rows " << index << parent;
308 beginInsertRows(parent, index, index); 308 beginInsertRows(parent, idx, idx);
309 mEntities.insert(childId, value); 309 mEntities.insert(childId, value);
310 mTree[id].insert(index, childId); 310 mTree[id].insert(idx, childId);
311 mParents.insert(childId, id); 311 mParents.insert(childId, id);
312 endInsertRows(); 312 endInsertRows();
313 // SinkTraceCtx(mLogCtx) << "Inserted rows " << mTree[id].size(); 313 // SinkTraceCtx(mLogCtx) << "Inserted rows " << mTree[id].size();
diff --git a/common/notification.cpp b/common/notification.cpp
index da31e20..20bc654 100644
--- a/common/notification.cpp
+++ b/common/notification.cpp
@@ -48,7 +48,7 @@ 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();
diff --git a/common/notifier.cpp b/common/notifier.cpp
index 1af65e9..1b7cbdb 100644
--- a/common/notifier.cpp
+++ b/common/notifier.cpp
@@ -49,6 +49,7 @@ public:
49 49
50 QList<QSharedPointer<ResourceAccess>> resourceAccess; 50 QList<QSharedPointer<ResourceAccess>> resourceAccess;
51 QList<std::function<void(const Notification &)>> handler; 51 QList<std::function<void(const Notification &)>> handler;
52 QSharedPointer<Sink::ResultEmitter<QSharedPointer<Sink::ApplicationDomain::SinkResource> > > mResourceEmitter;
52 QObject context; 53 QObject context;
53}; 54};
54 55
@@ -91,6 +92,9 @@ Notifier::Notifier(const Sink::Query &resourceQuery) : d(new Sink::Notifier::Pri
91 SinkTraceCtx(resourceCtx) << "Resource query complete"; 92 SinkTraceCtx(resourceCtx) << "Resource query complete";
92 }); 93 });
93 emitter->fetch({}); 94 emitter->fetch({});
95 if (resourceQuery.liveQuery()) {
96 d->mResourceEmitter = emitter;
97 }
94 result.first.exec(); 98 result.first.exec();
95} 99}
96 100
diff --git a/common/resource.cpp b/common/resource.cpp
index 32a92ca..804f0bf 100644
--- a/common/resource.cpp
+++ b/common/resource.cpp
@@ -56,10 +56,10 @@ class ResourceFactory::Private
56{ 56{
57public: 57public:
58 QByteArrayList capabilities; 58 QByteArrayList capabilities;
59 static QHash<QString, QPointer<ResourceFactory>> s_loadedFactories;
60}; 59};
61 60
62QHash<QString, QPointer<ResourceFactory>> ResourceFactory::Private::s_loadedFactories; 61typedef QHash<QString, QPointer<ResourceFactory>> FactoryRegistry;
62Q_GLOBAL_STATIC(FactoryRegistry, s_loadedFactories);
63 63
64ResourceFactory::ResourceFactory(QObject *parent, const QByteArrayList &capabilities) : QObject(parent), d(new ResourceFactory::Private) 64ResourceFactory::ResourceFactory(QObject *parent, const QByteArrayList &capabilities) : QObject(parent), d(new ResourceFactory::Private)
65{ 65{
@@ -73,7 +73,7 @@ ResourceFactory::~ResourceFactory()
73 73
74ResourceFactory *ResourceFactory::load(const QByteArray &resourceName) 74ResourceFactory *ResourceFactory::load(const QByteArray &resourceName)
75{ 75{
76 ResourceFactory *factory = Private::s_loadedFactories.value(resourceName); 76 ResourceFactory *factory = s_loadedFactories->value(resourceName);
77 if (factory) { 77 if (factory) {
78 return factory; 78 return factory;
79 } 79 }
@@ -96,7 +96,7 @@ ResourceFactory *ResourceFactory::load(const QByteArray &resourceName)
96 if (object) { 96 if (object) {
97 factory = qobject_cast<ResourceFactory *>(object); 97 factory = qobject_cast<ResourceFactory *>(object);
98 if (factory) { 98 if (factory) {
99 Private::s_loadedFactories.insert(resourceName, factory); 99 s_loadedFactories->insert(resourceName, factory);
100 //TODO: Instead of always loading both facades and adaptorfactories into the respective singletons, we could also leave this up to the caller. (ResourceFactory::loadFacades(...)) 100 //TODO: Instead of always loading both facades and adaptorfactories into the respective singletons, we could also leave this up to the caller. (ResourceFactory::loadFacades(...))
101 factory->registerFacades(resourceName, FacadeFactory::instance()); 101 factory->registerFacades(resourceName, FacadeFactory::instance());
102 factory->registerAdaptorFactories(resourceName, AdaptorFactoryRegistry::instance()); 102 factory->registerAdaptorFactories(resourceName, AdaptorFactoryRegistry::instance());
diff --git a/common/resourceaccess.cpp b/common/resourceaccess.cpp
index 808d892..35fa46c 100644
--- a/common/resourceaccess.cpp
+++ b/common/resourceaccess.cpp
@@ -232,7 +232,7 @@ KAsync::Job<void> ResourceAccess::Private::initializeSocket()
232ResourceAccess::ResourceAccess(const QByteArray &resourceInstanceIdentifier, const QByteArray &resourceType) 232ResourceAccess::ResourceAccess(const QByteArray &resourceInstanceIdentifier, const QByteArray &resourceType)
233 : ResourceAccessInterface(), d(new Private(resourceType, resourceInstanceIdentifier, this)) 233 : ResourceAccessInterface(), d(new Private(resourceType, resourceInstanceIdentifier, this))
234{ 234{
235 mResourceStatus = Sink::ApplicationDomain::OfflineStatus; 235 mResourceStatus = Sink::ApplicationDomain::NoStatus;
236 SinkTrace() << "Starting access"; 236 SinkTrace() << "Starting access";
237} 237}
238 238
diff --git a/common/resourcefacade.cpp b/common/resourcefacade.cpp
index dab6aed..0687bbc 100644
--- a/common/resourcefacade.cpp
+++ b/common/resourcefacade.cpp
@@ -24,6 +24,7 @@
24#include "store.h" 24#include "store.h"
25#include "resourceaccess.h" 25#include "resourceaccess.h"
26#include "resource.h" 26#include "resource.h"
27#include "facadefactory.h"
27 28
28using namespace Sink; 29using namespace Sink;
29 30
@@ -358,27 +359,50 @@ QPair<KAsync::Job<void>, typename Sink::ResultEmitter<typename ApplicationDomain
358 auto ctx = parentCtx.subContext("accounts"); 359 auto ctx = parentCtx.subContext("accounts");
359 auto runner = new LocalStorageQueryRunner<ApplicationDomain::SinkAccount>(query, mIdentifier, mTypeName, sConfigNotifier, ctx); 360 auto runner = new LocalStorageQueryRunner<ApplicationDomain::SinkAccount>(query, mIdentifier, mTypeName, sConfigNotifier, ctx);
360 auto monitoredResources = QSharedPointer<QSet<QByteArray>>::create(); 361 auto monitoredResources = QSharedPointer<QSet<QByteArray>>::create();
361 runner->setStatusUpdater([runner, monitoredResources, ctx](ApplicationDomain::SinkAccount &account) { 362 auto monitorResource = [monitoredResources, runner, ctx] (const QByteArray &accountIdentifier, const ApplicationDomain::SinkResource &resource, const ResourceAccess::Ptr &resourceAccess) {
362 Query query; 363 if (!monitoredResources->contains(resource.identifier())) {
364 auto ret = QObject::connect(resourceAccess.data(), &ResourceAccess::notification, runner->guard(), [resource, runner, resourceAccess, accountIdentifier, ctx](const Notification &notification) {
365 SinkTraceCtx(ctx) << "Received notification in facade: " << notification.type;
366 if (notification.type == Notification::Status) {
367 runner->statusChanged(accountIdentifier);
368 }
369 });
370 Q_ASSERT(ret);
371 monitoredResources->insert(resource.identifier());
372 }
373 };
374 runner->setStatusUpdater([this, runner, monitoredResources, ctx, monitorResource](ApplicationDomain::SinkAccount &account) {
375 Query query{Query::LiveQuery};
363 query.filter<ApplicationDomain::SinkResource::Account>(account.identifier()); 376 query.filter<ApplicationDomain::SinkResource::Account>(account.identifier());
364 query.request<ApplicationDomain::SinkResource::Account>() 377 query.request<ApplicationDomain::SinkResource::Account>()
365 .request<ApplicationDomain::SinkResource::Capabilities>(); 378 .request<ApplicationDomain::SinkResource::Capabilities>();
366 const auto resources = Store::read<ApplicationDomain::SinkResource>(query); 379 const auto resources = Store::read<ApplicationDomain::SinkResource>(query);
367 SinkTraceCtx(ctx) << "Found resource belonging to the account " << account.identifier() << " : " << resources; 380 SinkTraceCtx(ctx) << "Found resource belonging to the account " << account.identifier() << " : " << resources;
368 auto accountIdentifier = account.identifier(); 381 auto accountIdentifier = account.identifier();
382
383 //Monitor for new resources so they can be monitored as well
384 if (!runner->mResourceEmitter.contains(accountIdentifier)) {
385 auto facade = Sink::FacadeFactory::instance().getFacade<ApplicationDomain::SinkResource>();
386 Q_ASSERT(facade);
387
388 auto emitter = facade->load(query, ctx).second;
389 emitter->onAdded([=](const ApplicationDomain::SinkResource::Ptr &resource) {
390 auto resourceAccess = Sink::ResourceAccessFactory::instance().getAccess(resource->identifier(), ResourceConfig::getResourceType(resource->identifier()));
391 monitorResource(accountIdentifier, *resource, resourceAccess);
392 });
393 emitter->onModified([](const ApplicationDomain::SinkResource::Ptr &) {});
394 emitter->onRemoved([](const ApplicationDomain::SinkResource::Ptr &) {});
395 emitter->onInitialResultSetComplete([](const ApplicationDomain::SinkResource::Ptr &, bool) {});
396 emitter->onComplete([]() {});
397 emitter->fetch({});
398 runner->mResourceEmitter[accountIdentifier] = emitter;
399 }
400
369 QList<int> states; 401 QList<int> states;
402 //Gather all resources and ensure they are monitored
370 for (const auto &resource : resources) { 403 for (const auto &resource : resources) {
371 auto resourceAccess = ResourceAccessFactory::instance().getAccess(resource.identifier(), ResourceConfig::getResourceType(resource.identifier())); 404 auto resourceAccess = ResourceAccessFactory::instance().getAccess(resource.identifier(), ResourceConfig::getResourceType(resource.identifier()));
372 if (!monitoredResources->contains(resource.identifier())) { 405 monitorResource(accountIdentifier, resource, resourceAccess);
373 auto ret = QObject::connect(resourceAccess.data(), &ResourceAccess::notification, runner->guard(), [resource, runner, resourceAccess, accountIdentifier, ctx](const Notification &notification) {
374 SinkTraceCtx(ctx) << "Received notification in facade: " << notification.type;
375 if (notification.type == Notification::Status) {
376 runner->statusChanged(accountIdentifier);
377 }
378 });
379 Q_ASSERT(ret);
380 monitoredResources->insert(resource.identifier());
381 }
382 states << resourceAccess->getResourceStatus(); 406 states << resourceAccess->getResourceStatus();
383 } 407 }
384 const auto status = [&] { 408 const auto status = [&] {
@@ -391,7 +415,10 @@ QPair<KAsync::Job<void>, typename Sink::ResultEmitter<typename ApplicationDomain
391 if (states.contains(ApplicationDomain::OfflineStatus)) { 415 if (states.contains(ApplicationDomain::OfflineStatus)) {
392 return ApplicationDomain::OfflineStatus; 416 return ApplicationDomain::OfflineStatus;
393 } 417 }
394 return ApplicationDomain::ConnectedStatus; 418 if (states.contains(ApplicationDomain::ConnectedStatus)) {
419 return ApplicationDomain::ConnectedStatus;
420 }
421 return ApplicationDomain::NoStatus;
395 }(); 422 }();
396 account.setStatusStatus(status); 423 account.setStatusStatus(status);
397 }); 424 });
diff --git a/common/resourcefacade.h b/common/resourcefacade.h
index 76fadce..36049c4 100644
--- a/common/resourcefacade.h
+++ b/common/resourcefacade.h
@@ -65,6 +65,7 @@ public:
65 void setStatusUpdater(const std::function<void(DomainType &)> &); 65 void setStatusUpdater(const std::function<void(DomainType &)> &);
66 void statusChanged(const QByteArray &identifier); 66 void statusChanged(const QByteArray &identifier);
67 QObject *guard() const; 67 QObject *guard() const;
68 QMap<QByteArray, QSharedPointer<Sink::ResultEmitter<QSharedPointer<Sink::ApplicationDomain::SinkResource> > > > mResourceEmitter;
68 69
69private: 70private:
70 void updateStatus(DomainType &entity); 71 void updateStatus(DomainType &entity);
diff --git a/common/storage.h b/common/storage.h
index 8c129df..c39b904 100644
--- a/common/storage.h
+++ b/common/storage.h
@@ -198,9 +198,9 @@ public:
198 static QByteArray getTypeFromRevision(const Transaction &, qint64 revision); 198 static QByteArray getTypeFromRevision(const Transaction &, qint64 revision);
199 static void recordRevision(Transaction &, qint64 revision, const QByteArray &uid, const QByteArray &type); 199 static void recordRevision(Transaction &, qint64 revision, const QByteArray &uid, const QByteArray &type);
200 static void removeRevision(Transaction &, qint64 revision); 200 static void removeRevision(Transaction &, qint64 revision);
201 static void recordUid(DataStore::Transaction &transaction, const QByteArray &uid); 201 static void recordUid(DataStore::Transaction &transaction, const QByteArray &uid, const QByteArray &type);
202 static void removeUid(DataStore::Transaction &transaction, const QByteArray &uid); 202 static void removeUid(DataStore::Transaction &transaction, const QByteArray &uid, const QByteArray &type);
203 static void getUids(const Transaction &, const std::function<void(const QByteArray &uid)> &); 203 static void getUids(const QByteArray &type, const Transaction &, const std::function<void(const QByteArray &uid)> &);
204 204
205 bool exists() const; 205 bool exists() const;
206 206
diff --git a/common/storage/entitystore.cpp b/common/storage/entitystore.cpp
index 22e5ae3..5514e31 100644
--- a/common/storage/entitystore.cpp
+++ b/common/storage/entitystore.cpp
@@ -267,7 +267,7 @@ bool EntityStore::add(const QByteArray &type, ApplicationDomain::ApplicationDoma
267 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to write entity" << entity.identifier() << newRevision; }); 267 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to write entity" << entity.identifier() << newRevision; });
268 DataStore::setMaxRevision(d->transaction, newRevision); 268 DataStore::setMaxRevision(d->transaction, newRevision);
269 DataStore::recordRevision(d->transaction, newRevision, entity.identifier(), type); 269 DataStore::recordRevision(d->transaction, newRevision, entity.identifier(), type);
270 DataStore::recordUid(d->transaction, entity.identifier()); 270 DataStore::recordUid(d->transaction, entity.identifier(), type);
271 SinkTraceCtx(d->logCtx) << "Wrote entity: " << entity.identifier() << type << newRevision; 271 SinkTraceCtx(d->logCtx) << "Wrote entity: " << entity.identifier() << type << newRevision;
272 return true; 272 return true;
273} 273}
@@ -379,7 +379,7 @@ bool EntityStore::remove(const QByteArray &type, const Sink::ApplicationDomain::
379 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to write entity" << uid << newRevision; }); 379 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to write entity" << uid << newRevision; });
380 DataStore::setMaxRevision(d->transaction, newRevision); 380 DataStore::setMaxRevision(d->transaction, newRevision);
381 DataStore::recordRevision(d->transaction, newRevision, uid, type); 381 DataStore::recordRevision(d->transaction, newRevision, uid, type);
382 DataStore::removeUid(d->transaction, uid); 382 DataStore::removeUid(d->transaction, uid, type);
383 return true; 383 return true;
384} 384}
385 385
@@ -516,9 +516,8 @@ void EntityStore::readLatest(const QByteArray &type, const QByteArray &uid, cons
516{ 516{
517 auto db = DataStore::mainDatabase(d->getTransaction(), type); 517 auto db = DataStore::mainDatabase(d->getTransaction(), type);
518 db.findLatest(uid, 518 db.findLatest(uid,
519 [=](const QByteArray &key, const QByteArray &value) -> bool { 519 [=](const QByteArray &key, const QByteArray &value) {
520 callback(DataStore::uidFromKey(key), Sink::EntityBuffer(value.data(), value.size())); 520 callback(DataStore::uidFromKey(key), Sink::EntityBuffer(value.data(), value.size()));
521 return false;
522 }, 521 },
523 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Error during query: " << error.message << uid; }); 522 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Error during query: " << error.message << uid; });
524} 523}
@@ -639,7 +638,7 @@ ApplicationDomain::ApplicationDomainType EntityStore::readPrevious(const QByteAr
639 638
640void EntityStore::readAllUids(const QByteArray &type, const std::function<void(const QByteArray &uid)> callback) 639void EntityStore::readAllUids(const QByteArray &type, const std::function<void(const QByteArray &uid)> callback)
641{ 640{
642 DataStore::getUids(d->getTransaction(), callback); 641 DataStore::getUids(type, d->getTransaction(), callback);
643} 642}
644 643
645bool EntityStore::contains(const QByteArray &type, const QByteArray &uid) 644bool EntityStore::contains(const QByteArray &type, const QByteArray &uid)
@@ -653,7 +652,7 @@ bool EntityStore::exists(const QByteArray &type, const QByteArray &uid)
653 bool alreadyRemoved = false; 652 bool alreadyRemoved = false;
654 DataStore::mainDatabase(d->transaction, type) 653 DataStore::mainDatabase(d->transaction, type)
655 .findLatest(uid, 654 .findLatest(uid,
656 [&found, &alreadyRemoved](const QByteArray &key, const QByteArray &data) -> bool { 655 [&found, &alreadyRemoved](const QByteArray &key, const QByteArray &data) {
657 auto entity = GetEntity(data.data()); 656 auto entity = GetEntity(data.data());
658 if (entity && entity->metadata()) { 657 if (entity && entity->metadata()) {
659 auto metadata = GetMetadata(entity->metadata()->Data()); 658 auto metadata = GetMetadata(entity->metadata()->Data());
@@ -662,7 +661,6 @@ bool EntityStore::exists(const QByteArray &type, const QByteArray &uid)
662 alreadyRemoved = true; 661 alreadyRemoved = true;
663 } 662 }
664 } 663 }
665 return false;
666 }, 664 },
667 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to read old revision from storage: " << error.message; }); 665 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to read old revision from storage: " << error.message; });
668 if (!found) { 666 if (!found) {
diff --git a/common/storage_common.cpp b/common/storage_common.cpp
index 8603787..630dae9 100644
--- a/common/storage_common.cpp
+++ b/common/storage_common.cpp
@@ -156,19 +156,19 @@ void DataStore::removeRevision(DataStore::Transaction &transaction, qint64 revis
156 transaction.openDatabase("revisionType").remove(QByteArray::number(revision)); 156 transaction.openDatabase("revisionType").remove(QByteArray::number(revision));
157} 157}
158 158
159void DataStore::recordUid(DataStore::Transaction &transaction, const QByteArray &uid) 159void DataStore::recordUid(DataStore::Transaction &transaction, const QByteArray &uid, const QByteArray &type)
160{ 160{
161 transaction.openDatabase("uids").write(uid, ""); 161 transaction.openDatabase(type + "uids").write(uid, "");
162} 162}
163 163
164void DataStore::removeUid(DataStore::Transaction &transaction, const QByteArray &uid) 164void DataStore::removeUid(DataStore::Transaction &transaction, const QByteArray &uid, const QByteArray &type)
165{ 165{
166 transaction.openDatabase("uids").remove(uid); 166 transaction.openDatabase(type + "uids").remove(uid);
167} 167}
168 168
169void DataStore::getUids(const Transaction &transaction, const std::function<void(const QByteArray &uid)> &callback) 169void DataStore::getUids(const QByteArray &type, const Transaction &transaction, const std::function<void(const QByteArray &uid)> &callback)
170{ 170{
171 transaction.openDatabase("uids").scan("", [&] (const QByteArray &key, const QByteArray &) { 171 transaction.openDatabase(type + "uids").scan("", [&] (const QByteArray &key, const QByteArray &) {
172 callback(key); 172 callback(key);
173 return true; 173 return true;
174 }); 174 });
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp
index f7999d1..58e3a9a 100644
--- a/common/storage_lmdb.cpp
+++ b/common/storage_lmdb.cpp
@@ -406,7 +406,7 @@ int DataStore::NamedDatabase::scan(const QByteArray &k, const std::function<bool
406 mdb_cursor_close(cursor); 406 mdb_cursor_close(cursor);
407 407
408 if (rc) { 408 if (rc) {
409 Error error(d->name.toLatin1() + d->db, getErrorCode(rc), QByteArray("Key: ") + k + " : " + QByteArray(mdb_strerror(rc))); 409 Error error(d->name.toLatin1() + d->db, getErrorCode(rc), QByteArray("Error during scan. Key: ") + k + " : " + QByteArray(mdb_strerror(rc)));
410 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error); 410 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
411 } 411 }
412 412
@@ -420,6 +420,11 @@ void DataStore::NamedDatabase::findLatest(const QByteArray &k, const std::functi
420 // Not an error. We rely on this to read nothing from non-existing databases. 420 // Not an error. We rely on this to read nothing from non-existing databases.
421 return; 421 return;
422 } 422 }
423 if (k.isEmpty()) {
424 Error error(d->name.toLatin1() + d->db, GenericError, QByteArray("Can't use findLatest with empty key."));
425 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
426 return;
427 }
423 428
424 int rc; 429 int rc;
425 MDB_val key; 430 MDB_val key;
@@ -441,25 +446,23 @@ void DataStore::NamedDatabase::findLatest(const QByteArray &k, const std::functi
441 if ((rc = mdb_cursor_get(cursor, &key, &data, op)) == 0) { 446 if ((rc = mdb_cursor_get(cursor, &key, &data, op)) == 0) {
442 // The first lookup will find a key that is equal or greather than our key 447 // The first lookup will find a key that is equal or greather than our key
443 if (QByteArray::fromRawData((char *)key.mv_data, key.mv_size).startsWith(k)) { 448 if (QByteArray::fromRawData((char *)key.mv_data, key.mv_size).startsWith(k)) {
444 bool advanced = false; 449 //Read next value until we no longer match
445 while (QByteArray::fromRawData((char *)key.mv_data, key.mv_size).startsWith(k)) { 450 while (QByteArray::fromRawData((char *)key.mv_data, key.mv_size).startsWith(k)) {
446 advanced = true;
447 MDB_cursor_op nextOp = MDB_NEXT; 451 MDB_cursor_op nextOp = MDB_NEXT;
448 rc = mdb_cursor_get(cursor, &key, &data, nextOp); 452 rc = mdb_cursor_get(cursor, &key, &data, nextOp);
449 if (rc) { 453 if (rc) {
450 break; 454 break;
451 } 455 }
452 } 456 }
453 if (advanced) { 457 //Now read the previous value, and that's the latest one
454 MDB_cursor_op prefOp = MDB_PREV; 458 MDB_cursor_op prefOp = MDB_PREV;
455 // We read past the end above, just take the last value 459 // We read past the end above, just take the last value
456 if (rc == MDB_NOTFOUND) { 460 if (rc == MDB_NOTFOUND) {
457 prefOp = MDB_LAST; 461 prefOp = MDB_LAST;
458 }
459 rc = mdb_cursor_get(cursor, &key, &data, prefOp);
460 foundValue = true;
461 resultHandler(QByteArray::fromRawData((char *)key.mv_data, key.mv_size), QByteArray::fromRawData((char *)data.mv_data, data.mv_size));
462 } 462 }
463 rc = mdb_cursor_get(cursor, &key, &data, prefOp);
464 foundValue = true;
465 resultHandler(QByteArray::fromRawData((char *)key.mv_data, key.mv_size), QByteArray::fromRawData((char *)data.mv_data, data.mv_size));
463 } 466 }
464 } 467 }
465 468
@@ -471,10 +474,10 @@ void DataStore::NamedDatabase::findLatest(const QByteArray &k, const std::functi
471 mdb_cursor_close(cursor); 474 mdb_cursor_close(cursor);
472 475
473 if (rc) { 476 if (rc) {
474 Error error(d->name.toLatin1(), getErrorCode(rc), QByteArray("Key: ") + k + " : " + QByteArray(mdb_strerror(rc))); 477 Error error(d->name.toLatin1(), getErrorCode(rc), QByteArray("Error during find latest. Key: ") + k + " : " + QByteArray(mdb_strerror(rc)));
475 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error); 478 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
476 } else if (!foundValue) { 479 } else if (!foundValue) {
477 Error error(d->name.toLatin1(), 1, QByteArray("Key: ") + k + " : No value found"); 480 Error error(d->name.toLatin1(), 1, QByteArray("Error during find latest. Key: ") + k + " : No value found");
478 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error); 481 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
479 } 482 }
480 483
diff --git a/common/store.cpp b/common/store.cpp
index 4735113..1701a43 100644
--- a/common/store.cpp
+++ b/common/store.cpp
@@ -36,10 +36,34 @@
36#include "storage.h" 36#include "storage.h"
37#include "log.h" 37#include "log.h"
38 38
39#define ASSERT_ENUMS_MATCH(A, B) Q_STATIC_ASSERT_X(static_cast<int>(A) == static_cast<int>(B), "The enum values must match");
40
41//Ensure the copied enum matches
42typedef ModelResult<Sink::ApplicationDomain::Mail, Sink::ApplicationDomain::Mail::Ptr> MailModelResult;
43ASSERT_ENUMS_MATCH(Sink::Store::DomainObjectBaseRole, MailModelResult::DomainObjectBaseRole)
44ASSERT_ENUMS_MATCH(Sink::Store::ChildrenFetchedRole, MailModelResult::ChildrenFetchedRole)
45ASSERT_ENUMS_MATCH(Sink::Store::DomainObjectRole, MailModelResult::DomainObjectRole)
46ASSERT_ENUMS_MATCH(Sink::Store::StatusRole, MailModelResult::StatusRole)
47ASSERT_ENUMS_MATCH(Sink::Store::WarningRole, MailModelResult::WarningRole)
48ASSERT_ENUMS_MATCH(Sink::Store::ProgressRole, MailModelResult::ProgressRole)
49
39Q_DECLARE_METATYPE(QSharedPointer<Sink::ResultEmitter<Sink::ApplicationDomain::SinkResource::Ptr>>) 50Q_DECLARE_METATYPE(QSharedPointer<Sink::ResultEmitter<Sink::ApplicationDomain::SinkResource::Ptr>>)
40Q_DECLARE_METATYPE(QSharedPointer<Sink::ResourceAccessInterface>); 51Q_DECLARE_METATYPE(QSharedPointer<Sink::ResourceAccessInterface>);
41Q_DECLARE_METATYPE(std::shared_ptr<void>); 52Q_DECLARE_METATYPE(std::shared_ptr<void>);
42 53
54
55static bool sanityCheckQuery(const Sink::Query &query)
56{
57 for (const auto &id : query.ids()) {
58 if (id.isEmpty()) {
59 SinkError() << "Empty id in query.";
60 return false;
61 }
62 }
63 return true;
64}
65
66
43namespace Sink { 67namespace Sink {
44 68
45QString Store::storageLocation() 69QString Store::storageLocation()
@@ -138,6 +162,7 @@ static Log::Context getQueryContext(const Sink::Query &query, const QByteArray &
138template <class DomainType> 162template <class DomainType>
139QSharedPointer<QAbstractItemModel> Store::loadModel(const Query &query) 163QSharedPointer<QAbstractItemModel> Store::loadModel(const Query &query)
140{ 164{
165 Q_ASSERT(sanityCheckQuery(query));
141 auto ctx = getQueryContext(query, ApplicationDomain::getTypeName<DomainType>()); 166 auto ctx = getQueryContext(query, ApplicationDomain::getTypeName<DomainType>());
142 auto model = QSharedPointer<ModelResult<DomainType, typename DomainType::Ptr>>::create(query, query.requestedProperties, ctx); 167 auto model = QSharedPointer<ModelResult<DomainType, typename DomainType::Ptr>>::create(query, query.requestedProperties, ctx);
143 168
@@ -189,6 +214,10 @@ KAsync::Job<void> Store::create(const DomainType &domainObject)
189template <class DomainType> 214template <class DomainType>
190KAsync::Job<void> Store::modify(const DomainType &domainObject) 215KAsync::Job<void> Store::modify(const DomainType &domainObject)
191{ 216{
217 if (domainObject.changedProperties().isEmpty()) {
218 SinkLog() << "Nothing to modify: " << domainObject.identifier();
219 return KAsync::null();
220 }
192 SinkLog() << "Modify: " << domainObject; 221 SinkLog() << "Modify: " << domainObject;
193 // Potentially move to separate thread as well 222 // Potentially move to separate thread as well
194 auto facade = getFacade<DomainType>(domainObject.resourceInstanceIdentifier()); 223 auto facade = getFacade<DomainType>(domainObject.resourceInstanceIdentifier());
@@ -198,6 +227,10 @@ KAsync::Job<void> Store::modify(const DomainType &domainObject)
198template <class DomainType> 227template <class DomainType>
199KAsync::Job<void> Store::modify(const Query &query, const DomainType &domainObject) 228KAsync::Job<void> Store::modify(const Query &query, const DomainType &domainObject)
200{ 229{
230 if (domainObject.changedProperties().isEmpty()) {
231 SinkLog() << "Nothing to modify: " << domainObject.identifier();
232 return KAsync::null();
233 }
201 SinkLog() << "Modify: " << query << domainObject; 234 SinkLog() << "Modify: " << query << domainObject;
202 return fetchAll<DomainType>(query) 235 return fetchAll<DomainType>(query)
203 .each([=] (const typename DomainType::Ptr &entity) { 236 .each([=] (const typename DomainType::Ptr &entity) {
@@ -311,9 +344,14 @@ KAsync::Job<void> Store::synchronize(const Sink::Query &query)
311 344
312KAsync::Job<void> Store::synchronize(const Sink::SyncScope &scope) 345KAsync::Job<void> Store::synchronize(const Sink::SyncScope &scope)
313{ 346{
347 auto resourceFilter = scope.getResourceFilter();
348 //Filter resources by type by default
349 if (!resourceFilter.propertyFilter.contains(ApplicationDomain::SinkResource::Capabilities::name) && !scope.type().isEmpty()) {
350 resourceFilter.propertyFilter.insert(ApplicationDomain::SinkResource::Capabilities::name, Query::Comparator{scope.type(), Query::Comparator::Contains});
351 }
314 Sink::Query query; 352 Sink::Query query;
315 query.setFilter(scope.getResourceFilter()); 353 query.setFilter(resourceFilter);
316 SinkLog() << "Synchronizing: " << query; 354 SinkLog() << "Synchronizing all resource matching: " << query;
317 return fetchAll<ApplicationDomain::SinkResource>(query) 355 return fetchAll<ApplicationDomain::SinkResource>(query)
318 .template each([scope](const ApplicationDomain::SinkResource::Ptr &resource) -> KAsync::Job<void> { 356 .template each([scope](const ApplicationDomain::SinkResource::Ptr &resource) -> KAsync::Job<void> {
319 return synchronize(resource->identifier(), scope); 357 return synchronize(resource->identifier(), scope);
@@ -337,6 +375,7 @@ KAsync::Job<QList<typename DomainType::Ptr>> Store::fetchAll(const Sink::Query &
337template <class DomainType> 375template <class DomainType>
338KAsync::Job<QList<typename DomainType::Ptr>> Store::fetch(const Sink::Query &query, int minimumAmount) 376KAsync::Job<QList<typename DomainType::Ptr>> Store::fetch(const Sink::Query &query, int minimumAmount)
339{ 377{
378 Q_ASSERT(sanityCheckQuery(query));
340 auto model = loadModel<DomainType>(query); 379 auto model = loadModel<DomainType>(query);
341 auto list = QSharedPointer<QList<typename DomainType::Ptr>>::create(); 380 auto list = QSharedPointer<QList<typename DomainType::Ptr>>::create();
342 auto context = QSharedPointer<QObject>::create(); 381 auto context = QSharedPointer<QObject>::create();
@@ -388,6 +427,7 @@ DomainType Store::readOne(const Sink::Query &query)
388template <class DomainType> 427template <class DomainType>
389QList<DomainType> Store::read(const Sink::Query &query_) 428QList<DomainType> Store::read(const Sink::Query &query_)
390{ 429{
430 Q_ASSERT(sanityCheckQuery(query_));
391 auto query = query_; 431 auto query = query_;
392 query.setFlags(Query::SynchronousQuery); 432 query.setFlags(Query::SynchronousQuery);
393 433
diff --git a/common/store.h b/common/store.h
index 34e14df..3ad547e 100644
--- a/common/store.h
+++ b/common/store.h
@@ -75,12 +75,15 @@ KAsync::Job<void> SINK_EXPORT create(const DomainType &domainObject);
75 * Modify an entity. 75 * Modify an entity.
76 * 76 *
77 * This includes moving etc. since these are also simple settings on a property. 77 * This includes moving etc. since these are also simple settings on a property.
78 * Note that the modification will be dropped if there is no changedProperty on the domain object.
78 */ 79 */
79template <class DomainType> 80template <class DomainType>
80KAsync::Job<void> SINK_EXPORT modify(const DomainType &domainObject); 81KAsync::Job<void> SINK_EXPORT modify(const DomainType &domainObject);
81 82
82/** 83/**
83 * Modify a set of entities identified by @param query. 84 * Modify a set of entities identified by @param query.
85 *
86 * Note that the modification will be dropped if there is no changedProperty on the domain object.
84 */ 87 */
85template <class DomainType> 88template <class DomainType>
86KAsync::Job<void> SINK_EXPORT modify(const Query &query, const DomainType &domainObject); 89KAsync::Job<void> SINK_EXPORT modify(const Query &query, const DomainType &domainObject);
diff --git a/common/synchronizer.cpp b/common/synchronizer.cpp
index 3b32e68..46d3980 100644
--- a/common/synchronizer.cpp
+++ b/common/synchronizer.cpp
@@ -40,7 +40,7 @@ Synchronizer::Synchronizer(const Sink::ResourceContext &context)
40 mSyncStorage(Sink::storageLocation(), mResourceContext.instanceId() + ".synchronization", Sink::Storage::DataStore::DataStore::ReadWrite), 40 mSyncStorage(Sink::storageLocation(), mResourceContext.instanceId() + ".synchronization", Sink::Storage::DataStore::DataStore::ReadWrite),
41 mSyncInProgress(false) 41 mSyncInProgress(false)
42{ 42{
43 mCurrentState.push(ApplicationDomain::Status::OfflineStatus); 43 mCurrentState.push(ApplicationDomain::Status::NoStatus);
44 SinkTraceCtx(mLogCtx) << "Starting synchronizer: " << mResourceContext.resourceType << mResourceContext.instanceId(); 44 SinkTraceCtx(mLogCtx) << "Starting synchronizer: " << mResourceContext.resourceType << mResourceContext.instanceId();
45} 45}
46 46
@@ -344,8 +344,11 @@ void Synchronizer::setStatusFromResult(const KAsync::Error &error, const QString
344 } else if (error.errorCode == ApplicationDomain::LoginError) { 344 } else if (error.errorCode == ApplicationDomain::LoginError) {
345 //If we failed to login altough we could connect that indicates a problem with our setup. 345 //If we failed to login altough we could connect that indicates a problem with our setup.
346 setStatus(ApplicationDomain::ErrorStatus, s, requestId); 346 setStatus(ApplicationDomain::ErrorStatus, s, requestId);
347 } else if (error.errorCode == ApplicationDomain::ConnectionLostError) {
348 //We've lost the connection so we assume the connection to the server broke.
349 setStatus(ApplicationDomain::OfflineStatus, s, requestId);
347 } 350 }
348 //We don't know what kind of error this was, so we assume it's transient and don't change ou status. 351 //We don't know what kind of error this was, so we assume it's transient and don't change our status.
349 } else { 352 } else {
350 //An operation against the server worked, so we're probably online. 353 //An operation against the server worked, so we're probably online.
351 setStatus(ApplicationDomain::ConnectedStatus, s, requestId); 354 setStatus(ApplicationDomain::ConnectedStatus, s, requestId);
@@ -593,17 +596,13 @@ KAsync::Job<void> Synchronizer::replay(const QByteArray &type, const QByteArray
593 KAsync::Job<QByteArray> job = KAsync::null<QByteArray>(); 596 KAsync::Job<QByteArray> job = KAsync::null<QByteArray>();
594 //TODO This requires supporting every domain type here as well. Can we solve this better so we can do the dispatch somewhere centrally? 597 //TODO This requires supporting every domain type here as well. Can we solve this better so we can do the dispatch somewhere centrally?
595 if (type == ApplicationDomain::getTypeName<ApplicationDomain::Folder>()) { 598 if (type == ApplicationDomain::getTypeName<ApplicationDomain::Folder>()) {
596 auto folder = store().readEntity<ApplicationDomain::Folder>(key); 599 job = replay(store().readEntity<ApplicationDomain::Folder>(key), operation, oldRemoteId, modifiedProperties);
597 job = replay(folder, operation, oldRemoteId, modifiedProperties);
598 } else if (type == ApplicationDomain::getTypeName<ApplicationDomain::Mail>()) { 600 } else if (type == ApplicationDomain::getTypeName<ApplicationDomain::Mail>()) {
599 auto mail = store().readEntity<ApplicationDomain::Mail>(key); 601 job = replay(store().readEntity<ApplicationDomain::Mail>(key), operation, oldRemoteId, modifiedProperties);
600 job = replay(mail, operation, oldRemoteId, modifiedProperties);
601 } else if (type == ApplicationDomain::getTypeName<ApplicationDomain::Contact>()) { 602 } else if (type == ApplicationDomain::getTypeName<ApplicationDomain::Contact>()) {
602 auto mail = store().readEntity<ApplicationDomain::Contact>(key); 603 job = replay(store().readEntity<ApplicationDomain::Contact>(key), operation, oldRemoteId, modifiedProperties);
603 job = replay(mail, operation, oldRemoteId, modifiedProperties);
604 } else if (type == ApplicationDomain::getTypeName<ApplicationDomain::Addressbook>()) { 604 } else if (type == ApplicationDomain::getTypeName<ApplicationDomain::Addressbook>()) {
605 auto mail = store().readEntity<ApplicationDomain::Addressbook>(key); 605 job = replay(store().readEntity<ApplicationDomain::Addressbook>(key), operation, oldRemoteId, modifiedProperties);
606 job = replay(mail, operation, oldRemoteId, modifiedProperties);
607 } else { 606 } else {
608 SinkErrorCtx(mLogCtx) << "Replayed unknown type: " << type; 607 SinkErrorCtx(mLogCtx) << "Replayed unknown type: " << type;
609 } 608 }