summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--CMakeLists.txt6
-rw-r--r--cmake/modules/FindLibgit2.cmake29
-rw-r--r--common/CMakeLists.txt6
-rw-r--r--common/bufferadaptor.h4
-rw-r--r--common/changereplay.cpp13
-rw-r--r--common/changereplay.h3
-rw-r--r--common/commandprocessor.cpp22
-rw-r--r--common/commands.cpp24
-rw-r--r--common/commands/notification.fbs3
-rw-r--r--common/contactpreprocessor.cpp6
-rw-r--r--common/datastorequery.cpp19
-rw-r--r--common/datastorequery.h2
-rw-r--r--common/domain/addressbook.fbs9
-rw-r--r--common/domain/applicationdomaintype.cpp80
-rw-r--r--common/domain/applicationdomaintype.h144
-rw-r--r--common/domain/applicationdomaintype_p.h8
-rw-r--r--common/domain/contact.cpp57
-rw-r--r--common/domain/contact.fbs10
-rw-r--r--common/domain/contact.h57
-rw-r--r--common/domain/domaintypes.h5
-rw-r--r--common/domain/event.cpp57
-rw-r--r--common/domain/event.h57
-rw-r--r--common/domain/folder.cpp60
-rw-r--r--common/domain/folder.h51
-rw-r--r--common/domain/mail.h52
-rw-r--r--common/domain/propertyregistry.cpp18
-rw-r--r--common/domain/propertyregistry.h3
-rw-r--r--common/domain/typeimplementations.cpp (renamed from common/domain/mail.cpp)118
-rw-r--r--common/domain/typeimplementations.h101
-rw-r--r--common/domainadaptor.h17
-rw-r--r--common/genericresource.cpp4
-rw-r--r--common/index.cpp16
-rw-r--r--common/index.h2
-rw-r--r--common/listener.cpp11
-rw-r--r--common/mail/threadindexer.cpp11
-rw-r--r--common/modelresult.cpp119
-rw-r--r--common/modelresult.h12
-rw-r--r--common/notification.cpp31
-rw-r--r--common/notification.h7
-rw-r--r--common/notifier.cpp57
-rw-r--r--common/notifier.h5
-rw-r--r--common/pipeline.cpp169
-rw-r--r--common/pipeline.h24
-rw-r--r--common/propertymapper.cpp30
-rw-r--r--common/propertymapper.h18
-rw-r--r--common/query.h34
-rw-r--r--common/queryrunner.cpp38
-rw-r--r--common/queryrunner.h3
-rw-r--r--common/resourceaccess.cpp16
-rw-r--r--common/resourcecontext.h4
-rw-r--r--common/resourcecontrol.cpp4
-rw-r--r--common/resourcefacade.cpp68
-rw-r--r--common/resultprovider.h10
-rw-r--r--common/specialpurposepreprocessor.cpp22
-rw-r--r--common/specialpurposepreprocessor.h4
-rw-r--r--common/storage.h9
-rw-r--r--common/storage/entitystore.cpp127
-rw-r--r--common/storage/entitystore.h16
-rw-r--r--common/storage_common.cpp20
-rw-r--r--common/storage_lmdb.cpp86
-rw-r--r--common/store.cpp22
-rw-r--r--common/store.h8
-rw-r--r--common/synchronizer.cpp168
-rw-r--r--common/synchronizer.h26
-rw-r--r--common/synchronizerstore.cpp3
-rw-r--r--common/typeindex.cpp6
-rw-r--r--dist/sink.spec9
-rw-r--r--docs/applicationdomaintypes.md21
-rw-r--r--examples/davresource/CMakeLists.txt8
-rw-r--r--examples/davresource/davresource.cpp402
-rw-r--r--examples/davresource/davresource.h2
-rw-r--r--examples/davresource/domainadaptor.cpp35
-rw-r--r--examples/davresource/domainadaptor.h38
-rw-r--r--examples/davresource/facade.cpp44
-rw-r--r--examples/davresource/facade.h36
-rw-r--r--examples/dummyresource/CMakeLists.txt2
-rw-r--r--examples/dummyresource/facade.cpp51
-rw-r--r--examples/dummyresource/facade.h44
-rw-r--r--examples/dummyresource/resourcefactory.cpp13
-rw-r--r--examples/imapresource/CMakeLists.txt2
-rw-r--r--examples/imapresource/domainadaptor.cpp35
-rw-r--r--examples/imapresource/domainadaptor.h38
-rw-r--r--examples/imapresource/facade.cpp44
-rw-r--r--examples/imapresource/facade.h36
-rw-r--r--examples/imapresource/imapresource.cpp139
-rw-r--r--examples/imapresource/imapserverproxy.cpp14
-rw-r--r--examples/imapresource/imapserverproxy.h6
-rw-r--r--examples/maildirresource/CMakeLists.txt5
-rw-r--r--examples/maildirresource/domainadaptor.cpp35
-rw-r--r--examples/maildirresource/domainadaptor.h38
-rw-r--r--examples/maildirresource/libmaildir/CMakeLists.txt8
-rw-r--r--examples/maildirresource/libmaildir/maildir.cpp2
-rw-r--r--examples/maildirresource/libmaildir/maildir.h6
-rw-r--r--examples/maildirresource/libmaildir/maildir_export.h43
-rw-r--r--examples/maildirresource/maildirresource.cpp14
-rw-r--r--examples/mailtransportresource/mailtransport.cpp60
-rw-r--r--examples/mailtransportresource/mailtransport.h4
-rw-r--r--examples/mailtransportresource/mailtransportresource.cpp67
-rw-r--r--examples/mailtransportresource/tests/mailtransporttest.cpp43
-rw-r--r--sinksh/sinksh_utils.cpp4
-rw-r--r--sinksh/syntax_modules/sink_clear.cpp2
-rw-r--r--sinksh/syntax_modules/sink_count.cpp2
-rw-r--r--sinksh/syntax_modules/sink_inspect.cpp2
-rw-r--r--sinksh/syntax_modules/sink_remove.cpp2
-rw-r--r--sinksh/syntax_modules/sink_show.cpp2
-rw-r--r--sinksh/syntax_modules/sink_stat.cpp2
-rw-r--r--sinksh/syntax_modules/sink_sync.cpp4
-rw-r--r--synchronizer/main.cpp2
-rw-r--r--tests/CMakeLists.txt7
-rw-r--r--tests/clientapitest.cpp20
-rw-r--r--tests/dummyresourcetest.cpp9
-rw-r--r--tests/entitystoretest.cpp89
-rw-r--r--tests/genericfacadetest.cpp154
-rw-r--r--tests/genericresourcebenchmark.cpp209
-rw-r--r--tests/genericresourcetest.cpp85
-rw-r--r--tests/hawd/CMakeLists.txt3
-rw-r--r--tests/hawd/state.cpp2
-rw-r--r--tests/interresourcemovetest.cpp37
-rw-r--r--tests/mailquerybenchmark.cpp69
-rw-r--r--tests/mailsynctest.cpp37
-rw-r--r--tests/mailsynctest.h2
-rw-r--r--tests/modelinteractivitytest.cpp6
-rw-r--r--tests/notificationtest.cpp132
-rw-r--r--tests/querytest.cpp65
125 files changed, 2134 insertions, 2410 deletions
diff --git a/.gitignore b/.gitignore
index 1df13e3..8c58b8b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
1*.swp 1*.swp
2*.kdev4 2*.kdev4
3build/
3site 4site
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 532ad41..e3cb26d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,7 +3,7 @@ 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.1.0) 6project(sink VERSION 0.2)
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" OFF)
@@ -27,9 +27,9 @@ include(KDEInstallDirs)
27 27
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) 30find_package(FlatBuffers REQUIRED 1.4.0)
31find_package(KAsync REQUIRED 0.1.0) 31find_package(KAsync REQUIRED 0.1.0)
32find_package(LMDB REQUIRED) 32find_package(LMDB REQUIRED 0.9)
33 33
34find_program(MEMORYCHECK_COMMAND valgrind) 34find_program(MEMORYCHECK_COMMAND valgrind)
35set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes --leak-check=full") 35set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes --leak-check=full")
diff --git a/cmake/modules/FindLibgit2.cmake b/cmake/modules/FindLibgit2.cmake
index f33db4a..fbfb32f 100644
--- a/cmake/modules/FindLibgit2.cmake
+++ b/cmake/modules/FindLibgit2.cmake
@@ -9,8 +9,8 @@
9 9
10# use pkg-config to get the directories and then use these values 10# use pkg-config to get the directories and then use these values
11# in the FIND_PATH() and FIND_LIBRARY() calls 11# in the FIND_PATH() and FIND_LIBRARY() calls
12#FIND_PACKAGE(PkgConfig) 12FIND_PACKAGE(PkgConfig)
13#PKG_SEARCH_MODULE(PC_LIBGIT2 libgit2) 13PKG_SEARCH_MODULE(PC_LIBGIT2 libgit2)
14 14
15SET(LIBGIT2_DEFINITIONS ${PC_LIBGIT2_CFLAGS_OTHER}) 15SET(LIBGIT2_DEFINITIONS ${PC_LIBGIT2_CFLAGS_OTHER})
16 16
@@ -26,11 +26,28 @@ FIND_LIBRARY(LIBGIT2_LIBRARIES NAMES git2
26 ${PC_LIBGIT2_LIBRARY_DIRS} 26 ${PC_LIBGIT2_LIBRARY_DIRS}
27 ) 27 )
28 28
29#Should be replaced by a version check 29message("foo: ${LIBGIT2_INCLUDE_DIR} : ${PC_LIBGIT2_INCLUDEDIR} : ${PC_LIBGIT2_INCLUDE_DIRS}")
30include(CheckFunctionExists) 30
31CHECK_FUNCTION_EXISTS(git_buf_free HAVE_BUF_FREE) 31# get version from header, should work on windows, too
32if(LIBGIT2_INCLUDE_DIR)
33 file(STRINGS "${LIBGIT2_INCLUDE_DIR}/git2/version.h" LIBGIT2_H REGEX "^#define LIBGIT2_VERSION \"[^\"]*\"$")
34
35 string(REGEX REPLACE "^.*LIBGIT2_VERSION \"([0-9]+).*$" "\\1" LIBGIT2_VERSION_MAJOR "${LIBGIT2_H}")
36 string(REGEX REPLACE "^.*LIBGIT2_VERSION \"[0-9]+\\.([0-9]+).*$" "\\1" LIBGIT2_VERSION_MINOR "${LIBGIT2_H}")
37 string(REGEX REPLACE "^.*LIBGIT2_VERSION \"[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1" LIBGIT2_VERSION_PATCH "${LIBGIT2_H}")
38 set(LIBGIT2_VERSION "${LIBGIT2_VERSION_MAJOR}.${LIBGIT2_VERSION_MINOR}.${LIBGIT2_VERSION_PATCH}")
39
40 set(LIBGIT2_MAJOR_VERSION "${LIBGIT2_VERSION_MAJOR}")
41 set(LIBGIT2_MINOR_VERSION "${LIBGIT2_VERSION_MINOR}")
42 set(LIBGIT2_PATCH_VERSION "${LIBGIT2_VERSION_PATCH}")
43
44 unset(LIBGIT2_H)
45endif()
32 46
33INCLUDE(FindPackageHandleStandardArgs) 47INCLUDE(FindPackageHandleStandardArgs)
34FIND_PACKAGE_HANDLE_STANDARD_ARGS(libgit2 DEFAULT_MSG LIBGIT2_LIBRARIES LIBGIT2_INCLUDE_DIR HAVE_BUF_FREE) 48FIND_PACKAGE_HANDLE_STANDARD_ARGS(libgit2
49 REQUIRED_VARS LIBGIT2_LIBRARIES LIBGIT2_INCLUDE_DIR
50 FOUND_VAR LIBGIT2_FOUND
51 VERSION_VAR LIBGIT2_VERSION)
35 52
36MARK_AS_ADVANCED(LIBGIT2_INCLUDE_DIR LIBGIT2_LIBRARIES) 53MARK_AS_ADVANCED(LIBGIT2_INCLUDE_DIR LIBGIT2_LIBRARIES)
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index b5275e0..001a412 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -61,10 +61,7 @@ set(command_SRCS
61 resultset.cpp 61 resultset.cpp
62 domain/propertyregistry.cpp 62 domain/propertyregistry.cpp
63 domain/applicationdomaintype.cpp 63 domain/applicationdomaintype.cpp
64 domain/contact.cpp 64 domain/typeimplementations.cpp
65 domain/event.cpp
66 domain/mail.cpp
67 domain/folder.cpp
68 test.cpp 65 test.cpp
69 query.cpp 66 query.cpp
70 changereplay.cpp 67 changereplay.cpp
@@ -101,6 +98,7 @@ generate_flatbuffers(
101 commands/inspection 98 commands/inspection
102 commands/flush 99 commands/flush
103 domain/contact 100 domain/contact
101 domain/addressbook
104 domain/event 102 domain/event
105 domain/mail 103 domain/mail
106 domain/folder 104 domain/folder
diff --git a/common/bufferadaptor.h b/common/bufferadaptor.h
index 0ae7bf5..fd4809b 100644
--- a/common/bufferadaptor.h
+++ b/common/bufferadaptor.h
@@ -39,12 +39,12 @@ public:
39 } 39 }
40 virtual QVariant getProperty(const QByteArray &key) const 40 virtual QVariant getProperty(const QByteArray &key) const
41 { 41 {
42 qFatal("Tried to get property: " + key); 42 qFatal("Tried to get property: %s", key.data());
43 return QVariant(); 43 return QVariant();
44 } 44 }
45 virtual void setProperty(const QByteArray &key, const QVariant &value) 45 virtual void setProperty(const QByteArray &key, const QVariant &value)
46 { 46 {
47 qFatal("Tried to get property: " + key); 47 qFatal("Tried to get property: %s", key.data());
48 } 48 }
49 virtual QList<QByteArray> availableProperties() const 49 virtual QList<QByteArray> availableProperties() const
50 { 50 {
diff --git a/common/changereplay.cpp b/common/changereplay.cpp
index 532cca8..7895b66 100644
--- a/common/changereplay.cpp
+++ b/common/changereplay.cpp
@@ -29,10 +29,10 @@
29using namespace Sink; 29using namespace Sink;
30using namespace Sink::Storage; 30using namespace Sink::Storage;
31 31
32ChangeReplay::ChangeReplay(const ResourceContext &resourceContext) 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{"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}
34{ 35{
35 SinkTraceCtx(mLogCtx) << "Created change replay: " << resourceContext.instanceId();
36} 36}
37 37
38qint64 ChangeReplay::getLastReplayedRevision() 38qint64 ChangeReplay::getLastReplayedRevision()
@@ -54,7 +54,6 @@ bool ChangeReplay::allChangesReplayed()
54 SinkWarningCtx(mLogCtx) << error.message; 54 SinkWarningCtx(mLogCtx) << error.message;
55 })); 55 }));
56 const qint64 lastReplayedRevision = getLastReplayedRevision(); 56 const qint64 lastReplayedRevision = getLastReplayedRevision();
57 SinkTraceCtx(mLogCtx) << "Checking if all replayed. Top revision:" << topRevision << "Last replayed:" << lastReplayedRevision;
58 return (lastReplayedRevision >= topRevision); 57 return (lastReplayedRevision >= topRevision);
59} 58}
60 59
@@ -99,8 +98,12 @@ KAsync::Job<void> ChangeReplay::replayNextRevision()
99 SinkTraceCtx(mLogCtx) << "Changereplay from " << *lastReplayedRevision << " to " << *topRevision; 98 SinkTraceCtx(mLogCtx) << "Changereplay from " << *lastReplayedRevision << " to " << *topRevision;
100 return KAsync::doWhile( 99 return KAsync::doWhile(
101 [this, lastReplayedRevision, topRevision]() -> KAsync::Job<KAsync::ControlFlowFlag> { 100 [this, lastReplayedRevision, topRevision]() -> KAsync::Job<KAsync::ControlFlowFlag> {
101 if (!mGuard) {
102 SinkTraceCtx(mLogCtx) << "Exit due to guard";
103 return KAsync::value(KAsync::Break);
104 }
102 if (*lastReplayedRevision >= *topRevision) { 105 if (*lastReplayedRevision >= *topRevision) {
103 SinkTraceCtx(mLogCtx) << "Done replaying"; 106 SinkTraceCtx(mLogCtx) << "Done replaying" << *lastReplayedRevision << *topRevision;
104 return KAsync::value(KAsync::Break); 107 return KAsync::value(KAsync::Break);
105 } 108 }
106 109
diff --git a/common/changereplay.h b/common/changereplay.h
index 3ca896e..edc4462 100644
--- a/common/changereplay.h
+++ b/common/changereplay.h
@@ -39,7 +39,7 @@ class SINK_EXPORT ChangeReplay : public QObject
39{ 39{
40 Q_OBJECT 40 Q_OBJECT
41public: 41public:
42 ChangeReplay(const ResourceContext &resourceContext); 42 ChangeReplay(const ResourceContext &resourceContext, const Sink::Log::Context &ctx= {});
43 43
44 qint64 getLastReplayedRevision(); 44 qint64 getLastReplayedRevision();
45 virtual bool allChangesReplayed(); 45 virtual bool allChangesReplayed();
@@ -63,6 +63,7 @@ private:
63 bool mReplayInProgress; 63 bool mReplayInProgress;
64 Sink::Storage::DataStore::Transaction mMainStoreTransaction; 64 Sink::Storage::DataStore::Transaction mMainStoreTransaction;
65 Sink::Log::Context mLogCtx; 65 Sink::Log::Context mLogCtx;
66 QSharedPointer<QObject> mGuard;
66}; 67};
67 68
68class NullChangeReplay : public ChangeReplay 69class NullChangeReplay : public ChangeReplay
diff --git a/common/commandprocessor.cpp b/common/commandprocessor.cpp
index 33e2f81..3507ef1 100644
--- a/common/commandprocessor.cpp
+++ b/common/commandprocessor.cpp
@@ -245,9 +245,9 @@ KAsync::Job<void> CommandProcessor::processQueue(MessageQueue *queue)
245 } 245 }
246 } 246 }
247 if (queue->isEmpty()) { 247 if (queue->isEmpty()) {
248 return KAsync::value<KAsync::ControlFlowFlag>(KAsync::Break); 248 return KAsync::Break;
249 } else { 249 } else {
250 return KAsync::value<KAsync::ControlFlowFlag>(KAsync::Continue); 250 return KAsync::Continue;
251 } 251 }
252 }); 252 });
253 })) 253 }))
@@ -295,24 +295,6 @@ void CommandProcessor::setSynchronizer(const QSharedPointer<Synchronizer> &synch
295 mSynchronizer->setup([this](int commandId, const QByteArray &data) { 295 mSynchronizer->setup([this](int commandId, const QByteArray &data) {
296 enqueueCommand(mSynchronizerQueue, commandId, data); 296 enqueueCommand(mSynchronizerQueue, commandId, data);
297 }, mSynchronizerQueue); 297 }, mSynchronizerQueue);
298
299 QObject::connect(mSynchronizer.data(), &Synchronizer::replayingChanges, [this]() {
300 Sink::Notification n;
301 n.id = "changereplay";
302 n.type = Notification::Status;
303 n.message = "Replaying changes.";
304 n.code = ApplicationDomain::BusyStatus;
305 emit notify(n);
306 });
307 QObject::connect(mSynchronizer.data(), &Synchronizer::changesReplayed, [this]() {
308 Sink::Notification n;
309 n.id = "changereplay";
310 n.type = Notification::Status;
311 n.message = "All changes have been replayed.";
312 n.code = ApplicationDomain::ConnectedStatus;
313 emit notify(n);
314 });
315
316 QObject::connect(mSynchronizer.data(), &Synchronizer::notify, this, &CommandProcessor::notify); 298 QObject::connect(mSynchronizer.data(), &Synchronizer::notify, this, &CommandProcessor::notify);
317 setOldestUsedRevision(mSynchronizer->getLastReplayedRevision()); 299 setOldestUsedRevision(mSynchronizer->getLastReplayedRevision());
318} 300}
diff --git a/common/commands.cpp b/common/commands.cpp
index ce83d03..eeb7f08 100644
--- a/common/commands.cpp
+++ b/common/commands.cpp
@@ -21,6 +21,7 @@
21#include "commands.h" 21#include "commands.h"
22 22
23#include <QIODevice> 23#include <QIODevice>
24#include <log.h>
24 25
25namespace Sink { 26namespace Sink {
26 27
@@ -77,27 +78,34 @@ void write(QIODevice *device, int messageId, int commandId)
77 write(device, messageId, commandId, 0, 0); 78 write(device, messageId, commandId, 0, 0);
78} 79}
79 80
81static void write(QIODevice *device, const char *buffer, uint size)
82{
83 if (device->write(buffer, size) < 0) {
84 SinkWarningCtx(Sink::Log::Context{"commands"}) << "Error while writing " << device->errorString();
85 }
86}
87
80void write(QIODevice *device, int messageId, int commandId, const char *buffer, uint size) 88void write(QIODevice *device, int messageId, int commandId, const char *buffer, uint size)
81{ 89{
82 if (size > 0 && !buffer) { 90 if (size > 0 && !buffer) {
83 size = 0; 91 size = 0;
84 } 92 }
85 93
86 device->write((const char *)&messageId, sizeof(int)); 94 write(device, (const char *)&messageId, sizeof(int));
87 device->write((const char *)&commandId, sizeof(int)); 95 write(device, (const char *)&commandId, sizeof(int));
88 device->write((const char *)&size, sizeof(uint)); 96 write(device, (const char *)&size, sizeof(uint));
89 if (buffer) { 97 if (buffer) {
90 device->write(buffer, size); 98 write(device, buffer, size);
91 } 99 }
92} 100}
93 101
94void write(QIODevice *device, int messageId, int commandId, flatbuffers::FlatBufferBuilder &fbb) 102void write(QIODevice *device, int messageId, int commandId, flatbuffers::FlatBufferBuilder &fbb)
95{ 103{
96 const int dataSize = fbb.GetSize(); 104 const int dataSize = fbb.GetSize();
97 device->write((const char *)&messageId, sizeof(int)); 105 write(device, (const char *)&messageId, sizeof(int));
98 device->write((const char *)&commandId, sizeof(int)); 106 write(device, (const char *)&commandId, sizeof(int));
99 device->write((const char *)&dataSize, sizeof(int)); 107 write(device, (const char *)&dataSize, sizeof(int));
100 device->write((const char *)fbb.GetBufferPointer(), dataSize); 108 write(device, (const char *)fbb.GetBufferPointer(), dataSize);
101} 109}
102 110
103} // namespace Commands 111} // namespace Commands
diff --git a/common/commands/notification.fbs b/common/commands/notification.fbs
index c82fad3..517111c 100644
--- a/common/commands/notification.fbs
+++ b/common/commands/notification.fbs
@@ -2,9 +2,10 @@ namespace Sink.Commands;
2 2
3table Notification { 3table Notification {
4 type: int = 0; //See notification.h 4 type: int = 0; //See notification.h
5 identifier: string; //An identifier that links back to the something related to the notification (e.g. an entity id or 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 entities: [string]; //A list of entities this applies to
8} 9}
9 10
10root_type Notification; 11root_type Notification;
diff --git a/common/contactpreprocessor.cpp b/common/contactpreprocessor.cpp
index 0f2ca17..ac2c3bc 100644
--- a/common/contactpreprocessor.cpp
+++ b/common/contactpreprocessor.cpp
@@ -28,9 +28,11 @@ void updatedProperties(Sink::ApplicationDomain::Contact &contact, const KContact
28{ 28{
29 contact.setUid(addressee.uid()); 29 contact.setUid(addressee.uid());
30 contact.setFn(addressee.formattedName()); 30 contact.setFn(addressee.formattedName());
31 QByteArrayList emails; 31 contact.setFirstname(addressee.givenName());
32 contact.setLastname(addressee.familyName());
33 QList<Sink::ApplicationDomain::Contact::Email> emails;
32 for (const auto &email : addressee.emails()) { 34 for (const auto &email : addressee.emails()) {
33 emails << email.toUtf8(); 35 emails << Sink::ApplicationDomain::Contact::Email{Sink::ApplicationDomain::Contact::Email::Undefined, email};
34 } 36 }
35 contact.setEmails(emails); 37 contact.setEmails(emails);
36} 38}
diff --git a/common/datastorequery.cpp b/common/datastorequery.cpp
index 34d2bae..2e0c348 100644
--- a/common/datastorequery.cpp
+++ b/common/datastorequery.cpp
@@ -144,7 +144,7 @@ public:
144 const auto property = entity.getProperty(filterProperty); 144 const auto property = entity.getProperty(filterProperty);
145 const auto comparator = propertyFilter.value(filterProperty); 145 const auto comparator = propertyFilter.value(filterProperty);
146 if (!comparator.matches(property)) { 146 if (!comparator.matches(property)) {
147 SinkTraceCtx(mDatastore->mLogCtx) << "Filtering entity due to property mismatch on filter: " << filterProperty << property << ":" << comparator.value; 147 SinkTraceCtx(mDatastore->mLogCtx) << "Filtering entity due to property mismatch on filter: " << entity.identifier() << "Property: " << filterProperty << property << " Filter:" << comparator.value;
148 return false; 148 return false;
149 } 149 }
150 } 150 }
@@ -152,7 +152,7 @@ public:
152 } 152 }
153}; 153};
154 154
155class Reduce : public FilterBase { 155class Reduce : public Filter {
156public: 156public:
157 typedef QSharedPointer<Reduce> Ptr; 157 typedef QSharedPointer<Reduce> Ptr;
158 158
@@ -198,7 +198,7 @@ public:
198 QList<Aggregator> mAggregators; 198 QList<Aggregator> mAggregators;
199 199
200 Reduce(const QByteArray &reductionProperty, const QByteArray &selectionProperty, QueryBase::Reduce::Selector::Comparator comparator, FilterBase::Ptr source, DataStoreQuery *store) 200 Reduce(const QByteArray &reductionProperty, const QByteArray &selectionProperty, QueryBase::Reduce::Selector::Comparator comparator, FilterBase::Ptr source, DataStoreQuery *store)
201 : FilterBase(source, store), 201 : Filter(source, store),
202 mReductionProperty(reductionProperty), 202 mReductionProperty(reductionProperty),
203 mSelectionProperty(selectionProperty), 203 mSelectionProperty(selectionProperty),
204 mSelectionComparator(comparator) 204 mSelectionComparator(comparator)
@@ -236,6 +236,11 @@ public:
236 236
237 for (const auto &r : results) { 237 for (const auto &r : results) {
238 readEntity(r, [&, this](const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Operation operation) { 238 readEntity(r, [&, this](const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Operation operation) {
239 //We need to apply all property filters that we have until the reduction, because the index lookup was unfiltered.
240 if (!matchesFilter(entity)) {
241 return;
242 }
243
239 Q_ASSERT(operation != Sink::Operation_Removal); 244 Q_ASSERT(operation != Sink::Operation_Removal);
240 for (auto &aggregator : mAggregators) { 245 for (auto &aggregator : mAggregators) {
241 if (!aggregator.property.isEmpty()) { 246 if (!aggregator.property.isEmpty()) {
@@ -362,19 +367,22 @@ public:
362DataStoreQuery::DataStoreQuery(const Sink::QueryBase &query, const QByteArray &type, EntityStore &store) 367DataStoreQuery::DataStoreQuery(const Sink::QueryBase &query, const QByteArray &type, EntityStore &store)
363 : mType(type), mStore(store), mLogCtx(store.logContext().subContext("datastorequery")) 368 : mType(type), mStore(store), mLogCtx(store.logContext().subContext("datastorequery"))
364{ 369{
370 //This is what we use during a new query
365 setupQuery(query); 371 setupQuery(query);
366} 372}
367 373
368DataStoreQuery::DataStoreQuery(const DataStoreQuery::State &state, const QByteArray &type, Sink::Storage::EntityStore &store) 374DataStoreQuery::DataStoreQuery(const DataStoreQuery::State &state, const QByteArray &type, Sink::Storage::EntityStore &store, bool incremental)
369 : mType(type), mStore(store), mLogCtx(store.logContext().subContext("datastorequery")) 375 : mType(type), mStore(store), mLogCtx(store.logContext().subContext("datastorequery"))
370{ 376{
377 //This is what we use when fetching more data, without having a new revision with incremental=false
378 //And this is what we use when the data changed and we want to update with incremental = true
371 mCollector = state.mCollector; 379 mCollector = state.mCollector;
372 mSource = state.mSource; 380 mSource = state.mSource;
373 381
374 auto source = mCollector; 382 auto source = mCollector;
375 while (source) { 383 while (source) {
376 source->mDatastore = this; 384 source->mDatastore = this;
377 source->mIncremental = true; 385 source->mIncremental = incremental;
378 source = source->mSource; 386 source = source->mSource;
379 } 387 }
380} 388}
@@ -553,6 +561,7 @@ void DataStoreQuery::setupQuery(const Sink::QueryBase &query_)
553 for (const auto &aggregator : filter->aggregators) { 561 for (const auto &aggregator : filter->aggregators) {
554 reduction->mAggregators << Reduce::Aggregator(aggregator.operation, aggregator.propertyToCollect, aggregator.resultProperty); 562 reduction->mAggregators << Reduce::Aggregator(aggregator.operation, aggregator.propertyToCollect, aggregator.resultProperty);
555 } 563 }
564 reduction->propertyFilter = query.getBaseFilters();
556 baseSet = reduction; 565 baseSet = reduction;
557 } else if (auto filter = stage.dynamicCast<Query::Bloom>()) { 566 } else if (auto filter = stage.dynamicCast<Query::Bloom>()) {
558 baseSet = Bloom::Ptr::create(filter->property, baseSet, this); 567 baseSet = Bloom::Ptr::create(filter->property, baseSet, this);
diff --git a/common/datastorequery.h b/common/datastorequery.h
index a797782..ee5f99e 100644
--- a/common/datastorequery.h
+++ b/common/datastorequery.h
@@ -46,7 +46,7 @@ public:
46 }; 46 };
47 47
48 DataStoreQuery(const Sink::QueryBase &query, const QByteArray &type, Sink::Storage::EntityStore &store); 48 DataStoreQuery(const Sink::QueryBase &query, const QByteArray &type, Sink::Storage::EntityStore &store);
49 DataStoreQuery(const DataStoreQuery::State &state, const QByteArray &type, Sink::Storage::EntityStore &store); 49 DataStoreQuery(const DataStoreQuery::State &state, const QByteArray &type, Sink::Storage::EntityStore &store, bool incremental);
50 ~DataStoreQuery(); 50 ~DataStoreQuery();
51 ResultSet execute(); 51 ResultSet execute();
52 ResultSet update(qint64 baseRevision); 52 ResultSet update(qint64 baseRevision);
diff --git a/common/domain/addressbook.fbs b/common/domain/addressbook.fbs
new file mode 100644
index 0000000..c2bda2b
--- /dev/null
+++ b/common/domain/addressbook.fbs
@@ -0,0 +1,9 @@
1namespace Sink.ApplicationDomain.Buffer;
2
3table Addressbook {
4 name:string;
5 parent:string;
6}
7
8root_type Addressbook;
9file_identifier "AKFB";
diff --git a/common/domain/applicationdomaintype.cpp b/common/domain/applicationdomaintype.cpp
index fd88570..44f5a75 100644
--- a/common/domain/applicationdomaintype.cpp
+++ b/common/domain/applicationdomaintype.cpp
@@ -65,6 +65,9 @@ int registerProperty() {
65 return 0; 65 return 0;
66} 66}
67 67
68#define SINK_REGISTER_ENTITY(ENTITY) \
69 constexpr const char *ENTITY::name;
70
68#define SINK_REGISTER_PROPERTY(ENTITYTYPE, PROPERTY) \ 71#define SINK_REGISTER_PROPERTY(ENTITYTYPE, PROPERTY) \
69 constexpr const char *ENTITYTYPE::PROPERTY::name; \ 72 constexpr const char *ENTITYTYPE::PROPERTY::name; \
70 static int foo##ENTITYTYPE##PROPERTY = registerProperty<ENTITYTYPE, ENTITYTYPE::PROPERTY>(); 73 static int foo##ENTITYTYPE##PROPERTY = registerProperty<ENTITYTYPE, ENTITYTYPE::PROPERTY>();
@@ -72,6 +75,10 @@ int registerProperty() {
72namespace Sink { 75namespace Sink {
73namespace ApplicationDomain { 76namespace ApplicationDomain {
74 77
78constexpr const char *SinkResource::name;
79constexpr const char *SinkAccount::name;
80
81SINK_REGISTER_ENTITY(Mail);
75SINK_REGISTER_PROPERTY(Mail, Sender); 82SINK_REGISTER_PROPERTY(Mail, Sender);
76SINK_REGISTER_PROPERTY(Mail, To); 83SINK_REGISTER_PROPERTY(Mail, To);
77SINK_REGISTER_PROPERTY(Mail, Cc); 84SINK_REGISTER_PROPERTY(Mail, Cc);
@@ -90,11 +97,28 @@ SINK_REGISTER_PROPERTY(Mail, MessageId);
90SINK_REGISTER_PROPERTY(Mail, ParentMessageId); 97SINK_REGISTER_PROPERTY(Mail, ParentMessageId);
91SINK_REGISTER_PROPERTY(Mail, ThreadId); 98SINK_REGISTER_PROPERTY(Mail, ThreadId);
92 99
100SINK_REGISTER_ENTITY(Folder);
93SINK_REGISTER_PROPERTY(Folder, Name); 101SINK_REGISTER_PROPERTY(Folder, Name);
94SINK_REGISTER_PROPERTY(Folder, Icon); 102SINK_REGISTER_PROPERTY(Folder, Icon);
95SINK_REGISTER_PROPERTY(Folder, SpecialPurpose); 103SINK_REGISTER_PROPERTY(Folder, SpecialPurpose);
96SINK_REGISTER_PROPERTY(Folder, Enabled); 104SINK_REGISTER_PROPERTY(Folder, Enabled);
97SINK_REGISTER_PROPERTY(Folder, Parent); 105SINK_REGISTER_PROPERTY(Folder, Parent);
106SINK_REGISTER_PROPERTY(Folder, Count);
107SINK_REGISTER_PROPERTY(Folder, FullContentAvailable);
108
109SINK_REGISTER_ENTITY(Contact);
110SINK_REGISTER_PROPERTY(Contact, Uid);
111SINK_REGISTER_PROPERTY(Contact, Fn);
112SINK_REGISTER_PROPERTY(Contact, Firstname);
113SINK_REGISTER_PROPERTY(Contact, Lastname);
114SINK_REGISTER_PROPERTY(Contact, Emails);
115SINK_REGISTER_PROPERTY(Contact, Vcard);
116SINK_REGISTER_PROPERTY(Contact, Addressbook);
117
118SINK_REGISTER_ENTITY(Addressbook);
119SINK_REGISTER_PROPERTY(Addressbook, Name);
120SINK_REGISTER_PROPERTY(Addressbook, Parent);
121SINK_REGISTER_PROPERTY(Addressbook, LastUpdated);
98 122
99static const int foo = [] { 123static const int foo = [] {
100 QMetaType::registerEqualsComparator<Reference>(); 124 QMetaType::registerEqualsComparator<Reference>();
@@ -115,10 +139,8 @@ void copyBuffer(Sink::ApplicationDomain::BufferAdaptor &buffer, Sink::Applicatio
115 for (const auto &property : propertiesToCopy) { 139 for (const auto &property : propertiesToCopy) {
116 const auto value = buffer.getProperty(property); 140 const auto value = buffer.getProperty(property);
117 if (copyBlobs && value.canConvert<BLOB>()) { 141 if (copyBlobs && value.canConvert<BLOB>()) {
118 auto oldPath = value.value<BLOB>().value; 142 const auto oldPath = value.value<BLOB>().value;
119 //FIXME: This is neither pretty nor save if we have multiple modifications of the same property (the first modification will remove the file). 143 const auto newPath = Sink::temporaryFileLocation() + "/" + QUuid::createUuid().toString();
120 //At least if the modification fails the file will be removed once the entity is removed.
121 auto newPath = oldPath + "copy";
122 QFile::copy(oldPath, newPath); 144 QFile::copy(oldPath, newPath);
123 memoryAdaptor.setProperty(property, QVariant::fromValue(BLOB{newPath})); 145 memoryAdaptor.setProperty(property, QVariant::fromValue(BLOB{newPath}));
124 } else if (pruneReferences && value.canConvert<Reference>()) { 146 } else if (pruneReferences && value.canConvert<Reference>()) {
@@ -364,52 +386,12 @@ SinkResource ImapResource::create(const QByteArray &account)
364 return resource; 386 return resource;
365} 387}
366 388
367template<> 389SinkResource CardDavResource::create(const QByteArray &account)
368QByteArray getTypeName<Contact>()
369{
370 return "contact";
371}
372
373template<>
374QByteArray getTypeName<Event>()
375{
376 return "event";
377}
378
379template<>
380QByteArray getTypeName<Todo>()
381{
382 return "todo";
383}
384
385template<>
386QByteArray getTypeName<SinkResource>()
387{
388 return "resource";
389}
390
391template<>
392QByteArray getTypeName<SinkAccount>()
393{
394 return "account";
395}
396
397template<>
398QByteArray getTypeName<Identity>()
399{
400 return "identity";
401}
402
403template<>
404QByteArray getTypeName<Mail>()
405{ 390{
406 return "mail"; 391 auto &&resource = ApplicationDomainType::createEntity<SinkResource>();
407} 392 resource.setResourceType("sink.dav");
408 393 resource.setAccount(account);
409template<> 394 return resource;
410QByteArray getTypeName<Folder>()
411{
412 return "folder";
413} 395}
414 396
415QByteArrayList getTypeNames() 397QByteArrayList getTypeNames()
diff --git a/common/domain/applicationdomaintype.h b/common/domain/applicationdomaintype.h
index be04db9..e5aa46e 100644
--- a/common/domain/applicationdomaintype.h
+++ b/common/domain/applicationdomaintype.h
@@ -28,7 +28,8 @@
28#include <QUuid> 28#include <QUuid>
29#include "bufferadaptor.h" 29#include "bufferadaptor.h"
30 30
31#define SINK_ENTITY(TYPE) \ 31#define SINK_ENTITY(TYPE, LOWERCASENAME) \
32 static constexpr const char *name = #LOWERCASENAME; \
32 typedef QSharedPointer<TYPE> Ptr; \ 33 typedef QSharedPointer<TYPE> Ptr; \
33 using Entity::Entity; \ 34 using Entity::Entity; \
34 TYPE() = default; \ 35 TYPE() = default; \
@@ -92,6 +93,43 @@
92namespace Sink { 93namespace Sink {
93namespace ApplicationDomain { 94namespace ApplicationDomain {
94 95
96enum SINK_EXPORT ErrorCode {
97 NoError = 0,
98 UnknownError,
99 NoServerError,
100 ConnectionError,
101 LoginError,
102 ConfigurationError,
103 TransmissionError,
104};
105
106enum SINK_EXPORT SuccessCode {
107 TransmissionSuccess
108};
109
110enum SINK_EXPORT SyncStatus {
111 NoSyncStatus,
112 SyncInProgress,
113 SyncError,
114 SyncSuccess
115};
116
117/**
118 * The status of an account or resource.
119 *
120 * It is set as follows:
121 * * By default the status is offline.
122 * * 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.
124 * * If a long running operation is started the resource goes to the busy state (and return to the previous state after that).
125 */
126enum SINK_EXPORT Status {
127 OfflineStatus,
128 ConnectedStatus,
129 BusyStatus,
130 ErrorStatus
131};
132
95struct SINK_EXPORT Error { 133struct SINK_EXPORT Error {
96 134
97}; 135};
@@ -100,6 +138,11 @@ struct SINK_EXPORT Progress {
100 138
101}; 139};
102 140
141/**
142 * Internal type.
143 *
144 * Represents a BLOB property.
145 */
103struct BLOB { 146struct BLOB {
104 BLOB() = default; 147 BLOB() = default;
105 BLOB(const BLOB &) = default; 148 BLOB(const BLOB &) = default;
@@ -268,6 +311,7 @@ SINK_EXPORT QDebug operator<< (QDebug d, const BLOB &blob);
268 311
269 312
270struct SINK_EXPORT SinkAccount : public ApplicationDomainType { 313struct SINK_EXPORT SinkAccount : public ApplicationDomainType {
314 static constexpr const char *name = "account";
271 typedef QSharedPointer<SinkAccount> Ptr; 315 typedef QSharedPointer<SinkAccount> Ptr;
272 explicit SinkAccount(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer<BufferAdaptor> &adaptor); 316 explicit SinkAccount(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer<BufferAdaptor> &adaptor);
273 explicit SinkAccount(const QByteArray &identifier); 317 explicit SinkAccount(const QByteArray &identifier);
@@ -278,8 +322,6 @@ struct SINK_EXPORT SinkAccount : public ApplicationDomainType {
278 SINK_PROPERTY(QString, Icon, icon); 322 SINK_PROPERTY(QString, Icon, icon);
279 SINK_PROPERTY(QString, AccountType, type); 323 SINK_PROPERTY(QString, AccountType, type);
280 SINK_STATUS_PROPERTY(int, Status, status); 324 SINK_STATUS_PROPERTY(int, Status, status);
281 SINK_STATUS_PROPERTY(ApplicationDomain::Error, Error, error);
282 SINK_STATUS_PROPERTY(ApplicationDomain::Progress, Progress, progress);
283}; 325};
284 326
285 327
@@ -290,6 +332,7 @@ struct SINK_EXPORT SinkAccount : public ApplicationDomainType {
290 * and for creating and removing resource instances. 332 * and for creating and removing resource instances.
291 */ 333 */
292struct SINK_EXPORT SinkResource : public ApplicationDomainType { 334struct SINK_EXPORT SinkResource : public ApplicationDomainType {
335 static constexpr const char *name = "resource";
293 typedef QSharedPointer<SinkResource> Ptr; 336 typedef QSharedPointer<SinkResource> Ptr;
294 explicit SinkResource(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer<BufferAdaptor> &adaptor); 337 explicit SinkResource(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer<BufferAdaptor> &adaptor);
295 explicit SinkResource(const QByteArray &identifier); 338 explicit SinkResource(const QByteArray &identifier);
@@ -300,8 +343,6 @@ struct SINK_EXPORT SinkResource : public ApplicationDomainType {
300 SINK_PROPERTY(QByteArray, ResourceType, type); 343 SINK_PROPERTY(QByteArray, ResourceType, type);
301 SINK_PROPERTY(QByteArrayList, Capabilities, capabilities); 344 SINK_PROPERTY(QByteArrayList, Capabilities, capabilities);
302 SINK_STATUS_PROPERTY(int, Status, status); 345 SINK_STATUS_PROPERTY(int, Status, status);
303 SINK_STATUS_PROPERTY(ApplicationDomain::Error, Error, error);
304 SINK_STATUS_PROPERTY(ApplicationDomain::Progress, Progress, progress);
305}; 346};
306 347
307struct SINK_EXPORT Entity : public ApplicationDomainType { 348struct SINK_EXPORT Entity : public ApplicationDomainType {
@@ -312,16 +353,35 @@ struct SINK_EXPORT Entity : public ApplicationDomainType {
312 virtual ~Entity() = default; 353 virtual ~Entity() = default;
313}; 354};
314 355
356struct SINK_EXPORT Addressbook : public Entity {
357 SINK_ENTITY(Addressbook, addressbook);
358 SINK_REFERENCE_PROPERTY(Addressbook, Parent, parent);
359 SINK_PROPERTY(QString, Name, name);
360 SINK_EXTRACTED_PROPERTY(QDateTime, LastUpdated, lastUpdated);
361};
362
315struct SINK_EXPORT Contact : public Entity { 363struct SINK_EXPORT Contact : public Entity {
316 SINK_ENTITY(Contact); 364 struct SINK_EXPORT Email {
365 enum Type {
366 Undefined,
367 Work,
368 Home
369 };
370 Type type;
371 QString email;
372 };
373 SINK_ENTITY(Contact, contact);
317 SINK_PROPERTY(QString, Uid, uid); 374 SINK_PROPERTY(QString, Uid, uid);
318 SINK_PROPERTY(QString, Fn, fn); 375 SINK_PROPERTY(QString, Fn, fn);
319 SINK_PROPERTY(QByteArrayList, Emails, emails); 376 SINK_PROPERTY(QString, Firstname, firstname);
377 SINK_PROPERTY(QString, Lastname, lastname);
378 SINK_PROPERTY(QList<Email>, Emails, emails);
320 SINK_PROPERTY(QByteArray, Vcard, vcard); 379 SINK_PROPERTY(QByteArray, Vcard, vcard);
380 SINK_REFERENCE_PROPERTY(Addressbook, Addressbook, addressbook);
321}; 381};
322 382
323struct SINK_EXPORT Event : public Entity { 383struct SINK_EXPORT Event : public Entity {
324 SINK_ENTITY(Event); 384 SINK_ENTITY(Event, event);
325 SINK_PROPERTY(QString, Uid, uid); 385 SINK_PROPERTY(QString, Uid, uid);
326 SINK_PROPERTY(QString, Summary, summary); 386 SINK_PROPERTY(QString, Summary, summary);
327 SINK_PROPERTY(QString, Description, description); 387 SINK_PROPERTY(QString, Description, description);
@@ -329,20 +389,23 @@ struct SINK_EXPORT Event : public Entity {
329}; 389};
330 390
331struct SINK_EXPORT Todo : public Entity { 391struct SINK_EXPORT Todo : public Entity {
332 SINK_ENTITY(Todo); 392 SINK_ENTITY(Todo, todo);
333}; 393};
334 394
335struct SINK_EXPORT Calendar : public Entity { 395struct SINK_EXPORT Calendar : public Entity {
336 SINK_ENTITY(Calendar); 396 SINK_ENTITY(Calendar, calendar);
337}; 397};
338 398
339struct SINK_EXPORT Folder : public Entity { 399struct SINK_EXPORT Folder : public Entity {
340 SINK_ENTITY(Folder); 400 SINK_ENTITY(Folder, folder);
341 SINK_REFERENCE_PROPERTY(Folder, Parent, parent); 401 SINK_REFERENCE_PROPERTY(Folder, Parent, parent);
342 SINK_PROPERTY(QString, Name, name); 402 SINK_PROPERTY(QString, Name, name);
343 SINK_PROPERTY(QByteArray, Icon, icon); 403 SINK_PROPERTY(QByteArray, Icon, icon);
344 SINK_PROPERTY(QByteArrayList, SpecialPurpose, specialpurpose); 404 SINK_PROPERTY(QByteArrayList, SpecialPurpose, specialpurpose);
345 SINK_PROPERTY(bool, Enabled, enabled); 405 SINK_PROPERTY(bool, Enabled, enabled);
406 SINK_EXTRACTED_PROPERTY(QDateTime, LastUpdated, lastUpdated);
407 SINK_EXTRACTED_PROPERTY(int, Count, count);
408 SINK_EXTRACTED_PROPERTY(bool, FullContentAvailable, fullContentAvailable);
346}; 409};
347 410
348struct SINK_EXPORT Mail : public Entity { 411struct SINK_EXPORT Mail : public Entity {
@@ -351,7 +414,7 @@ struct SINK_EXPORT Mail : public Entity {
351 QString emailAddress; 414 QString emailAddress;
352 }; 415 };
353 416
354 SINK_ENTITY(Mail); 417 SINK_ENTITY(Mail, mail);
355 SINK_EXTRACTED_PROPERTY(Contact, Sender, sender); 418 SINK_EXTRACTED_PROPERTY(Contact, Sender, sender);
356 SINK_EXTRACTED_PROPERTY(QList<Contact>, To, to); 419 SINK_EXTRACTED_PROPERTY(QList<Contact>, To, to);
357 SINK_EXTRACTED_PROPERTY(QList<Contact>, Cc, cc); 420 SINK_EXTRACTED_PROPERTY(QList<Contact>, Cc, cc);
@@ -373,23 +436,8 @@ struct SINK_EXPORT Mail : public Entity {
373 436
374SINK_EXPORT QDebug operator<< (QDebug d, const Mail::Contact &c); 437SINK_EXPORT QDebug operator<< (QDebug d, const Mail::Contact &c);
375 438
376/**
377 * The status of an account or resource.
378 *
379 * It is set as follows:
380 * * By default the status is offline.
381 * * If a connection to the server could be established the status is Connected.
382 * * If an error occurred that keeps the resource from operating (so non transient), the resource enters the error state.
383 * * If a long running operation is started the resource goes to the busy state (and return to the previous state after that).
384 */
385enum SINK_EXPORT Status {
386 OfflineStatus,
387 ConnectedStatus,
388 BusyStatus,
389 ErrorStatus
390};
391
392struct SINK_EXPORT Identity : public ApplicationDomainType { 439struct SINK_EXPORT Identity : public ApplicationDomainType {
440 static constexpr const char *name = "identity";
393 typedef QSharedPointer<Identity> Ptr; 441 typedef QSharedPointer<Identity> Ptr;
394 explicit Identity(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer<BufferAdaptor> &adaptor); 442 explicit Identity(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer<BufferAdaptor> &adaptor);
395 explicit Identity(const QByteArray &identifier); 443 explicit Identity(const QByteArray &identifier);
@@ -416,6 +464,10 @@ struct SINK_EXPORT ImapResource {
416 static SinkResource create(const QByteArray &account); 464 static SinkResource create(const QByteArray &account);
417}; 465};
418 466
467struct SINK_EXPORT CardDavResource {
468 static SinkResource create(const QByteArray &account);
469};
470
419namespace ResourceCapabilities { 471namespace ResourceCapabilities {
420namespace Mail { 472namespace Mail {
421 static constexpr const char *mail = "mail"; 473 static constexpr const char *mail = "mail";
@@ -427,6 +479,11 @@ namespace Mail {
427 static constexpr const char *transport = "mail.transport"; 479 static constexpr const char *transport = "mail.transport";
428 static constexpr const char *folderhierarchy = "mail.folderhierarchy"; 480 static constexpr const char *folderhierarchy = "mail.folderhierarchy";
429}; 481};
482namespace Contact {
483 static constexpr const char *contact = "contact";
484 static constexpr const char *addressbook = "addressbook";
485 static constexpr const char *storage = "contact.storage";
486};
430}; 487};
431 488
432namespace SpecialPurpose { 489namespace SpecialPurpose {
@@ -444,31 +501,10 @@ namespace Mail {
444 * Do not store these types to disk, they may change over time. 501 * Do not store these types to disk, they may change over time.
445 */ 502 */
446template<class DomainType> 503template<class DomainType>
447QByteArray SINK_EXPORT getTypeName(); 504QByteArray SINK_EXPORT getTypeName()
448 505{
449template<> 506 return DomainType::name;
450QByteArray SINK_EXPORT getTypeName<Contact>(); 507}
451
452template<>
453QByteArray SINK_EXPORT getTypeName<Event>();
454
455template<>
456QByteArray SINK_EXPORT getTypeName<Todo>();
457
458template<>
459QByteArray SINK_EXPORT getTypeName<SinkResource>();
460
461template<>
462QByteArray SINK_EXPORT getTypeName<SinkAccount>();
463
464template<>
465QByteArray SINK_EXPORT getTypeName<Identity>();
466
467template<>
468QByteArray SINK_EXPORT getTypeName<Mail>();
469
470template<>
471QByteArray SINK_EXPORT getTypeName<Folder>();
472 508
473QByteArrayList SINK_EXPORT getTypeNames(); 509QByteArrayList SINK_EXPORT getTypeNames();
474 510
@@ -499,6 +535,7 @@ class SINK_EXPORT TypeImplementation;
499 */ 535 */
500#define SINK_REGISTER_TYPES() \ 536#define SINK_REGISTER_TYPES() \
501 REGISTER_TYPE(Sink::ApplicationDomain::Contact) \ 537 REGISTER_TYPE(Sink::ApplicationDomain::Contact) \
538 REGISTER_TYPE(Sink::ApplicationDomain::Addressbook) \
502 REGISTER_TYPE(Sink::ApplicationDomain::Event) \ 539 REGISTER_TYPE(Sink::ApplicationDomain::Event) \
503 REGISTER_TYPE(Sink::ApplicationDomain::Mail) \ 540 REGISTER_TYPE(Sink::ApplicationDomain::Mail) \
504 REGISTER_TYPE(Sink::ApplicationDomain::Folder) \ 541 REGISTER_TYPE(Sink::ApplicationDomain::Folder) \
@@ -520,6 +557,7 @@ Q_DECLARE_METATYPE(Sink::ApplicationDomain::ApplicationDomainType::Ptr)
520Q_DECLARE_METATYPE(Sink::ApplicationDomain::Entity) 557Q_DECLARE_METATYPE(Sink::ApplicationDomain::Entity)
521Q_DECLARE_METATYPE(Sink::ApplicationDomain::Entity::Ptr) 558Q_DECLARE_METATYPE(Sink::ApplicationDomain::Entity::Ptr)
522Q_DECLARE_METATYPE(Sink::ApplicationDomain::Mail::Contact) 559Q_DECLARE_METATYPE(Sink::ApplicationDomain::Mail::Contact)
560Q_DECLARE_METATYPE(Sink::ApplicationDomain::Contact::Email)
523Q_DECLARE_METATYPE(Sink::ApplicationDomain::Error) 561Q_DECLARE_METATYPE(Sink::ApplicationDomain::Error)
524Q_DECLARE_METATYPE(Sink::ApplicationDomain::Progress) 562Q_DECLARE_METATYPE(Sink::ApplicationDomain::Progress)
525Q_DECLARE_METATYPE(Sink::ApplicationDomain::BLOB) 563Q_DECLARE_METATYPE(Sink::ApplicationDomain::BLOB)
diff --git a/common/domain/applicationdomaintype_p.h b/common/domain/applicationdomaintype_p.h
index 4b06864..a5a6b1d 100644
--- a/common/domain/applicationdomaintype_p.h
+++ b/common/domain/applicationdomaintype_p.h
@@ -33,13 +33,15 @@ struct TypeHelper {
33 template <typename R, typename ...Args> 33 template <typename R, typename ...Args>
34 R operator()(Args && ... args) const { 34 R operator()(Args && ... args) const {
35 if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Folder>()) { 35 if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Folder>()) {
36 return Func<Sink::ApplicationDomain::Folder>{}(std::forward<Args...>(args...)); 36 return Func<Sink::ApplicationDomain::Folder>{}(std::forward<Args...>(args...));
37 } else if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Mail>()) { 37 } else if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Mail>()) {
38 return Func<Sink::ApplicationDomain::Mail>{}(std::forward<Args...>(args...)); 38 return Func<Sink::ApplicationDomain::Mail>{}(std::forward<Args...>(args...));
39 } else if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Event>()) { 39 } else if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Event>()) {
40 return Func<Sink::ApplicationDomain::Event>{}(std::forward<Args...>(args...)); 40 return Func<Sink::ApplicationDomain::Event>{}(std::forward<Args...>(args...));
41 } else if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Contact>()) { 41 } else if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Contact>()) {
42 return Func<Sink::ApplicationDomain::Contact>{}(std::forward<Args...>(args...)); 42 return Func<Sink::ApplicationDomain::Contact>{}(std::forward<Args...>(args...));
43 } else if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Addressbook>()) {
44 return Func<Sink::ApplicationDomain::Addressbook>{}(std::forward<Args...>(args...));
43 } else { 45 } else {
44 Q_ASSERT(false); 46 Q_ASSERT(false);
45 } 47 }
diff --git a/common/domain/contact.cpp b/common/domain/contact.cpp
deleted file mode 100644
index ea7cac2..0000000
--- a/common/domain/contact.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
1/*
2 * Copyright (C) 2017 Sandro Knauß <knauss@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#include "contact.h"
20
21#include <QVector>
22#include <QByteArray>
23#include <QString>
24
25#include "../propertymapper.h"
26#include "../typeindex.h"
27#include "entity_generated.h"
28
29#include "contact_generated.h"
30
31using namespace Sink::ApplicationDomain;
32
33void TypeImplementation<Contact>::configure(TypeIndex &index)
34{
35 index.addProperty<QByteArray>(Contact::Uid::name);
36}
37
38void TypeImplementation<Contact>::configure(ReadPropertyMapper<Buffer> &propertyMapper)
39{
40 propertyMapper.addMapping<Contact::Uid, Buffer>(&Buffer::uid);
41 propertyMapper.addMapping<Contact::Fn, Buffer>(&Buffer::fn);
42 propertyMapper.addMapping<Contact::Emails, Buffer>(&Buffer::emails);
43 propertyMapper.addMapping<Contact::Vcard, Buffer>(&Buffer::vcard);
44}
45
46void TypeImplementation<Contact>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper)
47{
48 propertyMapper.addMapping<Contact::Uid>(&BufferBuilder::add_uid);
49 propertyMapper.addMapping<Contact::Fn>(&BufferBuilder::add_fn);
50 propertyMapper.addMapping<Contact::Emails>(&BufferBuilder::add_emails);
51 propertyMapper.addMapping<Contact::Vcard>(&BufferBuilder::add_vcard);
52}
53
54void TypeImplementation<Contact>::configure(IndexPropertyMapper &)
55{
56
57}
diff --git a/common/domain/contact.fbs b/common/domain/contact.fbs
index 34fb1d6..d941d5a 100644
--- a/common/domain/contact.fbs
+++ b/common/domain/contact.fbs
@@ -1,9 +1,17 @@
1namespace Sink.ApplicationDomain.Buffer; 1namespace Sink.ApplicationDomain.Buffer;
2 2
3table ContactEmail {
4 type: int;
5 email: string;
6}
7
3table Contact { 8table Contact {
4 uid:string; 9 uid:string;
5 fn:string; 10 fn:string;
6 emails: [string]; 11 firstname:string;
12 lastname:string;
13 addressbook:string;
14 emails: [ContactEmail];
7 vcard: string; 15 vcard: string;
8} 16}
9 17
diff --git a/common/domain/contact.h b/common/domain/contact.h
deleted file mode 100644
index c803a9f..0000000
--- a/common/domain/contact.h
+++ /dev/null
@@ -1,57 +0,0 @@
1/*
2 * Copyright (C) 2017 Sandro Knauß <knauss@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#pragma once
20
21#include "applicationdomaintype.h"
22
23class QByteArray;
24
25template<typename T>
26class ReadPropertyMapper;
27template<typename T>
28class WritePropertyMapper;
29class IndexPropertyMapper;
30
31class TypeIndex;
32
33namespace Sink {
34namespace ApplicationDomain {
35 namespace Buffer {
36 struct Contact;
37 struct ContactBuilder;
38 }
39
40/**
41 * Implements all type-specific code such as updating and querying indexes.
42 *
43 * These are type specifiy default implementations. Theoretically a resource could implement it's own implementation.
44 */
45template<>
46class TypeImplementation<Sink::ApplicationDomain::Contact> {
47public:
48 typedef Sink::ApplicationDomain::Buffer::Contact Buffer;
49 typedef Sink::ApplicationDomain::Buffer::ContactBuilder BufferBuilder;
50 static void configure(TypeIndex &);
51 static void configure(ReadPropertyMapper<Buffer> &);
52 static void configure(WritePropertyMapper<BufferBuilder> &);
53 static void configure(IndexPropertyMapper &indexPropertyMapper);
54};
55
56}
57}
diff --git a/common/domain/domaintypes.h b/common/domain/domaintypes.h
deleted file mode 100644
index 0abdee7..0000000
--- a/common/domain/domaintypes.h
+++ /dev/null
@@ -1,5 +0,0 @@
1
2#include "contact.h"
3#include "mail.h"
4#include "folder.h"
5#include "event.h"
diff --git a/common/domain/event.cpp b/common/domain/event.cpp
deleted file mode 100644
index 10c92bb..0000000
--- a/common/domain/event.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
1/*
2 * Copyright (C) 2014 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#include "event.h"
20
21#include <QVector>
22#include <QByteArray>
23#include <QString>
24
25#include "../propertymapper.h"
26#include "../typeindex.h"
27#include "entity_generated.h"
28
29#include "event_generated.h"
30
31using namespace Sink::ApplicationDomain;
32
33void TypeImplementation<Event>::configure(TypeIndex &index)
34{
35 index.addProperty<QByteArray>(Event::Uid::name);
36}
37
38void TypeImplementation<Event>::configure(ReadPropertyMapper<Buffer> &propertyMapper)
39{
40 propertyMapper.addMapping<Event::Summary, Buffer>(&Buffer::summary);
41 propertyMapper.addMapping<Event::Description, Buffer>(&Buffer::description);
42 propertyMapper.addMapping<Event::Uid, Buffer>(&Buffer::uid);
43 propertyMapper.addMapping<Event::Attachment, Buffer>(&Buffer::attachment);
44}
45
46void TypeImplementation<Event>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper)
47{
48 propertyMapper.addMapping<Event::Summary>(&BufferBuilder::add_summary);
49 propertyMapper.addMapping<Event::Description>(&BufferBuilder::add_description);
50 propertyMapper.addMapping<Event::Uid>(&BufferBuilder::add_uid);
51 propertyMapper.addMapping<Event::Attachment>(&BufferBuilder::add_attachment);
52}
53
54void TypeImplementation<Event>::configure(IndexPropertyMapper &)
55{
56
57}
diff --git a/common/domain/event.h b/common/domain/event.h
deleted file mode 100644
index b683f5f..0000000
--- a/common/domain/event.h
+++ /dev/null
@@ -1,57 +0,0 @@
1/*
2 * Copyright (C) 2014 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#pragma once
20
21#include "applicationdomaintype.h"
22
23class QByteArray;
24
25template<typename T>
26class ReadPropertyMapper;
27template<typename T>
28class WritePropertyMapper;
29class IndexPropertyMapper;
30
31class TypeIndex;
32
33namespace Sink {
34namespace ApplicationDomain {
35 namespace Buffer {
36 struct Event;
37 struct EventBuilder;
38 }
39
40/**
41 * Implements all type-specific code such as updating and querying indexes.
42 *
43 * These are type specifiy default implementations. Theoretically a resource could implement it's own implementation.
44 */
45template<>
46class TypeImplementation<Sink::ApplicationDomain::Event> {
47public:
48 typedef Sink::ApplicationDomain::Buffer::Event Buffer;
49 typedef Sink::ApplicationDomain::Buffer::EventBuilder BufferBuilder;
50 static void configure(TypeIndex &);
51 static void configure(ReadPropertyMapper<Buffer> &);
52 static void configure(WritePropertyMapper<BufferBuilder> &);
53 static void configure(IndexPropertyMapper &indexPropertyMapper);
54};
55
56}
57}
diff --git a/common/domain/folder.cpp b/common/domain/folder.cpp
deleted file mode 100644
index 6717661..0000000
--- a/common/domain/folder.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastfolder.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#include "folder.h"
20
21#include <QByteArray>
22#include <QString>
23
24#include "../propertymapper.h"
25#include "../typeindex.h"
26#include "entitybuffer.h"
27#include "entity_generated.h"
28
29#include "folder_generated.h"
30
31using namespace Sink::ApplicationDomain;
32
33void TypeImplementation<Folder>::configure(TypeIndex &index)
34{
35 index.addProperty<QByteArray>(Folder::Parent::name);
36 index.addProperty<QString>(Folder::Name::name);
37}
38
39void TypeImplementation<Folder>::configure(ReadPropertyMapper<Buffer> &propertyMapper)
40{
41 propertyMapper.addMapping<Folder::Parent, Buffer>(&Buffer::parent);
42 propertyMapper.addMapping<Folder::Name, Buffer>(&Buffer::name);
43 propertyMapper.addMapping<Folder::Icon, Buffer>(&Buffer::icon);
44 propertyMapper.addMapping<Folder::SpecialPurpose, Buffer>(&Buffer::specialpurpose);
45 propertyMapper.addMapping<Folder::Enabled, Buffer>(&Buffer::enabled);
46}
47
48void TypeImplementation<Folder>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper)
49{
50 propertyMapper.addMapping<Folder::Parent>(&BufferBuilder::add_parent);
51 propertyMapper.addMapping<Folder::Name>(&BufferBuilder::add_name);
52 propertyMapper.addMapping<Folder::Icon>(&BufferBuilder::add_icon);
53 propertyMapper.addMapping<Folder::SpecialPurpose>(&BufferBuilder::add_specialpurpose);
54 propertyMapper.addMapping<Folder::Enabled>(&BufferBuilder::add_enabled);
55}
56
57void TypeImplementation<Folder>::configure(IndexPropertyMapper &)
58{
59
60}
diff --git a/common/domain/folder.h b/common/domain/folder.h
deleted file mode 100644
index f232ab5..0000000
--- a/common/domain/folder.h
+++ /dev/null
@@ -1,51 +0,0 @@
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#pragma once
20
21#include "applicationdomaintype.h"
22
23template<typename T>
24class ReadPropertyMapper;
25template<typename T>
26class WritePropertyMapper;
27class IndexPropertyMapper;
28
29class TypeIndex;
30
31namespace Sink {
32
33namespace ApplicationDomain {
34 namespace Buffer {
35 struct Folder;
36 struct FolderBuilder;
37 }
38
39template<>
40class TypeImplementation<Sink::ApplicationDomain::Folder> {
41public:
42 typedef Sink::ApplicationDomain::Buffer::Folder Buffer;
43 typedef Sink::ApplicationDomain::Buffer::FolderBuilder BufferBuilder;
44 static void configure(TypeIndex &);
45 static void configure(ReadPropertyMapper<Buffer> &);
46 static void configure(WritePropertyMapper<BufferBuilder> &);
47 static void configure(IndexPropertyMapper &indexPropertyMapper);
48};
49
50}
51}
diff --git a/common/domain/mail.h b/common/domain/mail.h
deleted file mode 100644
index e052448..0000000
--- a/common/domain/mail.h
+++ /dev/null
@@ -1,52 +0,0 @@
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#pragma once
20
21#include "applicationdomaintype.h"
22
23class QByteArray;
24
25template<typename T>
26class ReadPropertyMapper;
27template<typename T>
28class WritePropertyMapper;
29class IndexPropertyMapper;
30
31class TypeIndex;
32
33namespace Sink {
34namespace ApplicationDomain {
35 namespace Buffer {
36 struct Mail;
37 struct MailBuilder;
38 }
39
40template<>
41class TypeImplementation<Sink::ApplicationDomain::Mail> {
42public:
43 typedef Sink::ApplicationDomain::Buffer::Mail Buffer;
44 typedef Sink::ApplicationDomain::Buffer::MailBuilder BufferBuilder;
45 static void configure(TypeIndex &index);
46 static void configure(ReadPropertyMapper<Buffer> &propertyMapper);
47 static void configure(WritePropertyMapper<BufferBuilder> &propertyMapper);
48 static void configure(IndexPropertyMapper &indexPropertyMapper);
49};
50
51}
52}
diff --git a/common/domain/propertyregistry.cpp b/common/domain/propertyregistry.cpp
index 2208193..7b9b61a 100644
--- a/common/domain/propertyregistry.cpp
+++ b/common/domain/propertyregistry.cpp
@@ -64,6 +64,17 @@ QVariant parseString<bool>(const QString &s)
64} 64}
65 65
66template <> 66template <>
67QVariant parseString<int>(const QString &s)
68{
69 bool ok = false;
70 auto n = s.toInt(&ok);
71 if (ok) {
72 return QVariant::fromValue(n);
73 }
74 return {};
75}
76
77template <>
67QVariant parseString<QList<QByteArray>>(const QString &s) 78QVariant parseString<QList<QByteArray>>(const QString &s)
68{ 79{
69 auto list = s.split(','); 80 auto list = s.split(',');
@@ -92,6 +103,13 @@ QVariant parseString<QList<Sink::ApplicationDomain::Mail::Contact>>(const QStrin
92 return QVariant{}; 103 return QVariant{};
93} 104}
94 105
106template <>
107QVariant parseString<QList<Sink::ApplicationDomain::Contact::Email>>(const QString &s)
108{
109 Q_ASSERT(false);
110 return QVariant{};
111}
112
95PropertyRegistry &PropertyRegistry::instance() 113PropertyRegistry &PropertyRegistry::instance()
96{ 114{
97 static PropertyRegistry instance; 115 static PropertyRegistry instance;
diff --git a/common/domain/propertyregistry.h b/common/domain/propertyregistry.h
index 16df23b..758c10d 100644
--- a/common/domain/propertyregistry.h
+++ b/common/domain/propertyregistry.h
@@ -49,6 +49,9 @@ template <>
49QVariant parseString<bool>(const QString &s); 49QVariant parseString<bool>(const QString &s);
50 50
51template <> 51template <>
52QVariant parseString<int>(const QString &s);
53
54template <>
52QVariant parseString<QList<QByteArray>>(const QString &s); 55QVariant parseString<QList<QByteArray>>(const QString &s);
53 56
54template <> 57template <>
diff --git a/common/domain/mail.cpp b/common/domain/typeimplementations.cpp
index 8cbe61b..eb3851e 100644
--- a/common/domain/mail.cpp
+++ b/common/domain/typeimplementations.cpp
@@ -16,7 +16,7 @@
16 * Free Software Foundation, Inc., 16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */ 18 */
19#include "mail.h" 19#include "typeimplementations.h"
20 20
21#include <QVector> 21#include <QVector>
22#include <QByteArray> 22#include <QByteArray>
@@ -29,8 +29,6 @@
29#include "mail/threadindexer.h" 29#include "mail/threadindexer.h"
30#include "domainadaptor.h" 30#include "domainadaptor.h"
31 31
32#include "mail_generated.h"
33
34using namespace Sink; 32using namespace Sink;
35using namespace Sink::ApplicationDomain; 33using namespace Sink::ApplicationDomain;
36 34
@@ -102,3 +100,117 @@ void TypeImplementation<Mail>::configure(WritePropertyMapper<BufferBuilder> &pro
102 propertyMapper.addMapping<Mail::MessageId>(&BufferBuilder::add_messageId); 100 propertyMapper.addMapping<Mail::MessageId>(&BufferBuilder::add_messageId);
103 propertyMapper.addMapping<Mail::ParentMessageId>(&BufferBuilder::add_parentMessageId); 101 propertyMapper.addMapping<Mail::ParentMessageId>(&BufferBuilder::add_parentMessageId);
104} 102}
103
104
105void TypeImplementation<Folder>::configure(TypeIndex &index)
106{
107 index.addProperty<QByteArray>(Folder::Parent::name);
108 index.addProperty<QString>(Folder::Name::name);
109}
110
111void TypeImplementation<Folder>::configure(ReadPropertyMapper<Buffer> &propertyMapper)
112{
113 propertyMapper.addMapping<Folder::Parent, Buffer>(&Buffer::parent);
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}
119
120void TypeImplementation<Folder>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper)
121{
122 propertyMapper.addMapping<Folder::Parent>(&BufferBuilder::add_parent);
123 propertyMapper.addMapping<Folder::Name>(&BufferBuilder::add_name);
124 propertyMapper.addMapping<Folder::Icon>(&BufferBuilder::add_icon);
125 propertyMapper.addMapping<Folder::SpecialPurpose>(&BufferBuilder::add_specialpurpose);
126 propertyMapper.addMapping<Folder::Enabled>(&BufferBuilder::add_enabled);
127}
128
129void TypeImplementation<Folder>::configure(IndexPropertyMapper &)
130{
131
132}
133
134
135void TypeImplementation<Contact>::configure(TypeIndex &index)
136{
137 index.addProperty<QByteArray>(Contact::Uid::name);
138}
139
140void TypeImplementation<Contact>::configure(ReadPropertyMapper<Buffer> &propertyMapper)
141{
142 propertyMapper.addMapping<Contact::Uid, Buffer>(&Buffer::uid);
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}
150
151void TypeImplementation<Contact>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper)
152{
153 propertyMapper.addMapping<Contact::Uid>(&BufferBuilder::add_uid);
154 propertyMapper.addMapping<Contact::Fn>(&BufferBuilder::add_fn);
155 propertyMapper.addMapping<Contact::Emails>(&BufferBuilder::add_emails);
156 propertyMapper.addMapping<Contact::Vcard>(&BufferBuilder::add_vcard);
157 propertyMapper.addMapping<Contact::Addressbook>(&BufferBuilder::add_addressbook);
158 propertyMapper.addMapping<Contact::Firstname>(&BufferBuilder::add_firstname);
159 propertyMapper.addMapping<Contact::Lastname>(&BufferBuilder::add_lastname);
160}
161
162void TypeImplementation<Contact>::configure(IndexPropertyMapper &)
163{
164
165}
166
167
168void TypeImplementation<Addressbook>::configure(TypeIndex &index)
169{
170 index.addProperty<QByteArray>(Addressbook::Parent::name);
171 index.addProperty<QString>(Addressbook::Name::name);
172}
173
174void TypeImplementation<Addressbook>::configure(ReadPropertyMapper<Buffer> &propertyMapper)
175{
176 propertyMapper.addMapping<Addressbook::Parent, Buffer>(&Buffer::parent);
177 propertyMapper.addMapping<Addressbook::Name, Buffer>(&Buffer::name);
178}
179
180void TypeImplementation<Addressbook>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper)
181{
182 propertyMapper.addMapping<Addressbook::Parent>(&BufferBuilder::add_parent);
183 propertyMapper.addMapping<Addressbook::Name>(&BufferBuilder::add_name);
184}
185
186void TypeImplementation<Addressbook>::configure(IndexPropertyMapper &)
187{
188
189}
190
191
192void TypeImplementation<Event>::configure(TypeIndex &index)
193{
194 index.addProperty<QByteArray>(Event::Uid::name);
195}
196
197void TypeImplementation<Event>::configure(ReadPropertyMapper<Buffer> &propertyMapper)
198{
199 propertyMapper.addMapping<Event::Summary, Buffer>(&Buffer::summary);
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}
204
205void TypeImplementation<Event>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper)
206{
207 propertyMapper.addMapping<Event::Summary>(&BufferBuilder::add_summary);
208 propertyMapper.addMapping<Event::Description>(&BufferBuilder::add_description);
209 propertyMapper.addMapping<Event::Uid>(&BufferBuilder::add_uid);
210 propertyMapper.addMapping<Event::Attachment>(&BufferBuilder::add_attachment);
211}
212
213void TypeImplementation<Event>::configure(IndexPropertyMapper &)
214{
215
216}
diff --git a/common/domain/typeimplementations.h b/common/domain/typeimplementations.h
new file mode 100644
index 0000000..37d6ca9
--- /dev/null
+++ b/common/domain/typeimplementations.h
@@ -0,0 +1,101 @@
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#pragma once
20
21#include "applicationdomaintype.h"
22
23#include "mail_generated.h"
24#include "folder_generated.h"
25#include "event_generated.h"
26#include "contact_generated.h"
27#include "addressbook_generated.h"
28
29template<typename T>
30class ReadPropertyMapper;
31template<typename T>
32class WritePropertyMapper;
33class IndexPropertyMapper;
34
35class TypeIndex;
36
37/**
38 * Implements all type-specific code such as updating and querying indexes.
39 *
40 * These are type specifiy default implementations. Theoretically a resource could implement it's own implementation.
41 */
42namespace Sink {
43namespace ApplicationDomain {
44
45template<>
46class TypeImplementation<Sink::ApplicationDomain::Mail> {
47public:
48 typedef Sink::ApplicationDomain::Buffer::Mail Buffer;
49 typedef Sink::ApplicationDomain::Buffer::MailBuilder BufferBuilder;
50 static void configure(TypeIndex &index);
51 static void configure(ReadPropertyMapper<Buffer> &propertyMapper);
52 static void configure(WritePropertyMapper<BufferBuilder> &propertyMapper);
53 static void configure(IndexPropertyMapper &indexPropertyMapper);
54};
55
56template<>
57class TypeImplementation<Sink::ApplicationDomain::Folder> {
58public:
59 typedef Sink::ApplicationDomain::Buffer::Folder Buffer;
60 typedef Sink::ApplicationDomain::Buffer::FolderBuilder BufferBuilder;
61 static void configure(TypeIndex &);
62 static void configure(ReadPropertyMapper<Buffer> &);
63 static void configure(WritePropertyMapper<BufferBuilder> &);
64 static void configure(IndexPropertyMapper &indexPropertyMapper);
65};
66
67template<>
68class TypeImplementation<Sink::ApplicationDomain::Contact> {
69public:
70 typedef Sink::ApplicationDomain::Buffer::Contact Buffer;
71 typedef Sink::ApplicationDomain::Buffer::ContactBuilder BufferBuilder;
72 static void configure(TypeIndex &);
73 static void configure(ReadPropertyMapper<Buffer> &);
74 static void configure(WritePropertyMapper<BufferBuilder> &);
75 static void configure(IndexPropertyMapper &indexPropertyMapper);
76};
77
78template<>
79class TypeImplementation<Sink::ApplicationDomain::Addressbook> {
80public:
81 typedef Sink::ApplicationDomain::Buffer::Addressbook Buffer;
82 typedef Sink::ApplicationDomain::Buffer::AddressbookBuilder BufferBuilder;
83 static void configure(TypeIndex &);
84 static void configure(ReadPropertyMapper<Buffer> &);
85 static void configure(WritePropertyMapper<BufferBuilder> &);
86 static void configure(IndexPropertyMapper &indexPropertyMapper);
87};
88
89template<>
90class TypeImplementation<Sink::ApplicationDomain::Event> {
91public:
92 typedef Sink::ApplicationDomain::Buffer::Event Buffer;
93 typedef Sink::ApplicationDomain::Buffer::EventBuilder BufferBuilder;
94 static void configure(TypeIndex &);
95 static void configure(ReadPropertyMapper<Buffer> &);
96 static void configure(WritePropertyMapper<BufferBuilder> &);
97 static void configure(IndexPropertyMapper &indexPropertyMapper);
98};
99
100}
101}
diff --git a/common/domainadaptor.h b/common/domainadaptor.h
index 0377ef4..af5d5fc 100644
--- a/common/domainadaptor.h
+++ b/common/domainadaptor.h
@@ -26,10 +26,7 @@
26 26
27#include "domaintypeadaptorfactoryinterface.h" 27#include "domaintypeadaptorfactoryinterface.h"
28#include "domain/applicationdomaintype.h" 28#include "domain/applicationdomaintype.h"
29#include "domain/contact.h" 29#include "domain/typeimplementations.h"
30#include "domain/event.h"
31#include "domain/mail.h"
32#include "domain/folder.h"
33#include "bufferadaptor.h" 30#include "bufferadaptor.h"
34#include "entity_generated.h" 31#include "entity_generated.h"
35#include "metadata_generated.h" 32#include "metadata_generated.h"
@@ -245,3 +242,15 @@ protected:
245 QSharedPointer<WritePropertyMapper<ResourceBuilder>> mResourceWriteMapper; 242 QSharedPointer<WritePropertyMapper<ResourceBuilder>> mResourceWriteMapper;
246 QSharedPointer<IndexPropertyMapper> mIndexMapper; 243 QSharedPointer<IndexPropertyMapper> mIndexMapper;
247}; 244};
245
246/**
247 * A default adaptorfactory implemenation that simply instantiates a generic resource
248 */
249template<typename DomainType>
250class DefaultAdaptorFactory : public DomainTypeAdaptorFactory<DomainType>
251{
252public:
253 DefaultAdaptorFactory() : DomainTypeAdaptorFactory<DomainType>() {}
254 virtual ~DefaultAdaptorFactory(){}
255};
256
diff --git a/common/genericresource.cpp b/common/genericresource.cpp
index 5ba9e5d..7178b3d 100644
--- a/common/genericresource.cpp
+++ b/common/genericresource.cpp
@@ -31,8 +31,8 @@ using namespace Sink::Storage;
31GenericResource::GenericResource(const ResourceContext &resourceContext, const QSharedPointer<Pipeline> &pipeline ) 31GenericResource::GenericResource(const ResourceContext &resourceContext, const QSharedPointer<Pipeline> &pipeline )
32 : Sink::Resource(), 32 : Sink::Resource(),
33 mResourceContext(resourceContext), 33 mResourceContext(resourceContext),
34 mPipeline(pipeline ? pipeline : QSharedPointer<Sink::Pipeline>::create(resourceContext, "resource." + resourceContext.instanceId())), 34 mPipeline(pipeline ? pipeline : QSharedPointer<Sink::Pipeline>::create(resourceContext, Log::Context{})),
35 mProcessor(QSharedPointer<CommandProcessor>::create(mPipeline.data(), resourceContext.instanceId(), "resource." + resourceContext.instanceId())), 35 mProcessor(QSharedPointer<CommandProcessor>::create(mPipeline.data(), resourceContext.instanceId(), Log::Context{})),
36 mError(0), 36 mError(0),
37 mClientLowerBoundRevision(std::numeric_limits<qint64>::max()) 37 mClientLowerBoundRevision(std::numeric_limits<qint64>::max())
38{ 38{
diff --git a/common/index.cpp b/common/index.cpp
index f09e265..725c28b 100644
--- a/common/index.cpp
+++ b/common/index.cpp
@@ -2,31 +2,31 @@
2 2
3#include "log.h" 3#include "log.h"
4 4
5SINK_DEBUG_AREA("index")
6
7Index::Index(const QString &storageRoot, const QString &name, Sink::Storage::DataStore::AccessMode mode) 5Index::Index(const QString &storageRoot, const QString &name, Sink::Storage::DataStore::AccessMode mode)
8 : mTransaction(Sink::Storage::DataStore(storageRoot, name, mode).createTransaction(mode)), 6 : mTransaction(Sink::Storage::DataStore(storageRoot, name, mode).createTransaction(mode)),
9 mDb(mTransaction.openDatabase(name.toLatin1(), std::function<void(const Sink::Storage::DataStore::Error &)>(), true)), 7 mDb(mTransaction.openDatabase(name.toLatin1(), std::function<void(const Sink::Storage::DataStore::Error &)>(), true)),
10 mName(name) 8 mName(name),
9 mLogCtx("index." + name.toLatin1())
11{ 10{
12} 11}
13 12
14Index::Index(const QByteArray &name, Sink::Storage::DataStore::Transaction &transaction) 13Index::Index(const QByteArray &name, Sink::Storage::DataStore::Transaction &transaction)
15 : mDb(transaction.openDatabase(name, std::function<void(const Sink::Storage::DataStore::Error &)>(), true)), mName(name) 14 : mDb(transaction.openDatabase(name, std::function<void(const Sink::Storage::DataStore::Error &)>(), true)), mName(name),
15 mLogCtx("index." + name)
16{ 16{
17} 17}
18 18
19void Index::add(const QByteArray &key, const QByteArray &value) 19void Index::add(const QByteArray &key, const QByteArray &value)
20{ 20{
21 mDb.write(key, value, [&] (const Sink::Storage::DataStore::Error &error) { 21 mDb.write(key, value, [&] (const Sink::Storage::DataStore::Error &error) {
22 SinkWarning() << "Error while writing value" << error; 22 SinkWarningCtx(mLogCtx) << "Error while writing value" << error;
23 }); 23 });
24} 24}
25 25
26void Index::remove(const QByteArray &key, const QByteArray &value) 26void Index::remove(const QByteArray &key, const QByteArray &value)
27{ 27{
28 mDb.remove(key, value, [&] (const Sink::Storage::DataStore::Error &error) { 28 mDb.remove(key, value, [&] (const Sink::Storage::DataStore::Error &error) {
29 SinkWarning() << "Error while removing value: " << key << value << error << error.store; 29 SinkWarningCtx(mLogCtx) << "Error while removing value: " << key << value << error;
30 }); 30 });
31} 31}
32 32
@@ -38,7 +38,7 @@ void Index::lookup(const QByteArray &key, const std::function<void(const QByteAr
38 return true; 38 return true;
39 }, 39 },
40 [&](const Sink::Storage::DataStore::Error &error) { 40 [&](const Sink::Storage::DataStore::Error &error) {
41 SinkWarning() << "Error while retrieving value" << error.message; 41 SinkWarningCtx(mLogCtx) << "Error while retrieving value:" << error << mName;
42 errorHandler(Error(error.store, error.code, error.message)); 42 errorHandler(Error(error.store, error.code, error.message));
43 }, 43 },
44 matchSubStringKeys); 44 matchSubStringKeys);
@@ -48,6 +48,6 @@ QByteArray Index::lookup(const QByteArray &key)
48{ 48{
49 QByteArray result; 49 QByteArray result;
50 //We have to create a deep copy, otherwise the returned data may become invalid when the transaction ends. 50 //We have to create a deep copy, otherwise the returned data may become invalid when the transaction ends.
51 lookup(key, [&](const QByteArray &value) { result = QByteArray(value.constData(), value.size()); }, [this](const Index::Error &error) { SinkWarning() << "Error while retrieving value" << error.message; }); 51 lookup(key, [&](const QByteArray &value) { result = QByteArray(value.constData(), value.size()); }, [this](const Index::Error &) { });
52 return result; 52 return result;
53} 53}
diff --git a/common/index.h b/common/index.h
index cfcc7a0..81dc5bf 100644
--- a/common/index.h
+++ b/common/index.h
@@ -44,5 +44,5 @@ private:
44 Sink::Storage::DataStore::Transaction mTransaction; 44 Sink::Storage::DataStore::Transaction mTransaction;
45 Sink::Storage::DataStore::NamedDatabase mDb; 45 Sink::Storage::DataStore::NamedDatabase mDb;
46 QString mName; 46 QString mName;
47 SINK_DEBUG_COMPONENT(mName.toLatin1()) 47 Sink::Log::Context mLogCtx;
48}; 48};
diff --git a/common/listener.cpp b/common/listener.cpp
index f18fe1d..983e438 100644
--- a/common/listener.cpp
+++ b/common/listener.cpp
@@ -25,6 +25,7 @@
25#include "common/definitions.h" 25#include "common/definitions.h"
26#include "common/resourcecontext.h" 26#include "common/resourcecontext.h"
27#include "common/adaptorfactoryregistry.h" 27#include "common/adaptorfactoryregistry.h"
28#include "common/bufferutils.h"
28 29
29// commands 30// commands
30#include "common/commandcompletion_generated.h" 31#include "common/commandcompletion_generated.h"
@@ -66,7 +67,7 @@ Listener::Listener(const QByteArray &resourceInstanceIdentifier, const QByteArra
66 m_checkConnectionsTimer->setInterval(1000); 67 m_checkConnectionsTimer->setInterval(1000);
67 connect(m_checkConnectionsTimer.get(), &QTimer::timeout, [this]() { 68 connect(m_checkConnectionsTimer.get(), &QTimer::timeout, [this]() {
68 if (m_connections.isEmpty()) { 69 if (m_connections.isEmpty()) {
69 SinkLog() << QString("No connections, shutting down."); 70 SinkTrace() << QString("No connections, shutting down.");
70 quit(); 71 quit();
71 } 72 }
72 }); 73 });
@@ -87,6 +88,12 @@ Listener::~Listener()
87 88
88void Listener::emergencyAbortAllConnections() 89void Listener::emergencyAbortAllConnections()
89{ 90{
91 Sink::Notification n;
92 n.type = Sink::Notification::Status;
93 n.message = "The resource crashed.";
94 n.code = Sink::ApplicationDomain::ErrorStatus;
95 notify(n);
96
90 for (Client &client : m_connections) { 97 for (Client &client : m_connections) {
91 if (client.socket) { 98 if (client.socket) {
92 SinkWarning() << "Sending panic"; 99 SinkWarning() << "Sending panic";
@@ -406,11 +413,13 @@ void Listener::notify(const Sink::Notification &notification)
406{ 413{
407 auto messageString = m_fbb.CreateString(notification.message.toUtf8().constData(), notification.message.toUtf8().size()); 414 auto messageString = m_fbb.CreateString(notification.message.toUtf8().constData(), notification.message.toUtf8().size());
408 auto idString = m_fbb.CreateString(notification.id.constData(), notification.id.size()); 415 auto idString = m_fbb.CreateString(notification.id.constData(), notification.id.size());
416 auto entities = Sink::BufferUtils::toVector(m_fbb, notification.entities);
409 Sink::Commands::NotificationBuilder builder(m_fbb); 417 Sink::Commands::NotificationBuilder builder(m_fbb);
410 builder.add_type(notification.type); 418 builder.add_type(notification.type);
411 builder.add_code(notification.code); 419 builder.add_code(notification.code);
412 builder.add_identifier(idString); 420 builder.add_identifier(idString);
413 builder.add_message(messageString); 421 builder.add_message(messageString);
422 builder.add_entities(entities);
414 auto command = builder.Finish(); 423 auto command = builder.Finish();
415 Sink::Commands::FinishNotificationBuffer(m_fbb, command); 424 Sink::Commands::FinishNotificationBuffer(m_fbb, command);
416 for (Client &client : m_connections) { 425 for (Client &client : m_connections) {
diff --git a/common/mail/threadindexer.cpp b/common/mail/threadindexer.cpp
index 6f2933c..d91ab5f 100644
--- a/common/mail/threadindexer.cpp
+++ b/common/mail/threadindexer.cpp
@@ -101,9 +101,12 @@ void ThreadIndexer::updateThreadingIndex(const QByteArray &identifier, const App
101 thread = index().secondaryLookup<Mail::MessageId, Mail::ThreadId>(parentMessageId); 101 thread = index().secondaryLookup<Mail::MessageId, Mail::ThreadId>(parentMessageId);
102 SinkTrace() << "Found parent: " << thread; 102 SinkTrace() << "Found parent: " << thread;
103 } 103 }
104
104 if (thread.isEmpty()) { 105 if (thread.isEmpty()) {
105 //Try to lookup the thread by subject: 106 //Try to lookup the thread by subject if not empty
106 thread = index().secondaryLookup<Mail::Subject, Mail::ThreadId>(normalizedSubject); 107 if ( !normalizedSubject.isEmpty()) {
108 thread = index().secondaryLookup<Mail::Subject, Mail::ThreadId>(normalizedSubject);
109 }
107 if (thread.isEmpty()) { 110 if (thread.isEmpty()) {
108 thread << QUuid::createUuid().toByteArray(); 111 thread << QUuid::createUuid().toByteArray();
109 SinkTrace() << "Created a new thread: " << thread; 112 SinkTrace() << "Created a new thread: " << thread;
@@ -121,7 +124,9 @@ void ThreadIndexer::updateThreadingIndex(const QByteArray &identifier, const App
121 } 124 }
122 index().index<Mail::MessageId, Mail::ThreadId>(messageId, thread.first(), transaction); 125 index().index<Mail::MessageId, Mail::ThreadId>(messageId, thread.first(), transaction);
123 index().index<Mail::ThreadId, Mail::MessageId>(thread.first(), messageId, transaction); 126 index().index<Mail::ThreadId, Mail::MessageId>(thread.first(), messageId, transaction);
124 index().index<Mail::Subject, Mail::ThreadId>(normalizedSubject, thread.first(), transaction); 127 if (!normalizedSubject.isEmpty()) {
128 index().index<Mail::Subject, Mail::ThreadId>(normalizedSubject, thread.first(), transaction);
129 }
125} 130}
126 131
127 132
diff --git a/common/modelresult.cpp b/common/modelresult.cpp
index f935419..b12216b 100644
--- a/common/modelresult.cpp
+++ b/common/modelresult.cpp
@@ -24,12 +24,20 @@
24#include <QPointer> 24#include <QPointer>
25 25
26#include "log.h" 26#include "log.h"
27#include "notifier.h"
28#include "notification.h"
29
30using namespace Sink;
31
32static uint getInternalIdentifer(const QByteArray &resourceId, const QByteArray &entityId)
33{
34 return qHash(resourceId + entityId);
35}
27 36
28static uint qHash(const Sink::ApplicationDomain::ApplicationDomainType &type) 37static uint qHash(const Sink::ApplicationDomain::ApplicationDomainType &type)
29{ 38{
30 // Q_ASSERT(!type.resourceInstanceIdentifier().isEmpty());
31 Q_ASSERT(!type.identifier().isEmpty()); 39 Q_ASSERT(!type.identifier().isEmpty());
32 return qHash(type.resourceInstanceIdentifier() + type.identifier()); 40 return getInternalIdentifer(type.resourceInstanceIdentifier(), type.identifier());
33} 41}
34 42
35static qint64 getIdentifier(const QModelIndex &idx) 43static qint64 getIdentifier(const QModelIndex &idx)
@@ -44,6 +52,86 @@ template <class T, class Ptr>
44ModelResult<T, Ptr>::ModelResult(const Sink::Query &query, const QList<QByteArray> &propertyColumns, const Sink::Log::Context &ctx) 52ModelResult<T, Ptr>::ModelResult(const Sink::Query &query, const QList<QByteArray> &propertyColumns, const Sink::Log::Context &ctx)
45 : QAbstractItemModel(), mLogCtx(ctx.subContext("modelresult")), mPropertyColumns(propertyColumns), mQuery(query) 53 : QAbstractItemModel(), mLogCtx(ctx.subContext("modelresult")), mPropertyColumns(propertyColumns), mQuery(query)
46{ 54{
55 if (query.flags().testFlag(Sink::Query::UpdateStatus)) {
56 Sink::Query resourceQuery;
57 resourceQuery.setFilter(query.getResourceFilter());
58 mNotifier.reset(new Sink::Notifier{resourceQuery});
59 mNotifier->registerHandler([this](const Notification &notification) {
60 switch (notification.type) {
61 case Notification::Status:
62 case Notification::Warning:
63 case Notification::Error:
64 case Notification::Info:
65 case Notification::Progress:
66 //These are the notifications we care about
67 break;
68 default:
69 //We're not interested
70 return;
71 };
72 if (notification.resource.isEmpty() || notification.entities.isEmpty()) {
73 return;
74 }
75
76 QVector<qint64> idList;
77 for (const auto &entity : notification.entities) {
78 auto id = getInternalIdentifer(notification.resource, entity);
79 if (mEntities.contains(id)) {
80 idList << id;
81 }
82 }
83
84 if (idList.isEmpty()) {
85 //We don't have this entity in our model
86 return;
87 }
88 const int newStatus = [&] {
89 if (notification.type == Notification::Warning || notification.type == Notification::Error) {
90 return ApplicationDomain::SyncStatus::SyncError;
91 }
92 if (notification.type == Notification::Info) {
93 switch (notification.code) {
94 case ApplicationDomain::SyncInProgress:
95 return ApplicationDomain::SyncInProgress;
96 case ApplicationDomain::SyncSuccess:
97 return ApplicationDomain::SyncSuccess;
98 case ApplicationDomain::SyncError:
99 return ApplicationDomain::SyncError;
100 case ApplicationDomain::NoSyncStatus:
101 break;
102 }
103 return ApplicationDomain::NoSyncStatus;
104 }
105 if (notification.type == Notification::Progress) {
106 return ApplicationDomain::SyncStatus::SyncInProgress;
107 }
108 return ApplicationDomain::NoSyncStatus;
109 }();
110
111 for (const auto id : idList) {
112 const auto oldStatus = mEntityStatus.value(id);
113 QVector<int> changedRoles;
114 if (oldStatus != newStatus) {
115 SinkTraceCtx(mLogCtx) << "Status changed for entity:" << newStatus << ", id: " << id;
116 mEntityStatus.insert(id, newStatus);
117 changedRoles << StatusRole;
118 }
119
120 if (notification.type == Notification::Progress) {
121 changedRoles << ProgressRole;
122 } else if (notification.type == Notification::Warning || notification.type == Notification::Error) {
123 changedRoles << WarningRole;
124 }
125
126 if (!changedRoles.isEmpty()) {
127 const auto idx = createIndexFromId(id);
128 SinkTraceCtx(mLogCtx) << "Index changed:" << idx << changedRoles;
129 //We don't emit the changedRoles because the consuming model likely remaps the role anyways and would then need to translate dataChanged signals as well.
130 emit dataChanged(idx, idx);
131 }
132 }
133 });
134 }
47} 135}
48 136
49template <class T, class Ptr> 137template <class T, class Ptr>
@@ -60,7 +148,7 @@ qint64 ModelResult<T, Ptr>::parentId(const Ptr &value)
60 if (!mQuery.parentProperty().isEmpty()) { 148 if (!mQuery.parentProperty().isEmpty()) {
61 const auto identifier = value->getProperty(mQuery.parentProperty()).toByteArray(); 149 const auto identifier = value->getProperty(mQuery.parentProperty()).toByteArray();
62 if (!identifier.isEmpty()) { 150 if (!identifier.isEmpty()) {
63 return qHash(T(value->resourceInstanceIdentifier(), identifier, 0, QSharedPointer<Sink::ApplicationDomain::BufferAdaptor>())); 151 return getInternalIdentifer(value->resourceInstanceIdentifier(), identifier);
64 } 152 }
65 } 153 }
66 return 0; 154 return 0;
@@ -106,6 +194,13 @@ QVariant ModelResult<T, Ptr>::data(const QModelIndex &index, int role) const
106 if (role == ChildrenFetchedRole) { 194 if (role == ChildrenFetchedRole) {
107 return childrenFetched(index); 195 return childrenFetched(index);
108 } 196 }
197 if (role == StatusRole) {
198 auto it = mEntityStatus.constFind(index.internalId());
199 if (it != mEntityStatus.constEnd()) {
200 return *it;
201 }
202 return {};
203 }
109 if (role == Qt::DisplayRole && index.isValid()) { 204 if (role == Qt::DisplayRole && index.isValid()) {
110 if (index.column() < mPropertyColumns.size()) { 205 if (index.column() < mPropertyColumns.size()) {
111 Q_ASSERT(mEntities.contains(index.internalId())); 206 Q_ASSERT(mEntities.contains(index.internalId()));
@@ -296,15 +391,15 @@ void ModelResult<T, Ptr>::setEmitter(const typename Sink::ResultEmitter<Ptr>::Pt
296 emitter->onInitialResultSetComplete([this, guard](const Ptr &parent, bool fetchedAll) { 391 emitter->onInitialResultSetComplete([this, guard](const Ptr &parent, bool fetchedAll) {
297 SinkTraceCtx(mLogCtx) << "Initial result set complete. Fetched all: " << fetchedAll; 392 SinkTraceCtx(mLogCtx) << "Initial result set complete. Fetched all: " << fetchedAll;
298 Q_ASSERT(guard); 393 Q_ASSERT(guard);
299 threadBoundary.callInMainThread([=]() { 394 Q_ASSERT(QThread::currentThread() == this->thread());
300 const qint64 parentId = parent ? qHash(*parent) : 0; 395
301 const auto parentIndex = createIndexFromId(parentId); 396 const qint64 parentId = parent ? qHash(*parent) : 0;
302 mEntityChildrenFetchComplete.insert(parentId); 397 const auto parentIndex = createIndexFromId(parentId);
303 if (fetchedAll) { 398 mEntityChildrenFetchComplete.insert(parentId);
304 mEntityAllChildrenFetched.insert(parentId); 399 if (fetchedAll) {
305 } 400 mEntityAllChildrenFetched.insert(parentId);
306 emit dataChanged(parentIndex, parentIndex, QVector<int>() << ChildrenFetchedRole); 401 }
307 }); 402 emit dataChanged(parentIndex, parentIndex, QVector<int>() << ChildrenFetchedRole);
308 }); 403 });
309 mEmitter = emitter; 404 mEmitter = emitter;
310} 405}
diff --git a/common/modelresult.h b/common/modelresult.h
index f30a8e1..cc263cf 100644
--- a/common/modelresult.h
+++ b/common/modelresult.h
@@ -30,15 +30,23 @@
30#include "resultprovider.h" 30#include "resultprovider.h"
31#include "threadboundary.h" 31#include "threadboundary.h"
32 32
33namespace Sink {
34class Notifier;
35}
36
33template <class T, class Ptr> 37template <class T, class Ptr>
34class ModelResult : public QAbstractItemModel 38class ModelResult : public QAbstractItemModel
35{ 39{
36public: 40public:
41 //Update the copy in store.h as well if you modify this
37 enum Roles 42 enum Roles
38 { 43 {
39 DomainObjectRole = Qt::UserRole + 1, 44 DomainObjectRole = Qt::UserRole + 1,
40 ChildrenFetchedRole, 45 ChildrenFetchedRole,
41 DomainObjectBaseRole 46 DomainObjectBaseRole,
47 StatusRole, //ApplicationDomain::SyncStatus
48 WarningRole, //ApplicationDomain::Warning, only if status == warning || status == error
49 ProgressRole //ApplicationDomain::Progress
42 }; 50 };
43 51
44 ModelResult(const Sink::Query &query, const QList<QByteArray> &propertyColumns, const Sink::Log::Context &); 52 ModelResult(const Sink::Query &query, const QList<QByteArray> &propertyColumns, const Sink::Log::Context &);
@@ -77,9 +85,11 @@ private:
77 QSet<qint64 /* entity id */> mEntityChildrenFetched; 85 QSet<qint64 /* entity id */> mEntityChildrenFetched;
78 QSet<qint64 /* entity id */> mEntityChildrenFetchComplete; 86 QSet<qint64 /* entity id */> mEntityChildrenFetchComplete;
79 QSet<qint64 /* entity id */> mEntityAllChildrenFetched; 87 QSet<qint64 /* entity id */> mEntityAllChildrenFetched;
88 QMap<qint64 /* entity id */, int /* Status */> mEntityStatus;
80 QList<QByteArray> mPropertyColumns; 89 QList<QByteArray> mPropertyColumns;
81 Sink::Query mQuery; 90 Sink::Query mQuery;
82 std::function<void(const Ptr &)> loadEntities; 91 std::function<void(const Ptr &)> loadEntities;
83 typename Sink::ResultEmitter<Ptr>::Ptr mEmitter; 92 typename Sink::ResultEmitter<Ptr>::Ptr mEmitter;
84 async::ThreadBoundary threadBoundary; 93 async::ThreadBoundary threadBoundary;
94 QScopedPointer<Sink::Notifier> mNotifier;
85}; 95};
diff --git a/common/notification.cpp b/common/notification.cpp
index b399d50..e688b6d 100644
--- a/common/notification.cpp
+++ b/common/notification.cpp
@@ -19,8 +19,37 @@
19 */ 19 */
20#include "notification.h" 20#include "notification.h"
21 21
22using namespace Sink;
23
24static QByteArray name(int type)
25{
26 switch (type) {
27 case Notification::Shutdown:
28 return "shutdown";
29 case Notification::Status:
30 return "status";
31 case Notification::Info:
32 return "info";
33 case Notification::Warning:
34 return "warning";
35 case Notification::Error:
36 return "error";
37 case Notification::Progress:
38 return "progress";
39 case Notification::Inspection:
40 return "inspection";
41 case Notification::RevisionUpdate:
42 return "revisionupdate";
43 case Notification::FlushCompletion:
44 return "flushcompletion";
45 }
46 return "Unknown:" + QByteArray::number(type);
47}
48
22QDebug operator<<(QDebug dbg, const Sink::Notification &n) 49QDebug operator<<(QDebug dbg, const Sink::Notification &n)
23{ 50{
24 dbg << "Notification(Id: " << n.id << ", Type: " << n.type << ", Code: " << n.code << ", Message: " << n.message << ")"; 51 dbg << "Notification(Type: " << name(n.type) << "Id, : " << n.id << ", Code: ";
52 dbg << n.code;
53 dbg << ", Message: " << n.message << ", Entities: " << n.entities << ")";
25 return dbg.space(); 54 return dbg.space();
26} 55}
diff --git a/common/notification.h b/common/notification.h
index 8224f2a..f5379fd 100644
--- a/common/notification.h
+++ b/common/notification.h
@@ -34,22 +34,29 @@ public:
34 enum NoticationType { 34 enum NoticationType {
35 Shutdown, 35 Shutdown,
36 Status, 36 Status,
37 Info,
37 Warning, 38 Warning,
39 Error,
38 Progress, 40 Progress,
39 Inspection, 41 Inspection,
40 RevisionUpdate, 42 RevisionUpdate,
41 FlushCompletion 43 FlushCompletion
42 }; 44 };
45 /**
46 * Used as code for Inspection type notifications
47 */
43 enum InspectionCode { 48 enum InspectionCode {
44 Success = 0, 49 Success = 0,
45 Failure 50 Failure
46 }; 51 };
47 52
48 QByteArray id; 53 QByteArray id;
54 QByteArrayList entities;
49 int type = 0; 55 int type = 0;
50 QString message; 56 QString message;
51 //A return code. Zero typically indicates success. 57 //A return code. Zero typically indicates success.
52 int code = 0; 58 int code = 0;
59 QByteArray resource;
53}; 60};
54} 61}
55 62
diff --git a/common/notifier.cpp b/common/notifier.cpp
index 53db5be..f52e28b 100644
--- a/common/notifier.cpp
+++ b/common/notifier.cpp
@@ -24,6 +24,8 @@
24 24
25#include "resourceaccess.h" 25#include "resourceaccess.h"
26#include "resourceconfig.h" 26#include "resourceconfig.h"
27#include "query.h"
28#include "facadefactory.h"
27#include "log.h" 29#include "log.h"
28 30
29using namespace Sink; 31using namespace Sink;
@@ -34,37 +36,64 @@ public:
34 Private() : context(new QObject) 36 Private() : context(new QObject)
35 { 37 {
36 } 38 }
39
40 void listenForNotifications(const QSharedPointer<ResourceAccess> &access)
41 {
42 QObject::connect(access.data(), &ResourceAccess::notification, &context, [this](const Notification &notification) {
43 for (const auto &handler : handler) {
44 handler(notification);
45 }
46 });
47 resourceAccess << access;
48 }
49
37 QList<QSharedPointer<ResourceAccess>> resourceAccess; 50 QList<QSharedPointer<ResourceAccess>> resourceAccess;
38 QList<std::function<void(const Notification &)>> handler; 51 QList<std::function<void(const Notification &)>> handler;
39 QSharedPointer<QObject> context; 52 QObject context;
40}; 53};
41 54
42Notifier::Notifier(const QSharedPointer<ResourceAccess> &resourceAccess) : d(new Sink::Notifier::Private) 55Notifier::Notifier(const QSharedPointer<ResourceAccess> &resourceAccess) : d(new Sink::Notifier::Private)
43{ 56{
44 QObject::connect(resourceAccess.data(), &ResourceAccess::notification, d->context.data(), [this](const Notification &notification) { 57 d->listenForNotifications(resourceAccess);
45 for (const auto &handler : d->handler) {
46 handler(notification);
47 }
48 });
49 d->resourceAccess << resourceAccess;
50} 58}
51 59
52Notifier::Notifier(const QByteArray &instanceIdentifier, const QByteArray &resourceType) : d(new Sink::Notifier::Private) 60Notifier::Notifier(const QByteArray &instanceIdentifier, const QByteArray &resourceType) : d(new Sink::Notifier::Private)
53{ 61{
54 auto resourceAccess = Sink::ResourceAccess::Ptr::create(instanceIdentifier, resourceType); 62 auto resourceAccess = Sink::ResourceAccessFactory::instance().getAccess(instanceIdentifier, resourceType);
55 resourceAccess->open(); 63 resourceAccess->open();
56 QObject::connect(resourceAccess.data(), &ResourceAccess::notification, d->context.data(), [this](const Notification &notification) { 64 d->listenForNotifications(resourceAccess);
57 for (const auto &handler : d->handler) {
58 handler(notification);
59 }
60 });
61 d->resourceAccess << resourceAccess;
62} 65}
63 66
64Notifier::Notifier(const QByteArray &instanceIdentifier) : Notifier(instanceIdentifier, ResourceConfig::getResourceType(instanceIdentifier)) 67Notifier::Notifier(const QByteArray &instanceIdentifier) : Notifier(instanceIdentifier, ResourceConfig::getResourceType(instanceIdentifier))
65{ 68{
66} 69}
67 70
71Notifier::Notifier(const Sink::Query &resourceQuery) : d(new Sink::Notifier::Private)
72{
73 Sink::Log::Context resourceCtx{"notifier"};
74 auto facade = FacadeFactory::instance().getFacade<ApplicationDomain::SinkResource>();
75 Q_ASSERT(facade);
76
77 auto result = facade->load(resourceQuery, resourceCtx);
78 auto emitter = result.second;
79 emitter->onAdded([=](const ApplicationDomain::SinkResource::Ptr &resource) {
80 auto resourceAccess = Sink::ResourceAccessFactory::instance().getAccess(resource->identifier(), ResourceConfig::getResourceType(resource->identifier()));
81 resourceAccess->open();
82 d->listenForNotifications(resourceAccess);
83 });
84 emitter->onModified([](const ApplicationDomain::SinkResource::Ptr &) {
85 });
86 emitter->onRemoved([](const ApplicationDomain::SinkResource::Ptr &) {
87 });
88 emitter->onInitialResultSetComplete([](const ApplicationDomain::SinkResource::Ptr &, bool) {
89 });
90 emitter->onComplete([resourceCtx]() {
91 SinkTraceCtx(resourceCtx) << "Resource query complete";
92 });
93 emitter->fetch({});
94 result.first.exec();
95}
96
68void Notifier::registerHandler(std::function<void(const Notification &)> handler) 97void Notifier::registerHandler(std::function<void(const Notification &)> handler)
69{ 98{
70 d->handler << handler; 99 d->handler << handler;
diff --git a/common/notifier.h b/common/notifier.h
index 290458a..b5d3dfa 100644
--- a/common/notifier.h
+++ b/common/notifier.h
@@ -23,14 +23,14 @@
23#include "sink_export.h" 23#include "sink_export.h"
24#include <QByteArray> 24#include <QByteArray>
25#include <QSharedPointer> 25#include <QSharedPointer>
26 26#include <functional>
27#include <KAsync/Async>
28 27
29class QAbstractItemModel; 28class QAbstractItemModel;
30 29
31namespace Sink { 30namespace Sink {
32class ResourceAccess; 31class ResourceAccess;
33class Notification; 32class Notification;
33class Query;
34 34
35class SINK_EXPORT Notifier 35class SINK_EXPORT Notifier
36{ 36{
@@ -38,6 +38,7 @@ public:
38 Notifier(const QSharedPointer<ResourceAccess> &resourceAccess); 38 Notifier(const QSharedPointer<ResourceAccess> &resourceAccess);
39 Notifier(const QByteArray &resourceInstanceIdentifier); 39 Notifier(const QByteArray &resourceInstanceIdentifier);
40 Notifier(const QByteArray &resourceInstanceIdentifier, const QByteArray &resourceType); 40 Notifier(const QByteArray &resourceInstanceIdentifier, const QByteArray &resourceType);
41 Notifier(const Sink::Query &resourceQuery);
41 void registerHandler(std::function<void(const Notification &)>); 42 void registerHandler(std::function<void(const Notification &)>);
42 43
43private: 44private:
diff --git a/common/pipeline.cpp b/common/pipeline.cpp
index 887b6b3..019784e 100644
--- a/common/pipeline.cpp
+++ b/common/pipeline.cpp
@@ -175,13 +175,14 @@ KAsync::Job<qint64> Pipeline::newEntity(void const *command, size_t size)
175 auto o = Sink::ApplicationDomain::ApplicationDomainType{d->resourceContext.instanceId(), key, revision, memoryAdaptor}; 175 auto o = Sink::ApplicationDomain::ApplicationDomainType{d->resourceContext.instanceId(), key, revision, memoryAdaptor};
176 o.setChangedProperties(o.availableProperties().toSet()); 176 o.setChangedProperties(o.availableProperties().toSet());
177 177
178 auto preprocess = [&, this](ApplicationDomain::ApplicationDomainType &newEntity) { 178 auto newEntity = *ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<ApplicationDomain::ApplicationDomainType>(o, o.availableProperties());
179 foreach (const auto &processor, d->processors[bufferType]) { 179 newEntity.setChangedProperties(newEntity.availableProperties().toSet());
180 processor->newEntity(newEntity); 180
181 } 181 foreach (const auto &processor, d->processors[bufferType]) {
182 }; 182 processor->newEntity(newEntity);
183 }
183 184
184 if (!d->entityStore.add(bufferType, o, replayToSource, preprocess)) { 185 if (!d->entityStore.add(bufferType, o, replayToSource)) {
185 return KAsync::error<qint64>(0); 186 return KAsync::error<qint64>(0);
186 } 187 }
187 188
@@ -195,6 +196,11 @@ struct CreateHelper {
195 } 196 }
196}; 197};
197 198
199static KAsync::Job<void> create(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &newEntity)
200{
201 return TypeHelper<CreateHelper>{type}.operator()<KAsync::Job<void>, const ApplicationDomain::ApplicationDomainType&>(newEntity);
202}
203
198KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size) 204KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size)
199{ 205{
200 d->transactionItemCount++; 206 d->transactionItemCount++;
@@ -248,65 +254,71 @@ KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size)
248 deletions = BufferUtils::fromVector(*modifyEntity->deletions()); 254 deletions = BufferUtils::fromVector(*modifyEntity->deletions());
249 } 255 }
250 256
251 if (modifyEntity->targetResource()) { 257 const auto current = d->entityStore.readLatest(bufferType, diff.identifier());
252 auto isMove = modifyEntity->removeEntity(); 258 if (current.identifier().isEmpty()) {
253 auto targetResource = BufferUtils::extractBuffer(modifyEntity->targetResource()); 259 SinkWarningCtx(d->logCtx) << "Failed to read current version: " << diff.identifier();
254 auto changeset = diff.changedProperties(); 260 return KAsync::error<qint64>(0);
255 const auto current = d->entityStore.readLatest(bufferType, diff.identifier()); 261 }
256 if (current.identifier().isEmpty()) {
257 SinkWarningCtx(d->logCtx) << "Failed to read current version: " << diff.identifier();
258 return KAsync::error<qint64>(0);
259 }
260 262
261 auto newEntity = *ApplicationDomain::ApplicationDomainType::getInMemoryCopy<ApplicationDomain::ApplicationDomainType>(current, current.availableProperties()); 263 auto newEntity = d->entityStore.applyDiff(bufferType, current, diff, deletions);
262 264
263 // Apply diff 265 bool isMove = false;
264 for (const auto &property : changeset) { 266 if (modifyEntity->targetResource()) {
265 const auto value = diff.getProperty(property); 267 isMove = modifyEntity->removeEntity();
266 if (value.isValid()) { 268 newEntity.setResource(BufferUtils::extractBuffer(modifyEntity->targetResource()));
267 newEntity.setProperty(property, value); 269 }
268 }
269 }
270 270
271 // Remove deletions 271 foreach (const auto &processor, d->processors[bufferType]) {
272 for (const auto &property : deletions) { 272 bool exitLoop = false;
273 newEntity.setProperty(property, QVariant()); 273 const auto result = processor->processModification(Preprocessor::Modification, current, newEntity);
274 switch (result.action) {
275 case Preprocessor::MoveToResource:
276 isMove = true;
277 exitLoop = true;
278 break;
279 case Preprocessor::CopyToResource:
280 isMove = true;
281 exitLoop = true;
282 break;
283 case Preprocessor::DropModification:
284 SinkTraceCtx(d->logCtx) << "Dropping modification";
285 return KAsync::error<qint64>(0);
286 default:
287 break;
274 } 288 }
275 newEntity.setResource(targetResource); 289 if (exitLoop) {
276 newEntity.setChangedProperties(newEntity.availableProperties().toSet()); 290 break;
291 }
292 }
277 293
278 SinkTraceCtx(d->logCtx) << "Moving entity to new resource " << newEntity.identifier() << newEntity.resourceInstanceIdentifier() << targetResource; 294 //The entity is either being copied or moved
279 auto job = TypeHelper<CreateHelper>{bufferType}.operator()<KAsync::Job<void>, ApplicationDomain::ApplicationDomainType&>(newEntity); 295 if (newEntity.resourceInstanceIdentifier() != d->resourceContext.resourceInstanceIdentifier) {
280 job = job.then([this, current, isMove, targetResource, bufferType](const KAsync::Error &error) { 296 SinkTraceCtx(d->logCtx) << "Moving entity to new resource " << newEntity.identifier() << newEntity.resourceInstanceIdentifier();
281 if (!error) { 297 newEntity.setChangedProperties(newEntity.availableProperties().toSet());
282 SinkTraceCtx(d->logCtx) << "Move of " << current.identifier() << "was successfull"; 298 return create(bufferType, newEntity)
283 if (isMove) { 299 .then([=](const KAsync::Error &error) {
284 startTransaction(); 300 if (!error) {
285 flatbuffers::FlatBufferBuilder fbb; 301 SinkTraceCtx(d->logCtx) << "Move of " << current.identifier() << "was successfull";
286 auto entityId = fbb.CreateString(current.identifier()); 302 if (isMove) {
287 auto type = fbb.CreateString(bufferType); 303 flatbuffers::FlatBufferBuilder fbb;
288 auto location = Sink::Commands::CreateDeleteEntity(fbb, current.revision(), entityId, type, true); 304 auto entityId = fbb.CreateString(current.identifier());
289 Sink::Commands::FinishDeleteEntityBuffer(fbb, location); 305 auto type = fbb.CreateString(bufferType);
290 const auto data = BufferUtils::extractBuffer(fbb); 306 auto location = Sink::Commands::CreateDeleteEntity(fbb, current.revision(), entityId, type, true);
291 deletedEntity(data, data.size()).exec(); 307 Sink::Commands::FinishDeleteEntityBuffer(fbb, location);
292 commit(); 308 const auto data = BufferUtils::extractBuffer(fbb);
309 deletedEntity(data, data.size()).exec();
310 }
311 } else {
312 SinkErrorCtx(d->logCtx) << "Failed to move entity " << newEntity.identifier() << " to resource " << newEntity.resourceInstanceIdentifier();
293 } 313 }
294 } else { 314 })
295 SinkErrorCtx(d->logCtx) << "Failed to move entity " << targetResource << " to resource " << current.identifier(); 315 .then([this] {
296 } 316 return d->entityStore.maxRevision();
297 }); 317 });
298 job.exec();
299 return KAsync::value<qint64>(0);
300 } 318 }
301 319
302 auto preprocess = [&, this](const ApplicationDomain::ApplicationDomainType &oldEntity, ApplicationDomain::ApplicationDomainType &newEntity) {
303 foreach (const auto &processor, d->processors[bufferType]) {
304 processor->modifiedEntity(oldEntity, newEntity);
305 }
306 };
307
308 d->revisionChanged = true; 320 d->revisionChanged = true;
309 if (!d->entityStore.modify(bufferType, diff, deletions, replayToSource, preprocess)) { 321 if (!d->entityStore.modify(bufferType, current, newEntity, replayToSource)) {
310 return KAsync::error<qint64>(0); 322 return KAsync::error<qint64>(0);
311 } 323 }
312 324
@@ -331,14 +343,14 @@ KAsync::Job<qint64> Pipeline::deletedEntity(void const *command, size_t size)
331 const QByteArray key = QByteArray(reinterpret_cast<char const *>(deleteEntity->entityId()->Data()), deleteEntity->entityId()->size()); 343 const QByteArray key = QByteArray(reinterpret_cast<char const *>(deleteEntity->entityId()->Data()), deleteEntity->entityId()->size());
332 SinkTraceCtx(d->logCtx) << "Deleted Entity. Type: " << bufferType << "uid: "<< key << " replayToSource: " << replayToSource; 344 SinkTraceCtx(d->logCtx) << "Deleted Entity. Type: " << bufferType << "uid: "<< key << " replayToSource: " << replayToSource;
333 345
334 auto preprocess = [&, this](const ApplicationDomain::ApplicationDomainType &oldEntity) { 346 const auto current = d->entityStore.readLatest(bufferType, key);
335 foreach (const auto &processor, d->processors[bufferType]) { 347
336 processor->deletedEntity(oldEntity); 348 foreach (const auto &processor, d->processors[bufferType]) {
337 } 349 processor->deletedEntity(current);
338 }; 350 }
339 351
340 d->revisionChanged = true; 352 d->revisionChanged = true;
341 if (!d->entityStore.remove(bufferType, key, replayToSource, preprocess)) { 353 if (!d->entityStore.remove(bufferType, current, replayToSource)) {
342 return KAsync::error<qint64>(0); 354 return KAsync::error<qint64>(0);
343 } 355 }
344 356
@@ -385,6 +397,39 @@ void Preprocessor::finalizeBatch()
385{ 397{
386} 398}
387 399
400void Preprocessor::newEntity(ApplicationDomain::ApplicationDomainType &newEntity)
401{
402
403}
404
405void Preprocessor::modifiedEntity(const ApplicationDomain::ApplicationDomainType &oldEntity, ApplicationDomain::ApplicationDomainType &newEntity)
406{
407
408}
409
410void Preprocessor::deletedEntity(const ApplicationDomain::ApplicationDomainType &oldEntity)
411{
412
413}
414
415Preprocessor::Result Preprocessor::processModification(Type type, const ApplicationDomain::ApplicationDomainType &current, ApplicationDomain::ApplicationDomainType &diff)
416{
417 switch(type) {
418 case Creation:
419 newEntity(diff);
420 return {NoAction};
421 case Modification:
422 modifiedEntity(current, diff);
423 return {NoAction};
424 case Deletion:
425 deletedEntity(current);
426 return {NoAction};
427 default:
428 break;
429 }
430 return {NoAction};
431}
432
388QByteArray Preprocessor::resourceInstanceIdentifier() const 433QByteArray Preprocessor::resourceInstanceIdentifier() const
389{ 434{
390 return d->resourceInstanceIdentifier; 435 return d->resourceInstanceIdentifier;
diff --git a/common/pipeline.h b/common/pipeline.h
index c6dc5fe..11d52fd 100644
--- a/common/pipeline.h
+++ b/common/pipeline.h
@@ -77,10 +77,28 @@ public:
77 Preprocessor(); 77 Preprocessor();
78 virtual ~Preprocessor(); 78 virtual ~Preprocessor();
79 79
80 enum Action {
81 NoAction,
82 MoveToResource,
83 CopyToResource,
84 DropModification,
85 DeleteEntity
86 };
87
88 enum Type {
89 Creation,
90 Modification,
91 Deletion
92 };
93 struct Result {
94 Action action;
95 };
96
80 virtual void startBatch(); 97 virtual void startBatch();
81 virtual void newEntity(ApplicationDomain::ApplicationDomainType &newEntity) {}; 98 virtual void newEntity(ApplicationDomain::ApplicationDomainType &newEntity);
82 virtual void modifiedEntity(const ApplicationDomain::ApplicationDomainType &oldEntity, ApplicationDomain::ApplicationDomainType &newEntity) {}; 99 virtual void modifiedEntity(const ApplicationDomain::ApplicationDomainType &oldEntity, ApplicationDomain::ApplicationDomainType &newEntity);
83 virtual void deletedEntity(const ApplicationDomain::ApplicationDomainType &oldEntity) {}; 100 virtual void deletedEntity(const ApplicationDomain::ApplicationDomainType &oldEntity);
101 virtual Result processModification(Type type, const ApplicationDomain::ApplicationDomainType &current, ApplicationDomain::ApplicationDomainType &diff);
84 virtual void finalizeBatch(); 102 virtual void finalizeBatch();
85 103
86 void setup(const QByteArray &resourceType, const QByteArray &resourceInstanceIdentifier, Pipeline *, Storage::EntityStore *entityStore); 104 void setup(const QByteArray &resourceType, const QByteArray &resourceInstanceIdentifier, Pipeline *, Storage::EntityStore *entityStore);
diff --git a/common/propertymapper.cpp b/common/propertymapper.cpp
index 4d45644..c72cf31 100644
--- a/common/propertymapper.cpp
+++ b/common/propertymapper.cpp
@@ -22,6 +22,7 @@
22#include "applicationdomaintype.h" 22#include "applicationdomaintype.h"
23#include <QDateTime> 23#include <QDateTime>
24#include "mail_generated.h" 24#include "mail_generated.h"
25#include "contact_generated.h"
25 26
26template <> 27template <>
27flatbuffers::uoffset_t variantToProperty<QString>(const QVariant &property, flatbuffers::FlatBufferBuilder &fbb) 28flatbuffers::uoffset_t variantToProperty<QString>(const QVariant &property, flatbuffers::FlatBufferBuilder &fbb)
@@ -110,6 +111,21 @@ flatbuffers::uoffset_t variantToProperty<QList<Sink::ApplicationDomain::Mail::Co
110 return 0; 111 return 0;
111} 112}
112 113
114template <>
115flatbuffers::uoffset_t variantToProperty<QList<Sink::ApplicationDomain::Contact::Email>>(const QVariant &property, flatbuffers::FlatBufferBuilder &fbb)
116{
117 if (property.isValid()) {
118 const auto list = property.value<QList<Sink::ApplicationDomain::Contact::Email>>();
119 std::vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::ContactEmail>> vector;
120 for (const auto &value : list) {
121 auto offset = Sink::ApplicationDomain::Buffer::CreateContactEmailDirect(fbb, value.type, value.email.toUtf8().constData()).o;
122 vector.push_back(offset);
123 }
124 return fbb.CreateVector(vector).o;
125 }
126 return 0;
127}
128
113 129
114QString propertyToString(const flatbuffers::String *property) 130QString propertyToString(const flatbuffers::String *property)
115{ 131{
@@ -217,6 +233,20 @@ QVariant propertyToVariant<QList<Sink::ApplicationDomain::Mail::Contact>>(const
217} 233}
218 234
219template <> 235template <>
236QVariant propertyToVariant<QList<Sink::ApplicationDomain::Contact::Email>>(const flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::ContactEmail>> *property)
237{
238 if (property) {
239 QList<Sink::ApplicationDomain::Contact::Email> list;
240 for (auto it = property->begin(); it != property->end();) {
241 list << Sink::ApplicationDomain::Contact::Email{static_cast<Sink::ApplicationDomain::Contact::Email::Type>(it->type()), propertyToString(it->email())};
242 it.operator++();
243 }
244 return QVariant::fromValue(list);
245 }
246 return QVariant();
247}
248
249template <>
220QVariant propertyToVariant<bool>(uint8_t property) 250QVariant propertyToVariant<bool>(uint8_t property)
221{ 251{
222 return static_cast<bool>(property); 252 return static_cast<bool>(property);
diff --git a/common/propertymapper.h b/common/propertymapper.h
index 70491a1..9ea0b73 100644
--- a/common/propertymapper.h
+++ b/common/propertymapper.h
@@ -29,6 +29,7 @@ namespace Sink {
29namespace ApplicationDomain { 29namespace ApplicationDomain {
30namespace Buffer { 30namespace Buffer {
31 struct MailContact; 31 struct MailContact;
32 struct ContactEmail;
32} 33}
33} 34}
34} 35}
@@ -54,6 +55,8 @@ template <typename T>
54QVariant SINK_EXPORT propertyToVariant(const Sink::ApplicationDomain::Buffer::MailContact *); 55QVariant SINK_EXPORT propertyToVariant(const Sink::ApplicationDomain::Buffer::MailContact *);
55template <typename T> 56template <typename T>
56QVariant SINK_EXPORT propertyToVariant(const flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::MailContact>> *); 57QVariant SINK_EXPORT propertyToVariant(const flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::MailContact>> *);
58template <typename T>
59QVariant SINK_EXPORT propertyToVariant(const flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::ContactEmail>> *);
57 60
58/** 61/**
59 * The property mapper is a non-typesafe virtual dispatch. 62 * The property mapper is a non-typesafe virtual dispatch.
@@ -131,6 +134,12 @@ public:
131 addMapping(T::name, [f](Buffer const *buffer) -> QVariant { return propertyToVariant<typename T::Type>((buffer->*f)()); }); 134 addMapping(T::name, [f](Buffer const *buffer) -> QVariant { return propertyToVariant<typename T::Type>((buffer->*f)()); });
132 } 135 }
133 136
137 template <typename T, typename Buffer>
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
134private: 143private:
135 QHash<QByteArray, std::function<QVariant(BufferType const *)>> mReadAccessors; 144 QHash<QByteArray, std::function<QVariant(BufferType const *)>> mReadAccessors;
136}; 145};
@@ -218,6 +227,15 @@ public:
218 }); 227 });
219 } 228 }
220 229
230 template <typename T>
231 void addMapping(void (BufferBuilder::*f)(flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::ContactEmail>>>))
232 {
233 addMapping(T::name, [f](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function<void(BufferBuilder &)> {
234 auto offset = variantToProperty<typename T::Type>(value, fbb);
235 return [offset, f](BufferBuilder &builder) { (builder.*f)(offset); };
236 });
237 }
238
221private: 239private:
222 QHash<QByteArray, std::function<std::function<void(BufferBuilder &)>(const QVariant &, flatbuffers::FlatBufferBuilder &)>> mWriteAccessors; 240 QHash<QByteArray, std::function<std::function<void(BufferBuilder &)>(const QVariant &, flatbuffers::FlatBufferBuilder &)>> mWriteAccessors;
223}; 241};
diff --git a/common/query.h b/common/query.h
index 8e9050d..5b37cdd 100644
--- a/common/query.h
+++ b/common/query.h
@@ -300,7 +300,9 @@ public:
300 /** Leave the query running and continuously update the result set. */ 300 /** Leave the query running and continuously update the result set. */
301 LiveQuery = 1, 301 LiveQuery = 1,
302 /** Run the query synchronously. */ 302 /** Run the query synchronously. */
303 SynchronousQuery = 2 303 SynchronousQuery = 2,
304 /** Include status updates via notifications */
305 UpdateStatus = 4
304 }; 306 };
305 Q_DECLARE_FLAGS(Flags, Flag) 307 Q_DECLARE_FLAGS(Flags, Flag)
306 308
@@ -410,6 +412,11 @@ public:
410 mFlags = flags; 412 mFlags = flags;
411 } 413 }
412 414
415 Flags flags() const
416 {
417 return mFlags;
418 }
419
413 bool liveQuery() const 420 bool liveQuery() const
414 { 421 {
415 return mFlags.testFlag(LiveQuery); 422 return mFlags.testFlag(LiveQuery);
@@ -509,6 +516,31 @@ public:
509 } 516 }
510 517
511 template <typename T> 518 template <typename T>
519 SyncScope &resourceFilter(const ApplicationDomain::ApplicationDomainType &entity)
520 {
521 mResourceFilter.propertyFilter.insert(T::name, Comparator(entity.identifier()));
522 return *this;
523 }
524
525 SyncScope &resourceFilter(const QByteArray &name, const Comparator &comparator)
526 {
527 mResourceFilter.propertyFilter.insert(name, comparator);
528 return *this;
529 }
530
531 template <typename T>
532 SyncScope &resourceContainsFilter(const QVariant &value)
533 {
534 return resourceFilter(T::name, Comparator(value, Comparator::Contains));
535 }
536
537 template <typename T>
538 SyncScope &resourceFilter(const QVariant &value)
539 {
540 return resourceFilter(T::name, value);
541 }
542
543 template <typename T>
512 SyncScope &filter(const Query::Comparator &comparator) 544 SyncScope &filter(const Query::Comparator &comparator)
513 { 545 {
514 return filter(T::name, comparator); 546 return filter(T::name, comparator);
diff --git a/common/queryrunner.cpp b/common/queryrunner.cpp
index 802fc48..43f48c0 100644
--- a/common/queryrunner.cpp
+++ b/common/queryrunner.cpp
@@ -51,7 +51,7 @@ public:
51 virtual ~QueryWorker(); 51 virtual ~QueryWorker();
52 52
53 ReplayResult executeIncrementalQuery(const Sink::Query &query, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, DataStoreQuery::State::Ptr state); 53 ReplayResult executeIncrementalQuery(const Sink::Query &query, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, DataStoreQuery::State::Ptr state);
54 ReplayResult executeInitialQuery(const Sink::Query &query, const typename DomainType::Ptr &parent, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, int offset, int batchsize); 54 ReplayResult executeInitialQuery(const Sink::Query &query, const typename DomainType::Ptr &parent, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, int batchsize, DataStoreQuery::State::Ptr state);
55 55
56private: 56private:
57 void resultProviderCallback(const Sink::Query &query, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, const ResultSet::Result &result); 57 void resultProviderCallback(const Sink::Query &query, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, const ResultSet::Result &result);
@@ -72,18 +72,18 @@ QueryRunner<DomainType>::QueryRunner(const Sink::Query &query, const Sink::Resou
72 auto guardPtr = QPointer<QObject>(&guard); 72 auto guardPtr = QPointer<QObject>(&guard);
73 auto fetcher = [=](const typename DomainType::Ptr &parent) { 73 auto fetcher = [=](const typename DomainType::Ptr &parent) {
74 const QByteArray parentId = parent ? parent->identifier() : QByteArray(); 74 const QByteArray parentId = parent ? parent->identifier() : QByteArray();
75 SinkTraceCtx(mLogCtx) << "Running fetcher. Offset: " << mOffset[parentId] << " Batchsize: " << mBatchSize; 75 SinkTraceCtx(mLogCtx) << "Running fetcher. Batchsize: " << mBatchSize;
76 auto resultProvider = mResultProvider; 76 auto resultProvider = mResultProvider;
77 auto resultTransformation = mResultTransformation; 77 auto resultTransformation = mResultTransformation;
78 auto offset = mOffset[parentId];
79 auto batchSize = mBatchSize; 78 auto batchSize = mBatchSize;
80 auto resourceContext = mResourceContext; 79 auto resourceContext = mResourceContext;
81 auto logCtx = mLogCtx; 80 auto logCtx = mLogCtx;
81 auto state = mQueryState.value(parentId);
82 const bool runAsync = !query.synchronousQuery(); 82 const bool runAsync = !query.synchronousQuery();
83 //The lambda will be executed in a separate thread, so copy all arguments 83 //The lambda will be executed in a separate thread, so copy all arguments
84 async::run<ReplayResult>([resultTransformation, offset, batchSize, query, bufferType, resourceContext, resultProvider, parent, logCtx]() { 84 async::run<ReplayResult>([=]() {
85 QueryWorker<DomainType> worker(query, resourceContext, bufferType, resultTransformation, logCtx); 85 QueryWorker<DomainType> worker(query, resourceContext, bufferType, resultTransformation, logCtx);
86 return worker.executeInitialQuery(query, parent, *resultProvider, offset, batchSize); 86 return worker.executeInitialQuery(query, parent, *resultProvider, batchSize, state);
87 }, runAsync) 87 }, runAsync)
88 .then([this, parentId, query, parent, resultProvider, guardPtr](const ReplayResult &result) { 88 .then([this, parentId, query, parent, resultProvider, guardPtr](const ReplayResult &result) {
89 if (!guardPtr) { 89 if (!guardPtr) {
@@ -91,8 +91,7 @@ QueryRunner<DomainType>::QueryRunner(const Sink::Query &query, const Sink::Resou
91 return; 91 return;
92 } 92 }
93 mInitialQueryComplete = true; 93 mInitialQueryComplete = true;
94 mQueryState = result.queryState; 94 mQueryState[parentId] = result.queryState;
95 mOffset[parentId] += result.replayedEntities;
96 // Only send the revision replayed information if we're connected to the resource, there's no need to start the resource otherwise. 95 // Only send the revision replayed information if we're connected to the resource, there's no need to start the resource otherwise.
97 if (query.liveQuery()) { 96 if (query.liveQuery()) {
98 mResourceAccess->sendRevisionReplayedCommand(result.newRevision); 97 mResourceAccess->sendRevisionReplayedCommand(result.newRevision);
@@ -111,10 +110,11 @@ QueryRunner<DomainType>::QueryRunner(const Sink::Query &query, const Sink::Resou
111 Q_ASSERT(!query.synchronousQuery()); 110 Q_ASSERT(!query.synchronousQuery());
112 // Incremental updates are always loaded directly, leaving it up to the result to discard the changes if they are not interesting 111 // Incremental updates are always loaded directly, leaving it up to the result to discard the changes if they are not interesting
113 setQuery([=]() -> KAsync::Job<void> { 112 setQuery([=]() -> KAsync::Job<void> {
113 const QByteArray parentId;
114 auto resultProvider = mResultProvider; 114 auto resultProvider = mResultProvider;
115 auto resourceContext = mResourceContext; 115 auto resourceContext = mResourceContext;
116 auto logCtx = mLogCtx; 116 auto logCtx = mLogCtx;
117 auto state = mQueryState; 117 auto state = mQueryState.value(parentId);
118 if (!mInitialQueryComplete) { 118 if (!mInitialQueryComplete) {
119 SinkWarningCtx(mLogCtx) << "Can't start the incremental query before the initial query is complete"; 119 SinkWarningCtx(mLogCtx) << "Can't start the incremental query before the initial query is complete";
120 fetcher({}); 120 fetcher({});
@@ -225,7 +225,7 @@ ReplayResult QueryWorker<DomainType>::executeIncrementalQuery(const Sink::Query
225 SinkWarningCtx(mLogCtx) << "No previous query state."; 225 SinkWarningCtx(mLogCtx) << "No previous query state.";
226 return {0, 0, false, DataStoreQuery::State::Ptr{}}; 226 return {0, 0, false, DataStoreQuery::State::Ptr{}};
227 } 227 }
228 auto preparedQuery = DataStoreQuery{*state, ApplicationDomain::getTypeName<DomainType>(), entityStore}; 228 auto preparedQuery = DataStoreQuery{*state, ApplicationDomain::getTypeName<DomainType>(), entityStore, true};
229 auto resultSet = preparedQuery.update(baseRevision); 229 auto resultSet = preparedQuery.update(baseRevision);
230 SinkTraceCtx(mLogCtx) << "Filtered set retrieved. " << Log::TraceTime(time.elapsed()); 230 SinkTraceCtx(mLogCtx) << "Filtered set retrieved. " << Log::TraceTime(time.elapsed());
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) {
@@ -240,7 +240,7 @@ ReplayResult QueryWorker<DomainType>::executeIncrementalQuery(const Sink::Query
240 240
241template <class DomainType> 241template <class DomainType>
242ReplayResult QueryWorker<DomainType>::executeInitialQuery( 242ReplayResult QueryWorker<DomainType>::executeInitialQuery(
243 const Sink::Query &query, const typename DomainType::Ptr &parent, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, int offset, int batchsize) 243 const Sink::Query &query, const typename DomainType::Ptr &parent, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, int batchsize, DataStoreQuery::State::Ptr state)
244{ 244{
245 QTime time; 245 QTime time;
246 time.start(); 246 time.start();
@@ -257,11 +257,17 @@ ReplayResult QueryWorker<DomainType>::executeInitialQuery(
257 } 257 }
258 258
259 auto entityStore = EntityStore{mResourceContext, mLogCtx}; 259 auto entityStore = EntityStore{mResourceContext, mLogCtx};
260 auto preparedQuery = DataStoreQuery{modifiedQuery, ApplicationDomain::getTypeName<DomainType>(), entityStore}; 260 auto preparedQuery = [&] {
261 auto resultSet = preparedQuery.execute(); 261 if (state) {
262 return DataStoreQuery{*state, ApplicationDomain::getTypeName<DomainType>(), entityStore, false};
263 } else {
264 return DataStoreQuery{modifiedQuery, ApplicationDomain::getTypeName<DomainType>(), entityStore};
265 }
266 }();
267 auto resultSet = preparedQuery.execute();;
262 268
263 SinkTraceCtx(mLogCtx) << "Filtered set retrieved. " << Log::TraceTime(time.elapsed()); 269 SinkTraceCtx(mLogCtx) << "Filtered set retrieved." << Log::TraceTime(time.elapsed());
264 auto replayResult = resultSet.replaySet(offset, batchsize, [this, query, &resultProvider](const ResultSet::Result &result) { 270 auto replayResult = resultSet.replaySet(0, batchsize, [this, query, &resultProvider](const ResultSet::Result &result) {
265 resultProviderCallback(query, resultProvider, result); 271 resultProviderCallback(query, resultProvider, result);
266 }); 272 });
267 273
@@ -269,9 +275,7 @@ ReplayResult QueryWorker<DomainType>::executeInitialQuery(
269 << (replayResult.replayedAll ? "Replayed all available results.\n" : "") 275 << (replayResult.replayedAll ? "Replayed all available results.\n" : "")
270 << "Initial query took: " << Log::TraceTime(time.elapsed()); 276 << "Initial query took: " << Log::TraceTime(time.elapsed());
271 277
272 auto state = preparedQuery.getState(); 278 return {entityStore.maxRevision(), replayResult.replayedEntities, replayResult.replayedAll, preparedQuery.getState()};
273
274 return {entityStore.maxRevision(), replayResult.replayedEntities, replayResult.replayedAll, state};
275} 279}
276 280
277#define REGISTER_TYPE(T) \ 281#define REGISTER_TYPE(T) \
diff --git a/common/queryrunner.h b/common/queryrunner.h
index f5c7ead..5308eac 100644
--- a/common/queryrunner.h
+++ b/common/queryrunner.h
@@ -98,11 +98,10 @@ private:
98 QSharedPointer<Sink::ResourceAccessInterface> mResourceAccess; 98 QSharedPointer<Sink::ResourceAccessInterface> mResourceAccess;
99 QSharedPointer<Sink::ResultProvider<typename DomainType::Ptr>> mResultProvider; 99 QSharedPointer<Sink::ResultProvider<typename DomainType::Ptr>> mResultProvider;
100 ResultTransformation mResultTransformation; 100 ResultTransformation mResultTransformation;
101 QHash<QByteArray, qint64> mOffset; 101 QHash<QByteArray, DataStoreQuery::State::Ptr> mQueryState;
102 int mBatchSize; 102 int mBatchSize;
103 QObject guard; 103 QObject guard;
104 Sink::Log::Context mLogCtx; 104 Sink::Log::Context mLogCtx;
105 DataStoreQuery::State::Ptr mQueryState;
106 bool mInitialQueryComplete = false; 105 bool mInitialQueryComplete = false;
107 bool mQueryInProgress = false; 106 bool mQueryInProgress = false;
108}; 107};
diff --git a/common/resourceaccess.cpp b/common/resourceaccess.cpp
index 50845ac..ad8cae9 100644
--- a/common/resourceaccess.cpp
+++ b/common/resourceaccess.cpp
@@ -547,6 +547,7 @@ 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.entities = BufferUtils::fromVector(*buffer->entities());
550 return n; 551 return n;
551} 552}
552 553
@@ -601,20 +602,23 @@ bool ResourceAccess::processMessageBuffer()
601 queuedInvoke([=]() { emit notification(n); }, this); 602 queuedInvoke([=]() { emit notification(n); }, this);
602 } break; 603 } break;
603 case Sink::Notification::Status: 604 case Sink::Notification::Status:
604 if (mResourceStatus == buffer->code()) { 605 if (mResourceStatus != buffer->code()) {
605 SinkTrace() << "Got an unnecessary status notification"; 606 mResourceStatus = buffer->code();
606 break; 607 SinkTrace() << "Updated status: " << mResourceStatus;
607 } 608 }
608 mResourceStatus = buffer->code(); 609 [[clang::fallthrough]];
609 SinkTrace() << "Updated status: " << mResourceStatus; 610 case Sink::Notification::Info:
610 [[clang::fallthrough]]; 611 [[clang::fallthrough]];
611 case Sink::Notification::Warning: 612 case Sink::Notification::Warning:
612 [[clang::fallthrough]]; 613 [[clang::fallthrough]];
614 case Sink::Notification::Error:
615 [[clang::fallthrough]];
613 case Sink::Notification::FlushCompletion: 616 case Sink::Notification::FlushCompletion:
614 [[clang::fallthrough]]; 617 [[clang::fallthrough]];
615 case Sink::Notification::Progress: { 618 case Sink::Notification::Progress: {
616 auto n = getNotification(buffer); 619 auto n = getNotification(buffer);
617 SinkTrace() << "Received notification: " << n.type; 620 SinkTrace() << "Received notification: " << n;
621 n.resource = d->resourceInstanceIdentifier;
618 emit notification(n); 622 emit notification(n);
619 } break; 623 } break;
620 case Sink::Notification::RevisionUpdate: 624 case Sink::Notification::RevisionUpdate:
diff --git a/common/resourcecontext.h b/common/resourcecontext.h
index 6058ac7..6ceba01 100644
--- a/common/resourcecontext.h
+++ b/common/resourcecontext.h
@@ -55,7 +55,9 @@ struct ResourceContext {
55 DomainTypeAdaptorFactoryInterface &adaptorFactory(const QByteArray &type) const 55 DomainTypeAdaptorFactoryInterface &adaptorFactory(const QByteArray &type) const
56 { 56 {
57 auto factory = adaptorFactories.value(type); 57 auto factory = adaptorFactories.value(type);
58 Q_ASSERT(factory); 58 if (!factory) {
59 qFatal("Failed to find a factory for %s", type.constData());
60 }
59 return *factory; 61 return *factory;
60 } 62 }
61 63
diff --git a/common/resourcecontrol.cpp b/common/resourcecontrol.cpp
index 1f61a1c..70a3f7d 100644
--- a/common/resourcecontrol.cpp
+++ b/common/resourcecontrol.cpp
@@ -100,9 +100,9 @@ KAsync::Job<void> ResourceControl::flush(Flush::FlushType type, const QByteArray
100 auto notifier = QSharedPointer<Sink::Notifier>::create(resourceAccess); 100 auto notifier = QSharedPointer<Sink::Notifier>::create(resourceAccess);
101 auto id = QUuid::createUuid().toByteArray(); 101 auto id = QUuid::createUuid().toByteArray();
102 return KAsync::start<void>([=](KAsync::Future<void> &future) { 102 return KAsync::start<void>([=](KAsync::Future<void> &future) {
103 SinkTrace() << "Waiting for notification notification " << id; 103 SinkTrace() << "Waiting for flush completion notification " << id;
104 notifier->registerHandler([&future, id](const Notification &notification) { 104 notifier->registerHandler([&future, id](const Notification &notification) {
105 SinkTrace() << "Received notification " << notification.type << notification.id; 105 SinkTrace() << "Received notification: " << notification.type << notification.id;
106 if (notification.id == id) { 106 if (notification.id == id) {
107 SinkTrace() << "FlushComplete"; 107 SinkTrace() << "FlushComplete";
108 if (notification.code) { 108 if (notification.code) {
diff --git a/common/resourcefacade.cpp b/common/resourcefacade.cpp
index 4a8037d..dee0711 100644
--- a/common/resourcefacade.cpp
+++ b/common/resourcefacade.cpp
@@ -34,33 +34,42 @@ SINK_DEBUG_AREA("ResourceFacade")
34template<typename DomainType> 34template<typename DomainType>
35ConfigNotifier LocalStorageFacade<DomainType>::sConfigNotifier; 35ConfigNotifier LocalStorageFacade<DomainType>::sConfigNotifier;
36 36
37static void applyConfig(ConfigStore &configStore, const QByteArray &id, ApplicationDomain::ApplicationDomainType &object) 37static void applyConfig(ConfigStore &configStore, const QByteArray &id, ApplicationDomain::ApplicationDomainType &object, const QByteArrayList &requestedProperties)
38{ 38{
39 const auto configurationValues = configStore.get(id); 39 const auto configurationValues = configStore.get(id);
40 for (auto it = configurationValues.constBegin(); it != configurationValues.constEnd(); it++) { 40 for (auto it = configurationValues.constBegin(); it != configurationValues.constEnd(); it++) {
41 object.setProperty(it.key(), it.value()); 41 object.setProperty(it.key(), it.value());
42 } 42 }
43 //Populate the object with dummy values for non-available but requested properties.
44 //This avoid a warning about non-existing properties in bufferadaptor.h
45 if (!requestedProperties.isEmpty()) {
46 for (const auto &requested: requestedProperties) {
47 if (!object.hasProperty(requested)) {
48 object.setProperty(requested, QVariant{});
49 }
50 }
51 }
43} 52}
44 53
45template <typename DomainType> 54template <typename DomainType>
46static typename DomainType::Ptr readFromConfig(ConfigStore &configStore, const QByteArray &id, const QByteArray &type) 55static typename DomainType::Ptr readFromConfig(ConfigStore &configStore, const QByteArray &id, const QByteArray &type, const QByteArrayList &requestedProperties)
47{ 56{
48 auto object = DomainType::Ptr::create(id); 57 auto object = DomainType::Ptr::create(id);
49 applyConfig(configStore, id, *object); 58 applyConfig(configStore, id, *object, requestedProperties);
50 return object; 59 return object;
51} 60}
52 61
53template <> 62template <>
54typename ApplicationDomain::SinkAccount::Ptr readFromConfig<ApplicationDomain::SinkAccount>(ConfigStore &configStore, const QByteArray &id, const QByteArray &type) 63typename ApplicationDomain::SinkAccount::Ptr readFromConfig<ApplicationDomain::SinkAccount>(ConfigStore &configStore, const QByteArray &id, const QByteArray &type, const QByteArrayList &requestedProperties)
55{ 64{
56 auto object = ApplicationDomain::SinkAccount::Ptr::create(id); 65 auto object = ApplicationDomain::SinkAccount::Ptr::create(id);
57 object->setProperty(ApplicationDomain::SinkAccount::AccountType::name, type); 66 object->setProperty(ApplicationDomain::SinkAccount::AccountType::name, type);
58 applyConfig(configStore, id, *object); 67 applyConfig(configStore, id, *object, requestedProperties);
59 return object; 68 return object;
60} 69}
61 70
62template <> 71template <>
63typename ApplicationDomain::SinkResource::Ptr readFromConfig<ApplicationDomain::SinkResource>(ConfigStore &configStore, const QByteArray &id, const QByteArray &type) 72typename ApplicationDomain::SinkResource::Ptr readFromConfig<ApplicationDomain::SinkResource>(ConfigStore &configStore, const QByteArray &id, const QByteArray &type, const QByteArrayList &requestedProperties)
64{ 73{
65 auto object = ApplicationDomain::SinkResource::Ptr::create(id); 74 auto object = ApplicationDomain::SinkResource::Ptr::create(id);
66 object->setProperty(ApplicationDomain::SinkResource::ResourceType::name, type); 75 object->setProperty(ApplicationDomain::SinkResource::ResourceType::name, type);
@@ -70,7 +79,7 @@ typename ApplicationDomain::SinkResource::Ptr readFromConfig<ApplicationDomain::
70 object->setCapabilities(res->capabilities()); 79 object->setCapabilities(res->capabilities());
71 } 80 }
72 } 81 }
73 applyConfig(configStore, id, *object); 82 applyConfig(configStore, id, *object, requestedProperties);
74 return object; 83 return object;
75} 84}
76 85
@@ -104,7 +113,7 @@ LocalStorageQueryRunner<DomainType>::LocalStorageQueryRunner(const Query &query,
104 if (!query.ids().isEmpty() && !query.ids().contains(res)) { 113 if (!query.ids().isEmpty() && !query.ids().contains(res)) {
105 continue; 114 continue;
106 } 115 }
107 auto entity = readFromConfig<DomainType>(mConfigStore, res, type); 116 auto entity = readFromConfig<DomainType>(mConfigStore, res, type, query.requestedProperties);
108 if (!matchesFilter(query.getBaseFilters(), *entity)){ 117 if (!matchesFilter(query.getBaseFilters(), *entity)){
109 SinkTraceCtx(mLogCtx) << "Skipping due to filter." << res; 118 SinkTraceCtx(mLogCtx) << "Skipping due to filter." << res;
110 continue; 119 continue;
@@ -169,7 +178,7 @@ template<typename DomainType>
169void LocalStorageQueryRunner<DomainType>::statusChanged(const QByteArray &identifier) 178void LocalStorageQueryRunner<DomainType>::statusChanged(const QByteArray &identifier)
170{ 179{
171 SinkTraceCtx(mLogCtx) << "Status changed " << identifier; 180 SinkTraceCtx(mLogCtx) << "Status changed " << identifier;
172 auto entity = readFromConfig<DomainType>(mConfigStore, identifier, ApplicationDomain::getTypeName<DomainType>()); 181 auto entity = readFromConfig<DomainType>(mConfigStore, identifier, ApplicationDomain::getTypeName<DomainType>(), QByteArrayList{});
173 updateStatus(*entity); 182 updateStatus(*entity);
174 mResultProvider->modify(entity); 183 mResultProvider->modify(entity);
175} 184}
@@ -213,7 +222,7 @@ KAsync::Job<void> LocalStorageFacade<DomainType>::create(const DomainType &domai
213 } 222 }
214 configStore.modify(identifier, configurationValues); 223 configStore.modify(identifier, configurationValues);
215 } 224 }
216 sConfigNotifier.add(::readFromConfig<DomainType>(configStore, identifier, type)); 225 sConfigNotifier.add(::readFromConfig<DomainType>(configStore, identifier, type, QByteArrayList{}));
217 }); 226 });
218} 227}
219 228
@@ -242,7 +251,7 @@ KAsync::Job<void> LocalStorageFacade<DomainType>::modify(const DomainType &domai
242 } 251 }
243 252
244 const auto type = configStore.getEntries().value(identifier); 253 const auto type = configStore.getEntries().value(identifier);
245 sConfigNotifier.modify(::readFromConfig<DomainType>(configStore, identifier, type)); 254 sConfigNotifier.modify(::readFromConfig<DomainType>(configStore, identifier, type, QByteArrayList{}));
246 }); 255 });
247} 256}
248 257
@@ -337,10 +346,12 @@ QPair<KAsync::Job<void>, typename Sink::ResultEmitter<typename ApplicationDomain
337 runner->setStatusUpdater([runner, monitoredResources, ctx](ApplicationDomain::SinkAccount &account) { 346 runner->setStatusUpdater([runner, monitoredResources, ctx](ApplicationDomain::SinkAccount &account) {
338 Query query; 347 Query query;
339 query.filter<ApplicationDomain::SinkResource::Account>(account.identifier()); 348 query.filter<ApplicationDomain::SinkResource::Account>(account.identifier());
349 query.request<ApplicationDomain::SinkResource::Account>()
350 .request<ApplicationDomain::SinkResource::Capabilities>();
340 const auto resources = Store::read<ApplicationDomain::SinkResource>(query); 351 const auto resources = Store::read<ApplicationDomain::SinkResource>(query);
341 SinkTraceCtx(ctx) << "Found resource belonging to the account " << account.identifier() << " : " << resources; 352 SinkTraceCtx(ctx) << "Found resource belonging to the account " << account.identifier() << " : " << resources;
342 auto accountIdentifier = account.identifier(); 353 auto accountIdentifier = account.identifier();
343 ApplicationDomain::Status status = ApplicationDomain::ConnectedStatus; 354 QList<int> states;
344 for (const auto &resource : resources) { 355 for (const auto &resource : resources) {
345 auto resourceAccess = ResourceAccessFactory::instance().getAccess(resource.identifier(), ResourceConfig::getResourceType(resource.identifier())); 356 auto resourceAccess = ResourceAccessFactory::instance().getAccess(resource.identifier(), ResourceConfig::getResourceType(resource.identifier()));
346 if (!monitoredResources->contains(resource.identifier())) { 357 if (!monitoredResources->contains(resource.identifier())) {
@@ -353,27 +364,20 @@ QPair<KAsync::Job<void>, typename Sink::ResultEmitter<typename ApplicationDomain
353 Q_ASSERT(ret); 364 Q_ASSERT(ret);
354 monitoredResources->insert(resource.identifier()); 365 monitoredResources->insert(resource.identifier());
355 } 366 }
356 367 states << resourceAccess->getResourceStatus();
357 //Figure out overall status
358 auto s = resourceAccess->getResourceStatus();
359 switch (s) {
360 case ApplicationDomain::ErrorStatus:
361 status = ApplicationDomain::ErrorStatus;
362 break;
363 case ApplicationDomain::OfflineStatus:
364 if (status == ApplicationDomain::ConnectedStatus) {
365 status = ApplicationDomain::OfflineStatus;
366 }
367 break;
368 case ApplicationDomain::ConnectedStatus:
369 break;
370 case ApplicationDomain::BusyStatus:
371 if (status != ApplicationDomain::ErrorStatus) {
372 status = ApplicationDomain::BusyStatus;
373 }
374 break;
375 }
376 } 368 }
369 const auto status = [&] {
370 if (states.contains(ApplicationDomain::ErrorStatus)) {
371 return ApplicationDomain::ErrorStatus;
372 }
373 if (states.contains(ApplicationDomain::BusyStatus)) {
374 return ApplicationDomain::BusyStatus;
375 }
376 if (states.contains(ApplicationDomain::ConnectedStatus)) {
377 return ApplicationDomain::ConnectedStatus;
378 }
379 return ApplicationDomain::OfflineStatus;
380 }();
377 account.setStatusStatus(status); 381 account.setStatusStatus(status);
378 }); 382 });
379 return qMakePair(KAsync::null<void>(), runner->emitter()); 383 return qMakePair(KAsync::null<void>(), runner->emitter());
diff --git a/common/resultprovider.h b/common/resultprovider.h
index a2ed0b5..d6feaf9 100644
--- a/common/resultprovider.h
+++ b/common/resultprovider.h
@@ -268,8 +268,9 @@ public:
268 268
269 void initialResultSetComplete(const DomainType &parent, bool replayedAll) 269 void initialResultSetComplete(const DomainType &parent, bool replayedAll)
270 { 270 {
271 QMutexLocker locker{&mMutex}; 271 //This callback is only ever called from the main thread, so we don't do any locking
272 if (initialResultSetCompleteHandler && guardOk()) { 272 if (initialResultSetCompleteHandler && guardOk()) {
273 //This can directly lead to our destruction and thus waitForMethodExecutionEnd
273 initialResultSetCompleteHandler(parent, replayedAll); 274 initialResultSetCompleteHandler(parent, replayedAll);
274 } 275 }
275 } 276 }
@@ -313,6 +314,13 @@ private:
313 std::function<void(void)> clearHandler; 314 std::function<void(void)> clearHandler;
314 315
315 std::function<void(const DomainType &parent)> mFetcher; 316 std::function<void(const DomainType &parent)> mFetcher;
317 /*
318 * This mutex is here to protect the emitter from getting destroyed while the producer-thread (ResultProvider) is calling into it,
319 * and vice-verca, to protect the producer thread from calling into a destroyed emitter.
320 *
321 * This is necessary because Emitter and ResultProvider have lifetimes managed by two different threads.
322 * The emitter lives in the application thread, and the resultprovider in the query thread.
323 */
316 QMutex mMutex; 324 QMutex mMutex;
317 bool mDone = false; 325 bool mDone = false;
318}; 326};
diff --git a/common/specialpurposepreprocessor.cpp b/common/specialpurposepreprocessor.cpp
index be5fa50..25a6d1a 100644
--- a/common/specialpurposepreprocessor.cpp
+++ b/common/specialpurposepreprocessor.cpp
@@ -46,7 +46,7 @@ QByteArray getSpecialPurposeType(const QString &name)
46} 46}
47} 47}
48 48
49SpecialPurposeProcessor::SpecialPurposeProcessor(const QByteArray &resourceType, const QByteArray &resourceInstanceIdentifier) : mResourceType(resourceType), mResourceInstanceIdentifier(resourceInstanceIdentifier) {} 49SpecialPurposeProcessor::SpecialPurposeProcessor() : Sink::Preprocessor() {}
50 50
51QByteArray SpecialPurposeProcessor::findFolder(const QByteArray &specialPurpose, bool createIfMissing) 51QByteArray SpecialPurposeProcessor::findFolder(const QByteArray &specialPurpose, bool createIfMissing)
52{ 52{
@@ -60,7 +60,7 @@ QByteArray SpecialPurposeProcessor::findFolder(const QByteArray &specialPurpose,
60 60
61 if (!mSpecialPurposeFolders.contains(specialPurpose) && createIfMissing) { 61 if (!mSpecialPurposeFolders.contains(specialPurpose) && createIfMissing) {
62 SinkTrace() << "Failed to find a " << specialPurpose << " folder, creating a new one"; 62 SinkTrace() << "Failed to find a " << specialPurpose << " folder, creating a new one";
63 auto folder = ApplicationDomain::Folder::create(mResourceInstanceIdentifier); 63 auto folder = ApplicationDomain::Folder::create(resourceInstanceIdentifier());
64 folder.setSpecialPurpose(QByteArrayList() << specialPurpose); 64 folder.setSpecialPurpose(QByteArrayList() << specialPurpose);
65 folder.setName(sSpecialPurposeFolders.value(specialPurpose)); 65 folder.setName(sSpecialPurposeFolders.value(specialPurpose));
66 folder.setIcon("folder"); 66 folder.setIcon("folder");
@@ -104,7 +104,19 @@ void SpecialPurposeProcessor::moveToFolder(Sink::ApplicationDomain::ApplicationD
104 104
105void SpecialPurposeProcessor::newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) 105void SpecialPurposeProcessor::newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity)
106{ 106{
107 moveToFolder(newEntity); 107 auto mail = newEntity.cast<ApplicationDomain::Mail>();
108 const auto folder = mail.getFolder();
109 if (folder.isEmpty()) {
110 moveToFolder(newEntity);
111 } else {
112 bool isDraft = findFolder(ApplicationDomain::SpecialPurpose::Mail::drafts) == folder;
113 bool isSent = findFolder(ApplicationDomain::SpecialPurpose::Mail::sent) == folder;
114 bool isTrash = findFolder(ApplicationDomain::SpecialPurpose::Mail::trash) == folder;
115 mail.setDraft(isDraft);
116 mail.setTrash(isTrash);
117 mail.setSent(isSent);
118 }
119
108} 120}
109 121
110void SpecialPurposeProcessor::modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity) 122void SpecialPurposeProcessor::modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity)
@@ -118,8 +130,8 @@ void SpecialPurposeProcessor::modifiedEntity(const Sink::ApplicationDomain::Appl
118 bool isSent = findFolder(ApplicationDomain::SpecialPurpose::Mail::sent) == folder; 130 bool isSent = findFolder(ApplicationDomain::SpecialPurpose::Mail::sent) == folder;
119 bool isTrash = findFolder(ApplicationDomain::SpecialPurpose::Mail::trash) == folder; 131 bool isTrash = findFolder(ApplicationDomain::SpecialPurpose::Mail::trash) == folder;
120 mail.setDraft(isDraft); 132 mail.setDraft(isDraft);
121 mail.setTrash(isSent); 133 mail.setTrash(isTrash);
122 mail.setSent(isTrash); 134 mail.setSent(isSent);
123 } else { 135 } else {
124 moveToFolder(newEntity); 136 moveToFolder(newEntity);
125 } 137 }
diff --git a/common/specialpurposepreprocessor.h b/common/specialpurposepreprocessor.h
index 6eb325c..6173aff 100644
--- a/common/specialpurposepreprocessor.h
+++ b/common/specialpurposepreprocessor.h
@@ -28,7 +28,7 @@ namespace SpecialPurpose {
28class SINK_EXPORT SpecialPurposeProcessor : public Sink::Preprocessor 28class SINK_EXPORT SpecialPurposeProcessor : public Sink::Preprocessor
29{ 29{
30public: 30public:
31 SpecialPurposeProcessor(const QByteArray &resourceType, const QByteArray &resourceInstanceIdentifier); 31 SpecialPurposeProcessor();
32 32
33 void newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE; 33 void newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE;
34 void modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE; 34 void modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE;
@@ -39,6 +39,4 @@ private:
39 bool isSpecialPurposeFolder(const QByteArray &folder) const; 39 bool isSpecialPurposeFolder(const QByteArray &folder) const;
40 40
41 QHash<QByteArray, QByteArray> mSpecialPurposeFolders; 41 QHash<QByteArray, QByteArray> mSpecialPurposeFolders;
42 QByteArray mResourceType;
43 QByteArray mResourceInstanceIdentifier;
44}; 42};
diff --git a/common/storage.h b/common/storage.h
index fd349f3..71e9401 100644
--- a/common/storage.h
+++ b/common/storage.h
@@ -128,14 +128,14 @@ public:
128 public: 128 public:
129 Transaction(); 129 Transaction();
130 ~Transaction(); 130 ~Transaction();
131 bool commit(const std::function<void(const DataStore::Error &error)> &errorHandler = std::function<void(const DataStore::Error &error)>()); 131 bool commit(const std::function<void(const DataStore::Error &error)> &errorHandler = {});
132 void abort(); 132 void abort();
133 133
134 QList<QByteArray> getDatabaseNames() const; 134 QList<QByteArray> getDatabaseNames() const;
135 bool validateNamedDatabases(); 135 bool validateNamedDatabases();
136 136
137 NamedDatabase openDatabase(const QByteArray &name = QByteArray("default"), 137 NamedDatabase openDatabase(const QByteArray &name = {"default"},
138 const std::function<void(const DataStore::Error &error)> &errorHandler = std::function<void(const DataStore::Error &error)>(), bool allowDuplicates = false) const; 138 const std::function<void(const DataStore::Error &error)> &errorHandler = {}, bool allowDuplicates = false) const;
139 139
140 Transaction(Transaction &&other); 140 Transaction(Transaction &&other);
141 Transaction &operator=(Transaction &&other); 141 Transaction &operator=(Transaction &&other);
@@ -189,6 +189,9 @@ public:
189 static QByteArray getTypeFromRevision(const Transaction &, qint64 revision); 189 static QByteArray getTypeFromRevision(const Transaction &, qint64 revision);
190 static void recordRevision(Transaction &, qint64 revision, const QByteArray &uid, const QByteArray &type); 190 static void recordRevision(Transaction &, qint64 revision, const QByteArray &uid, const QByteArray &type);
191 static void removeRevision(Transaction &, qint64 revision); 191 static void removeRevision(Transaction &, qint64 revision);
192 static void recordUid(DataStore::Transaction &transaction, const QByteArray &uid);
193 static void removeUid(DataStore::Transaction &transaction, const QByteArray &uid);
194 static void getUids(const Transaction &, const std::function<void(const QByteArray &uid)> &);
192 195
193 bool exists() const; 196 bool exists() const;
194 197
diff --git a/common/storage/entitystore.cpp b/common/storage/entitystore.cpp
index 909f1b2..b7309ab 100644
--- a/common/storage/entitystore.cpp
+++ b/common/storage/entitystore.cpp
@@ -31,8 +31,7 @@
31#include "bufferutils.h" 31#include "bufferutils.h"
32#include "entity_generated.h" 32#include "entity_generated.h"
33#include "applicationdomaintype_p.h" 33#include "applicationdomaintype_p.h"
34 34#include "typeimplementations.h"
35#include "domaintypes.h"
36 35
37using namespace Sink; 36using namespace Sink;
38using namespace Sink::Storage; 37using namespace Sink::Storage;
@@ -168,19 +167,15 @@ void EntityStore::copyBlobs(ApplicationDomain::ApplicationDomainType &entity, qi
168 } 167 }
169} 168}
170 169
171bool EntityStore::add(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &entity_, bool replayToSource, const PreprocessCreation &preprocess) 170bool EntityStore::add(const QByteArray &type, ApplicationDomain::ApplicationDomainType entity, bool replayToSource)
172{ 171{
173 if (entity_.identifier().isEmpty()) { 172 if (entity.identifier().isEmpty()) {
174 SinkWarningCtx(d->logCtx) << "Can't write entity with an empty identifier"; 173 SinkWarningCtx(d->logCtx) << "Can't write entity with an empty identifier";
175 return false; 174 return false;
176 } 175 }
177 176
178 auto entity = *ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<ApplicationDomain::ApplicationDomainType>(entity_, entity_.availableProperties());
179 entity.setChangedProperties(entity.availableProperties().toSet());
180
181 SinkTraceCtx(d->logCtx) << "New entity " << entity; 177 SinkTraceCtx(d->logCtx) << "New entity " << entity;
182 178
183 preprocess(entity);
184 d->typeIndex(type).add(entity.identifier(), entity, d->transaction); 179 d->typeIndex(type).add(entity.identifier(), entity, d->transaction);
185 180
186 //The maxRevision may have changed meanwhile if the entity created sub-entities 181 //The maxRevision may have changed meanwhile if the entity created sub-entities
@@ -205,26 +200,20 @@ bool EntityStore::add(const QByteArray &type, const ApplicationDomain::Applicati
205 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to write entity" << entity.identifier() << newRevision; }); 200 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to write entity" << entity.identifier() << newRevision; });
206 DataStore::setMaxRevision(d->transaction, newRevision); 201 DataStore::setMaxRevision(d->transaction, newRevision);
207 DataStore::recordRevision(d->transaction, newRevision, entity.identifier(), type); 202 DataStore::recordRevision(d->transaction, newRevision, entity.identifier(), type);
203 DataStore::recordUid(d->transaction, entity.identifier());
208 SinkTraceCtx(d->logCtx) << "Wrote entity: " << entity.identifier() << type << newRevision; 204 SinkTraceCtx(d->logCtx) << "Wrote entity: " << entity.identifier() << type << newRevision;
209 return true; 205 return true;
210} 206}
211 207
212bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &diff, const QByteArrayList &deletions, bool replayToSource, const PreprocessModification &preprocess) 208ApplicationDomain::ApplicationDomainType EntityStore::applyDiff(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &current, const ApplicationDomain::ApplicationDomainType &diff, const QByteArrayList &deletions) const
213{ 209{
214 auto changeset = diff.changedProperties();
215 const auto current = readLatest(type, diff.identifier());
216 if (current.identifier().isEmpty()) {
217 SinkWarningCtx(d->logCtx) << "Failed to read current version: " << diff.identifier();
218 return false;
219 }
220
221 auto newEntity = *ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<ApplicationDomain::ApplicationDomainType>(current, current.availableProperties()); 210 auto newEntity = *ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<ApplicationDomain::ApplicationDomainType>(current, current.availableProperties());
222 211
223 SinkTraceCtx(d->logCtx) << "Modified entity: " << newEntity; 212 SinkTraceCtx(d->logCtx) << "Modified entity: " << newEntity;
224 213
225 // Apply diff 214 // Apply diff
226 //SinkTrace() << "Applying changed properties: " << changeset; 215 //SinkTrace() << "Applying changed properties: " << changeset;
227 for (const auto &property : changeset) { 216 for (const auto &property : diff.changedProperties()) {
228 const auto value = diff.getProperty(property); 217 const auto value = diff.getProperty(property);
229 if (value.isValid()) { 218 if (value.isValid()) {
230 //SinkTrace() << "Setting property: " << property; 219 //SinkTrace() << "Setting property: " << property;
@@ -237,8 +226,25 @@ bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::Applic
237 //SinkTrace() << "Removing property: " << property; 226 //SinkTrace() << "Removing property: " << property;
238 newEntity.setProperty(property, QVariant()); 227 newEntity.setProperty(property, QVariant());
239 } 228 }
229 return newEntity;
230}
231
232bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &diff, const QByteArrayList &deletions, bool replayToSource)
233{
234 const auto current = readLatest(type, diff.identifier());
235 if (current.identifier().isEmpty()) {
236 SinkWarningCtx(d->logCtx) << "Failed to read current version: " << diff.identifier();
237 return false;
238 }
239
240 auto newEntity = applyDiff(type, current, diff, deletions);
241 return modify(type, current, newEntity, replayToSource);
242}
243
244bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &current, ApplicationDomain::ApplicationDomainType newEntity, bool replayToSource)
245{
246 SinkTraceCtx(d->logCtx) << "Modified entity: " << newEntity;
240 247
241 preprocess(current, newEntity);
242 d->typeIndex(type).remove(current.identifier(), current, d->transaction); 248 d->typeIndex(type).remove(current.identifier(), current, d->transaction);
243 d->typeIndex(type).add(newEntity.identifier(), newEntity, d->transaction); 249 d->typeIndex(type).add(newEntity.identifier(), newEntity, d->transaction);
244 250
@@ -250,7 +256,7 @@ bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::Applic
250 flatbuffers::FlatBufferBuilder metadataFbb; 256 flatbuffers::FlatBufferBuilder metadataFbb;
251 { 257 {
252 //We add availableProperties to account for the properties that have been changed by the preprocessors 258 //We add availableProperties to account for the properties that have been changed by the preprocessors
253 auto modifiedProperties = BufferUtils::toVector(metadataFbb, changeset + newEntity.changedProperties()); 259 auto modifiedProperties = BufferUtils::toVector(metadataFbb, newEntity.changedProperties());
254 auto metadataBuilder = MetadataBuilder(metadataFbb); 260 auto metadataBuilder = MetadataBuilder(metadataFbb);
255 metadataBuilder.add_revision(newRevision); 261 metadataBuilder.add_revision(newRevision);
256 metadataBuilder.add_operation(Operation_Modification); 262 metadataBuilder.add_operation(Operation_Modification);
@@ -259,7 +265,7 @@ bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::Applic
259 auto metadataBuffer = metadataBuilder.Finish(); 265 auto metadataBuffer = metadataBuilder.Finish();
260 FinishMetadataBuffer(metadataFbb, metadataBuffer); 266 FinishMetadataBuffer(metadataFbb, metadataBuffer);
261 } 267 }
262 SinkTraceCtx(d->logCtx) << "Changed properties: " << changeset + newEntity.changedProperties(); 268 SinkTraceCtx(d->logCtx) << "Changed properties: " << newEntity.changedProperties();
263 269
264 newEntity.setChangedProperties(newEntity.availableProperties().toSet()); 270 newEntity.setChangedProperties(newEntity.availableProperties().toSet());
265 271
@@ -275,36 +281,14 @@ bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::Applic
275 return true; 281 return true;
276} 282}
277 283
278bool EntityStore::remove(const QByteArray &type, const QByteArray &uid, bool replayToSource, const PreprocessRemoval &preprocess) 284bool EntityStore::remove(const QByteArray &type, const Sink::ApplicationDomain::ApplicationDomainType &current, bool replayToSource)
279{ 285{
280 bool found = false; 286 const auto uid = current.identifier();
281 bool alreadyRemoved = false; 287 if (!exists(type, uid)) {
282 DataStore::mainDatabase(d->transaction, type)
283 .findLatest(uid,
284 [&found, &alreadyRemoved](const QByteArray &key, const QByteArray &data) -> bool {
285 auto entity = GetEntity(data.data());
286 if (entity && entity->metadata()) {
287 auto metadata = GetMetadata(entity->metadata()->Data());
288 found = true;
289 if (metadata->operation() == Operation_Removal) {
290 alreadyRemoved = true;
291 }
292 }
293 return false;
294 },
295 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to read old revision from storage: " << error.message; });
296
297 if (!found) {
298 SinkWarningCtx(d->logCtx) << "Remove: Failed to find entity " << uid;
299 return false;
300 }
301 if (alreadyRemoved) {
302 SinkWarningCtx(d->logCtx) << "Remove: Entity is already removed " << uid; 288 SinkWarningCtx(d->logCtx) << "Remove: Entity is already removed " << uid;
303 return false; 289 return false;
304 } 290 }
305 291
306 const auto current = readLatest(type, uid);
307 preprocess(current);
308 d->typeIndex(type).remove(current.identifier(), current, d->transaction); 292 d->typeIndex(type).remove(current.identifier(), current, d->transaction);
309 293
310 SinkTraceCtx(d->logCtx) << "Removed entity " << current; 294 SinkTraceCtx(d->logCtx) << "Removed entity " << current;
@@ -328,6 +312,7 @@ bool EntityStore::remove(const QByteArray &type, const QByteArray &uid, bool rep
328 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to write entity" << uid << newRevision; }); 312 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to write entity" << uid << newRevision; });
329 DataStore::setMaxRevision(d->transaction, newRevision); 313 DataStore::setMaxRevision(d->transaction, newRevision);
330 DataStore::recordRevision(d->transaction, newRevision, uid, type); 314 DataStore::recordRevision(d->transaction, newRevision, uid, type);
315 DataStore::removeUid(d->transaction, uid);
331 return true; 316 return true;
332} 317}
333 318
@@ -521,15 +506,9 @@ ApplicationDomain::ApplicationDomainType EntityStore::readEntity(const QByteArra
521 506
522void EntityStore::readAll(const QByteArray &type, const std::function<void(const ApplicationDomain::ApplicationDomainType &entity)> &callback) 507void EntityStore::readAll(const QByteArray &type, const std::function<void(const ApplicationDomain::ApplicationDomainType &entity)> &callback)
523{ 508{
524 auto db = DataStore::mainDatabase(d->getTransaction(), type); 509 readAllUids(type, [&] (const QByteArray &uid) {
525 db.scan("", 510 readLatest(type, uid, callback);
526 [=](const QByteArray &key, const QByteArray &value) -> bool { 511 });
527 auto uid = DataStore::uidFromKey(key);
528 auto buffer = Sink::EntityBuffer{value.data(), value.size()};
529 callback(d->createApplicationDomainType(type, uid, DataStore::maxRevision(d->getTransaction()), buffer));
530 return true;
531 },
532 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Error during query: " << error.message; });
533} 512}
534 513
535void EntityStore::readRevisions(qint64 baseRevision, const QByteArray &expectedType, const std::function<void(const QByteArray &key)> &callback) 514void EntityStore::readRevisions(qint64 baseRevision, const QByteArray &expectedType, const std::function<void(const QByteArray &key)> &callback)
@@ -588,15 +567,7 @@ ApplicationDomain::ApplicationDomainType EntityStore::readPrevious(const QByteAr
588 567
589void EntityStore::readAllUids(const QByteArray &type, const std::function<void(const QByteArray &uid)> callback) 568void EntityStore::readAllUids(const QByteArray &type, const std::function<void(const QByteArray &uid)> callback)
590{ 569{
591 //TODO use uid index instead 570 DataStore::getUids(d->getTransaction(), callback);
592 //FIXME we currently report each uid for every revision with the same uid
593 auto db = DataStore::mainDatabase(d->getTransaction(), type);
594 db.scan("",
595 [callback](const QByteArray &key, const QByteArray &) -> bool {
596 callback(Sink::Storage::DataStore::uidFromKey(key));
597 return true;
598 },
599 [&](const Sink::Storage::DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to read current value from storage: " << error.message; });
600} 571}
601 572
602bool EntityStore::contains(const QByteArray &type, const QByteArray &uid) 573bool EntityStore::contains(const QByteArray &type, const QByteArray &uid)
@@ -604,6 +575,36 @@ bool EntityStore::contains(const QByteArray &type, const QByteArray &uid)
604 return DataStore::mainDatabase(d->getTransaction(), type).contains(uid); 575 return DataStore::mainDatabase(d->getTransaction(), type).contains(uid);
605} 576}
606 577
578bool EntityStore::exists(const QByteArray &type, const QByteArray &uid)
579{
580 bool found = false;
581 bool alreadyRemoved = false;
582 DataStore::mainDatabase(d->transaction, type)
583 .findLatest(uid,
584 [&found, &alreadyRemoved](const QByteArray &key, const QByteArray &data) -> bool {
585 auto entity = GetEntity(data.data());
586 if (entity && entity->metadata()) {
587 auto metadata = GetMetadata(entity->metadata()->Data());
588 found = true;
589 if (metadata->operation() == Operation_Removal) {
590 alreadyRemoved = true;
591 }
592 }
593 return false;
594 },
595 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to read old revision from storage: " << error.message; });
596 if (!found) {
597 SinkTraceCtx(d->logCtx) << "Remove: Failed to find entity " << uid;
598 return false;
599 }
600 if (alreadyRemoved) {
601 SinkTraceCtx(d->logCtx) << "Remove: Entity is already removed " << uid;
602 return false;
603 }
604 return true;
605}
606
607
607qint64 EntityStore::maxRevision() 608qint64 EntityStore::maxRevision()
608{ 609{
609 if (!d->exists()) { 610 if (!d->exists()) {
diff --git a/common/storage/entitystore.h b/common/storage/entitystore.h
index 46410cd..00241f2 100644
--- a/common/storage/entitystore.h
+++ b/common/storage/entitystore.h
@@ -38,15 +38,13 @@ public:
38 typedef QSharedPointer<EntityStore> Ptr; 38 typedef QSharedPointer<EntityStore> Ptr;
39 EntityStore(const ResourceContext &resourceContext, const Sink::Log::Context &); 39 EntityStore(const ResourceContext &resourceContext, const Sink::Log::Context &);
40 40
41 typedef std::function<void(const ApplicationDomain::ApplicationDomainType &, ApplicationDomain::ApplicationDomainType &)> PreprocessModification;
42 typedef std::function<void(ApplicationDomain::ApplicationDomainType &)> PreprocessCreation;
43 typedef std::function<void(const ApplicationDomain::ApplicationDomainType &)> PreprocessRemoval;
44
45 //Only the pipeline may call the following functions outside of tests 41 //Only the pipeline may call the following functions outside of tests
46 bool add(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &, bool replayToSource, const PreprocessCreation &); 42 bool add(const QByteArray &type, ApplicationDomain::ApplicationDomainType newEntity, bool replayToSource);
47 bool modify(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &, const QByteArrayList &deletions, bool replayToSource, const PreprocessModification &); 43 bool modify(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &diff, const QByteArrayList &deletions, bool replayToSource);
48 bool remove(const QByteArray &type, const QByteArray &uid, bool replayToSource, const PreprocessRemoval &); 44 bool modify(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &current, ApplicationDomain::ApplicationDomainType newEntity, bool replayToSource);
45 bool remove(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &current, bool replayToSource);
49 bool cleanupRevisions(qint64 revision); 46 bool cleanupRevisions(qint64 revision);
47 ApplicationDomain::ApplicationDomainType applyDiff(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &current, const ApplicationDomain::ApplicationDomainType &diff, const QByteArrayList &deletions) const;
50 48
51 void startTransaction(Sink::Storage::DataStore::AccessMode); 49 void startTransaction(Sink::Storage::DataStore::AccessMode);
52 void commitTransaction(); 50 void commitTransaction();
@@ -105,8 +103,12 @@ public:
105 103
106 void readRevisions(qint64 baseRevision, const QByteArray &type, const std::function<void(const QByteArray &key)> &callback); 104 void readRevisions(qint64 baseRevision, const QByteArray &type, const std::function<void(const QByteArray &key)> &callback);
107 105
106 ///Db contains entity (but may already be marked as removed
108 bool contains(const QByteArray &type, const QByteArray &uid); 107 bool contains(const QByteArray &type, const QByteArray &uid);
109 108
109 ///Db contains entity and entity is not yet removed
110 bool exists(const QByteArray &type, const QByteArray &uid);
111
110 qint64 maxRevision(); 112 qint64 maxRevision();
111 113
112 Sink::Log::Context logContext() const; 114 Sink::Log::Context logContext() const;
diff --git a/common/storage_common.cpp b/common/storage_common.cpp
index d8b1f42..81a38c7 100644
--- a/common/storage_common.cpp
+++ b/common/storage_common.cpp
@@ -28,7 +28,7 @@ SINK_DEBUG_AREA("storage")
28 28
29QDebug& operator<<(QDebug &dbg, const Sink::Storage::DataStore::Error &error) 29QDebug& operator<<(QDebug &dbg, const Sink::Storage::DataStore::Error &error)
30{ 30{
31 dbg << error.message; 31 dbg << error.message << "Code: " << error.code << "Db: " << error.store;
32 return dbg; 32 return dbg;
33} 33}
34 34
@@ -146,6 +146,24 @@ void DataStore::removeRevision(DataStore::Transaction &transaction, qint64 revis
146 transaction.openDatabase("revisionType").remove(QByteArray::number(revision)); 146 transaction.openDatabase("revisionType").remove(QByteArray::number(revision));
147} 147}
148 148
149void DataStore::recordUid(DataStore::Transaction &transaction, const QByteArray &uid)
150{
151 transaction.openDatabase("uids").write(uid, "");
152}
153
154void DataStore::removeUid(DataStore::Transaction &transaction, const QByteArray &uid)
155{
156 transaction.openDatabase("uids").remove(uid);
157}
158
159void DataStore::getUids(const Transaction &transaction, const std::function<void(const QByteArray &uid)> &callback)
160{
161 transaction.openDatabase("uids").scan("", [&] (const QByteArray &key, const QByteArray &) {
162 callback(key);
163 return true;
164 });
165}
166
149bool DataStore::isInternalKey(const char *key) 167bool DataStore::isInternalKey(const char *key)
150{ 168{
151 return key && strncmp(key, s_internalPrefix, s_internalPrefixSize) == 0; 169 return key && strncmp(key, s_internalPrefix, s_internalPrefixSize) == 0;
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp
index ed385ad..08eea37 100644
--- a/common/storage_lmdb.cpp
+++ b/common/storage_lmdb.cpp
@@ -62,6 +62,44 @@ int getErrorCode(int e)
62 return -1; 62 return -1;
63} 63}
64 64
65static QList<QByteArray> getDatabaseNames(MDB_txn *transaction)
66{
67 if (!transaction) {
68 SinkWarning() << "Invalid transaction";
69 return QList<QByteArray>();
70 }
71 int rc;
72 QList<QByteArray> list;
73 MDB_dbi dbi;
74 if ((rc = mdb_dbi_open(transaction, nullptr, 0, &dbi) == 0)) {
75 MDB_val key;
76 MDB_val data;
77 MDB_cursor *cursor;
78
79 mdb_cursor_open(transaction, dbi, &cursor);
80 if ((rc = mdb_cursor_get(cursor, &key, &data, MDB_FIRST)) == 0) {
81 list << QByteArray::fromRawData((char *)key.mv_data, key.mv_size);
82 while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
83 list << QByteArray::fromRawData((char *)key.mv_data, key.mv_size);
84 }
85 } else {
86 //Normal if we don't have any databases yet
87 if (rc == MDB_NOTFOUND) {
88 rc = 0;
89 }
90 if (rc) {
91 SinkWarning() << "Failed to get a value" << rc;
92 }
93 }
94 mdb_cursor_close(cursor);
95 } else {
96 SinkWarning() << "Failed to open db" << rc << QByteArray(mdb_strerror(rc));
97 }
98 return list;
99
100}
101
102
65class DataStore::NamedDatabase::Private 103class DataStore::NamedDatabase::Private
66{ 104{
67public: 105public:
@@ -93,6 +131,18 @@ public:
93 const auto dbiName = name + db; 131 const auto dbiName = name + db;
94 if (sDbis.contains(dbiName)) { 132 if (sDbis.contains(dbiName)) {
95 dbi = sDbis.value(dbiName); 133 dbi = sDbis.value(dbiName);
134 //sDbis can contain dbi's that are not available to this transaction.
135 //We use mdb_dbi_flags to check if the dbi is valid for this transaction.
136 uint f;
137 if (mdb_dbi_flags(transaction, dbi, &f) == EINVAL) {
138 //In readonly mode we can just ignore this. In read-write we would have tried to concurrently create a db.
139 if (!readOnly) {
140 SinkWarning() << "Tried to create database in second transaction: " << dbiName;
141 }
142 dbi = 0;
143 transaction = 0;
144 return false;
145 }
96 } else { 146 } else {
97 MDB_dbi flagtableDbi; 147 MDB_dbi flagtableDbi;
98 if (const int rc = mdb_dbi_open(transaction, "__flagtable", readOnly ? 0 : MDB_CREATE, &flagtableDbi)) { 148 if (const int rc = mdb_dbi_open(transaction, "__flagtable", readOnly ? 0 : MDB_CREATE, &flagtableDbi)) {
@@ -279,7 +329,8 @@ int DataStore::NamedDatabase::scan(const QByteArray &k, const std::function<bool
279 329
280 rc = mdb_cursor_open(d->transaction, d->dbi, &cursor); 330 rc = mdb_cursor_open(d->transaction, d->dbi, &cursor);
281 if (rc) { 331 if (rc) {
282 Error error(d->name.toLatin1() + d->db, getErrorCode(rc), QByteArray("Error during mdb_cursor open: ") + QByteArray(mdb_strerror(rc))); 332 //Invalid arguments can mean that the transaction doesn't contain the db dbi
333 Error error(d->name.toLatin1() + d->db, getErrorCode(rc), QByteArray("Error during mdb_cursor_open: ") + QByteArray(mdb_strerror(rc)) + ". Key: " + k);
283 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error); 334 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
284 return 0; 335 return 0;
285 } 336 }
@@ -447,7 +498,6 @@ public:
447 498
448 MDB_env *env; 499 MDB_env *env;
449 MDB_txn *transaction; 500 MDB_txn *transaction;
450 MDB_dbi dbi;
451 bool requestedRead; 501 bool requestedRead;
452 std::function<void(const DataStore::Error &error)> defaultErrorHandler; 502 std::function<void(const DataStore::Error &error)> defaultErrorHandler;
453 QString name; 503 QString name;
@@ -578,8 +628,7 @@ static bool ensureCorrectDb(DataStore::NamedDatabase &database, const QByteArray
578 } 628 }
579 return false; 629 return false;
580 }, 630 },
581 [](const DataStore::Error &error) -> bool{ 631 [&](const DataStore::Error &) {
582 return false;
583 }, false); 632 }, false);
584 //This is the first time we open this database in a write transaction, write the db name 633 //This is the first time we open this database in a write transaction, write the db name
585 if (!count) { 634 if (!count) {
@@ -637,35 +686,8 @@ QList<QByteArray> DataStore::Transaction::getDatabaseNames() const
637 SinkWarning() << "Invalid transaction"; 686 SinkWarning() << "Invalid transaction";
638 return QList<QByteArray>(); 687 return QList<QByteArray>();
639 } 688 }
689 return Sink::Storage::getDatabaseNames(d->transaction);
640 690
641 int rc;
642 QList<QByteArray> list;
643 Q_ASSERT(d->transaction);
644 if ((rc = mdb_dbi_open(d->transaction, nullptr, 0, &d->dbi) == 0)) {
645 MDB_val key;
646 MDB_val data;
647 MDB_cursor *cursor;
648
649 mdb_cursor_open(d->transaction, d->dbi, &cursor);
650 if ((rc = mdb_cursor_get(cursor, &key, &data, MDB_FIRST)) == 0) {
651 list << QByteArray::fromRawData((char *)key.mv_data, key.mv_size);
652 while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
653 list << QByteArray::fromRawData((char *)key.mv_data, key.mv_size);
654 }
655 } else {
656 //Normal if we don't have any databases yet
657 if (rc == MDB_NOTFOUND) {
658 rc = 0;
659 }
660 if (rc) {
661 SinkWarning() << "Failed to get a value" << rc;
662 }
663 }
664 mdb_cursor_close(cursor);
665 } else {
666 SinkWarning() << "Failed to open db" << rc << QByteArray(mdb_strerror(rc));
667 }
668 return list;
669} 691}
670 692
671 693
diff --git a/common/store.cpp b/common/store.cpp
index 1c8620b..d266098 100644
--- a/common/store.cpp
+++ b/common/store.cpp
@@ -92,6 +92,7 @@ QPair<typename AggregatingResultEmitter<typename DomainType::Ptr>::Ptr, typenam
92 auto facade = FacadeFactory::instance().getFacade<ApplicationDomain::SinkResource>(); 92 auto facade = FacadeFactory::instance().getFacade<ApplicationDomain::SinkResource>();
93 Q_ASSERT(facade); 93 Q_ASSERT(facade);
94 Sink::Query resourceQuery; 94 Sink::Query resourceQuery;
95 resourceQuery.request<ApplicationDomain::SinkResource::Capabilities>();
95 if (query.liveQuery()) { 96 if (query.liveQuery()) {
96 SinkTraceCtx(ctx) << "Listening for new resources."; 97 SinkTraceCtx(ctx) << "Listening for new resources.";
97 resourceQuery.setFlags(Query::LiveQuery); 98 resourceQuery.setFlags(Query::LiveQuery);
@@ -103,6 +104,7 @@ QPair<typename AggregatingResultEmitter<typename DomainType::Ptr>::Ptr, typenam
103 resourceFilter.propertyFilter.insert(ApplicationDomain::SinkResource::Capabilities::name, Query::Comparator{ApplicationDomain::getTypeName<DomainType>(), Query::Comparator::Contains}); 104 resourceFilter.propertyFilter.insert(ApplicationDomain::SinkResource::Capabilities::name, Query::Comparator{ApplicationDomain::getTypeName<DomainType>(), Query::Comparator::Contains});
104 } 105 }
105 resourceQuery.setFilter(resourceFilter); 106 resourceQuery.setFilter(resourceFilter);
107 resourceQuery.requestedProperties << resourceFilter.propertyFilter.keys();
106 108
107 auto result = facade->load(resourceQuery, resourceCtx); 109 auto result = facade->load(resourceQuery, resourceCtx);
108 auto emitter = result.second; 110 auto emitter = result.second;
@@ -249,7 +251,7 @@ KAsync::Job<void> Store::remove(const Sink::Query &query)
249KAsync::Job<void> Store::removeDataFromDisk(const QByteArray &identifier) 251KAsync::Job<void> Store::removeDataFromDisk(const QByteArray &identifier)
250{ 252{
251 // All databases are going to become invalid, nuke the environments 253 // All databases are going to become invalid, nuke the environments
252 // TODO: all clients should react to a notification the resource 254 // TODO: all clients should react to a notification from the resource
253 Sink::Storage::DataStore::clearEnv(); 255 Sink::Storage::DataStore::clearEnv();
254 SinkTrace() << "Remove data from disk " << identifier; 256 SinkTrace() << "Remove data from disk " << identifier;
255 auto time = QSharedPointer<QTime>::create(); 257 auto time = QSharedPointer<QTime>::create();
@@ -277,18 +279,18 @@ KAsync::Job<void> Store::removeDataFromDisk(const QByteArray &identifier)
277 279
278static KAsync::Job<void> synchronize(const QByteArray &resource, const Sink::SyncScope &scope) 280static KAsync::Job<void> synchronize(const QByteArray &resource, const Sink::SyncScope &scope)
279{ 281{
280 SinkLog() << "Synchronizing " << resource; 282 SinkLog() << "Synchronizing " << resource << scope;
281 auto resourceAccess = ResourceAccessFactory::instance().getAccess(resource, ResourceConfig::getResourceType(resource)); 283 auto resourceAccess = ResourceAccessFactory::instance().getAccess(resource, ResourceConfig::getResourceType(resource));
282 return resourceAccess->synchronizeResource(scope) 284 return resourceAccess->synchronizeResource(scope)
283 .addToContext(resourceAccess) 285 .addToContext(resourceAccess)
284 .then<void>([](const KAsync::Error &error) { 286 .then([=](const KAsync::Error &error) {
285 if (error) { 287 if (error) {
286 SinkWarning() << "Error during sync."; 288 SinkWarning() << "Error during sync.";
287 return KAsync::error<void>(error); 289 return KAsync::error(error);
288 } 290 }
289 SinkTrace() << "synced."; 291 SinkTrace() << "Synchronization of resource " << resource << " complete.";
290 return KAsync::null<void>(); 292 return KAsync::null();
291 }); 293 });
292} 294}
293 295
294KAsync::Job<void> Store::synchronize(const Sink::Query &query) 296KAsync::Job<void> Store::synchronize(const Sink::Query &query)
diff --git a/common/store.h b/common/store.h
index 86e4d20..fae76e5 100644
--- a/common/store.h
+++ b/common/store.h
@@ -48,11 +48,15 @@ QString SINK_EXPORT storageLocation();
48 */ 48 */
49QString SINK_EXPORT getTemporaryFilePath(); 49QString SINK_EXPORT getTemporaryFilePath();
50 50
51// Must be the same as in ModelResult
51enum Roles 52enum Roles
52{ 53{
53 DomainObjectRole = Qt::UserRole + 1, // Must be the same as in ModelResult 54 DomainObjectRole = Qt::UserRole + 1,
54 ChildrenFetchedRole, 55 ChildrenFetchedRole,
55 DomainObjectBaseRole 56 DomainObjectBaseRole,
57 StatusRole, //ApplicationDomain::SyncStatus
58 WarningRole, //ApplicationDomain::Warning, only if status == warning || status == error
59 ProgressRole //ApplicationDomain::Progress
56}; 60};
57 61
58/** 62/**
diff --git a/common/synchronizer.cpp b/common/synchronizer.cpp
index b147615..3e7bd30 100644
--- a/common/synchronizer.cpp
+++ b/common/synchronizer.cpp
@@ -33,13 +33,14 @@
33using namespace Sink; 33using namespace Sink;
34 34
35Synchronizer::Synchronizer(const Sink::ResourceContext &context) 35Synchronizer::Synchronizer(const Sink::ResourceContext &context)
36 : ChangeReplay(context), 36 : ChangeReplay(context, {"synchronizer"}),
37 mLogCtx{"synchronizer"}, 37 mLogCtx{"synchronizer"},
38 mResourceContext(context), 38 mResourceContext(context),
39 mEntityStore(Storage::EntityStore::Ptr::create(mResourceContext, mLogCtx)), 39 mEntityStore(Storage::EntityStore::Ptr::create(mResourceContext, mLogCtx)),
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 SinkTraceCtx(mLogCtx) << "Starting synchronizer: " << mResourceContext.resourceType << mResourceContext.instanceId(); 44 SinkTraceCtx(mLogCtx) << "Starting synchronizer: " << mResourceContext.resourceType << mResourceContext.instanceId();
44} 45}
45 46
@@ -252,15 +253,21 @@ void Synchronizer::modify(const DomainType &entity, const QByteArray &newResourc
252 253
253QList<Synchronizer::SyncRequest> Synchronizer::getSyncRequests(const Sink::QueryBase &query) 254QList<Synchronizer::SyncRequest> Synchronizer::getSyncRequests(const Sink::QueryBase &query)
254{ 255{
255 QList<Synchronizer::SyncRequest> list; 256 return QList<Synchronizer::SyncRequest>() << Synchronizer::SyncRequest{query, "sync"};
256 list << Synchronizer::SyncRequest{query, "sync"}; 257}
257 return list; 258
259void Synchronizer::mergeIntoQueue(const Synchronizer::SyncRequest &request, QList<Synchronizer::SyncRequest> &queue)
260{
261 mSyncRequestQueue << request;
258} 262}
259 263
260void Synchronizer::synchronize(const Sink::QueryBase &query) 264void Synchronizer::synchronize(const Sink::QueryBase &query)
261{ 265{
262 SinkTraceCtx(mLogCtx) << "Synchronizing"; 266 SinkTraceCtx(mLogCtx) << "Synchronizing";
263 mSyncRequestQueue << getSyncRequests(query); 267 auto newRequests = getSyncRequests(query);
268 for (const auto &request: newRequests) {
269 mergeIntoQueue(request, mSyncRequestQueue);
270 }
264 processSyncQueue().exec(); 271 processSyncQueue().exec();
265} 272}
266 273
@@ -286,6 +293,42 @@ void Synchronizer::flushComplete(const QByteArray &flushId)
286 } 293 }
287} 294}
288 295
296void Synchronizer::emitNotification(Notification::NoticationType type, int code, const QString &message, const QByteArray &id, const QByteArrayList &entities)
297{
298 Sink::Notification n;
299 n.id = id;
300 n.type = type;
301 n.message = message;
302 n.code = code;
303 n.entities = entities;
304 emit notify(n);
305}
306
307void Synchronizer::reportProgress(int progress, int total)
308{
309 SinkLogCtx(mLogCtx) << "Progress: " << progress << " out of " << total;
310}
311
312void Synchronizer::setStatusFromResult(const KAsync::Error &error, const QString &s, const QByteArray &requestId)
313{
314 if (error) {
315 if (error.errorCode == ApplicationDomain::ConnectionError) {
316 //Couldn't connect, so we assume we don't have a network connection.
317 setStatus(ApplicationDomain::OfflineStatus, s, requestId);
318 } else if (error.errorCode == ApplicationDomain::ConfigurationError) {
319 //There is an error with the configuration.
320 setStatus(ApplicationDomain::ErrorStatus, s, requestId);
321 } else if (error.errorCode == ApplicationDomain::LoginError) {
322 //If we failed to login altough we could connect that indicates a problem with our setup.
323 setStatus(ApplicationDomain::ErrorStatus, s, requestId);
324 }
325 //We don't know what kind of error this was, so we assume it's transient and don't change ou status.
326 } else {
327 //An operation against the server worked, so we're probably online.
328 setStatus(ApplicationDomain::ConnectedStatus, s, requestId);
329 }
330}
331
289KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request) 332KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request)
290{ 333{
291 if (request.options & SyncRequest::RequestFlush) { 334 if (request.options & SyncRequest::RequestFlush) {
@@ -310,35 +353,21 @@ KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request)
310 }); 353 });
311 } else if (request.requestType == Synchronizer::SyncRequest::Synchronization) { 354 } else if (request.requestType == Synchronizer::SyncRequest::Synchronization) {
312 return KAsync::start([this, request] { 355 return KAsync::start([this, request] {
313 Sink::Notification n;
314 n.id = request.requestId;
315 n.type = Notification::Status;
316 n.message = "Synchronization has started.";
317 n.code = ApplicationDomain::BusyStatus;
318 emit notify(n);
319 SinkLogCtx(mLogCtx) << "Synchronizing: " << request.query; 356 SinkLogCtx(mLogCtx) << "Synchronizing: " << request.query;
357 emitNotification(Notification::Info, ApplicationDomain::SyncInProgress, {}, {}, request.applicableEntities);
320 }).then(synchronizeWithSource(request.query)).then([this] { 358 }).then(synchronizeWithSource(request.query)).then([this] {
321 //Commit after every request, so implementations only have to commit more if they add a lot of data. 359 //Commit after every request, so implementations only have to commit more if they add a lot of data.
322 commit(); 360 commit();
323 }).then<void>([this, request](const KAsync::Error &error) { 361 }).then<void>([this, request](const KAsync::Error &error) {
362 setStatusFromResult(error, "Synchronization has ended.", request.requestId);
324 if (error) { 363 if (error) {
325 //Emit notification with error 364 //Emit notification with error
326 SinkWarningCtx(mLogCtx) << "Synchronization failed: " << error.errorMessage; 365 SinkWarningCtx(mLogCtx) << "Synchronization failed: " << error;
327 Sink::Notification n; 366 emitNotification(Notification::Warning, ApplicationDomain::SyncError, {}, {}, request.applicableEntities);
328 n.id = request.requestId;
329 n.type = Notification::Status;
330 n.message = "Synchronization has ended.";
331 n.code = ApplicationDomain::ErrorStatus;
332 emit notify(n);
333 return KAsync::error(error); 367 return KAsync::error(error);
334 } else { 368 } else {
335 SinkLogCtx(mLogCtx) << "Done Synchronizing"; 369 SinkLogCtx(mLogCtx) << "Done Synchronizing";
336 Sink::Notification n; 370 emitNotification(Notification::Info, ApplicationDomain::SyncSuccess, {}, {}, request.applicableEntities);
337 n.id = request.requestId;
338 n.type = Notification::Status;
339 n.message = "Synchronization has ended.";
340 n.code = ApplicationDomain::ConnectedStatus;
341 emit notify(n);
342 return KAsync::null(); 371 return KAsync::null();
343 } 372 }
344 }); 373 });
@@ -347,11 +376,8 @@ KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request)
347 Q_ASSERT(!request.requestId.isEmpty()); 376 Q_ASSERT(!request.requestId.isEmpty());
348 //FIXME it looks like this is emitted before the replay actually finishes 377 //FIXME it looks like this is emitted before the replay actually finishes
349 if (request.flushType == Flush::FlushReplayQueue) { 378 if (request.flushType == Flush::FlushReplayQueue) {
350 SinkTraceCtx(mLogCtx) << "Emitting flush completion."; 379 SinkTraceCtx(mLogCtx) << "Emitting flush completion: " << request.requestId;
351 Sink::Notification n; 380 emitNotification(Notification::FlushCompletion, 0, "", request.requestId);
352 n.type = Sink::Notification::FlushCompletion;
353 n.id = request.requestId;
354 emit notify(n);
355 } else { 381 } else {
356 flatbuffers::FlatBufferBuilder fbb; 382 flatbuffers::FlatBufferBuilder fbb;
357 auto flushId = fbb.CreateString(request.requestId.toStdString()); 383 auto flushId = fbb.CreateString(request.requestId.toStdString());
@@ -361,7 +387,24 @@ KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request)
361 } 387 }
362 }); 388 });
363 } else if (request.requestType == Synchronizer::SyncRequest::ChangeReplay) { 389 } else if (request.requestType == Synchronizer::SyncRequest::ChangeReplay) {
364 return replayNextRevision(); 390 if (ChangeReplay::allChangesReplayed()) {
391 return KAsync::null();
392 } else {
393 return KAsync::start([this, request] {
394 SinkLogCtx(mLogCtx) << "Replaying changes.";
395 })
396 .then(replayNextRevision())
397 .then<void>([this, request](const KAsync::Error &error) {
398 setStatusFromResult(error, "Changereplay has ended.", "changereplay");
399 if (error) {
400 SinkWarningCtx(mLogCtx) << "Changereplay failed: " << error.errorMessage;
401 return KAsync::error(error);
402 } else {
403 SinkLogCtx(mLogCtx) << "Done replaying changes";
404 return KAsync::null();
405 }
406 });
407 }
365 } else { 408 } else {
366 SinkWarningCtx(mLogCtx) << "Unknown request type: " << request.requestType; 409 SinkWarningCtx(mLogCtx) << "Unknown request type: " << request.requestType;
367 return KAsync::error(KAsync::Error{"Unknown request type."}); 410 return KAsync::error(KAsync::Error{"Unknown request type."});
@@ -369,6 +412,34 @@ KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request)
369 412
370} 413}
371 414
415void Synchronizer::setStatus(ApplicationDomain::Status state, const QString &reason, const QByteArray requestId)
416{
417 if (state != mCurrentState.top()) {
418 if (mCurrentState.top() == ApplicationDomain::BusyStatus) {
419 mCurrentState.pop();
420 }
421 mCurrentState.push(state);
422 emitNotification(Notification::Status, state, reason, requestId);
423 }
424}
425
426void Synchronizer::resetStatus(const QByteArray requestId)
427{
428 mCurrentState.pop();
429 emitNotification(Notification::Status, mCurrentState.top(), {}, requestId);
430}
431
432void Synchronizer::setBusy(bool busy, const QString &reason, const QByteArray requestId)
433{
434 if (busy) {
435 setStatus(ApplicationDomain::BusyStatus, reason, requestId);
436 } else {
437 if (mCurrentState.top() == ApplicationDomain::BusyStatus) {
438 resetStatus(requestId);
439 }
440 }
441}
442
372KAsync::Job<void> Synchronizer::processSyncQueue() 443KAsync::Job<void> Synchronizer::processSyncQueue()
373{ 444{
374 if (mSyncRequestQueue.isEmpty()) { 445 if (mSyncRequestQueue.isEmpty()) {
@@ -387,14 +458,20 @@ KAsync::Job<void> Synchronizer::processSyncQueue()
387 } 458 }
388 459
389 const auto request = mSyncRequestQueue.takeFirst(); 460 const auto request = mSyncRequestQueue.takeFirst();
390 return KAsync::start([this] { 461 return KAsync::start([=] {
391 mMessageQueue->startTransaction(); 462 mMessageQueue->startTransaction();
392 mEntityStore->startTransaction(Sink::Storage::DataStore::ReadOnly); 463 mEntityStore->startTransaction(Sink::Storage::DataStore::ReadOnly);
393 mSyncInProgress = true; 464 mSyncInProgress = true;
465 if (request.requestType == Synchronizer::SyncRequest::Synchronization) {
466 setBusy(true, "Synchronization has started.", request.requestId);
467 } else if (request.requestType == Synchronizer::SyncRequest::ChangeReplay) {
468 setBusy(true, "ChangeReplay has started.", "changereplay");
469 }
394 }) 470 })
395 .then(processRequest(request)) 471 .then(processRequest(request))
396 .then<void>([this](const KAsync::Error &error) { 472 .then<void>([this, request](const KAsync::Error &error) {
397 SinkTraceCtx(mLogCtx) << "Sync request processed"; 473 SinkTraceCtx(mLogCtx) << "Sync request processed";
474 setBusy(false, {}, request.requestId);
398 mEntityStore->abortTransaction(); 475 mEntityStore->abortTransaction();
399 mSyncTransaction.abort(); 476 mSyncTransaction.abort();
400 mMessageQueue->commit(); 477 mMessageQueue->commit();
@@ -404,8 +481,8 @@ KAsync::Job<void> Synchronizer::processSyncQueue()
404 emit changesReplayed(); 481 emit changesReplayed();
405 } 482 }
406 if (error) { 483 if (error) {
407 SinkWarningCtx(mLogCtx) << "Error during sync: " << error.errorMessage; 484 SinkWarningCtx(mLogCtx) << "Error during sync: " << error;
408 return KAsync::error(error); 485 emitNotification(Notification::Error, error.errorCode, error.errorMessage, request.requestId);
409 } 486 }
410 //In case we got more requests meanwhile. 487 //In case we got more requests meanwhile.
411 return processSyncQueue(); 488 return processSyncQueue();
@@ -499,6 +576,12 @@ KAsync::Job<void> Synchronizer::replay(const QByteArray &type, const QByteArray
499 } else if (type == ApplicationDomain::getTypeName<ApplicationDomain::Mail>()) { 576 } else if (type == ApplicationDomain::getTypeName<ApplicationDomain::Mail>()) {
500 auto mail = store().readEntity<ApplicationDomain::Mail>(key); 577 auto mail = store().readEntity<ApplicationDomain::Mail>(key);
501 job = replay(mail, operation, oldRemoteId, modifiedProperties); 578 job = replay(mail, operation, oldRemoteId, modifiedProperties);
579 } else if (type == ApplicationDomain::getTypeName<ApplicationDomain::Contact>()) {
580 auto mail = store().readEntity<ApplicationDomain::Contact>(key);
581 job = replay(mail, operation, oldRemoteId, modifiedProperties);
582 } else if (type == ApplicationDomain::getTypeName<ApplicationDomain::Addressbook>()) {
583 auto mail = store().readEntity<ApplicationDomain::Addressbook>(key);
584 job = replay(mail, operation, oldRemoteId, modifiedProperties);
502 } else { 585 } else {
503 SinkErrorCtx(mLogCtx) << "Replayed unknown type: " << type; 586 SinkErrorCtx(mLogCtx) << "Replayed unknown type: " << type;
504 } 587 }
@@ -506,21 +589,19 @@ KAsync::Job<void> Synchronizer::replay(const QByteArray &type, const QByteArray
506 return job.then([this, operation, type, uid, oldRemoteId](const QByteArray &remoteId) { 589 return job.then([this, operation, type, uid, oldRemoteId](const QByteArray &remoteId) {
507 if (operation == Sink::Operation_Creation) { 590 if (operation == Sink::Operation_Creation) {
508 SinkTraceCtx(mLogCtx) << "Replayed creation with remote id: " << remoteId; 591 SinkTraceCtx(mLogCtx) << "Replayed creation with remote id: " << remoteId;
509 if (remoteId.isEmpty()) { 592 if (!remoteId.isEmpty()) {
510 SinkWarningCtx(mLogCtx) << "Returned an empty remoteId from the creation";
511 } else {
512 syncStore().recordRemoteId(type, uid, remoteId); 593 syncStore().recordRemoteId(type, uid, remoteId);
513 } 594 }
514 } else if (operation == Sink::Operation_Modification) { 595 } else if (operation == Sink::Operation_Modification) {
515 SinkTraceCtx(mLogCtx) << "Replayed modification with remote id: " << remoteId; 596 SinkTraceCtx(mLogCtx) << "Replayed modification with remote id: " << remoteId;
516 if (remoteId.isEmpty()) { 597 if (!remoteId.isEmpty()) {
517 SinkWarningCtx(mLogCtx) << "Returned an empty remoteId from the modification";
518 } else {
519 syncStore().updateRemoteId(type, uid, remoteId); 598 syncStore().updateRemoteId(type, uid, remoteId);
520 } 599 }
521 } else if (operation == Sink::Operation_Removal) { 600 } else if (operation == Sink::Operation_Removal) {
522 SinkTraceCtx(mLogCtx) << "Replayed removal with remote id: " << oldRemoteId; 601 SinkTraceCtx(mLogCtx) << "Replayed removal with remote id: " << oldRemoteId;
523 syncStore().removeRemoteId(type, uid, oldRemoteId); 602 if (!oldRemoteId.isEmpty()) {
603 syncStore().removeRemoteId(type, uid, oldRemoteId);
604 }
524 } else { 605 } else {
525 SinkErrorCtx(mLogCtx) << "Unkown operation" << operation; 606 SinkErrorCtx(mLogCtx) << "Unkown operation" << operation;
526 } 607 }
@@ -539,6 +620,11 @@ KAsync::Job<QByteArray> Synchronizer::replay(const ApplicationDomain::Contact &,
539 return KAsync::null<QByteArray>(); 620 return KAsync::null<QByteArray>();
540} 621}
541 622
623KAsync::Job<QByteArray> Synchronizer::replay(const ApplicationDomain::Addressbook &, Sink::Operation, const QByteArray &, const QList<QByteArray> &)
624{
625 return KAsync::null<QByteArray>();
626}
627
542KAsync::Job<QByteArray> Synchronizer::replay(const ApplicationDomain::Mail &, Sink::Operation, const QByteArray &, const QList<QByteArray> &) 628KAsync::Job<QByteArray> Synchronizer::replay(const ApplicationDomain::Mail &, Sink::Operation, const QByteArray &, const QList<QByteArray> &)
543{ 629{
544 return KAsync::null<QByteArray>(); 630 return KAsync::null<QByteArray>();
diff --git a/common/synchronizer.h b/common/synchronizer.h
index 120a8a5..b1ee122 100644
--- a/common/synchronizer.h
+++ b/common/synchronizer.h
@@ -21,6 +21,7 @@
21 21
22#include "sink_export.h" 22#include "sink_export.h"
23#include <QObject> 23#include <QObject>
24#include <QStack>
24#include <KAsync/Async> 25#include <KAsync/Async>
25#include <domainadaptor.h> 26#include <domainadaptor.h>
26#include <query.h> 27#include <query.h>
@@ -73,9 +74,9 @@ protected:
73protected: 74protected:
74 ///Implement to write back changes to the server 75 ///Implement to write back changes to the server
75 virtual KAsync::Job<QByteArray> replay(const Sink::ApplicationDomain::Contact &, Sink::Operation, const QByteArray &oldRemoteId, const QList<QByteArray> &); 76 virtual KAsync::Job<QByteArray> replay(const Sink::ApplicationDomain::Contact &, Sink::Operation, const QByteArray &oldRemoteId, const QList<QByteArray> &);
77 virtual KAsync::Job<QByteArray> replay(const Sink::ApplicationDomain::Addressbook &, Sink::Operation, const QByteArray &oldRemoteId, const QList<QByteArray> &);
76 virtual KAsync::Job<QByteArray> replay(const Sink::ApplicationDomain::Mail &, Sink::Operation, const QByteArray &oldRemoteId, const QList<QByteArray> &); 78 virtual KAsync::Job<QByteArray> replay(const Sink::ApplicationDomain::Mail &, Sink::Operation, const QByteArray &oldRemoteId, const QList<QByteArray> &);
77 virtual KAsync::Job<QByteArray> replay(const Sink::ApplicationDomain::Folder &, Sink::Operation, const QByteArray &oldRemoteId, const QList<QByteArray> &); 79 virtual KAsync::Job<QByteArray> replay(const Sink::ApplicationDomain::Folder &, Sink::Operation, const QByteArray &oldRemoteId, const QList<QByteArray> &);
78
79protected: 80protected:
80 ///Calls the callback to enqueue the command 81 ///Calls the callback to enqueue the command
81 void enqueueCommand(int commandId, const QByteArray &data); 82 void enqueueCommand(int commandId, const QByteArray &data);
@@ -134,7 +135,8 @@ protected:
134 : requestId(requestId_), 135 : requestId(requestId_),
135 requestType(Synchronization), 136 requestType(Synchronization),
136 options(o), 137 options(o),
137 query(q) 138 query(q),
139 applicableEntities(q.ids())
138 { 140 {
139 } 141 }
140 142
@@ -155,6 +157,7 @@ protected:
155 RequestType requestType; 157 RequestType requestType;
156 RequestOptions options = NoOptions; 158 RequestOptions options = NoOptions;
157 Sink::QueryBase query; 159 Sink::QueryBase query;
160 QByteArrayList applicableEntities;
158 }; 161 };
159 162
160 /** 163 /**
@@ -175,9 +178,28 @@ protected:
175 */ 178 */
176 virtual QList<Synchronizer::SyncRequest> getSyncRequests(const Sink::QueryBase &query); 179 virtual QList<Synchronizer::SyncRequest> getSyncRequests(const Sink::QueryBase &query);
177 180
181 /**
182 * This allows the synchronizer to merge new requests with existing requests in the queue.
183 */
184 virtual void mergeIntoQueue(const Synchronizer::SyncRequest &request, QList<Synchronizer::SyncRequest> &queue);
185
186 void emitNotification(Notification::NoticationType type, int code, const QString &message, const QByteArray &id = QByteArray{}, const QByteArrayList &entiteis = QByteArrayList{});
187
188 /**
189 * Report progress for current task
190 */
191 void reportProgress(int progress, int total);
192
178protected: 193protected:
179 Sink::Log::Context mLogCtx; 194 Sink::Log::Context mLogCtx;
195
180private: 196private:
197 QStack<ApplicationDomain::Status> mCurrentState;
198 void setStatusFromResult(const KAsync::Error &error, const QString &s, const QByteArray &requestId);
199 void setStatus(ApplicationDomain::Status busy, const QString &reason, const QByteArray requestId);
200 void resetStatus(const QByteArray requestId);
201 void setBusy(bool busy, const QString &reason, const QByteArray requestId);
202
181 void modifyIfChanged(Storage::EntityStore &store, const QByteArray &bufferType, const QByteArray &sinkId, const Sink::ApplicationDomain::ApplicationDomainType &entity); 203 void modifyIfChanged(Storage::EntityStore &store, const QByteArray &bufferType, const QByteArray &sinkId, const Sink::ApplicationDomain::ApplicationDomainType &entity);
182 KAsync::Job<void> processRequest(const SyncRequest &request); 204 KAsync::Job<void> processRequest(const SyncRequest &request);
183 KAsync::Job<void> processSyncQueue(); 205 KAsync::Job<void> processSyncQueue();
diff --git a/common/synchronizerstore.cpp b/common/synchronizerstore.cpp
index dea4821..5364094 100644
--- a/common/synchronizerstore.cpp
+++ b/common/synchronizerstore.cpp
@@ -73,7 +73,8 @@ QByteArray SynchronizerStore::resolveLocalId(const QByteArray &bufferType, const
73{ 73{
74 QByteArray remoteId = Index("localid.mapping." + bufferType, mTransaction).lookup(localId); 74 QByteArray remoteId = Index("localid.mapping." + bufferType, mTransaction).lookup(localId);
75 if (remoteId.isEmpty()) { 75 if (remoteId.isEmpty()) {
76 SinkWarning() << "Couldn't find the remote id for " << bufferType << localId; 76 //This can happen if we didn't store the remote id in the first place
77 SinkTrace() << "Couldn't find the remote id for " << bufferType << localId;
77 return QByteArray(); 78 return QByteArray();
78 } 79 }
79 return remoteId; 80 return remoteId;
diff --git a/common/typeindex.cpp b/common/typeindex.cpp
index 5589e13..153aa43 100644
--- a/common/typeindex.cpp
+++ b/common/typeindex.cpp
@@ -190,7 +190,7 @@ static QVector<QByteArray> indexLookup(Index &index, QueryBase::Comparator filte
190 190
191 for (const auto &lookupKey : lookupKeys) { 191 for (const auto &lookupKey : lookupKeys) {
192 index.lookup(lookupKey, [&](const QByteArray &value) { keys << value; }, 192 index.lookup(lookupKey, [&](const QByteArray &value) { keys << value; },
193 [lookupKey](const Index::Error &error) { SinkWarning() << "Error in index: " << error.message << lookupKey; }, true); 193 [lookupKey](const Index::Error &error) { SinkWarning() << "Lookup error in index: " << error.message << lookupKey; }, true);
194 } 194 }
195 return keys; 195 return keys;
196} 196}
@@ -272,7 +272,7 @@ QVector<QByteArray> TypeIndex::secondaryLookup<QByteArray>(const QByteArray &lef
272 Index index(indexName(leftName + rightName), *mTransaction); 272 Index index(indexName(leftName + rightName), *mTransaction);
273 const auto lookupKey = getByteArray(value); 273 const auto lookupKey = getByteArray(value);
274 index.lookup( 274 index.lookup(
275 lookupKey, [&](const QByteArray &value) { keys << value; }, [=](const Index::Error &error) { SinkWarning() << "Error in index: " << error.message << value; }); 275 lookupKey, [&](const QByteArray &value) { keys << value; }, [=](const Index::Error &error) { SinkWarning() << "Lookup error in secondary index: " << error.message << value << lookupKey; });
276 276
277 return keys; 277 return keys;
278} 278}
@@ -284,7 +284,7 @@ QVector<QByteArray> TypeIndex::secondaryLookup<QString>(const QByteArray &leftNa
284 Index index(indexName(leftName + rightName), *mTransaction); 284 Index index(indexName(leftName + rightName), *mTransaction);
285 const auto lookupKey = getByteArray(value); 285 const auto lookupKey = getByteArray(value);
286 index.lookup( 286 index.lookup(
287 lookupKey, [&](const QByteArray &value) { keys << value; }, [=](const Index::Error &error) { SinkWarning() << "Error in index: " << error.message << value; }); 287 lookupKey, [&](const QByteArray &value) { keys << value; }, [=](const Index::Error &error) { SinkWarning() << "Lookup error in secondary index: " << error.message << value << lookupKey; });
288 288
289 return keys; 289 return keys;
290} 290}
diff --git a/dist/sink.spec b/dist/sink.spec
index ed8822e..bf2df7a 100644
--- a/dist/sink.spec
+++ b/dist/sink.spec
@@ -1,13 +1,13 @@
1 1
2Name: sink 2Name: sink
3Version: 0.1.0 3Version: 0.2
4Release: 6%{?dist} 4Release: 2%{?dist}
5Summary: sink 5Summary: sink
6 6
7Group: Applications/Desktop 7Group: Applications/Desktop
8License: GPL 8License: GPL
9URL: https://docs.kolab.org/about/sink 9URL: https://docs.kolab.org/about/sink
10Source0: sink-%{version}.tar.gz 10Source0: sink-%{version}.tar.xz
11 11
12BuildRequires: cmake >= 2.8.12 12BuildRequires: cmake >= 2.8.12
13BuildRequires: extra-cmake-modules 13BuildRequires: extra-cmake-modules
@@ -16,7 +16,7 @@ BuildRequires: gcc-c++
16BuildRequires: kasync-devel 16BuildRequires: kasync-devel
17BuildRequires: kf5-kcoreaddons-devel 17BuildRequires: kf5-kcoreaddons-devel
18BuildRequires: kf5-kcontacts-devel 18BuildRequires: kf5-kcontacts-devel
19BuildRequires: kmime-devel 19BuildRequires: kf5-kmime-devel
20BuildRequires: kimap2-devel 20BuildRequires: kimap2-devel
21BuildRequires: libcurl-devel 21BuildRequires: libcurl-devel
22BuildRequires: libgit2-devel 22BuildRequires: libgit2-devel
@@ -69,7 +69,6 @@ rm %{buildroot}%{_prefix}/bin/sink_smtp_test
69%{_bindir}/sink_synchronizer 69%{_bindir}/sink_synchronizer
70%{_bindir}/sinksh 70%{_bindir}/sinksh
71%{_libdir}/liblibhawd.so 71%{_libdir}/liblibhawd.so
72%{_libdir}/libmaildir.so
73%{_libdir}/libsink.so.* 72%{_libdir}/libsink.so.*
74%dir %{_libdir}/qt5/plugins/ 73%dir %{_libdir}/qt5/plugins/
75%{_libdir}/qt5/plugins/sink/ 74%{_libdir}/qt5/plugins/sink/
diff --git a/docs/applicationdomaintypes.md b/docs/applicationdomaintypes.md
index 09fec9f..48fe17c 100644
--- a/docs/applicationdomaintypes.md
+++ b/docs/applicationdomaintypes.md
@@ -56,7 +56,7 @@ Event:
56``` 56```
57```no-highlight 57```no-highlight
58Mail: 58Mail:
59 uid [QByteArray]: The message id. 59 messageId [QByteArray]: The message id.
60 subject [QString]: The subject of the email. 60 subject [QString]: The subject of the email.
61 folder [MailFolder.id]: The parent folder. 61 folder [MailFolder.id]: The parent folder.
62 date [QDateTime]: The date of the email. 62 date [QDateTime]: The date of the email.
@@ -67,6 +67,25 @@ Mail Folder:
67 parent [MailFolder.id]: The parent folder. 67 parent [MailFolder.id]: The parent folder.
68 name [QString]: The user visible name of the folder. 68 name [QString]: The user visible name of the folder.
69 icon [QString]: The name of the icon of the folder. 69 icon [QString]: The name of the icon of the folder.
70 lastUpdated [QDateTime]: time of last successful update.
71 count [int]: Number of messages available on the server.
72 fullDataAvailable [bool]: Inidicates whether the local dataset is complete.
73```
74```no-highlight
75Contact:
76 uid [QByteArray]: The contact uid.
77 fn [QString]: The full name.
78 firstName [QString]: The first name.
79 lastName [QString]: The last name.
80 addressbook [Addressbook.id]: The parent addressbook.
81 emails [Email]: The availale email addresses.
82```
83```no-highlight
84Addressbook:
85 parent [Addressbook.id]: The parent addressbook.
86 name [QString]: The user visible name of the addressbook.
87 icon [QString]: The name of the icon of the addressbook.
88 lastUpdated [QDateTime]: time of last successful update.
70``` 89```
71```no-highlight 90```no-highlight
72Sink Resource: 91Sink Resource:
diff --git a/examples/davresource/CMakeLists.txt b/examples/davresource/CMakeLists.txt
index c7899eb..28829d5 100644
--- a/examples/davresource/CMakeLists.txt
+++ b/examples/davresource/CMakeLists.txt
@@ -3,16 +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(KF5 COMPONENTS REQUIRED Mime)
7find_package(KPimKDAV REQUIRED) 6find_package(KPimKDAV REQUIRED)
8 7
9add_library(${PROJECT_NAME} SHARED facade.cpp davresource.cpp domainadaptor.cpp) 8add_library(${PROJECT_NAME} SHARED davresource.cpp)
10qt5_use_modules(${PROJECT_NAME} Core Network) 9qt5_use_modules(${PROJECT_NAME} Core Network)
11target_link_libraries(${PROJECT_NAME} sink KPim::KDAV) 10target_link_libraries(${PROJECT_NAME} sink KPim::KDAV)
12 11
13install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH}) 12install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH})
14
15#add_definitions(-DTESTDATAPATH="${CMAKE_CURRENT_SOURCE_DIR}/tests/data")
16
17#add_subdirectory(libmaildir)
18#add_subdirectory(tests)
diff --git a/examples/davresource/davresource.cpp b/examples/davresource/davresource.cpp
index de4c0b0..50471ed 100644
--- a/examples/davresource/davresource.cpp
+++ b/examples/davresource/davresource.cpp
@@ -21,10 +21,8 @@
21 21
22#include "facade.h" 22#include "facade.h"
23#include "resourceconfig.h" 23#include "resourceconfig.h"
24#include "index.h"
25#include "log.h" 24#include "log.h"
26#include "definitions.h" 25#include "definitions.h"
27#include "inspection.h"
28#include "synchronizer.h" 26#include "synchronizer.h"
29#include "inspector.h" 27#include "inspector.h"
30 28
@@ -40,181 +38,14 @@
40#include <KDAV/DavItemFetchJob> 38#include <KDAV/DavItemFetchJob>
41#include <KDAV/EtagCache> 39#include <KDAV/EtagCache>
42 40
43#include <QDir>
44#include <QDirIterator>
45
46//This is the resources entity type, and not the domain type 41//This is the resources entity type, and not the domain type
47#define ENTITY_TYPE_CONTACT "contact" 42#define ENTITY_TYPE_CONTACT "contact"
48#define ENTITY_TYPE_ADDRESSBOOK "folder" 43#define ENTITY_TYPE_ADDRESSBOOK "addressbook"
49 44
50SINK_DEBUG_AREA("davresource") 45SINK_DEBUG_AREA("davresource")
51 46
52using namespace Sink; 47using namespace Sink;
53 48
54/*static QString getFilePathFromMimeMessagePath(const QString &mimeMessagePath)
55{
56 auto parts = mimeMessagePath.split('/');
57 const auto key = parts.takeLast();
58 const auto path = parts.join("/") + "/cur/";
59
60 QDir dir(path);
61 const QFileInfoList list = dir.entryInfoList(QStringList() << (key+"*"), QDir::Files);
62 if (list.size() != 1) {
63 SinkWarning() << "Failed to find message " << mimeMessagePath;
64 SinkWarning() << "Failed to find message " << path;
65 return QString();
66 }
67 return list.first().filePath();
68}
69
70class MaildirMailPropertyExtractor : public MailPropertyExtractor
71{
72protected:
73 virtual QString getFilePathFromMimeMessagePath(const QString &mimeMessagePath) const Q_DECL_OVERRIDE
74 {
75 return ::getFilePathFromMimeMessagePath(mimeMessagePath);
76 }
77};
78
79class MaildirMimeMessageMover : public Sink::Preprocessor
80{
81public:
82 MaildirMimeMessageMover(const QByteArray &resourceInstanceIdentifier, const QString &maildirPath) : mResourceInstanceIdentifier(resourceInstanceIdentifier), mMaildirPath(maildirPath) {}
83
84 QString getPath(const QByteArray &folderIdentifier)
85 {
86 if (folderIdentifier.isEmpty()) {
87 return mMaildirPath;
88 }
89 QString folderPath;
90 const auto folder = entityStore().readLatest<ApplicationDomain::Folder>(folderIdentifier);
91 if (mMaildirPath.endsWith(folder.getName())) {
92 folderPath = mMaildirPath;
93 } else {
94 auto folderName = folder.getName();
95 //FIXME handle non toplevel folders
96 folderPath = mMaildirPath + "/" + folderName;
97 }
98 return folderPath;
99 }
100
101 QString moveMessage(const QString &oldPath, const QByteArray &folder)
102 {
103 if (oldPath.startsWith(Sink::temporaryFileLocation())) {
104 const auto path = getPath(folder);
105 KPIM::Contactdir maildir(path, false);
106 if (!maildir.isValid(true)) {
107 SinkWarning() << "Maildir is not existing: " << path;
108 }
109 auto identifier = maildir.addEntryFromPath(oldPath);
110 return path + "/" + identifier;
111 } else {
112 //Handle moves
113 const auto path = getPath(folder);
114 KPIM::Contactdir maildir(path, false);
115 if (!maildir.isValid(true)) {
116 SinkWarning() << "Maildir is not existing: " << path;
117 }
118 auto oldIdentifier = KPIM::Contactdir::getKeyFromFile(oldPath);
119 auto pathParts = oldPath.split('/');
120 pathParts.takeLast();
121 auto oldDirectory = pathParts.join('/');
122 if (oldDirectory == path) {
123 return oldPath;
124 }
125 KPIM::Contactdir oldMaildir(oldDirectory, false);
126 if (!oldMaildir.isValid(false)) {
127 SinkWarning() << "Maildir is not existing: " << path;
128 }
129 auto identifier = oldMaildir.moveEntryTo(oldIdentifier, maildir);
130 return path + "/" + identifier;
131 }
132 }
133
134 void newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE
135 {
136 auto mail = newEntity.cast<ApplicationDomain::Contact>();
137 const auto mimeMessage = mail.getMimeMessagePath();
138 if (!mimeMessage.isNull()) {
139 const auto path = moveMessage(mimeMessage, mail.getFolder());
140 auto blob = ApplicationDomain::BLOB{path};
141 blob.isExternal = false;
142 mail.setProperty(ApplicationDomain::Contact::MimeMessage::name, QVariant::fromValue(blob));
143 }
144 }
145
146 void modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE
147 {
148 auto newMail = newEntity.cast<ApplicationDomain::Contact>();
149 const ApplicationDomain::Contact oldMail{oldEntity};
150 const auto mimeMessage = newMail.getMimeMessagePath();
151 const auto newFolder = newMail.getFolder();
152 const bool mimeMessageChanged = !mimeMessage.isNull() && mimeMessage != oldMail.getMimeMessagePath();
153 const bool folderChanged = !newFolder.isNull() && newFolder != oldMail.getFolder();
154 if (mimeMessageChanged || folderChanged) {
155 SinkTrace() << "Moving mime message: " << mimeMessageChanged << folderChanged;
156 auto newPath = moveMessage(mimeMessage, newMail.getFolder());
157 if (newPath != oldMail.getMimeMessagePath()) {
158 const auto oldPath = getFilePathFromMimeMessagePath(oldMail.getMimeMessagePath());
159 auto blob = ApplicationDomain::BLOB{newPath};
160 blob.isExternal = false;
161 newMail.setProperty(ApplicationDomain::Contact::MimeMessage::name, QVariant::fromValue(blob));
162 //Remove the olde mime message if there is a new one
163 QFile::remove(oldPath);
164 }
165 }
166
167 auto mimeMessagePath = newMail.getMimeMessagePath();
168 const auto maildirPath = getPath(newMail.getFolder());
169 KPIM::Contactdir maildir(maildirPath, false);
170 const auto file = getFilePathFromMimeMessagePath(mimeMessagePath);
171 QString identifier = KPIM::Contactdir::getKeyFromFile(file);
172
173 //get flags from
174 KPIM::Contactdir::Flags flags;
175 if (!newMail.getUnread()) {
176 flags |= KPIM::Contactdir::Seen;
177 }
178 if (newMail.getImportant()) {
179 flags |= KPIM::Contactdir::Flagged;
180 }
181
182 maildir.changeEntryFlags(identifier, flags);
183 }
184
185 void deletedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity) Q_DECL_OVERRIDE
186 {
187 const ApplicationDomain::Contact oldMail{oldEntity};
188 const auto filePath = getFilePathFromMimeMessagePath(oldMail.getMimeMessagePath());
189 QFile::remove(filePath);
190 }
191 QByteArray mResourceInstanceIdentifier;
192 QString mMaildirPath;
193};
194
195class FolderPreprocessor : public Sink::Preprocessor
196{
197public:
198 FolderPreprocessor(const QString maildirPath) : mMaildirPath(maildirPath) {}
199
200 void newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE
201 {
202 auto folderName = Sink::ApplicationDomain::Folder{newEntity}.getName();
203 const auto path = mMaildirPath + "/" + folderName;
204 KPIM::Contactdir maildir(path, false);
205 maildir.create();
206 }
207
208 void modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE
209 {
210 }
211
212 void deletedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity) Q_DECL_OVERRIDE
213 {
214 }
215 QString mMaildirPath;
216};*/
217
218static KAsync::Job<void> runJob(KJob *job) 49static KAsync::Job<void> runJob(KJob *job)
219{ 50{
220 return KAsync::start<void>([job](KAsync::Future<void> &future) { 51 return KAsync::start<void>([job](KAsync::Future<void> &future) {
@@ -240,33 +71,33 @@ public:
240 71
241 } 72 }
242 73
243 QByteArray createAddressbook(const QString &folderName, const QString &folderPath, const QString &parentFolderRid, const QByteArray &icon) 74 QByteArray createAddressbook(const QString &addressbookName, const QString &addressbookPath, const QString &parentAddressbookRid)
244 { 75 {
245 SinkTrace() << "Creating addressbook: " << folderName << parentFolderRid; 76 SinkTrace() << "Creating addressbook: " << addressbookName << parentAddressbookRid;
246 const auto remoteId = folderPath.toUtf8(); 77 const auto remoteId = addressbookPath.toUtf8();
247 const auto bufferType = ENTITY_TYPE_ADDRESSBOOK; 78 const auto bufferType = ENTITY_TYPE_ADDRESSBOOK;
248 Sink::ApplicationDomain::Folder folder; 79 Sink::ApplicationDomain::Addressbook addressbook;
249 folder.setName(folderName); 80 addressbook.setName(addressbookName);
250 folder.setIcon(icon);
251 QHash<QByteArray, Query::Comparator> mergeCriteria; 81 QHash<QByteArray, Query::Comparator> mergeCriteria;
252 82
253 if (!parentFolderRid.isEmpty()) { 83 if (!parentAddressbookRid.isEmpty()) {
254 folder.setParent(syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, parentFolderRid.toUtf8())); 84 addressbook.setParent(syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, parentAddressbookRid.toUtf8()));
255 } 85 }
256 createOrModify(bufferType, remoteId, folder, mergeCriteria); 86 createOrModify(bufferType, remoteId, addressbook, mergeCriteria);
257 return remoteId; 87 return remoteId;
258 } 88 }
259 89
260 void synchronizeAddressbooks(const KDAV::DavCollection::List &folderList) 90 void synchronizeAddressbooks(const KDAV::DavCollection::List &addressbookList)
261 { 91 {
262 const QByteArray bufferType = ENTITY_TYPE_ADDRESSBOOK; 92 const QByteArray bufferType = ENTITY_TYPE_ADDRESSBOOK;
263 SinkTrace() << "Found addressbooks " << folderList.size(); 93 SinkTrace() << "Found addressbooks " << addressbookList.size();
264 94
265 QVector<QByteArray> ridList; 95 QVector<QByteArray> ridList;
266 for(const auto &f : folderList) { 96 for(const auto &f : addressbookList) {
267 const auto &rid = f.url().toDisplayString(); 97 const auto &rid = getRid(f);
268 ridList.append(rid.toUtf8()); 98 SinkTrace() << "Found addressbook:" << rid;
269 createAddressbook(f.displayName(), rid, "", "addressbook"); 99 ridList.append(rid);
100 createAddressbook(f.displayName(), rid, "");
270 } 101 }
271 102
272 scanForRemovals(bufferType, 103 scanForRemovals(bufferType,
@@ -284,54 +115,73 @@ public:
284 list << Synchronizer::SyncRequest{query}; 115 list << Synchronizer::SyncRequest{query};
285 } else { 116 } else {
286 //We want to synchronize everything 117 //We want to synchronize everything
118 list << Synchronizer::SyncRequest{Sink::QueryBase(ApplicationDomain::getTypeName<ApplicationDomain::Addressbook>())};
287 list << Synchronizer::SyncRequest{Sink::QueryBase(ApplicationDomain::getTypeName<ApplicationDomain::Contact>())}; 119 list << Synchronizer::SyncRequest{Sink::QueryBase(ApplicationDomain::getTypeName<ApplicationDomain::Contact>())};
288 } 120 }
289 return list; 121 return list;
290 } 122 }
291 123
124 static QByteArray getRid(const KDAV::DavItem &item)
125 {
126 return item.url().toDisplayString().toUtf8();
127 }
128
129 static QByteArray getRid(const KDAV::DavCollection &item)
130 {
131 return item.url().toDisplayString().toUtf8();
132 }
133
292 KAsync::Job<void> synchronizeWithSource(const Sink::QueryBase &query) Q_DECL_OVERRIDE 134 KAsync::Job<void> synchronizeWithSource(const Sink::QueryBase &query) Q_DECL_OVERRIDE
293 { 135 {
294 if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Folder>()) { 136 if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Addressbook>()) {
137 SinkLogCtx(mLogCtx) << "Synchronizing addressbooks:" << mResourceUrl.url();
295 auto collectionsFetchJob = new KDAV::DavCollectionsFetchJob(mResourceUrl); 138 auto collectionsFetchJob = new KDAV::DavCollectionsFetchJob(mResourceUrl);
296 auto job = runJob(collectionsFetchJob).then<void>([this, collectionsFetchJob] { 139 auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob] (const KAsync::Error &error) {
297 synchronizeAddressbooks(collectionsFetchJob ->collections()); 140 if (error) {
141 SinkWarningCtx(mLogCtx) << "Failed to synchronize addressbooks." << collectionsFetchJob->errorString();
142 } else {
143 synchronizeAddressbooks(collectionsFetchJob ->collections());
144 }
298 }); 145 });
299 return job; 146 return job;
300 } else if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Contact>()) { 147 } else if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Contact>()) {
148 SinkLogCtx(mLogCtx) << "Synchronizing contacts.";
301 auto ridList = QSharedPointer<QByteArrayList>::create(); 149 auto ridList = QSharedPointer<QByteArrayList>::create();
302 auto collectionsFetchJob = new KDAV::DavCollectionsFetchJob(mResourceUrl); 150 auto collectionsFetchJob = new KDAV::DavCollectionsFetchJob(mResourceUrl);
303 auto job = runJob(collectionsFetchJob).then<KDAV::DavCollection::List>([this, collectionsFetchJob] { 151 auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob] {
304 synchronizeAddressbooks(collectionsFetchJob ->collections()); 152 synchronizeAddressbooks(collectionsFetchJob ->collections());
305 return collectionsFetchJob->collections(); 153 return collectionsFetchJob->collections();
306 }) 154 })
307 .serialEach<void>([this, ridList](const KDAV::DavCollection &collection) { 155 .serialEach([this, ridList](const KDAV::DavCollection &collection) {
308 auto collId = collection.url().toDisplayString().toLatin1(); 156 auto collId = getRid(collection);
157 const auto addressbookLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, collId);
309 auto ctag = collection.CTag().toLatin1(); 158 auto ctag = collection.CTag().toLatin1();
310 if (ctag != syncStore().readValue(collId + "_ctagXX")) { 159 if (ctag != syncStore().readValue(collId + "_ctagXX")) {
311 SinkTrace() << "Syncing " << collId; 160 SinkTraceCtx(mLogCtx) << "Syncing " << collId;
312 auto cache = std::shared_ptr<KDAV::EtagCache>(new KDAV::EtagCache()); 161 auto cache = std::shared_ptr<KDAV::EtagCache>(new KDAV::EtagCache());
313 auto davItemsListJob = new KDAV::DavItemsListJob(collection.url(), cache); 162 auto davItemsListJob = new KDAV::DavItemsListJob(collection.url(), cache);
314 const QByteArray bufferType = ENTITY_TYPE_CONTACT; 163 const QByteArray bufferType = ENTITY_TYPE_CONTACT;
315 QHash<QByteArray, Query::Comparator> mergeCriteria; 164 QHash<QByteArray, Query::Comparator> mergeCriteria;
316 auto colljob = runJob(davItemsListJob).then<KDAV::DavItem::List>([davItemsListJob] { 165 auto colljob = runJob(davItemsListJob).then([davItemsListJob] {
317 return KAsync::value(davItemsListJob->items()); 166 return KAsync::value(davItemsListJob->items());
318 }) 167 })
319 .serialEach<QByteArray>([this, ridList, bufferType, mergeCriteria] (const KDAV::DavItem &item) { 168 .serialEach([=] (const KDAV::DavItem &item) {
320 QByteArray rid = item.url().toDisplayString().toUtf8(); 169 QByteArray rid = getRid(item);
321 if (item.etag().toLatin1() != syncStore().readValue(rid + "_etag")){ 170 if (item.etag().toLatin1() != syncStore().readValue(rid + "_etag")){
322 SinkTrace() << "Updating " << rid; 171 SinkTrace() << "Updating " << rid;
323 auto davItemFetchJob = new KDAV::DavItemFetchJob(item); 172 auto davItemFetchJob = new KDAV::DavItemFetchJob(item);
324 auto itemjob = runJob(davItemFetchJob) 173 auto itemjob = runJob(davItemFetchJob)
325 .then<KDAV::DavItem>([this, davItemFetchJob, bufferType, mergeCriteria] { 174 .then([=] {
326 const auto item = davItemFetchJob->item(); 175 const auto item = davItemFetchJob->item();
327 const auto rid = item.url().toDisplayString().toUtf8(); 176 const auto rid = getRid(item);
328 Sink::ApplicationDomain::Contact contact; 177 Sink::ApplicationDomain::Contact contact;
329 contact.setVcard(item.data()); 178 contact.setVcard(item.data());
179 contact.setAddressbook(addressbookLocalId);
330 createOrModify(bufferType, rid, contact, mergeCriteria); 180 createOrModify(bufferType, rid, contact, mergeCriteria);
331 return item; 181 return item;
332 }) 182 })
333 .then<QByteArray>([this, ridList] (const KDAV::DavItem &item) { 183 .then([this, ridList] (const KDAV::DavItem &item) {
334 const auto rid = item.url().toDisplayString().toUtf8(); 184 const auto rid = getRid(item);
335 syncStore().writeValue(rid + "_etag", item.etag().toLatin1()); 185 syncStore().writeValue(rid + "_etag", item.etag().toLatin1());
336 ridList->append(rid); 186 ridList->append(rid);
337 return rid; 187 return rid;
@@ -342,11 +192,12 @@ public:
342 return KAsync::value(rid); 192 return KAsync::value(rid);
343 } 193 }
344 }) 194 })
345 .then<void>([this, collId, ctag] () { 195 .then([this, collId, ctag] () {
346 syncStore().writeValue(collId + "_ctag", ctag); 196 syncStore().writeValue(collId + "_ctag", ctag);
347 }); 197 });
348 return colljob; 198 return colljob;
349 } else { 199 } else {
200 SinkTraceCtx(mLogCtx) << "Collection unchanged: " << ctag;
350 // for(const auto &item : addressbook) { 201 // for(const auto &item : addressbook) {
351 // ridList->append(rid); 202 // ridList->append(rid);
352 // } 203 // }
@@ -367,43 +218,11 @@ public:
367 218
368KAsync::Job<QByteArray> replay(const ApplicationDomain::Contact &contact, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE 219KAsync::Job<QByteArray> replay(const ApplicationDomain::Contact &contact, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE
369 { 220 {
370 /*
371 if (operation == Sink::Operation_Creation) {
372 const auto remoteId = getFilePathFromMimeMessagePath(mail.getMimeMessagePath());
373 SinkTrace() << "Contact created: " << remoteId;
374 return KAsync::value(remoteId.toUtf8());
375 } else if (operation == Sink::Operation_Removal) {
376 SinkTrace() << "Removing a contact " << oldRemoteId;
377 return KAsync::null<QByteArray>();
378 } else if (operation == Sink::Operation_Modification) {
379 SinkTrace() << "Modifying a contact: " << oldRemoteId;
380 const auto remoteId = getFilePathFromMimeMessagePath(mail.getMimeMessagePath());
381 return KAsync::value(remoteId.toUtf8());
382 }*/
383 return KAsync::null<QByteArray>(); 221 return KAsync::null<QByteArray>();
384 } 222 }
385 223
386 KAsync::Job<QByteArray> replay(const ApplicationDomain::Folder &folder, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE 224 KAsync::Job<QByteArray> replay(const ApplicationDomain::Addressbook &addressbook, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE
387 { 225 {
388 /*
389 if (operation == Sink::Operation_Creation) {
390 auto folderName = folder.getName();
391 //FIXME handle non toplevel folders
392 auto path = mMaildirPath + "/" + folderName;
393 SinkTrace() << "Creating a new folder: " << path;
394 KPIM::Contactdir maildir(path, false);
395 maildir.create();
396 return KAsync::value(path.toUtf8());
397 } else if (operation == Sink::Operation_Removal) {
398 const auto path = oldRemoteId;
399 SinkTrace() << "Removing a folder: " << path;
400 KPIM::Contactdir maildir(path, false);
401 maildir.remove();
402 return KAsync::null<QByteArray>();
403 } else if (operation == Sink::Operation_Modification) {
404 SinkWarning() << "Folder modifications are not implemented";
405 return KAsync::value(oldRemoteId);
406 }*/
407 return KAsync::null<QByteArray>(); 226 return KAsync::null<QByteArray>();
408 } 227 }
409 228
@@ -411,109 +230,17 @@ public:
411 KDAV::DavUrl mResourceUrl; 230 KDAV::DavUrl mResourceUrl;
412}; 231};
413 232
414/*
415class MaildirInspector : public Sink::Inspector {
416public:
417 MaildirInspector(const Sink::ResourceContext &resourceContext)
418 : Sink::Inspector(resourceContext)
419 {
420
421 }
422protected:
423
424 KAsync::Job<void> inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) Q_DECL_OVERRIDE {
425 auto synchronizationStore = QSharedPointer<Sink::Storage::DataStore>::create(Sink::storageLocation(), mResourceContext.instanceId() + ".synchronization", Sink::Storage::DataStore::ReadOnly);
426 auto synchronizationTransaction = synchronizationStore->createTransaction(Sink::Storage::DataStore::ReadOnly);
427
428 auto mainStore = QSharedPointer<Sink::Storage::DataStore>::create(Sink::storageLocation(), mResourceContext.instanceId(), Sink::Storage::DataStore::ReadOnly);
429 auto transaction = mainStore->createTransaction(Sink::Storage::DataStore::ReadOnly);
430
431 Sink::Storage::EntityStore entityStore(mResourceContext, {"maildirresource"});
432 auto syncStore = QSharedPointer<SynchronizerStore>::create(synchronizationTransaction);
433
434 SinkTrace() << "Inspecting " << inspectionType << domainType << entityId << property << expectedValue;
435
436 if (domainType == ENTITY_TYPE_MAIL) {
437 auto mail = entityStore.readLatest<Sink::ApplicationDomain::Contact>(entityId);
438 const auto filePath = getFilePathFromMimeMessagePath(mail.getMimeMessagePath());
439
440 if (inspectionType == Sink::ResourceControl::Inspection::PropertyInspectionType) {
441 if (property == "unread") {
442 const auto flags = KPIM::Contactdir::readEntryFlags(filePath.split('/').last());
443 if (expectedValue.toBool() && (flags & KPIM::Contactdir::Seen)) {
444 return KAsync::error<void>(1, "Expected unread but couldn't find it.");
445 }
446 if (!expectedValue.toBool() && !(flags & KPIM::Contactdir::Seen)) {
447 return KAsync::error<void>(1, "Expected read but couldn't find it.");
448 }
449 return KAsync::null<void>();
450 }
451 if (property == "subject") {
452 KMime::Message *msg = new KMime::Message;
453 msg->setHead(KMime::CRLFtoLF(KPIM::Contactdir::readEntryHeadersFromFile(filePath)));
454 msg->parse();
455
456 if (msg->subject(true)->asUnicodeString() != expectedValue.toString()) {
457 return KAsync::error<void>(1, "Subject not as expected: " + msg->subject(true)->asUnicodeString());
458 }
459 return KAsync::null<void>();
460 }
461 }
462 if (inspectionType == Sink::ResourceControl::Inspection::ExistenceInspectionType) {
463 if (QFileInfo(filePath).exists() != expectedValue.toBool()) {
464 return KAsync::error<void>(1, "Wrong file existence: " + filePath);
465 }
466 }
467 }
468 if (domainType == ENTITY_TYPE_FOLDER) {
469 const auto remoteId = syncStore->resolveLocalId(ENTITY_TYPE_FOLDER, entityId);
470 auto folder = entityStore.readLatest<Sink::ApplicationDomain::Folder>(entityId);
471
472 if (inspectionType == Sink::ResourceControl::Inspection::CacheIntegrityInspectionType) {
473 SinkTrace() << "Inspecting cache integrity" << remoteId;
474 if (!QDir(remoteId).exists()) {
475 return KAsync::error<void>(1, "The directory is not existing: " + remoteId);
476 }
477
478 int expectedCount = 0;
479 Index index("mail.index.folder", transaction);
480 index.lookup(entityId, [&](const QByteArray &sinkId) {
481 expectedCount++;
482 },
483 [&](const Index::Error &error) {
484 SinkWarning() << "Error in index: " << error.message << property;
485 });
486
487 QDir dir(remoteId + "/cur");
488 const QFileInfoList list = dir.entryInfoList(QDir::Files);
489 if (list.size() != expectedCount) {
490 for (const auto &fileInfo : list) {
491 SinkWarning() << "Found in cache: " << fileInfo.fileName();
492 }
493 return KAsync::error<void>(1, QString("Wrong number of files; found %1 instead of %2.").arg(list.size()).arg(expectedCount));
494 }
495 }
496 if (inspectionType == Sink::ResourceControl::Inspection::ExistenceInspectionType) {
497 if (!remoteId.endsWith(folder.getName().toUtf8())) {
498 return KAsync::error<void>(1, "Wrong folder name: " + remoteId);
499 }
500 //TODO we shouldn't use the remoteId here to figure out the path, it could be gone/changed already
501 if (QDir(remoteId).exists() != expectedValue.toBool()) {
502 return KAsync::error<void>(1, "Wrong folder existence: " + remoteId);
503 }
504 }
505
506 }
507 return KAsync::null<void>();
508 }
509};*/
510
511 233
512DavResource::DavResource(const Sink::ResourceContext &resourceContext) 234DavResource::DavResource(const Sink::ResourceContext &resourceContext)
513 : Sink::GenericResource(resourceContext) 235 : Sink::GenericResource(resourceContext)
514{ 236{
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");
515 auto config = ResourceConfig::getConfiguration(resourceContext.instanceId()); 242 auto config = ResourceConfig::getConfiguration(resourceContext.instanceId());
516 auto resourceUrl = QUrl::fromUserInput(config.value("resourceUrl").toString()); 243 auto resourceUrl = QUrl::fromUserInput(config.value("server").toString());
517 resourceUrl.setUserName(config.value("username").toString()); 244 resourceUrl.setUserName(config.value("username").toString());
518 resourceUrl.setPassword(config.value("password").toString()); 245 resourceUrl.setPassword(config.value("password").toString());
519 246
@@ -522,7 +249,6 @@ DavResource::DavResource(const Sink::ResourceContext &resourceContext)
522 auto synchronizer = QSharedPointer<ContactSynchronizer>::create(resourceContext); 249 auto synchronizer = QSharedPointer<ContactSynchronizer>::create(resourceContext);
523 synchronizer->mResourceUrl = mResourceUrl; 250 synchronizer->mResourceUrl = mResourceUrl;
524 setupSynchronizer(synchronizer); 251 setupSynchronizer(synchronizer);
525 //setupInspector(QSharedPointer<MaildirInspector>::create(resourceContext));
526 252
527 setupPreprocessors(ENTITY_TYPE_CONTACT, QVector<Sink::Preprocessor*>() << new ContactPropertyExtractor); 253 setupPreprocessors(ENTITY_TYPE_CONTACT, QVector<Sink::Preprocessor*>() << new ContactPropertyExtractor);
528} 254}
@@ -530,7 +256,9 @@ DavResource::DavResource(const Sink::ResourceContext &resourceContext)
530 256
531DavResourceFactory::DavResourceFactory(QObject *parent) 257DavResourceFactory::DavResourceFactory(QObject *parent)
532 : Sink::ResourceFactory(parent, 258 : Sink::ResourceFactory(parent,
533 {"-folder.rename"} 259 {Sink::ApplicationDomain::ResourceCapabilities::Contact::contact,
260 Sink::ApplicationDomain::ResourceCapabilities::Contact::addressbook,
261 }
534 ) 262 )
535{ 263{
536} 264}
@@ -542,14 +270,14 @@ Sink::Resource *DavResourceFactory::createResource(const ResourceContext &contex
542 270
543void DavResourceFactory::registerFacades(const QByteArray &name, Sink::FacadeFactory &factory) 271void DavResourceFactory::registerFacades(const QByteArray &name, Sink::FacadeFactory &factory)
544{ 272{
545 factory.registerFacade<Sink::ApplicationDomain::Contact, DavResourceContactFacade>(name); 273 factory.registerFacade<ApplicationDomain::Contact, DefaultFacade<ApplicationDomain::Contact>>(name);
546 factory.registerFacade<Sink::ApplicationDomain::Folder, DavResourceFolderFacade>(name); 274 factory.registerFacade<ApplicationDomain::Addressbook, DefaultFacade<ApplicationDomain::Contact>>(name);
547} 275}
548 276
549void DavResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry &registry) 277void DavResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry &registry)
550{ 278{
551 registry.registerFactory<Sink::ApplicationDomain::Contact, ContactAdaptorFactory>(name); 279 registry.registerFactory<ApplicationDomain::Contact, DefaultAdaptorFactory<ApplicationDomain::Contact>>(name);
552 registry.registerFactory<Sink::ApplicationDomain::Folder, AddressbookAdaptorFactory>(name); 280 registry.registerFactory<ApplicationDomain::Addressbook, DefaultAdaptorFactory<ApplicationDomain::Addressbook>>(name);
553} 281}
554 282
555void DavResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier) 283void DavResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier)
diff --git a/examples/davresource/davresource.h b/examples/davresource/davresource.h
index 415527a..1ce66ea 100644
--- a/examples/davresource/davresource.h
+++ b/examples/davresource/davresource.h
@@ -54,7 +54,7 @@ private:
54class DavResourceFactory : public Sink::ResourceFactory 54class DavResourceFactory : public Sink::ResourceFactory
55{ 55{
56 Q_OBJECT 56 Q_OBJECT
57 Q_PLUGIN_METADATA(IID "sink.davresource") 57 Q_PLUGIN_METADATA(IID "sink.dav")
58 Q_INTERFACES(Sink::ResourceFactory) 58 Q_INTERFACES(Sink::ResourceFactory)
59 59
60public: 60public:
diff --git a/examples/davresource/domainadaptor.cpp b/examples/davresource/domainadaptor.cpp
deleted file mode 100644
index 861a10e..0000000
--- a/examples/davresource/domainadaptor.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
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 "domainadaptor.h"
21
22using namespace flatbuffers;
23
24ContactAdaptorFactory::ContactAdaptorFactory()
25 : DomainTypeAdaptorFactory()
26{
27
28}
29
30AddressbookAdaptorFactory::AddressbookAdaptorFactory()
31 : DomainTypeAdaptorFactory()
32{
33
34}
35
diff --git a/examples/davresource/domainadaptor.h b/examples/davresource/domainadaptor.h
deleted file mode 100644
index 7e3c723..0000000
--- a/examples/davresource/domainadaptor.h
+++ /dev/null
@@ -1,38 +0,0 @@
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#pragma once
20
21#include <common/domainadaptor.h>
22#include "contact_generated.h"
23#include "folder_generated.h"
24#include "dummy_generated.h"
25
26class ContactAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Contact, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder>
27{
28public:
29 ContactAdaptorFactory();
30 virtual ~ContactAdaptorFactory() {};
31};
32
33class AddressbookAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Folder, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder>
34{
35public:
36 AddressbookAdaptorFactory();
37 virtual ~AddressbookAdaptorFactory() {};
38};
diff --git a/examples/davresource/facade.cpp b/examples/davresource/facade.cpp
deleted file mode 100644
index b56815a..0000000
--- a/examples/davresource/facade.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
1/*
2 * Copyright (C) 2014 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 "facade.h"
21
22#include <QDir>
23#include <QFileInfo>
24
25#include "query.h"
26
27DavResourceContactFacade::DavResourceContactFacade(const Sink::ResourceContext &context)
28 : Sink::GenericFacade<Sink::ApplicationDomain::Contact>(context)
29{
30}
31
32DavResourceContactFacade::~DavResourceContactFacade()
33{
34}
35
36
37DavResourceFolderFacade::DavResourceFolderFacade(const Sink::ResourceContext &context)
38 : Sink::GenericFacade<Sink::ApplicationDomain::Folder>(context)
39{
40}
41
42DavResourceFolderFacade::~DavResourceFolderFacade()
43{
44}
diff --git a/examples/davresource/facade.h b/examples/davresource/facade.h
deleted file mode 100644
index 02bd5dc..0000000
--- a/examples/davresource/facade.h
+++ /dev/null
@@ -1,36 +0,0 @@
1/*
2 * Copyright (C) 2014 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#pragma once
21
22#include "common/facade.h"
23
24class DavResourceContactFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Contact>
25{
26public:
27 DavResourceContactFacade(const Sink::ResourceContext &context);
28 virtual ~DavResourceContactFacade();
29};
30
31class DavResourceFolderFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Folder>
32{
33public:
34 DavResourceFolderFacade(const Sink::ResourceContext &context);
35 virtual ~DavResourceFolderFacade();
36};
diff --git a/examples/dummyresource/CMakeLists.txt b/examples/dummyresource/CMakeLists.txt
index 6400f0c..2bbaa47 100644
--- a/examples/dummyresource/CMakeLists.txt
+++ b/examples/dummyresource/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
6 6
7add_library(${PROJECT_NAME} SHARED facade.cpp resourcefactory.cpp domainadaptor.cpp dummystore.cpp) 7add_library(${PROJECT_NAME} SHARED resourcefactory.cpp domainadaptor.cpp dummystore.cpp)
8generate_flatbuffers(${PROJECT_NAME} dummycalendar) 8generate_flatbuffers(${PROJECT_NAME} dummycalendar)
9qt5_use_modules(${PROJECT_NAME} Core Network) 9qt5_use_modules(${PROJECT_NAME} Core Network)
10target_link_libraries(${PROJECT_NAME} sink) 10target_link_libraries(${PROJECT_NAME} sink)
diff --git a/examples/dummyresource/facade.cpp b/examples/dummyresource/facade.cpp
deleted file mode 100644
index 4343eba..0000000
--- a/examples/dummyresource/facade.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
1/*
2 * Copyright (C) 2014 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 "facade.h"
21
22#include "domainadaptor.h"
23
24DummyResourceFacade::DummyResourceFacade(const Sink::ResourceContext &context)
25 : Sink::GenericFacade<Sink::ApplicationDomain::Event>(context)
26{
27}
28
29DummyResourceFacade::~DummyResourceFacade()
30{
31}
32
33
34DummyResourceMailFacade::DummyResourceMailFacade(const Sink::ResourceContext &context)
35 : Sink::GenericFacade<Sink::ApplicationDomain::Mail>(context)
36{
37}
38
39DummyResourceMailFacade::~DummyResourceMailFacade()
40{
41}
42
43
44DummyResourceFolderFacade::DummyResourceFolderFacade(const Sink::ResourceContext &context)
45 : Sink::GenericFacade<Sink::ApplicationDomain::Folder>(context)
46{
47}
48
49DummyResourceFolderFacade::~DummyResourceFolderFacade()
50{
51}
diff --git a/examples/dummyresource/facade.h b/examples/dummyresource/facade.h
deleted file mode 100644
index 1bb45fd..0000000
--- a/examples/dummyresource/facade.h
+++ /dev/null
@@ -1,44 +0,0 @@
1/*
2 * Copyright (C) 2014 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#pragma once
21
22#include "common/facade.h"
23#include "common/domain/event.h"
24
25class DummyResourceFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Event>
26{
27public:
28 DummyResourceFacade(const Sink::ResourceContext &context);
29 virtual ~DummyResourceFacade();
30};
31
32class DummyResourceMailFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Mail>
33{
34public:
35 DummyResourceMailFacade(const Sink::ResourceContext &context);
36 virtual ~DummyResourceMailFacade();
37};
38
39class DummyResourceFolderFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Folder>
40{
41public:
42 DummyResourceFolderFacade(const Sink::ResourceContext &context);
43 virtual ~DummyResourceFolderFacade();
44};
diff --git a/examples/dummyresource/resourcefactory.cpp b/examples/dummyresource/resourcefactory.cpp
index e915710..c1f536e 100644
--- a/examples/dummyresource/resourcefactory.cpp
+++ b/examples/dummyresource/resourcefactory.cpp
@@ -26,8 +26,6 @@
26#include "mail_generated.h" 26#include "mail_generated.h"
27#include "domainadaptor.h" 27#include "domainadaptor.h"
28#include "log.h" 28#include "log.h"
29#include "domain/event.h"
30#include "domain/mail.h"
31#include "dummystore.h" 29#include "dummystore.h"
32#include "definitions.h" 30#include "definitions.h"
33#include "facadefactory.h" 31#include "facadefactory.h"
@@ -46,6 +44,8 @@
46 44
47SINK_DEBUG_AREA("dummyresource") 45SINK_DEBUG_AREA("dummyresource")
48 46
47using namespace Sink;
48
49class DummySynchronizer : public Sink::Synchronizer { 49class DummySynchronizer : public Sink::Synchronizer {
50 public: 50 public:
51 51
@@ -69,6 +69,7 @@ class DummySynchronizer : public Sink::Synchronizer {
69 Sink::ApplicationDomain::Mail::Ptr createMail(const QByteArray &ridBuffer, const QMap<QString, QVariant> &data) 69 Sink::ApplicationDomain::Mail::Ptr createMail(const QByteArray &ridBuffer, const QMap<QString, QVariant> &data)
70 { 70 {
71 auto mail = Sink::ApplicationDomain::Mail::Ptr::create(); 71 auto mail = Sink::ApplicationDomain::Mail::Ptr::create();
72 mail->setExtractedMessageId(ridBuffer);
72 mail->setExtractedSubject(data.value("subject").toString()); 73 mail->setExtractedSubject(data.value("subject").toString());
73 mail->setExtractedSender(Sink::ApplicationDomain::Mail::Contact{data.value("senderName").toString(), data.value("senderEmail").toString()}); 74 mail->setExtractedSender(Sink::ApplicationDomain::Mail::Contact{data.value("senderName").toString(), data.value("senderEmail").toString()});
74 mail->setExtractedDate(data.value("date").toDateTime()); 75 mail->setExtractedDate(data.value("date").toDateTime());
@@ -163,7 +164,7 @@ DummyResource::DummyResource(const Sink::ResourceContext &resourceContext, const
163 setupSynchronizer(QSharedPointer<DummySynchronizer>::create(resourceContext)); 164 setupSynchronizer(QSharedPointer<DummySynchronizer>::create(resourceContext));
164 setupInspector(QSharedPointer<DummyInspector>::create(resourceContext)); 165 setupInspector(QSharedPointer<DummyInspector>::create(resourceContext));
165 setupPreprocessors(ENTITY_TYPE_MAIL, 166 setupPreprocessors(ENTITY_TYPE_MAIL,
166 QVector<Sink::Preprocessor*>() << new MailPropertyExtractor << new SpecialPurposeProcessor{resourceContext.resourceType, resourceContext.instanceId()}); 167 QVector<Sink::Preprocessor*>() << new MailPropertyExtractor << new SpecialPurposeProcessor);
167 setupPreprocessors(ENTITY_TYPE_FOLDER, 168 setupPreprocessors(ENTITY_TYPE_FOLDER,
168 QVector<Sink::Preprocessor*>()); 169 QVector<Sink::Preprocessor*>());
169 setupPreprocessors(ENTITY_TYPE_EVENT, 170 setupPreprocessors(ENTITY_TYPE_EVENT,
@@ -194,9 +195,9 @@ Sink::Resource *DummyResourceFactory::createResource(const Sink::ResourceContext
194 195
195void DummyResourceFactory::registerFacades(const QByteArray &resourceName, Sink::FacadeFactory &factory) 196void DummyResourceFactory::registerFacades(const QByteArray &resourceName, Sink::FacadeFactory &factory)
196{ 197{
197 factory.registerFacade<Sink::ApplicationDomain::Event, DummyResourceFacade>(resourceName); 198 factory.registerFacade<ApplicationDomain::Event, DefaultFacade<ApplicationDomain::Event>>(resourceName);
198 factory.registerFacade<Sink::ApplicationDomain::Mail, DummyResourceMailFacade>(resourceName); 199 factory.registerFacade<ApplicationDomain::Mail, DefaultFacade<ApplicationDomain::Mail>>(resourceName);
199 factory.registerFacade<Sink::ApplicationDomain::Folder, DummyResourceFolderFacade>(resourceName); 200 factory.registerFacade<ApplicationDomain::Folder, DefaultFacade<ApplicationDomain::Folder>>(resourceName);
200} 201}
201 202
202void DummyResourceFactory::registerAdaptorFactories(const QByteArray &resourceName, Sink::AdaptorFactoryRegistry &registry) 203void DummyResourceFactory::registerAdaptorFactories(const QByteArray &resourceName, Sink::AdaptorFactoryRegistry &registry)
diff --git a/examples/imapresource/CMakeLists.txt b/examples/imapresource/CMakeLists.txt
index 15a720d..46a8b08 100644
--- a/examples/imapresource/CMakeLists.txt
+++ b/examples/imapresource/CMakeLists.txt
@@ -8,7 +8,7 @@ find_package(KIMAP2 0.0.1 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
11add_library(${PROJECT_NAME} SHARED facade.cpp imapresource.cpp domainadaptor.cpp imapserverproxy.cpp) 11add_library(${PROJECT_NAME} SHARED imapresource.cpp imapserverproxy.cpp)
12qt5_use_modules(${PROJECT_NAME} Core Network) 12qt5_use_modules(${PROJECT_NAME} Core Network)
13target_link_libraries(${PROJECT_NAME} sink KF5::Mime KIMAP2) 13target_link_libraries(${PROJECT_NAME} sink KF5::Mime KIMAP2)
14 14
diff --git a/examples/imapresource/domainadaptor.cpp b/examples/imapresource/domainadaptor.cpp
deleted file mode 100644
index 4e74ad2..0000000
--- a/examples/imapresource/domainadaptor.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
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 "domainadaptor.h"
21
22using namespace flatbuffers;
23
24ImapMailAdaptorFactory::ImapMailAdaptorFactory()
25 : DomainTypeAdaptorFactory()
26{
27
28}
29
30ImapFolderAdaptorFactory::ImapFolderAdaptorFactory()
31 : DomainTypeAdaptorFactory()
32{
33
34}
35
diff --git a/examples/imapresource/domainadaptor.h b/examples/imapresource/domainadaptor.h
deleted file mode 100644
index 06a513c..0000000
--- a/examples/imapresource/domainadaptor.h
+++ /dev/null
@@ -1,38 +0,0 @@
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#pragma once
20
21#include <common/domainadaptor.h>
22#include "mail_generated.h"
23#include "folder_generated.h"
24#include "dummy_generated.h"
25
26class ImapMailAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Mail, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder>
27{
28public:
29 ImapMailAdaptorFactory();
30 virtual ~ImapMailAdaptorFactory() {};
31};
32
33class ImapFolderAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Folder, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder>
34{
35public:
36 ImapFolderAdaptorFactory();
37 virtual ~ImapFolderAdaptorFactory() {};
38};
diff --git a/examples/imapresource/facade.cpp b/examples/imapresource/facade.cpp
deleted file mode 100644
index 2829bb1..0000000
--- a/examples/imapresource/facade.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
1/*
2 * Copyright (C) 2014 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 "facade.h"
21
22#include <QDir>
23#include <QFileInfo>
24
25#include "domainadaptor.h"
26#include "queryrunner.h"
27
28ImapResourceMailFacade::ImapResourceMailFacade(const Sink::ResourceContext &context)
29 : Sink::GenericFacade<Sink::ApplicationDomain::Mail>(context)
30{
31}
32
33ImapResourceMailFacade::~ImapResourceMailFacade()
34{
35}
36
37ImapResourceFolderFacade::ImapResourceFolderFacade(const Sink::ResourceContext &context)
38 : Sink::GenericFacade<Sink::ApplicationDomain::Folder>(context)
39{
40}
41
42ImapResourceFolderFacade::~ImapResourceFolderFacade()
43{
44}
diff --git a/examples/imapresource/facade.h b/examples/imapresource/facade.h
deleted file mode 100644
index 1d24856..0000000
--- a/examples/imapresource/facade.h
+++ /dev/null
@@ -1,36 +0,0 @@
1/*
2 * Copyright (C) 2014 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#pragma once
21
22#include "common/facade.h"
23
24class ImapResourceMailFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Mail>
25{
26public:
27 ImapResourceMailFacade(const Sink::ResourceContext &context);
28 virtual ~ImapResourceMailFacade();
29};
30
31class ImapResourceFolderFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Folder>
32{
33public:
34 ImapResourceFolderFacade(const Sink::ResourceContext &context);
35 virtual ~ImapResourceFolderFacade();
36};
diff --git a/examples/imapresource/imapresource.cpp b/examples/imapresource/imapresource.cpp
index 09f57d5..0579dae 100644
--- a/examples/imapresource/imapresource.cpp
+++ b/examples/imapresource/imapresource.cpp
@@ -100,7 +100,7 @@ public:
100 QByteArray createFolder(const Imap::Folder &f) 100 QByteArray createFolder(const Imap::Folder &f)
101 { 101 {
102 const auto parentFolderRid = parentRid(f); 102 const auto parentFolderRid = parentRid(f);
103 SinkTrace() << "Creating folder: " << f.name() << parentFolderRid; 103 SinkTraceCtx(mLogCtx) << "Creating folder: " << f.name() << parentFolderRid;
104 104
105 const auto remoteId = folderRid(f); 105 const auto remoteId = folderRid(f);
106 Sink::ApplicationDomain::Folder folder; 106 Sink::ApplicationDomain::Folder folder;
@@ -123,7 +123,7 @@ public:
123 123
124 void synchronizeFolders(const QVector<Folder> &folderList) 124 void synchronizeFolders(const QVector<Folder> &folderList)
125 { 125 {
126 SinkTrace() << "Found folders " << folderList.size(); 126 SinkTraceCtx(mLogCtx) << "Found folders " << folderList.size();
127 127
128 scanForRemovals(ENTITY_TYPE_FOLDER, 128 scanForRemovals(ENTITY_TYPE_FOLDER,
129 [&folderList](const QByteArray &remoteId) -> bool { 129 [&folderList](const QByteArray &remoteId) -> bool {
@@ -164,14 +164,14 @@ public:
164 { 164 {
165 auto time = QSharedPointer<QTime>::create(); 165 auto time = QSharedPointer<QTime>::create();
166 time->start(); 166 time->start();
167 SinkTrace() << "Importing new mail." << folderRid; 167 SinkTraceCtx(mLogCtx) << "Importing new mail." << folderRid;
168 168
169 const auto folderLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, folderRid); 169 const auto folderLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, folderRid);
170 170
171 const auto remoteId = assembleMailRid(folderLocalId, message.uid); 171 const auto remoteId = assembleMailRid(folderLocalId, message.uid);
172 172
173 Q_ASSERT(message.msg); 173 Q_ASSERT(message.msg);
174 SinkTrace() << "Found a mail " << remoteId << message.msg->subject(true)->asUnicodeString() << message.flags; 174 SinkTraceCtx(mLogCtx) << "Found a mail " << remoteId << message.msg->subject(true)->asUnicodeString() << message.flags;
175 175
176 auto mail = Sink::ApplicationDomain::Mail::create(mResourceInstanceIdentifier); 176 auto mail = Sink::ApplicationDomain::Mail::create(mResourceInstanceIdentifier);
177 mail.setFolder(folderLocalId); 177 mail.setFolder(folderLocalId);
@@ -181,7 +181,7 @@ public:
181 181
182 createOrModify(ENTITY_TYPE_MAIL, remoteId, mail); 182 createOrModify(ENTITY_TYPE_MAIL, remoteId, mail);
183 // const auto elapsed = time->elapsed(); 183 // const auto elapsed = time->elapsed();
184 // SinkTrace() << "Synchronized " << count << " mails in " << folderRid << Sink::Log::TraceTime(elapsed) << " " << elapsed/qMax(count, 1) << " [ms/mail]"; 184 // SinkTraceCtx(mLogCtx) << "Synchronized " << count << " mails in " << folderRid << Sink::Log::TraceTime(elapsed) << " " << elapsed/qMax(count, 1) << " [ms/mail]";
185 } 185 }
186 186
187 void synchronizeRemovals(const QByteArray &folderRid, const QSet<qint64> &messages) 187 void synchronizeRemovals(const QByteArray &folderRid, const QSet<qint64> &messages)
@@ -194,7 +194,7 @@ public:
194 return; 194 return;
195 } 195 }
196 196
197 SinkTrace() << "Finding removed mail: " << folderLocalId << " remoteId: " << folderRid; 197 SinkTraceCtx(mLogCtx) << "Finding removed mail: " << folderLocalId << " remoteId: " << folderRid;
198 198
199 int count = 0; 199 int count = 0;
200 200
@@ -370,7 +370,7 @@ public:
370 370
371 Sink::QueryBase applyMailDefaults(const Sink::QueryBase &query) 371 Sink::QueryBase applyMailDefaults(const Sink::QueryBase &query)
372 { 372 {
373 auto defaultDateFilter = QDate::currentDate().addDays(-14); 373 auto defaultDateFilter = QDate::currentDate().addDays(0 - mDaysToSync);
374 auto queryWithDefaults = query; 374 auto queryWithDefaults = query;
375 if (!queryWithDefaults.hasFilter<ApplicationDomain::Mail::Date>()) { 375 if (!queryWithDefaults.hasFilter<ApplicationDomain::Mail::Date>()) {
376 queryWithDefaults.filter(ApplicationDomain::Mail::Date::name, QVariant::fromValue(defaultDateFilter)); 376 queryWithDefaults.filter(ApplicationDomain::Mail::Date::name, QVariant::fromValue(defaultDateFilter));
@@ -382,7 +382,11 @@ public:
382 { 382 {
383 QList<Synchronizer::SyncRequest> list; 383 QList<Synchronizer::SyncRequest> list;
384 if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Mail>()) { 384 if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Mail>()) {
385 list << Synchronizer::SyncRequest{applyMailDefaults(query)}; 385 auto request = Synchronizer::SyncRequest{applyMailDefaults(query)};
386 if (query.hasFilter(ApplicationDomain::Mail::Folder::name)) {
387 request.applicableEntities << query.getFilter(ApplicationDomain::Mail::Folder::name).value.toByteArray();
388 }
389 list << request;
386 } else if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Folder>()) { 390 } else if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Folder>()) {
387 list << Synchronizer::SyncRequest{query}; 391 list << Synchronizer::SyncRequest{query};
388 } else { 392 } else {
@@ -393,15 +397,57 @@ public:
393 return list; 397 return list;
394 } 398 }
395 399
400 QByteArray getFolderFromLocalId(const QByteArray &id)
401 {
402 auto mailRemoteId = syncStore().resolveLocalId(ApplicationDomain::getTypeName<ApplicationDomain::Mail>(), id);
403 if (mailRemoteId.isEmpty()) {
404 return {};
405 }
406 return folderIdFromMailRid(mailRemoteId);
407 }
408
409 void mergeIntoQueue(const Synchronizer::SyncRequest &request, QList<Synchronizer::SyncRequest> &queue) Q_DECL_OVERRIDE
410 {
411 auto isIndividualMailSync = [](const Synchronizer::SyncRequest &request) {
412 if (request.requestType == SyncRequest::Synchronization) {
413 const auto query = request.query;
414 if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Mail>()) {
415 return !query.ids().isEmpty();
416 }
417 }
418 return false;
419
420 };
421
422 if (isIndividualMailSync(request)) {
423 auto newId = request.query.ids().first();
424 auto requestFolder = getFolderFromLocalId(newId);
425 if (requestFolder.isEmpty()) {
426 SinkWarningCtx(mLogCtx) << "Failed to find folder for local id. Ignoring request: " << request.query;
427 return;
428 }
429 for (auto &r : queue) {
430 if (isIndividualMailSync(r)) {
431 auto queueFolder = getFolderFromLocalId(r.query.ids().first());
432 if (requestFolder == queueFolder) {
433 //Merge
434 r.query.filter(newId);
435 SinkTrace() << "Merging request " << request.query;
436 SinkTrace() << " to " << r.query;
437 return;
438 }
439 }
440 }
441 }
442 queue << request;
443 }
444
396 KAsync::Job<void> login(QSharedPointer<ImapServerProxy> imap) 445 KAsync::Job<void> login(QSharedPointer<ImapServerProxy> imap)
397 { 446 {
398 SinkTrace() << "Connecting to:" << mServer << mPort; 447 SinkTrace() << "Connecting to:" << mServer << mPort;
399 SinkTrace() << "as:" << mUser; 448 SinkTrace() << "as:" << mUser;
400 return imap->login(mUser, mPassword) 449 return imap->login(mUser, mPassword)
401 .addToContext(imap) 450 .addToContext(imap);
402 .onError([](const KAsync::Error &error) {
403 SinkWarning() << "Login failed.";
404 });
405 } 451 }
406 452
407 KAsync::Job<QVector<Folder>> getFolderList(QSharedPointer<ImapServerProxy> imap, const Sink::QueryBase &query) 453 KAsync::Job<QVector<Folder>> getFolderList(QSharedPointer<ImapServerProxy> imap, const Sink::QueryBase &query)
@@ -431,6 +477,19 @@ public:
431 } 477 }
432 } 478 }
433 479
480 KAsync::Error getError(const KAsync::Error &error)
481 {
482 if (error) {
483 if (error.errorCode == Imap::CouldNotConnectError) {
484 return {ApplicationDomain::ConnectionError, error.errorMessage};
485 } else if (error.errorCode == Imap::SslHandshakeError) {
486 return {ApplicationDomain::LoginError, error.errorMessage};
487 }
488 return {ApplicationDomain::UnknownError, error.errorMessage};
489 }
490 return {};
491 }
492
434 KAsync::Job<void> synchronizeWithSource(const Sink::QueryBase &query) Q_DECL_OVERRIDE 493 KAsync::Job<void> synchronizeWithSource(const Sink::QueryBase &query) Q_DECL_OVERRIDE
435 { 494 {
436 auto imap = QSharedPointer<ImapServerProxy>::create(mServer, mPort, &mSessionCache); 495 auto imap = QSharedPointer<ImapServerProxy>::create(mServer, mPort, &mSessionCache);
@@ -446,11 +505,8 @@ public:
446 }); 505 });
447 }) 506 })
448 .then([=] (const KAsync::Error &error) { 507 .then([=] (const KAsync::Error &error) {
449 if (error) {
450 SinkWarning() << "Error during folder sync: " << error.errorMessage;
451 }
452 return imap->logout() 508 return imap->logout()
453 .then(KAsync::error(error)); 509 .then(KAsync::error(getError(error)));
454 }); 510 });
455 } else if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Mail>()) { 511 } else if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Mail>()) {
456 //TODO 512 //TODO
@@ -487,7 +543,7 @@ public:
487 synchronizeMails(folderRemoteId, m); 543 synchronizeMails(folderRemoteId, m);
488 }, 544 },
489 [=](int progress, int total) { 545 [=](int progress, int total) {
490 SinkLogCtx(mLogCtx) << "Progress: " << progress << " out of " << total; 546 reportProgress(progress, total);
491 //commit every 100 messages 547 //commit every 100 messages
492 if ((progress % 100) == 0) { 548 if ((progress % 100) == 0) {
493 commit(); 549 commit();
@@ -503,6 +559,8 @@ public:
503 return KAsync::value(folders) 559 return KAsync::value(folders)
504 .serialEach<void>([=](const Folder &folder) { 560 .serialEach<void>([=](const Folder &folder) {
505 SinkLog() << "Syncing folder " << folder.path(); 561 SinkLog() << "Syncing folder " << folder.path();
562 //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.
506 QDate dateFilter; 564 QDate dateFilter;
507 auto filter = query.getFilter<ApplicationDomain::Mail::Date>(); 565 auto filter = query.getFilter<ApplicationDomain::Mail::Date>();
508 if (filter.value.canConvert<QDate>()) { 566 if (filter.value.canConvert<QDate>()) {
@@ -510,7 +568,7 @@ public:
510 SinkLog() << " with date-range " << dateFilter; 568 SinkLog() << " with date-range " << dateFilter;
511 } 569 }
512 return synchronizeFolder(imap, folder, dateFilter, syncHeaders) 570 return synchronizeFolder(imap, folder, dateFilter, syncHeaders)
513 .onError([folder](const KAsync::Error &error) { 571 .onError([=](const KAsync::Error &error) {
514 SinkWarning() << "Failed to sync folder: " << folder.path() << "Error: " << error.errorMessage; 572 SinkWarning() << "Failed to sync folder: " << folder.path() << "Error: " << error.errorMessage;
515 }); 573 });
516 }); 574 });
@@ -518,11 +576,8 @@ public:
518 } 576 }
519 }) 577 })
520 .then([=] (const KAsync::Error &error) { 578 .then([=] (const KAsync::Error &error) {
521 if (error) {
522 SinkWarning() << "Error during sync: " << error.errorMessage;
523 }
524 return imap->logout() 579 return imap->logout()
525 .then(KAsync::error(error)); 580 .then(KAsync::error(getError(error)));
526 }); 581 });
527 } 582 }
528 return KAsync::error<void>("Nothing to do"); 583 return KAsync::error<void>("Nothing to do");
@@ -616,11 +671,11 @@ public:
616 if (!folder.getParent().isEmpty()) { 671 if (!folder.getParent().isEmpty()) {
617 parentFolder = syncStore().resolveLocalId(ENTITY_TYPE_FOLDER, folder.getParent()); 672 parentFolder = syncStore().resolveLocalId(ENTITY_TYPE_FOLDER, folder.getParent());
618 } 673 }
619 SinkTrace() << "Creating a new folder: " << parentFolder << folder.getName(); 674 SinkTraceCtx(mLogCtx) << "Creating a new folder: " << parentFolder << folder.getName();
620 auto rid = QSharedPointer<QByteArray>::create(); 675 auto rid = QSharedPointer<QByteArray>::create();
621 auto createFolder = login.then(imap->createSubfolder(parentFolder, folder.getName())) 676 auto createFolder = login.then(imap->createSubfolder(parentFolder, folder.getName()))
622 .then([imap, rid](const QString &createdFolder) { 677 .then([this, imap, rid](const QString &createdFolder) {
623 SinkTrace() << "Finished creating a new folder: " << createdFolder; 678 SinkTraceCtx(mLogCtx) << "Finished creating a new folder: " << createdFolder;
624 *rid = createdFolder.toUtf8(); 679 *rid = createdFolder.toUtf8();
625 }); 680 });
626 if (folder.getSpecialPurpose().isEmpty()) { 681 if (folder.getSpecialPurpose().isEmpty()) {
@@ -636,19 +691,19 @@ public:
636 specialPurposeFolders->insert(SpecialPurpose::getSpecialPurposeType(folder.name()), folder.path()); 691 specialPurposeFolders->insert(SpecialPurpose::getSpecialPurposeType(folder.name()), folder.path());
637 }; 692 };
638 })) 693 }))
639 .then([specialPurposeFolders, folder, imap, parentFolder, rid]() -> KAsync::Job<void> { 694 .then([this, specialPurposeFolders, folder, imap, parentFolder, rid]() -> KAsync::Job<void> {
640 for (const auto &purpose : folder.getSpecialPurpose()) { 695 for (const auto &purpose : folder.getSpecialPurpose()) {
641 if (specialPurposeFolders->contains(purpose)) { 696 if (specialPurposeFolders->contains(purpose)) {
642 auto f = specialPurposeFolders->value(purpose); 697 auto f = specialPurposeFolders->value(purpose);
643 SinkTrace() << "Merging specialpurpose folder with: " << f << " with purpose: " << purpose; 698 SinkTraceCtx(mLogCtx) << "Merging specialpurpose folder with: " << f << " with purpose: " << purpose;
644 *rid = f.toUtf8(); 699 *rid = f.toUtf8();
645 return KAsync::null<void>(); 700 return KAsync::null<void>();
646 } 701 }
647 } 702 }
648 SinkTrace() << "No match found for merging, creating a new folder"; 703 SinkTraceCtx(mLogCtx) << "No match found for merging, creating a new folder";
649 return imap->createSubfolder(parentFolder, folder.getName()) 704 return imap->createSubfolder(parentFolder, folder.getName())
650 .then([imap, rid](const QString &createdFolder) { 705 .then([this, imap, rid](const QString &createdFolder) {
651 SinkTrace() << "Finished creating a new folder: " << createdFolder; 706 SinkTraceCtx(mLogCtx) << "Finished creating a new folder: " << createdFolder;
652 *rid = createdFolder.toUtf8(); 707 *rid = createdFolder.toUtf8();
653 }); 708 });
654 709
@@ -659,18 +714,18 @@ public:
659 return mergeJob; 714 return mergeJob;
660 } 715 }
661 } else if (operation == Sink::Operation_Removal) { 716 } else if (operation == Sink::Operation_Removal) {
662 SinkTrace() << "Removing a folder: " << oldRemoteId; 717 SinkTraceCtx(mLogCtx) << "Removing a folder: " << oldRemoteId;
663 return login.then(imap->remove(oldRemoteId)) 718 return login.then(imap->remove(oldRemoteId))
664 .then([oldRemoteId, imap] { 719 .then([this, oldRemoteId, imap] {
665 SinkTrace() << "Finished removing a folder: " << oldRemoteId; 720 SinkTraceCtx(mLogCtx) << "Finished removing a folder: " << oldRemoteId;
666 return QByteArray(); 721 return QByteArray();
667 }); 722 });
668 } else if (operation == Sink::Operation_Modification) { 723 } else if (operation == Sink::Operation_Modification) {
669 SinkTrace() << "Renaming a folder: " << oldRemoteId << folder.getName(); 724 SinkTraceCtx(mLogCtx) << "Renaming a folder: " << oldRemoteId << folder.getName();
670 auto rid = QSharedPointer<QByteArray>::create(); 725 auto rid = QSharedPointer<QByteArray>::create();
671 return login.then(imap->renameSubfolder(oldRemoteId, folder.getName())) 726 return login.then(imap->renameSubfolder(oldRemoteId, folder.getName()))
672 .then([imap, rid](const QString &createdFolder) { 727 .then([this, imap, rid](const QString &createdFolder) {
673 SinkTrace() << "Finished renaming a folder: " << createdFolder; 728 SinkTraceCtx(mLogCtx) << "Finished renaming a folder: " << createdFolder;
674 *rid = createdFolder.toUtf8(); 729 *rid = createdFolder.toUtf8();
675 }) 730 })
676 .then([rid] { 731 .then([rid] {
@@ -685,6 +740,7 @@ public:
685 int mPort; 740 int mPort;
686 QString mUser; 741 QString mUser;
687 QString mPassword; 742 QString mPassword;
743 int mDaysToSync = 0;
688 QByteArray mResourceInstanceIdentifier; 744 QByteArray mResourceInstanceIdentifier;
689 Imap::SessionCache mSessionCache; 745 Imap::SessionCache mSessionCache;
690}; 746};
@@ -864,6 +920,7 @@ ImapResource::ImapResource(const ResourceContext &resourceContext)
864 synchronizer->mPort = port; 920 synchronizer->mPort = port;
865 synchronizer->mUser = user; 921 synchronizer->mUser = user;
866 synchronizer->mPassword = password; 922 synchronizer->mPassword = password;
923 synchronizer->mDaysToSync = 14;
867 setupSynchronizer(synchronizer); 924 setupSynchronizer(synchronizer);
868 925
869 auto inspector = QSharedPointer<ImapInspector>::create(resourceContext); 926 auto inspector = QSharedPointer<ImapInspector>::create(resourceContext);
@@ -873,7 +930,7 @@ ImapResource::ImapResource(const ResourceContext &resourceContext)
873 inspector->mPassword = password; 930 inspector->mPassword = password;
874 setupInspector(inspector); 931 setupInspector(inspector);
875 932
876 setupPreprocessors(ENTITY_TYPE_MAIL, QVector<Sink::Preprocessor*>() << new SpecialPurposeProcessor(resourceContext.resourceType, resourceContext.instanceId()) << new MailPropertyExtractor); 933 setupPreprocessors(ENTITY_TYPE_MAIL, QVector<Sink::Preprocessor*>() << new SpecialPurposeProcessor << new MailPropertyExtractor);
877 setupPreprocessors(ENTITY_TYPE_FOLDER, QVector<Sink::Preprocessor*>()); 934 setupPreprocessors(ENTITY_TYPE_FOLDER, QVector<Sink::Preprocessor*>());
878} 935}
879 936
@@ -898,14 +955,14 @@ Sink::Resource *ImapResourceFactory::createResource(const ResourceContext &conte
898 955
899void ImapResourceFactory::registerFacades(const QByteArray &name, Sink::FacadeFactory &factory) 956void ImapResourceFactory::registerFacades(const QByteArray &name, Sink::FacadeFactory &factory)
900{ 957{
901 factory.registerFacade<Sink::ApplicationDomain::Mail, ImapResourceMailFacade>(name); 958 factory.registerFacade<ApplicationDomain::Mail, DefaultFacade<ApplicationDomain::Mail>>(name);
902 factory.registerFacade<Sink::ApplicationDomain::Folder, ImapResourceFolderFacade>(name); 959 factory.registerFacade<ApplicationDomain::Folder, DefaultFacade<ApplicationDomain::Folder>>(name);
903} 960}
904 961
905void ImapResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry &registry) 962void ImapResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry &registry)
906{ 963{
907 registry.registerFactory<Sink::ApplicationDomain::Mail, ImapMailAdaptorFactory>(name); 964 registry.registerFactory<ApplicationDomain::Mail, DefaultAdaptorFactory<ApplicationDomain::Mail>>(name);
908 registry.registerFactory<Sink::ApplicationDomain::Folder, ImapFolderAdaptorFactory>(name); 965 registry.registerFactory<ApplicationDomain::Folder, DefaultAdaptorFactory<ApplicationDomain::Folder>>(name);
909} 966}
910 967
911void ImapResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier) 968void ImapResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier)
diff --git a/examples/imapresource/imapserverproxy.cpp b/examples/imapresource/imapserverproxy.cpp
index dabdd8e..0cc43b8 100644
--- a/examples/imapresource/imapserverproxy.cpp
+++ b/examples/imapresource/imapserverproxy.cpp
@@ -69,7 +69,7 @@ static KAsync::Job<T> runJob(KJob *job, const std::function<T(KJob*)> &f)
69 QObject::connect(job, &KJob::result, [&future, f](KJob *job) { 69 QObject::connect(job, &KJob::result, [&future, f](KJob *job) {
70 SinkTrace() << "Job done: " << job->metaObject()->className(); 70 SinkTrace() << "Job done: " << job->metaObject()->className();
71 if (job->error()) { 71 if (job->error()) {
72 SinkWarning() << "Job failed: " << job->errorString(); 72 SinkWarning() << "Job failed: " << job->errorString() << job->metaObject()->className();
73 future.setError(job->error(), job->errorString()); 73 future.setError(job->error(), job->errorString());
74 } else { 74 } else {
75 future.setValue(f(job)); 75 future.setValue(f(job));
@@ -87,7 +87,7 @@ static KAsync::Job<void> runJob(KJob *job)
87 QObject::connect(job, &KJob::result, [&future](KJob *job) { 87 QObject::connect(job, &KJob::result, [&future](KJob *job) {
88 SinkTrace() << "Job done: " << job->metaObject()->className(); 88 SinkTrace() << "Job done: " << job->metaObject()->className();
89 if (job->error()) { 89 if (job->error()) {
90 SinkWarning() << "Job failed: " << job->errorString(); 90 SinkWarning() << "Job failed: " << job->errorString() << job->metaObject()->className();
91 future.setError(job->error(), job->errorString()); 91 future.setError(job->error(), job->errorString());
92 } else { 92 } else {
93 future.setFinished(); 93 future.setFinished();
@@ -159,6 +159,16 @@ KAsync::Job<void> ImapServerProxy::login(const QString &username, const QString
159 // SinkTrace() << "Found personal namespaces: " << mNamespaces.personal; 159 // SinkTrace() << "Found personal namespaces: " << mNamespaces.personal;
160 // SinkTrace() << "Found shared namespaces: " << mNamespaces.shared; 160 // SinkTrace() << "Found shared namespaces: " << mNamespaces.shared;
161 // SinkTrace() << "Found user namespaces: " << mNamespaces.user; 161 // SinkTrace() << "Found user namespaces: " << mNamespaces.user;
162 }).then([=] (const KAsync::Error &error) {
163 if (error) {
164 if (error.errorCode == KIMAP2::LoginJob::ErrorCode::ERR_COULD_NOT_CONNECT) {
165 return KAsync::error(CouldNotConnectError, "Failed to connect: " + error.errorMessage);
166 } else if (error.errorCode == KIMAP2::LoginJob::ErrorCode::ERR_SSL_HANDSHAKE_FAILED) {
167 return KAsync::error(SslHandshakeError, "Ssl handshake failed: " + error.errorMessage);
168 }
169 return KAsync::error(error);
170 }
171 return KAsync::null();
162 }); 172 });
163} 173}
164 174
diff --git a/examples/imapresource/imapserverproxy.h b/examples/imapresource/imapserverproxy.h
index 6eb47ee..872f032 100644
--- a/examples/imapresource/imapserverproxy.h
+++ b/examples/imapresource/imapserverproxy.h
@@ -29,6 +29,12 @@
29 29
30namespace Imap { 30namespace Imap {
31 31
32enum ErrorCode {
33 NoError,
34 CouldNotConnectError,
35 SslHandshakeError
36};
37
32namespace Flags 38namespace Flags
33{ 39{
34 /// The flag for a message being seen (i.e. opened by user). 40 /// The flag for a message being seen (i.e. opened by user).
diff --git a/examples/maildirresource/CMakeLists.txt b/examples/maildirresource/CMakeLists.txt
index e4d113c..a8f0359 100644
--- a/examples/maildirresource/CMakeLists.txt
+++ b/examples/maildirresource/CMakeLists.txt
@@ -5,13 +5,12 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
5 5
6find_package(KF5 COMPONENTS REQUIRED Mime) 6find_package(KF5 COMPONENTS REQUIRED Mime)
7 7
8add_library(${PROJECT_NAME} SHARED facade.cpp maildirresource.cpp domainadaptor.cpp) 8add_library(${PROJECT_NAME} SHARED facade.cpp maildirresource.cpp libmaildir/maildir.cpp libmaildir/keycache.cpp)
9qt5_use_modules(${PROJECT_NAME} Core Network) 9qt5_use_modules(${PROJECT_NAME} Core Network)
10target_link_libraries(${PROJECT_NAME} sink maildir KF5::Mime) 10target_link_libraries(${PROJECT_NAME} sink KF5::Mime)
11 11
12install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH}) 12install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH})
13 13
14add_definitions(-DTESTDATAPATH="${CMAKE_CURRENT_SOURCE_DIR}/tests/data") 14add_definitions(-DTESTDATAPATH="${CMAKE_CURRENT_SOURCE_DIR}/tests/data")
15 15
16add_subdirectory(libmaildir)
17add_subdirectory(tests) 16add_subdirectory(tests)
diff --git a/examples/maildirresource/domainadaptor.cpp b/examples/maildirresource/domainadaptor.cpp
deleted file mode 100644
index 71b2354..0000000
--- a/examples/maildirresource/domainadaptor.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
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 "domainadaptor.h"
21
22using namespace flatbuffers;
23
24MaildirMailAdaptorFactory::MaildirMailAdaptorFactory()
25 : DomainTypeAdaptorFactory()
26{
27
28}
29
30MaildirFolderAdaptorFactory::MaildirFolderAdaptorFactory()
31 : DomainTypeAdaptorFactory()
32{
33
34}
35
diff --git a/examples/maildirresource/domainadaptor.h b/examples/maildirresource/domainadaptor.h
deleted file mode 100644
index 700d2e5..0000000
--- a/examples/maildirresource/domainadaptor.h
+++ /dev/null
@@ -1,38 +0,0 @@
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#pragma once
20
21#include <common/domainadaptor.h>
22#include "mail_generated.h"
23#include "folder_generated.h"
24#include "dummy_generated.h"
25
26class MaildirMailAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Mail, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder>
27{
28public:
29 MaildirMailAdaptorFactory();
30 virtual ~MaildirMailAdaptorFactory() {};
31};
32
33class MaildirFolderAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Folder, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder>
34{
35public:
36 MaildirFolderAdaptorFactory();
37 virtual ~MaildirFolderAdaptorFactory() {};
38};
diff --git a/examples/maildirresource/libmaildir/CMakeLists.txt b/examples/maildirresource/libmaildir/CMakeLists.txt
deleted file mode 100644
index e7803f5..0000000
--- a/examples/maildirresource/libmaildir/CMakeLists.txt
+++ /dev/null
@@ -1,8 +0,0 @@
1# add_subdirectory( tests )
2
3set(maildir_LIB_SRCS keycache.cpp maildir.cpp)
4
5add_library(maildir ${LIBRARY_TYPE} ${maildir_LIB_SRCS})
6qt5_use_modules(maildir Core Network)
7# set_target_properties(maildir PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} )
8install(TARGETS maildir ${INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/examples/maildirresource/libmaildir/maildir.cpp b/examples/maildirresource/libmaildir/maildir.cpp
index a889ea2..203f6a6 100644
--- a/examples/maildirresource/libmaildir/maildir.cpp
+++ b/examples/maildirresource/libmaildir/maildir.cpp
@@ -1,5 +1,6 @@
1/* 1/*
2 Copyright (c) 2007 Till Adam <adam@kde.org> 2 Copyright (c) 2007 Till Adam <adam@kde.org>
3 Copyright (c) 2017 Christian Mollekopf <mollekopf@kolabsys.com>
3 4
4 This library is free software; you can redistribute it and/or modify it 5 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by 6 under the terms of the GNU Library General Public License as published by
@@ -24,7 +25,6 @@
24#include <QDirIterator> 25#include <QDirIterator>
25#include <QFileInfo> 26#include <QFileInfo>
26#include <QHostInfo> 27#include <QHostInfo>
27#include <QUuid>
28#include <QLoggingCategory> 28#include <QLoggingCategory>
29 29
30Q_LOGGING_CATEGORY(log, "maildir"); 30Q_LOGGING_CATEGORY(log, "maildir");
diff --git a/examples/maildirresource/libmaildir/maildir.h b/examples/maildirresource/libmaildir/maildir.h
index c10b046..a72f2bc 100644
--- a/examples/maildirresource/libmaildir/maildir.h
+++ b/examples/maildirresource/libmaildir/maildir.h
@@ -1,5 +1,6 @@
1/* 1/*
2 Copyright (c) 2007 Till Adam <adam@kde.org> 2 Copyright (c) 2007 Till Adam <adam@kde.org>
3 Copyright (c) 2017 Christian Mollekopf <mollekopf@kolabsys.com>
3 4
4 This library is free software; you can redistribute it and/or modify it 5 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by 6 under the terms of the GNU Library General Public License as published by
@@ -20,9 +21,6 @@
20#ifndef MAILDIR_H 21#ifndef MAILDIR_H
21#define MAILDIR_H 22#define MAILDIR_H
22 23
23
24#include "maildir_export.h"
25
26#include <QString> 24#include <QString>
27#include <QStringList> 25#include <QStringList>
28 26
@@ -30,7 +28,7 @@ class QDateTime;
30 28
31namespace KPIM { 29namespace KPIM {
32 30
33class MAILDIR_EXPORT Maildir 31class Maildir
34{ 32{
35public: 33public:
36 /** 34 /**
diff --git a/examples/maildirresource/libmaildir/maildir_export.h b/examples/maildirresource/libmaildir/maildir_export.h
deleted file mode 100644
index b330fd0..0000000
--- a/examples/maildirresource/libmaildir/maildir_export.h
+++ /dev/null
@@ -1,43 +0,0 @@
1/* This file is part of the KDE project
2 Copyright (C) 2007 David Faure <faure@kde.org>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library 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 GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
18*/
19
20#ifndef MAILDIR_EXPORT_H
21#define MAILDIR_EXPORT_H
22
23/* needed for KDE_EXPORT and KDE_IMPORT macros */
24// #include <kdemacros.h>
25
26#ifndef MAILDIR_EXPORT
27// # if defined(KDEPIM_STATIC_LIBS)
28// #<{(| No export/import for static libraries |)}>#
29# define MAILDIR_EXPORT
30// # elif defined(MAKE_MAILDIR_LIB)
31// #<{(| We are building this library |)}>#
32// # define MAILDIR_EXPORT KDE_EXPORT
33// # else
34// #<{(| We are using this library |)}>#
35// # define MAILDIR_EXPORT KDE_IMPORT
36// # endif
37#endif
38//
39// # ifndef MAILDIR_EXPORT_DEPRECATED
40// # define MAILDIR_EXPORT_DEPRECATED KDE_DEPRECATED MAILDIR_EXPORT
41// # endif
42
43#endif
diff --git a/examples/maildirresource/maildirresource.cpp b/examples/maildirresource/maildirresource.cpp
index 813d84f..40bab37 100644
--- a/examples/maildirresource/maildirresource.cpp
+++ b/examples/maildirresource/maildirresource.cpp
@@ -37,7 +37,7 @@
37 37
38#include <QDir> 38#include <QDir>
39#include <QDirIterator> 39#include <QDirIterator>
40#include <KMime/KMime/KMimeMessage> 40#include <KMime/KMimeMessage>
41 41
42//This is the resources entity type, and not the domain type 42//This is the resources entity type, and not the domain type
43#define ENTITY_TYPE_MAIL "mail" 43#define ENTITY_TYPE_MAIL "mail"
@@ -360,12 +360,12 @@ public:
360 360
361 KAsync::Job<void> synchronizeWithSource(const Sink::QueryBase &query) Q_DECL_OVERRIDE 361 KAsync::Job<void> synchronizeWithSource(const Sink::QueryBase &query) Q_DECL_OVERRIDE
362 { 362 {
363 auto job = KAsync::start<void>([this] { 363 auto job = KAsync::start([this] {
364 KPIM::Maildir maildir(mMaildirPath, true); 364 KPIM::Maildir maildir(mMaildirPath, true);
365 if (!maildir.isValid(false)) { 365 if (!maildir.isValid(false)) {
366 return KAsync::error<void>(1, "Maildir path doesn't point to a valid maildir: " + mMaildirPath); 366 return KAsync::error(ApplicationDomain::ConfigurationError, "Maildir path doesn't point to a valid maildir: " + mMaildirPath);
367 } 367 }
368 return KAsync::null<void>(); 368 return KAsync::null();
369 }); 369 });
370 370
371 if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Folder>()) { 371 if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Folder>()) {
@@ -551,7 +551,7 @@ MaildirResource::MaildirResource(const Sink::ResourceContext &resourceContext)
551 setupSynchronizer(synchronizer); 551 setupSynchronizer(synchronizer);
552 setupInspector(QSharedPointer<MaildirInspector>::create(resourceContext)); 552 setupInspector(QSharedPointer<MaildirInspector>::create(resourceContext));
553 553
554 setupPreprocessors(ENTITY_TYPE_MAIL, QVector<Sink::Preprocessor*>() << new SpecialPurposeProcessor(resourceContext.resourceType, resourceContext.instanceId()) << new MaildirMimeMessageMover(resourceContext.instanceId(), mMaildirPath) << new MaildirMailPropertyExtractor); 554 setupPreprocessors(ENTITY_TYPE_MAIL, QVector<Sink::Preprocessor*>() << new SpecialPurposeProcessor << new MaildirMimeMessageMover(resourceContext.instanceId(), mMaildirPath) << new MaildirMailPropertyExtractor);
555 setupPreprocessors(ENTITY_TYPE_FOLDER, QVector<Sink::Preprocessor*>() << new FolderPreprocessor(mMaildirPath)); 555 setupPreprocessors(ENTITY_TYPE_FOLDER, QVector<Sink::Preprocessor*>() << new FolderPreprocessor(mMaildirPath));
556 556
557 KPIM::Maildir dir(mMaildirPath, true); 557 KPIM::Maildir dir(mMaildirPath, true);
@@ -596,8 +596,8 @@ void MaildirResourceFactory::registerFacades(const QByteArray &name, Sink::Facad
596 596
597void MaildirResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry &registry) 597void MaildirResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry &registry)
598{ 598{
599 registry.registerFactory<Sink::ApplicationDomain::Mail, MaildirMailAdaptorFactory>(name); 599 registry.registerFactory<ApplicationDomain::Mail, DefaultAdaptorFactory<ApplicationDomain::Mail>>(name);
600 registry.registerFactory<Sink::ApplicationDomain::Folder, MaildirFolderAdaptorFactory>(name); 600 registry.registerFactory<ApplicationDomain::Folder, DefaultAdaptorFactory<ApplicationDomain::Folder>>(name);
601} 601}
602 602
603void MaildirResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier) 603void MaildirResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier)
diff --git a/examples/mailtransportresource/mailtransport.cpp b/examples/mailtransportresource/mailtransport.cpp
index 3d56af9..84c1556 100644
--- a/examples/mailtransportresource/mailtransport.cpp
+++ b/examples/mailtransportresource/mailtransport.cpp
@@ -41,17 +41,17 @@ static size_t payload_source(void *ptr, size_t size, size_t nmemb, void *userp)
41 struct upload_status *upload_ctx = (struct upload_status *)userp; 41 struct upload_status *upload_ctx = (struct upload_status *)userp;
42 const char *data; 42 const char *data;
43 43
44 if((size == 0) || (nmemb == 0) || ((size*nmemb) < 1)) { 44 if ((size == 0) || (nmemb == 0) || ((size*nmemb) < 1)) {
45 return 0; 45 return 0;
46 } 46 }
47 47
48 data = &upload_ctx->data[upload_ctx->offset]; 48 data = &upload_ctx->data[upload_ctx->offset];
49 if(data) { 49 if (data) {
50 size_t len = strlen(data); 50 size_t len = strlen(data);
51 if (len > size * nmemb) { 51 if (len > size * nmemb) {
52 len = size * nmemb; 52 len = size * nmemb;
53 } 53 }
54 fprintf(stderr, "read n bytes: %d\n", int(len)); 54 fprintf(stdout, "read n bytes: %d\n", int(len));
55 memcpy(ptr, data, len); 55 memcpy(ptr, data, len);
56 upload_ctx->offset += len; 56 upload_ctx->offset += len;
57 return len; 57 return len;
@@ -69,8 +69,17 @@ static int progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow
69 return 0; 69 return 0;
70} 70}
71 71
72static int debug_callback(CURL *handle,
73 curl_infotype type,
74 char *data,
75 size_t size,
76 void *userptr)
77{
78 fprintf(stdout, "CURL_DEBUG: %s", data);
79 return 0;
80}
72 81
73bool sendMessageCurl(const char *to[], int numTos, const char *cc[], int numCcs, const char *msg, bool useTls, const char* from, const char *username, const char *password, const char *server, bool verifyPeer, const QByteArray &cacert) 82bool sendMessageCurl(const char *to[], int numTos, const char *cc[], int numCcs, const char *msg, bool useTls, const char* from, const char *username, const char *password, const char *server, bool verifyPeer, const QByteArray &cacert, QByteArray &errorMessage)
74{ 83{
75 CURL *curl; 84 CURL *curl;
76 CURLcode res = CURLE_OK; 85 CURLcode res = CURLE_OK;
@@ -88,7 +97,7 @@ bool sendMessageCurl(const char *to[], int numTos, const char *cc[], int numCcs,
88 curl_easy_setopt(curl, CURLOPT_URL, server); 97 curl_easy_setopt(curl, CURLOPT_URL, server);
89 98
90 if (useTls) { 99 if (useTls) {
91 curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_TRY); 100 curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL);
92 } 101 }
93 102
94 if (!verifyPeer) { 103 if (!verifyPeer) {
@@ -121,19 +130,29 @@ bool sendMessageCurl(const char *to[], int numTos, const char *cc[], int numCcs,
121 /* Since the traffic will be encrypted, it is very useful to turn on debug 130 /* Since the traffic will be encrypted, it is very useful to turn on debug
122 * information within libcurl to see what is happening during the transfer. 131 * information within libcurl to see what is happening during the transfer.
123 */ 132 */
124 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); 133 // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
134 curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, debug_callback);
125 135
126 // curl_easy_setopt(curl, CURLOPT_TIMEOUT, 1L); 136 // curl_easy_setopt(curl, CURLOPT_TIMEOUT, 1L);
127 //Connection timeout of 10s 137 //Connection timeout of 40s
128 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L); 138 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 40L);
129 139
130 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); 140 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
131 curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback); 141 curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
142 char errorBuffer[CURL_ERROR_SIZE];
143 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorBuffer);
132 144
133 res = curl_easy_perform(curl); 145 res = curl_easy_perform(curl);
134 if(res != CURLE_OK) { 146 if(res != CURLE_OK) {
135 fprintf(stderr, "curl_easy_perform() failed: %s\n", 147 errorMessage += curl_easy_strerror(res);
136 curl_easy_strerror(res)); 148 errorMessage += "; ";
149 }
150 long http_code = 0;
151 curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code);
152 if (http_code == 200 && res != CURLE_ABORTED_BY_CALLBACK) {
153 //Succeeded
154 } else {
155 errorMessage += errorBuffer;
137 } 156 }
138 curl_slist_free_all(recipients); 157 curl_slist_free_all(recipients);
139 curl_easy_cleanup(curl); 158 curl_easy_cleanup(curl);
@@ -147,8 +166,6 @@ bool sendMessageCurl(const char *to[], int numTos, const char *cc[], int numCcs,
147bool MailTransport::sendMessage(const KMime::Message::Ptr &message, const QByteArray &server, const QByteArray &username, const QByteArray &password, const QByteArray &cacert, MailTransport::Options options) 166bool MailTransport::sendMessage(const KMime::Message::Ptr &message, const QByteArray &server, const QByteArray &username, const QByteArray &password, const QByteArray &cacert, MailTransport::Options options)
148{ 167{
149 QByteArray msg = message->encodedContent(); 168 QByteArray msg = message->encodedContent();
150 SinkLog() << "Sending message " << server << username << password << cacert;
151 SinkTrace() << "Sending message " << msg;
152 169
153 QByteArray from(message->from(true)->mailboxes().isEmpty() ? QByteArray() : message->from(true)->mailboxes().first().address()); 170 QByteArray from(message->from(true)->mailboxes().isEmpty() ? QByteArray() : message->from(true)->mailboxes().first().address());
154 QList<QByteArray> toList; 171 QList<QByteArray> toList;
@@ -159,8 +176,11 @@ bool MailTransport::sendMessage(const KMime::Message::Ptr &message, const QByteA
159 for (const auto &mb : message->cc(true)->mailboxes()) { 176 for (const auto &mb : message->cc(true)->mailboxes()) {
160 ccList << mb.address(); 177 ccList << mb.address();
161 } 178 }
162 bool verifyPeer = options & VerifyPeers; 179 const bool verifyPeer = options.testFlag(VerifyPeers);
163 bool useTls = options & UseTls; 180 const bool useTls = options.testFlag(UseTls);
181
182 SinkLog() << "Sending message " << server << username << password << "CaCert: " << cacert << "Use tls: " << useTls << " Verify peer: " << verifyPeer;
183 SinkTrace() << "Sending message " << msg;
164 184
165 const int numTos = toList.size(); 185 const int numTos = toList.size();
166 const char* to[numTos]; 186 const char* to[numTos];
@@ -173,6 +193,14 @@ bool MailTransport::sendMessage(const KMime::Message::Ptr &message, const QByteA
173 for (int i = 0; i < numCcs; i++) { 193 for (int i = 0; i < numCcs; i++) {
174 cc[i] = ccList.at(i); 194 cc[i] = ccList.at(i);
175 } 195 }
176 196 //Because curl will fail with smtps, but it won't tell you why.
177 return sendMessageCurl(to, numTos, cc, numCcs, msg, useTls, from.isEmpty() ? nullptr : from, username, password, server, verifyPeer, cacert); 197 auto serverAddress = server;
198 serverAddress.replace("smtps://", "smtp://");
199
200 QByteArray errorMessage;
201 auto ret = sendMessageCurl(to, numTos, cc, numCcs, msg, useTls, from.isEmpty() ? nullptr : from, username, password, serverAddress, verifyPeer, cacert, errorMessage);
202 if (!ret) {
203 SinkWarning() << "Failed to send message: " << errorMessage;
204 }
205 return ret;
178} 206}
diff --git a/examples/mailtransportresource/mailtransport.h b/examples/mailtransportresource/mailtransport.h
index 3ef4a6d..662fdc9 100644
--- a/examples/mailtransportresource/mailtransport.h
+++ b/examples/mailtransportresource/mailtransport.h
@@ -26,8 +26,8 @@
26namespace MailTransport 26namespace MailTransport
27{ 27{
28 enum Option { 28 enum Option {
29 UseTls, 29 UseTls = 1,
30 VerifyPeers 30 VerifyPeers = 2
31 }; 31 };
32 Q_DECLARE_FLAGS(Options, Option); 32 Q_DECLARE_FLAGS(Options, Option);
33 33
diff --git a/examples/mailtransportresource/mailtransportresource.cpp b/examples/mailtransportresource/mailtransportresource.cpp
index 88a90c6..8a4ef92 100644
--- a/examples/mailtransportresource/mailtransportresource.cpp
+++ b/examples/mailtransportresource/mailtransportresource.cpp
@@ -30,7 +30,6 @@
30#include <KMime/Message> 30#include <KMime/Message>
31 31
32#include "mailtransport.h" 32#include "mailtransport.h"
33#include "mail_generated.h"
34#include "inspection.h" 33#include "inspection.h"
35#include <synchronizer.h> 34#include <synchronizer.h>
36#include <log.h> 35#include <log.h>
@@ -44,6 +43,47 @@ SINK_DEBUG_AREA("mailtransportresource")
44 43
45using namespace Sink; 44using namespace Sink;
46 45
46class MailtransportPreprocessor : public Sink::Preprocessor
47{
48public:
49 MailtransportPreprocessor() : Sink::Preprocessor() {}
50
51 QByteArray getTargetResource()
52 {
53 using namespace Sink::ApplicationDomain;
54
55 auto resource = Store::readOne<ApplicationDomain::SinkResource>(Query{}.filter(resourceInstanceIdentifier()).request<ApplicationDomain::SinkResource::Account>());
56 if (resource.identifier().isEmpty()) {
57 SinkWarning() << "Failed to retrieve this resource: " << resourceInstanceIdentifier();
58 }
59 Query query;
60 query.containsFilter<ApplicationDomain::SinkResource::Capabilities>(ApplicationDomain::ResourceCapabilities::Mail::sent);
61 query.filter<ApplicationDomain::SinkResource::Account>(resource.getAccount());
62 auto targetResource = Store::readOne<ApplicationDomain::SinkResource>(query);
63 if (targetResource.identifier().isEmpty()) {
64 SinkWarning() << "Failed to find target resource: " << targetResource.identifier();
65 }
66 return targetResource.identifier();
67 }
68
69 virtual Result processModification(Type type, const ApplicationDomain::ApplicationDomainType &current, ApplicationDomain::ApplicationDomainType &diff) Q_DECL_OVERRIDE
70 {
71 if (type == Preprocessor::Modification) {
72 using namespace Sink::ApplicationDomain;
73 if (diff.changedProperties().contains(Mail::Trash::name)) {
74 //Move back to regular resource
75 diff.setResource(getTargetResource());
76 return {MoveToResource};
77 } else if (diff.changedProperties().contains(Mail::Draft::name)) {
78 //Move back to regular resource
79 diff.setResource(getTargetResource());
80 return {MoveToResource};
81 }
82 }
83 return {NoAction};
84 }
85};
86
47class MailtransportSynchronizer : public Sink::Synchronizer { 87class MailtransportSynchronizer : public Sink::Synchronizer {
48public: 88public:
49 MailtransportSynchronizer(const Sink::ResourceContext &resourceContext) 89 MailtransportSynchronizer(const Sink::ResourceContext &resourceContext)
@@ -55,17 +95,22 @@ public:
55 95
56 KAsync::Job<void> send(const ApplicationDomain::Mail &mail, const MailtransportResource::Settings &settings) 96 KAsync::Job<void> send(const ApplicationDomain::Mail &mail, const MailtransportResource::Settings &settings)
57 { 97 {
58 return KAsync::start<void>([=] { 98 return KAsync::start([=] {
59 if (!syncStore().readValue(mail.identifier()).isEmpty()) { 99 if (!syncStore().readValue(mail.identifier()).isEmpty()) {
60 SinkLog() << "Mail is already sent: " << mail.identifier(); 100 SinkLog() << "Mail is already sent: " << mail.identifier();
61 return KAsync::null(); 101 return KAsync::null();
62 } 102 }
103 emitNotification(Notification::Info, ApplicationDomain::SyncInProgress, "Sending message.", {}, {mail.identifier()});
63 const auto data = mail.getMimeMessage(); 104 const auto data = mail.getMimeMessage();
64 auto msg = KMime::Message::Ptr::create(); 105 auto msg = KMime::Message::Ptr::create();
65 msg->setHead(KMime::CRLFtoLF(data)); 106 msg->setHead(KMime::CRLFtoLF(data));
66 msg->parse(); 107 msg->parse();
67 if (settings.testMode) { 108 if (settings.testMode) {
68 SinkLog() << "I would totally send that mail, but I'm in test mode." << mail.identifier(); 109 auto subject = msg->subject(true)->asUnicodeString();
110 SinkLog() << "I would totally send that mail, but I'm in test mode." << mail.identifier() << subject;
111 if (!subject.contains("send")) {
112 return KAsync::error("Failed to send the message.");
113 }
69 auto path = resourceStorageLocation(mResourceInstanceIdentifier) + "/test/"; 114 auto path = resourceStorageLocation(mResourceInstanceIdentifier) + "/test/";
70 SinkTrace() << path; 115 SinkTrace() << path;
71 QDir dir; 116 QDir dir;
@@ -77,11 +122,16 @@ public:
77 } else { 122 } else {
78 MailTransport::Options options; 123 MailTransport::Options options;
79 if (settings.server.contains("smtps")) { 124 if (settings.server.contains("smtps")) {
80 options &= MailTransport::UseTls; 125 options |= MailTransport::UseTls;
81 } 126 }
82 if (!MailTransport::sendMessage(msg, settings.server.toUtf8(), settings.username.toUtf8(), settings.password.toUtf8(), settings.cacert.toUtf8(), options)) { 127 if (!MailTransport::sendMessage(msg, settings.server.toUtf8(), settings.username.toUtf8(), settings.password.toUtf8(), settings.cacert.toUtf8(), options)) {
83 SinkWarning() << "Failed to send message: " << mail; 128 SinkWarning() << "Failed to send message: " << mail;
129 emitNotification(Notification::Warning, ApplicationDomain::SyncError, "Failed to send message.", {}, {mail.identifier()});
130 emitNotification(Notification::Warning, ApplicationDomain::TransmissionError, "Failed to send message.", {}, {mail.identifier()});
84 return KAsync::error("Failed to send the message."); 131 return KAsync::error("Failed to send the message.");
132 } else {
133 emitNotification(Notification::Info, ApplicationDomain::SyncSuccess, "Message successfully sent.", {}, {mail.identifier()});
134 emitNotification(Notification::Info, ApplicationDomain::TransmissionSuccess, "Message successfully sent.", {}, {mail.identifier()});
85 } 135 }
86 } 136 }
87 syncStore().writeValue(mail.identifier(), "sent"); 137 syncStore().writeValue(mail.identifier(), "sent");
@@ -100,9 +150,8 @@ public:
100 query.filter<ApplicationDomain::SinkResource::Account>(resource.getAccount()); 150 query.filter<ApplicationDomain::SinkResource::Account>(resource.getAccount());
101 return Store::fetchOne<ApplicationDomain::SinkResource>(query) 151 return Store::fetchOne<ApplicationDomain::SinkResource>(query)
102 .then([this, modifiedMail](const ApplicationDomain::SinkResource &resource) { 152 .then([this, modifiedMail](const ApplicationDomain::SinkResource &resource) {
103 //First modify the mail to have the sent property set to true 153 //Modify the mail to have the sent property set to true, and move it to the new resource.
104 modify(modifiedMail, resource.identifier(), true); 154 modify(modifiedMail, resource.identifier(), true);
105 return KAsync::null<void>();
106 }); 155 });
107 }); 156 });
108 } 157 }
@@ -112,12 +161,10 @@ public:
112 return KAsync::start<void>([this]() { 161 return KAsync::start<void>([this]() {
113 QList<ApplicationDomain::Mail> toSend; 162 QList<ApplicationDomain::Mail> toSend;
114 SinkLog() << "Looking for mails to send."; 163 SinkLog() << "Looking for mails to send.";
115 store().readAll<ApplicationDomain::Mail>([&](const ApplicationDomain::Mail &mail) -> bool { 164 store().readAll<ApplicationDomain::Mail>([&](const ApplicationDomain::Mail &mail) {
116 SinkTrace() << "Found mail: " << mail.identifier();
117 if (!mail.getSent()) { 165 if (!mail.getSent()) {
118 toSend << mail; 166 toSend << mail;
119 } 167 }
120 return true;
121 }); 168 });
122 SinkLog() << "Found " << toSend.size() << " mails to send"; 169 SinkLog() << "Found " << toSend.size() << " mails to send";
123 auto job = KAsync::null<void>(); 170 auto job = KAsync::null<void>();
@@ -192,7 +239,7 @@ MailtransportResource::MailtransportResource(const Sink::ResourceContext &resour
192 setupSynchronizer(synchronizer); 239 setupSynchronizer(synchronizer);
193 setupInspector(QSharedPointer<MailtransportInspector>::create(resourceContext)); 240 setupInspector(QSharedPointer<MailtransportInspector>::create(resourceContext));
194 241
195 setupPreprocessors(ENTITY_TYPE_MAIL, QVector<Sink::Preprocessor*>() << new MailPropertyExtractor); 242 setupPreprocessors(ENTITY_TYPE_MAIL, QVector<Sink::Preprocessor*>() << new MailPropertyExtractor << new MailtransportPreprocessor);
196} 243}
197 244
198MailtransportResourceFactory::MailtransportResourceFactory(QObject *parent) 245MailtransportResourceFactory::MailtransportResourceFactory(QObject *parent)
diff --git a/examples/mailtransportresource/tests/mailtransporttest.cpp b/examples/mailtransportresource/tests/mailtransporttest.cpp
index 3b848b3..e4cc447 100644
--- a/examples/mailtransportresource/tests/mailtransporttest.cpp
+++ b/examples/mailtransportresource/tests/mailtransporttest.cpp
@@ -47,7 +47,8 @@ private slots:
47 47
48 void cleanup() 48 void cleanup()
49 { 49 {
50 VERIFYEXEC(ResourceControl::shutdown(mResourceInstanceIdentifier)); 50 VERIFYEXEC(Store::removeDataFromDisk(mResourceInstanceIdentifier));
51 VERIFYEXEC(Store::removeDataFromDisk(mStorageResource));
51 } 52 }
52 53
53 void init() 54 void init()
@@ -58,7 +59,8 @@ private slots:
58 void testSendMail() 59 void testSendMail()
59 { 60 {
60 auto message = KMime::Message::Ptr::create(); 61 auto message = KMime::Message::Ptr::create();
61 message->subject(true)->fromUnicodeString(QString::fromLatin1("Foobar"), "utf8"); 62 message->messageID(true)->generate("foo.com");
63 message->subject(true)->fromUnicodeString(QString::fromLatin1("send: Foobar"), "utf8");
62 message->assemble(); 64 message->assemble();
63 65
64 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier); 66 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier);
@@ -67,9 +69,10 @@ private slots:
67 VERIFYEXEC(Store::create(mail)); 69 VERIFYEXEC(Store::create(mail));
68 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier)); 70 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));
69 71
72 //FIXME the email is sent already because changereplay kicks of automatically
70 //Ensure the mail is queryable in the outbox 73 //Ensure the mail is queryable in the outbox
71 auto mailInOutbox = Store::readOne<ApplicationDomain::Mail>(Query().resourceFilter(mResourceInstanceIdentifier).filter<Mail::Sent>(false).request<Mail::Subject>().request<Mail::Folder>().request<Mail::MimeMessage>().request<Mail::Sent>()); 74 // auto mailInOutbox = Store::readOne<ApplicationDomain::Mail>(Query().resourceFilter(mResourceInstanceIdentifier).filter<Mail::Sent>(false).request<Mail::Subject>().request<Mail::Folder>().request<Mail::MimeMessage>().request<Mail::Sent>());
72 QVERIFY(!mailInOutbox.identifier().isEmpty()); 75 // QVERIFY(!mailInOutbox.identifier().isEmpty());
73 76
74 //Ensure the mail is sent and moved to the sent mail folder on sync 77 //Ensure the mail is sent and moved to the sent mail folder on sync
75 VERIFYEXEC(Store::synchronize(Query().resourceFilter(mResourceInstanceIdentifier))); 78 VERIFYEXEC(Store::synchronize(Query().resourceFilter(mResourceInstanceIdentifier)));
@@ -81,7 +84,37 @@ private slots:
81 QVERIFY(!mailInSentMailFolder.getSubject().isEmpty()); 84 QVERIFY(!mailInSentMailFolder.getSubject().isEmpty());
82 } 85 }
83 86
84 //TODO test mail that fails to be sent. add a special header to the mail and have the resource fail sending. Ensure we can modify the mail to fix sending of the message. 87 void testSendFailure()
88 {
89 auto message = KMime::Message::Ptr::create();
90 message->messageID(true)->generate("foo.com");
91 message->subject(true)->fromUnicodeString(QString::fromLatin1("error: Foobar"), "utf8");
92 message->assemble();
93
94 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier);
95 mail.setMimeMessage(message->encodedContent());
96
97 VERIFYEXEC(Store::create(mail));
98 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));
99
100 //Ensure the mail is queryable in the outbox
101 auto mailInOutbox = Store::readOne<ApplicationDomain::Mail>(Query().resourceFilter(mResourceInstanceIdentifier).filter<Mail::Sent>(false));
102 QVERIFY(!mailInOutbox.identifier().isEmpty());
103
104 //Modify back to drafts
105 auto modifiedMail = mailInOutbox;
106 modifiedMail.setDraft(true);
107 VERIFYEXEC(Store::modify(modifiedMail));
108 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));
109
110 QTest::qWait(100);
111 auto mailsInOutbox = Store::read<ApplicationDomain::Mail>(Query().resourceFilter(mResourceInstanceIdentifier));
112 QCOMPARE(mailsInOutbox.size(), 0);
113
114 auto mailsInDrafts = Store::read<ApplicationDomain::Mail>(Query().resourceFilter(mStorageResource));
115 QCOMPARE(mailsInDrafts.size(), 1);
116
117 }
85 118
86}; 119};
87 120
diff --git a/sinksh/sinksh_utils.cpp b/sinksh/sinksh_utils.cpp
index 3bbffef..3a3484a 100644
--- a/sinksh/sinksh_utils.cpp
+++ b/sinksh/sinksh_utils.cpp
@@ -62,6 +62,10 @@ QList<QByteArray> requestedProperties(const QString &type)
62 << Mail::Date::name; 62 << Mail::Date::name;
63 } else if (type == getTypeName<Event>()) { 63 } else if (type == getTypeName<Event>()) {
64 return QList<QByteArray>() << Event::Summary::name; 64 return QList<QByteArray>() << Event::Summary::name;
65 } else if (type == getTypeName<Contact>()) {
66 return QList<QByteArray>() << Contact::Fn::name << Contact::Emails::name << Contact::Addressbook::name;
67 } else if (type == getTypeName<Addressbook>()) {
68 return QList<QByteArray>() << Addressbook::Name::name << Addressbook::Parent::name;
65 } else if (type == getTypeName<SinkResource>()) { 69 } else if (type == getTypeName<SinkResource>()) {
66 return QList<QByteArray>() << SinkResource::ResourceType::name << SinkResource::Account::name << SinkResource::Capabilities::name; 70 return QList<QByteArray>() << SinkResource::ResourceType::name << SinkResource::Account::name << SinkResource::Capabilities::name;
67 } else if (type == getTypeName<SinkAccount>()) { 71 } else if (type == getTypeName<SinkAccount>()) {
diff --git a/sinksh/syntax_modules/sink_clear.cpp b/sinksh/syntax_modules/sink_clear.cpp
index 1537ecd..0f0f296 100644
--- a/sinksh/syntax_modules/sink_clear.cpp
+++ b/sinksh/syntax_modules/sink_clear.cpp
@@ -23,8 +23,6 @@
23 23
24#include "common/resource.h" 24#include "common/resource.h"
25#include "common/storage.h" 25#include "common/storage.h"
26#include "common/domain/event.h"
27#include "common/domain/folder.h"
28#include "common/resourceconfig.h" 26#include "common/resourceconfig.h"
29#include "common/log.h" 27#include "common/log.h"
30#include "common/storage.h" 28#include "common/storage.h"
diff --git a/sinksh/syntax_modules/sink_count.cpp b/sinksh/syntax_modules/sink_count.cpp
index 5ab9ca5..9b6aa1e 100644
--- a/sinksh/syntax_modules/sink_count.cpp
+++ b/sinksh/syntax_modules/sink_count.cpp
@@ -25,8 +25,6 @@
25 25
26#include "common/resource.h" 26#include "common/resource.h"
27#include "common/storage.h" 27#include "common/storage.h"
28#include "common/domain/event.h"
29#include "common/domain/folder.h"
30#include "common/resourceconfig.h" 28#include "common/resourceconfig.h"
31#include "common/log.h" 29#include "common/log.h"
32#include "common/storage.h" 30#include "common/storage.h"
diff --git a/sinksh/syntax_modules/sink_inspect.cpp b/sinksh/syntax_modules/sink_inspect.cpp
index 0d2ea00..a8a3805 100644
--- a/sinksh/syntax_modules/sink_inspect.cpp
+++ b/sinksh/syntax_modules/sink_inspect.cpp
@@ -24,8 +24,6 @@
24 24
25#include "common/resource.h" 25#include "common/resource.h"
26#include "common/storage.h" 26#include "common/storage.h"
27#include "common/domain/event.h"
28#include "common/domain/folder.h"
29#include "common/resourceconfig.h" 27#include "common/resourceconfig.h"
30#include "common/log.h" 28#include "common/log.h"
31#include "common/storage.h" 29#include "common/storage.h"
diff --git a/sinksh/syntax_modules/sink_remove.cpp b/sinksh/syntax_modules/sink_remove.cpp
index 7e66ece..bbc1752 100644
--- a/sinksh/syntax_modules/sink_remove.cpp
+++ b/sinksh/syntax_modules/sink_remove.cpp
@@ -25,8 +25,6 @@
25 25
26#include "common/resource.h" 26#include "common/resource.h"
27#include "common/storage.h" 27#include "common/storage.h"
28#include "common/domain/event.h"
29#include "common/domain/folder.h"
30#include "common/resourceconfig.h" 28#include "common/resourceconfig.h"
31#include "common/log.h" 29#include "common/log.h"
32#include "common/storage.h" 30#include "common/storage.h"
diff --git a/sinksh/syntax_modules/sink_show.cpp b/sinksh/syntax_modules/sink_show.cpp
index 8e3f715..391505a 100644
--- a/sinksh/syntax_modules/sink_show.cpp
+++ b/sinksh/syntax_modules/sink_show.cpp
@@ -26,8 +26,6 @@
26 26
27#include "common/resource.h" 27#include "common/resource.h"
28#include "common/storage.h" 28#include "common/storage.h"
29#include "common/domain/event.h"
30#include "common/domain/folder.h"
31#include "common/resourceconfig.h" 29#include "common/resourceconfig.h"
32#include "common/log.h" 30#include "common/log.h"
33#include "common/storage.h" 31#include "common/storage.h"
diff --git a/sinksh/syntax_modules/sink_stat.cpp b/sinksh/syntax_modules/sink_stat.cpp
index 982d4cf..73b6ac4 100644
--- a/sinksh/syntax_modules/sink_stat.cpp
+++ b/sinksh/syntax_modules/sink_stat.cpp
@@ -24,8 +24,6 @@
24 24
25#include "common/resource.h" 25#include "common/resource.h"
26#include "common/storage.h" 26#include "common/storage.h"
27#include "common/domain/event.h"
28#include "common/domain/folder.h"
29#include "common/resourceconfig.h" 27#include "common/resourceconfig.h"
30#include "common/log.h" 28#include "common/log.h"
31#include "common/storage.h" 29#include "common/storage.h"
diff --git a/sinksh/syntax_modules/sink_sync.cpp b/sinksh/syntax_modules/sink_sync.cpp
index 250cd75..ef84734 100644
--- a/sinksh/syntax_modules/sink_sync.cpp
+++ b/sinksh/syntax_modules/sink_sync.cpp
@@ -24,8 +24,6 @@
24#include "common/resource.h" 24#include "common/resource.h"
25#include "common/storage.h" 25#include "common/storage.h"
26#include "common/resourcecontrol.h" 26#include "common/resourcecontrol.h"
27#include "common/domain/event.h"
28#include "common/domain/folder.h"
29#include "common/resourceconfig.h" 27#include "common/resourceconfig.h"
30#include "common/log.h" 28#include "common/log.h"
31#include "common/storage.h" 29#include "common/storage.h"
@@ -51,7 +49,6 @@ bool sync(const QStringList &args, State &state)
51 } 49 }
52 } 50 }
53 51
54 QTimer::singleShot(0, [query, state]() {
55 Sink::Store::synchronize(query) 52 Sink::Store::synchronize(query)
56 .then(Sink::ResourceControl::flushMessageQueue(query.getResourceFilter().ids)) 53 .then(Sink::ResourceControl::flushMessageQueue(query.getResourceFilter().ids))
57 .then([state](const KAsync::Error &error) { 54 .then([state](const KAsync::Error &error) {
@@ -62,7 +59,6 @@ bool sync(const QStringList &args, State &state)
62 } 59 }
63 state.commandFinished(); 60 state.commandFinished();
64 }).exec(); 61 }).exec();
65 });
66 62
67 return true; 63 return true;
68} 64}
diff --git a/synchronizer/main.cpp b/synchronizer/main.cpp
index e6a4d56..c66a2fb 100644
--- a/synchronizer/main.cpp
+++ b/synchronizer/main.cpp
@@ -201,7 +201,7 @@ int main(int argc, char *argv[])
201 SinkLog() << "Starting: " << instanceIdentifier << resourceType; 201 SinkLog() << "Starting: " << instanceIdentifier << resourceType;
202 202
203 QDir{}.mkpath(Sink::resourceStorageLocation(instanceIdentifier)); 203 QDir{}.mkpath(Sink::resourceStorageLocation(instanceIdentifier));
204 QLockFile lockfile(Sink::resourceStorageLocation(instanceIdentifier) + "/resource.lock"); 204 QLockFile lockfile(Sink::storageLocation() + QString("/%1.lock").arg(QString(instanceIdentifier)));
205 lockfile.setStaleLockTime(500); 205 lockfile.setStaleLockTime(500);
206 if (!lockfile.tryLock(0)) { 206 if (!lockfile.tryLock(0)) {
207 const auto error = lockfile.error(); 207 const auto error = lockfile.error();
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index a69fcb3..c77a736 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -21,10 +21,8 @@ include(SinkTest)
21manual_tests ( 21manual_tests (
22 storagebenchmark 22 storagebenchmark
23 dummyresourcebenchmark 23 dummyresourcebenchmark
24# genericresourcebenchmark
25 mailquerybenchmark 24 mailquerybenchmark
26 pipelinebenchmark 25 pipelinebenchmark
27# genericfacadebenchmark
28) 26)
29 27
30auto_tests ( 28auto_tests (
@@ -35,8 +33,6 @@ auto_tests (
35 domainadaptortest 33 domainadaptortest
36 messagequeuetest 34 messagequeuetest
37 indextest 35 indextest
38 # genericresourcetest
39 # genericfacadetest
40 resourcecommunicationtest 36 resourcecommunicationtest
41 pipelinetest 37 pipelinetest
42 querytest 38 querytest
@@ -48,6 +44,8 @@ auto_tests (
48 testaccounttest 44 testaccounttest
49 dummyresourcemailtest 45 dummyresourcemailtest
50 interresourcemovetest 46 interresourcemovetest
47 notificationtest
48 entitystoretest
51) 49)
52generate_flatbuffers(dummyresourcetest calendar) 50generate_flatbuffers(dummyresourcetest calendar)
53target_link_libraries(dummyresourcetest sink_resource_dummy) 51target_link_libraries(dummyresourcetest sink_resource_dummy)
@@ -56,3 +54,4 @@ target_link_libraries(dummyresourcewritebenchmark sink_resource_dummy)
56target_link_libraries(querytest sink_resource_dummy) 54target_link_libraries(querytest sink_resource_dummy)
57target_link_libraries(modelinteractivitytest sink_resource_dummy) 55target_link_libraries(modelinteractivitytest sink_resource_dummy)
58target_link_libraries(inspectiontest sink_resource_dummy) 56target_link_libraries(inspectiontest sink_resource_dummy)
57target_link_libraries(notificationtest sink_resource_dummy)
diff --git a/tests/clientapitest.cpp b/tests/clientapitest.cpp
index da52afb..3955ebd 100644
--- a/tests/clientapitest.cpp
+++ b/tests/clientapitest.cpp
@@ -12,6 +12,12 @@
12#include "asyncutils.h" 12#include "asyncutils.h"
13 13
14template <typename T> 14template <typename T>
15struct Result {
16 QSharedPointer<T> parent;
17 bool fetchedAll;
18};
19
20template <typename T>
15class TestDummyResourceFacade : public Sink::StoreFacade<T> 21class TestDummyResourceFacade : public Sink::StoreFacade<T>
16{ 22{
17public: 23public:
@@ -68,7 +74,7 @@ public:
68 auto emitter = resultProvider->emitter(); 74 auto emitter = resultProvider->emitter();
69 75
70 resultProvider->setFetcher([query, resultProvider, this, ctx](const typename T::Ptr &parent) { 76 resultProvider->setFetcher([query, resultProvider, this, ctx](const typename T::Ptr &parent) {
71 async::run<int>([=] { 77 async::run<Result<T>>([=] {
72 if (parent) { 78 if (parent) {
73 SinkTraceCtx(ctx) << "Running the fetcher " << parent->identifier(); 79 SinkTraceCtx(ctx) << "Running the fetcher " << parent->identifier();
74 } else { 80 } else {
@@ -90,14 +96,16 @@ public:
90 SinkTraceCtx(ctx) << "Aborting early after " << count << "results."; 96 SinkTraceCtx(ctx) << "Aborting early after " << count << "results.";
91 offset = i + 1; 97 offset = i + 1;
92 bool fetchedAll = (i + 1 >= results.size()); 98 bool fetchedAll = (i + 1 >= results.size());
93 resultProvider->initialResultSetComplete(parent, fetchedAll); 99 return Result<T>{parent, fetchedAll};
94 return 0;
95 } 100 }
96 } 101 }
97 } 102 }
98 resultProvider->initialResultSetComplete(parent, true); 103 return Result<T>{parent, true};
99 return 0; 104 }, runAsync)
100 }, runAsync).exec(); 105 .then([=] (const Result<T> &r) {
106 resultProvider->initialResultSetComplete(r.parent, r.fetchedAll);
107 })
108 .exec();
101 }); 109 });
102 auto job = KAsync::start([query, resultProvider]() {}); 110 auto job = KAsync::start([query, resultProvider]() {});
103 mResultProvider = resultProvider.data(); 111 mResultProvider = resultProvider.data();
diff --git a/tests/dummyresourcetest.cpp b/tests/dummyresourcetest.cpp
index eea63c0..17df160 100644
--- a/tests/dummyresourcetest.cpp
+++ b/tests/dummyresourcetest.cpp
@@ -136,12 +136,7 @@ private slots:
136 void testResourceSync() 136 void testResourceSync()
137 { 137 {
138 ::DummyResource resource(getContext()); 138 ::DummyResource resource(getContext());
139 auto job = resource.synchronizeWithSource(Sink::QueryBase()); 139 VERIFYEXEC(resource.synchronizeWithSource(Sink::QueryBase()));
140 // TODO pass in optional timeout?
141 auto future = job.exec();
142 future.waitForFinished();
143 QVERIFY(!future.errorCode());
144 QVERIFY(future.isFinished());
145 QVERIFY(!resource.error()); 140 QVERIFY(!resource.error());
146 auto processAllMessagesFuture = resource.processAllMessages().exec(); 141 auto processAllMessagesFuture = resource.processAllMessages().exec();
147 processAllMessagesFuture.waitForFinished(); 142 processAllMessagesFuture.waitForFinished();
@@ -152,7 +147,7 @@ private slots:
152 const auto query = Query().resourceFilter("sink.dummy.instance1"); 147 const auto query = Query().resourceFilter("sink.dummy.instance1");
153 148
154 // Ensure all local data is processed 149 // Ensure all local data is processed
155 Sink::Store::synchronize(query).exec().waitForFinished(); 150 VERIFYEXEC(Sink::Store::synchronize(query));
156 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1")); 151 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
157 152
158 auto model = Sink::Store::loadModel<Event>(query); 153 auto model = Sink::Store::loadModel<Event>(query);
diff --git a/tests/entitystoretest.cpp b/tests/entitystoretest.cpp
new file mode 100644
index 0000000..90575a5
--- /dev/null
+++ b/tests/entitystoretest.cpp
@@ -0,0 +1,89 @@
1#include <QtTest>
2
3#include <QDebug>
4#include <QString>
5
6#include "common/storage/entitystore.h"
7#include "common/adaptorfactoryregistry.h"
8#include "common/definitions.h"
9#include "testimplementations.h"
10
11class EntityStoreTest : public QObject
12{
13 Q_OBJECT
14private:
15 QString resourceInstanceIdentifier{"resourceId"};
16
17private slots:
18 void initTestCase()
19 {
20 Sink::AdaptorFactoryRegistry::instance().registerFactory<Sink::ApplicationDomain::Mail, TestMailAdaptorFactory>("test");
21 }
22
23 void cleanup()
24 {
25 Sink::Storage::DataStore storage(Sink::storageLocation(), resourceInstanceIdentifier);
26 storage.removeFromDisk();
27 }
28
29 void testCleanup()
30 {
31 }
32
33 void readAll()
34 {
35 using namespace Sink;
36 ResourceContext resourceContext{resourceInstanceIdentifier.toUtf8(), "dummy", AdaptorFactoryRegistry::instance().getFactories("test")};
37 Storage::EntityStore store(resourceContext, {});
38
39 auto mail = ApplicationDomain::ApplicationDomainType::createEntity<ApplicationDomain::Mail>("res1");
40 mail.setExtractedMessageId("messageid");
41 mail.setExtractedSubject("boo");
42
43 auto mail2 = ApplicationDomain::ApplicationDomainType::createEntity<ApplicationDomain::Mail>("res1");
44 mail2.setExtractedMessageId("messageid2");
45 mail2.setExtractedSubject("foo");
46
47 auto mail3 = ApplicationDomain::ApplicationDomainType::createEntity<ApplicationDomain::Mail>("res1");
48 mail3.setExtractedMessageId("messageid2");
49 mail3.setExtractedSubject("foo");
50
51 store.startTransaction(Storage::DataStore::ReadWrite);
52 store.add("mail", mail, false);
53 store.add("mail", mail2, false);
54 store.add("mail", mail3, false);
55
56 mail.setExtractedSubject("foo");
57
58 store.modify("mail", mail, QByteArrayList{}, false);
59 store.remove("mail", mail3, false);
60 store.commitTransaction();
61
62 store.startTransaction(Storage::DataStore::ReadOnly);
63 {
64 //We get every uid once
65 QList<QByteArray> uids;
66 store.readAllUids("mail", [&] (const QByteArray &uid) {
67 uids << uid;
68 });
69 QCOMPARE(uids.size(), 2);
70 }
71
72 {
73 //We get the latest version of every entity once
74 QList<QByteArray> uids;
75 store.readAll("mail", [&] (const ApplicationDomain::ApplicationDomainType &entity) {
76 //The first revision should be superseeded by the modification
77 QCOMPARE(entity.getProperty(ApplicationDomain::Mail::Subject::name).toString(), QString::fromLatin1("foo"));
78 uids << entity.identifier();
79 });
80 QCOMPARE(uids.size(), 2);
81 }
82
83 store.abortTransaction();
84
85 }
86};
87
88QTEST_MAIN(EntityStoreTest)
89#include "entitystoretest.moc"
diff --git a/tests/genericfacadetest.cpp b/tests/genericfacadetest.cpp
deleted file mode 100644
index 0267dac..0000000
--- a/tests/genericfacadetest.cpp
+++ /dev/null
@@ -1,154 +0,0 @@
1#include <QtTest>
2
3#include <QString>
4
5#include "testimplementations.h"
6
7#include <common/facade.h>
8#include <common/domainadaptor.h>
9#include <common/resultprovider.h>
10#include <common/synclistresult.h>
11#include <common/test.h>
12
13// Replace with something different
14#include "event_generated.h"
15
16
17/**
18 * Test for the generic facade implementation.
19 *
20 * This test doesn't use the actual storage and thus only tests the update logic of the facade.
21 * //FIXME this now uses the actual storage
22 */
23class GenericFacadeTest : public QObject
24{
25 Q_OBJECT
26private slots:
27 void initTestCase()
28 {
29 Sink::Test::initTest();
30 }
31
32 void testLoad()
33 {
34 Sink::Query query;
35 query.liveQuery = false;
36
37 auto resultSet = QSharedPointer<Sink::ResultProvider<Sink::ApplicationDomain::Event::Ptr>>::create();
38 auto resourceAccess = QSharedPointer<TestResourceAccess>::create();
39 // storage->mResults << Sink::ApplicationDomain::Event::Ptr::create();
40 TestResourceFacade facade("identifier", resourceAccess);
41
42 async::SyncListResult<Sink::ApplicationDomain::Event::Ptr> result(resultSet->emitter());
43
44 facade.load(query, *resultSet).exec().waitForFinished();
45 resultSet->initialResultSetComplete();
46
47 // We have to wait for the events that deliver the results to be processed by the eventloop
48 result.exec();
49
50 QCOMPARE(result.size(), 1);
51 }
52
53 void testLiveQuery()
54 {
55 Sink::Query query;
56 query.liveQuery = true;
57
58 auto resultSet = QSharedPointer<Sink::ResultProvider<Sink::ApplicationDomain::Event::Ptr>>::create();
59 auto resourceAccess = QSharedPointer<TestResourceAccess>::create();
60 // storage->mResults << Sink::ApplicationDomain::Event::Ptr::create();
61 TestResourceFacade facade("identifier", resourceAccess);
62
63 async::SyncListResult<Sink::ApplicationDomain::Event::Ptr> result(resultSet->emitter());
64
65 facade.load(query, *resultSet).exec().waitForFinished();
66 resultSet->initialResultSetComplete();
67
68 result.exec();
69 QCOMPARE(result.size(), 1);
70
71 // Enter a second result
72 // storage->mResults.clear();
73 // storage->mResults << QSharedPointer<Sink::ApplicationDomain::Event>::create("resource", "id2", 0, QSharedPointer<Sink::ApplicationDomain::BufferAdaptor>());
74 // storage->mLatestRevision = 2;
75 resourceAccess->emit revisionChanged(2);
76
77 // Hack to get event loop in synclistresult to abort again
78 resultSet->initialResultSetComplete();
79 result.exec();
80
81 QCOMPARE(result.size(), 2);
82 }
83
84 void testLiveQueryModify()
85 {
86 Sink::Query query;
87 query.liveQuery = true;
88
89 auto resultSet = QSharedPointer<Sink::ResultProvider<Sink::ApplicationDomain::Event::Ptr>>::create();
90 auto resourceAccess = QSharedPointer<TestResourceAccess>::create();
91 auto entity = QSharedPointer<Sink::ApplicationDomain::Event>::create("resource", "id2", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
92 entity->setProperty("test", "test1");
93 // storage->mResults << entity;
94 TestResourceFacade facade("identifier", resourceAccess);
95
96 async::SyncListResult<Sink::ApplicationDomain::Event::Ptr> result(resultSet->emitter());
97
98 facade.load(query, *resultSet).exec().waitForFinished();
99 resultSet->initialResultSetComplete();
100
101 result.exec();
102 QCOMPARE(result.size(), 1);
103
104 // Modify the entity again
105 // storage->mResults.clear();
106 entity = QSharedPointer<Sink::ApplicationDomain::Event>::create("resource", "id2", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
107 entity->setProperty("test", "test2");
108 // storage->mModifications << entity;
109 // storage->mLatestRevision = 2;
110 resourceAccess->emit revisionChanged(2);
111
112 // Hack to get event loop in synclistresult to abort again
113 resultSet->initialResultSetComplete();
114 result.exec();
115
116 QCOMPARE(result.size(), 1);
117 QCOMPARE(result.first()->getProperty("test").toByteArray(), QByteArray("test2"));
118 }
119
120 void testLiveQueryRemove()
121 {
122 Sink::Query query;
123 query.liveQuery = true;
124
125 auto resultSet = QSharedPointer<Sink::ResultProvider<Sink::ApplicationDomain::Event::Ptr>>::create();
126 auto resourceAccess = QSharedPointer<TestResourceAccess>::create();
127 auto entity = QSharedPointer<Sink::ApplicationDomain::Event>::create("resource", "id2", 0, QSharedPointer<Sink::ApplicationDomain::BufferAdaptor>());
128 // storage->mResults << entity;
129 TestResourceFacade facade("identifier", resourceAccess);
130
131 async::SyncListResult<Sink::ApplicationDomain::Event::Ptr> result(resultSet->emitter());
132
133 facade.load(query, *resultSet).exec().waitForFinished();
134 resultSet->initialResultSetComplete();
135
136 result.exec();
137 QCOMPARE(result.size(), 1);
138
139 // Remove the entity again
140 // storage->mResults.clear();
141 // storage->mRemovals << entity;
142 // storage->mLatestRevision = 2;
143 resourceAccess->emit revisionChanged(2);
144
145 // Hack to get event loop in synclistresult to abort again
146 resultSet->initialResultSetComplete();
147 result.exec();
148
149 QCOMPARE(result.size(), 0);
150 }
151};
152
153QTEST_MAIN(GenericFacadeTest)
154#include "genericfacadetest.moc"
diff --git a/tests/genericresourcebenchmark.cpp b/tests/genericresourcebenchmark.cpp
deleted file mode 100644
index 2315d0b..0000000
--- a/tests/genericresourcebenchmark.cpp
+++ /dev/null
@@ -1,209 +0,0 @@
1#include <QtTest>
2
3#include <QString>
4
5#include "testimplementations.h"
6
7#include "event_generated.h"
8#include "createentity_generated.h"
9#include "commands.h"
10#include "entitybuffer.h"
11#include "pipeline.h"
12#include "genericresource.h"
13#include "definitions.h"
14#include "domainadaptor.h"
15#include "index.h"
16
17#include "hawd/dataset.h"
18#include "hawd/formatter.h"
19
20
21static void removeFromDisk(const QString &name)
22{
23 Sink::Storage store(Sink::storageLocation(), name, Sink::Storage::ReadWrite);
24 store.removeFromDisk();
25}
26
27static QByteArray createEntityBuffer()
28{
29 flatbuffers::FlatBufferBuilder eventFbb;
30 eventFbb.Clear();
31 {
32 auto summary = eventFbb.CreateString("summary");
33 Sink::ApplicationDomain::Buffer::EventBuilder eventBuilder(eventFbb);
34 eventBuilder.add_summary(summary);
35 auto eventLocation = eventBuilder.Finish();
36 Sink::ApplicationDomain::Buffer::FinishEventBuffer(eventFbb, eventLocation);
37 }
38
39 flatbuffers::FlatBufferBuilder localFbb;
40 {
41 auto uid = localFbb.CreateString("testuid");
42 auto localBuilder = Sink::ApplicationDomain::Buffer::EventBuilder(localFbb);
43 localBuilder.add_uid(uid);
44 auto location = localBuilder.Finish();
45 Sink::ApplicationDomain::Buffer::FinishEventBuffer(localFbb, location);
46 }
47
48 flatbuffers::FlatBufferBuilder entityFbb;
49 Sink::EntityBuffer::assembleEntityBuffer(entityFbb, 0, 0, eventFbb.GetBufferPointer(), eventFbb.GetSize(), localFbb.GetBufferPointer(), localFbb.GetSize());
50
51 flatbuffers::FlatBufferBuilder fbb;
52 auto type = fbb.CreateString(Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Event>().toStdString().data());
53 auto delta = fbb.CreateVector<uint8_t>(entityFbb.GetBufferPointer(), entityFbb.GetSize());
54 Sink::Commands::CreateEntityBuilder builder(fbb);
55 builder.add_domainType(type);
56 builder.add_delta(delta);
57 auto location = builder.Finish();
58 Sink::Commands::FinishCreateEntityBuffer(fbb, location);
59
60 return QByteArray(reinterpret_cast<const char *>(fbb.GetBufferPointer()), fbb.GetSize());
61}
62
63class IndexUpdater : public Sink::Preprocessor
64{
65public:
66 void newEntity(const QByteArray &uid, qint64 revision, const Sink::ApplicationDomain::BufferAdaptor &newEntity, Sink::Storage::Transaction &transaction) Q_DECL_OVERRIDE
67 {
68 for (int i = 0; i < 10; i++) {
69 Index ridIndex(QString("index.index%1").arg(i).toLatin1(), transaction);
70 ridIndex.add("foo", uid);
71 }
72 }
73
74 void modifiedEntity(const QByteArray &key, qint64 revision, const Sink::ApplicationDomain::BufferAdaptor &oldEntity, const Sink::ApplicationDomain::BufferAdaptor &newEntity,
75 Sink::Storage::Transaction &transaction) Q_DECL_OVERRIDE
76 {
77 }
78
79 void deletedEntity(const QByteArray &key, qint64 revision, const Sink::ApplicationDomain::BufferAdaptor &oldEntity, Sink::Storage::Transaction &transaction) Q_DECL_OVERRIDE
80 {
81 }
82};
83
84/**
85 * Benchmark write performance of generic resource implementation including queues and pipeline.
86 */
87class GenericResourceBenchmark : public QObject
88{
89 Q_OBJECT
90private slots:
91
92 void init()
93 {
94 Sink::Log::setDebugOutputLevel(Sink::Log::Warning);
95 }
96
97 void initTestCase()
98 {
99 removeFromDisk("sink.test.instance1");
100 removeFromDisk("sink.test.instance1.userqueue");
101 removeFromDisk("sink.test.instance1.synchronizerqueue");
102 }
103
104
105 void testWriteInProcess()
106 {
107 int num = 10000;
108
109 auto pipeline = QSharedPointer<Sink::Pipeline>::create("sink.test.instance1");
110 TestResource resource("sink.test.instance1", pipeline);
111
112 auto command = createEntityBuffer();
113
114 QTime time;
115 time.start();
116
117 for (int i = 0; i < num; i++) {
118 resource.processCommand(Sink::Commands::CreateEntityCommand, command);
119 }
120 auto appendTime = time.elapsed();
121
122 // Wait until all messages have been processed
123 resource.processAllMessages().exec().waitForFinished();
124
125 auto allProcessedTime = time.elapsed();
126
127 // Print memory layout, RSS is what is in memory
128 // std::system("exec pmap -x \"$PPID\"");
129
130 HAWD::Dataset dataset("generic_write_in_process", m_hawdState);
131 HAWD::Dataset::Row row = dataset.row();
132
133 row.setValue("rows", num);
134 row.setValue("append", (qreal)num / appendTime);
135 row.setValue("total", (qreal)num / allProcessedTime);
136 dataset.insertRow(row);
137 HAWD::Formatter::print(dataset);
138 }
139
140 void testWriteInProcessWithIndex()
141 {
142 int num = 50000;
143
144 auto pipeline = QSharedPointer<Sink::Pipeline>::create("sink.test.instance1");
145
146 auto eventFactory = QSharedPointer<TestEventAdaptorFactory>::create();
147 const QByteArray resourceIdentifier = "sink.test.instance1";
148 auto indexer = QSharedPointer<IndexUpdater>::create();
149
150 pipeline->setPreprocessors("event", QVector<Sink::Preprocessor *>() << indexer.data());
151 pipeline->setAdaptorFactory("event", eventFactory);
152
153 TestResource resource("sink.test.instance1", pipeline);
154
155 auto command = createEntityBuffer();
156
157 QTime time;
158 time.start();
159
160 for (int i = 0; i < num; i++) {
161 resource.processCommand(Sink::Commands::CreateEntityCommand, command);
162 }
163 auto appendTime = time.elapsed();
164
165 // Wait until all messages have been processed
166 resource.processAllMessages().exec().waitForFinished();
167
168 auto allProcessedTime = time.elapsed();
169
170 // Print memory layout, RSS is what is in memory
171 // std::system("exec pmap -x \"$PPID\"");
172
173 HAWD::Dataset dataset("generic_write_in_process_with_indexes", m_hawdState);
174 HAWD::Dataset::Row row = dataset.row();
175
176 row.setValue("rows", num);
177 row.setValue("append", (qreal)num / appendTime);
178 row.setValue("total", (qreal)num / allProcessedTime);
179 dataset.insertRow(row);
180 HAWD::Formatter::print(dataset);
181 }
182
183 void testCreateCommand()
184 {
185 Sink::ApplicationDomain::Event event;
186
187 QBENCHMARK {
188 auto mFactory = new TestEventAdaptorFactory;
189 static flatbuffers::FlatBufferBuilder entityFbb;
190 entityFbb.Clear();
191 mFactory->createBuffer(event, entityFbb);
192
193 static flatbuffers::FlatBufferBuilder fbb;
194 fbb.Clear();
195 // This is the resource buffer type and not the domain type
196 auto type = fbb.CreateString("event");
197 // auto delta = fbb.CreateVector<uint8_t>(entityFbb.GetBufferPointer(), entityFbb.GetSize());
198 auto delta = Sink::EntityBuffer::appendAsVector(fbb, entityFbb.GetBufferPointer(), entityFbb.GetSize());
199 auto location = Sink::Commands::CreateCreateEntity(fbb, type, delta);
200 Sink::Commands::FinishCreateEntityBuffer(fbb, location);
201 }
202 }
203
204private:
205 HAWD::State m_hawdState;
206};
207
208QTEST_MAIN(GenericResourceBenchmark)
209#include "genericresourcebenchmark.moc"
diff --git a/tests/genericresourcetest.cpp b/tests/genericresourcetest.cpp
deleted file mode 100644
index 77a901d..0000000
--- a/tests/genericresourcetest.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
1#include <QtTest>
2
3#include <QString>
4
5#include "testimplementations.h"
6
7#include "event_generated.h"
8#include "entity_generated.h"
9#include "metadata_generated.h"
10#include "createentity_generated.h"
11#include "commands.h"
12#include "entitybuffer.h"
13#include "pipeline.h"
14#include "genericresource.h"
15#include "definitions.h"
16
17/**
18 * Test of the generic resource implementation.
19 *
20 * This test relies on a working pipeline implementation, and writes to storage.
21 */
22class GenericResourceTest : public QObject
23{
24 Q_OBJECT
25private slots:
26
27 void init()
28 {
29 Sink::GenericResource::removeFromDisk("sink.test.instance1");
30 }
31
32 /// Ensure the resource can process messages
33 void testProcessCommand()
34 {
35 flatbuffers::FlatBufferBuilder eventFbb;
36 eventFbb.Clear();
37 {
38 auto summary = eventFbb.CreateString("summary");
39 Sink::ApplicationDomain::Buffer::EventBuilder eventBuilder(eventFbb);
40 eventBuilder.add_summary(summary);
41 auto eventLocation = eventBuilder.Finish();
42 Sink::ApplicationDomain::Buffer::FinishEventBuffer(eventFbb, eventLocation);
43 }
44
45 flatbuffers::FlatBufferBuilder localFbb;
46 {
47 auto uid = localFbb.CreateString("testuid");
48 auto localBuilder = Sink::ApplicationDomain::Buffer::EventBuilder(localFbb);
49 localBuilder.add_uid(uid);
50 auto location = localBuilder.Finish();
51 Sink::ApplicationDomain::Buffer::FinishEventBuffer(localFbb, location);
52 }
53
54 flatbuffers::FlatBufferBuilder entityFbb;
55 Sink::EntityBuffer::assembleEntityBuffer(entityFbb, 0, 0, eventFbb.GetBufferPointer(), eventFbb.GetSize(), localFbb.GetBufferPointer(), localFbb.GetSize());
56
57 flatbuffers::FlatBufferBuilder fbb;
58 auto type = fbb.CreateString(Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Event>().toStdString().data());
59 auto delta = fbb.CreateVector<uint8_t>(entityFbb.GetBufferPointer(), entityFbb.GetSize());
60 Sink::Commands::CreateEntityBuilder builder(fbb);
61 builder.add_domainType(type);
62 builder.add_delta(delta);
63 auto location = builder.Finish();
64 Sink::Commands::FinishCreateEntityBuffer(fbb, location);
65
66 const QByteArray command(reinterpret_cast<const char *>(fbb.GetBufferPointer()), fbb.GetSize());
67 {
68 flatbuffers::Verifier verifyer(reinterpret_cast<const uint8_t *>(command.data()), command.size());
69 QVERIFY(Sink::Commands::VerifyCreateEntityBuffer(verifyer));
70 }
71
72 // Actual test
73 auto pipeline = QSharedPointer<Sink::Pipeline>::create("sink.test.instance1");
74 QSignalSpy revisionSpy(pipeline.data(), SIGNAL(revisionUpdated(qint64)));
75 QVERIFY(revisionSpy.isValid());
76 TestResource resource("sink.test.instance1", pipeline);
77 resource.processCommand(Sink::Commands::CreateEntityCommand, command);
78 resource.processCommand(Sink::Commands::CreateEntityCommand, command);
79 resource.processAllMessages().exec().waitForFinished();
80 QCOMPARE(revisionSpy.last().at(0).toInt(), 2);
81 }
82};
83
84QTEST_MAIN(GenericResourceTest)
85#include "genericresourcetest.moc"
diff --git a/tests/hawd/CMakeLists.txt b/tests/hawd/CMakeLists.txt
index b3de83e..6ae5f13 100644
--- a/tests/hawd/CMakeLists.txt
+++ b/tests/hawd/CMakeLists.txt
@@ -1,6 +1,7 @@
1project(hawd) 1project(hawd)
2 2
3find_package(Libgit2) 3#We require git_buf_free function
4find_package(Libgit2 0.20.0)
4 5
5if (LIBGIT2_FOUND) 6if (LIBGIT2_FOUND)
6 add_definitions(-DHAVE_LIBGIT2) 7 add_definitions(-DHAVE_LIBGIT2)
diff --git a/tests/hawd/state.cpp b/tests/hawd/state.cpp
index dbfe019..dfeef41 100644
--- a/tests/hawd/state.cpp
+++ b/tests/hawd/state.cpp
@@ -116,6 +116,7 @@ const char *State::commitHash() const
116void State::findGitHash() 116void State::findGitHash()
117{ 117{
118#ifdef HAVE_LIBGIT2 118#ifdef HAVE_LIBGIT2
119 git_libgit2_init();
119 git_buf root = {0}; 120 git_buf root = {0};
120 int error = git_repository_discover(&root, projectPath().toStdString().data(), 0, NULL); 121 int error = git_repository_discover(&root, projectPath().toStdString().data(), 0, NULL);
121 if (!error) { 122 if (!error) {
@@ -133,6 +134,7 @@ void State::findGitHash()
133 git_repository_free(repo); 134 git_repository_free(repo);
134 } 135 }
135 git_buf_free(&root); 136 git_buf_free(&root);
137 git_libgit2_shutdown();
136#endif 138#endif
137} 139}
138 140
diff --git a/tests/interresourcemovetest.cpp b/tests/interresourcemovetest.cpp
index 7561c5b..3ac6ad4 100644
--- a/tests/interresourcemovetest.cpp
+++ b/tests/interresourcemovetest.cpp
@@ -28,6 +28,7 @@
28#include "log.h" 28#include "log.h"
29#include "test.h" 29#include "test.h"
30#include "testutils.h" 30#include "testutils.h"
31#include <KMime/Message>
31 32
32using namespace Sink; 33using namespace Sink;
33using namespace Sink::ApplicationDomain; 34using namespace Sink::ApplicationDomain;
@@ -41,6 +42,15 @@ class InterResourceMoveTest : public QObject
41{ 42{
42 Q_OBJECT 43 Q_OBJECT
43 44
45 QByteArray message(const QByteArray &uid, const QString &subject)
46 {
47 KMime::Message m;
48 m.subject(true)->fromUnicodeString(subject, "utf8");
49 m.messageID(true)->setIdentifier(uid);
50 m.assemble();
51 return m.encodedContent();
52 }
53
44private slots: 54private slots:
45 void initTestCase() 55 void initTestCase()
46 { 56 {
@@ -65,24 +75,25 @@ private slots:
65 75
66 void testMove() 76 void testMove()
67 { 77 {
68 Event event("instance1"); 78 QByteArray testuid = "testuid@test.test";
69 event.setProperty("uid", "testuid"); 79 QString subject = "summaryValue";
70 QCOMPARE(event.getProperty("uid").toByteArray(), QByteArray("testuid")); 80 auto mimeMessage = message(testuid, subject);
71 event.setProperty("summary", "summaryValue");
72 VERIFYEXEC(Sink::Store::create<Event>(event));
73 81
82 Mail mail("instance1");
83 mail.setMimeMessage(mimeMessage);
84 VERIFYEXEC(Sink::Store::create<Mail>(mail));
74 85
75 Event createdEvent; 86 Mail createdmail;
76 // Ensure all local data is processed 87 // Ensure all local data is processed
77 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "instance1")); 88 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "instance1"));
78 { 89 {
79 auto query = Query().resourceFilter("instance1") ; 90 auto query = Query().resourceFilter("instance1") ;
80 auto list = Sink::Store::read<Event>(query.filter<Event::Uid>("testuid")); 91 auto list = Sink::Store::read<Mail>(query.filter<Mail::MessageId>(testuid));
81 QCOMPARE(list.size(), 1); 92 QCOMPARE(list.size(), 1);
82 createdEvent = list.first(); 93 createdmail = list.first();
83 } 94 }
84 95
85 VERIFYEXEC(Sink::Store::move<Event>(createdEvent, "instance2")); 96 VERIFYEXEC(Sink::Store::move<Mail>(createdmail, "instance2"));
86 97
87 //FIXME we can't guarantee that that the create command arrives at instance2 before the flush command, so we'll just wait for a little bit. 98 //FIXME we can't guarantee that that the create command arrives at instance2 before the flush command, so we'll just wait for a little bit.
88 QTest::qWait(1000); 99 QTest::qWait(1000);
@@ -92,14 +103,18 @@ private slots:
92 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "instance2")); 103 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "instance2"));
93 { 104 {
94 auto query = Query().resourceFilter("instance2") ; 105 auto query = Query().resourceFilter("instance2") ;
95 auto list = Sink::Store::read<Event>(query.filter<Event::Uid>("testuid")); 106 auto list = Sink::Store::read<Mail>(query.filter<Mail::MessageId>(testuid));
96 QCOMPARE(list.size(), 1); 107 QCOMPARE(list.size(), 1);
108 const auto mail = list.first();
109 QVERIFY(!mail.getMimeMessagePath().isEmpty());
110 QCOMPARE(mail.getSubject(), subject);
111 QCOMPARE(mail.getMimeMessage(), mimeMessage);
97 } 112 }
98 113
99 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "instance1")); 114 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "instance1"));
100 { 115 {
101 auto query = Query().resourceFilter("instance1") ; 116 auto query = Query().resourceFilter("instance1") ;
102 auto list = Sink::Store::read<Event>(query.filter<Event::Uid>("testuid")); 117 auto list = Sink::Store::read<Mail>(query.filter<Mail::MessageId>(testuid));
103 QCOMPARE(list.size(), 0); 118 QCOMPARE(list.size(), 0);
104 } 119 }
105 } 120 }
diff --git a/tests/mailquerybenchmark.cpp b/tests/mailquerybenchmark.cpp
index d68151a..b15c8d6 100644
--- a/tests/mailquerybenchmark.cpp
+++ b/tests/mailquerybenchmark.cpp
@@ -23,16 +23,10 @@
23 23
24#include "testimplementations.h" 24#include "testimplementations.h"
25 25
26#include <common/facade.h>
27#include <common/domainadaptor.h>
28#include <common/resultprovider.h> 26#include <common/resultprovider.h>
29#include <common/synclistresult.h>
30#include <common/definitions.h> 27#include <common/definitions.h>
31#include <common/query.h> 28#include <common/query.h>
32#include <common/store.h> 29#include <common/storage/entitystore.h>
33#include <common/pipeline.h>
34#include <common/index.h>
35#include <common/adaptorfactoryregistry.h>
36 30
37#include "hawd/dataset.h" 31#include "hawd/dataset.h"
38#include "hawd/formatter.h" 32#include "hawd/formatter.h"
@@ -57,30 +51,34 @@ class MailQueryBenchmark : public QObject
57 QByteArray resourceIdentifier; 51 QByteArray resourceIdentifier;
58 HAWD::State mHawdState; 52 HAWD::State mHawdState;
59 53
60 void populateDatabase(int count) 54 void populateDatabase(int count, int folderSpreadFactor = 0)
61 { 55 {
62 TestResource::removeFromDisk(resourceIdentifier); 56 TestResource::removeFromDisk(resourceIdentifier);
63 57
64 auto pipeline = QSharedPointer<Sink::Pipeline>::create(Sink::ResourceContext{resourceIdentifier, "test"}, "test"); 58 Sink::ResourceContext resourceContext{resourceIdentifier, "test", {{"mail", QSharedPointer<TestMailAdaptorFactory>::create()}}};
59 Sink::Storage::EntityStore entityStore{resourceContext, {}};
60 entityStore.startTransaction(Sink::Storage::DataStore::ReadWrite);
65 61
66 auto domainTypeAdaptorFactory = QSharedPointer<TestMailAdaptorFactory>::create();
67
68 pipeline->startTransaction();
69 const auto date = QDateTime::currentDateTimeUtc(); 62 const auto date = QDateTime::currentDateTimeUtc();
70 for (int i = 0; i < count; i++) { 63 for (int i = 0; i < count; i++) {
71 auto domainObject = Mail::Ptr::create(); 64 auto domainObject = Mail::createEntity<Mail>(resourceIdentifier);
72 domainObject->setExtractedMessageId("uid"); 65 domainObject.setExtractedMessageId("uid");
73 domainObject->setExtractedSubject(QString("subject%1").arg(i)); 66 domainObject.setExtractedParentMessageId("parentuid");
74 domainObject->setExtractedDate(date.addSecs(count)); 67 domainObject.setExtractedSubject(QString("subject%1").arg(i));
75 domainObject->setFolder("folder1"); 68 domainObject.setExtractedDate(date.addSecs(count));
76 // domainObject->setAttachment(attachment); 69 if (folderSpreadFactor == 0) {
77 const auto command = createCommand<Mail>(*domainObject, *domainTypeAdaptorFactory); 70 domainObject.setFolder("folder1");
78 pipeline->newEntity(command.data(), command.size()); 71 } else {
72 domainObject.setFolder(QByteArray("folder") + QByteArray::number(i - (i % folderSpreadFactor)));
73 }
74
75 entityStore.add("mail", domainObject, false);
79 } 76 }
80 pipeline->commit(); 77
78 entityStore.commitTransaction();
81 } 79 }
82 80
83 void testLoad(const Sink::Query &query, int count) 81 void testLoad(const Sink::Query &query, int count, int expectedSize)
84 { 82 {
85 const auto startingRss = getCurrentRSS(); 83 const auto startingRss = getCurrentRSS();
86 84
@@ -88,7 +86,10 @@ class MailQueryBenchmark : public QObject
88 QTime time; 86 QTime time;
89 time.start(); 87 time.start();
90 auto resultSet = QSharedPointer<Sink::ResultProvider<Mail::Ptr>>::create(); 88 auto resultSet = QSharedPointer<Sink::ResultProvider<Mail::Ptr>>::create();
91 Sink::ResourceContext context{resourceIdentifier, "test"}; 89
90 //FIXME why do we need this here?
91 auto domainTypeAdaptorFactory = QSharedPointer<TestMailAdaptorFactory>::create();
92 Sink::ResourceContext context{resourceIdentifier, "test", {{"mail", domainTypeAdaptorFactory}}};
92 context.mResourceAccess = QSharedPointer<TestResourceAccess>::create(); 93 context.mResourceAccess = QSharedPointer<TestResourceAccess>::create();
93 TestMailResourceFacade facade(context); 94 TestMailResourceFacade facade(context);
94 95
@@ -101,7 +102,7 @@ class MailQueryBenchmark : public QObject
101 emitter->onInitialResultSetComplete([&done](const Mail::Ptr &mail, bool) { done = true; }); 102 emitter->onInitialResultSetComplete([&done](const Mail::Ptr &mail, bool) { done = true; });
102 emitter->fetch(Mail::Ptr()); 103 emitter->fetch(Mail::Ptr());
103 QTRY_VERIFY(done); 104 QTRY_VERIFY(done);
104 QCOMPARE(list.size(), query.limit()); 105 QCOMPARE(list.size(), expectedSize);
105 106
106 const auto elapsed = time.elapsed(); 107 const auto elapsed = time.elapsed();
107 108
@@ -145,7 +146,6 @@ private slots:
145 void init() 146 void init()
146 { 147 {
147 resourceIdentifier = "sink.test.instance1"; 148 resourceIdentifier = "sink.test.instance1";
148 Sink::AdaptorFactoryRegistry::instance().registerFactory<Mail, TestMailAdaptorFactory>("test");
149 } 149 }
150 150
151 void test50k() 151 void test50k()
@@ -159,7 +159,24 @@ private slots:
159 query.limit(1000); 159 query.limit(1000);
160 160
161 populateDatabase(50000); 161 populateDatabase(50000);
162 testLoad(query, 50000); 162 testLoad(query, 50000, query.limit());
163 }
164
165 void test50kThreadleader()
166 {
167 Sink::Query query;
168 query.request<Mail::MessageId>()
169 .request<Mail::Subject>()
170 .request<Mail::Date>();
171 // query.filter<ApplicationDomain::Mail::Trash>(false);
172 query.reduce<ApplicationDomain::Mail::Folder>(Query::Reduce::Selector::max<ApplicationDomain::Mail::Date>());
173 query.limit(1000);
174
175 int mailsPerFolder = 10;
176
177 int count = 50000;
178 populateDatabase(count, mailsPerFolder);
179 testLoad(query, count, query.limit());
163 } 180 }
164}; 181};
165 182
diff --git a/tests/mailsynctest.cpp b/tests/mailsynctest.cpp
index 3e5a928..c8ba9f1 100644
--- a/tests/mailsynctest.cpp
+++ b/tests/mailsynctest.cpp
@@ -411,6 +411,43 @@ void MailSyncTest::testSyncSingleFolder()
411 411
412} 412}
413 413
414void MailSyncTest::testSyncSingleMail()
415{
416 VERIFYEXEC(Store::synchronize(Sink::SyncScope{}.resourceFilter(mResourceInstanceIdentifier)));
417 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier));
418
419 Mail::Ptr mail;
420 {
421 auto job = Store::fetchAll<Mail>(Sink::Query{}.resourceFilter(mResourceInstanceIdentifier)).template then([&](const QList<Mail::Ptr> &mails) {
422 QVERIFY(mails.size() >= 1);
423 mail = mails.first();
424 });
425 VERIFYEXEC(job);
426 }
427
428 auto syncScope = Sink::SyncScope{ApplicationDomain::getTypeName<Mail>()};
429 syncScope.resourceFilter(mResourceInstanceIdentifier);
430 syncScope.filter(mail->identifier());
431
432 // Ensure all local data is processed
433 VERIFYEXEC(Store::synchronize(syncScope));
434 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier));
435}
436
437void MailSyncTest::testSyncSingleMailWithBogusId()
438{
439 VERIFYEXEC(Store::synchronize(Sink::SyncScope{}.resourceFilter(mResourceInstanceIdentifier)));
440 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier));
441
442 auto syncScope = Sink::SyncScope{ApplicationDomain::getTypeName<Mail>()};
443 syncScope.resourceFilter(mResourceInstanceIdentifier);
444 syncScope.filter("WTFisThisEven?");
445
446 // Ensure all local data is processed
447 VERIFYEXEC(Store::synchronize(syncScope));
448 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier));
449}
450
414void MailSyncTest::testFailingSync() 451void MailSyncTest::testFailingSync()
415{ 452{
416 auto resource = createFaultyResource(); 453 auto resource = createFaultyResource();
diff --git a/tests/mailsynctest.h b/tests/mailsynctest.h
index 8ad3bb3..0decf00 100644
--- a/tests/mailsynctest.h
+++ b/tests/mailsynctest.h
@@ -70,6 +70,8 @@ private slots:
70 void testFlagChange(); 70 void testFlagChange();
71 71
72 void testSyncSingleFolder(); 72 void testSyncSingleFolder();
73 void testSyncSingleMail();
74 void testSyncSingleMailWithBogusId();
73 75
74 void testFailingSync(); 76 void testFailingSync();
75}; 77};
diff --git a/tests/modelinteractivitytest.cpp b/tests/modelinteractivitytest.cpp
index 5231c1a..caa9ca2 100644
--- a/tests/modelinteractivitytest.cpp
+++ b/tests/modelinteractivitytest.cpp
@@ -70,9 +70,9 @@ private slots:
70 { 70 {
71 // Setup 71 // Setup
72 { 72 {
73 Sink::ApplicationDomain::Mail mail("sink.dummy.instance1"); 73 Sink::ApplicationDomain::Event event("sink.dummy.instance1");
74 for (int i = 0; i < 1000; i++) { 74 for (int i = 0; i < 1000; i++) {
75 Sink::Store::create<Sink::ApplicationDomain::Mail>(mail).exec().waitForFinished(); 75 Sink::Store::create<Sink::ApplicationDomain::Event>(event).exec().waitForFinished();
76 } 76 }
77 } 77 }
78 78
@@ -85,7 +85,7 @@ private slots:
85 // Test 85 // Test
86 QTime time; 86 QTime time;
87 time.start(); 87 time.start();
88 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query); 88 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Event>(query);
89 blockingTime += time.elapsed(); 89 blockingTime += time.elapsed();
90 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); 90 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
91 // Never block longer than 10 ms 91 // Never block longer than 10 ms
diff --git a/tests/notificationtest.cpp b/tests/notificationtest.cpp
new file mode 100644
index 0000000..a34d325
--- /dev/null
+++ b/tests/notificationtest.cpp
@@ -0,0 +1,132 @@
1#include <QtTest>
2
3#include <QString>
4#include <QSignalSpy>
5
6#include "store.h"
7#include "resourceconfig.h"
8#include "resourcecontrol.h"
9#include "modelresult.h"
10#include "log.h"
11#include "test.h"
12#include "testutils.h"
13#include "notifier.h"
14#include "notification.h"
15
16using namespace Sink;
17using namespace Sink::ApplicationDomain;
18
19/**
20 * Test of complete system using the dummy resource.
21 *
22 * This test requires the dummy resource installed.
23 */
24class NotificationTest : public QObject
25{
26 Q_OBJECT
27
28private slots:
29 void initTestCase()
30 {
31 Sink::Test::initTest();
32 ResourceConfig::addResource("sink.dummy.instance1", "sink.dummy");
33 }
34
35 void cleanup()
36 {
37 VERIFYEXEC(Sink::Store::removeDataFromDisk("sink.dummy.instance1"));
38 }
39
40 void testSyncNotifications()
41 {
42 auto query = Query().resourceFilter("sink.dummy.instance1");
43 query.setType<ApplicationDomain::Mail>();
44 query.filter("id1");
45 query.filter("id2");
46
47 QList<Sink::Notification> statusNotifications;
48 QList<Sink::Notification> infoNotifications;
49 Sink::Notifier notifier("sink.dummy.instance1");
50 notifier.registerHandler([&] (const Sink::Notification &n){
51 SinkLogCtx(Sink::Log::Context{"dummyresourcetest"}) << "Received notification " << n;
52 if (n.type == Notification::Status) {
53 if (n.id == "changereplay") {
54 //We filter all changereplay notifications.
55 //Not the best way but otherwise the test becomes unstable and we currently
56 //only have the id to detect changereplay notifications.
57 return;
58 }
59 statusNotifications << n;
60 }
61 if (n.type == Notification::Info) {
62 infoNotifications << n;
63 }
64 });
65
66 // Ensure all local data is processed
67 VERIFYEXEC(Sink::Store::synchronize(query));
68 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
69
70 QVERIFY(statusNotifications.size() <= 3);
71 QTRY_COMPARE(statusNotifications.size(), 3);
72 //Sync
73 QCOMPARE(statusNotifications.at(0).code, static_cast<int>(ApplicationDomain::Status::ConnectedStatus));
74 QCOMPARE(statusNotifications.at(1).code, static_cast<int>(ApplicationDomain::Status::BusyStatus));
75 QCOMPARE(statusNotifications.at(2).code, static_cast<int>(ApplicationDomain::Status::ConnectedStatus));
76 //Changereplay
77 // 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).
79 // QCOMPARE(statusNotifications.at(3).code, static_cast<int>(Sink::ApplicationDomain::Status::BusyStatus));
80 // QCOMPARE(statusNotifications.at(4).code, static_cast<int>(Sink::ApplicationDomain::Status::ConnectedStatus));
81
82 QTRY_COMPARE(infoNotifications.size(), 2);
83 QCOMPARE(infoNotifications.at(0).code, static_cast<int>(ApplicationDomain::SyncStatus::SyncInProgress));
84 QCOMPARE(infoNotifications.at(0).entities, QList<QByteArray>{} << "id1" << "id2");
85 QCOMPARE(infoNotifications.at(1).code, static_cast<int>(ApplicationDomain::SyncStatus::SyncSuccess));
86 QCOMPARE(infoNotifications.at(1).entities, QList<QByteArray>{} << "id1" << "id2");
87
88 QCOMPARE(infoNotifications.at(1).code, static_cast<int>(ApplicationDomain::SyncStatus::SyncSuccess));
89 }
90
91 void testModelNotifications()
92 {
93 auto query = Query().resourceFilter("sink.dummy.instance1");
94 query.setType<ApplicationDomain::Mail>();
95 query.setFlags(Query::LiveQuery | Query::UpdateStatus);
96
97 VERIFYEXEC(Sink::Store::synchronize(query));
98 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
99
100 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query);
101 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
102 QVERIFY(model->rowCount() >= 1);
103
104 QSignalSpy changedSpy(model.data(), &QAbstractItemModel::dataChanged);
105 auto mail = model->index(0, 0, QModelIndex()).data(Sink::Store::DomainObjectRole).value<Mail::Ptr>();
106 auto newQuery = query;
107 newQuery.filter(mail->identifier());
108
109 QList<int> status;
110 QObject::connect(model.data(), &QAbstractItemModel::dataChanged, [&] (const QModelIndex &begin, const QModelIndex &end, const QVector<int> &roles) {
111 QVERIFY(begin.row() == end.row());
112 if (begin.row() == 0) {
113 status << model->data(begin, Store::StatusRole).value<int>();
114 // qWarning() << "New status: " << status.last() << roles;
115 }
116 });
117
118 //This will trigger a modification of all previous items as well.
119 VERIFYEXEC(Sink::Store::synchronize(newQuery));
120 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
121
122 QCOMPARE(status.size(), 3);
123 //Sync progress of item
124 QCOMPARE(status.at(0), static_cast<int>(ApplicationDomain::SyncStatus::SyncInProgress));
125 QCOMPARE(status.at(1), static_cast<int>(ApplicationDomain::SyncStatus::SyncSuccess));
126 //Modification triggered during sync
127 QCOMPARE(status.at(2), static_cast<int>(ApplicationDomain::SyncStatus::SyncSuccess));
128 }
129};
130
131QTEST_MAIN(NotificationTest)
132#include "notificationtest.moc"
diff --git a/tests/querytest.cpp b/tests/querytest.cpp
index bd3b927..f639d94 100644
--- a/tests/querytest.cpp
+++ b/tests/querytest.cpp
@@ -1042,6 +1042,71 @@ private slots:
1042 QCOMPARE(layoutChangedSpy.size(), 0); 1042 QCOMPARE(layoutChangedSpy.size(), 0);
1043 QCOMPARE(resetSpy.size(), 0); 1043 QCOMPARE(resetSpy.size(), 0);
1044 } 1044 }
1045
1046 /*
1047 * Ensure that we handle the situation properly if the thread-leader doesn't match a property filter.
1048 */
1049 void testFilteredThreadLeader()
1050 {
1051 // Setup
1052 auto folder1 = Folder::createEntity<Folder>("sink.dummy.instance1");
1053 VERIFYEXEC(Sink::Store::create<Folder>(folder1));
1054
1055 auto folder2 = Folder::createEntity<Folder>("sink.dummy.instance1");
1056 VERIFYEXEC(Sink::Store::create<Folder>(folder2));
1057
1058 QDateTime earlier{QDate{2017, 2, 3}, QTime{9, 0, 0}};
1059 QDateTime now{QDate{2017, 2, 3}, QTime{10, 0, 0}};
1060 QDateTime later{QDate{2017, 2, 3}, QTime{11, 0, 0}};
1061
1062 {
1063 auto mail1 = Mail::createEntity<Mail>("sink.dummy.instance1");
1064 mail1.setExtractedMessageId("mail1");
1065 mail1.setFolder(folder1);
1066 mail1.setExtractedDate(now);
1067 mail1.setImportant(false);
1068 VERIFYEXEC(Sink::Store::create(mail1));
1069 }
1070 {
1071 auto mail2 = Mail::createEntity<Mail>("sink.dummy.instance1");
1072 mail2.setExtractedMessageId("mail2");
1073 mail2.setFolder(folder1);
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
1087 // Ensure all local data is processed
1088 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1"));
1089
1090 Query query;
1091 query.setId("testLivequeryThreadleaderChange");
1092 query.setFlags(Query::LiveQuery);
1093 query.reduce<Mail::Folder>(Query::Reduce::Selector::max<Mail::Date>()).count("count").collect<Mail::Folder>("folders");
1094 query.sort<Mail::Date>();
1095 query.request<Mail::MessageId>();
1096 query.filter<Mail::Important>(false);
1097
1098 auto model = Sink::Store::loadModel<Mail>(query);
1099 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
1100
1101 QCOMPARE(model->rowCount(), 1);
1102
1103 {
1104 auto mail = model->data(model->index(0, 0, QModelIndex{}), Sink::Store::DomainObjectRole).value<Mail::Ptr>();
1105 QCOMPARE(mail->getMessageId(), QByteArray{"mail1"});
1106 QCOMPARE(mail->getProperty("count").toInt(), 2);
1107 QCOMPARE(mail->getProperty("folders").toList().size(), 2);
1108 }
1109 }
1045}; 1110};
1046 1111
1047QTEST_MAIN(QueryTest) 1112QTEST_MAIN(QueryTest)