summaryrefslogtreecommitdiffstats
path: root/common
diff options
context:
space:
mode:
Diffstat (limited to 'common')
-rw-r--r--common/CMakeLists.txt6
-rw-r--r--common/bufferadaptor.h4
-rw-r--r--common/changereplay.cpp13
-rw-r--r--common/changereplay.h3
-rw-r--r--common/commandprocessor.cpp22
-rw-r--r--common/commands.cpp24
-rw-r--r--common/commands/notification.fbs3
-rw-r--r--common/contactpreprocessor.cpp6
-rw-r--r--common/datastorequery.cpp19
-rw-r--r--common/datastorequery.h2
-rw-r--r--common/domain/addressbook.fbs9
-rw-r--r--common/domain/applicationdomaintype.cpp80
-rw-r--r--common/domain/applicationdomaintype.h144
-rw-r--r--common/domain/applicationdomaintype_p.h8
-rw-r--r--common/domain/contact.cpp57
-rw-r--r--common/domain/contact.fbs10
-rw-r--r--common/domain/contact.h57
-rw-r--r--common/domain/domaintypes.h5
-rw-r--r--common/domain/event.cpp57
-rw-r--r--common/domain/event.h57
-rw-r--r--common/domain/folder.cpp60
-rw-r--r--common/domain/folder.h51
-rw-r--r--common/domain/mail.h52
-rw-r--r--common/domain/propertyregistry.cpp18
-rw-r--r--common/domain/propertyregistry.h3
-rw-r--r--common/domain/typeimplementations.cpp (renamed from common/domain/mail.cpp)118
-rw-r--r--common/domain/typeimplementations.h101
-rw-r--r--common/domainadaptor.h17
-rw-r--r--common/genericresource.cpp4
-rw-r--r--common/index.cpp16
-rw-r--r--common/index.h2
-rw-r--r--common/listener.cpp11
-rw-r--r--common/mail/threadindexer.cpp11
-rw-r--r--common/modelresult.cpp119
-rw-r--r--common/modelresult.h12
-rw-r--r--common/notification.cpp31
-rw-r--r--common/notification.h7
-rw-r--r--common/notifier.cpp57
-rw-r--r--common/notifier.h5
-rw-r--r--common/pipeline.cpp169
-rw-r--r--common/pipeline.h24
-rw-r--r--common/propertymapper.cpp30
-rw-r--r--common/propertymapper.h18
-rw-r--r--common/query.h34
-rw-r--r--common/queryrunner.cpp38
-rw-r--r--common/queryrunner.h3
-rw-r--r--common/resourceaccess.cpp16
-rw-r--r--common/resourcecontext.h4
-rw-r--r--common/resourcecontrol.cpp4
-rw-r--r--common/resourcefacade.cpp68
-rw-r--r--common/resultprovider.h10
-rw-r--r--common/specialpurposepreprocessor.cpp22
-rw-r--r--common/specialpurposepreprocessor.h4
-rw-r--r--common/storage.h9
-rw-r--r--common/storage/entitystore.cpp127
-rw-r--r--common/storage/entitystore.h16
-rw-r--r--common/storage_common.cpp20
-rw-r--r--common/storage_lmdb.cpp86
-rw-r--r--common/store.cpp22
-rw-r--r--common/store.h8
-rw-r--r--common/synchronizer.cpp168
-rw-r--r--common/synchronizer.h26
-rw-r--r--common/synchronizerstore.cpp3
-rw-r--r--common/typeindex.cpp6
64 files changed, 1313 insertions, 903 deletions
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index b5275e0..001a412 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -61,10 +61,7 @@ set(command_SRCS
61 resultset.cpp 61 resultset.cpp
62 domain/propertyregistry.cpp 62 domain/propertyregistry.cpp
63 domain/applicationdomaintype.cpp 63 domain/applicationdomaintype.cpp
64 domain/contact.cpp 64 domain/typeimplementations.cpp
65 domain/event.cpp
66 domain/mail.cpp
67 domain/folder.cpp
68 test.cpp 65 test.cpp
69 query.cpp 66 query.cpp
70 changereplay.cpp 67 changereplay.cpp
@@ -101,6 +98,7 @@ generate_flatbuffers(
101 commands/inspection 98 commands/inspection
102 commands/flush 99 commands/flush
103 domain/contact 100 domain/contact
101 domain/addressbook
104 domain/event 102 domain/event
105 domain/mail 103 domain/mail
106 domain/folder 104 domain/folder
diff --git a/common/bufferadaptor.h b/common/bufferadaptor.h
index 0ae7bf5..fd4809b 100644
--- a/common/bufferadaptor.h
+++ b/common/bufferadaptor.h
@@ -39,12 +39,12 @@ public:
39 } 39 }
40 virtual QVariant getProperty(const QByteArray &key) const 40 virtual QVariant getProperty(const QByteArray &key) const
41 { 41 {
42 qFatal("Tried to get property: " + key); 42 qFatal("Tried to get property: %s", key.data());
43 return QVariant(); 43 return QVariant();
44 } 44 }
45 virtual void setProperty(const QByteArray &key, const QVariant &value) 45 virtual void setProperty(const QByteArray &key, const QVariant &value)
46 { 46 {
47 qFatal("Tried to get property: " + key); 47 qFatal("Tried to get property: %s", key.data());
48 } 48 }
49 virtual QList<QByteArray> availableProperties() const 49 virtual QList<QByteArray> availableProperties() const
50 { 50 {
diff --git a/common/changereplay.cpp b/common/changereplay.cpp
index 532cca8..7895b66 100644
--- a/common/changereplay.cpp
+++ b/common/changereplay.cpp
@@ -29,10 +29,10 @@
29using namespace Sink; 29using namespace Sink;
30using namespace Sink::Storage; 30using namespace Sink::Storage;
31 31
32ChangeReplay::ChangeReplay(const ResourceContext &resourceContext) 32ChangeReplay::ChangeReplay(const ResourceContext &resourceContext, const Sink::Log::Context &ctx)
33 : mStorage(storageLocation(), resourceContext.instanceId(), DataStore::ReadOnly), mChangeReplayStore(storageLocation(), resourceContext.instanceId() + ".changereplay", DataStore::ReadWrite), mReplayInProgress(false), mLogCtx{"changereplay"} 33 : mStorage(storageLocation(), resourceContext.instanceId(), DataStore::ReadOnly), mChangeReplayStore(storageLocation(), resourceContext.instanceId() + ".changereplay", DataStore::ReadWrite), mReplayInProgress(false), mLogCtx{ctx.subContext("changereplay")},
34 mGuard{new QObject}
34{ 35{
35 SinkTraceCtx(mLogCtx) << "Created change replay: " << resourceContext.instanceId();
36} 36}
37 37
38qint64 ChangeReplay::getLastReplayedRevision() 38qint64 ChangeReplay::getLastReplayedRevision()
@@ -54,7 +54,6 @@ bool ChangeReplay::allChangesReplayed()
54 SinkWarningCtx(mLogCtx) << error.message; 54 SinkWarningCtx(mLogCtx) << error.message;
55 })); 55 }));
56 const qint64 lastReplayedRevision = getLastReplayedRevision(); 56 const qint64 lastReplayedRevision = getLastReplayedRevision();
57 SinkTraceCtx(mLogCtx) << "Checking if all replayed. Top revision:" << topRevision << "Last replayed:" << lastReplayedRevision;
58 return (lastReplayedRevision >= topRevision); 57 return (lastReplayedRevision >= topRevision);
59} 58}
60 59
@@ -99,8 +98,12 @@ KAsync::Job<void> ChangeReplay::replayNextRevision()
99 SinkTraceCtx(mLogCtx) << "Changereplay from " << *lastReplayedRevision << " to " << *topRevision; 98 SinkTraceCtx(mLogCtx) << "Changereplay from " << *lastReplayedRevision << " to " << *topRevision;
100 return KAsync::doWhile( 99 return KAsync::doWhile(
101 [this, lastReplayedRevision, topRevision]() -> KAsync::Job<KAsync::ControlFlowFlag> { 100 [this, lastReplayedRevision, topRevision]() -> KAsync::Job<KAsync::ControlFlowFlag> {
101 if (!mGuard) {
102 SinkTraceCtx(mLogCtx) << "Exit due to guard";
103 return KAsync::value(KAsync::Break);
104 }
102 if (*lastReplayedRevision >= *topRevision) { 105 if (*lastReplayedRevision >= *topRevision) {
103 SinkTraceCtx(mLogCtx) << "Done replaying"; 106 SinkTraceCtx(mLogCtx) << "Done replaying" << *lastReplayedRevision << *topRevision;
104 return KAsync::value(KAsync::Break); 107 return KAsync::value(KAsync::Break);
105 } 108 }
106 109
diff --git a/common/changereplay.h b/common/changereplay.h
index 3ca896e..edc4462 100644
--- a/common/changereplay.h
+++ b/common/changereplay.h
@@ -39,7 +39,7 @@ class SINK_EXPORT ChangeReplay : public QObject
39{ 39{
40 Q_OBJECT 40 Q_OBJECT
41public: 41public:
42 ChangeReplay(const ResourceContext &resourceContext); 42 ChangeReplay(const ResourceContext &resourceContext, const Sink::Log::Context &ctx= {});
43 43
44 qint64 getLastReplayedRevision(); 44 qint64 getLastReplayedRevision();
45 virtual bool allChangesReplayed(); 45 virtual bool allChangesReplayed();
@@ -63,6 +63,7 @@ private:
63 bool mReplayInProgress; 63 bool mReplayInProgress;
64 Sink::Storage::DataStore::Transaction mMainStoreTransaction; 64 Sink::Storage::DataStore::Transaction mMainStoreTransaction;
65 Sink::Log::Context mLogCtx; 65 Sink::Log::Context mLogCtx;
66 QSharedPointer<QObject> mGuard;
66}; 67};
67 68
68class NullChangeReplay : public ChangeReplay 69class NullChangeReplay : public ChangeReplay
diff --git a/common/commandprocessor.cpp b/common/commandprocessor.cpp
index 33e2f81..3507ef1 100644
--- a/common/commandprocessor.cpp
+++ b/common/commandprocessor.cpp
@@ -245,9 +245,9 @@ KAsync::Job<void> CommandProcessor::processQueue(MessageQueue *queue)
245 } 245 }
246 } 246 }
247 if (queue->isEmpty()) { 247 if (queue->isEmpty()) {
248 return KAsync::value<KAsync::ControlFlowFlag>(KAsync::Break); 248 return KAsync::Break;
249 } else { 249 } else {
250 return KAsync::value<KAsync::ControlFlowFlag>(KAsync::Continue); 250 return KAsync::Continue;
251 } 251 }
252 }); 252 });
253 })) 253 }))
@@ -295,24 +295,6 @@ void CommandProcessor::setSynchronizer(const QSharedPointer<Synchronizer> &synch
295 mSynchronizer->setup([this](int commandId, const QByteArray &data) { 295 mSynchronizer->setup([this](int commandId, const QByteArray &data) {
296 enqueueCommand(mSynchronizerQueue, commandId, data); 296 enqueueCommand(mSynchronizerQueue, commandId, data);
297 }, mSynchronizerQueue); 297 }, mSynchronizerQueue);
298
299 QObject::connect(mSynchronizer.data(), &Synchronizer::replayingChanges, [this]() {
300 Sink::Notification n;
301 n.id = "changereplay";
302 n.type = Notification::Status;
303 n.message = "Replaying changes.";
304 n.code = ApplicationDomain::BusyStatus;
305 emit notify(n);
306 });
307 QObject::connect(mSynchronizer.data(), &Synchronizer::changesReplayed, [this]() {
308 Sink::Notification n;
309 n.id = "changereplay";
310 n.type = Notification::Status;
311 n.message = "All changes have been replayed.";
312 n.code = ApplicationDomain::ConnectedStatus;
313 emit notify(n);
314 });
315
316 QObject::connect(mSynchronizer.data(), &Synchronizer::notify, this, &CommandProcessor::notify); 298 QObject::connect(mSynchronizer.data(), &Synchronizer::notify, this, &CommandProcessor::notify);
317 setOldestUsedRevision(mSynchronizer->getLastReplayedRevision()); 299 setOldestUsedRevision(mSynchronizer->getLastReplayedRevision());
318} 300}
diff --git a/common/commands.cpp b/common/commands.cpp
index ce83d03..eeb7f08 100644
--- a/common/commands.cpp
+++ b/common/commands.cpp
@@ -21,6 +21,7 @@
21#include "commands.h" 21#include "commands.h"
22 22
23#include <QIODevice> 23#include <QIODevice>
24#include <log.h>
24 25
25namespace Sink { 26namespace Sink {
26 27
@@ -77,27 +78,34 @@ void write(QIODevice *device, int messageId, int commandId)
77 write(device, messageId, commandId, 0, 0); 78 write(device, messageId, commandId, 0, 0);
78} 79}
79 80
81static void write(QIODevice *device, const char *buffer, uint size)
82{
83 if (device->write(buffer, size) < 0) {
84 SinkWarningCtx(Sink::Log::Context{"commands"}) << "Error while writing " << device->errorString();
85 }
86}
87
80void write(QIODevice *device, int messageId, int commandId, const char *buffer, uint size) 88void write(QIODevice *device, int messageId, int commandId, const char *buffer, uint size)
81{ 89{
82 if (size > 0 && !buffer) { 90 if (size > 0 && !buffer) {
83 size = 0; 91 size = 0;
84 } 92 }
85 93
86 device->write((const char *)&messageId, sizeof(int)); 94 write(device, (const char *)&messageId, sizeof(int));
87 device->write((const char *)&commandId, sizeof(int)); 95 write(device, (const char *)&commandId, sizeof(int));
88 device->write((const char *)&size, sizeof(uint)); 96 write(device, (const char *)&size, sizeof(uint));
89 if (buffer) { 97 if (buffer) {
90 device->write(buffer, size); 98 write(device, buffer, size);
91 } 99 }
92} 100}
93 101
94void write(QIODevice *device, int messageId, int commandId, flatbuffers::FlatBufferBuilder &fbb) 102void write(QIODevice *device, int messageId, int commandId, flatbuffers::FlatBufferBuilder &fbb)
95{ 103{
96 const int dataSize = fbb.GetSize(); 104 const int dataSize = fbb.GetSize();
97 device->write((const char *)&messageId, sizeof(int)); 105 write(device, (const char *)&messageId, sizeof(int));
98 device->write((const char *)&commandId, sizeof(int)); 106 write(device, (const char *)&commandId, sizeof(int));
99 device->write((const char *)&dataSize, sizeof(int)); 107 write(device, (const char *)&dataSize, sizeof(int));
100 device->write((const char *)fbb.GetBufferPointer(), dataSize); 108 write(device, (const char *)fbb.GetBufferPointer(), dataSize);
101} 109}
102 110
103} // namespace Commands 111} // namespace Commands
diff --git a/common/commands/notification.fbs b/common/commands/notification.fbs
index c82fad3..517111c 100644
--- a/common/commands/notification.fbs
+++ b/common/commands/notification.fbs
@@ -2,9 +2,10 @@ namespace Sink.Commands;
2 2
3table Notification { 3table Notification {
4 type: int = 0; //See notification.h 4 type: int = 0; //See notification.h
5 identifier: string; //An identifier that links back to the something related to the notification (e.g. an entity id or a command id) 5 identifier: string; //An identifier that links back to the something related to the notification (e.g. a command id)
6 message: string; 6 message: string;
7 code: int = 0; //See notification.h 7 code: int = 0; //See notification.h
8 entities: [string]; //A list of entities this applies to
8} 9}
9 10
10root_type Notification; 11root_type Notification;
diff --git a/common/contactpreprocessor.cpp b/common/contactpreprocessor.cpp
index 0f2ca17..ac2c3bc 100644
--- a/common/contactpreprocessor.cpp
+++ b/common/contactpreprocessor.cpp
@@ -28,9 +28,11 @@ void updatedProperties(Sink::ApplicationDomain::Contact &contact, const KContact
28{ 28{
29 contact.setUid(addressee.uid()); 29 contact.setUid(addressee.uid());
30 contact.setFn(addressee.formattedName()); 30 contact.setFn(addressee.formattedName());
31 QByteArrayList emails; 31 contact.setFirstname(addressee.givenName());
32 contact.setLastname(addressee.familyName());
33 QList<Sink::ApplicationDomain::Contact::Email> emails;
32 for (const auto &email : addressee.emails()) { 34 for (const auto &email : addressee.emails()) {
33 emails << email.toUtf8(); 35 emails << Sink::ApplicationDomain::Contact::Email{Sink::ApplicationDomain::Contact::Email::Undefined, email};
34 } 36 }
35 contact.setEmails(emails); 37 contact.setEmails(emails);
36} 38}
diff --git a/common/datastorequery.cpp b/common/datastorequery.cpp
index 34d2bae..2e0c348 100644
--- a/common/datastorequery.cpp
+++ b/common/datastorequery.cpp
@@ -144,7 +144,7 @@ public:
144 const auto property = entity.getProperty(filterProperty); 144 const auto property = entity.getProperty(filterProperty);
145 const auto comparator = propertyFilter.value(filterProperty); 145 const auto comparator = propertyFilter.value(filterProperty);
146 if (!comparator.matches(property)) { 146 if (!comparator.matches(property)) {
147 SinkTraceCtx(mDatastore->mLogCtx) << "Filtering entity due to property mismatch on filter: " << filterProperty << property << ":" << comparator.value; 147 SinkTraceCtx(mDatastore->mLogCtx) << "Filtering entity due to property mismatch on filter: " << entity.identifier() << "Property: " << filterProperty << property << " Filter:" << comparator.value;
148 return false; 148 return false;
149 } 149 }
150 } 150 }
@@ -152,7 +152,7 @@ public:
152 } 152 }
153}; 153};
154 154
155class Reduce : public FilterBase { 155class Reduce : public Filter {
156public: 156public:
157 typedef QSharedPointer<Reduce> Ptr; 157 typedef QSharedPointer<Reduce> Ptr;
158 158
@@ -198,7 +198,7 @@ public:
198 QList<Aggregator> mAggregators; 198 QList<Aggregator> mAggregators;
199 199
200 Reduce(const QByteArray &reductionProperty, const QByteArray &selectionProperty, QueryBase::Reduce::Selector::Comparator comparator, FilterBase::Ptr source, DataStoreQuery *store) 200 Reduce(const QByteArray &reductionProperty, const QByteArray &selectionProperty, QueryBase::Reduce::Selector::Comparator comparator, FilterBase::Ptr source, DataStoreQuery *store)
201 : FilterBase(source, store), 201 : Filter(source, store),
202 mReductionProperty(reductionProperty), 202 mReductionProperty(reductionProperty),
203 mSelectionProperty(selectionProperty), 203 mSelectionProperty(selectionProperty),
204 mSelectionComparator(comparator) 204 mSelectionComparator(comparator)
@@ -236,6 +236,11 @@ public:
236 236
237 for (const auto &r : results) { 237 for (const auto &r : results) {
238 readEntity(r, [&, this](const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Operation operation) { 238 readEntity(r, [&, this](const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Operation operation) {
239 //We need to apply all property filters that we have until the reduction, because the index lookup was unfiltered.
240 if (!matchesFilter(entity)) {
241 return;
242 }
243
239 Q_ASSERT(operation != Sink::Operation_Removal); 244 Q_ASSERT(operation != Sink::Operation_Removal);
240 for (auto &aggregator : mAggregators) { 245 for (auto &aggregator : mAggregators) {
241 if (!aggregator.property.isEmpty()) { 246 if (!aggregator.property.isEmpty()) {
@@ -362,19 +367,22 @@ public:
362DataStoreQuery::DataStoreQuery(const Sink::QueryBase &query, const QByteArray &type, EntityStore &store) 367DataStoreQuery::DataStoreQuery(const Sink::QueryBase &query, const QByteArray &type, EntityStore &store)
363 : mType(type), mStore(store), mLogCtx(store.logContext().subContext("datastorequery")) 368 : mType(type), mStore(store), mLogCtx(store.logContext().subContext("datastorequery"))
364{ 369{
370 //This is what we use during a new query
365 setupQuery(query); 371 setupQuery(query);
366} 372}
367 373
368DataStoreQuery::DataStoreQuery(const DataStoreQuery::State &state, const QByteArray &type, Sink::Storage::EntityStore &store) 374DataStoreQuery::DataStoreQuery(const DataStoreQuery::State &state, const QByteArray &type, Sink::Storage::EntityStore &store, bool incremental)
369 : mType(type), mStore(store), mLogCtx(store.logContext().subContext("datastorequery")) 375 : mType(type), mStore(store), mLogCtx(store.logContext().subContext("datastorequery"))
370{ 376{
377 //This is what we use when fetching more data, without having a new revision with incremental=false
378 //And this is what we use when the data changed and we want to update with incremental = true
371 mCollector = state.mCollector; 379 mCollector = state.mCollector;
372 mSource = state.mSource; 380 mSource = state.mSource;
373 381
374 auto source = mCollector; 382 auto source = mCollector;
375 while (source) { 383 while (source) {
376 source->mDatastore = this; 384 source->mDatastore = this;
377 source->mIncremental = true; 385 source->mIncremental = incremental;
378 source = source->mSource; 386 source = source->mSource;
379 } 387 }
380} 388}
@@ -553,6 +561,7 @@ void DataStoreQuery::setupQuery(const Sink::QueryBase &query_)
553 for (const auto &aggregator : filter->aggregators) { 561 for (const auto &aggregator : filter->aggregators) {
554 reduction->mAggregators << Reduce::Aggregator(aggregator.operation, aggregator.propertyToCollect, aggregator.resultProperty); 562 reduction->mAggregators << Reduce::Aggregator(aggregator.operation, aggregator.propertyToCollect, aggregator.resultProperty);
555 } 563 }
564 reduction->propertyFilter = query.getBaseFilters();
556 baseSet = reduction; 565 baseSet = reduction;
557 } else if (auto filter = stage.dynamicCast<Query::Bloom>()) { 566 } else if (auto filter = stage.dynamicCast<Query::Bloom>()) {
558 baseSet = Bloom::Ptr::create(filter->property, baseSet, this); 567 baseSet = Bloom::Ptr::create(filter->property, baseSet, this);
diff --git a/common/datastorequery.h b/common/datastorequery.h
index a797782..ee5f99e 100644
--- a/common/datastorequery.h
+++ b/common/datastorequery.h
@@ -46,7 +46,7 @@ public:
46 }; 46 };
47 47
48 DataStoreQuery(const Sink::QueryBase &query, const QByteArray &type, Sink::Storage::EntityStore &store); 48 DataStoreQuery(const Sink::QueryBase &query, const QByteArray &type, Sink::Storage::EntityStore &store);
49 DataStoreQuery(const DataStoreQuery::State &state, const QByteArray &type, Sink::Storage::EntityStore &store); 49 DataStoreQuery(const DataStoreQuery::State &state, const QByteArray &type, Sink::Storage::EntityStore &store, bool incremental);
50 ~DataStoreQuery(); 50 ~DataStoreQuery();
51 ResultSet execute(); 51 ResultSet execute();
52 ResultSet update(qint64 baseRevision); 52 ResultSet update(qint64 baseRevision);
diff --git a/common/domain/addressbook.fbs b/common/domain/addressbook.fbs
new file mode 100644
index 0000000..c2bda2b
--- /dev/null
+++ b/common/domain/addressbook.fbs
@@ -0,0 +1,9 @@
1namespace Sink.ApplicationDomain.Buffer;
2
3table Addressbook {
4 name:string;
5 parent:string;
6}
7
8root_type Addressbook;
9file_identifier "AKFB";
diff --git a/common/domain/applicationdomaintype.cpp b/common/domain/applicationdomaintype.cpp
index fd88570..44f5a75 100644
--- a/common/domain/applicationdomaintype.cpp
+++ b/common/domain/applicationdomaintype.cpp
@@ -65,6 +65,9 @@ int registerProperty() {
65 return 0; 65 return 0;
66} 66}
67 67
68#define SINK_REGISTER_ENTITY(ENTITY) \
69 constexpr const char *ENTITY::name;
70
68#define SINK_REGISTER_PROPERTY(ENTITYTYPE, PROPERTY) \ 71#define SINK_REGISTER_PROPERTY(ENTITYTYPE, PROPERTY) \
69 constexpr const char *ENTITYTYPE::PROPERTY::name; \ 72 constexpr const char *ENTITYTYPE::PROPERTY::name; \
70 static int foo##ENTITYTYPE##PROPERTY = registerProperty<ENTITYTYPE, ENTITYTYPE::PROPERTY>(); 73 static int foo##ENTITYTYPE##PROPERTY = registerProperty<ENTITYTYPE, ENTITYTYPE::PROPERTY>();
@@ -72,6 +75,10 @@ int registerProperty() {
72namespace Sink { 75namespace Sink {
73namespace ApplicationDomain { 76namespace ApplicationDomain {
74 77
78constexpr const char *SinkResource::name;
79constexpr const char *SinkAccount::name;
80
81SINK_REGISTER_ENTITY(Mail);
75SINK_REGISTER_PROPERTY(Mail, Sender); 82SINK_REGISTER_PROPERTY(Mail, Sender);
76SINK_REGISTER_PROPERTY(Mail, To); 83SINK_REGISTER_PROPERTY(Mail, To);
77SINK_REGISTER_PROPERTY(Mail, Cc); 84SINK_REGISTER_PROPERTY(Mail, Cc);
@@ -90,11 +97,28 @@ SINK_REGISTER_PROPERTY(Mail, MessageId);
90SINK_REGISTER_PROPERTY(Mail, ParentMessageId); 97SINK_REGISTER_PROPERTY(Mail, ParentMessageId);
91SINK_REGISTER_PROPERTY(Mail, ThreadId); 98SINK_REGISTER_PROPERTY(Mail, ThreadId);
92 99
100SINK_REGISTER_ENTITY(Folder);
93SINK_REGISTER_PROPERTY(Folder, Name); 101SINK_REGISTER_PROPERTY(Folder, Name);
94SINK_REGISTER_PROPERTY(Folder, Icon); 102SINK_REGISTER_PROPERTY(Folder, Icon);
95SINK_REGISTER_PROPERTY(Folder, SpecialPurpose); 103SINK_REGISTER_PROPERTY(Folder, SpecialPurpose);
96SINK_REGISTER_PROPERTY(Folder, Enabled); 104SINK_REGISTER_PROPERTY(Folder, Enabled);
97SINK_REGISTER_PROPERTY(Folder, Parent); 105SINK_REGISTER_PROPERTY(Folder, Parent);
106SINK_REGISTER_PROPERTY(Folder, Count);
107SINK_REGISTER_PROPERTY(Folder, FullContentAvailable);
108
109SINK_REGISTER_ENTITY(Contact);
110SINK_REGISTER_PROPERTY(Contact, Uid);
111SINK_REGISTER_PROPERTY(Contact, Fn);
112SINK_REGISTER_PROPERTY(Contact, Firstname);
113SINK_REGISTER_PROPERTY(Contact, Lastname);
114SINK_REGISTER_PROPERTY(Contact, Emails);
115SINK_REGISTER_PROPERTY(Contact, Vcard);
116SINK_REGISTER_PROPERTY(Contact, Addressbook);
117
118SINK_REGISTER_ENTITY(Addressbook);
119SINK_REGISTER_PROPERTY(Addressbook, Name);
120SINK_REGISTER_PROPERTY(Addressbook, Parent);
121SINK_REGISTER_PROPERTY(Addressbook, LastUpdated);
98 122
99static const int foo = [] { 123static const int foo = [] {
100 QMetaType::registerEqualsComparator<Reference>(); 124 QMetaType::registerEqualsComparator<Reference>();
@@ -115,10 +139,8 @@ void copyBuffer(Sink::ApplicationDomain::BufferAdaptor &buffer, Sink::Applicatio
115 for (const auto &property : propertiesToCopy) { 139 for (const auto &property : propertiesToCopy) {
116 const auto value = buffer.getProperty(property); 140 const auto value = buffer.getProperty(property);
117 if (copyBlobs && value.canConvert<BLOB>()) { 141 if (copyBlobs && value.canConvert<BLOB>()) {
118 auto oldPath = value.value<BLOB>().value; 142 const auto oldPath = value.value<BLOB>().value;
119 //FIXME: This is neither pretty nor save if we have multiple modifications of the same property (the first modification will remove the file). 143 const auto newPath = Sink::temporaryFileLocation() + "/" + QUuid::createUuid().toString();
120 //At least if the modification fails the file will be removed once the entity is removed.
121 auto newPath = oldPath + "copy";
122 QFile::copy(oldPath, newPath); 144 QFile::copy(oldPath, newPath);
123 memoryAdaptor.setProperty(property, QVariant::fromValue(BLOB{newPath})); 145 memoryAdaptor.setProperty(property, QVariant::fromValue(BLOB{newPath}));
124 } else if (pruneReferences && value.canConvert<Reference>()) { 146 } else if (pruneReferences && value.canConvert<Reference>()) {
@@ -364,52 +386,12 @@ SinkResource ImapResource::create(const QByteArray &account)
364 return resource; 386 return resource;
365} 387}
366 388
367template<> 389SinkResource CardDavResource::create(const QByteArray &account)
368QByteArray getTypeName<Contact>()
369{
370 return "contact";
371}
372
373template<>
374QByteArray getTypeName<Event>()
375{
376 return "event";
377}
378
379template<>
380QByteArray getTypeName<Todo>()
381{
382 return "todo";
383}
384
385template<>
386QByteArray getTypeName<SinkResource>()
387{
388 return "resource";
389}
390
391template<>
392QByteArray getTypeName<SinkAccount>()
393{
394 return "account";
395}
396
397template<>
398QByteArray getTypeName<Identity>()
399{
400 return "identity";
401}
402
403template<>
404QByteArray getTypeName<Mail>()
405{ 390{
406 return "mail"; 391 auto &&resource = ApplicationDomainType::createEntity<SinkResource>();
407} 392 resource.setResourceType("sink.dav");
408 393 resource.setAccount(account);
409template<> 394 return resource;
410QByteArray getTypeName<Folder>()
411{
412 return "folder";
413} 395}
414 396
415QByteArrayList getTypeNames() 397QByteArrayList getTypeNames()
diff --git a/common/domain/applicationdomaintype.h b/common/domain/applicationdomaintype.h
index be04db9..e5aa46e 100644
--- a/common/domain/applicationdomaintype.h
+++ b/common/domain/applicationdomaintype.h
@@ -28,7 +28,8 @@
28#include <QUuid> 28#include <QUuid>
29#include "bufferadaptor.h" 29#include "bufferadaptor.h"
30 30
31#define SINK_ENTITY(TYPE) \ 31#define SINK_ENTITY(TYPE, LOWERCASENAME) \
32 static constexpr const char *name = #LOWERCASENAME; \
32 typedef QSharedPointer<TYPE> Ptr; \ 33 typedef QSharedPointer<TYPE> Ptr; \
33 using Entity::Entity; \ 34 using Entity::Entity; \
34 TYPE() = default; \ 35 TYPE() = default; \
@@ -92,6 +93,43 @@
92namespace Sink { 93namespace Sink {
93namespace ApplicationDomain { 94namespace ApplicationDomain {
94 95
96enum SINK_EXPORT ErrorCode {
97 NoError = 0,
98 UnknownError,
99 NoServerError,
100 ConnectionError,
101 LoginError,
102 ConfigurationError,
103 TransmissionError,
104};
105
106enum SINK_EXPORT SuccessCode {
107 TransmissionSuccess
108};
109
110enum SINK_EXPORT SyncStatus {
111 NoSyncStatus,
112 SyncInProgress,
113 SyncError,
114 SyncSuccess
115};
116
117/**
118 * The status of an account or resource.
119 *
120 * It is set as follows:
121 * * By default the status is offline.
122 * * If a connection to the server could be established the status is Connected.
123 * * If an error occurred that keeps the resource from operating (so non transient), the resource enters the error state.
124 * * If a long running operation is started the resource goes to the busy state (and return to the previous state after that).
125 */
126enum SINK_EXPORT Status {
127 OfflineStatus,
128 ConnectedStatus,
129 BusyStatus,
130 ErrorStatus
131};
132
95struct SINK_EXPORT Error { 133struct SINK_EXPORT Error {
96 134
97}; 135};
@@ -100,6 +138,11 @@ struct SINK_EXPORT Progress {
100 138
101}; 139};
102 140
141/**
142 * Internal type.
143 *
144 * Represents a BLOB property.
145 */
103struct BLOB { 146struct BLOB {
104 BLOB() = default; 147 BLOB() = default;
105 BLOB(const BLOB &) = default; 148 BLOB(const BLOB &) = default;
@@ -268,6 +311,7 @@ SINK_EXPORT QDebug operator<< (QDebug d, const BLOB &blob);
268 311
269 312
270struct SINK_EXPORT SinkAccount : public ApplicationDomainType { 313struct SINK_EXPORT SinkAccount : public ApplicationDomainType {
314 static constexpr const char *name = "account";
271 typedef QSharedPointer<SinkAccount> Ptr; 315 typedef QSharedPointer<SinkAccount> Ptr;
272 explicit SinkAccount(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer<BufferAdaptor> &adaptor); 316 explicit SinkAccount(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer<BufferAdaptor> &adaptor);
273 explicit SinkAccount(const QByteArray &identifier); 317 explicit SinkAccount(const QByteArray &identifier);
@@ -278,8 +322,6 @@ struct SINK_EXPORT SinkAccount : public ApplicationDomainType {
278 SINK_PROPERTY(QString, Icon, icon); 322 SINK_PROPERTY(QString, Icon, icon);
279 SINK_PROPERTY(QString, AccountType, type); 323 SINK_PROPERTY(QString, AccountType, type);
280 SINK_STATUS_PROPERTY(int, Status, status); 324 SINK_STATUS_PROPERTY(int, Status, status);
281 SINK_STATUS_PROPERTY(ApplicationDomain::Error, Error, error);
282 SINK_STATUS_PROPERTY(ApplicationDomain::Progress, Progress, progress);
283}; 325};
284 326
285 327
@@ -290,6 +332,7 @@ struct SINK_EXPORT SinkAccount : public ApplicationDomainType {
290 * and for creating and removing resource instances. 332 * and for creating and removing resource instances.
291 */ 333 */
292struct SINK_EXPORT SinkResource : public ApplicationDomainType { 334struct SINK_EXPORT SinkResource : public ApplicationDomainType {
335 static constexpr const char *name = "resource";
293 typedef QSharedPointer<SinkResource> Ptr; 336 typedef QSharedPointer<SinkResource> Ptr;
294 explicit SinkResource(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer<BufferAdaptor> &adaptor); 337 explicit SinkResource(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer<BufferAdaptor> &adaptor);
295 explicit SinkResource(const QByteArray &identifier); 338 explicit SinkResource(const QByteArray &identifier);
@@ -300,8 +343,6 @@ struct SINK_EXPORT SinkResource : public ApplicationDomainType {
300 SINK_PROPERTY(QByteArray, ResourceType, type); 343 SINK_PROPERTY(QByteArray, ResourceType, type);
301 SINK_PROPERTY(QByteArrayList, Capabilities, capabilities); 344 SINK_PROPERTY(QByteArrayList, Capabilities, capabilities);
302 SINK_STATUS_PROPERTY(int, Status, status); 345 SINK_STATUS_PROPERTY(int, Status, status);
303 SINK_STATUS_PROPERTY(ApplicationDomain::Error, Error, error);
304 SINK_STATUS_PROPERTY(ApplicationDomain::Progress, Progress, progress);
305}; 346};
306 347
307struct SINK_EXPORT Entity : public ApplicationDomainType { 348struct SINK_EXPORT Entity : public ApplicationDomainType {
@@ -312,16 +353,35 @@ struct SINK_EXPORT Entity : public ApplicationDomainType {
312 virtual ~Entity() = default; 353 virtual ~Entity() = default;
313}; 354};
314 355
356struct SINK_EXPORT Addressbook : public Entity {
357 SINK_ENTITY(Addressbook, addressbook);
358 SINK_REFERENCE_PROPERTY(Addressbook, Parent, parent);
359 SINK_PROPERTY(QString, Name, name);
360 SINK_EXTRACTED_PROPERTY(QDateTime, LastUpdated, lastUpdated);
361};
362
315struct SINK_EXPORT Contact : public Entity { 363struct SINK_EXPORT Contact : public Entity {
316 SINK_ENTITY(Contact); 364 struct SINK_EXPORT Email {
365 enum Type {
366 Undefined,
367 Work,
368 Home
369 };
370 Type type;
371 QString email;
372 };
373 SINK_ENTITY(Contact, contact);
317 SINK_PROPERTY(QString, Uid, uid); 374 SINK_PROPERTY(QString, Uid, uid);
318 SINK_PROPERTY(QString, Fn, fn); 375 SINK_PROPERTY(QString, Fn, fn);
319 SINK_PROPERTY(QByteArrayList, Emails, emails); 376 SINK_PROPERTY(QString, Firstname, firstname);
377 SINK_PROPERTY(QString, Lastname, lastname);
378 SINK_PROPERTY(QList<Email>, Emails, emails);
320 SINK_PROPERTY(QByteArray, Vcard, vcard); 379 SINK_PROPERTY(QByteArray, Vcard, vcard);
380 SINK_REFERENCE_PROPERTY(Addressbook, Addressbook, addressbook);
321}; 381};
322 382
323struct SINK_EXPORT Event : public Entity { 383struct SINK_EXPORT Event : public Entity {
324 SINK_ENTITY(Event); 384 SINK_ENTITY(Event, event);
325 SINK_PROPERTY(QString, Uid, uid); 385 SINK_PROPERTY(QString, Uid, uid);
326 SINK_PROPERTY(QString, Summary, summary); 386 SINK_PROPERTY(QString, Summary, summary);
327 SINK_PROPERTY(QString, Description, description); 387 SINK_PROPERTY(QString, Description, description);
@@ -329,20 +389,23 @@ struct SINK_EXPORT Event : public Entity {
329}; 389};
330 390
331struct SINK_EXPORT Todo : public Entity { 391struct SINK_EXPORT Todo : public Entity {
332 SINK_ENTITY(Todo); 392 SINK_ENTITY(Todo, todo);
333}; 393};
334 394
335struct SINK_EXPORT Calendar : public Entity { 395struct SINK_EXPORT Calendar : public Entity {
336 SINK_ENTITY(Calendar); 396 SINK_ENTITY(Calendar, calendar);
337}; 397};
338 398
339struct SINK_EXPORT Folder : public Entity { 399struct SINK_EXPORT Folder : public Entity {
340 SINK_ENTITY(Folder); 400 SINK_ENTITY(Folder, folder);
341 SINK_REFERENCE_PROPERTY(Folder, Parent, parent); 401 SINK_REFERENCE_PROPERTY(Folder, Parent, parent);
342 SINK_PROPERTY(QString, Name, name); 402 SINK_PROPERTY(QString, Name, name);
343 SINK_PROPERTY(QByteArray, Icon, icon); 403 SINK_PROPERTY(QByteArray, Icon, icon);
344 SINK_PROPERTY(QByteArrayList, SpecialPurpose, specialpurpose); 404 SINK_PROPERTY(QByteArrayList, SpecialPurpose, specialpurpose);
345 SINK_PROPERTY(bool, Enabled, enabled); 405 SINK_PROPERTY(bool, Enabled, enabled);
406 SINK_EXTRACTED_PROPERTY(QDateTime, LastUpdated, lastUpdated);
407 SINK_EXTRACTED_PROPERTY(int, Count, count);
408 SINK_EXTRACTED_PROPERTY(bool, FullContentAvailable, fullContentAvailable);
346}; 409};
347 410
348struct SINK_EXPORT Mail : public Entity { 411struct SINK_EXPORT Mail : public Entity {
@@ -351,7 +414,7 @@ struct SINK_EXPORT Mail : public Entity {
351 QString emailAddress; 414 QString emailAddress;
352 }; 415 };
353 416
354 SINK_ENTITY(Mail); 417 SINK_ENTITY(Mail, mail);
355 SINK_EXTRACTED_PROPERTY(Contact, Sender, sender); 418 SINK_EXTRACTED_PROPERTY(Contact, Sender, sender);
356 SINK_EXTRACTED_PROPERTY(QList<Contact>, To, to); 419 SINK_EXTRACTED_PROPERTY(QList<Contact>, To, to);
357 SINK_EXTRACTED_PROPERTY(QList<Contact>, Cc, cc); 420 SINK_EXTRACTED_PROPERTY(QList<Contact>, Cc, cc);
@@ -373,23 +436,8 @@ struct SINK_EXPORT Mail : public Entity {
373 436
374SINK_EXPORT QDebug operator<< (QDebug d, const Mail::Contact &c); 437SINK_EXPORT QDebug operator<< (QDebug d, const Mail::Contact &c);
375 438
376/**
377 * The status of an account or resource.
378 *
379 * It is set as follows:
380 * * By default the status is offline.
381 * * If a connection to the server could be established the status is Connected.
382 * * If an error occurred that keeps the resource from operating (so non transient), the resource enters the error state.
383 * * If a long running operation is started the resource goes to the busy state (and return to the previous state after that).
384 */
385enum SINK_EXPORT Status {
386 OfflineStatus,
387 ConnectedStatus,
388 BusyStatus,
389 ErrorStatus
390};
391
392struct SINK_EXPORT Identity : public ApplicationDomainType { 439struct SINK_EXPORT Identity : public ApplicationDomainType {
440 static constexpr const char *name = "identity";
393 typedef QSharedPointer<Identity> Ptr; 441 typedef QSharedPointer<Identity> Ptr;
394 explicit Identity(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer<BufferAdaptor> &adaptor); 442 explicit Identity(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer<BufferAdaptor> &adaptor);
395 explicit Identity(const QByteArray &identifier); 443 explicit Identity(const QByteArray &identifier);
@@ -416,6 +464,10 @@ struct SINK_EXPORT ImapResource {
416 static SinkResource create(const QByteArray &account); 464 static SinkResource create(const QByteArray &account);
417}; 465};
418 466
467struct SINK_EXPORT CardDavResource {
468 static SinkResource create(const QByteArray &account);
469};
470
419namespace ResourceCapabilities { 471namespace ResourceCapabilities {
420namespace Mail { 472namespace Mail {
421 static constexpr const char *mail = "mail"; 473 static constexpr const char *mail = "mail";
@@ -427,6 +479,11 @@ namespace Mail {
427 static constexpr const char *transport = "mail.transport"; 479 static constexpr const char *transport = "mail.transport";
428 static constexpr const char *folderhierarchy = "mail.folderhierarchy"; 480 static constexpr const char *folderhierarchy = "mail.folderhierarchy";
429}; 481};
482namespace Contact {
483 static constexpr const char *contact = "contact";
484 static constexpr const char *addressbook = "addressbook";
485 static constexpr const char *storage = "contact.storage";
486};
430}; 487};
431 488
432namespace SpecialPurpose { 489namespace SpecialPurpose {
@@ -444,31 +501,10 @@ namespace Mail {
444 * Do not store these types to disk, they may change over time. 501 * Do not store these types to disk, they may change over time.
445 */ 502 */
446template<class DomainType> 503template<class DomainType>
447QByteArray SINK_EXPORT getTypeName(); 504QByteArray SINK_EXPORT getTypeName()
448 505{
449template<> 506 return DomainType::name;
450QByteArray SINK_EXPORT getTypeName<Contact>(); 507}
451
452template<>
453QByteArray SINK_EXPORT getTypeName<Event>();
454
455template<>
456QByteArray SINK_EXPORT getTypeName<Todo>();
457
458template<>
459QByteArray SINK_EXPORT getTypeName<SinkResource>();
460
461template<>
462QByteArray SINK_EXPORT getTypeName<SinkAccount>();
463
464template<>
465QByteArray SINK_EXPORT getTypeName<Identity>();
466
467template<>
468QByteArray SINK_EXPORT getTypeName<Mail>();
469
470template<>
471QByteArray SINK_EXPORT getTypeName<Folder>();
472 508
473QByteArrayList SINK_EXPORT getTypeNames(); 509QByteArrayList SINK_EXPORT getTypeNames();
474 510
@@ -499,6 +535,7 @@ class SINK_EXPORT TypeImplementation;
499 */ 535 */
500#define SINK_REGISTER_TYPES() \ 536#define SINK_REGISTER_TYPES() \
501 REGISTER_TYPE(Sink::ApplicationDomain::Contact) \ 537 REGISTER_TYPE(Sink::ApplicationDomain::Contact) \
538 REGISTER_TYPE(Sink::ApplicationDomain::Addressbook) \
502 REGISTER_TYPE(Sink::ApplicationDomain::Event) \ 539 REGISTER_TYPE(Sink::ApplicationDomain::Event) \
503 REGISTER_TYPE(Sink::ApplicationDomain::Mail) \ 540 REGISTER_TYPE(Sink::ApplicationDomain::Mail) \
504 REGISTER_TYPE(Sink::ApplicationDomain::Folder) \ 541 REGISTER_TYPE(Sink::ApplicationDomain::Folder) \
@@ -520,6 +557,7 @@ Q_DECLARE_METATYPE(Sink::ApplicationDomain::ApplicationDomainType::Ptr)
520Q_DECLARE_METATYPE(Sink::ApplicationDomain::Entity) 557Q_DECLARE_METATYPE(Sink::ApplicationDomain::Entity)
521Q_DECLARE_METATYPE(Sink::ApplicationDomain::Entity::Ptr) 558Q_DECLARE_METATYPE(Sink::ApplicationDomain::Entity::Ptr)
522Q_DECLARE_METATYPE(Sink::ApplicationDomain::Mail::Contact) 559Q_DECLARE_METATYPE(Sink::ApplicationDomain::Mail::Contact)
560Q_DECLARE_METATYPE(Sink::ApplicationDomain::Contact::Email)
523Q_DECLARE_METATYPE(Sink::ApplicationDomain::Error) 561Q_DECLARE_METATYPE(Sink::ApplicationDomain::Error)
524Q_DECLARE_METATYPE(Sink::ApplicationDomain::Progress) 562Q_DECLARE_METATYPE(Sink::ApplicationDomain::Progress)
525Q_DECLARE_METATYPE(Sink::ApplicationDomain::BLOB) 563Q_DECLARE_METATYPE(Sink::ApplicationDomain::BLOB)
diff --git a/common/domain/applicationdomaintype_p.h b/common/domain/applicationdomaintype_p.h
index 4b06864..a5a6b1d 100644
--- a/common/domain/applicationdomaintype_p.h
+++ b/common/domain/applicationdomaintype_p.h
@@ -33,13 +33,15 @@ struct TypeHelper {
33 template <typename R, typename ...Args> 33 template <typename R, typename ...Args>
34 R operator()(Args && ... args) const { 34 R operator()(Args && ... args) const {
35 if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Folder>()) { 35 if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Folder>()) {
36 return Func<Sink::ApplicationDomain::Folder>{}(std::forward<Args...>(args...)); 36 return Func<Sink::ApplicationDomain::Folder>{}(std::forward<Args...>(args...));
37 } else if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Mail>()) { 37 } else if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Mail>()) {
38 return Func<Sink::ApplicationDomain::Mail>{}(std::forward<Args...>(args...)); 38 return Func<Sink::ApplicationDomain::Mail>{}(std::forward<Args...>(args...));
39 } else if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Event>()) { 39 } else if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Event>()) {
40 return Func<Sink::ApplicationDomain::Event>{}(std::forward<Args...>(args...)); 40 return Func<Sink::ApplicationDomain::Event>{}(std::forward<Args...>(args...));
41 } else if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Contact>()) { 41 } else if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Contact>()) {
42 return Func<Sink::ApplicationDomain::Contact>{}(std::forward<Args...>(args...)); 42 return Func<Sink::ApplicationDomain::Contact>{}(std::forward<Args...>(args...));
43 } else if (type == Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Addressbook>()) {
44 return Func<Sink::ApplicationDomain::Addressbook>{}(std::forward<Args...>(args...));
43 } else { 45 } else {
44 Q_ASSERT(false); 46 Q_ASSERT(false);
45 } 47 }
diff --git a/common/domain/contact.cpp b/common/domain/contact.cpp
deleted file mode 100644
index ea7cac2..0000000
--- a/common/domain/contact.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
1/*
2 * Copyright (C) 2017 Sandro Knauß <knauss@kolabsys.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19#include "contact.h"
20
21#include <QVector>
22#include <QByteArray>
23#include <QString>
24
25#include "../propertymapper.h"
26#include "../typeindex.h"
27#include "entity_generated.h"
28
29#include "contact_generated.h"
30
31using namespace Sink::ApplicationDomain;
32
33void TypeImplementation<Contact>::configure(TypeIndex &index)
34{
35 index.addProperty<QByteArray>(Contact::Uid::name);
36}
37
38void TypeImplementation<Contact>::configure(ReadPropertyMapper<Buffer> &propertyMapper)
39{
40 propertyMapper.addMapping<Contact::Uid, Buffer>(&Buffer::uid);
41 propertyMapper.addMapping<Contact::Fn, Buffer>(&Buffer::fn);
42 propertyMapper.addMapping<Contact::Emails, Buffer>(&Buffer::emails);
43 propertyMapper.addMapping<Contact::Vcard, Buffer>(&Buffer::vcard);
44}
45
46void TypeImplementation<Contact>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper)
47{
48 propertyMapper.addMapping<Contact::Uid>(&BufferBuilder::add_uid);
49 propertyMapper.addMapping<Contact::Fn>(&BufferBuilder::add_fn);
50 propertyMapper.addMapping<Contact::Emails>(&BufferBuilder::add_emails);
51 propertyMapper.addMapping<Contact::Vcard>(&BufferBuilder::add_vcard);
52}
53
54void TypeImplementation<Contact>::configure(IndexPropertyMapper &)
55{
56
57}
diff --git a/common/domain/contact.fbs b/common/domain/contact.fbs
index 34fb1d6..d941d5a 100644
--- a/common/domain/contact.fbs
+++ b/common/domain/contact.fbs
@@ -1,9 +1,17 @@
1namespace Sink.ApplicationDomain.Buffer; 1namespace Sink.ApplicationDomain.Buffer;
2 2
3table ContactEmail {
4 type: int;
5 email: string;
6}
7
3table Contact { 8table Contact {
4 uid:string; 9 uid:string;
5 fn:string; 10 fn:string;
6 emails: [string]; 11 firstname:string;
12 lastname:string;
13 addressbook:string;
14 emails: [ContactEmail];
7 vcard: string; 15 vcard: string;
8} 16}
9 17
diff --git a/common/domain/contact.h b/common/domain/contact.h
deleted file mode 100644
index c803a9f..0000000
--- a/common/domain/contact.h
+++ /dev/null
@@ -1,57 +0,0 @@
1/*
2 * Copyright (C) 2017 Sandro Knauß <knauss@kolabsys.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19#pragma once
20
21#include "applicationdomaintype.h"
22
23class QByteArray;
24
25template<typename T>
26class ReadPropertyMapper;
27template<typename T>
28class WritePropertyMapper;
29class IndexPropertyMapper;
30
31class TypeIndex;
32
33namespace Sink {
34namespace ApplicationDomain {
35 namespace Buffer {
36 struct Contact;
37 struct ContactBuilder;
38 }
39
40/**
41 * Implements all type-specific code such as updating and querying indexes.
42 *
43 * These are type specifiy default implementations. Theoretically a resource could implement it's own implementation.
44 */
45template<>
46class TypeImplementation<Sink::ApplicationDomain::Contact> {
47public:
48 typedef Sink::ApplicationDomain::Buffer::Contact Buffer;
49 typedef Sink::ApplicationDomain::Buffer::ContactBuilder BufferBuilder;
50 static void configure(TypeIndex &);
51 static void configure(ReadPropertyMapper<Buffer> &);
52 static void configure(WritePropertyMapper<BufferBuilder> &);
53 static void configure(IndexPropertyMapper &indexPropertyMapper);
54};
55
56}
57}
diff --git a/common/domain/domaintypes.h b/common/domain/domaintypes.h
deleted file mode 100644
index 0abdee7..0000000
--- a/common/domain/domaintypes.h
+++ /dev/null
@@ -1,5 +0,0 @@
1
2#include "contact.h"
3#include "mail.h"
4#include "folder.h"
5#include "event.h"
diff --git a/common/domain/event.cpp b/common/domain/event.cpp
deleted file mode 100644
index 10c92bb..0000000
--- a/common/domain/event.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19#include "event.h"
20
21#include <QVector>
22#include <QByteArray>
23#include <QString>
24
25#include "../propertymapper.h"
26#include "../typeindex.h"
27#include "entity_generated.h"
28
29#include "event_generated.h"
30
31using namespace Sink::ApplicationDomain;
32
33void TypeImplementation<Event>::configure(TypeIndex &index)
34{
35 index.addProperty<QByteArray>(Event::Uid::name);
36}
37
38void TypeImplementation<Event>::configure(ReadPropertyMapper<Buffer> &propertyMapper)
39{
40 propertyMapper.addMapping<Event::Summary, Buffer>(&Buffer::summary);
41 propertyMapper.addMapping<Event::Description, Buffer>(&Buffer::description);
42 propertyMapper.addMapping<Event::Uid, Buffer>(&Buffer::uid);
43 propertyMapper.addMapping<Event::Attachment, Buffer>(&Buffer::attachment);
44}
45
46void TypeImplementation<Event>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper)
47{
48 propertyMapper.addMapping<Event::Summary>(&BufferBuilder::add_summary);
49 propertyMapper.addMapping<Event::Description>(&BufferBuilder::add_description);
50 propertyMapper.addMapping<Event::Uid>(&BufferBuilder::add_uid);
51 propertyMapper.addMapping<Event::Attachment>(&BufferBuilder::add_attachment);
52}
53
54void TypeImplementation<Event>::configure(IndexPropertyMapper &)
55{
56
57}
diff --git a/common/domain/event.h b/common/domain/event.h
deleted file mode 100644
index b683f5f..0000000
--- a/common/domain/event.h
+++ /dev/null
@@ -1,57 +0,0 @@
1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19#pragma once
20
21#include "applicationdomaintype.h"
22
23class QByteArray;
24
25template<typename T>
26class ReadPropertyMapper;
27template<typename T>
28class WritePropertyMapper;
29class IndexPropertyMapper;
30
31class TypeIndex;
32
33namespace Sink {
34namespace ApplicationDomain {
35 namespace Buffer {
36 struct Event;
37 struct EventBuilder;
38 }
39
40/**
41 * Implements all type-specific code such as updating and querying indexes.
42 *
43 * These are type specifiy default implementations. Theoretically a resource could implement it's own implementation.
44 */
45template<>
46class TypeImplementation<Sink::ApplicationDomain::Event> {
47public:
48 typedef Sink::ApplicationDomain::Buffer::Event Buffer;
49 typedef Sink::ApplicationDomain::Buffer::EventBuilder BufferBuilder;
50 static void configure(TypeIndex &);
51 static void configure(ReadPropertyMapper<Buffer> &);
52 static void configure(WritePropertyMapper<BufferBuilder> &);
53 static void configure(IndexPropertyMapper &indexPropertyMapper);
54};
55
56}
57}
diff --git a/common/domain/folder.cpp b/common/domain/folder.cpp
deleted file mode 100644
index 6717661..0000000
--- a/common/domain/folder.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastfolder.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19#include "folder.h"
20
21#include <QByteArray>
22#include <QString>
23
24#include "../propertymapper.h"
25#include "../typeindex.h"
26#include "entitybuffer.h"
27#include "entity_generated.h"
28
29#include "folder_generated.h"
30
31using namespace Sink::ApplicationDomain;
32
33void TypeImplementation<Folder>::configure(TypeIndex &index)
34{
35 index.addProperty<QByteArray>(Folder::Parent::name);
36 index.addProperty<QString>(Folder::Name::name);
37}
38
39void TypeImplementation<Folder>::configure(ReadPropertyMapper<Buffer> &propertyMapper)
40{
41 propertyMapper.addMapping<Folder::Parent, Buffer>(&Buffer::parent);
42 propertyMapper.addMapping<Folder::Name, Buffer>(&Buffer::name);
43 propertyMapper.addMapping<Folder::Icon, Buffer>(&Buffer::icon);
44 propertyMapper.addMapping<Folder::SpecialPurpose, Buffer>(&Buffer::specialpurpose);
45 propertyMapper.addMapping<Folder::Enabled, Buffer>(&Buffer::enabled);
46}
47
48void TypeImplementation<Folder>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper)
49{
50 propertyMapper.addMapping<Folder::Parent>(&BufferBuilder::add_parent);
51 propertyMapper.addMapping<Folder::Name>(&BufferBuilder::add_name);
52 propertyMapper.addMapping<Folder::Icon>(&BufferBuilder::add_icon);
53 propertyMapper.addMapping<Folder::SpecialPurpose>(&BufferBuilder::add_specialpurpose);
54 propertyMapper.addMapping<Folder::Enabled>(&BufferBuilder::add_enabled);
55}
56
57void TypeImplementation<Folder>::configure(IndexPropertyMapper &)
58{
59
60}
diff --git a/common/domain/folder.h b/common/domain/folder.h
deleted file mode 100644
index f232ab5..0000000
--- a/common/domain/folder.h
+++ /dev/null
@@ -1,51 +0,0 @@
1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19#pragma once
20
21#include "applicationdomaintype.h"
22
23template<typename T>
24class ReadPropertyMapper;
25template<typename T>
26class WritePropertyMapper;
27class IndexPropertyMapper;
28
29class TypeIndex;
30
31namespace Sink {
32
33namespace ApplicationDomain {
34 namespace Buffer {
35 struct Folder;
36 struct FolderBuilder;
37 }
38
39template<>
40class TypeImplementation<Sink::ApplicationDomain::Folder> {
41public:
42 typedef Sink::ApplicationDomain::Buffer::Folder Buffer;
43 typedef Sink::ApplicationDomain::Buffer::FolderBuilder BufferBuilder;
44 static void configure(TypeIndex &);
45 static void configure(ReadPropertyMapper<Buffer> &);
46 static void configure(WritePropertyMapper<BufferBuilder> &);
47 static void configure(IndexPropertyMapper &indexPropertyMapper);
48};
49
50}
51}
diff --git a/common/domain/mail.h b/common/domain/mail.h
deleted file mode 100644
index e052448..0000000
--- a/common/domain/mail.h
+++ /dev/null
@@ -1,52 +0,0 @@
1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19#pragma once
20
21#include "applicationdomaintype.h"
22
23class QByteArray;
24
25template<typename T>
26class ReadPropertyMapper;
27template<typename T>
28class WritePropertyMapper;
29class IndexPropertyMapper;
30
31class TypeIndex;
32
33namespace Sink {
34namespace ApplicationDomain {
35 namespace Buffer {
36 struct Mail;
37 struct MailBuilder;
38 }
39
40template<>
41class TypeImplementation<Sink::ApplicationDomain::Mail> {
42public:
43 typedef Sink::ApplicationDomain::Buffer::Mail Buffer;
44 typedef Sink::ApplicationDomain::Buffer::MailBuilder BufferBuilder;
45 static void configure(TypeIndex &index);
46 static void configure(ReadPropertyMapper<Buffer> &propertyMapper);
47 static void configure(WritePropertyMapper<BufferBuilder> &propertyMapper);
48 static void configure(IndexPropertyMapper &indexPropertyMapper);
49};
50
51}
52}
diff --git a/common/domain/propertyregistry.cpp b/common/domain/propertyregistry.cpp
index 2208193..7b9b61a 100644
--- a/common/domain/propertyregistry.cpp
+++ b/common/domain/propertyregistry.cpp
@@ -64,6 +64,17 @@ QVariant parseString<bool>(const QString &s)
64} 64}
65 65
66template <> 66template <>
67QVariant parseString<int>(const QString &s)
68{
69 bool ok = false;
70 auto n = s.toInt(&ok);
71 if (ok) {
72 return QVariant::fromValue(n);
73 }
74 return {};
75}
76
77template <>
67QVariant parseString<QList<QByteArray>>(const QString &s) 78QVariant parseString<QList<QByteArray>>(const QString &s)
68{ 79{
69 auto list = s.split(','); 80 auto list = s.split(',');
@@ -92,6 +103,13 @@ QVariant parseString<QList<Sink::ApplicationDomain::Mail::Contact>>(const QStrin
92 return QVariant{}; 103 return QVariant{};
93} 104}
94 105
106template <>
107QVariant parseString<QList<Sink::ApplicationDomain::Contact::Email>>(const QString &s)
108{
109 Q_ASSERT(false);
110 return QVariant{};
111}
112
95PropertyRegistry &PropertyRegistry::instance() 113PropertyRegistry &PropertyRegistry::instance()
96{ 114{
97 static PropertyRegistry instance; 115 static PropertyRegistry instance;
diff --git a/common/domain/propertyregistry.h b/common/domain/propertyregistry.h
index 16df23b..758c10d 100644
--- a/common/domain/propertyregistry.h
+++ b/common/domain/propertyregistry.h
@@ -49,6 +49,9 @@ template <>
49QVariant parseString<bool>(const QString &s); 49QVariant parseString<bool>(const QString &s);
50 50
51template <> 51template <>
52QVariant parseString<int>(const QString &s);
53
54template <>
52QVariant parseString<QList<QByteArray>>(const QString &s); 55QVariant parseString<QList<QByteArray>>(const QString &s);
53 56
54template <> 57template <>
diff --git a/common/domain/mail.cpp b/common/domain/typeimplementations.cpp
index 8cbe61b..eb3851e 100644
--- a/common/domain/mail.cpp
+++ b/common/domain/typeimplementations.cpp
@@ -16,7 +16,7 @@
16 * Free Software Foundation, Inc., 16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */ 18 */
19#include "mail.h" 19#include "typeimplementations.h"
20 20
21#include <QVector> 21#include <QVector>
22#include <QByteArray> 22#include <QByteArray>
@@ -29,8 +29,6 @@
29#include "mail/threadindexer.h" 29#include "mail/threadindexer.h"
30#include "domainadaptor.h" 30#include "domainadaptor.h"
31 31
32#include "mail_generated.h"
33
34using namespace Sink; 32using namespace Sink;
35using namespace Sink::ApplicationDomain; 33using namespace Sink::ApplicationDomain;
36 34
@@ -102,3 +100,117 @@ void TypeImplementation<Mail>::configure(WritePropertyMapper<BufferBuilder> &pro
102 propertyMapper.addMapping<Mail::MessageId>(&BufferBuilder::add_messageId); 100 propertyMapper.addMapping<Mail::MessageId>(&BufferBuilder::add_messageId);
103 propertyMapper.addMapping<Mail::ParentMessageId>(&BufferBuilder::add_parentMessageId); 101 propertyMapper.addMapping<Mail::ParentMessageId>(&BufferBuilder::add_parentMessageId);
104} 102}
103
104
105void TypeImplementation<Folder>::configure(TypeIndex &index)
106{
107 index.addProperty<QByteArray>(Folder::Parent::name);
108 index.addProperty<QString>(Folder::Name::name);
109}
110
111void TypeImplementation<Folder>::configure(ReadPropertyMapper<Buffer> &propertyMapper)
112{
113 propertyMapper.addMapping<Folder::Parent, Buffer>(&Buffer::parent);
114 propertyMapper.addMapping<Folder::Name, Buffer>(&Buffer::name);
115 propertyMapper.addMapping<Folder::Icon, Buffer>(&Buffer::icon);
116 propertyMapper.addMapping<Folder::SpecialPurpose, Buffer>(&Buffer::specialpurpose);
117 propertyMapper.addMapping<Folder::Enabled, Buffer>(&Buffer::enabled);
118}
119
120void TypeImplementation<Folder>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper)
121{
122 propertyMapper.addMapping<Folder::Parent>(&BufferBuilder::add_parent);
123 propertyMapper.addMapping<Folder::Name>(&BufferBuilder::add_name);
124 propertyMapper.addMapping<Folder::Icon>(&BufferBuilder::add_icon);
125 propertyMapper.addMapping<Folder::SpecialPurpose>(&BufferBuilder::add_specialpurpose);
126 propertyMapper.addMapping<Folder::Enabled>(&BufferBuilder::add_enabled);
127}
128
129void TypeImplementation<Folder>::configure(IndexPropertyMapper &)
130{
131
132}
133
134
135void TypeImplementation<Contact>::configure(TypeIndex &index)
136{
137 index.addProperty<QByteArray>(Contact::Uid::name);
138}
139
140void TypeImplementation<Contact>::configure(ReadPropertyMapper<Buffer> &propertyMapper)
141{
142 propertyMapper.addMapping<Contact::Uid, Buffer>(&Buffer::uid);
143 propertyMapper.addMapping<Contact::Fn, Buffer>(&Buffer::fn);
144 propertyMapper.addMapping<Contact::Emails, Buffer>(&Buffer::emails);
145 propertyMapper.addMapping<Contact::Vcard, Buffer>(&Buffer::vcard);
146 propertyMapper.addMapping<Contact::Addressbook, Buffer>(&Buffer::addressbook);
147 propertyMapper.addMapping<Contact::Firstname, Buffer>(&Buffer::firstname);
148 propertyMapper.addMapping<Contact::Lastname, Buffer>(&Buffer::lastname);
149}
150
151void TypeImplementation<Contact>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper)
152{
153 propertyMapper.addMapping<Contact::Uid>(&BufferBuilder::add_uid);
154 propertyMapper.addMapping<Contact::Fn>(&BufferBuilder::add_fn);
155 propertyMapper.addMapping<Contact::Emails>(&BufferBuilder::add_emails);
156 propertyMapper.addMapping<Contact::Vcard>(&BufferBuilder::add_vcard);
157 propertyMapper.addMapping<Contact::Addressbook>(&BufferBuilder::add_addressbook);
158 propertyMapper.addMapping<Contact::Firstname>(&BufferBuilder::add_firstname);
159 propertyMapper.addMapping<Contact::Lastname>(&BufferBuilder::add_lastname);
160}
161
162void TypeImplementation<Contact>::configure(IndexPropertyMapper &)
163{
164
165}
166
167
168void TypeImplementation<Addressbook>::configure(TypeIndex &index)
169{
170 index.addProperty<QByteArray>(Addressbook::Parent::name);
171 index.addProperty<QString>(Addressbook::Name::name);
172}
173
174void TypeImplementation<Addressbook>::configure(ReadPropertyMapper<Buffer> &propertyMapper)
175{
176 propertyMapper.addMapping<Addressbook::Parent, Buffer>(&Buffer::parent);
177 propertyMapper.addMapping<Addressbook::Name, Buffer>(&Buffer::name);
178}
179
180void TypeImplementation<Addressbook>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper)
181{
182 propertyMapper.addMapping<Addressbook::Parent>(&BufferBuilder::add_parent);
183 propertyMapper.addMapping<Addressbook::Name>(&BufferBuilder::add_name);
184}
185
186void TypeImplementation<Addressbook>::configure(IndexPropertyMapper &)
187{
188
189}
190
191
192void TypeImplementation<Event>::configure(TypeIndex &index)
193{
194 index.addProperty<QByteArray>(Event::Uid::name);
195}
196
197void TypeImplementation<Event>::configure(ReadPropertyMapper<Buffer> &propertyMapper)
198{
199 propertyMapper.addMapping<Event::Summary, Buffer>(&Buffer::summary);
200 propertyMapper.addMapping<Event::Description, Buffer>(&Buffer::description);
201 propertyMapper.addMapping<Event::Uid, Buffer>(&Buffer::uid);
202 propertyMapper.addMapping<Event::Attachment, Buffer>(&Buffer::attachment);
203}
204
205void TypeImplementation<Event>::configure(WritePropertyMapper<BufferBuilder> &propertyMapper)
206{
207 propertyMapper.addMapping<Event::Summary>(&BufferBuilder::add_summary);
208 propertyMapper.addMapping<Event::Description>(&BufferBuilder::add_description);
209 propertyMapper.addMapping<Event::Uid>(&BufferBuilder::add_uid);
210 propertyMapper.addMapping<Event::Attachment>(&BufferBuilder::add_attachment);
211}
212
213void TypeImplementation<Event>::configure(IndexPropertyMapper &)
214{
215
216}
diff --git a/common/domain/typeimplementations.h b/common/domain/typeimplementations.h
new file mode 100644
index 0000000..37d6ca9
--- /dev/null
+++ b/common/domain/typeimplementations.h
@@ -0,0 +1,101 @@
1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19#pragma once
20
21#include "applicationdomaintype.h"
22
23#include "mail_generated.h"
24#include "folder_generated.h"
25#include "event_generated.h"
26#include "contact_generated.h"
27#include "addressbook_generated.h"
28
29template<typename T>
30class ReadPropertyMapper;
31template<typename T>
32class WritePropertyMapper;
33class IndexPropertyMapper;
34
35class TypeIndex;
36
37/**
38 * Implements all type-specific code such as updating and querying indexes.
39 *
40 * These are type specifiy default implementations. Theoretically a resource could implement it's own implementation.
41 */
42namespace Sink {
43namespace ApplicationDomain {
44
45template<>
46class TypeImplementation<Sink::ApplicationDomain::Mail> {
47public:
48 typedef Sink::ApplicationDomain::Buffer::Mail Buffer;
49 typedef Sink::ApplicationDomain::Buffer::MailBuilder BufferBuilder;
50 static void configure(TypeIndex &index);
51 static void configure(ReadPropertyMapper<Buffer> &propertyMapper);
52 static void configure(WritePropertyMapper<BufferBuilder> &propertyMapper);
53 static void configure(IndexPropertyMapper &indexPropertyMapper);
54};
55
56template<>
57class TypeImplementation<Sink::ApplicationDomain::Folder> {
58public:
59 typedef Sink::ApplicationDomain::Buffer::Folder Buffer;
60 typedef Sink::ApplicationDomain::Buffer::FolderBuilder BufferBuilder;
61 static void configure(TypeIndex &);
62 static void configure(ReadPropertyMapper<Buffer> &);
63 static void configure(WritePropertyMapper<BufferBuilder> &);
64 static void configure(IndexPropertyMapper &indexPropertyMapper);
65};
66
67template<>
68class TypeImplementation<Sink::ApplicationDomain::Contact> {
69public:
70 typedef Sink::ApplicationDomain::Buffer::Contact Buffer;
71 typedef Sink::ApplicationDomain::Buffer::ContactBuilder BufferBuilder;
72 static void configure(TypeIndex &);
73 static void configure(ReadPropertyMapper<Buffer> &);
74 static void configure(WritePropertyMapper<BufferBuilder> &);
75 static void configure(IndexPropertyMapper &indexPropertyMapper);
76};
77
78template<>
79class TypeImplementation<Sink::ApplicationDomain::Addressbook> {
80public:
81 typedef Sink::ApplicationDomain::Buffer::Addressbook Buffer;
82 typedef Sink::ApplicationDomain::Buffer::AddressbookBuilder BufferBuilder;
83 static void configure(TypeIndex &);
84 static void configure(ReadPropertyMapper<Buffer> &);
85 static void configure(WritePropertyMapper<BufferBuilder> &);
86 static void configure(IndexPropertyMapper &indexPropertyMapper);
87};
88
89template<>
90class TypeImplementation<Sink::ApplicationDomain::Event> {
91public:
92 typedef Sink::ApplicationDomain::Buffer::Event Buffer;
93 typedef Sink::ApplicationDomain::Buffer::EventBuilder BufferBuilder;
94 static void configure(TypeIndex &);
95 static void configure(ReadPropertyMapper<Buffer> &);
96 static void configure(WritePropertyMapper<BufferBuilder> &);
97 static void configure(IndexPropertyMapper &indexPropertyMapper);
98};
99
100}
101}
diff --git a/common/domainadaptor.h b/common/domainadaptor.h
index 0377ef4..af5d5fc 100644
--- a/common/domainadaptor.h
+++ b/common/domainadaptor.h
@@ -26,10 +26,7 @@
26 26
27#include "domaintypeadaptorfactoryinterface.h" 27#include "domaintypeadaptorfactoryinterface.h"
28#include "domain/applicationdomaintype.h" 28#include "domain/applicationdomaintype.h"
29#include "domain/contact.h" 29#include "domain/typeimplementations.h"
30#include "domain/event.h"
31#include "domain/mail.h"
32#include "domain/folder.h"
33#include "bufferadaptor.h" 30#include "bufferadaptor.h"
34#include "entity_generated.h" 31#include "entity_generated.h"
35#include "metadata_generated.h" 32#include "metadata_generated.h"
@@ -245,3 +242,15 @@ protected:
245 QSharedPointer<WritePropertyMapper<ResourceBuilder>> mResourceWriteMapper; 242 QSharedPointer<WritePropertyMapper<ResourceBuilder>> mResourceWriteMapper;
246 QSharedPointer<IndexPropertyMapper> mIndexMapper; 243 QSharedPointer<IndexPropertyMapper> mIndexMapper;
247}; 244};
245
246/**
247 * A default adaptorfactory implemenation that simply instantiates a generic resource
248 */
249template<typename DomainType>
250class DefaultAdaptorFactory : public DomainTypeAdaptorFactory<DomainType>
251{
252public:
253 DefaultAdaptorFactory() : DomainTypeAdaptorFactory<DomainType>() {}
254 virtual ~DefaultAdaptorFactory(){}
255};
256
diff --git a/common/genericresource.cpp b/common/genericresource.cpp
index 5ba9e5d..7178b3d 100644
--- a/common/genericresource.cpp
+++ b/common/genericresource.cpp
@@ -31,8 +31,8 @@ using namespace Sink::Storage;
31GenericResource::GenericResource(const ResourceContext &resourceContext, const QSharedPointer<Pipeline> &pipeline ) 31GenericResource::GenericResource(const ResourceContext &resourceContext, const QSharedPointer<Pipeline> &pipeline )
32 : Sink::Resource(), 32 : Sink::Resource(),
33 mResourceContext(resourceContext), 33 mResourceContext(resourceContext),
34 mPipeline(pipeline ? pipeline : QSharedPointer<Sink::Pipeline>::create(resourceContext, "resource." + resourceContext.instanceId())), 34 mPipeline(pipeline ? pipeline : QSharedPointer<Sink::Pipeline>::create(resourceContext, Log::Context{})),
35 mProcessor(QSharedPointer<CommandProcessor>::create(mPipeline.data(), resourceContext.instanceId(), "resource." + resourceContext.instanceId())), 35 mProcessor(QSharedPointer<CommandProcessor>::create(mPipeline.data(), resourceContext.instanceId(), Log::Context{})),
36 mError(0), 36 mError(0),
37 mClientLowerBoundRevision(std::numeric_limits<qint64>::max()) 37 mClientLowerBoundRevision(std::numeric_limits<qint64>::max())
38{ 38{
diff --git a/common/index.cpp b/common/index.cpp
index f09e265..725c28b 100644
--- a/common/index.cpp
+++ b/common/index.cpp
@@ -2,31 +2,31 @@
2 2
3#include "log.h" 3#include "log.h"
4 4
5SINK_DEBUG_AREA("index")
6
7Index::Index(const QString &storageRoot, const QString &name, Sink::Storage::DataStore::AccessMode mode) 5Index::Index(const QString &storageRoot, const QString &name, Sink::Storage::DataStore::AccessMode mode)
8 : mTransaction(Sink::Storage::DataStore(storageRoot, name, mode).createTransaction(mode)), 6 : mTransaction(Sink::Storage::DataStore(storageRoot, name, mode).createTransaction(mode)),
9 mDb(mTransaction.openDatabase(name.toLatin1(), std::function<void(const Sink::Storage::DataStore::Error &)>(), true)), 7 mDb(mTransaction.openDatabase(name.toLatin1(), std::function<void(const Sink::Storage::DataStore::Error &)>(), true)),
10 mName(name) 8 mName(name),
9 mLogCtx("index." + name.toLatin1())
11{ 10{
12} 11}
13 12
14Index::Index(const QByteArray &name, Sink::Storage::DataStore::Transaction &transaction) 13Index::Index(const QByteArray &name, Sink::Storage::DataStore::Transaction &transaction)
15 : mDb(transaction.openDatabase(name, std::function<void(const Sink::Storage::DataStore::Error &)>(), true)), mName(name) 14 : mDb(transaction.openDatabase(name, std::function<void(const Sink::Storage::DataStore::Error &)>(), true)), mName(name),
15 mLogCtx("index." + name)
16{ 16{
17} 17}
18 18
19void Index::add(const QByteArray &key, const QByteArray &value) 19void Index::add(const QByteArray &key, const QByteArray &value)
20{ 20{
21 mDb.write(key, value, [&] (const Sink::Storage::DataStore::Error &error) { 21 mDb.write(key, value, [&] (const Sink::Storage::DataStore::Error &error) {
22 SinkWarning() << "Error while writing value" << error; 22 SinkWarningCtx(mLogCtx) << "Error while writing value" << error;
23 }); 23 });
24} 24}
25 25
26void Index::remove(const QByteArray &key, const QByteArray &value) 26void Index::remove(const QByteArray &key, const QByteArray &value)
27{ 27{
28 mDb.remove(key, value, [&] (const Sink::Storage::DataStore::Error &error) { 28 mDb.remove(key, value, [&] (const Sink::Storage::DataStore::Error &error) {
29 SinkWarning() << "Error while removing value: " << key << value << error << error.store; 29 SinkWarningCtx(mLogCtx) << "Error while removing value: " << key << value << error;
30 }); 30 });
31} 31}
32 32
@@ -38,7 +38,7 @@ void Index::lookup(const QByteArray &key, const std::function<void(const QByteAr
38 return true; 38 return true;
39 }, 39 },
40 [&](const Sink::Storage::DataStore::Error &error) { 40 [&](const Sink::Storage::DataStore::Error &error) {
41 SinkWarning() << "Error while retrieving value" << error.message; 41 SinkWarningCtx(mLogCtx) << "Error while retrieving value:" << error << mName;
42 errorHandler(Error(error.store, error.code, error.message)); 42 errorHandler(Error(error.store, error.code, error.message));
43 }, 43 },
44 matchSubStringKeys); 44 matchSubStringKeys);
@@ -48,6 +48,6 @@ QByteArray Index::lookup(const QByteArray &key)
48{ 48{
49 QByteArray result; 49 QByteArray result;
50 //We have to create a deep copy, otherwise the returned data may become invalid when the transaction ends. 50 //We have to create a deep copy, otherwise the returned data may become invalid when the transaction ends.
51 lookup(key, [&](const QByteArray &value) { result = QByteArray(value.constData(), value.size()); }, [this](const Index::Error &error) { SinkWarning() << "Error while retrieving value" << error.message; }); 51 lookup(key, [&](const QByteArray &value) { result = QByteArray(value.constData(), value.size()); }, [this](const Index::Error &) { });
52 return result; 52 return result;
53} 53}
diff --git a/common/index.h b/common/index.h
index cfcc7a0..81dc5bf 100644
--- a/common/index.h
+++ b/common/index.h
@@ -44,5 +44,5 @@ private:
44 Sink::Storage::DataStore::Transaction mTransaction; 44 Sink::Storage::DataStore::Transaction mTransaction;
45 Sink::Storage::DataStore::NamedDatabase mDb; 45 Sink::Storage::DataStore::NamedDatabase mDb;
46 QString mName; 46 QString mName;
47 SINK_DEBUG_COMPONENT(mName.toLatin1()) 47 Sink::Log::Context mLogCtx;
48}; 48};
diff --git a/common/listener.cpp b/common/listener.cpp
index f18fe1d..983e438 100644
--- a/common/listener.cpp
+++ b/common/listener.cpp
@@ -25,6 +25,7 @@
25#include "common/definitions.h" 25#include "common/definitions.h"
26#include "common/resourcecontext.h" 26#include "common/resourcecontext.h"
27#include "common/adaptorfactoryregistry.h" 27#include "common/adaptorfactoryregistry.h"
28#include "common/bufferutils.h"
28 29
29// commands 30// commands
30#include "common/commandcompletion_generated.h" 31#include "common/commandcompletion_generated.h"
@@ -66,7 +67,7 @@ Listener::Listener(const QByteArray &resourceInstanceIdentifier, const QByteArra
66 m_checkConnectionsTimer->setInterval(1000); 67 m_checkConnectionsTimer->setInterval(1000);
67 connect(m_checkConnectionsTimer.get(), &QTimer::timeout, [this]() { 68 connect(m_checkConnectionsTimer.get(), &QTimer::timeout, [this]() {
68 if (m_connections.isEmpty()) { 69 if (m_connections.isEmpty()) {
69 SinkLog() << QString("No connections, shutting down."); 70 SinkTrace() << QString("No connections, shutting down.");
70 quit(); 71 quit();
71 } 72 }
72 }); 73 });
@@ -87,6 +88,12 @@ Listener::~Listener()
87 88
88void Listener::emergencyAbortAllConnections() 89void Listener::emergencyAbortAllConnections()
89{ 90{
91 Sink::Notification n;
92 n.type = Sink::Notification::Status;
93 n.message = "The resource crashed.";
94 n.code = Sink::ApplicationDomain::ErrorStatus;
95 notify(n);
96
90 for (Client &client : m_connections) { 97 for (Client &client : m_connections) {
91 if (client.socket) { 98 if (client.socket) {
92 SinkWarning() << "Sending panic"; 99 SinkWarning() << "Sending panic";
@@ -406,11 +413,13 @@ void Listener::notify(const Sink::Notification &notification)
406{ 413{
407 auto messageString = m_fbb.CreateString(notification.message.toUtf8().constData(), notification.message.toUtf8().size()); 414 auto messageString = m_fbb.CreateString(notification.message.toUtf8().constData(), notification.message.toUtf8().size());
408 auto idString = m_fbb.CreateString(notification.id.constData(), notification.id.size()); 415 auto idString = m_fbb.CreateString(notification.id.constData(), notification.id.size());
416 auto entities = Sink::BufferUtils::toVector(m_fbb, notification.entities);
409 Sink::Commands::NotificationBuilder builder(m_fbb); 417 Sink::Commands::NotificationBuilder builder(m_fbb);
410 builder.add_type(notification.type); 418 builder.add_type(notification.type);
411 builder.add_code(notification.code); 419 builder.add_code(notification.code);
412 builder.add_identifier(idString); 420 builder.add_identifier(idString);
413 builder.add_message(messageString); 421 builder.add_message(messageString);
422 builder.add_entities(entities);
414 auto command = builder.Finish(); 423 auto command = builder.Finish();
415 Sink::Commands::FinishNotificationBuffer(m_fbb, command); 424 Sink::Commands::FinishNotificationBuffer(m_fbb, command);
416 for (Client &client : m_connections) { 425 for (Client &client : m_connections) {
diff --git a/common/mail/threadindexer.cpp b/common/mail/threadindexer.cpp
index 6f2933c..d91ab5f 100644
--- a/common/mail/threadindexer.cpp
+++ b/common/mail/threadindexer.cpp
@@ -101,9 +101,12 @@ void ThreadIndexer::updateThreadingIndex(const QByteArray &identifier, const App
101 thread = index().secondaryLookup<Mail::MessageId, Mail::ThreadId>(parentMessageId); 101 thread = index().secondaryLookup<Mail::MessageId, Mail::ThreadId>(parentMessageId);
102 SinkTrace() << "Found parent: " << thread; 102 SinkTrace() << "Found parent: " << thread;
103 } 103 }
104
104 if (thread.isEmpty()) { 105 if (thread.isEmpty()) {
105 //Try to lookup the thread by subject: 106 //Try to lookup the thread by subject if not empty
106 thread = index().secondaryLookup<Mail::Subject, Mail::ThreadId>(normalizedSubject); 107 if ( !normalizedSubject.isEmpty()) {
108 thread = index().secondaryLookup<Mail::Subject, Mail::ThreadId>(normalizedSubject);
109 }
107 if (thread.isEmpty()) { 110 if (thread.isEmpty()) {
108 thread << QUuid::createUuid().toByteArray(); 111 thread << QUuid::createUuid().toByteArray();
109 SinkTrace() << "Created a new thread: " << thread; 112 SinkTrace() << "Created a new thread: " << thread;
@@ -121,7 +124,9 @@ void ThreadIndexer::updateThreadingIndex(const QByteArray &identifier, const App
121 } 124 }
122 index().index<Mail::MessageId, Mail::ThreadId>(messageId, thread.first(), transaction); 125 index().index<Mail::MessageId, Mail::ThreadId>(messageId, thread.first(), transaction);
123 index().index<Mail::ThreadId, Mail::MessageId>(thread.first(), messageId, transaction); 126 index().index<Mail::ThreadId, Mail::MessageId>(thread.first(), messageId, transaction);
124 index().index<Mail::Subject, Mail::ThreadId>(normalizedSubject, thread.first(), transaction); 127 if (!normalizedSubject.isEmpty()) {
128 index().index<Mail::Subject, Mail::ThreadId>(normalizedSubject, thread.first(), transaction);
129 }
125} 130}
126 131
127 132
diff --git a/common/modelresult.cpp b/common/modelresult.cpp
index f935419..b12216b 100644
--- a/common/modelresult.cpp
+++ b/common/modelresult.cpp
@@ -24,12 +24,20 @@
24#include <QPointer> 24#include <QPointer>
25 25
26#include "log.h" 26#include "log.h"
27#include "notifier.h"
28#include "notification.h"
29
30using namespace Sink;
31
32static uint getInternalIdentifer(const QByteArray &resourceId, const QByteArray &entityId)
33{
34 return qHash(resourceId + entityId);
35}
27 36
28static uint qHash(const Sink::ApplicationDomain::ApplicationDomainType &type) 37static uint qHash(const Sink::ApplicationDomain::ApplicationDomainType &type)
29{ 38{
30 // Q_ASSERT(!type.resourceInstanceIdentifier().isEmpty());
31 Q_ASSERT(!type.identifier().isEmpty()); 39 Q_ASSERT(!type.identifier().isEmpty());
32 return qHash(type.resourceInstanceIdentifier() + type.identifier()); 40 return getInternalIdentifer(type.resourceInstanceIdentifier(), type.identifier());
33} 41}
34 42
35static qint64 getIdentifier(const QModelIndex &idx) 43static qint64 getIdentifier(const QModelIndex &idx)
@@ -44,6 +52,86 @@ template <class T, class Ptr>
44ModelResult<T, Ptr>::ModelResult(const Sink::Query &query, const QList<QByteArray> &propertyColumns, const Sink::Log::Context &ctx) 52ModelResult<T, Ptr>::ModelResult(const Sink::Query &query, const QList<QByteArray> &propertyColumns, const Sink::Log::Context &ctx)
45 : QAbstractItemModel(), mLogCtx(ctx.subContext("modelresult")), mPropertyColumns(propertyColumns), mQuery(query) 53 : QAbstractItemModel(), mLogCtx(ctx.subContext("modelresult")), mPropertyColumns(propertyColumns), mQuery(query)
46{ 54{
55 if (query.flags().testFlag(Sink::Query::UpdateStatus)) {
56 Sink::Query resourceQuery;
57 resourceQuery.setFilter(query.getResourceFilter());
58 mNotifier.reset(new Sink::Notifier{resourceQuery});
59 mNotifier->registerHandler([this](const Notification &notification) {
60 switch (notification.type) {
61 case Notification::Status:
62 case Notification::Warning:
63 case Notification::Error:
64 case Notification::Info:
65 case Notification::Progress:
66 //These are the notifications we care about
67 break;
68 default:
69 //We're not interested
70 return;
71 };
72 if (notification.resource.isEmpty() || notification.entities.isEmpty()) {
73 return;
74 }
75
76 QVector<qint64> idList;
77 for (const auto &entity : notification.entities) {
78 auto id = getInternalIdentifer(notification.resource, entity);
79 if (mEntities.contains(id)) {
80 idList << id;
81 }
82 }
83
84 if (idList.isEmpty()) {
85 //We don't have this entity in our model
86 return;
87 }
88 const int newStatus = [&] {
89 if (notification.type == Notification::Warning || notification.type == Notification::Error) {
90 return ApplicationDomain::SyncStatus::SyncError;
91 }
92 if (notification.type == Notification::Info) {
93 switch (notification.code) {
94 case ApplicationDomain::SyncInProgress:
95 return ApplicationDomain::SyncInProgress;
96 case ApplicationDomain::SyncSuccess:
97 return ApplicationDomain::SyncSuccess;
98 case ApplicationDomain::SyncError:
99 return ApplicationDomain::SyncError;
100 case ApplicationDomain::NoSyncStatus:
101 break;
102 }
103 return ApplicationDomain::NoSyncStatus;
104 }
105 if (notification.type == Notification::Progress) {
106 return ApplicationDomain::SyncStatus::SyncInProgress;
107 }
108 return ApplicationDomain::NoSyncStatus;
109 }();
110
111 for (const auto id : idList) {
112 const auto oldStatus = mEntityStatus.value(id);
113 QVector<int> changedRoles;
114 if (oldStatus != newStatus) {
115 SinkTraceCtx(mLogCtx) << "Status changed for entity:" << newStatus << ", id: " << id;
116 mEntityStatus.insert(id, newStatus);
117 changedRoles << StatusRole;
118 }
119
120 if (notification.type == Notification::Progress) {
121 changedRoles << ProgressRole;
122 } else if (notification.type == Notification::Warning || notification.type == Notification::Error) {
123 changedRoles << WarningRole;
124 }
125
126 if (!changedRoles.isEmpty()) {
127 const auto idx = createIndexFromId(id);
128 SinkTraceCtx(mLogCtx) << "Index changed:" << idx << changedRoles;
129 //We don't emit the changedRoles because the consuming model likely remaps the role anyways and would then need to translate dataChanged signals as well.
130 emit dataChanged(idx, idx);
131 }
132 }
133 });
134 }
47} 135}
48 136
49template <class T, class Ptr> 137template <class T, class Ptr>
@@ -60,7 +148,7 @@ qint64 ModelResult<T, Ptr>::parentId(const Ptr &value)
60 if (!mQuery.parentProperty().isEmpty()) { 148 if (!mQuery.parentProperty().isEmpty()) {
61 const auto identifier = value->getProperty(mQuery.parentProperty()).toByteArray(); 149 const auto identifier = value->getProperty(mQuery.parentProperty()).toByteArray();
62 if (!identifier.isEmpty()) { 150 if (!identifier.isEmpty()) {
63 return qHash(T(value->resourceInstanceIdentifier(), identifier, 0, QSharedPointer<Sink::ApplicationDomain::BufferAdaptor>())); 151 return getInternalIdentifer(value->resourceInstanceIdentifier(), identifier);
64 } 152 }
65 } 153 }
66 return 0; 154 return 0;
@@ -106,6 +194,13 @@ QVariant ModelResult<T, Ptr>::data(const QModelIndex &index, int role) const
106 if (role == ChildrenFetchedRole) { 194 if (role == ChildrenFetchedRole) {
107 return childrenFetched(index); 195 return childrenFetched(index);
108 } 196 }
197 if (role == StatusRole) {
198 auto it = mEntityStatus.constFind(index.internalId());
199 if (it != mEntityStatus.constEnd()) {
200 return *it;
201 }
202 return {};
203 }
109 if (role == Qt::DisplayRole && index.isValid()) { 204 if (role == Qt::DisplayRole && index.isValid()) {
110 if (index.column() < mPropertyColumns.size()) { 205 if (index.column() < mPropertyColumns.size()) {
111 Q_ASSERT(mEntities.contains(index.internalId())); 206 Q_ASSERT(mEntities.contains(index.internalId()));
@@ -296,15 +391,15 @@ void ModelResult<T, Ptr>::setEmitter(const typename Sink::ResultEmitter<Ptr>::Pt
296 emitter->onInitialResultSetComplete([this, guard](const Ptr &parent, bool fetchedAll) { 391 emitter->onInitialResultSetComplete([this, guard](const Ptr &parent, bool fetchedAll) {
297 SinkTraceCtx(mLogCtx) << "Initial result set complete. Fetched all: " << fetchedAll; 392 SinkTraceCtx(mLogCtx) << "Initial result set complete. Fetched all: " << fetchedAll;
298 Q_ASSERT(guard); 393 Q_ASSERT(guard);
299 threadBoundary.callInMainThread([=]() { 394 Q_ASSERT(QThread::currentThread() == this->thread());
300 const qint64 parentId = parent ? qHash(*parent) : 0; 395
301 const auto parentIndex = createIndexFromId(parentId); 396 const qint64 parentId = parent ? qHash(*parent) : 0;
302 mEntityChildrenFetchComplete.insert(parentId); 397 const auto parentIndex = createIndexFromId(parentId);
303 if (fetchedAll) { 398 mEntityChildrenFetchComplete.insert(parentId);
304 mEntityAllChildrenFetched.insert(parentId); 399 if (fetchedAll) {
305 } 400 mEntityAllChildrenFetched.insert(parentId);
306 emit dataChanged(parentIndex, parentIndex, QVector<int>() << ChildrenFetchedRole); 401 }
307 }); 402 emit dataChanged(parentIndex, parentIndex, QVector<int>() << ChildrenFetchedRole);
308 }); 403 });
309 mEmitter = emitter; 404 mEmitter = emitter;
310} 405}
diff --git a/common/modelresult.h b/common/modelresult.h
index f30a8e1..cc263cf 100644
--- a/common/modelresult.h
+++ b/common/modelresult.h
@@ -30,15 +30,23 @@
30#include "resultprovider.h" 30#include "resultprovider.h"
31#include "threadboundary.h" 31#include "threadboundary.h"
32 32
33namespace Sink {
34class Notifier;
35}
36
33template <class T, class Ptr> 37template <class T, class Ptr>
34class ModelResult : public QAbstractItemModel 38class ModelResult : public QAbstractItemModel
35{ 39{
36public: 40public:
41 //Update the copy in store.h as well if you modify this
37 enum Roles 42 enum Roles
38 { 43 {
39 DomainObjectRole = Qt::UserRole + 1, 44 DomainObjectRole = Qt::UserRole + 1,
40 ChildrenFetchedRole, 45 ChildrenFetchedRole,
41 DomainObjectBaseRole 46 DomainObjectBaseRole,
47 StatusRole, //ApplicationDomain::SyncStatus
48 WarningRole, //ApplicationDomain::Warning, only if status == warning || status == error
49 ProgressRole //ApplicationDomain::Progress
42 }; 50 };
43 51
44 ModelResult(const Sink::Query &query, const QList<QByteArray> &propertyColumns, const Sink::Log::Context &); 52 ModelResult(const Sink::Query &query, const QList<QByteArray> &propertyColumns, const Sink::Log::Context &);
@@ -77,9 +85,11 @@ private:
77 QSet<qint64 /* entity id */> mEntityChildrenFetched; 85 QSet<qint64 /* entity id */> mEntityChildrenFetched;
78 QSet<qint64 /* entity id */> mEntityChildrenFetchComplete; 86 QSet<qint64 /* entity id */> mEntityChildrenFetchComplete;
79 QSet<qint64 /* entity id */> mEntityAllChildrenFetched; 87 QSet<qint64 /* entity id */> mEntityAllChildrenFetched;
88 QMap<qint64 /* entity id */, int /* Status */> mEntityStatus;
80 QList<QByteArray> mPropertyColumns; 89 QList<QByteArray> mPropertyColumns;
81 Sink::Query mQuery; 90 Sink::Query mQuery;
82 std::function<void(const Ptr &)> loadEntities; 91 std::function<void(const Ptr &)> loadEntities;
83 typename Sink::ResultEmitter<Ptr>::Ptr mEmitter; 92 typename Sink::ResultEmitter<Ptr>::Ptr mEmitter;
84 async::ThreadBoundary threadBoundary; 93 async::ThreadBoundary threadBoundary;
94 QScopedPointer<Sink::Notifier> mNotifier;
85}; 95};
diff --git a/common/notification.cpp b/common/notification.cpp
index b399d50..e688b6d 100644
--- a/common/notification.cpp
+++ b/common/notification.cpp
@@ -19,8 +19,37 @@
19 */ 19 */
20#include "notification.h" 20#include "notification.h"
21 21
22using namespace Sink;
23
24static QByteArray name(int type)
25{
26 switch (type) {
27 case Notification::Shutdown:
28 return "shutdown";
29 case Notification::Status:
30 return "status";
31 case Notification::Info:
32 return "info";
33 case Notification::Warning:
34 return "warning";
35 case Notification::Error:
36 return "error";
37 case Notification::Progress:
38 return "progress";
39 case Notification::Inspection:
40 return "inspection";
41 case Notification::RevisionUpdate:
42 return "revisionupdate";
43 case Notification::FlushCompletion:
44 return "flushcompletion";
45 }
46 return "Unknown:" + QByteArray::number(type);
47}
48
22QDebug operator<<(QDebug dbg, const Sink::Notification &n) 49QDebug operator<<(QDebug dbg, const Sink::Notification &n)
23{ 50{
24 dbg << "Notification(Id: " << n.id << ", Type: " << n.type << ", Code: " << n.code << ", Message: " << n.message << ")"; 51 dbg << "Notification(Type: " << name(n.type) << "Id, : " << n.id << ", Code: ";
52 dbg << n.code;
53 dbg << ", Message: " << n.message << ", Entities: " << n.entities << ")";
25 return dbg.space(); 54 return dbg.space();
26} 55}
diff --git a/common/notification.h b/common/notification.h
index 8224f2a..f5379fd 100644
--- a/common/notification.h
+++ b/common/notification.h
@@ -34,22 +34,29 @@ public:
34 enum NoticationType { 34 enum NoticationType {
35 Shutdown, 35 Shutdown,
36 Status, 36 Status,
37 Info,
37 Warning, 38 Warning,
39 Error,
38 Progress, 40 Progress,
39 Inspection, 41 Inspection,
40 RevisionUpdate, 42 RevisionUpdate,
41 FlushCompletion 43 FlushCompletion
42 }; 44 };
45 /**
46 * Used as code for Inspection type notifications
47 */
43 enum InspectionCode { 48 enum InspectionCode {
44 Success = 0, 49 Success = 0,
45 Failure 50 Failure
46 }; 51 };
47 52
48 QByteArray id; 53 QByteArray id;
54 QByteArrayList entities;
49 int type = 0; 55 int type = 0;
50 QString message; 56 QString message;
51 //A return code. Zero typically indicates success. 57 //A return code. Zero typically indicates success.
52 int code = 0; 58 int code = 0;
59 QByteArray resource;
53}; 60};
54} 61}
55 62
diff --git a/common/notifier.cpp b/common/notifier.cpp
index 53db5be..f52e28b 100644
--- a/common/notifier.cpp
+++ b/common/notifier.cpp
@@ -24,6 +24,8 @@
24 24
25#include "resourceaccess.h" 25#include "resourceaccess.h"
26#include "resourceconfig.h" 26#include "resourceconfig.h"
27#include "query.h"
28#include "facadefactory.h"
27#include "log.h" 29#include "log.h"
28 30
29using namespace Sink; 31using namespace Sink;
@@ -34,37 +36,64 @@ public:
34 Private() : context(new QObject) 36 Private() : context(new QObject)
35 { 37 {
36 } 38 }
39
40 void listenForNotifications(const QSharedPointer<ResourceAccess> &access)
41 {
42 QObject::connect(access.data(), &ResourceAccess::notification, &context, [this](const Notification &notification) {
43 for (const auto &handler : handler) {
44 handler(notification);
45 }
46 });
47 resourceAccess << access;
48 }
49
37 QList<QSharedPointer<ResourceAccess>> resourceAccess; 50 QList<QSharedPointer<ResourceAccess>> resourceAccess;
38 QList<std::function<void(const Notification &)>> handler; 51 QList<std::function<void(const Notification &)>> handler;
39 QSharedPointer<QObject> context; 52 QObject context;
40}; 53};
41 54
42Notifier::Notifier(const QSharedPointer<ResourceAccess> &resourceAccess) : d(new Sink::Notifier::Private) 55Notifier::Notifier(const QSharedPointer<ResourceAccess> &resourceAccess) : d(new Sink::Notifier::Private)
43{ 56{
44 QObject::connect(resourceAccess.data(), &ResourceAccess::notification, d->context.data(), [this](const Notification &notification) { 57 d->listenForNotifications(resourceAccess);
45 for (const auto &handler : d->handler) {
46 handler(notification);
47 }
48 });
49 d->resourceAccess << resourceAccess;
50} 58}
51 59
52Notifier::Notifier(const QByteArray &instanceIdentifier, const QByteArray &resourceType) : d(new Sink::Notifier::Private) 60Notifier::Notifier(const QByteArray &instanceIdentifier, const QByteArray &resourceType) : d(new Sink::Notifier::Private)
53{ 61{
54 auto resourceAccess = Sink::ResourceAccess::Ptr::create(instanceIdentifier, resourceType); 62 auto resourceAccess = Sink::ResourceAccessFactory::instance().getAccess(instanceIdentifier, resourceType);
55 resourceAccess->open(); 63 resourceAccess->open();
56 QObject::connect(resourceAccess.data(), &ResourceAccess::notification, d->context.data(), [this](const Notification &notification) { 64 d->listenForNotifications(resourceAccess);
57 for (const auto &handler : d->handler) {
58 handler(notification);
59 }
60 });
61 d->resourceAccess << resourceAccess;
62} 65}
63 66
64Notifier::Notifier(const QByteArray &instanceIdentifier) : Notifier(instanceIdentifier, ResourceConfig::getResourceType(instanceIdentifier)) 67Notifier::Notifier(const QByteArray &instanceIdentifier) : Notifier(instanceIdentifier, ResourceConfig::getResourceType(instanceIdentifier))
65{ 68{
66} 69}
67 70
71Notifier::Notifier(const Sink::Query &resourceQuery) : d(new Sink::Notifier::Private)
72{
73 Sink::Log::Context resourceCtx{"notifier"};
74 auto facade = FacadeFactory::instance().getFacade<ApplicationDomain::SinkResource>();
75 Q_ASSERT(facade);
76
77 auto result = facade->load(resourceQuery, resourceCtx);
78 auto emitter = result.second;
79 emitter->onAdded([=](const ApplicationDomain::SinkResource::Ptr &resource) {
80 auto resourceAccess = Sink::ResourceAccessFactory::instance().getAccess(resource->identifier(), ResourceConfig::getResourceType(resource->identifier()));
81 resourceAccess->open();
82 d->listenForNotifications(resourceAccess);
83 });
84 emitter->onModified([](const ApplicationDomain::SinkResource::Ptr &) {
85 });
86 emitter->onRemoved([](const ApplicationDomain::SinkResource::Ptr &) {
87 });
88 emitter->onInitialResultSetComplete([](const ApplicationDomain::SinkResource::Ptr &, bool) {
89 });
90 emitter->onComplete([resourceCtx]() {
91 SinkTraceCtx(resourceCtx) << "Resource query complete";
92 });
93 emitter->fetch({});
94 result.first.exec();
95}
96
68void Notifier::registerHandler(std::function<void(const Notification &)> handler) 97void Notifier::registerHandler(std::function<void(const Notification &)> handler)
69{ 98{
70 d->handler << handler; 99 d->handler << handler;
diff --git a/common/notifier.h b/common/notifier.h
index 290458a..b5d3dfa 100644
--- a/common/notifier.h
+++ b/common/notifier.h
@@ -23,14 +23,14 @@
23#include "sink_export.h" 23#include "sink_export.h"
24#include <QByteArray> 24#include <QByteArray>
25#include <QSharedPointer> 25#include <QSharedPointer>
26 26#include <functional>
27#include <KAsync/Async>
28 27
29class QAbstractItemModel; 28class QAbstractItemModel;
30 29
31namespace Sink { 30namespace Sink {
32class ResourceAccess; 31class ResourceAccess;
33class Notification; 32class Notification;
33class Query;
34 34
35class SINK_EXPORT Notifier 35class SINK_EXPORT Notifier
36{ 36{
@@ -38,6 +38,7 @@ public:
38 Notifier(const QSharedPointer<ResourceAccess> &resourceAccess); 38 Notifier(const QSharedPointer<ResourceAccess> &resourceAccess);
39 Notifier(const QByteArray &resourceInstanceIdentifier); 39 Notifier(const QByteArray &resourceInstanceIdentifier);
40 Notifier(const QByteArray &resourceInstanceIdentifier, const QByteArray &resourceType); 40 Notifier(const QByteArray &resourceInstanceIdentifier, const QByteArray &resourceType);
41 Notifier(const Sink::Query &resourceQuery);
41 void registerHandler(std::function<void(const Notification &)>); 42 void registerHandler(std::function<void(const Notification &)>);
42 43
43private: 44private:
diff --git a/common/pipeline.cpp b/common/pipeline.cpp
index 887b6b3..019784e 100644
--- a/common/pipeline.cpp
+++ b/common/pipeline.cpp
@@ -175,13 +175,14 @@ KAsync::Job<qint64> Pipeline::newEntity(void const *command, size_t size)
175 auto o = Sink::ApplicationDomain::ApplicationDomainType{d->resourceContext.instanceId(), key, revision, memoryAdaptor}; 175 auto o = Sink::ApplicationDomain::ApplicationDomainType{d->resourceContext.instanceId(), key, revision, memoryAdaptor};
176 o.setChangedProperties(o.availableProperties().toSet()); 176 o.setChangedProperties(o.availableProperties().toSet());
177 177
178 auto preprocess = [&, this](ApplicationDomain::ApplicationDomainType &newEntity) { 178 auto newEntity = *ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<ApplicationDomain::ApplicationDomainType>(o, o.availableProperties());
179 foreach (const auto &processor, d->processors[bufferType]) { 179 newEntity.setChangedProperties(newEntity.availableProperties().toSet());
180 processor->newEntity(newEntity); 180
181 } 181 foreach (const auto &processor, d->processors[bufferType]) {
182 }; 182 processor->newEntity(newEntity);
183 }
183 184
184 if (!d->entityStore.add(bufferType, o, replayToSource, preprocess)) { 185 if (!d->entityStore.add(bufferType, o, replayToSource)) {
185 return KAsync::error<qint64>(0); 186 return KAsync::error<qint64>(0);
186 } 187 }
187 188
@@ -195,6 +196,11 @@ struct CreateHelper {
195 } 196 }
196}; 197};
197 198
199static KAsync::Job<void> create(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &newEntity)
200{
201 return TypeHelper<CreateHelper>{type}.operator()<KAsync::Job<void>, const ApplicationDomain::ApplicationDomainType&>(newEntity);
202}
203
198KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size) 204KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size)
199{ 205{
200 d->transactionItemCount++; 206 d->transactionItemCount++;
@@ -248,65 +254,71 @@ KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size)
248 deletions = BufferUtils::fromVector(*modifyEntity->deletions()); 254 deletions = BufferUtils::fromVector(*modifyEntity->deletions());
249 } 255 }
250 256
251 if (modifyEntity->targetResource()) { 257 const auto current = d->entityStore.readLatest(bufferType, diff.identifier());
252 auto isMove = modifyEntity->removeEntity(); 258 if (current.identifier().isEmpty()) {
253 auto targetResource = BufferUtils::extractBuffer(modifyEntity->targetResource()); 259 SinkWarningCtx(d->logCtx) << "Failed to read current version: " << diff.identifier();
254 auto changeset = diff.changedProperties(); 260 return KAsync::error<qint64>(0);
255 const auto current = d->entityStore.readLatest(bufferType, diff.identifier()); 261 }
256 if (current.identifier().isEmpty()) {
257 SinkWarningCtx(d->logCtx) << "Failed to read current version: " << diff.identifier();
258 return KAsync::error<qint64>(0);
259 }
260 262
261 auto newEntity = *ApplicationDomain::ApplicationDomainType::getInMemoryCopy<ApplicationDomain::ApplicationDomainType>(current, current.availableProperties()); 263 auto newEntity = d->entityStore.applyDiff(bufferType, current, diff, deletions);
262 264
263 // Apply diff 265 bool isMove = false;
264 for (const auto &property : changeset) { 266 if (modifyEntity->targetResource()) {
265 const auto value = diff.getProperty(property); 267 isMove = modifyEntity->removeEntity();
266 if (value.isValid()) { 268 newEntity.setResource(BufferUtils::extractBuffer(modifyEntity->targetResource()));
267 newEntity.setProperty(property, value); 269 }
268 }
269 }
270 270
271 // Remove deletions 271 foreach (const auto &processor, d->processors[bufferType]) {
272 for (const auto &property : deletions) { 272 bool exitLoop = false;
273 newEntity.setProperty(property, QVariant()); 273 const auto result = processor->processModification(Preprocessor::Modification, current, newEntity);
274 switch (result.action) {
275 case Preprocessor::MoveToResource:
276 isMove = true;
277 exitLoop = true;
278 break;
279 case Preprocessor::CopyToResource:
280 isMove = true;
281 exitLoop = true;
282 break;
283 case Preprocessor::DropModification:
284 SinkTraceCtx(d->logCtx) << "Dropping modification";
285 return KAsync::error<qint64>(0);
286 default:
287 break;
274 } 288 }
275 newEntity.setResource(targetResource); 289 if (exitLoop) {
276 newEntity.setChangedProperties(newEntity.availableProperties().toSet()); 290 break;
291 }
292 }
277 293
278 SinkTraceCtx(d->logCtx) << "Moving entity to new resource " << newEntity.identifier() << newEntity.resourceInstanceIdentifier() << targetResource; 294 //The entity is either being copied or moved
279 auto job = TypeHelper<CreateHelper>{bufferType}.operator()<KAsync::Job<void>, ApplicationDomain::ApplicationDomainType&>(newEntity); 295 if (newEntity.resourceInstanceIdentifier() != d->resourceContext.resourceInstanceIdentifier) {
280 job = job.then([this, current, isMove, targetResource, bufferType](const KAsync::Error &error) { 296 SinkTraceCtx(d->logCtx) << "Moving entity to new resource " << newEntity.identifier() << newEntity.resourceInstanceIdentifier();
281 if (!error) { 297 newEntity.setChangedProperties(newEntity.availableProperties().toSet());
282 SinkTraceCtx(d->logCtx) << "Move of " << current.identifier() << "was successfull"; 298 return create(bufferType, newEntity)
283 if (isMove) { 299 .then([=](const KAsync::Error &error) {
284 startTransaction(); 300 if (!error) {
285 flatbuffers::FlatBufferBuilder fbb; 301 SinkTraceCtx(d->logCtx) << "Move of " << current.identifier() << "was successfull";
286 auto entityId = fbb.CreateString(current.identifier()); 302 if (isMove) {
287 auto type = fbb.CreateString(bufferType); 303 flatbuffers::FlatBufferBuilder fbb;
288 auto location = Sink::Commands::CreateDeleteEntity(fbb, current.revision(), entityId, type, true); 304 auto entityId = fbb.CreateString(current.identifier());
289 Sink::Commands::FinishDeleteEntityBuffer(fbb, location); 305 auto type = fbb.CreateString(bufferType);
290 const auto data = BufferUtils::extractBuffer(fbb); 306 auto location = Sink::Commands::CreateDeleteEntity(fbb, current.revision(), entityId, type, true);
291 deletedEntity(data, data.size()).exec(); 307 Sink::Commands::FinishDeleteEntityBuffer(fbb, location);
292 commit(); 308 const auto data = BufferUtils::extractBuffer(fbb);
309 deletedEntity(data, data.size()).exec();
310 }
311 } else {
312 SinkErrorCtx(d->logCtx) << "Failed to move entity " << newEntity.identifier() << " to resource " << newEntity.resourceInstanceIdentifier();
293 } 313 }
294 } else { 314 })
295 SinkErrorCtx(d->logCtx) << "Failed to move entity " << targetResource << " to resource " << current.identifier(); 315 .then([this] {
296 } 316 return d->entityStore.maxRevision();
297 }); 317 });
298 job.exec();
299 return KAsync::value<qint64>(0);
300 } 318 }
301 319
302 auto preprocess = [&, this](const ApplicationDomain::ApplicationDomainType &oldEntity, ApplicationDomain::ApplicationDomainType &newEntity) {
303 foreach (const auto &processor, d->processors[bufferType]) {
304 processor->modifiedEntity(oldEntity, newEntity);
305 }
306 };
307
308 d->revisionChanged = true; 320 d->revisionChanged = true;
309 if (!d->entityStore.modify(bufferType, diff, deletions, replayToSource, preprocess)) { 321 if (!d->entityStore.modify(bufferType, current, newEntity, replayToSource)) {
310 return KAsync::error<qint64>(0); 322 return KAsync::error<qint64>(0);
311 } 323 }
312 324
@@ -331,14 +343,14 @@ KAsync::Job<qint64> Pipeline::deletedEntity(void const *command, size_t size)
331 const QByteArray key = QByteArray(reinterpret_cast<char const *>(deleteEntity->entityId()->Data()), deleteEntity->entityId()->size()); 343 const QByteArray key = QByteArray(reinterpret_cast<char const *>(deleteEntity->entityId()->Data()), deleteEntity->entityId()->size());
332 SinkTraceCtx(d->logCtx) << "Deleted Entity. Type: " << bufferType << "uid: "<< key << " replayToSource: " << replayToSource; 344 SinkTraceCtx(d->logCtx) << "Deleted Entity. Type: " << bufferType << "uid: "<< key << " replayToSource: " << replayToSource;
333 345
334 auto preprocess = [&, this](const ApplicationDomain::ApplicationDomainType &oldEntity) { 346 const auto current = d->entityStore.readLatest(bufferType, key);
335 foreach (const auto &processor, d->processors[bufferType]) { 347
336 processor->deletedEntity(oldEntity); 348 foreach (const auto &processor, d->processors[bufferType]) {
337 } 349 processor->deletedEntity(current);
338 }; 350 }
339 351
340 d->revisionChanged = true; 352 d->revisionChanged = true;
341 if (!d->entityStore.remove(bufferType, key, replayToSource, preprocess)) { 353 if (!d->entityStore.remove(bufferType, current, replayToSource)) {
342 return KAsync::error<qint64>(0); 354 return KAsync::error<qint64>(0);
343 } 355 }
344 356
@@ -385,6 +397,39 @@ void Preprocessor::finalizeBatch()
385{ 397{
386} 398}
387 399
400void Preprocessor::newEntity(ApplicationDomain::ApplicationDomainType &newEntity)
401{
402
403}
404
405void Preprocessor::modifiedEntity(const ApplicationDomain::ApplicationDomainType &oldEntity, ApplicationDomain::ApplicationDomainType &newEntity)
406{
407
408}
409
410void Preprocessor::deletedEntity(const ApplicationDomain::ApplicationDomainType &oldEntity)
411{
412
413}
414
415Preprocessor::Result Preprocessor::processModification(Type type, const ApplicationDomain::ApplicationDomainType &current, ApplicationDomain::ApplicationDomainType &diff)
416{
417 switch(type) {
418 case Creation:
419 newEntity(diff);
420 return {NoAction};
421 case Modification:
422 modifiedEntity(current, diff);
423 return {NoAction};
424 case Deletion:
425 deletedEntity(current);
426 return {NoAction};
427 default:
428 break;
429 }
430 return {NoAction};
431}
432
388QByteArray Preprocessor::resourceInstanceIdentifier() const 433QByteArray Preprocessor::resourceInstanceIdentifier() const
389{ 434{
390 return d->resourceInstanceIdentifier; 435 return d->resourceInstanceIdentifier;
diff --git a/common/pipeline.h b/common/pipeline.h
index c6dc5fe..11d52fd 100644
--- a/common/pipeline.h
+++ b/common/pipeline.h
@@ -77,10 +77,28 @@ public:
77 Preprocessor(); 77 Preprocessor();
78 virtual ~Preprocessor(); 78 virtual ~Preprocessor();
79 79
80 enum Action {
81 NoAction,
82 MoveToResource,
83 CopyToResource,
84 DropModification,
85 DeleteEntity
86 };
87
88 enum Type {
89 Creation,
90 Modification,
91 Deletion
92 };
93 struct Result {
94 Action action;
95 };
96
80 virtual void startBatch(); 97 virtual void startBatch();
81 virtual void newEntity(ApplicationDomain::ApplicationDomainType &newEntity) {}; 98 virtual void newEntity(ApplicationDomain::ApplicationDomainType &newEntity);
82 virtual void modifiedEntity(const ApplicationDomain::ApplicationDomainType &oldEntity, ApplicationDomain::ApplicationDomainType &newEntity) {}; 99 virtual void modifiedEntity(const ApplicationDomain::ApplicationDomainType &oldEntity, ApplicationDomain::ApplicationDomainType &newEntity);
83 virtual void deletedEntity(const ApplicationDomain::ApplicationDomainType &oldEntity) {}; 100 virtual void deletedEntity(const ApplicationDomain::ApplicationDomainType &oldEntity);
101 virtual Result processModification(Type type, const ApplicationDomain::ApplicationDomainType &current, ApplicationDomain::ApplicationDomainType &diff);
84 virtual void finalizeBatch(); 102 virtual void finalizeBatch();
85 103
86 void setup(const QByteArray &resourceType, const QByteArray &resourceInstanceIdentifier, Pipeline *, Storage::EntityStore *entityStore); 104 void setup(const QByteArray &resourceType, const QByteArray &resourceInstanceIdentifier, Pipeline *, Storage::EntityStore *entityStore);
diff --git a/common/propertymapper.cpp b/common/propertymapper.cpp
index 4d45644..c72cf31 100644
--- a/common/propertymapper.cpp
+++ b/common/propertymapper.cpp
@@ -22,6 +22,7 @@
22#include "applicationdomaintype.h" 22#include "applicationdomaintype.h"
23#include <QDateTime> 23#include <QDateTime>
24#include "mail_generated.h" 24#include "mail_generated.h"
25#include "contact_generated.h"
25 26
26template <> 27template <>
27flatbuffers::uoffset_t variantToProperty<QString>(const QVariant &property, flatbuffers::FlatBufferBuilder &fbb) 28flatbuffers::uoffset_t variantToProperty<QString>(const QVariant &property, flatbuffers::FlatBufferBuilder &fbb)
@@ -110,6 +111,21 @@ flatbuffers::uoffset_t variantToProperty<QList<Sink::ApplicationDomain::Mail::Co
110 return 0; 111 return 0;
111} 112}
112 113
114template <>
115flatbuffers::uoffset_t variantToProperty<QList<Sink::ApplicationDomain::Contact::Email>>(const QVariant &property, flatbuffers::FlatBufferBuilder &fbb)
116{
117 if (property.isValid()) {
118 const auto list = property.value<QList<Sink::ApplicationDomain::Contact::Email>>();
119 std::vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::ContactEmail>> vector;
120 for (const auto &value : list) {
121 auto offset = Sink::ApplicationDomain::Buffer::CreateContactEmailDirect(fbb, value.type, value.email.toUtf8().constData()).o;
122 vector.push_back(offset);
123 }
124 return fbb.CreateVector(vector).o;
125 }
126 return 0;
127}
128
113 129
114QString propertyToString(const flatbuffers::String *property) 130QString propertyToString(const flatbuffers::String *property)
115{ 131{
@@ -217,6 +233,20 @@ QVariant propertyToVariant<QList<Sink::ApplicationDomain::Mail::Contact>>(const
217} 233}
218 234
219template <> 235template <>
236QVariant propertyToVariant<QList<Sink::ApplicationDomain::Contact::Email>>(const flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::ContactEmail>> *property)
237{
238 if (property) {
239 QList<Sink::ApplicationDomain::Contact::Email> list;
240 for (auto it = property->begin(); it != property->end();) {
241 list << Sink::ApplicationDomain::Contact::Email{static_cast<Sink::ApplicationDomain::Contact::Email::Type>(it->type()), propertyToString(it->email())};
242 it.operator++();
243 }
244 return QVariant::fromValue(list);
245 }
246 return QVariant();
247}
248
249template <>
220QVariant propertyToVariant<bool>(uint8_t property) 250QVariant propertyToVariant<bool>(uint8_t property)
221{ 251{
222 return static_cast<bool>(property); 252 return static_cast<bool>(property);
diff --git a/common/propertymapper.h b/common/propertymapper.h
index 70491a1..9ea0b73 100644
--- a/common/propertymapper.h
+++ b/common/propertymapper.h
@@ -29,6 +29,7 @@ namespace Sink {
29namespace ApplicationDomain { 29namespace ApplicationDomain {
30namespace Buffer { 30namespace Buffer {
31 struct MailContact; 31 struct MailContact;
32 struct ContactEmail;
32} 33}
33} 34}
34} 35}
@@ -54,6 +55,8 @@ template <typename T>
54QVariant SINK_EXPORT propertyToVariant(const Sink::ApplicationDomain::Buffer::MailContact *); 55QVariant SINK_EXPORT propertyToVariant(const Sink::ApplicationDomain::Buffer::MailContact *);
55template <typename T> 56template <typename T>
56QVariant SINK_EXPORT propertyToVariant(const flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::MailContact>> *); 57QVariant SINK_EXPORT propertyToVariant(const flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::MailContact>> *);
58template <typename T>
59QVariant SINK_EXPORT propertyToVariant(const flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::ContactEmail>> *);
57 60
58/** 61/**
59 * The property mapper is a non-typesafe virtual dispatch. 62 * The property mapper is a non-typesafe virtual dispatch.
@@ -131,6 +134,12 @@ public:
131 addMapping(T::name, [f](Buffer const *buffer) -> QVariant { return propertyToVariant<typename T::Type>((buffer->*f)()); }); 134 addMapping(T::name, [f](Buffer const *buffer) -> QVariant { return propertyToVariant<typename T::Type>((buffer->*f)()); });
132 } 135 }
133 136
137 template <typename T, typename Buffer>
138 void addMapping(const flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::ContactEmail>> *(Buffer::*f)() const)
139 {
140 addMapping(T::name, [f](Buffer const *buffer) -> QVariant { return propertyToVariant<typename T::Type>((buffer->*f)()); });
141 }
142
134private: 143private:
135 QHash<QByteArray, std::function<QVariant(BufferType const *)>> mReadAccessors; 144 QHash<QByteArray, std::function<QVariant(BufferType const *)>> mReadAccessors;
136}; 145};
@@ -218,6 +227,15 @@ public:
218 }); 227 });
219 } 228 }
220 229
230 template <typename T>
231 void addMapping(void (BufferBuilder::*f)(flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Sink::ApplicationDomain::Buffer::ContactEmail>>>))
232 {
233 addMapping(T::name, [f](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function<void(BufferBuilder &)> {
234 auto offset = variantToProperty<typename T::Type>(value, fbb);
235 return [offset, f](BufferBuilder &builder) { (builder.*f)(offset); };
236 });
237 }
238
221private: 239private:
222 QHash<QByteArray, std::function<std::function<void(BufferBuilder &)>(const QVariant &, flatbuffers::FlatBufferBuilder &)>> mWriteAccessors; 240 QHash<QByteArray, std::function<std::function<void(BufferBuilder &)>(const QVariant &, flatbuffers::FlatBufferBuilder &)>> mWriteAccessors;
223}; 241};
diff --git a/common/query.h b/common/query.h
index 8e9050d..5b37cdd 100644
--- a/common/query.h
+++ b/common/query.h
@@ -300,7 +300,9 @@ public:
300 /** Leave the query running and continuously update the result set. */ 300 /** Leave the query running and continuously update the result set. */
301 LiveQuery = 1, 301 LiveQuery = 1,
302 /** Run the query synchronously. */ 302 /** Run the query synchronously. */
303 SynchronousQuery = 2 303 SynchronousQuery = 2,
304 /** Include status updates via notifications */
305 UpdateStatus = 4
304 }; 306 };
305 Q_DECLARE_FLAGS(Flags, Flag) 307 Q_DECLARE_FLAGS(Flags, Flag)
306 308
@@ -410,6 +412,11 @@ public:
410 mFlags = flags; 412 mFlags = flags;
411 } 413 }
412 414
415 Flags flags() const
416 {
417 return mFlags;
418 }
419
413 bool liveQuery() const 420 bool liveQuery() const
414 { 421 {
415 return mFlags.testFlag(LiveQuery); 422 return mFlags.testFlag(LiveQuery);
@@ -509,6 +516,31 @@ public:
509 } 516 }
510 517
511 template <typename T> 518 template <typename T>
519 SyncScope &resourceFilter(const ApplicationDomain::ApplicationDomainType &entity)
520 {
521 mResourceFilter.propertyFilter.insert(T::name, Comparator(entity.identifier()));
522 return *this;
523 }
524
525 SyncScope &resourceFilter(const QByteArray &name, const Comparator &comparator)
526 {
527 mResourceFilter.propertyFilter.insert(name, comparator);
528 return *this;
529 }
530
531 template <typename T>
532 SyncScope &resourceContainsFilter(const QVariant &value)
533 {
534 return resourceFilter(T::name, Comparator(value, Comparator::Contains));
535 }
536
537 template <typename T>
538 SyncScope &resourceFilter(const QVariant &value)
539 {
540 return resourceFilter(T::name, value);
541 }
542
543 template <typename T>
512 SyncScope &filter(const Query::Comparator &comparator) 544 SyncScope &filter(const Query::Comparator &comparator)
513 { 545 {
514 return filter(T::name, comparator); 546 return filter(T::name, comparator);
diff --git a/common/queryrunner.cpp b/common/queryrunner.cpp
index 802fc48..43f48c0 100644
--- a/common/queryrunner.cpp
+++ b/common/queryrunner.cpp
@@ -51,7 +51,7 @@ public:
51 virtual ~QueryWorker(); 51 virtual ~QueryWorker();
52 52
53 ReplayResult executeIncrementalQuery(const Sink::Query &query, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, DataStoreQuery::State::Ptr state); 53 ReplayResult executeIncrementalQuery(const Sink::Query &query, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, DataStoreQuery::State::Ptr state);
54 ReplayResult executeInitialQuery(const Sink::Query &query, const typename DomainType::Ptr &parent, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, int offset, int batchsize); 54 ReplayResult executeInitialQuery(const Sink::Query &query, const typename DomainType::Ptr &parent, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, int batchsize, DataStoreQuery::State::Ptr state);
55 55
56private: 56private:
57 void resultProviderCallback(const Sink::Query &query, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, const ResultSet::Result &result); 57 void resultProviderCallback(const Sink::Query &query, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, const ResultSet::Result &result);
@@ -72,18 +72,18 @@ QueryRunner<DomainType>::QueryRunner(const Sink::Query &query, const Sink::Resou
72 auto guardPtr = QPointer<QObject>(&guard); 72 auto guardPtr = QPointer<QObject>(&guard);
73 auto fetcher = [=](const typename DomainType::Ptr &parent) { 73 auto fetcher = [=](const typename DomainType::Ptr &parent) {
74 const QByteArray parentId = parent ? parent->identifier() : QByteArray(); 74 const QByteArray parentId = parent ? parent->identifier() : QByteArray();
75 SinkTraceCtx(mLogCtx) << "Running fetcher. Offset: " << mOffset[parentId] << " Batchsize: " << mBatchSize; 75 SinkTraceCtx(mLogCtx) << "Running fetcher. Batchsize: " << mBatchSize;
76 auto resultProvider = mResultProvider; 76 auto resultProvider = mResultProvider;
77 auto resultTransformation = mResultTransformation; 77 auto resultTransformation = mResultTransformation;
78 auto offset = mOffset[parentId];
79 auto batchSize = mBatchSize; 78 auto batchSize = mBatchSize;
80 auto resourceContext = mResourceContext; 79 auto resourceContext = mResourceContext;
81 auto logCtx = mLogCtx; 80 auto logCtx = mLogCtx;
81 auto state = mQueryState.value(parentId);
82 const bool runAsync = !query.synchronousQuery(); 82 const bool runAsync = !query.synchronousQuery();
83 //The lambda will be executed in a separate thread, so copy all arguments 83 //The lambda will be executed in a separate thread, so copy all arguments
84 async::run<ReplayResult>([resultTransformation, offset, batchSize, query, bufferType, resourceContext, resultProvider, parent, logCtx]() { 84 async::run<ReplayResult>([=]() {
85 QueryWorker<DomainType> worker(query, resourceContext, bufferType, resultTransformation, logCtx); 85 QueryWorker<DomainType> worker(query, resourceContext, bufferType, resultTransformation, logCtx);
86 return worker.executeInitialQuery(query, parent, *resultProvider, offset, batchSize); 86 return worker.executeInitialQuery(query, parent, *resultProvider, batchSize, state);
87 }, runAsync) 87 }, runAsync)
88 .then([this, parentId, query, parent, resultProvider, guardPtr](const ReplayResult &result) { 88 .then([this, parentId, query, parent, resultProvider, guardPtr](const ReplayResult &result) {
89 if (!guardPtr) { 89 if (!guardPtr) {
@@ -91,8 +91,7 @@ QueryRunner<DomainType>::QueryRunner(const Sink::Query &query, const Sink::Resou
91 return; 91 return;
92 } 92 }
93 mInitialQueryComplete = true; 93 mInitialQueryComplete = true;
94 mQueryState = result.queryState; 94 mQueryState[parentId] = result.queryState;
95 mOffset[parentId] += result.replayedEntities;
96 // Only send the revision replayed information if we're connected to the resource, there's no need to start the resource otherwise. 95 // Only send the revision replayed information if we're connected to the resource, there's no need to start the resource otherwise.
97 if (query.liveQuery()) { 96 if (query.liveQuery()) {
98 mResourceAccess->sendRevisionReplayedCommand(result.newRevision); 97 mResourceAccess->sendRevisionReplayedCommand(result.newRevision);
@@ -111,10 +110,11 @@ QueryRunner<DomainType>::QueryRunner(const Sink::Query &query, const Sink::Resou
111 Q_ASSERT(!query.synchronousQuery()); 110 Q_ASSERT(!query.synchronousQuery());
112 // Incremental updates are always loaded directly, leaving it up to the result to discard the changes if they are not interesting 111 // Incremental updates are always loaded directly, leaving it up to the result to discard the changes if they are not interesting
113 setQuery([=]() -> KAsync::Job<void> { 112 setQuery([=]() -> KAsync::Job<void> {
113 const QByteArray parentId;
114 auto resultProvider = mResultProvider; 114 auto resultProvider = mResultProvider;
115 auto resourceContext = mResourceContext; 115 auto resourceContext = mResourceContext;
116 auto logCtx = mLogCtx; 116 auto logCtx = mLogCtx;
117 auto state = mQueryState; 117 auto state = mQueryState.value(parentId);
118 if (!mInitialQueryComplete) { 118 if (!mInitialQueryComplete) {
119 SinkWarningCtx(mLogCtx) << "Can't start the incremental query before the initial query is complete"; 119 SinkWarningCtx(mLogCtx) << "Can't start the incremental query before the initial query is complete";
120 fetcher({}); 120 fetcher({});
@@ -225,7 +225,7 @@ ReplayResult QueryWorker<DomainType>::executeIncrementalQuery(const Sink::Query
225 SinkWarningCtx(mLogCtx) << "No previous query state."; 225 SinkWarningCtx(mLogCtx) << "No previous query state.";
226 return {0, 0, false, DataStoreQuery::State::Ptr{}}; 226 return {0, 0, false, DataStoreQuery::State::Ptr{}};
227 } 227 }
228 auto preparedQuery = DataStoreQuery{*state, ApplicationDomain::getTypeName<DomainType>(), entityStore}; 228 auto preparedQuery = DataStoreQuery{*state, ApplicationDomain::getTypeName<DomainType>(), entityStore, true};
229 auto resultSet = preparedQuery.update(baseRevision); 229 auto resultSet = preparedQuery.update(baseRevision);
230 SinkTraceCtx(mLogCtx) << "Filtered set retrieved. " << Log::TraceTime(time.elapsed()); 230 SinkTraceCtx(mLogCtx) << "Filtered set retrieved. " << Log::TraceTime(time.elapsed());
231 auto replayResult = resultSet.replaySet(0, 0, [this, query, &resultProvider](const ResultSet::Result &result) { 231 auto replayResult = resultSet.replaySet(0, 0, [this, query, &resultProvider](const ResultSet::Result &result) {
@@ -240,7 +240,7 @@ ReplayResult QueryWorker<DomainType>::executeIncrementalQuery(const Sink::Query
240 240
241template <class DomainType> 241template <class DomainType>
242ReplayResult QueryWorker<DomainType>::executeInitialQuery( 242ReplayResult QueryWorker<DomainType>::executeInitialQuery(
243 const Sink::Query &query, const typename DomainType::Ptr &parent, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, int offset, int batchsize) 243 const Sink::Query &query, const typename DomainType::Ptr &parent, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, int batchsize, DataStoreQuery::State::Ptr state)
244{ 244{
245 QTime time; 245 QTime time;
246 time.start(); 246 time.start();
@@ -257,11 +257,17 @@ ReplayResult QueryWorker<DomainType>::executeInitialQuery(
257 } 257 }
258 258
259 auto entityStore = EntityStore{mResourceContext, mLogCtx}; 259 auto entityStore = EntityStore{mResourceContext, mLogCtx};
260 auto preparedQuery = DataStoreQuery{modifiedQuery, ApplicationDomain::getTypeName<DomainType>(), entityStore}; 260 auto preparedQuery = [&] {
261 auto resultSet = preparedQuery.execute(); 261 if (state) {
262 return DataStoreQuery{*state, ApplicationDomain::getTypeName<DomainType>(), entityStore, false};
263 } else {
264 return DataStoreQuery{modifiedQuery, ApplicationDomain::getTypeName<DomainType>(), entityStore};
265 }
266 }();
267 auto resultSet = preparedQuery.execute();;
262 268
263 SinkTraceCtx(mLogCtx) << "Filtered set retrieved. " << Log::TraceTime(time.elapsed()); 269 SinkTraceCtx(mLogCtx) << "Filtered set retrieved." << Log::TraceTime(time.elapsed());
264 auto replayResult = resultSet.replaySet(offset, batchsize, [this, query, &resultProvider](const ResultSet::Result &result) { 270 auto replayResult = resultSet.replaySet(0, batchsize, [this, query, &resultProvider](const ResultSet::Result &result) {
265 resultProviderCallback(query, resultProvider, result); 271 resultProviderCallback(query, resultProvider, result);
266 }); 272 });
267 273
@@ -269,9 +275,7 @@ ReplayResult QueryWorker<DomainType>::executeInitialQuery(
269 << (replayResult.replayedAll ? "Replayed all available results.\n" : "") 275 << (replayResult.replayedAll ? "Replayed all available results.\n" : "")
270 << "Initial query took: " << Log::TraceTime(time.elapsed()); 276 << "Initial query took: " << Log::TraceTime(time.elapsed());
271 277
272 auto state = preparedQuery.getState(); 278 return {entityStore.maxRevision(), replayResult.replayedEntities, replayResult.replayedAll, preparedQuery.getState()};
273
274 return {entityStore.maxRevision(), replayResult.replayedEntities, replayResult.replayedAll, state};
275} 279}
276 280
277#define REGISTER_TYPE(T) \ 281#define REGISTER_TYPE(T) \
diff --git a/common/queryrunner.h b/common/queryrunner.h
index f5c7ead..5308eac 100644
--- a/common/queryrunner.h
+++ b/common/queryrunner.h
@@ -98,11 +98,10 @@ private:
98 QSharedPointer<Sink::ResourceAccessInterface> mResourceAccess; 98 QSharedPointer<Sink::ResourceAccessInterface> mResourceAccess;
99 QSharedPointer<Sink::ResultProvider<typename DomainType::Ptr>> mResultProvider; 99 QSharedPointer<Sink::ResultProvider<typename DomainType::Ptr>> mResultProvider;
100 ResultTransformation mResultTransformation; 100 ResultTransformation mResultTransformation;
101 QHash<QByteArray, qint64> mOffset; 101 QHash<QByteArray, DataStoreQuery::State::Ptr> mQueryState;
102 int mBatchSize; 102 int mBatchSize;
103 QObject guard; 103 QObject guard;
104 Sink::Log::Context mLogCtx; 104 Sink::Log::Context mLogCtx;
105 DataStoreQuery::State::Ptr mQueryState;
106 bool mInitialQueryComplete = false; 105 bool mInitialQueryComplete = false;
107 bool mQueryInProgress = false; 106 bool mQueryInProgress = false;
108}; 107};
diff --git a/common/resourceaccess.cpp b/common/resourceaccess.cpp
index 50845ac..ad8cae9 100644
--- a/common/resourceaccess.cpp
+++ b/common/resourceaccess.cpp
@@ -547,6 +547,7 @@ static Sink::Notification getNotification(const Sink::Commands::Notification *bu
547 } 547 }
548 n.type = buffer->type(); 548 n.type = buffer->type();
549 n.code = buffer->code(); 549 n.code = buffer->code();
550 n.entities = BufferUtils::fromVector(*buffer->entities());
550 return n; 551 return n;
551} 552}
552 553
@@ -601,20 +602,23 @@ bool ResourceAccess::processMessageBuffer()
601 queuedInvoke([=]() { emit notification(n); }, this); 602 queuedInvoke([=]() { emit notification(n); }, this);
602 } break; 603 } break;
603 case Sink::Notification::Status: 604 case Sink::Notification::Status:
604 if (mResourceStatus == buffer->code()) { 605 if (mResourceStatus != buffer->code()) {
605 SinkTrace() << "Got an unnecessary status notification"; 606 mResourceStatus = buffer->code();
606 break; 607 SinkTrace() << "Updated status: " << mResourceStatus;
607 } 608 }
608 mResourceStatus = buffer->code(); 609 [[clang::fallthrough]];
609 SinkTrace() << "Updated status: " << mResourceStatus; 610 case Sink::Notification::Info:
610 [[clang::fallthrough]]; 611 [[clang::fallthrough]];
611 case Sink::Notification::Warning: 612 case Sink::Notification::Warning:
612 [[clang::fallthrough]]; 613 [[clang::fallthrough]];
614 case Sink::Notification::Error:
615 [[clang::fallthrough]];
613 case Sink::Notification::FlushCompletion: 616 case Sink::Notification::FlushCompletion:
614 [[clang::fallthrough]]; 617 [[clang::fallthrough]];
615 case Sink::Notification::Progress: { 618 case Sink::Notification::Progress: {
616 auto n = getNotification(buffer); 619 auto n = getNotification(buffer);
617 SinkTrace() << "Received notification: " << n.type; 620 SinkTrace() << "Received notification: " << n;
621 n.resource = d->resourceInstanceIdentifier;
618 emit notification(n); 622 emit notification(n);
619 } break; 623 } break;
620 case Sink::Notification::RevisionUpdate: 624 case Sink::Notification::RevisionUpdate:
diff --git a/common/resourcecontext.h b/common/resourcecontext.h
index 6058ac7..6ceba01 100644
--- a/common/resourcecontext.h
+++ b/common/resourcecontext.h
@@ -55,7 +55,9 @@ struct ResourceContext {
55 DomainTypeAdaptorFactoryInterface &adaptorFactory(const QByteArray &type) const 55 DomainTypeAdaptorFactoryInterface &adaptorFactory(const QByteArray &type) const
56 { 56 {
57 auto factory = adaptorFactories.value(type); 57 auto factory = adaptorFactories.value(type);
58 Q_ASSERT(factory); 58 if (!factory) {
59 qFatal("Failed to find a factory for %s", type.constData());
60 }
59 return *factory; 61 return *factory;
60 } 62 }
61 63
diff --git a/common/resourcecontrol.cpp b/common/resourcecontrol.cpp
index 1f61a1c..70a3f7d 100644
--- a/common/resourcecontrol.cpp
+++ b/common/resourcecontrol.cpp
@@ -100,9 +100,9 @@ KAsync::Job<void> ResourceControl::flush(Flush::FlushType type, const QByteArray
100 auto notifier = QSharedPointer<Sink::Notifier>::create(resourceAccess); 100 auto notifier = QSharedPointer<Sink::Notifier>::create(resourceAccess);
101 auto id = QUuid::createUuid().toByteArray(); 101 auto id = QUuid::createUuid().toByteArray();
102 return KAsync::start<void>([=](KAsync::Future<void> &future) { 102 return KAsync::start<void>([=](KAsync::Future<void> &future) {
103 SinkTrace() << "Waiting for notification notification " << id; 103 SinkTrace() << "Waiting for flush completion notification " << id;
104 notifier->registerHandler([&future, id](const Notification &notification) { 104 notifier->registerHandler([&future, id](const Notification &notification) {
105 SinkTrace() << "Received notification " << notification.type << notification.id; 105 SinkTrace() << "Received notification: " << notification.type << notification.id;
106 if (notification.id == id) { 106 if (notification.id == id) {
107 SinkTrace() << "FlushComplete"; 107 SinkTrace() << "FlushComplete";
108 if (notification.code) { 108 if (notification.code) {
diff --git a/common/resourcefacade.cpp b/common/resourcefacade.cpp
index 4a8037d..dee0711 100644
--- a/common/resourcefacade.cpp
+++ b/common/resourcefacade.cpp
@@ -34,33 +34,42 @@ SINK_DEBUG_AREA("ResourceFacade")
34template<typename DomainType> 34template<typename DomainType>
35ConfigNotifier LocalStorageFacade<DomainType>::sConfigNotifier; 35ConfigNotifier LocalStorageFacade<DomainType>::sConfigNotifier;
36 36
37static void applyConfig(ConfigStore &configStore, const QByteArray &id, ApplicationDomain::ApplicationDomainType &object) 37static void applyConfig(ConfigStore &configStore, const QByteArray &id, ApplicationDomain::ApplicationDomainType &object, const QByteArrayList &requestedProperties)
38{ 38{
39 const auto configurationValues = configStore.get(id); 39 const auto configurationValues = configStore.get(id);
40 for (auto it = configurationValues.constBegin(); it != configurationValues.constEnd(); it++) { 40 for (auto it = configurationValues.constBegin(); it != configurationValues.constEnd(); it++) {
41 object.setProperty(it.key(), it.value()); 41 object.setProperty(it.key(), it.value());
42 } 42 }
43 //Populate the object with dummy values for non-available but requested properties.
44 //This avoid a warning about non-existing properties in bufferadaptor.h
45 if (!requestedProperties.isEmpty()) {
46 for (const auto &requested: requestedProperties) {
47 if (!object.hasProperty(requested)) {
48 object.setProperty(requested, QVariant{});
49 }
50 }
51 }
43} 52}
44 53
45template <typename DomainType> 54template <typename DomainType>
46static typename DomainType::Ptr readFromConfig(ConfigStore &configStore, const QByteArray &id, const QByteArray &type) 55static typename DomainType::Ptr readFromConfig(ConfigStore &configStore, const QByteArray &id, const QByteArray &type, const QByteArrayList &requestedProperties)
47{ 56{
48 auto object = DomainType::Ptr::create(id); 57 auto object = DomainType::Ptr::create(id);
49 applyConfig(configStore, id, *object); 58 applyConfig(configStore, id, *object, requestedProperties);
50 return object; 59 return object;
51} 60}
52 61
53template <> 62template <>
54typename ApplicationDomain::SinkAccount::Ptr readFromConfig<ApplicationDomain::SinkAccount>(ConfigStore &configStore, const QByteArray &id, const QByteArray &type) 63typename ApplicationDomain::SinkAccount::Ptr readFromConfig<ApplicationDomain::SinkAccount>(ConfigStore &configStore, const QByteArray &id, const QByteArray &type, const QByteArrayList &requestedProperties)
55{ 64{
56 auto object = ApplicationDomain::SinkAccount::Ptr::create(id); 65 auto object = ApplicationDomain::SinkAccount::Ptr::create(id);
57 object->setProperty(ApplicationDomain::SinkAccount::AccountType::name, type); 66 object->setProperty(ApplicationDomain::SinkAccount::AccountType::name, type);
58 applyConfig(configStore, id, *object); 67 applyConfig(configStore, id, *object, requestedProperties);
59 return object; 68 return object;
60} 69}
61 70
62template <> 71template <>
63typename ApplicationDomain::SinkResource::Ptr readFromConfig<ApplicationDomain::SinkResource>(ConfigStore &configStore, const QByteArray &id, const QByteArray &type) 72typename ApplicationDomain::SinkResource::Ptr readFromConfig<ApplicationDomain::SinkResource>(ConfigStore &configStore, const QByteArray &id, const QByteArray &type, const QByteArrayList &requestedProperties)
64{ 73{
65 auto object = ApplicationDomain::SinkResource::Ptr::create(id); 74 auto object = ApplicationDomain::SinkResource::Ptr::create(id);
66 object->setProperty(ApplicationDomain::SinkResource::ResourceType::name, type); 75 object->setProperty(ApplicationDomain::SinkResource::ResourceType::name, type);
@@ -70,7 +79,7 @@ typename ApplicationDomain::SinkResource::Ptr readFromConfig<ApplicationDomain::
70 object->setCapabilities(res->capabilities()); 79 object->setCapabilities(res->capabilities());
71 } 80 }
72 } 81 }
73 applyConfig(configStore, id, *object); 82 applyConfig(configStore, id, *object, requestedProperties);
74 return object; 83 return object;
75} 84}
76 85
@@ -104,7 +113,7 @@ LocalStorageQueryRunner<DomainType>::LocalStorageQueryRunner(const Query &query,
104 if (!query.ids().isEmpty() && !query.ids().contains(res)) { 113 if (!query.ids().isEmpty() && !query.ids().contains(res)) {
105 continue; 114 continue;
106 } 115 }
107 auto entity = readFromConfig<DomainType>(mConfigStore, res, type); 116 auto entity = readFromConfig<DomainType>(mConfigStore, res, type, query.requestedProperties);
108 if (!matchesFilter(query.getBaseFilters(), *entity)){ 117 if (!matchesFilter(query.getBaseFilters(), *entity)){
109 SinkTraceCtx(mLogCtx) << "Skipping due to filter." << res; 118 SinkTraceCtx(mLogCtx) << "Skipping due to filter." << res;
110 continue; 119 continue;
@@ -169,7 +178,7 @@ template<typename DomainType>
169void LocalStorageQueryRunner<DomainType>::statusChanged(const QByteArray &identifier) 178void LocalStorageQueryRunner<DomainType>::statusChanged(const QByteArray &identifier)
170{ 179{
171 SinkTraceCtx(mLogCtx) << "Status changed " << identifier; 180 SinkTraceCtx(mLogCtx) << "Status changed " << identifier;
172 auto entity = readFromConfig<DomainType>(mConfigStore, identifier, ApplicationDomain::getTypeName<DomainType>()); 181 auto entity = readFromConfig<DomainType>(mConfigStore, identifier, ApplicationDomain::getTypeName<DomainType>(), QByteArrayList{});
173 updateStatus(*entity); 182 updateStatus(*entity);
174 mResultProvider->modify(entity); 183 mResultProvider->modify(entity);
175} 184}
@@ -213,7 +222,7 @@ KAsync::Job<void> LocalStorageFacade<DomainType>::create(const DomainType &domai
213 } 222 }
214 configStore.modify(identifier, configurationValues); 223 configStore.modify(identifier, configurationValues);
215 } 224 }
216 sConfigNotifier.add(::readFromConfig<DomainType>(configStore, identifier, type)); 225 sConfigNotifier.add(::readFromConfig<DomainType>(configStore, identifier, type, QByteArrayList{}));
217 }); 226 });
218} 227}
219 228
@@ -242,7 +251,7 @@ KAsync::Job<void> LocalStorageFacade<DomainType>::modify(const DomainType &domai
242 } 251 }
243 252
244 const auto type = configStore.getEntries().value(identifier); 253 const auto type = configStore.getEntries().value(identifier);
245 sConfigNotifier.modify(::readFromConfig<DomainType>(configStore, identifier, type)); 254 sConfigNotifier.modify(::readFromConfig<DomainType>(configStore, identifier, type, QByteArrayList{}));
246 }); 255 });
247} 256}
248 257
@@ -337,10 +346,12 @@ QPair<KAsync::Job<void>, typename Sink::ResultEmitter<typename ApplicationDomain
337 runner->setStatusUpdater([runner, monitoredResources, ctx](ApplicationDomain::SinkAccount &account) { 346 runner->setStatusUpdater([runner, monitoredResources, ctx](ApplicationDomain::SinkAccount &account) {
338 Query query; 347 Query query;
339 query.filter<ApplicationDomain::SinkResource::Account>(account.identifier()); 348 query.filter<ApplicationDomain::SinkResource::Account>(account.identifier());
349 query.request<ApplicationDomain::SinkResource::Account>()
350 .request<ApplicationDomain::SinkResource::Capabilities>();
340 const auto resources = Store::read<ApplicationDomain::SinkResource>(query); 351 const auto resources = Store::read<ApplicationDomain::SinkResource>(query);
341 SinkTraceCtx(ctx) << "Found resource belonging to the account " << account.identifier() << " : " << resources; 352 SinkTraceCtx(ctx) << "Found resource belonging to the account " << account.identifier() << " : " << resources;
342 auto accountIdentifier = account.identifier(); 353 auto accountIdentifier = account.identifier();
343 ApplicationDomain::Status status = ApplicationDomain::ConnectedStatus; 354 QList<int> states;
344 for (const auto &resource : resources) { 355 for (const auto &resource : resources) {
345 auto resourceAccess = ResourceAccessFactory::instance().getAccess(resource.identifier(), ResourceConfig::getResourceType(resource.identifier())); 356 auto resourceAccess = ResourceAccessFactory::instance().getAccess(resource.identifier(), ResourceConfig::getResourceType(resource.identifier()));
346 if (!monitoredResources->contains(resource.identifier())) { 357 if (!monitoredResources->contains(resource.identifier())) {
@@ -353,27 +364,20 @@ QPair<KAsync::Job<void>, typename Sink::ResultEmitter<typename ApplicationDomain
353 Q_ASSERT(ret); 364 Q_ASSERT(ret);
354 monitoredResources->insert(resource.identifier()); 365 monitoredResources->insert(resource.identifier());
355 } 366 }
356 367 states << resourceAccess->getResourceStatus();
357 //Figure out overall status
358 auto s = resourceAccess->getResourceStatus();
359 switch (s) {
360 case ApplicationDomain::ErrorStatus:
361 status = ApplicationDomain::ErrorStatus;
362 break;
363 case ApplicationDomain::OfflineStatus:
364 if (status == ApplicationDomain::ConnectedStatus) {
365 status = ApplicationDomain::OfflineStatus;
366 }
367 break;
368 case ApplicationDomain::ConnectedStatus:
369 break;
370 case ApplicationDomain::BusyStatus:
371 if (status != ApplicationDomain::ErrorStatus) {
372 status = ApplicationDomain::BusyStatus;
373 }
374 break;
375 }
376 } 368 }
369 const auto status = [&] {
370 if (states.contains(ApplicationDomain::ErrorStatus)) {
371 return ApplicationDomain::ErrorStatus;
372 }
373 if (states.contains(ApplicationDomain::BusyStatus)) {
374 return ApplicationDomain::BusyStatus;
375 }
376 if (states.contains(ApplicationDomain::ConnectedStatus)) {
377 return ApplicationDomain::ConnectedStatus;
378 }
379 return ApplicationDomain::OfflineStatus;
380 }();
377 account.setStatusStatus(status); 381 account.setStatusStatus(status);
378 }); 382 });
379 return qMakePair(KAsync::null<void>(), runner->emitter()); 383 return qMakePair(KAsync::null<void>(), runner->emitter());
diff --git a/common/resultprovider.h b/common/resultprovider.h
index a2ed0b5..d6feaf9 100644
--- a/common/resultprovider.h
+++ b/common/resultprovider.h
@@ -268,8 +268,9 @@ public:
268 268
269 void initialResultSetComplete(const DomainType &parent, bool replayedAll) 269 void initialResultSetComplete(const DomainType &parent, bool replayedAll)
270 { 270 {
271 QMutexLocker locker{&mMutex}; 271 //This callback is only ever called from the main thread, so we don't do any locking
272 if (initialResultSetCompleteHandler && guardOk()) { 272 if (initialResultSetCompleteHandler && guardOk()) {
273 //This can directly lead to our destruction and thus waitForMethodExecutionEnd
273 initialResultSetCompleteHandler(parent, replayedAll); 274 initialResultSetCompleteHandler(parent, replayedAll);
274 } 275 }
275 } 276 }
@@ -313,6 +314,13 @@ private:
313 std::function<void(void)> clearHandler; 314 std::function<void(void)> clearHandler;
314 315
315 std::function<void(const DomainType &parent)> mFetcher; 316 std::function<void(const DomainType &parent)> mFetcher;
317 /*
318 * This mutex is here to protect the emitter from getting destroyed while the producer-thread (ResultProvider) is calling into it,
319 * and vice-verca, to protect the producer thread from calling into a destroyed emitter.
320 *
321 * This is necessary because Emitter and ResultProvider have lifetimes managed by two different threads.
322 * The emitter lives in the application thread, and the resultprovider in the query thread.
323 */
316 QMutex mMutex; 324 QMutex mMutex;
317 bool mDone = false; 325 bool mDone = false;
318}; 326};
diff --git a/common/specialpurposepreprocessor.cpp b/common/specialpurposepreprocessor.cpp
index be5fa50..25a6d1a 100644
--- a/common/specialpurposepreprocessor.cpp
+++ b/common/specialpurposepreprocessor.cpp
@@ -46,7 +46,7 @@ QByteArray getSpecialPurposeType(const QString &name)
46} 46}
47} 47}
48 48
49SpecialPurposeProcessor::SpecialPurposeProcessor(const QByteArray &resourceType, const QByteArray &resourceInstanceIdentifier) : mResourceType(resourceType), mResourceInstanceIdentifier(resourceInstanceIdentifier) {} 49SpecialPurposeProcessor::SpecialPurposeProcessor() : Sink::Preprocessor() {}
50 50
51QByteArray SpecialPurposeProcessor::findFolder(const QByteArray &specialPurpose, bool createIfMissing) 51QByteArray SpecialPurposeProcessor::findFolder(const QByteArray &specialPurpose, bool createIfMissing)
52{ 52{
@@ -60,7 +60,7 @@ QByteArray SpecialPurposeProcessor::findFolder(const QByteArray &specialPurpose,
60 60
61 if (!mSpecialPurposeFolders.contains(specialPurpose) && createIfMissing) { 61 if (!mSpecialPurposeFolders.contains(specialPurpose) && createIfMissing) {
62 SinkTrace() << "Failed to find a " << specialPurpose << " folder, creating a new one"; 62 SinkTrace() << "Failed to find a " << specialPurpose << " folder, creating a new one";
63 auto folder = ApplicationDomain::Folder::create(mResourceInstanceIdentifier); 63 auto folder = ApplicationDomain::Folder::create(resourceInstanceIdentifier());
64 folder.setSpecialPurpose(QByteArrayList() << specialPurpose); 64 folder.setSpecialPurpose(QByteArrayList() << specialPurpose);
65 folder.setName(sSpecialPurposeFolders.value(specialPurpose)); 65 folder.setName(sSpecialPurposeFolders.value(specialPurpose));
66 folder.setIcon("folder"); 66 folder.setIcon("folder");
@@ -104,7 +104,19 @@ void SpecialPurposeProcessor::moveToFolder(Sink::ApplicationDomain::ApplicationD
104 104
105void SpecialPurposeProcessor::newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) 105void SpecialPurposeProcessor::newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity)
106{ 106{
107 moveToFolder(newEntity); 107 auto mail = newEntity.cast<ApplicationDomain::Mail>();
108 const auto folder = mail.getFolder();
109 if (folder.isEmpty()) {
110 moveToFolder(newEntity);
111 } else {
112 bool isDraft = findFolder(ApplicationDomain::SpecialPurpose::Mail::drafts) == folder;
113 bool isSent = findFolder(ApplicationDomain::SpecialPurpose::Mail::sent) == folder;
114 bool isTrash = findFolder(ApplicationDomain::SpecialPurpose::Mail::trash) == folder;
115 mail.setDraft(isDraft);
116 mail.setTrash(isTrash);
117 mail.setSent(isSent);
118 }
119
108} 120}
109 121
110void SpecialPurposeProcessor::modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity) 122void SpecialPurposeProcessor::modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity)
@@ -118,8 +130,8 @@ void SpecialPurposeProcessor::modifiedEntity(const Sink::ApplicationDomain::Appl
118 bool isSent = findFolder(ApplicationDomain::SpecialPurpose::Mail::sent) == folder; 130 bool isSent = findFolder(ApplicationDomain::SpecialPurpose::Mail::sent) == folder;
119 bool isTrash = findFolder(ApplicationDomain::SpecialPurpose::Mail::trash) == folder; 131 bool isTrash = findFolder(ApplicationDomain::SpecialPurpose::Mail::trash) == folder;
120 mail.setDraft(isDraft); 132 mail.setDraft(isDraft);
121 mail.setTrash(isSent); 133 mail.setTrash(isTrash);
122 mail.setSent(isTrash); 134 mail.setSent(isSent);
123 } else { 135 } else {
124 moveToFolder(newEntity); 136 moveToFolder(newEntity);
125 } 137 }
diff --git a/common/specialpurposepreprocessor.h b/common/specialpurposepreprocessor.h
index 6eb325c..6173aff 100644
--- a/common/specialpurposepreprocessor.h
+++ b/common/specialpurposepreprocessor.h
@@ -28,7 +28,7 @@ namespace SpecialPurpose {
28class SINK_EXPORT SpecialPurposeProcessor : public Sink::Preprocessor 28class SINK_EXPORT SpecialPurposeProcessor : public Sink::Preprocessor
29{ 29{
30public: 30public:
31 SpecialPurposeProcessor(const QByteArray &resourceType, const QByteArray &resourceInstanceIdentifier); 31 SpecialPurposeProcessor();
32 32
33 void newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE; 33 void newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE;
34 void modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE; 34 void modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE;
@@ -39,6 +39,4 @@ private:
39 bool isSpecialPurposeFolder(const QByteArray &folder) const; 39 bool isSpecialPurposeFolder(const QByteArray &folder) const;
40 40
41 QHash<QByteArray, QByteArray> mSpecialPurposeFolders; 41 QHash<QByteArray, QByteArray> mSpecialPurposeFolders;
42 QByteArray mResourceType;
43 QByteArray mResourceInstanceIdentifier;
44}; 42};
diff --git a/common/storage.h b/common/storage.h
index fd349f3..71e9401 100644
--- a/common/storage.h
+++ b/common/storage.h
@@ -128,14 +128,14 @@ public:
128 public: 128 public:
129 Transaction(); 129 Transaction();
130 ~Transaction(); 130 ~Transaction();
131 bool commit(const std::function<void(const DataStore::Error &error)> &errorHandler = std::function<void(const DataStore::Error &error)>()); 131 bool commit(const std::function<void(const DataStore::Error &error)> &errorHandler = {});
132 void abort(); 132 void abort();
133 133
134 QList<QByteArray> getDatabaseNames() const; 134 QList<QByteArray> getDatabaseNames() const;
135 bool validateNamedDatabases(); 135 bool validateNamedDatabases();
136 136
137 NamedDatabase openDatabase(const QByteArray &name = QByteArray("default"), 137 NamedDatabase openDatabase(const QByteArray &name = {"default"},
138 const std::function<void(const DataStore::Error &error)> &errorHandler = std::function<void(const DataStore::Error &error)>(), bool allowDuplicates = false) const; 138 const std::function<void(const DataStore::Error &error)> &errorHandler = {}, bool allowDuplicates = false) const;
139 139
140 Transaction(Transaction &&other); 140 Transaction(Transaction &&other);
141 Transaction &operator=(Transaction &&other); 141 Transaction &operator=(Transaction &&other);
@@ -189,6 +189,9 @@ public:
189 static QByteArray getTypeFromRevision(const Transaction &, qint64 revision); 189 static QByteArray getTypeFromRevision(const Transaction &, qint64 revision);
190 static void recordRevision(Transaction &, qint64 revision, const QByteArray &uid, const QByteArray &type); 190 static void recordRevision(Transaction &, qint64 revision, const QByteArray &uid, const QByteArray &type);
191 static void removeRevision(Transaction &, qint64 revision); 191 static void removeRevision(Transaction &, qint64 revision);
192 static void recordUid(DataStore::Transaction &transaction, const QByteArray &uid);
193 static void removeUid(DataStore::Transaction &transaction, const QByteArray &uid);
194 static void getUids(const Transaction &, const std::function<void(const QByteArray &uid)> &);
192 195
193 bool exists() const; 196 bool exists() const;
194 197
diff --git a/common/storage/entitystore.cpp b/common/storage/entitystore.cpp
index 909f1b2..b7309ab 100644
--- a/common/storage/entitystore.cpp
+++ b/common/storage/entitystore.cpp
@@ -31,8 +31,7 @@
31#include "bufferutils.h" 31#include "bufferutils.h"
32#include "entity_generated.h" 32#include "entity_generated.h"
33#include "applicationdomaintype_p.h" 33#include "applicationdomaintype_p.h"
34 34#include "typeimplementations.h"
35#include "domaintypes.h"
36 35
37using namespace Sink; 36using namespace Sink;
38using namespace Sink::Storage; 37using namespace Sink::Storage;
@@ -168,19 +167,15 @@ void EntityStore::copyBlobs(ApplicationDomain::ApplicationDomainType &entity, qi
168 } 167 }
169} 168}
170 169
171bool EntityStore::add(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &entity_, bool replayToSource, const PreprocessCreation &preprocess) 170bool EntityStore::add(const QByteArray &type, ApplicationDomain::ApplicationDomainType entity, bool replayToSource)
172{ 171{
173 if (entity_.identifier().isEmpty()) { 172 if (entity.identifier().isEmpty()) {
174 SinkWarningCtx(d->logCtx) << "Can't write entity with an empty identifier"; 173 SinkWarningCtx(d->logCtx) << "Can't write entity with an empty identifier";
175 return false; 174 return false;
176 } 175 }
177 176
178 auto entity = *ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<ApplicationDomain::ApplicationDomainType>(entity_, entity_.availableProperties());
179 entity.setChangedProperties(entity.availableProperties().toSet());
180
181 SinkTraceCtx(d->logCtx) << "New entity " << entity; 177 SinkTraceCtx(d->logCtx) << "New entity " << entity;
182 178
183 preprocess(entity);
184 d->typeIndex(type).add(entity.identifier(), entity, d->transaction); 179 d->typeIndex(type).add(entity.identifier(), entity, d->transaction);
185 180
186 //The maxRevision may have changed meanwhile if the entity created sub-entities 181 //The maxRevision may have changed meanwhile if the entity created sub-entities
@@ -205,26 +200,20 @@ bool EntityStore::add(const QByteArray &type, const ApplicationDomain::Applicati
205 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to write entity" << entity.identifier() << newRevision; }); 200 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to write entity" << entity.identifier() << newRevision; });
206 DataStore::setMaxRevision(d->transaction, newRevision); 201 DataStore::setMaxRevision(d->transaction, newRevision);
207 DataStore::recordRevision(d->transaction, newRevision, entity.identifier(), type); 202 DataStore::recordRevision(d->transaction, newRevision, entity.identifier(), type);
203 DataStore::recordUid(d->transaction, entity.identifier());
208 SinkTraceCtx(d->logCtx) << "Wrote entity: " << entity.identifier() << type << newRevision; 204 SinkTraceCtx(d->logCtx) << "Wrote entity: " << entity.identifier() << type << newRevision;
209 return true; 205 return true;
210} 206}
211 207
212bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &diff, const QByteArrayList &deletions, bool replayToSource, const PreprocessModification &preprocess) 208ApplicationDomain::ApplicationDomainType EntityStore::applyDiff(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &current, const ApplicationDomain::ApplicationDomainType &diff, const QByteArrayList &deletions) const
213{ 209{
214 auto changeset = diff.changedProperties();
215 const auto current = readLatest(type, diff.identifier());
216 if (current.identifier().isEmpty()) {
217 SinkWarningCtx(d->logCtx) << "Failed to read current version: " << diff.identifier();
218 return false;
219 }
220
221 auto newEntity = *ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<ApplicationDomain::ApplicationDomainType>(current, current.availableProperties()); 210 auto newEntity = *ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<ApplicationDomain::ApplicationDomainType>(current, current.availableProperties());
222 211
223 SinkTraceCtx(d->logCtx) << "Modified entity: " << newEntity; 212 SinkTraceCtx(d->logCtx) << "Modified entity: " << newEntity;
224 213
225 // Apply diff 214 // Apply diff
226 //SinkTrace() << "Applying changed properties: " << changeset; 215 //SinkTrace() << "Applying changed properties: " << changeset;
227 for (const auto &property : changeset) { 216 for (const auto &property : diff.changedProperties()) {
228 const auto value = diff.getProperty(property); 217 const auto value = diff.getProperty(property);
229 if (value.isValid()) { 218 if (value.isValid()) {
230 //SinkTrace() << "Setting property: " << property; 219 //SinkTrace() << "Setting property: " << property;
@@ -237,8 +226,25 @@ bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::Applic
237 //SinkTrace() << "Removing property: " << property; 226 //SinkTrace() << "Removing property: " << property;
238 newEntity.setProperty(property, QVariant()); 227 newEntity.setProperty(property, QVariant());
239 } 228 }
229 return newEntity;
230}
231
232bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &diff, const QByteArrayList &deletions, bool replayToSource)
233{
234 const auto current = readLatest(type, diff.identifier());
235 if (current.identifier().isEmpty()) {
236 SinkWarningCtx(d->logCtx) << "Failed to read current version: " << diff.identifier();
237 return false;
238 }
239
240 auto newEntity = applyDiff(type, current, diff, deletions);
241 return modify(type, current, newEntity, replayToSource);
242}
243
244bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &current, ApplicationDomain::ApplicationDomainType newEntity, bool replayToSource)
245{
246 SinkTraceCtx(d->logCtx) << "Modified entity: " << newEntity;
240 247
241 preprocess(current, newEntity);
242 d->typeIndex(type).remove(current.identifier(), current, d->transaction); 248 d->typeIndex(type).remove(current.identifier(), current, d->transaction);
243 d->typeIndex(type).add(newEntity.identifier(), newEntity, d->transaction); 249 d->typeIndex(type).add(newEntity.identifier(), newEntity, d->transaction);
244 250
@@ -250,7 +256,7 @@ bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::Applic
250 flatbuffers::FlatBufferBuilder metadataFbb; 256 flatbuffers::FlatBufferBuilder metadataFbb;
251 { 257 {
252 //We add availableProperties to account for the properties that have been changed by the preprocessors 258 //We add availableProperties to account for the properties that have been changed by the preprocessors
253 auto modifiedProperties = BufferUtils::toVector(metadataFbb, changeset + newEntity.changedProperties()); 259 auto modifiedProperties = BufferUtils::toVector(metadataFbb, newEntity.changedProperties());
254 auto metadataBuilder = MetadataBuilder(metadataFbb); 260 auto metadataBuilder = MetadataBuilder(metadataFbb);
255 metadataBuilder.add_revision(newRevision); 261 metadataBuilder.add_revision(newRevision);
256 metadataBuilder.add_operation(Operation_Modification); 262 metadataBuilder.add_operation(Operation_Modification);
@@ -259,7 +265,7 @@ bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::Applic
259 auto metadataBuffer = metadataBuilder.Finish(); 265 auto metadataBuffer = metadataBuilder.Finish();
260 FinishMetadataBuffer(metadataFbb, metadataBuffer); 266 FinishMetadataBuffer(metadataFbb, metadataBuffer);
261 } 267 }
262 SinkTraceCtx(d->logCtx) << "Changed properties: " << changeset + newEntity.changedProperties(); 268 SinkTraceCtx(d->logCtx) << "Changed properties: " << newEntity.changedProperties();
263 269
264 newEntity.setChangedProperties(newEntity.availableProperties().toSet()); 270 newEntity.setChangedProperties(newEntity.availableProperties().toSet());
265 271
@@ -275,36 +281,14 @@ bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::Applic
275 return true; 281 return true;
276} 282}
277 283
278bool EntityStore::remove(const QByteArray &type, const QByteArray &uid, bool replayToSource, const PreprocessRemoval &preprocess) 284bool EntityStore::remove(const QByteArray &type, const Sink::ApplicationDomain::ApplicationDomainType &current, bool replayToSource)
279{ 285{
280 bool found = false; 286 const auto uid = current.identifier();
281 bool alreadyRemoved = false; 287 if (!exists(type, uid)) {
282 DataStore::mainDatabase(d->transaction, type)
283 .findLatest(uid,
284 [&found, &alreadyRemoved](const QByteArray &key, const QByteArray &data) -> bool {
285 auto entity = GetEntity(data.data());
286 if (entity && entity->metadata()) {
287 auto metadata = GetMetadata(entity->metadata()->Data());
288 found = true;
289 if (metadata->operation() == Operation_Removal) {
290 alreadyRemoved = true;
291 }
292 }
293 return false;
294 },
295 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to read old revision from storage: " << error.message; });
296
297 if (!found) {
298 SinkWarningCtx(d->logCtx) << "Remove: Failed to find entity " << uid;
299 return false;
300 }
301 if (alreadyRemoved) {
302 SinkWarningCtx(d->logCtx) << "Remove: Entity is already removed " << uid; 288 SinkWarningCtx(d->logCtx) << "Remove: Entity is already removed " << uid;
303 return false; 289 return false;
304 } 290 }
305 291
306 const auto current = readLatest(type, uid);
307 preprocess(current);
308 d->typeIndex(type).remove(current.identifier(), current, d->transaction); 292 d->typeIndex(type).remove(current.identifier(), current, d->transaction);
309 293
310 SinkTraceCtx(d->logCtx) << "Removed entity " << current; 294 SinkTraceCtx(d->logCtx) << "Removed entity " << current;
@@ -328,6 +312,7 @@ bool EntityStore::remove(const QByteArray &type, const QByteArray &uid, bool rep
328 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to write entity" << uid << newRevision; }); 312 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to write entity" << uid << newRevision; });
329 DataStore::setMaxRevision(d->transaction, newRevision); 313 DataStore::setMaxRevision(d->transaction, newRevision);
330 DataStore::recordRevision(d->transaction, newRevision, uid, type); 314 DataStore::recordRevision(d->transaction, newRevision, uid, type);
315 DataStore::removeUid(d->transaction, uid);
331 return true; 316 return true;
332} 317}
333 318
@@ -521,15 +506,9 @@ ApplicationDomain::ApplicationDomainType EntityStore::readEntity(const QByteArra
521 506
522void EntityStore::readAll(const QByteArray &type, const std::function<void(const ApplicationDomain::ApplicationDomainType &entity)> &callback) 507void EntityStore::readAll(const QByteArray &type, const std::function<void(const ApplicationDomain::ApplicationDomainType &entity)> &callback)
523{ 508{
524 auto db = DataStore::mainDatabase(d->getTransaction(), type); 509 readAllUids(type, [&] (const QByteArray &uid) {
525 db.scan("", 510 readLatest(type, uid, callback);
526 [=](const QByteArray &key, const QByteArray &value) -> bool { 511 });
527 auto uid = DataStore::uidFromKey(key);
528 auto buffer = Sink::EntityBuffer{value.data(), value.size()};
529 callback(d->createApplicationDomainType(type, uid, DataStore::maxRevision(d->getTransaction()), buffer));
530 return true;
531 },
532 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Error during query: " << error.message; });
533} 512}
534 513
535void EntityStore::readRevisions(qint64 baseRevision, const QByteArray &expectedType, const std::function<void(const QByteArray &key)> &callback) 514void EntityStore::readRevisions(qint64 baseRevision, const QByteArray &expectedType, const std::function<void(const QByteArray &key)> &callback)
@@ -588,15 +567,7 @@ ApplicationDomain::ApplicationDomainType EntityStore::readPrevious(const QByteAr
588 567
589void EntityStore::readAllUids(const QByteArray &type, const std::function<void(const QByteArray &uid)> callback) 568void EntityStore::readAllUids(const QByteArray &type, const std::function<void(const QByteArray &uid)> callback)
590{ 569{
591 //TODO use uid index instead 570 DataStore::getUids(d->getTransaction(), callback);
592 //FIXME we currently report each uid for every revision with the same uid
593 auto db = DataStore::mainDatabase(d->getTransaction(), type);
594 db.scan("",
595 [callback](const QByteArray &key, const QByteArray &) -> bool {
596 callback(Sink::Storage::DataStore::uidFromKey(key));
597 return true;
598 },
599 [&](const Sink::Storage::DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to read current value from storage: " << error.message; });
600} 571}
601 572
602bool EntityStore::contains(const QByteArray &type, const QByteArray &uid) 573bool EntityStore::contains(const QByteArray &type, const QByteArray &uid)
@@ -604,6 +575,36 @@ bool EntityStore::contains(const QByteArray &type, const QByteArray &uid)
604 return DataStore::mainDatabase(d->getTransaction(), type).contains(uid); 575 return DataStore::mainDatabase(d->getTransaction(), type).contains(uid);
605} 576}
606 577
578bool EntityStore::exists(const QByteArray &type, const QByteArray &uid)
579{
580 bool found = false;
581 bool alreadyRemoved = false;
582 DataStore::mainDatabase(d->transaction, type)
583 .findLatest(uid,
584 [&found, &alreadyRemoved](const QByteArray &key, const QByteArray &data) -> bool {
585 auto entity = GetEntity(data.data());
586 if (entity && entity->metadata()) {
587 auto metadata = GetMetadata(entity->metadata()->Data());
588 found = true;
589 if (metadata->operation() == Operation_Removal) {
590 alreadyRemoved = true;
591 }
592 }
593 return false;
594 },
595 [&](const DataStore::Error &error) { SinkWarningCtx(d->logCtx) << "Failed to read old revision from storage: " << error.message; });
596 if (!found) {
597 SinkTraceCtx(d->logCtx) << "Remove: Failed to find entity " << uid;
598 return false;
599 }
600 if (alreadyRemoved) {
601 SinkTraceCtx(d->logCtx) << "Remove: Entity is already removed " << uid;
602 return false;
603 }
604 return true;
605}
606
607
607qint64 EntityStore::maxRevision() 608qint64 EntityStore::maxRevision()
608{ 609{
609 if (!d->exists()) { 610 if (!d->exists()) {
diff --git a/common/storage/entitystore.h b/common/storage/entitystore.h
index 46410cd..00241f2 100644
--- a/common/storage/entitystore.h
+++ b/common/storage/entitystore.h
@@ -38,15 +38,13 @@ public:
38 typedef QSharedPointer<EntityStore> Ptr; 38 typedef QSharedPointer<EntityStore> Ptr;
39 EntityStore(const ResourceContext &resourceContext, const Sink::Log::Context &); 39 EntityStore(const ResourceContext &resourceContext, const Sink::Log::Context &);
40 40
41 typedef std::function<void(const ApplicationDomain::ApplicationDomainType &, ApplicationDomain::ApplicationDomainType &)> PreprocessModification;
42 typedef std::function<void(ApplicationDomain::ApplicationDomainType &)> PreprocessCreation;
43 typedef std::function<void(const ApplicationDomain::ApplicationDomainType &)> PreprocessRemoval;
44
45 //Only the pipeline may call the following functions outside of tests 41 //Only the pipeline may call the following functions outside of tests
46 bool add(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &, bool replayToSource, const PreprocessCreation &); 42 bool add(const QByteArray &type, ApplicationDomain::ApplicationDomainType newEntity, bool replayToSource);
47 bool modify(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &, const QByteArrayList &deletions, bool replayToSource, const PreprocessModification &); 43 bool modify(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &diff, const QByteArrayList &deletions, bool replayToSource);
48 bool remove(const QByteArray &type, const QByteArray &uid, bool replayToSource, const PreprocessRemoval &); 44 bool modify(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &current, ApplicationDomain::ApplicationDomainType newEntity, bool replayToSource);
45 bool remove(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &current, bool replayToSource);
49 bool cleanupRevisions(qint64 revision); 46 bool cleanupRevisions(qint64 revision);
47 ApplicationDomain::ApplicationDomainType applyDiff(const QByteArray &type, const ApplicationDomain::ApplicationDomainType &current, const ApplicationDomain::ApplicationDomainType &diff, const QByteArrayList &deletions) const;
50 48
51 void startTransaction(Sink::Storage::DataStore::AccessMode); 49 void startTransaction(Sink::Storage::DataStore::AccessMode);
52 void commitTransaction(); 50 void commitTransaction();
@@ -105,8 +103,12 @@ public:
105 103
106 void readRevisions(qint64 baseRevision, const QByteArray &type, const std::function<void(const QByteArray &key)> &callback); 104 void readRevisions(qint64 baseRevision, const QByteArray &type, const std::function<void(const QByteArray &key)> &callback);
107 105
106 ///Db contains entity (but may already be marked as removed
108 bool contains(const QByteArray &type, const QByteArray &uid); 107 bool contains(const QByteArray &type, const QByteArray &uid);
109 108
109 ///Db contains entity and entity is not yet removed
110 bool exists(const QByteArray &type, const QByteArray &uid);
111
110 qint64 maxRevision(); 112 qint64 maxRevision();
111 113
112 Sink::Log::Context logContext() const; 114 Sink::Log::Context logContext() const;
diff --git a/common/storage_common.cpp b/common/storage_common.cpp
index d8b1f42..81a38c7 100644
--- a/common/storage_common.cpp
+++ b/common/storage_common.cpp
@@ -28,7 +28,7 @@ SINK_DEBUG_AREA("storage")
28 28
29QDebug& operator<<(QDebug &dbg, const Sink::Storage::DataStore::Error &error) 29QDebug& operator<<(QDebug &dbg, const Sink::Storage::DataStore::Error &error)
30{ 30{
31 dbg << error.message; 31 dbg << error.message << "Code: " << error.code << "Db: " << error.store;
32 return dbg; 32 return dbg;
33} 33}
34 34
@@ -146,6 +146,24 @@ void DataStore::removeRevision(DataStore::Transaction &transaction, qint64 revis
146 transaction.openDatabase("revisionType").remove(QByteArray::number(revision)); 146 transaction.openDatabase("revisionType").remove(QByteArray::number(revision));
147} 147}
148 148
149void DataStore::recordUid(DataStore::Transaction &transaction, const QByteArray &uid)
150{
151 transaction.openDatabase("uids").write(uid, "");
152}
153
154void DataStore::removeUid(DataStore::Transaction &transaction, const QByteArray &uid)
155{
156 transaction.openDatabase("uids").remove(uid);
157}
158
159void DataStore::getUids(const Transaction &transaction, const std::function<void(const QByteArray &uid)> &callback)
160{
161 transaction.openDatabase("uids").scan("", [&] (const QByteArray &key, const QByteArray &) {
162 callback(key);
163 return true;
164 });
165}
166
149bool DataStore::isInternalKey(const char *key) 167bool DataStore::isInternalKey(const char *key)
150{ 168{
151 return key && strncmp(key, s_internalPrefix, s_internalPrefixSize) == 0; 169 return key && strncmp(key, s_internalPrefix, s_internalPrefixSize) == 0;
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp
index ed385ad..08eea37 100644
--- a/common/storage_lmdb.cpp
+++ b/common/storage_lmdb.cpp
@@ -62,6 +62,44 @@ int getErrorCode(int e)
62 return -1; 62 return -1;
63} 63}
64 64
65static QList<QByteArray> getDatabaseNames(MDB_txn *transaction)
66{
67 if (!transaction) {
68 SinkWarning() << "Invalid transaction";
69 return QList<QByteArray>();
70 }
71 int rc;
72 QList<QByteArray> list;
73 MDB_dbi dbi;
74 if ((rc = mdb_dbi_open(transaction, nullptr, 0, &dbi) == 0)) {
75 MDB_val key;
76 MDB_val data;
77 MDB_cursor *cursor;
78
79 mdb_cursor_open(transaction, dbi, &cursor);
80 if ((rc = mdb_cursor_get(cursor, &key, &data, MDB_FIRST)) == 0) {
81 list << QByteArray::fromRawData((char *)key.mv_data, key.mv_size);
82 while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
83 list << QByteArray::fromRawData((char *)key.mv_data, key.mv_size);
84 }
85 } else {
86 //Normal if we don't have any databases yet
87 if (rc == MDB_NOTFOUND) {
88 rc = 0;
89 }
90 if (rc) {
91 SinkWarning() << "Failed to get a value" << rc;
92 }
93 }
94 mdb_cursor_close(cursor);
95 } else {
96 SinkWarning() << "Failed to open db" << rc << QByteArray(mdb_strerror(rc));
97 }
98 return list;
99
100}
101
102
65class DataStore::NamedDatabase::Private 103class DataStore::NamedDatabase::Private
66{ 104{
67public: 105public:
@@ -93,6 +131,18 @@ public:
93 const auto dbiName = name + db; 131 const auto dbiName = name + db;
94 if (sDbis.contains(dbiName)) { 132 if (sDbis.contains(dbiName)) {
95 dbi = sDbis.value(dbiName); 133 dbi = sDbis.value(dbiName);
134 //sDbis can contain dbi's that are not available to this transaction.
135 //We use mdb_dbi_flags to check if the dbi is valid for this transaction.
136 uint f;
137 if (mdb_dbi_flags(transaction, dbi, &f) == EINVAL) {
138 //In readonly mode we can just ignore this. In read-write we would have tried to concurrently create a db.
139 if (!readOnly) {
140 SinkWarning() << "Tried to create database in second transaction: " << dbiName;
141 }
142 dbi = 0;
143 transaction = 0;
144 return false;
145 }
96 } else { 146 } else {
97 MDB_dbi flagtableDbi; 147 MDB_dbi flagtableDbi;
98 if (const int rc = mdb_dbi_open(transaction, "__flagtable", readOnly ? 0 : MDB_CREATE, &flagtableDbi)) { 148 if (const int rc = mdb_dbi_open(transaction, "__flagtable", readOnly ? 0 : MDB_CREATE, &flagtableDbi)) {
@@ -279,7 +329,8 @@ int DataStore::NamedDatabase::scan(const QByteArray &k, const std::function<bool
279 329
280 rc = mdb_cursor_open(d->transaction, d->dbi, &cursor); 330 rc = mdb_cursor_open(d->transaction, d->dbi, &cursor);
281 if (rc) { 331 if (rc) {
282 Error error(d->name.toLatin1() + d->db, getErrorCode(rc), QByteArray("Error during mdb_cursor open: ") + QByteArray(mdb_strerror(rc))); 332 //Invalid arguments can mean that the transaction doesn't contain the db dbi
333 Error error(d->name.toLatin1() + d->db, getErrorCode(rc), QByteArray("Error during mdb_cursor_open: ") + QByteArray(mdb_strerror(rc)) + ". Key: " + k);
283 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error); 334 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
284 return 0; 335 return 0;
285 } 336 }
@@ -447,7 +498,6 @@ public:
447 498
448 MDB_env *env; 499 MDB_env *env;
449 MDB_txn *transaction; 500 MDB_txn *transaction;
450 MDB_dbi dbi;
451 bool requestedRead; 501 bool requestedRead;
452 std::function<void(const DataStore::Error &error)> defaultErrorHandler; 502 std::function<void(const DataStore::Error &error)> defaultErrorHandler;
453 QString name; 503 QString name;
@@ -578,8 +628,7 @@ static bool ensureCorrectDb(DataStore::NamedDatabase &database, const QByteArray
578 } 628 }
579 return false; 629 return false;
580 }, 630 },
581 [](const DataStore::Error &error) -> bool{ 631 [&](const DataStore::Error &) {
582 return false;
583 }, false); 632 }, false);
584 //This is the first time we open this database in a write transaction, write the db name 633 //This is the first time we open this database in a write transaction, write the db name
585 if (!count) { 634 if (!count) {
@@ -637,35 +686,8 @@ QList<QByteArray> DataStore::Transaction::getDatabaseNames() const
637 SinkWarning() << "Invalid transaction"; 686 SinkWarning() << "Invalid transaction";
638 return QList<QByteArray>(); 687 return QList<QByteArray>();
639 } 688 }
689 return Sink::Storage::getDatabaseNames(d->transaction);
640 690
641 int rc;
642 QList<QByteArray> list;
643 Q_ASSERT(d->transaction);
644 if ((rc = mdb_dbi_open(d->transaction, nullptr, 0, &d->dbi) == 0)) {
645 MDB_val key;
646 MDB_val data;
647 MDB_cursor *cursor;
648
649 mdb_cursor_open(d->transaction, d->dbi, &cursor);
650 if ((rc = mdb_cursor_get(cursor, &key, &data, MDB_FIRST)) == 0) {
651 list << QByteArray::fromRawData((char *)key.mv_data, key.mv_size);
652 while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
653 list << QByteArray::fromRawData((char *)key.mv_data, key.mv_size);
654 }
655 } else {
656 //Normal if we don't have any databases yet
657 if (rc == MDB_NOTFOUND) {
658 rc = 0;
659 }
660 if (rc) {
661 SinkWarning() << "Failed to get a value" << rc;
662 }
663 }
664 mdb_cursor_close(cursor);
665 } else {
666 SinkWarning() << "Failed to open db" << rc << QByteArray(mdb_strerror(rc));
667 }
668 return list;
669} 691}
670 692
671 693
diff --git a/common/store.cpp b/common/store.cpp
index 1c8620b..d266098 100644
--- a/common/store.cpp
+++ b/common/store.cpp
@@ -92,6 +92,7 @@ QPair<typename AggregatingResultEmitter<typename DomainType::Ptr>::Ptr, typenam
92 auto facade = FacadeFactory::instance().getFacade<ApplicationDomain::SinkResource>(); 92 auto facade = FacadeFactory::instance().getFacade<ApplicationDomain::SinkResource>();
93 Q_ASSERT(facade); 93 Q_ASSERT(facade);
94 Sink::Query resourceQuery; 94 Sink::Query resourceQuery;
95 resourceQuery.request<ApplicationDomain::SinkResource::Capabilities>();
95 if (query.liveQuery()) { 96 if (query.liveQuery()) {
96 SinkTraceCtx(ctx) << "Listening for new resources."; 97 SinkTraceCtx(ctx) << "Listening for new resources.";
97 resourceQuery.setFlags(Query::LiveQuery); 98 resourceQuery.setFlags(Query::LiveQuery);
@@ -103,6 +104,7 @@ QPair<typename AggregatingResultEmitter<typename DomainType::Ptr>::Ptr, typenam
103 resourceFilter.propertyFilter.insert(ApplicationDomain::SinkResource::Capabilities::name, Query::Comparator{ApplicationDomain::getTypeName<DomainType>(), Query::Comparator::Contains}); 104 resourceFilter.propertyFilter.insert(ApplicationDomain::SinkResource::Capabilities::name, Query::Comparator{ApplicationDomain::getTypeName<DomainType>(), Query::Comparator::Contains});
104 } 105 }
105 resourceQuery.setFilter(resourceFilter); 106 resourceQuery.setFilter(resourceFilter);
107 resourceQuery.requestedProperties << resourceFilter.propertyFilter.keys();
106 108
107 auto result = facade->load(resourceQuery, resourceCtx); 109 auto result = facade->load(resourceQuery, resourceCtx);
108 auto emitter = result.second; 110 auto emitter = result.second;
@@ -249,7 +251,7 @@ KAsync::Job<void> Store::remove(const Sink::Query &query)
249KAsync::Job<void> Store::removeDataFromDisk(const QByteArray &identifier) 251KAsync::Job<void> Store::removeDataFromDisk(const QByteArray &identifier)
250{ 252{
251 // All databases are going to become invalid, nuke the environments 253 // All databases are going to become invalid, nuke the environments
252 // TODO: all clients should react to a notification the resource 254 // TODO: all clients should react to a notification from the resource
253 Sink::Storage::DataStore::clearEnv(); 255 Sink::Storage::DataStore::clearEnv();
254 SinkTrace() << "Remove data from disk " << identifier; 256 SinkTrace() << "Remove data from disk " << identifier;
255 auto time = QSharedPointer<QTime>::create(); 257 auto time = QSharedPointer<QTime>::create();
@@ -277,18 +279,18 @@ KAsync::Job<void> Store::removeDataFromDisk(const QByteArray &identifier)
277 279
278static KAsync::Job<void> synchronize(const QByteArray &resource, const Sink::SyncScope &scope) 280static KAsync::Job<void> synchronize(const QByteArray &resource, const Sink::SyncScope &scope)
279{ 281{
280 SinkLog() << "Synchronizing " << resource; 282 SinkLog() << "Synchronizing " << resource << scope;
281 auto resourceAccess = ResourceAccessFactory::instance().getAccess(resource, ResourceConfig::getResourceType(resource)); 283 auto resourceAccess = ResourceAccessFactory::instance().getAccess(resource, ResourceConfig::getResourceType(resource));
282 return resourceAccess->synchronizeResource(scope) 284 return resourceAccess->synchronizeResource(scope)
283 .addToContext(resourceAccess) 285 .addToContext(resourceAccess)
284 .then<void>([](const KAsync::Error &error) { 286 .then([=](const KAsync::Error &error) {
285 if (error) { 287 if (error) {
286 SinkWarning() << "Error during sync."; 288 SinkWarning() << "Error during sync.";
287 return KAsync::error<void>(error); 289 return KAsync::error(error);
288 } 290 }
289 SinkTrace() << "synced."; 291 SinkTrace() << "Synchronization of resource " << resource << " complete.";
290 return KAsync::null<void>(); 292 return KAsync::null();
291 }); 293 });
292} 294}
293 295
294KAsync::Job<void> Store::synchronize(const Sink::Query &query) 296KAsync::Job<void> Store::synchronize(const Sink::Query &query)
diff --git a/common/store.h b/common/store.h
index 86e4d20..fae76e5 100644
--- a/common/store.h
+++ b/common/store.h
@@ -48,11 +48,15 @@ QString SINK_EXPORT storageLocation();
48 */ 48 */
49QString SINK_EXPORT getTemporaryFilePath(); 49QString SINK_EXPORT getTemporaryFilePath();
50 50
51// Must be the same as in ModelResult
51enum Roles 52enum Roles
52{ 53{
53 DomainObjectRole = Qt::UserRole + 1, // Must be the same as in ModelResult 54 DomainObjectRole = Qt::UserRole + 1,
54 ChildrenFetchedRole, 55 ChildrenFetchedRole,
55 DomainObjectBaseRole 56 DomainObjectBaseRole,
57 StatusRole, //ApplicationDomain::SyncStatus
58 WarningRole, //ApplicationDomain::Warning, only if status == warning || status == error
59 ProgressRole //ApplicationDomain::Progress
56}; 60};
57 61
58/** 62/**
diff --git a/common/synchronizer.cpp b/common/synchronizer.cpp
index b147615..3e7bd30 100644
--- a/common/synchronizer.cpp
+++ b/common/synchronizer.cpp
@@ -33,13 +33,14 @@
33using namespace Sink; 33using namespace Sink;
34 34
35Synchronizer::Synchronizer(const Sink::ResourceContext &context) 35Synchronizer::Synchronizer(const Sink::ResourceContext &context)
36 : ChangeReplay(context), 36 : ChangeReplay(context, {"synchronizer"}),
37 mLogCtx{"synchronizer"}, 37 mLogCtx{"synchronizer"},
38 mResourceContext(context), 38 mResourceContext(context),
39 mEntityStore(Storage::EntityStore::Ptr::create(mResourceContext, mLogCtx)), 39 mEntityStore(Storage::EntityStore::Ptr::create(mResourceContext, mLogCtx)),
40 mSyncStorage(Sink::storageLocation(), mResourceContext.instanceId() + ".synchronization", Sink::Storage::DataStore::DataStore::ReadWrite), 40 mSyncStorage(Sink::storageLocation(), mResourceContext.instanceId() + ".synchronization", Sink::Storage::DataStore::DataStore::ReadWrite),
41 mSyncInProgress(false) 41 mSyncInProgress(false)
42{ 42{
43 mCurrentState.push(ApplicationDomain::Status::OfflineStatus);
43 SinkTraceCtx(mLogCtx) << "Starting synchronizer: " << mResourceContext.resourceType << mResourceContext.instanceId(); 44 SinkTraceCtx(mLogCtx) << "Starting synchronizer: " << mResourceContext.resourceType << mResourceContext.instanceId();
44} 45}
45 46
@@ -252,15 +253,21 @@ void Synchronizer::modify(const DomainType &entity, const QByteArray &newResourc
252 253
253QList<Synchronizer::SyncRequest> Synchronizer::getSyncRequests(const Sink::QueryBase &query) 254QList<Synchronizer::SyncRequest> Synchronizer::getSyncRequests(const Sink::QueryBase &query)
254{ 255{
255 QList<Synchronizer::SyncRequest> list; 256 return QList<Synchronizer::SyncRequest>() << Synchronizer::SyncRequest{query, "sync"};
256 list << Synchronizer::SyncRequest{query, "sync"}; 257}
257 return list; 258
259void Synchronizer::mergeIntoQueue(const Synchronizer::SyncRequest &request, QList<Synchronizer::SyncRequest> &queue)
260{
261 mSyncRequestQueue << request;
258} 262}
259 263
260void Synchronizer::synchronize(const Sink::QueryBase &query) 264void Synchronizer::synchronize(const Sink::QueryBase &query)
261{ 265{
262 SinkTraceCtx(mLogCtx) << "Synchronizing"; 266 SinkTraceCtx(mLogCtx) << "Synchronizing";
263 mSyncRequestQueue << getSyncRequests(query); 267 auto newRequests = getSyncRequests(query);
268 for (const auto &request: newRequests) {
269 mergeIntoQueue(request, mSyncRequestQueue);
270 }
264 processSyncQueue().exec(); 271 processSyncQueue().exec();
265} 272}
266 273
@@ -286,6 +293,42 @@ void Synchronizer::flushComplete(const QByteArray &flushId)
286 } 293 }
287} 294}
288 295
296void Synchronizer::emitNotification(Notification::NoticationType type, int code, const QString &message, const QByteArray &id, const QByteArrayList &entities)
297{
298 Sink::Notification n;
299 n.id = id;
300 n.type = type;
301 n.message = message;
302 n.code = code;
303 n.entities = entities;
304 emit notify(n);
305}
306
307void Synchronizer::reportProgress(int progress, int total)
308{
309 SinkLogCtx(mLogCtx) << "Progress: " << progress << " out of " << total;
310}
311
312void Synchronizer::setStatusFromResult(const KAsync::Error &error, const QString &s, const QByteArray &requestId)
313{
314 if (error) {
315 if (error.errorCode == ApplicationDomain::ConnectionError) {
316 //Couldn't connect, so we assume we don't have a network connection.
317 setStatus(ApplicationDomain::OfflineStatus, s, requestId);
318 } else if (error.errorCode == ApplicationDomain::ConfigurationError) {
319 //There is an error with the configuration.
320 setStatus(ApplicationDomain::ErrorStatus, s, requestId);
321 } else if (error.errorCode == ApplicationDomain::LoginError) {
322 //If we failed to login altough we could connect that indicates a problem with our setup.
323 setStatus(ApplicationDomain::ErrorStatus, s, requestId);
324 }
325 //We don't know what kind of error this was, so we assume it's transient and don't change ou status.
326 } else {
327 //An operation against the server worked, so we're probably online.
328 setStatus(ApplicationDomain::ConnectedStatus, s, requestId);
329 }
330}
331
289KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request) 332KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request)
290{ 333{
291 if (request.options & SyncRequest::RequestFlush) { 334 if (request.options & SyncRequest::RequestFlush) {
@@ -310,35 +353,21 @@ KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request)
310 }); 353 });
311 } else if (request.requestType == Synchronizer::SyncRequest::Synchronization) { 354 } else if (request.requestType == Synchronizer::SyncRequest::Synchronization) {
312 return KAsync::start([this, request] { 355 return KAsync::start([this, request] {
313 Sink::Notification n;
314 n.id = request.requestId;
315 n.type = Notification::Status;
316 n.message = "Synchronization has started.";
317 n.code = ApplicationDomain::BusyStatus;
318 emit notify(n);
319 SinkLogCtx(mLogCtx) << "Synchronizing: " << request.query; 356 SinkLogCtx(mLogCtx) << "Synchronizing: " << request.query;
357 emitNotification(Notification::Info, ApplicationDomain::SyncInProgress, {}, {}, request.applicableEntities);
320 }).then(synchronizeWithSource(request.query)).then([this] { 358 }).then(synchronizeWithSource(request.query)).then([this] {
321 //Commit after every request, so implementations only have to commit more if they add a lot of data. 359 //Commit after every request, so implementations only have to commit more if they add a lot of data.
322 commit(); 360 commit();
323 }).then<void>([this, request](const KAsync::Error &error) { 361 }).then<void>([this, request](const KAsync::Error &error) {
362 setStatusFromResult(error, "Synchronization has ended.", request.requestId);
324 if (error) { 363 if (error) {
325 //Emit notification with error 364 //Emit notification with error
326 SinkWarningCtx(mLogCtx) << "Synchronization failed: " << error.errorMessage; 365 SinkWarningCtx(mLogCtx) << "Synchronization failed: " << error;
327 Sink::Notification n; 366 emitNotification(Notification::Warning, ApplicationDomain::SyncError, {}, {}, request.applicableEntities);
328 n.id = request.requestId;
329 n.type = Notification::Status;
330 n.message = "Synchronization has ended.";
331 n.code = ApplicationDomain::ErrorStatus;
332 emit notify(n);
333 return KAsync::error(error); 367 return KAsync::error(error);
334 } else { 368 } else {
335 SinkLogCtx(mLogCtx) << "Done Synchronizing"; 369 SinkLogCtx(mLogCtx) << "Done Synchronizing";
336 Sink::Notification n; 370 emitNotification(Notification::Info, ApplicationDomain::SyncSuccess, {}, {}, request.applicableEntities);
337 n.id = request.requestId;
338 n.type = Notification::Status;
339 n.message = "Synchronization has ended.";
340 n.code = ApplicationDomain::ConnectedStatus;
341 emit notify(n);
342 return KAsync::null(); 371 return KAsync::null();
343 } 372 }
344 }); 373 });
@@ -347,11 +376,8 @@ KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request)
347 Q_ASSERT(!request.requestId.isEmpty()); 376 Q_ASSERT(!request.requestId.isEmpty());
348 //FIXME it looks like this is emitted before the replay actually finishes 377 //FIXME it looks like this is emitted before the replay actually finishes
349 if (request.flushType == Flush::FlushReplayQueue) { 378 if (request.flushType == Flush::FlushReplayQueue) {
350 SinkTraceCtx(mLogCtx) << "Emitting flush completion."; 379 SinkTraceCtx(mLogCtx) << "Emitting flush completion: " << request.requestId;
351 Sink::Notification n; 380 emitNotification(Notification::FlushCompletion, 0, "", request.requestId);
352 n.type = Sink::Notification::FlushCompletion;
353 n.id = request.requestId;
354 emit notify(n);
355 } else { 381 } else {
356 flatbuffers::FlatBufferBuilder fbb; 382 flatbuffers::FlatBufferBuilder fbb;
357 auto flushId = fbb.CreateString(request.requestId.toStdString()); 383 auto flushId = fbb.CreateString(request.requestId.toStdString());
@@ -361,7 +387,24 @@ KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request)
361 } 387 }
362 }); 388 });
363 } else if (request.requestType == Synchronizer::SyncRequest::ChangeReplay) { 389 } else if (request.requestType == Synchronizer::SyncRequest::ChangeReplay) {
364 return replayNextRevision(); 390 if (ChangeReplay::allChangesReplayed()) {
391 return KAsync::null();
392 } else {
393 return KAsync::start([this, request] {
394 SinkLogCtx(mLogCtx) << "Replaying changes.";
395 })
396 .then(replayNextRevision())
397 .then<void>([this, request](const KAsync::Error &error) {
398 setStatusFromResult(error, "Changereplay has ended.", "changereplay");
399 if (error) {
400 SinkWarningCtx(mLogCtx) << "Changereplay failed: " << error.errorMessage;
401 return KAsync::error(error);
402 } else {
403 SinkLogCtx(mLogCtx) << "Done replaying changes";
404 return KAsync::null();
405 }
406 });
407 }
365 } else { 408 } else {
366 SinkWarningCtx(mLogCtx) << "Unknown request type: " << request.requestType; 409 SinkWarningCtx(mLogCtx) << "Unknown request type: " << request.requestType;
367 return KAsync::error(KAsync::Error{"Unknown request type."}); 410 return KAsync::error(KAsync::Error{"Unknown request type."});
@@ -369,6 +412,34 @@ KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request)
369 412
370} 413}
371 414
415void Synchronizer::setStatus(ApplicationDomain::Status state, const QString &reason, const QByteArray requestId)
416{
417 if (state != mCurrentState.top()) {
418 if (mCurrentState.top() == ApplicationDomain::BusyStatus) {
419 mCurrentState.pop();
420 }
421 mCurrentState.push(state);
422 emitNotification(Notification::Status, state, reason, requestId);
423 }
424}
425
426void Synchronizer::resetStatus(const QByteArray requestId)
427{
428 mCurrentState.pop();
429 emitNotification(Notification::Status, mCurrentState.top(), {}, requestId);
430}
431
432void Synchronizer::setBusy(bool busy, const QString &reason, const QByteArray requestId)
433{
434 if (busy) {
435 setStatus(ApplicationDomain::BusyStatus, reason, requestId);
436 } else {
437 if (mCurrentState.top() == ApplicationDomain::BusyStatus) {
438 resetStatus(requestId);
439 }
440 }
441}
442
372KAsync::Job<void> Synchronizer::processSyncQueue() 443KAsync::Job<void> Synchronizer::processSyncQueue()
373{ 444{
374 if (mSyncRequestQueue.isEmpty()) { 445 if (mSyncRequestQueue.isEmpty()) {
@@ -387,14 +458,20 @@ KAsync::Job<void> Synchronizer::processSyncQueue()
387 } 458 }
388 459
389 const auto request = mSyncRequestQueue.takeFirst(); 460 const auto request = mSyncRequestQueue.takeFirst();
390 return KAsync::start([this] { 461 return KAsync::start([=] {
391 mMessageQueue->startTransaction(); 462 mMessageQueue->startTransaction();
392 mEntityStore->startTransaction(Sink::Storage::DataStore::ReadOnly); 463 mEntityStore->startTransaction(Sink::Storage::DataStore::ReadOnly);
393 mSyncInProgress = true; 464 mSyncInProgress = true;
465 if (request.requestType == Synchronizer::SyncRequest::Synchronization) {
466 setBusy(true, "Synchronization has started.", request.requestId);
467 } else if (request.requestType == Synchronizer::SyncRequest::ChangeReplay) {
468 setBusy(true, "ChangeReplay has started.", "changereplay");
469 }
394 }) 470 })
395 .then(processRequest(request)) 471 .then(processRequest(request))
396 .then<void>([this](const KAsync::Error &error) { 472 .then<void>([this, request](const KAsync::Error &error) {
397 SinkTraceCtx(mLogCtx) << "Sync request processed"; 473 SinkTraceCtx(mLogCtx) << "Sync request processed";
474 setBusy(false, {}, request.requestId);
398 mEntityStore->abortTransaction(); 475 mEntityStore->abortTransaction();
399 mSyncTransaction.abort(); 476 mSyncTransaction.abort();
400 mMessageQueue->commit(); 477 mMessageQueue->commit();
@@ -404,8 +481,8 @@ KAsync::Job<void> Synchronizer::processSyncQueue()
404 emit changesReplayed(); 481 emit changesReplayed();
405 } 482 }
406 if (error) { 483 if (error) {
407 SinkWarningCtx(mLogCtx) << "Error during sync: " << error.errorMessage; 484 SinkWarningCtx(mLogCtx) << "Error during sync: " << error;
408 return KAsync::error(error); 485 emitNotification(Notification::Error, error.errorCode, error.errorMessage, request.requestId);
409 } 486 }
410 //In case we got more requests meanwhile. 487 //In case we got more requests meanwhile.
411 return processSyncQueue(); 488 return processSyncQueue();
@@ -499,6 +576,12 @@ KAsync::Job<void> Synchronizer::replay(const QByteArray &type, const QByteArray
499 } else if (type == ApplicationDomain::getTypeName<ApplicationDomain::Mail>()) { 576 } else if (type == ApplicationDomain::getTypeName<ApplicationDomain::Mail>()) {
500 auto mail = store().readEntity<ApplicationDomain::Mail>(key); 577 auto mail = store().readEntity<ApplicationDomain::Mail>(key);
501 job = replay(mail, operation, oldRemoteId, modifiedProperties); 578 job = replay(mail, operation, oldRemoteId, modifiedProperties);
579 } else if (type == ApplicationDomain::getTypeName<ApplicationDomain::Contact>()) {
580 auto mail = store().readEntity<ApplicationDomain::Contact>(key);
581 job = replay(mail, operation, oldRemoteId, modifiedProperties);
582 } else if (type == ApplicationDomain::getTypeName<ApplicationDomain::Addressbook>()) {
583 auto mail = store().readEntity<ApplicationDomain::Addressbook>(key);
584 job = replay(mail, operation, oldRemoteId, modifiedProperties);
502 } else { 585 } else {
503 SinkErrorCtx(mLogCtx) << "Replayed unknown type: " << type; 586 SinkErrorCtx(mLogCtx) << "Replayed unknown type: " << type;
504 } 587 }
@@ -506,21 +589,19 @@ KAsync::Job<void> Synchronizer::replay(const QByteArray &type, const QByteArray
506 return job.then([this, operation, type, uid, oldRemoteId](const QByteArray &remoteId) { 589 return job.then([this, operation, type, uid, oldRemoteId](const QByteArray &remoteId) {
507 if (operation == Sink::Operation_Creation) { 590 if (operation == Sink::Operation_Creation) {
508 SinkTraceCtx(mLogCtx) << "Replayed creation with remote id: " << remoteId; 591 SinkTraceCtx(mLogCtx) << "Replayed creation with remote id: " << remoteId;
509 if (remoteId.isEmpty()) { 592 if (!remoteId.isEmpty()) {
510 SinkWarningCtx(mLogCtx) << "Returned an empty remoteId from the creation";
511 } else {
512 syncStore().recordRemoteId(type, uid, remoteId); 593 syncStore().recordRemoteId(type, uid, remoteId);
513 } 594 }
514 } else if (operation == Sink::Operation_Modification) { 595 } else if (operation == Sink::Operation_Modification) {
515 SinkTraceCtx(mLogCtx) << "Replayed modification with remote id: " << remoteId; 596 SinkTraceCtx(mLogCtx) << "Replayed modification with remote id: " << remoteId;
516 if (remoteId.isEmpty()) { 597 if (!remoteId.isEmpty()) {
517 SinkWarningCtx(mLogCtx) << "Returned an empty remoteId from the modification";
518 } else {
519 syncStore().updateRemoteId(type, uid, remoteId); 598 syncStore().updateRemoteId(type, uid, remoteId);
520 } 599 }
521 } else if (operation == Sink::Operation_Removal) { 600 } else if (operation == Sink::Operation_Removal) {
522 SinkTraceCtx(mLogCtx) << "Replayed removal with remote id: " << oldRemoteId; 601 SinkTraceCtx(mLogCtx) << "Replayed removal with remote id: " << oldRemoteId;
523 syncStore().removeRemoteId(type, uid, oldRemoteId); 602 if (!oldRemoteId.isEmpty()) {
603 syncStore().removeRemoteId(type, uid, oldRemoteId);
604 }
524 } else { 605 } else {
525 SinkErrorCtx(mLogCtx) << "Unkown operation" << operation; 606 SinkErrorCtx(mLogCtx) << "Unkown operation" << operation;
526 } 607 }
@@ -539,6 +620,11 @@ KAsync::Job<QByteArray> Synchronizer::replay(const ApplicationDomain::Contact &,
539 return KAsync::null<QByteArray>(); 620 return KAsync::null<QByteArray>();
540} 621}
541 622
623KAsync::Job<QByteArray> Synchronizer::replay(const ApplicationDomain::Addressbook &, Sink::Operation, const QByteArray &, const QList<QByteArray> &)
624{
625 return KAsync::null<QByteArray>();
626}
627
542KAsync::Job<QByteArray> Synchronizer::replay(const ApplicationDomain::Mail &, Sink::Operation, const QByteArray &, const QList<QByteArray> &) 628KAsync::Job<QByteArray> Synchronizer::replay(const ApplicationDomain::Mail &, Sink::Operation, const QByteArray &, const QList<QByteArray> &)
543{ 629{
544 return KAsync::null<QByteArray>(); 630 return KAsync::null<QByteArray>();
diff --git a/common/synchronizer.h b/common/synchronizer.h
index 120a8a5..b1ee122 100644
--- a/common/synchronizer.h
+++ b/common/synchronizer.h
@@ -21,6 +21,7 @@
21 21
22#include "sink_export.h" 22#include "sink_export.h"
23#include <QObject> 23#include <QObject>
24#include <QStack>
24#include <KAsync/Async> 25#include <KAsync/Async>
25#include <domainadaptor.h> 26#include <domainadaptor.h>
26#include <query.h> 27#include <query.h>
@@ -73,9 +74,9 @@ protected:
73protected: 74protected:
74 ///Implement to write back changes to the server 75 ///Implement to write back changes to the server
75 virtual KAsync::Job<QByteArray> replay(const Sink::ApplicationDomain::Contact &, Sink::Operation, const QByteArray &oldRemoteId, const QList<QByteArray> &); 76 virtual KAsync::Job<QByteArray> replay(const Sink::ApplicationDomain::Contact &, Sink::Operation, const QByteArray &oldRemoteId, const QList<QByteArray> &);
77 virtual KAsync::Job<QByteArray> replay(const Sink::ApplicationDomain::Addressbook &, Sink::Operation, const QByteArray &oldRemoteId, const QList<QByteArray> &);
76 virtual KAsync::Job<QByteArray> replay(const Sink::ApplicationDomain::Mail &, Sink::Operation, const QByteArray &oldRemoteId, const QList<QByteArray> &); 78 virtual KAsync::Job<QByteArray> replay(const Sink::ApplicationDomain::Mail &, Sink::Operation, const QByteArray &oldRemoteId, const QList<QByteArray> &);
77 virtual KAsync::Job<QByteArray> replay(const Sink::ApplicationDomain::Folder &, Sink::Operation, const QByteArray &oldRemoteId, const QList<QByteArray> &); 79 virtual KAsync::Job<QByteArray> replay(const Sink::ApplicationDomain::Folder &, Sink::Operation, const QByteArray &oldRemoteId, const QList<QByteArray> &);
78
79protected: 80protected:
80 ///Calls the callback to enqueue the command 81 ///Calls the callback to enqueue the command
81 void enqueueCommand(int commandId, const QByteArray &data); 82 void enqueueCommand(int commandId, const QByteArray &data);
@@ -134,7 +135,8 @@ protected:
134 : requestId(requestId_), 135 : requestId(requestId_),
135 requestType(Synchronization), 136 requestType(Synchronization),
136 options(o), 137 options(o),
137 query(q) 138 query(q),
139 applicableEntities(q.ids())
138 { 140 {
139 } 141 }
140 142
@@ -155,6 +157,7 @@ protected:
155 RequestType requestType; 157 RequestType requestType;
156 RequestOptions options = NoOptions; 158 RequestOptions options = NoOptions;
157 Sink::QueryBase query; 159 Sink::QueryBase query;
160 QByteArrayList applicableEntities;
158 }; 161 };
159 162
160 /** 163 /**
@@ -175,9 +178,28 @@ protected:
175 */ 178 */
176 virtual QList<Synchronizer::SyncRequest> getSyncRequests(const Sink::QueryBase &query); 179 virtual QList<Synchronizer::SyncRequest> getSyncRequests(const Sink::QueryBase &query);
177 180
181 /**
182 * This allows the synchronizer to merge new requests with existing requests in the queue.
183 */
184 virtual void mergeIntoQueue(const Synchronizer::SyncRequest &request, QList<Synchronizer::SyncRequest> &queue);
185
186 void emitNotification(Notification::NoticationType type, int code, const QString &message, const QByteArray &id = QByteArray{}, const QByteArrayList &entiteis = QByteArrayList{});
187
188 /**
189 * Report progress for current task
190 */
191 void reportProgress(int progress, int total);
192
178protected: 193protected:
179 Sink::Log::Context mLogCtx; 194 Sink::Log::Context mLogCtx;
195
180private: 196private:
197 QStack<ApplicationDomain::Status> mCurrentState;
198 void setStatusFromResult(const KAsync::Error &error, const QString &s, const QByteArray &requestId);
199 void setStatus(ApplicationDomain::Status busy, const QString &reason, const QByteArray requestId);
200 void resetStatus(const QByteArray requestId);
201 void setBusy(bool busy, const QString &reason, const QByteArray requestId);
202
181 void modifyIfChanged(Storage::EntityStore &store, const QByteArray &bufferType, const QByteArray &sinkId, const Sink::ApplicationDomain::ApplicationDomainType &entity); 203 void modifyIfChanged(Storage::EntityStore &store, const QByteArray &bufferType, const QByteArray &sinkId, const Sink::ApplicationDomain::ApplicationDomainType &entity);
182 KAsync::Job<void> processRequest(const SyncRequest &request); 204 KAsync::Job<void> processRequest(const SyncRequest &request);
183 KAsync::Job<void> processSyncQueue(); 205 KAsync::Job<void> processSyncQueue();
diff --git a/common/synchronizerstore.cpp b/common/synchronizerstore.cpp
index dea4821..5364094 100644
--- a/common/synchronizerstore.cpp
+++ b/common/synchronizerstore.cpp
@@ -73,7 +73,8 @@ QByteArray SynchronizerStore::resolveLocalId(const QByteArray &bufferType, const
73{ 73{
74 QByteArray remoteId = Index("localid.mapping." + bufferType, mTransaction).lookup(localId); 74 QByteArray remoteId = Index("localid.mapping." + bufferType, mTransaction).lookup(localId);
75 if (remoteId.isEmpty()) { 75 if (remoteId.isEmpty()) {
76 SinkWarning() << "Couldn't find the remote id for " << bufferType << localId; 76 //This can happen if we didn't store the remote id in the first place
77 SinkTrace() << "Couldn't find the remote id for " << bufferType << localId;
77 return QByteArray(); 78 return QByteArray();
78 } 79 }
79 return remoteId; 80 return remoteId;
diff --git a/common/typeindex.cpp b/common/typeindex.cpp
index 5589e13..153aa43 100644
--- a/common/typeindex.cpp
+++ b/common/typeindex.cpp
@@ -190,7 +190,7 @@ static QVector<QByteArray> indexLookup(Index &index, QueryBase::Comparator filte
190 190
191 for (const auto &lookupKey : lookupKeys) { 191 for (const auto &lookupKey : lookupKeys) {
192 index.lookup(lookupKey, [&](const QByteArray &value) { keys << value; }, 192 index.lookup(lookupKey, [&](const QByteArray &value) { keys << value; },
193 [lookupKey](const Index::Error &error) { SinkWarning() << "Error in index: " << error.message << lookupKey; }, true); 193 [lookupKey](const Index::Error &error) { SinkWarning() << "Lookup error in index: " << error.message << lookupKey; }, true);
194 } 194 }
195 return keys; 195 return keys;
196} 196}
@@ -272,7 +272,7 @@ QVector<QByteArray> TypeIndex::secondaryLookup<QByteArray>(const QByteArray &lef
272 Index index(indexName(leftName + rightName), *mTransaction); 272 Index index(indexName(leftName + rightName), *mTransaction);
273 const auto lookupKey = getByteArray(value); 273 const auto lookupKey = getByteArray(value);
274 index.lookup( 274 index.lookup(
275 lookupKey, [&](const QByteArray &value) { keys << value; }, [=](const Index::Error &error) { SinkWarning() << "Error in index: " << error.message << value; }); 275 lookupKey, [&](const QByteArray &value) { keys << value; }, [=](const Index::Error &error) { SinkWarning() << "Lookup error in secondary index: " << error.message << value << lookupKey; });
276 276
277 return keys; 277 return keys;
278} 278}
@@ -284,7 +284,7 @@ QVector<QByteArray> TypeIndex::secondaryLookup<QString>(const QByteArray &leftNa
284 Index index(indexName(leftName + rightName), *mTransaction); 284 Index index(indexName(leftName + rightName), *mTransaction);
285 const auto lookupKey = getByteArray(value); 285 const auto lookupKey = getByteArray(value);
286 index.lookup( 286 index.lookup(
287 lookupKey, [&](const QByteArray &value) { keys << value; }, [=](const Index::Error &error) { SinkWarning() << "Error in index: " << error.message << value; }); 287 lookupKey, [&](const QByteArray &value) { keys << value; }, [=](const Index::Error &error) { SinkWarning() << "Lookup error in secondary index: " << error.message << value << lookupKey; });
288 288
289 return keys; 289 return keys;
290} 290}