diff options
125 files changed, 2134 insertions, 2410 deletions
@@ -1,3 +1,4 @@ | |||
1 | *.swp | 1 | *.swp |
2 | *.kdev4 | 2 | *.kdev4 |
3 | build/ | ||
3 | site | 4 | site |
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) | |||
3 | cmake_policy(SET CMP0048 NEW) | 3 | cmake_policy(SET CMP0048 NEW) |
4 | cmake_policy(SET CMP0028 NEW) | 4 | cmake_policy(SET CMP0028 NEW) |
5 | 5 | ||
6 | project(sink VERSION 0.1.0) | 6 | project(sink VERSION 0.2) |
7 | 7 | ||
8 | option(BUILD_MAILDIR "BUILD_MAILDIR" ON) | 8 | option(BUILD_MAILDIR "BUILD_MAILDIR" ON) |
9 | option(BUILD_DAV "BUILD_DAV" OFF) | 9 | option(BUILD_DAV "BUILD_DAV" OFF) |
@@ -27,9 +27,9 @@ include(KDEInstallDirs) | |||
27 | 27 | ||
28 | find_package(Qt5 COMPONENTS REQUIRED Core Network Gui) | 28 | find_package(Qt5 COMPONENTS REQUIRED Core Network Gui) |
29 | find_package(KF5 COMPONENTS REQUIRED Mime Contacts) | 29 | find_package(KF5 COMPONENTS REQUIRED Mime Contacts) |
30 | find_package(FlatBuffers REQUIRED) | 30 | find_package(FlatBuffers REQUIRED 1.4.0) |
31 | find_package(KAsync REQUIRED 0.1.0) | 31 | find_package(KAsync REQUIRED 0.1.0) |
32 | find_package(LMDB REQUIRED) | 32 | find_package(LMDB REQUIRED 0.9) |
33 | 33 | ||
34 | find_program(MEMORYCHECK_COMMAND valgrind) | 34 | find_program(MEMORYCHECK_COMMAND valgrind) |
35 | set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes --leak-check=full") | 35 | set(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) | 12 | FIND_PACKAGE(PkgConfig) |
13 | #PKG_SEARCH_MODULE(PC_LIBGIT2 libgit2) | 13 | PKG_SEARCH_MODULE(PC_LIBGIT2 libgit2) |
14 | 14 | ||
15 | SET(LIBGIT2_DEFINITIONS ${PC_LIBGIT2_CFLAGS_OTHER}) | 15 | SET(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 | 29 | message("foo: ${LIBGIT2_INCLUDE_DIR} : ${PC_LIBGIT2_INCLUDEDIR} : ${PC_LIBGIT2_INCLUDE_DIRS}") |
30 | include(CheckFunctionExists) | 30 | |
31 | CHECK_FUNCTION_EXISTS(git_buf_free HAVE_BUF_FREE) | 31 | # get version from header, should work on windows, too |
32 | if(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) | ||
45 | endif() | ||
32 | 46 | ||
33 | INCLUDE(FindPackageHandleStandardArgs) | 47 | INCLUDE(FindPackageHandleStandardArgs) |
34 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(libgit2 DEFAULT_MSG LIBGIT2_LIBRARIES LIBGIT2_INCLUDE_DIR HAVE_BUF_FREE) | 48 | FIND_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 | ||
36 | MARK_AS_ADVANCED(LIBGIT2_INCLUDE_DIR LIBGIT2_LIBRARIES) | 53 | MARK_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 @@ | |||
29 | using namespace Sink; | 29 | using namespace Sink; |
30 | using namespace Sink::Storage; | 30 | using namespace Sink::Storage; |
31 | 31 | ||
32 | ChangeReplay::ChangeReplay(const ResourceContext &resourceContext) | 32 | ChangeReplay::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 | ||
38 | qint64 ChangeReplay::getLastReplayedRevision() | 38 | qint64 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 |
41 | public: | 41 | public: |
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 | ||
68 | class NullChangeReplay : public ChangeReplay | 69 | class 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 | ||
25 | namespace Sink { | 26 | namespace 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 | ||
81 | static 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 | |||
80 | void write(QIODevice *device, int messageId, int commandId, const char *buffer, uint size) | 88 | void 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 | ||
94 | void write(QIODevice *device, int messageId, int commandId, flatbuffers::FlatBufferBuilder &fbb) | 102 | void 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 | ||
3 | table Notification { | 3 | table 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 | ||
10 | root_type Notification; | 11 | root_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 | ||
155 | class Reduce : public FilterBase { | 155 | class Reduce : public Filter { |
156 | public: | 156 | public: |
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: | |||
362 | DataStoreQuery::DataStoreQuery(const Sink::QueryBase &query, const QByteArray &type, EntityStore &store) | 367 | DataStoreQuery::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 | ||
368 | DataStoreQuery::DataStoreQuery(const DataStoreQuery::State &state, const QByteArray &type, Sink::Storage::EntityStore &store) | 374 | DataStoreQuery::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 @@ | |||
1 | namespace Sink.ApplicationDomain.Buffer; | ||
2 | |||
3 | table Addressbook { | ||
4 | name:string; | ||
5 | parent:string; | ||
6 | } | ||
7 | |||
8 | root_type Addressbook; | ||
9 | file_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() { | |||
72 | namespace Sink { | 75 | namespace Sink { |
73 | namespace ApplicationDomain { | 76 | namespace ApplicationDomain { |
74 | 77 | ||
78 | constexpr const char *SinkResource::name; | ||
79 | constexpr const char *SinkAccount::name; | ||
80 | |||
81 | SINK_REGISTER_ENTITY(Mail); | ||
75 | SINK_REGISTER_PROPERTY(Mail, Sender); | 82 | SINK_REGISTER_PROPERTY(Mail, Sender); |
76 | SINK_REGISTER_PROPERTY(Mail, To); | 83 | SINK_REGISTER_PROPERTY(Mail, To); |
77 | SINK_REGISTER_PROPERTY(Mail, Cc); | 84 | SINK_REGISTER_PROPERTY(Mail, Cc); |
@@ -90,11 +97,28 @@ SINK_REGISTER_PROPERTY(Mail, MessageId); | |||
90 | SINK_REGISTER_PROPERTY(Mail, ParentMessageId); | 97 | SINK_REGISTER_PROPERTY(Mail, ParentMessageId); |
91 | SINK_REGISTER_PROPERTY(Mail, ThreadId); | 98 | SINK_REGISTER_PROPERTY(Mail, ThreadId); |
92 | 99 | ||
100 | SINK_REGISTER_ENTITY(Folder); | ||
93 | SINK_REGISTER_PROPERTY(Folder, Name); | 101 | SINK_REGISTER_PROPERTY(Folder, Name); |
94 | SINK_REGISTER_PROPERTY(Folder, Icon); | 102 | SINK_REGISTER_PROPERTY(Folder, Icon); |
95 | SINK_REGISTER_PROPERTY(Folder, SpecialPurpose); | 103 | SINK_REGISTER_PROPERTY(Folder, SpecialPurpose); |
96 | SINK_REGISTER_PROPERTY(Folder, Enabled); | 104 | SINK_REGISTER_PROPERTY(Folder, Enabled); |
97 | SINK_REGISTER_PROPERTY(Folder, Parent); | 105 | SINK_REGISTER_PROPERTY(Folder, Parent); |
106 | SINK_REGISTER_PROPERTY(Folder, Count); | ||
107 | SINK_REGISTER_PROPERTY(Folder, FullContentAvailable); | ||
108 | |||
109 | SINK_REGISTER_ENTITY(Contact); | ||
110 | SINK_REGISTER_PROPERTY(Contact, Uid); | ||
111 | SINK_REGISTER_PROPERTY(Contact, Fn); | ||
112 | SINK_REGISTER_PROPERTY(Contact, Firstname); | ||
113 | SINK_REGISTER_PROPERTY(Contact, Lastname); | ||
114 | SINK_REGISTER_PROPERTY(Contact, Emails); | ||
115 | SINK_REGISTER_PROPERTY(Contact, Vcard); | ||
116 | SINK_REGISTER_PROPERTY(Contact, Addressbook); | ||
117 | |||
118 | SINK_REGISTER_ENTITY(Addressbook); | ||
119 | SINK_REGISTER_PROPERTY(Addressbook, Name); | ||
120 | SINK_REGISTER_PROPERTY(Addressbook, Parent); | ||
121 | SINK_REGISTER_PROPERTY(Addressbook, LastUpdated); | ||
98 | 122 | ||
99 | static const int foo = [] { | 123 | static 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 | ||
367 | template<> | 389 | SinkResource CardDavResource::create(const QByteArray &account) |
368 | QByteArray getTypeName<Contact>() | ||
369 | { | ||
370 | return "contact"; | ||
371 | } | ||
372 | |||
373 | template<> | ||
374 | QByteArray getTypeName<Event>() | ||
375 | { | ||
376 | return "event"; | ||
377 | } | ||
378 | |||
379 | template<> | ||
380 | QByteArray getTypeName<Todo>() | ||
381 | { | ||
382 | return "todo"; | ||
383 | } | ||
384 | |||
385 | template<> | ||
386 | QByteArray getTypeName<SinkResource>() | ||
387 | { | ||
388 | return "resource"; | ||
389 | } | ||
390 | |||
391 | template<> | ||
392 | QByteArray getTypeName<SinkAccount>() | ||
393 | { | ||
394 | return "account"; | ||
395 | } | ||
396 | |||
397 | template<> | ||
398 | QByteArray getTypeName<Identity>() | ||
399 | { | ||
400 | return "identity"; | ||
401 | } | ||
402 | |||
403 | template<> | ||
404 | QByteArray getTypeName<Mail>() | ||
405 | { | 390 | { |
406 | return "mail"; | 391 | auto &&resource = ApplicationDomainType::createEntity<SinkResource>(); |
407 | } | 392 | resource.setResourceType("sink.dav"); |
408 | 393 | resource.setAccount(account); | |
409 | template<> | 394 | return resource; |
410 | QByteArray getTypeName<Folder>() | ||
411 | { | ||
412 | return "folder"; | ||
413 | } | 395 | } |
414 | 396 | ||
415 | QByteArrayList getTypeNames() | 397 | QByteArrayList 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 @@ | |||
92 | namespace Sink { | 93 | namespace Sink { |
93 | namespace ApplicationDomain { | 94 | namespace ApplicationDomain { |
94 | 95 | ||
96 | enum SINK_EXPORT ErrorCode { | ||
97 | NoError = 0, | ||
98 | UnknownError, | ||
99 | NoServerError, | ||
100 | ConnectionError, | ||
101 | LoginError, | ||
102 | ConfigurationError, | ||
103 | TransmissionError, | ||
104 | }; | ||
105 | |||
106 | enum SINK_EXPORT SuccessCode { | ||
107 | TransmissionSuccess | ||
108 | }; | ||
109 | |||
110 | enum 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 | */ | ||
126 | enum SINK_EXPORT Status { | ||
127 | OfflineStatus, | ||
128 | ConnectedStatus, | ||
129 | BusyStatus, | ||
130 | ErrorStatus | ||
131 | }; | ||
132 | |||
95 | struct SINK_EXPORT Error { | 133 | struct 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 | */ | ||
103 | struct BLOB { | 146 | struct 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 | ||
270 | struct SINK_EXPORT SinkAccount : public ApplicationDomainType { | 313 | struct 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 | */ |
292 | struct SINK_EXPORT SinkResource : public ApplicationDomainType { | 334 | struct 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 | ||
307 | struct SINK_EXPORT Entity : public ApplicationDomainType { | 348 | struct 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 | ||
356 | struct 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 | |||
315 | struct SINK_EXPORT Contact : public Entity { | 363 | struct 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 | ||
323 | struct SINK_EXPORT Event : public Entity { | 383 | struct 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 | ||
331 | struct SINK_EXPORT Todo : public Entity { | 391 | struct SINK_EXPORT Todo : public Entity { |
332 | SINK_ENTITY(Todo); | 392 | SINK_ENTITY(Todo, todo); |
333 | }; | 393 | }; |
334 | 394 | ||
335 | struct SINK_EXPORT Calendar : public Entity { | 395 | struct SINK_EXPORT Calendar : public Entity { |
336 | SINK_ENTITY(Calendar); | 396 | SINK_ENTITY(Calendar, calendar); |
337 | }; | 397 | }; |
338 | 398 | ||
339 | struct SINK_EXPORT Folder : public Entity { | 399 | struct 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 | ||
348 | struct SINK_EXPORT Mail : public Entity { | 411 | struct 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 | ||
374 | SINK_EXPORT QDebug operator<< (QDebug d, const Mail::Contact &c); | 437 | SINK_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 | */ | ||
385 | enum SINK_EXPORT Status { | ||
386 | OfflineStatus, | ||
387 | ConnectedStatus, | ||
388 | BusyStatus, | ||
389 | ErrorStatus | ||
390 | }; | ||
391 | |||
392 | struct SINK_EXPORT Identity : public ApplicationDomainType { | 439 | struct 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 | ||
467 | struct SINK_EXPORT CardDavResource { | ||
468 | static SinkResource create(const QByteArray &account); | ||
469 | }; | ||
470 | |||
419 | namespace ResourceCapabilities { | 471 | namespace ResourceCapabilities { |
420 | namespace Mail { | 472 | namespace 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 | }; |
482 | namespace 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 | ||
432 | namespace SpecialPurpose { | 489 | namespace 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 | */ |
446 | template<class DomainType> | 503 | template<class DomainType> |
447 | QByteArray SINK_EXPORT getTypeName(); | 504 | QByteArray SINK_EXPORT getTypeName() |
448 | 505 | { | |
449 | template<> | 506 | return DomainType::name; |
450 | QByteArray SINK_EXPORT getTypeName<Contact>(); | 507 | } |
451 | |||
452 | template<> | ||
453 | QByteArray SINK_EXPORT getTypeName<Event>(); | ||
454 | |||
455 | template<> | ||
456 | QByteArray SINK_EXPORT getTypeName<Todo>(); | ||
457 | |||
458 | template<> | ||
459 | QByteArray SINK_EXPORT getTypeName<SinkResource>(); | ||
460 | |||
461 | template<> | ||
462 | QByteArray SINK_EXPORT getTypeName<SinkAccount>(); | ||
463 | |||
464 | template<> | ||
465 | QByteArray SINK_EXPORT getTypeName<Identity>(); | ||
466 | |||
467 | template<> | ||
468 | QByteArray SINK_EXPORT getTypeName<Mail>(); | ||
469 | |||
470 | template<> | ||
471 | QByteArray SINK_EXPORT getTypeName<Folder>(); | ||
472 | 508 | ||
473 | QByteArrayList SINK_EXPORT getTypeNames(); | 509 | QByteArrayList 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) | |||
520 | Q_DECLARE_METATYPE(Sink::ApplicationDomain::Entity) | 557 | Q_DECLARE_METATYPE(Sink::ApplicationDomain::Entity) |
521 | Q_DECLARE_METATYPE(Sink::ApplicationDomain::Entity::Ptr) | 558 | Q_DECLARE_METATYPE(Sink::ApplicationDomain::Entity::Ptr) |
522 | Q_DECLARE_METATYPE(Sink::ApplicationDomain::Mail::Contact) | 559 | Q_DECLARE_METATYPE(Sink::ApplicationDomain::Mail::Contact) |
560 | Q_DECLARE_METATYPE(Sink::ApplicationDomain::Contact::Email) | ||
523 | Q_DECLARE_METATYPE(Sink::ApplicationDomain::Error) | 561 | Q_DECLARE_METATYPE(Sink::ApplicationDomain::Error) |
524 | Q_DECLARE_METATYPE(Sink::ApplicationDomain::Progress) | 562 | Q_DECLARE_METATYPE(Sink::ApplicationDomain::Progress) |
525 | Q_DECLARE_METATYPE(Sink::ApplicationDomain::BLOB) | 563 | Q_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 | |||
31 | using namespace Sink::ApplicationDomain; | ||
32 | |||
33 | void TypeImplementation<Contact>::configure(TypeIndex &index) | ||
34 | { | ||
35 | index.addProperty<QByteArray>(Contact::Uid::name); | ||
36 | } | ||
37 | |||
38 | void 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 | |||
46 | void 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 | |||
54 | void 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 @@ | |||
1 | namespace Sink.ApplicationDomain.Buffer; | 1 | namespace Sink.ApplicationDomain.Buffer; |
2 | 2 | ||
3 | table ContactEmail { | ||
4 | type: int; | ||
5 | email: string; | ||
6 | } | ||
7 | |||
3 | table Contact { | 8 | table 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 | |||
23 | class QByteArray; | ||
24 | |||
25 | template<typename T> | ||
26 | class ReadPropertyMapper; | ||
27 | template<typename T> | ||
28 | class WritePropertyMapper; | ||
29 | class IndexPropertyMapper; | ||
30 | |||
31 | class TypeIndex; | ||
32 | |||
33 | namespace Sink { | ||
34 | namespace 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 | */ | ||
45 | template<> | ||
46 | class TypeImplementation<Sink::ApplicationDomain::Contact> { | ||
47 | public: | ||
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 | |||
31 | using namespace Sink::ApplicationDomain; | ||
32 | |||
33 | void TypeImplementation<Event>::configure(TypeIndex &index) | ||
34 | { | ||
35 | index.addProperty<QByteArray>(Event::Uid::name); | ||
36 | } | ||
37 | |||
38 | void 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 | |||
46 | void 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 | |||
54 | void 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 | |||
23 | class QByteArray; | ||
24 | |||
25 | template<typename T> | ||
26 | class ReadPropertyMapper; | ||
27 | template<typename T> | ||
28 | class WritePropertyMapper; | ||
29 | class IndexPropertyMapper; | ||
30 | |||
31 | class TypeIndex; | ||
32 | |||
33 | namespace Sink { | ||
34 | namespace 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 | */ | ||
45 | template<> | ||
46 | class TypeImplementation<Sink::ApplicationDomain::Event> { | ||
47 | public: | ||
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 | |||
31 | using namespace Sink::ApplicationDomain; | ||
32 | |||
33 | void TypeImplementation<Folder>::configure(TypeIndex &index) | ||
34 | { | ||
35 | index.addProperty<QByteArray>(Folder::Parent::name); | ||
36 | index.addProperty<QString>(Folder::Name::name); | ||
37 | } | ||
38 | |||
39 | void 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 | |||
48 | void 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 | |||
57 | void 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 | |||
23 | template<typename T> | ||
24 | class ReadPropertyMapper; | ||
25 | template<typename T> | ||
26 | class WritePropertyMapper; | ||
27 | class IndexPropertyMapper; | ||
28 | |||
29 | class TypeIndex; | ||
30 | |||
31 | namespace Sink { | ||
32 | |||
33 | namespace ApplicationDomain { | ||
34 | namespace Buffer { | ||
35 | struct Folder; | ||
36 | struct FolderBuilder; | ||
37 | } | ||
38 | |||
39 | template<> | ||
40 | class TypeImplementation<Sink::ApplicationDomain::Folder> { | ||
41 | public: | ||
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 | |||
23 | class QByteArray; | ||
24 | |||
25 | template<typename T> | ||
26 | class ReadPropertyMapper; | ||
27 | template<typename T> | ||
28 | class WritePropertyMapper; | ||
29 | class IndexPropertyMapper; | ||
30 | |||
31 | class TypeIndex; | ||
32 | |||
33 | namespace Sink { | ||
34 | namespace ApplicationDomain { | ||
35 | namespace Buffer { | ||
36 | struct Mail; | ||
37 | struct MailBuilder; | ||
38 | } | ||
39 | |||
40 | template<> | ||
41 | class TypeImplementation<Sink::ApplicationDomain::Mail> { | ||
42 | public: | ||
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 | ||
66 | template <> | 66 | template <> |
67 | QVariant 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 | |||
77 | template <> | ||
67 | QVariant parseString<QList<QByteArray>>(const QString &s) | 78 | QVariant 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 | ||
106 | template <> | ||
107 | QVariant parseString<QList<Sink::ApplicationDomain::Contact::Email>>(const QString &s) | ||
108 | { | ||
109 | Q_ASSERT(false); | ||
110 | return QVariant{}; | ||
111 | } | ||
112 | |||
95 | PropertyRegistry &PropertyRegistry::instance() | 113 | PropertyRegistry &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 <> | |||
49 | QVariant parseString<bool>(const QString &s); | 49 | QVariant parseString<bool>(const QString &s); |
50 | 50 | ||
51 | template <> | 51 | template <> |
52 | QVariant parseString<int>(const QString &s); | ||
53 | |||
54 | template <> | ||
52 | QVariant parseString<QList<QByteArray>>(const QString &s); | 55 | QVariant parseString<QList<QByteArray>>(const QString &s); |
53 | 56 | ||
54 | template <> | 57 | template <> |
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 | |||
34 | using namespace Sink; | 32 | using namespace Sink; |
35 | using namespace Sink::ApplicationDomain; | 33 | using 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 | |||
105 | void TypeImplementation<Folder>::configure(TypeIndex &index) | ||
106 | { | ||
107 | index.addProperty<QByteArray>(Folder::Parent::name); | ||
108 | index.addProperty<QString>(Folder::Name::name); | ||
109 | } | ||
110 | |||
111 | void 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 | |||
120 | void 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 | |||
129 | void TypeImplementation<Folder>::configure(IndexPropertyMapper &) | ||
130 | { | ||
131 | |||
132 | } | ||
133 | |||
134 | |||
135 | void TypeImplementation<Contact>::configure(TypeIndex &index) | ||
136 | { | ||
137 | index.addProperty<QByteArray>(Contact::Uid::name); | ||
138 | } | ||
139 | |||
140 | void 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 | |||
151 | void 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 | |||
162 | void TypeImplementation<Contact>::configure(IndexPropertyMapper &) | ||
163 | { | ||
164 | |||
165 | } | ||
166 | |||
167 | |||
168 | void TypeImplementation<Addressbook>::configure(TypeIndex &index) | ||
169 | { | ||
170 | index.addProperty<QByteArray>(Addressbook::Parent::name); | ||
171 | index.addProperty<QString>(Addressbook::Name::name); | ||
172 | } | ||
173 | |||
174 | void 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 | |||
180 | void 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 | |||
186 | void TypeImplementation<Addressbook>::configure(IndexPropertyMapper &) | ||
187 | { | ||
188 | |||
189 | } | ||
190 | |||
191 | |||
192 | void TypeImplementation<Event>::configure(TypeIndex &index) | ||
193 | { | ||
194 | index.addProperty<QByteArray>(Event::Uid::name); | ||
195 | } | ||
196 | |||
197 | void 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 | |||
205 | void 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 | |||
213 | void 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 | |||
29 | template<typename T> | ||
30 | class ReadPropertyMapper; | ||
31 | template<typename T> | ||
32 | class WritePropertyMapper; | ||
33 | class IndexPropertyMapper; | ||
34 | |||
35 | class 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 | */ | ||
42 | namespace Sink { | ||
43 | namespace ApplicationDomain { | ||
44 | |||
45 | template<> | ||
46 | class TypeImplementation<Sink::ApplicationDomain::Mail> { | ||
47 | public: | ||
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 | |||
56 | template<> | ||
57 | class TypeImplementation<Sink::ApplicationDomain::Folder> { | ||
58 | public: | ||
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 | |||
67 | template<> | ||
68 | class TypeImplementation<Sink::ApplicationDomain::Contact> { | ||
69 | public: | ||
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 | |||
78 | template<> | ||
79 | class TypeImplementation<Sink::ApplicationDomain::Addressbook> { | ||
80 | public: | ||
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 | |||
89 | template<> | ||
90 | class TypeImplementation<Sink::ApplicationDomain::Event> { | ||
91 | public: | ||
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 | */ | ||
249 | template<typename DomainType> | ||
250 | class DefaultAdaptorFactory : public DomainTypeAdaptorFactory<DomainType> | ||
251 | { | ||
252 | public: | ||
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; | |||
31 | GenericResource::GenericResource(const ResourceContext &resourceContext, const QSharedPointer<Pipeline> &pipeline ) | 31 | GenericResource::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 | ||
5 | SINK_DEBUG_AREA("index") | ||
6 | |||
7 | Index::Index(const QString &storageRoot, const QString &name, Sink::Storage::DataStore::AccessMode mode) | 5 | Index::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 | ||
14 | Index::Index(const QByteArray &name, Sink::Storage::DataStore::Transaction &transaction) | 13 | Index::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 | ||
19 | void Index::add(const QByteArray &key, const QByteArray &value) | 19 | void 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 | ||
26 | void Index::remove(const QByteArray &key, const QByteArray &value) | 26 | void 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 | ||
88 | void Listener::emergencyAbortAllConnections() | 89 | void 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 ¬ification) | |||
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 | |||
30 | using namespace Sink; | ||
31 | |||
32 | static uint getInternalIdentifer(const QByteArray &resourceId, const QByteArray &entityId) | ||
33 | { | ||
34 | return qHash(resourceId + entityId); | ||
35 | } | ||
27 | 36 | ||
28 | static uint qHash(const Sink::ApplicationDomain::ApplicationDomainType &type) | 37 | static 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 | ||
35 | static qint64 getIdentifier(const QModelIndex &idx) | 43 | static qint64 getIdentifier(const QModelIndex &idx) |
@@ -44,6 +52,86 @@ template <class T, class Ptr> | |||
44 | ModelResult<T, Ptr>::ModelResult(const Sink::Query &query, const QList<QByteArray> &propertyColumns, const Sink::Log::Context &ctx) | 52 | ModelResult<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 ¬ification) { | ||
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 | ||
49 | template <class T, class Ptr> | 137 | template <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 | ||
33 | namespace Sink { | ||
34 | class Notifier; | ||
35 | } | ||
36 | |||
33 | template <class T, class Ptr> | 37 | template <class T, class Ptr> |
34 | class ModelResult : public QAbstractItemModel | 38 | class ModelResult : public QAbstractItemModel |
35 | { | 39 | { |
36 | public: | 40 | public: |
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 | ||
22 | using namespace Sink; | ||
23 | |||
24 | static 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 | |||
22 | QDebug operator<<(QDebug dbg, const Sink::Notification &n) | 49 | QDebug 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 | ||
29 | using namespace Sink; | 31 | using 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 ¬ification) { | ||
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 | ||
42 | Notifier::Notifier(const QSharedPointer<ResourceAccess> &resourceAccess) : d(new Sink::Notifier::Private) | 55 | Notifier::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 ¬ification) { | 57 | d->listenForNotifications(resourceAccess); |
45 | for (const auto &handler : d->handler) { | ||
46 | handler(notification); | ||
47 | } | ||
48 | }); | ||
49 | d->resourceAccess << resourceAccess; | ||
50 | } | 58 | } |
51 | 59 | ||
52 | Notifier::Notifier(const QByteArray &instanceIdentifier, const QByteArray &resourceType) : d(new Sink::Notifier::Private) | 60 | Notifier::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 ¬ification) { | 64 | d->listenForNotifications(resourceAccess); |
57 | for (const auto &handler : d->handler) { | ||
58 | handler(notification); | ||
59 | } | ||
60 | }); | ||
61 | d->resourceAccess << resourceAccess; | ||
62 | } | 65 | } |
63 | 66 | ||
64 | Notifier::Notifier(const QByteArray &instanceIdentifier) : Notifier(instanceIdentifier, ResourceConfig::getResourceType(instanceIdentifier)) | 67 | Notifier::Notifier(const QByteArray &instanceIdentifier) : Notifier(instanceIdentifier, ResourceConfig::getResourceType(instanceIdentifier)) |
65 | { | 68 | { |
66 | } | 69 | } |
67 | 70 | ||
71 | Notifier::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 | |||
68 | void Notifier::registerHandler(std::function<void(const Notification &)> handler) | 97 | void 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 | ||
29 | class QAbstractItemModel; | 28 | class QAbstractItemModel; |
30 | 29 | ||
31 | namespace Sink { | 30 | namespace Sink { |
32 | class ResourceAccess; | 31 | class ResourceAccess; |
33 | class Notification; | 32 | class Notification; |
33 | class Query; | ||
34 | 34 | ||
35 | class SINK_EXPORT Notifier | 35 | class 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 | ||
43 | private: | 44 | private: |
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 | ||
199 | static 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 | |||
198 | KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size) | 204 | KAsync::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 | ||
400 | void Preprocessor::newEntity(ApplicationDomain::ApplicationDomainType &newEntity) | ||
401 | { | ||
402 | |||
403 | } | ||
404 | |||
405 | void Preprocessor::modifiedEntity(const ApplicationDomain::ApplicationDomainType &oldEntity, ApplicationDomain::ApplicationDomainType &newEntity) | ||
406 | { | ||
407 | |||
408 | } | ||
409 | |||
410 | void Preprocessor::deletedEntity(const ApplicationDomain::ApplicationDomainType &oldEntity) | ||
411 | { | ||
412 | |||
413 | } | ||
414 | |||
415 | Preprocessor::Result Preprocessor::processModification(Type type, const ApplicationDomain::ApplicationDomainType ¤t, 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 | |||
388 | QByteArray Preprocessor::resourceInstanceIdentifier() const | 433 | QByteArray 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 ¤t, 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 | ||
26 | template <> | 27 | template <> |
27 | flatbuffers::uoffset_t variantToProperty<QString>(const QVariant &property, flatbuffers::FlatBufferBuilder &fbb) | 28 | flatbuffers::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 | ||
114 | template <> | ||
115 | flatbuffers::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 | ||
114 | QString propertyToString(const flatbuffers::String *property) | 130 | QString propertyToString(const flatbuffers::String *property) |
115 | { | 131 | { |
@@ -217,6 +233,20 @@ QVariant propertyToVariant<QList<Sink::ApplicationDomain::Mail::Contact>>(const | |||
217 | } | 233 | } |
218 | 234 | ||
219 | template <> | 235 | template <> |
236 | QVariant 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 | |||
249 | template <> | ||
220 | QVariant propertyToVariant<bool>(uint8_t property) | 250 | QVariant 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 { | |||
29 | namespace ApplicationDomain { | 29 | namespace ApplicationDomain { |
30 | namespace Buffer { | 30 | namespace Buffer { |
31 | struct MailContact; | 31 | struct MailContact; |
32 | struct ContactEmail; | ||
32 | } | 33 | } |
33 | } | 34 | } |
34 | } | 35 | } |
@@ -54,6 +55,8 @@ template <typename T> | |||
54 | QVariant SINK_EXPORT propertyToVariant(const Sink::ApplicationDomain::Buffer::MailContact *); | 55 | QVariant SINK_EXPORT propertyToVariant(const Sink::ApplicationDomain::Buffer::MailContact *); |
55 | template <typename T> | 56 | template <typename T> |
56 | QVariant SINK_EXPORT propertyToVariant(const flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::MailContact>> *); | 57 | QVariant SINK_EXPORT propertyToVariant(const flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::MailContact>> *); |
58 | template <typename T> | ||
59 | QVariant 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 | |||
134 | private: | 143 | private: |
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 | |||
221 | private: | 239 | private: |
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 | ||
56 | private: | 56 | private: |
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 | ||
241 | template <class DomainType> | 241 | template <class DomainType> |
242 | ReplayResult QueryWorker<DomainType>::executeInitialQuery( | 242 | ReplayResult 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 ¬ification) { | 104 | notifier->registerHandler([&future, id](const Notification ¬ification) { |
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") | |||
34 | template<typename DomainType> | 34 | template<typename DomainType> |
35 | ConfigNotifier LocalStorageFacade<DomainType>::sConfigNotifier; | 35 | ConfigNotifier LocalStorageFacade<DomainType>::sConfigNotifier; |
36 | 36 | ||
37 | static void applyConfig(ConfigStore &configStore, const QByteArray &id, ApplicationDomain::ApplicationDomainType &object) | 37 | static 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 | ||
45 | template <typename DomainType> | 54 | template <typename DomainType> |
46 | static typename DomainType::Ptr readFromConfig(ConfigStore &configStore, const QByteArray &id, const QByteArray &type) | 55 | static 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 | ||
53 | template <> | 62 | template <> |
54 | typename ApplicationDomain::SinkAccount::Ptr readFromConfig<ApplicationDomain::SinkAccount>(ConfigStore &configStore, const QByteArray &id, const QByteArray &type) | 63 | typename 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 | ||
62 | template <> | 71 | template <> |
63 | typename ApplicationDomain::SinkResource::Ptr readFromConfig<ApplicationDomain::SinkResource>(ConfigStore &configStore, const QByteArray &id, const QByteArray &type) | 72 | typename 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> | |||
169 | void LocalStorageQueryRunner<DomainType>::statusChanged(const QByteArray &identifier) | 178 | void 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 | ||
49 | SpecialPurposeProcessor::SpecialPurposeProcessor(const QByteArray &resourceType, const QByteArray &resourceInstanceIdentifier) : mResourceType(resourceType), mResourceInstanceIdentifier(resourceInstanceIdentifier) {} | 49 | SpecialPurposeProcessor::SpecialPurposeProcessor() : Sink::Preprocessor() {} |
50 | 50 | ||
51 | QByteArray SpecialPurposeProcessor::findFolder(const QByteArray &specialPurpose, bool createIfMissing) | 51 | QByteArray 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 | ||
105 | void SpecialPurposeProcessor::newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) | 105 | void 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 | ||
110 | void SpecialPurposeProcessor::modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity) | 122 | void 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 { | |||
28 | class SINK_EXPORT SpecialPurposeProcessor : public Sink::Preprocessor | 28 | class SINK_EXPORT SpecialPurposeProcessor : public Sink::Preprocessor |
29 | { | 29 | { |
30 | public: | 30 | public: |
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 | ||
37 | using namespace Sink; | 36 | using namespace Sink; |
38 | using namespace Sink::Storage; | 37 | using namespace Sink::Storage; |
@@ -168,19 +167,15 @@ void EntityStore::copyBlobs(ApplicationDomain::ApplicationDomainType &entity, qi | |||
168 | } | 167 | } |
169 | } | 168 | } |
170 | 169 | ||
171 | bool EntityStore::add(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &entity_, bool replayToSource, const PreprocessCreation &preprocess) | 170 | bool 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 | ||
212 | bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &diff, const QByteArrayList &deletions, bool replayToSource, const PreprocessModification &preprocess) | 208 | ApplicationDomain::ApplicationDomainType EntityStore::applyDiff(const QByteArray &type, const ApplicationDomain::ApplicationDomainType ¤t, 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 | |||
232 | bool 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 | |||
244 | bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::ApplicationDomainType ¤t, 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 | ||
278 | bool EntityStore::remove(const QByteArray &type, const QByteArray &uid, bool replayToSource, const PreprocessRemoval &preprocess) | 284 | bool EntityStore::remove(const QByteArray &type, const Sink::ApplicationDomain::ApplicationDomainType ¤t, 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 | ||
522 | void EntityStore::readAll(const QByteArray &type, const std::function<void(const ApplicationDomain::ApplicationDomainType &entity)> &callback) | 507 | void 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 | ||
535 | void EntityStore::readRevisions(qint64 baseRevision, const QByteArray &expectedType, const std::function<void(const QByteArray &key)> &callback) | 514 | void 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 | ||
589 | void EntityStore::readAllUids(const QByteArray &type, const std::function<void(const QByteArray &uid)> callback) | 568 | void 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 | ||
602 | bool EntityStore::contains(const QByteArray &type, const QByteArray &uid) | 573 | bool 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 | ||
578 | bool 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 | |||
607 | qint64 EntityStore::maxRevision() | 608 | qint64 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 ¤t, ApplicationDomain::ApplicationDomainType newEntity, bool replayToSource); |
45 | bool remove(const QByteArray &type, const ApplicationDomain::ApplicationDomainType ¤t, bool replayToSource); | ||
49 | bool cleanupRevisions(qint64 revision); | 46 | bool cleanupRevisions(qint64 revision); |
47 | ApplicationDomain::ApplicationDomainType applyDiff(const QByteArray &type, const ApplicationDomain::ApplicationDomainType ¤t, 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 | ||
29 | QDebug& operator<<(QDebug &dbg, const Sink::Storage::DataStore::Error &error) | 29 | QDebug& 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 | ||
149 | void DataStore::recordUid(DataStore::Transaction &transaction, const QByteArray &uid) | ||
150 | { | ||
151 | transaction.openDatabase("uids").write(uid, ""); | ||
152 | } | ||
153 | |||
154 | void DataStore::removeUid(DataStore::Transaction &transaction, const QByteArray &uid) | ||
155 | { | ||
156 | transaction.openDatabase("uids").remove(uid); | ||
157 | } | ||
158 | |||
159 | void 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 | |||
149 | bool DataStore::isInternalKey(const char *key) | 167 | bool 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 | ||
65 | static 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 | |||
65 | class DataStore::NamedDatabase::Private | 103 | class DataStore::NamedDatabase::Private |
66 | { | 104 | { |
67 | public: | 105 | public: |
@@ -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) | |||
249 | KAsync::Job<void> Store::removeDataFromDisk(const QByteArray &identifier) | 251 | KAsync::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 | ||
278 | static KAsync::Job<void> synchronize(const QByteArray &resource, const Sink::SyncScope &scope) | 280 | static 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 | ||
294 | KAsync::Job<void> Store::synchronize(const Sink::Query &query) | 296 | KAsync::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 | */ |
49 | QString SINK_EXPORT getTemporaryFilePath(); | 49 | QString SINK_EXPORT getTemporaryFilePath(); |
50 | 50 | ||
51 | // Must be the same as in ModelResult | ||
51 | enum Roles | 52 | enum 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 @@ | |||
33 | using namespace Sink; | 33 | using namespace Sink; |
34 | 34 | ||
35 | Synchronizer::Synchronizer(const Sink::ResourceContext &context) | 35 | Synchronizer::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 | ||
253 | QList<Synchronizer::SyncRequest> Synchronizer::getSyncRequests(const Sink::QueryBase &query) | 254 | QList<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 | |
259 | void Synchronizer::mergeIntoQueue(const Synchronizer::SyncRequest &request, QList<Synchronizer::SyncRequest> &queue) | ||
260 | { | ||
261 | mSyncRequestQueue << request; | ||
258 | } | 262 | } |
259 | 263 | ||
260 | void Synchronizer::synchronize(const Sink::QueryBase &query) | 264 | void 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 | ||
296 | void 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 | |||
307 | void Synchronizer::reportProgress(int progress, int total) | ||
308 | { | ||
309 | SinkLogCtx(mLogCtx) << "Progress: " << progress << " out of " << total; | ||
310 | } | ||
311 | |||
312 | void 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 | |||
289 | KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request) | 332 | KAsync::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 | ||
415 | void 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 | |||
426 | void Synchronizer::resetStatus(const QByteArray requestId) | ||
427 | { | ||
428 | mCurrentState.pop(); | ||
429 | emitNotification(Notification::Status, mCurrentState.top(), {}, requestId); | ||
430 | } | ||
431 | |||
432 | void 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 | |||
372 | KAsync::Job<void> Synchronizer::processSyncQueue() | 443 | KAsync::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 | ||
623 | KAsync::Job<QByteArray> Synchronizer::replay(const ApplicationDomain::Addressbook &, Sink::Operation, const QByteArray &, const QList<QByteArray> &) | ||
624 | { | ||
625 | return KAsync::null<QByteArray>(); | ||
626 | } | ||
627 | |||
542 | KAsync::Job<QByteArray> Synchronizer::replay(const ApplicationDomain::Mail &, Sink::Operation, const QByteArray &, const QList<QByteArray> &) | 628 | KAsync::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: | |||
73 | protected: | 74 | protected: |
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 | |||
79 | protected: | 80 | protected: |
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 | |||
178 | protected: | 193 | protected: |
179 | Sink::Log::Context mLogCtx; | 194 | Sink::Log::Context mLogCtx; |
195 | |||
180 | private: | 196 | private: |
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 | ||
2 | Name: sink | 2 | Name: sink |
3 | Version: 0.1.0 | 3 | Version: 0.2 |
4 | Release: 6%{?dist} | 4 | Release: 2%{?dist} |
5 | Summary: sink | 5 | Summary: sink |
6 | 6 | ||
7 | Group: Applications/Desktop | 7 | Group: Applications/Desktop |
8 | License: GPL | 8 | License: GPL |
9 | URL: https://docs.kolab.org/about/sink | 9 | URL: https://docs.kolab.org/about/sink |
10 | Source0: sink-%{version}.tar.gz | 10 | Source0: sink-%{version}.tar.xz |
11 | 11 | ||
12 | BuildRequires: cmake >= 2.8.12 | 12 | BuildRequires: cmake >= 2.8.12 |
13 | BuildRequires: extra-cmake-modules | 13 | BuildRequires: extra-cmake-modules |
@@ -16,7 +16,7 @@ BuildRequires: gcc-c++ | |||
16 | BuildRequires: kasync-devel | 16 | BuildRequires: kasync-devel |
17 | BuildRequires: kf5-kcoreaddons-devel | 17 | BuildRequires: kf5-kcoreaddons-devel |
18 | BuildRequires: kf5-kcontacts-devel | 18 | BuildRequires: kf5-kcontacts-devel |
19 | BuildRequires: kmime-devel | 19 | BuildRequires: kf5-kmime-devel |
20 | BuildRequires: kimap2-devel | 20 | BuildRequires: kimap2-devel |
21 | BuildRequires: libcurl-devel | 21 | BuildRequires: libcurl-devel |
22 | BuildRequires: libgit2-devel | 22 | BuildRequires: 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 |
58 | Mail: | 58 | Mail: |
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 | ||
75 | Contact: | ||
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 | ||
84 | Addressbook: | ||
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 |
72 | Sink Resource: | 91 | Sink 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) | |||
3 | add_definitions(-DQT_PLUGIN) | 3 | add_definitions(-DQT_PLUGIN) |
4 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) | 4 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) |
5 | 5 | ||
6 | find_package(KF5 COMPONENTS REQUIRED Mime) | ||
7 | find_package(KPimKDAV REQUIRED) | 6 | find_package(KPimKDAV REQUIRED) |
8 | 7 | ||
9 | add_library(${PROJECT_NAME} SHARED facade.cpp davresource.cpp domainadaptor.cpp) | 8 | add_library(${PROJECT_NAME} SHARED davresource.cpp) |
10 | qt5_use_modules(${PROJECT_NAME} Core Network) | 9 | qt5_use_modules(${PROJECT_NAME} Core Network) |
11 | target_link_libraries(${PROJECT_NAME} sink KPim::KDAV) | 10 | target_link_libraries(${PROJECT_NAME} sink KPim::KDAV) |
12 | 11 | ||
13 | install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH}) | 12 | install(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 | ||
50 | SINK_DEBUG_AREA("davresource") | 45 | SINK_DEBUG_AREA("davresource") |
51 | 46 | ||
52 | using namespace Sink; | 47 | using 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 | |||
70 | class MaildirMailPropertyExtractor : public MailPropertyExtractor | ||
71 | { | ||
72 | protected: | ||
73 | virtual QString getFilePathFromMimeMessagePath(const QString &mimeMessagePath) const Q_DECL_OVERRIDE | ||
74 | { | ||
75 | return ::getFilePathFromMimeMessagePath(mimeMessagePath); | ||
76 | } | ||
77 | }; | ||
78 | |||
79 | class MaildirMimeMessageMover : public Sink::Preprocessor | ||
80 | { | ||
81 | public: | ||
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 | |||
195 | class FolderPreprocessor : public Sink::Preprocessor | ||
196 | { | ||
197 | public: | ||
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 | |||
218 | static KAsync::Job<void> runJob(KJob *job) | 49 | static 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 | ||
368 | KAsync::Job<QByteArray> replay(const ApplicationDomain::Contact &contact, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE | 219 | KAsync::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 | /* | ||
415 | class MaildirInspector : public Sink::Inspector { | ||
416 | public: | ||
417 | MaildirInspector(const Sink::ResourceContext &resourceContext) | ||
418 | : Sink::Inspector(resourceContext) | ||
419 | { | ||
420 | |||
421 | } | ||
422 | protected: | ||
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 | ||
512 | DavResource::DavResource(const Sink::ResourceContext &resourceContext) | 234 | DavResource::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 | ||
531 | DavResourceFactory::DavResourceFactory(QObject *parent) | 257 | DavResourceFactory::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 | ||
543 | void DavResourceFactory::registerFacades(const QByteArray &name, Sink::FacadeFactory &factory) | 271 | void 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 | ||
549 | void DavResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry ®istry) | 277 | void DavResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry ®istry) |
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 | ||
555 | void DavResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier) | 283 | void 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: | |||
54 | class DavResourceFactory : public Sink::ResourceFactory | 54 | class 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 | ||
60 | public: | 60 | public: |
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 | |||
22 | using namespace flatbuffers; | ||
23 | |||
24 | ContactAdaptorFactory::ContactAdaptorFactory() | ||
25 | : DomainTypeAdaptorFactory() | ||
26 | { | ||
27 | |||
28 | } | ||
29 | |||
30 | AddressbookAdaptorFactory::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 | |||
26 | class ContactAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Contact, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder> | ||
27 | { | ||
28 | public: | ||
29 | ContactAdaptorFactory(); | ||
30 | virtual ~ContactAdaptorFactory() {}; | ||
31 | }; | ||
32 | |||
33 | class AddressbookAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Folder, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder> | ||
34 | { | ||
35 | public: | ||
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 | |||
27 | DavResourceContactFacade::DavResourceContactFacade(const Sink::ResourceContext &context) | ||
28 | : Sink::GenericFacade<Sink::ApplicationDomain::Contact>(context) | ||
29 | { | ||
30 | } | ||
31 | |||
32 | DavResourceContactFacade::~DavResourceContactFacade() | ||
33 | { | ||
34 | } | ||
35 | |||
36 | |||
37 | DavResourceFolderFacade::DavResourceFolderFacade(const Sink::ResourceContext &context) | ||
38 | : Sink::GenericFacade<Sink::ApplicationDomain::Folder>(context) | ||
39 | { | ||
40 | } | ||
41 | |||
42 | DavResourceFolderFacade::~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 | |||
24 | class DavResourceContactFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Contact> | ||
25 | { | ||
26 | public: | ||
27 | DavResourceContactFacade(const Sink::ResourceContext &context); | ||
28 | virtual ~DavResourceContactFacade(); | ||
29 | }; | ||
30 | |||
31 | class DavResourceFolderFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Folder> | ||
32 | { | ||
33 | public: | ||
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) | |||
4 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) | 4 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) |
5 | 5 | ||
6 | 6 | ||
7 | add_library(${PROJECT_NAME} SHARED facade.cpp resourcefactory.cpp domainadaptor.cpp dummystore.cpp) | 7 | add_library(${PROJECT_NAME} SHARED resourcefactory.cpp domainadaptor.cpp dummystore.cpp) |
8 | generate_flatbuffers(${PROJECT_NAME} dummycalendar) | 8 | generate_flatbuffers(${PROJECT_NAME} dummycalendar) |
9 | qt5_use_modules(${PROJECT_NAME} Core Network) | 9 | qt5_use_modules(${PROJECT_NAME} Core Network) |
10 | target_link_libraries(${PROJECT_NAME} sink) | 10 | target_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 | |||
24 | DummyResourceFacade::DummyResourceFacade(const Sink::ResourceContext &context) | ||
25 | : Sink::GenericFacade<Sink::ApplicationDomain::Event>(context) | ||
26 | { | ||
27 | } | ||
28 | |||
29 | DummyResourceFacade::~DummyResourceFacade() | ||
30 | { | ||
31 | } | ||
32 | |||
33 | |||
34 | DummyResourceMailFacade::DummyResourceMailFacade(const Sink::ResourceContext &context) | ||
35 | : Sink::GenericFacade<Sink::ApplicationDomain::Mail>(context) | ||
36 | { | ||
37 | } | ||
38 | |||
39 | DummyResourceMailFacade::~DummyResourceMailFacade() | ||
40 | { | ||
41 | } | ||
42 | |||
43 | |||
44 | DummyResourceFolderFacade::DummyResourceFolderFacade(const Sink::ResourceContext &context) | ||
45 | : Sink::GenericFacade<Sink::ApplicationDomain::Folder>(context) | ||
46 | { | ||
47 | } | ||
48 | |||
49 | DummyResourceFolderFacade::~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 | |||
25 | class DummyResourceFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Event> | ||
26 | { | ||
27 | public: | ||
28 | DummyResourceFacade(const Sink::ResourceContext &context); | ||
29 | virtual ~DummyResourceFacade(); | ||
30 | }; | ||
31 | |||
32 | class DummyResourceMailFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Mail> | ||
33 | { | ||
34 | public: | ||
35 | DummyResourceMailFacade(const Sink::ResourceContext &context); | ||
36 | virtual ~DummyResourceMailFacade(); | ||
37 | }; | ||
38 | |||
39 | class DummyResourceFolderFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Folder> | ||
40 | { | ||
41 | public: | ||
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 | ||
47 | SINK_DEBUG_AREA("dummyresource") | 45 | SINK_DEBUG_AREA("dummyresource") |
48 | 46 | ||
47 | using namespace Sink; | ||
48 | |||
49 | class DummySynchronizer : public Sink::Synchronizer { | 49 | class 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 | ||
195 | void DummyResourceFactory::registerFacades(const QByteArray &resourceName, Sink::FacadeFactory &factory) | 196 | void 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 | ||
202 | void DummyResourceFactory::registerAdaptorFactories(const QByteArray &resourceName, Sink::AdaptorFactoryRegistry ®istry) | 203 | void DummyResourceFactory::registerAdaptorFactories(const QByteArray &resourceName, Sink::AdaptorFactoryRegistry ®istry) |
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 | ||
9 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) | 9 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) |
10 | 10 | ||
11 | add_library(${PROJECT_NAME} SHARED facade.cpp imapresource.cpp domainadaptor.cpp imapserverproxy.cpp) | 11 | add_library(${PROJECT_NAME} SHARED imapresource.cpp imapserverproxy.cpp) |
12 | qt5_use_modules(${PROJECT_NAME} Core Network) | 12 | qt5_use_modules(${PROJECT_NAME} Core Network) |
13 | target_link_libraries(${PROJECT_NAME} sink KF5::Mime KIMAP2) | 13 | target_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 | |||
22 | using namespace flatbuffers; | ||
23 | |||
24 | ImapMailAdaptorFactory::ImapMailAdaptorFactory() | ||
25 | : DomainTypeAdaptorFactory() | ||
26 | { | ||
27 | |||
28 | } | ||
29 | |||
30 | ImapFolderAdaptorFactory::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 | |||
26 | class ImapMailAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Mail, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder> | ||
27 | { | ||
28 | public: | ||
29 | ImapMailAdaptorFactory(); | ||
30 | virtual ~ImapMailAdaptorFactory() {}; | ||
31 | }; | ||
32 | |||
33 | class ImapFolderAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Folder, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder> | ||
34 | { | ||
35 | public: | ||
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 | |||
28 | ImapResourceMailFacade::ImapResourceMailFacade(const Sink::ResourceContext &context) | ||
29 | : Sink::GenericFacade<Sink::ApplicationDomain::Mail>(context) | ||
30 | { | ||
31 | } | ||
32 | |||
33 | ImapResourceMailFacade::~ImapResourceMailFacade() | ||
34 | { | ||
35 | } | ||
36 | |||
37 | ImapResourceFolderFacade::ImapResourceFolderFacade(const Sink::ResourceContext &context) | ||
38 | : Sink::GenericFacade<Sink::ApplicationDomain::Folder>(context) | ||
39 | { | ||
40 | } | ||
41 | |||
42 | ImapResourceFolderFacade::~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 | |||
24 | class ImapResourceMailFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Mail> | ||
25 | { | ||
26 | public: | ||
27 | ImapResourceMailFacade(const Sink::ResourceContext &context); | ||
28 | virtual ~ImapResourceMailFacade(); | ||
29 | }; | ||
30 | |||
31 | class ImapResourceFolderFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Folder> | ||
32 | { | ||
33 | public: | ||
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 | ||
899 | void ImapResourceFactory::registerFacades(const QByteArray &name, Sink::FacadeFactory &factory) | 956 | void 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 | ||
905 | void ImapResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry ®istry) | 962 | void ImapResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry ®istry) |
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 | ||
911 | void ImapResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier) | 968 | void 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 | ||
30 | namespace Imap { | 30 | namespace Imap { |
31 | 31 | ||
32 | enum ErrorCode { | ||
33 | NoError, | ||
34 | CouldNotConnectError, | ||
35 | SslHandshakeError | ||
36 | }; | ||
37 | |||
32 | namespace Flags | 38 | namespace 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 | ||
6 | find_package(KF5 COMPONENTS REQUIRED Mime) | 6 | find_package(KF5 COMPONENTS REQUIRED Mime) |
7 | 7 | ||
8 | add_library(${PROJECT_NAME} SHARED facade.cpp maildirresource.cpp domainadaptor.cpp) | 8 | add_library(${PROJECT_NAME} SHARED facade.cpp maildirresource.cpp libmaildir/maildir.cpp libmaildir/keycache.cpp) |
9 | qt5_use_modules(${PROJECT_NAME} Core Network) | 9 | qt5_use_modules(${PROJECT_NAME} Core Network) |
10 | target_link_libraries(${PROJECT_NAME} sink maildir KF5::Mime) | 10 | target_link_libraries(${PROJECT_NAME} sink KF5::Mime) |
11 | 11 | ||
12 | install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH}) | 12 | install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH}) |
13 | 13 | ||
14 | add_definitions(-DTESTDATAPATH="${CMAKE_CURRENT_SOURCE_DIR}/tests/data") | 14 | add_definitions(-DTESTDATAPATH="${CMAKE_CURRENT_SOURCE_DIR}/tests/data") |
15 | 15 | ||
16 | add_subdirectory(libmaildir) | ||
17 | add_subdirectory(tests) | 16 | add_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 | |||
22 | using namespace flatbuffers; | ||
23 | |||
24 | MaildirMailAdaptorFactory::MaildirMailAdaptorFactory() | ||
25 | : DomainTypeAdaptorFactory() | ||
26 | { | ||
27 | |||
28 | } | ||
29 | |||
30 | MaildirFolderAdaptorFactory::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 | |||
26 | class MaildirMailAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Mail, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder> | ||
27 | { | ||
28 | public: | ||
29 | MaildirMailAdaptorFactory(); | ||
30 | virtual ~MaildirMailAdaptorFactory() {}; | ||
31 | }; | ||
32 | |||
33 | class MaildirFolderAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Folder, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder> | ||
34 | { | ||
35 | public: | ||
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 | |||
3 | set(maildir_LIB_SRCS keycache.cpp maildir.cpp) | ||
4 | |||
5 | add_library(maildir ${LIBRARY_TYPE} ${maildir_LIB_SRCS}) | ||
6 | qt5_use_modules(maildir Core Network) | ||
7 | # set_target_properties(maildir PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) | ||
8 | install(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 | ||
30 | Q_LOGGING_CATEGORY(log, "maildir"); | 30 | Q_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 | ||
31 | namespace KPIM { | 29 | namespace KPIM { |
32 | 30 | ||
33 | class MAILDIR_EXPORT Maildir | 31 | class Maildir |
34 | { | 32 | { |
35 | public: | 33 | public: |
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 | ||
597 | void MaildirResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry ®istry) | 597 | void MaildirResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry ®istry) |
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 | ||
603 | void MaildirResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier) | 603 | void 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 | ||
72 | static 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 | ||
73 | bool 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) | 82 | bool 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, | |||
147 | bool MailTransport::sendMessage(const KMime::Message::Ptr &message, const QByteArray &server, const QByteArray &username, const QByteArray &password, const QByteArray &cacert, MailTransport::Options options) | 166 | bool 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 @@ | |||
26 | namespace MailTransport | 26 | namespace 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 | ||
45 | using namespace Sink; | 44 | using namespace Sink; |
46 | 45 | ||
46 | class MailtransportPreprocessor : public Sink::Preprocessor | ||
47 | { | ||
48 | public: | ||
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 ¤t, 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 | |||
47 | class MailtransportSynchronizer : public Sink::Synchronizer { | 87 | class MailtransportSynchronizer : public Sink::Synchronizer { |
48 | public: | 88 | public: |
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 | ||
198 | MailtransportResourceFactory::MailtransportResourceFactory(QObject *parent) | 245 | MailtransportResourceFactory::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) | |||
21 | manual_tests ( | 21 | manual_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 | ||
30 | auto_tests ( | 28 | auto_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 | ) |
52 | generate_flatbuffers(dummyresourcetest calendar) | 50 | generate_flatbuffers(dummyresourcetest calendar) |
53 | target_link_libraries(dummyresourcetest sink_resource_dummy) | 51 | target_link_libraries(dummyresourcetest sink_resource_dummy) |
@@ -56,3 +54,4 @@ target_link_libraries(dummyresourcewritebenchmark sink_resource_dummy) | |||
56 | target_link_libraries(querytest sink_resource_dummy) | 54 | target_link_libraries(querytest sink_resource_dummy) |
57 | target_link_libraries(modelinteractivitytest sink_resource_dummy) | 55 | target_link_libraries(modelinteractivitytest sink_resource_dummy) |
58 | target_link_libraries(inspectiontest sink_resource_dummy) | 56 | target_link_libraries(inspectiontest sink_resource_dummy) |
57 | target_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 | ||
14 | template <typename T> | 14 | template <typename T> |
15 | struct Result { | ||
16 | QSharedPointer<T> parent; | ||
17 | bool fetchedAll; | ||
18 | }; | ||
19 | |||
20 | template <typename T> | ||
15 | class TestDummyResourceFacade : public Sink::StoreFacade<T> | 21 | class TestDummyResourceFacade : public Sink::StoreFacade<T> |
16 | { | 22 | { |
17 | public: | 23 | public: |
@@ -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 | |||
11 | class EntityStoreTest : public QObject | ||
12 | { | ||
13 | Q_OBJECT | ||
14 | private: | ||
15 | QString resourceInstanceIdentifier{"resourceId"}; | ||
16 | |||
17 | private 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 | |||
88 | QTEST_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 | */ | ||
23 | class GenericFacadeTest : public QObject | ||
24 | { | ||
25 | Q_OBJECT | ||
26 | private 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 | |||
153 | QTEST_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 | |||
21 | static void removeFromDisk(const QString &name) | ||
22 | { | ||
23 | Sink::Storage store(Sink::storageLocation(), name, Sink::Storage::ReadWrite); | ||
24 | store.removeFromDisk(); | ||
25 | } | ||
26 | |||
27 | static 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 | |||
63 | class IndexUpdater : public Sink::Preprocessor | ||
64 | { | ||
65 | public: | ||
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 | */ | ||
87 | class GenericResourceBenchmark : public QObject | ||
88 | { | ||
89 | Q_OBJECT | ||
90 | private 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 | |||
204 | private: | ||
205 | HAWD::State m_hawdState; | ||
206 | }; | ||
207 | |||
208 | QTEST_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 | */ | ||
22 | class GenericResourceTest : public QObject | ||
23 | { | ||
24 | Q_OBJECT | ||
25 | private 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 | |||
84 | QTEST_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 @@ | |||
1 | project(hawd) | 1 | project(hawd) |
2 | 2 | ||
3 | find_package(Libgit2) | 3 | #We require git_buf_free function |
4 | find_package(Libgit2 0.20.0) | ||
4 | 5 | ||
5 | if (LIBGIT2_FOUND) | 6 | if (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 | |||
116 | void State::findGitHash() | 116 | void 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 | ||
32 | using namespace Sink; | 33 | using namespace Sink; |
33 | using namespace Sink::ApplicationDomain; | 34 | using 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 | |||
44 | private slots: | 54 | private 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 | ||
414 | void 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 | |||
437 | void 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 | |||
414 | void MailSyncTest::testFailingSync() | 451 | void 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 | |||
16 | using namespace Sink; | ||
17 | using namespace Sink::ApplicationDomain; | ||
18 | |||
19 | /** | ||
20 | * Test of complete system using the dummy resource. | ||
21 | * | ||
22 | * This test requires the dummy resource installed. | ||
23 | */ | ||
24 | class NotificationTest : public QObject | ||
25 | { | ||
26 | Q_OBJECT | ||
27 | |||
28 | private 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 | |||
131 | QTEST_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 | ||
1047 | QTEST_MAIN(QueryTest) | 1112 | QTEST_MAIN(QueryTest) |