summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/commands/notification.fbs3
-rw-r--r--common/domain/applicationdomaintype.h48
-rw-r--r--common/listener.cpp3
-rw-r--r--common/modelresult.cpp90
-rw-r--r--common/modelresult.h12
-rw-r--r--common/notification.cpp2
-rw-r--r--common/notification.h1
-rw-r--r--common/query.h9
-rw-r--r--common/resourceaccess.cpp7
-rw-r--r--common/store.h8
-rw-r--r--common/synchronizer.cpp6
-rw-r--r--common/synchronizer.h2
-rw-r--r--examples/dummyresource/resourcefactory.cpp1
-rw-r--r--tests/CMakeLists.txt2
-rw-r--r--tests/dummyresourcetest.cpp9
-rw-r--r--tests/notificationtest.cpp124
16 files changed, 290 insertions, 37 deletions
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/domain/applicationdomaintype.h b/common/domain/applicationdomaintype.h
index ef38d58..6fd2b90 100644
--- a/common/domain/applicationdomaintype.h
+++ b/common/domain/applicationdomaintype.h
@@ -93,7 +93,7 @@
93namespace Sink { 93namespace Sink {
94namespace ApplicationDomain { 94namespace ApplicationDomain {
95 95
96enum ErrorCode { 96enum SINK_EXPORT ErrorCode {
97 NoError = 0, 97 NoError = 0,
98 UnknownError, 98 UnknownError,
99 NoServerError, 99 NoServerError,
@@ -101,10 +101,33 @@ enum ErrorCode {
101 TransmissionError, 101 TransmissionError,
102}; 102};
103 103
104enum SuccessCode { 104enum SINK_EXPORT SuccessCode {
105 TransmissionSuccess 105 TransmissionSuccess
106}; 106};
107 107
108enum SINK_EXPORT SyncStatus {
109 NoSyncStatus,
110 SyncInProgress,
111 SyncError,
112 SyncSuccess
113};
114
115/**
116 * The status of an account or resource.
117 *
118 * It is set as follows:
119 * * By default the status is offline.
120 * * If a connection to the server could be established the status is Connected.
121 * * If an error occurred that keeps the resource from operating (so non transient), the resource enters the error state.
122 * * If a long running operation is started the resource goes to the busy state (and return to the previous state after that).
123 */
124enum SINK_EXPORT Status {
125 OfflineStatus,
126 ConnectedStatus,
127 BusyStatus,
128 ErrorStatus
129};
130
108struct SINK_EXPORT Error { 131struct SINK_EXPORT Error {
109 132
110}; 133};
@@ -113,6 +136,11 @@ struct SINK_EXPORT Progress {
113 136
114}; 137};
115 138
139/**
140 * Internal type.
141 *
142 * Represents a BLOB property.
143 */
116struct BLOB { 144struct BLOB {
117 BLOB() = default; 145 BLOB() = default;
118 BLOB(const BLOB &) = default; 146 BLOB(const BLOB &) = default;
@@ -410,22 +438,6 @@ struct SINK_EXPORT Mail : public Entity {
410 438
411SINK_EXPORT QDebug operator<< (QDebug d, const Mail::Contact &c); 439SINK_EXPORT QDebug operator<< (QDebug d, const Mail::Contact &c);
412 440
413/**
414 * The status of an account or resource.
415 *
416 * It is set as follows:
417 * * By default the status is offline.
418 * * If a connection to the server could be established the status is Connected.
419 * * If an error occurred that keeps the resource from operating (so non transient), the resource enters the error state.
420 * * If a long running operation is started the resource goes to the busy state (and return to the previous state after that).
421 */
422enum SINK_EXPORT Status {
423 OfflineStatus,
424 ConnectedStatus,
425 BusyStatus,
426 ErrorStatus
427};
428
429struct SINK_EXPORT Identity : public ApplicationDomainType { 441struct SINK_EXPORT Identity : public ApplicationDomainType {
430 static constexpr const char *name = "identity"; 442 static constexpr const char *name = "identity";
431 typedef QSharedPointer<Identity> Ptr; 443 typedef QSharedPointer<Identity> Ptr;
diff --git a/common/listener.cpp b/common/listener.cpp
index f18fe1d..96806ad 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"
@@ -406,11 +407,13 @@ void Listener::notify(const Sink::Notification &notification)
406{ 407{
407 auto messageString = m_fbb.CreateString(notification.message.toUtf8().constData(), notification.message.toUtf8().size()); 408 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()); 409 auto idString = m_fbb.CreateString(notification.id.constData(), notification.id.size());
410 auto entities = Sink::BufferUtils::toVector(m_fbb, notification.entities);
409 Sink::Commands::NotificationBuilder builder(m_fbb); 411 Sink::Commands::NotificationBuilder builder(m_fbb);
410 builder.add_type(notification.type); 412 builder.add_type(notification.type);
411 builder.add_code(notification.code); 413 builder.add_code(notification.code);
412 builder.add_identifier(idString); 414 builder.add_identifier(idString);
413 builder.add_message(messageString); 415 builder.add_message(messageString);
416 builder.add_entities(entities);
414 auto command = builder.Finish(); 417 auto command = builder.Finish();
415 Sink::Commands::FinishNotificationBuffer(m_fbb, command); 418 Sink::Commands::FinishNotificationBuffer(m_fbb, command);
416 for (Client &client : m_connections) { 419 for (Client &client : m_connections) {
diff --git a/common/modelresult.cpp b/common/modelresult.cpp
index 904766d..3edbec7 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,79 @@ 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 mNotifier.reset(new Sink::Notifier{query});
57 mNotifier->registerHandler([this](const Notification &notification) {
58 switch (notification.type) {
59 case Notification::Status:
60 case Notification::Warning:
61 case Notification::Error:
62 case Notification::Info:
63 case Notification::Progress:
64 //These are the notifications we care about
65 break;
66 default:
67 //We're not interested
68 return;
69 };
70 if (notification.resource.isEmpty()|| notification.entities.isEmpty()) {
71 return;
72 }
73
74 QVector<qint64> idList;
75 for (const auto &entity : notification.entities) {
76 auto id = getInternalIdentifer(notification.resource, entity);
77 if (mEntities.contains(id)) {
78 idList << id;
79 }
80 }
81
82 if (idList.isEmpty()) {
83 //We don't have this entity in our model
84 return;
85 }
86 const int newStatus = [&] {
87 if (notification.type == Notification::Warning || notification.type == Notification::Error) {
88 return ApplicationDomain::SyncStatus::SyncError;
89 }
90 if (notification.type == Notification::Info) {
91 switch (notification.code) {
92 case ApplicationDomain::SyncInProgress:
93 return ApplicationDomain::SyncInProgress;
94 case ApplicationDomain::SyncSuccess:
95 return ApplicationDomain::SyncSuccess;
96 case ApplicationDomain::SyncError:
97 return ApplicationDomain::SyncError;
98 case ApplicationDomain::NoSyncStatus:
99 break;
100 }
101 return ApplicationDomain::NoSyncStatus;
102 }
103 if (notification.type == Notification::Progress) {
104 return ApplicationDomain::SyncStatus::SyncInProgress;
105 }
106 return ApplicationDomain::NoSyncStatus;
107 }();
108
109 for (const auto id : idList) {
110 const auto oldStatus = mEntityStatus.value(id);
111 QVector<int> changedRoles;
112 if (oldStatus != newStatus) {
113 mEntityStatus.insert(id, newStatus);
114 changedRoles << StatusRole;
115 }
116
117 if (notification.type == Notification::Progress) {
118 changedRoles << ProgressRole;
119 } else if (notification.type == Notification::Warning || notification.type == Notification::Error) {
120 changedRoles << WarningRole;
121 }
122
123 const auto idx = createIndexFromId(id);
124 emit dataChanged(idx, idx, changedRoles);
125 }
126 });
127 }
47} 128}
48 129
49template <class T, class Ptr> 130template <class T, class Ptr>
@@ -60,7 +141,7 @@ qint64 ModelResult<T, Ptr>::parentId(const Ptr &value)
60 if (!mQuery.parentProperty().isEmpty()) { 141 if (!mQuery.parentProperty().isEmpty()) {
61 const auto identifier = value->getProperty(mQuery.parentProperty()).toByteArray(); 142 const auto identifier = value->getProperty(mQuery.parentProperty()).toByteArray();
62 if (!identifier.isEmpty()) { 143 if (!identifier.isEmpty()) {
63 return qHash(T(value->resourceInstanceIdentifier(), identifier, 0, QSharedPointer<Sink::ApplicationDomain::BufferAdaptor>())); 144 return getInternalIdentifer(value->resourceInstanceIdentifier(), identifier);
64 } 145 }
65 } 146 }
66 return 0; 147 return 0;
@@ -106,6 +187,9 @@ QVariant ModelResult<T, Ptr>::data(const QModelIndex &index, int role) const
106 if (role == ChildrenFetchedRole) { 187 if (role == ChildrenFetchedRole) {
107 return childrenFetched(index); 188 return childrenFetched(index);
108 } 189 }
190 if (role == StatusRole) {
191 return mEntityStatus.value(index.internalId());
192 }
109 if (role == Qt::DisplayRole && index.isValid()) { 193 if (role == Qt::DisplayRole && index.isValid()) {
110 if (index.column() < mPropertyColumns.size()) { 194 if (index.column() < mPropertyColumns.size()) {
111 Q_ASSERT(mEntities.contains(index.internalId())); 195 Q_ASSERT(mEntities.contains(index.internalId()));
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..806d04a 100644
--- a/common/notification.cpp
+++ b/common/notification.cpp
@@ -21,6 +21,6 @@
21 21
22QDebug operator<<(QDebug dbg, const Sink::Notification &n) 22QDebug operator<<(QDebug dbg, const Sink::Notification &n)
23{ 23{
24 dbg << "Notification(Id: " << n.id << ", Type: " << n.type << ", Code: " << n.code << ", Message: " << n.message << ")"; 24 dbg << "Notification(Type: " << n.type << "Id, : " << n.id << ", Code: " << n.code << ", Message: " << n.message << ", Entities: " << n.entities << ")";
25 return dbg.space(); 25 return dbg.space();
26} 26}
diff --git a/common/notification.h b/common/notification.h
index 4b52274..f5379fd 100644
--- a/common/notification.h
+++ b/common/notification.h
@@ -51,6 +51,7 @@ public:
51 }; 51 };
52 52
53 QByteArray id; 53 QByteArray id;
54 QByteArrayList entities;
54 int type = 0; 55 int type = 0;
55 QString message; 56 QString message;
56 //A return code. Zero typically indicates success. 57 //A return code. Zero typically indicates success.
diff --git a/common/query.h b/common/query.h
index 49c8d5e..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);
diff --git a/common/resourceaccess.cpp b/common/resourceaccess.cpp
index e48b624..9f4f14c 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
@@ -608,13 +609,17 @@ bool ResourceAccess::processMessageBuffer()
608 mResourceStatus = buffer->code(); 609 mResourceStatus = buffer->code();
609 SinkTrace() << "Updated status: " << mResourceStatus; 610 SinkTrace() << "Updated status: " << mResourceStatus;
610 [[clang::fallthrough]]; 611 [[clang::fallthrough]];
612 case Sink::Notification::Info:
613 [[clang::fallthrough]];
611 case Sink::Notification::Warning: 614 case Sink::Notification::Warning:
612 [[clang::fallthrough]]; 615 [[clang::fallthrough]];
616 case Sink::Notification::Error:
617 [[clang::fallthrough]];
613 case Sink::Notification::FlushCompletion: 618 case Sink::Notification::FlushCompletion:
614 [[clang::fallthrough]]; 619 [[clang::fallthrough]];
615 case Sink::Notification::Progress: { 620 case Sink::Notification::Progress: {
616 auto n = getNotification(buffer); 621 auto n = getNotification(buffer);
617 SinkTrace() << "Received notification: Type:" << n.type << "Message: " << n.message << "Code: " << n.code; 622 SinkTrace() << "Received notification: " << n;
618 n.resource = d->resourceInstanceIdentifier; 623 n.resource = d->resourceInstanceIdentifier;
619 emit notification(n); 624 emit notification(n);
620 } break; 625 } break;
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 4ed6e3a..ec896ed 100644
--- a/common/synchronizer.cpp
+++ b/common/synchronizer.cpp
@@ -292,13 +292,14 @@ void Synchronizer::flushComplete(const QByteArray &flushId)
292 } 292 }
293} 293}
294 294
295void Synchronizer::emitNotification(Notification::NoticationType type, int code, const QString &message, const QByteArray &id) 295void Synchronizer::emitNotification(Notification::NoticationType type, int code, const QString &message, const QByteArray &id, const QByteArrayList &entities)
296{ 296{
297 Sink::Notification n; 297 Sink::Notification n;
298 n.id = id; 298 n.id = id;
299 n.type = type; 299 n.type = type;
300 n.message = message; 300 n.message = message;
301 n.code = code; 301 n.code = code;
302 n.entities = entities;
302 emit notify(n); 303 emit notify(n);
303} 304}
304 305
@@ -328,6 +329,7 @@ KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request)
328 return KAsync::start([this, request] { 329 return KAsync::start([this, request] {
329 SinkLogCtx(mLogCtx) << "Synchronizing: " << request.query; 330 SinkLogCtx(mLogCtx) << "Synchronizing: " << request.query;
330 emitNotification(Notification::Status, ApplicationDomain::BusyStatus, "Synchronization has started.", request.requestId); 331 emitNotification(Notification::Status, ApplicationDomain::BusyStatus, "Synchronization has started.", request.requestId);
332 emitNotification(Notification::Info, ApplicationDomain::SyncInProgress, {}, {}, request.query.ids());
331 }).then(synchronizeWithSource(request.query)).then([this] { 333 }).then(synchronizeWithSource(request.query)).then([this] {
332 //Commit after every request, so implementations only have to commit more if they add a lot of data. 334 //Commit after every request, so implementations only have to commit more if they add a lot of data.
333 commit(); 335 commit();
@@ -335,10 +337,12 @@ KAsync::Job<void> Synchronizer::processRequest(const SyncRequest &request)
335 if (error) { 337 if (error) {
336 //Emit notification with error 338 //Emit notification with error
337 SinkWarningCtx(mLogCtx) << "Synchronization failed: " << error.errorMessage; 339 SinkWarningCtx(mLogCtx) << "Synchronization failed: " << error.errorMessage;
340 emitNotification(Notification::Warning, ApplicationDomain::SyncError, {}, {}, request.query.ids());
338 emitNotification(Notification::Status, ApplicationDomain::ErrorStatus, "Synchronization has ended.", request.requestId); 341 emitNotification(Notification::Status, ApplicationDomain::ErrorStatus, "Synchronization has ended.", request.requestId);
339 return KAsync::error(error); 342 return KAsync::error(error);
340 } else { 343 } else {
341 SinkLogCtx(mLogCtx) << "Done Synchronizing"; 344 SinkLogCtx(mLogCtx) << "Done Synchronizing";
345 emitNotification(Notification::Info, ApplicationDomain::SyncSuccess, {}, {}, request.query.ids());
342 emitNotification(Notification::Status, ApplicationDomain::ConnectedStatus, "Synchronization has ended.", request.requestId); 346 emitNotification(Notification::Status, ApplicationDomain::ConnectedStatus, "Synchronization has ended.", request.requestId);
343 return KAsync::null(); 347 return KAsync::null();
344 } 348 }
diff --git a/common/synchronizer.h b/common/synchronizer.h
index 28fe645..e3dbddc 100644
--- a/common/synchronizer.h
+++ b/common/synchronizer.h
@@ -180,7 +180,7 @@ protected:
180 */ 180 */
181 virtual void mergeIntoQueue(const Synchronizer::SyncRequest &request, QList<Synchronizer::SyncRequest> &queue); 181 virtual void mergeIntoQueue(const Synchronizer::SyncRequest &request, QList<Synchronizer::SyncRequest> &queue);
182 182
183 void emitNotification(Notification::NoticationType type, int code, const QString &message, const QByteArray &id = QByteArray{}); 183 void emitNotification(Notification::NoticationType type, int code, const QString &message, const QByteArray &id = QByteArray{}, const QByteArrayList &entiteis = QByteArrayList{});
184 184
185protected: 185protected:
186 Sink::Log::Context mLogCtx; 186 Sink::Log::Context mLogCtx;
diff --git a/examples/dummyresource/resourcefactory.cpp b/examples/dummyresource/resourcefactory.cpp
index e89edde..ece3440 100644
--- a/examples/dummyresource/resourcefactory.cpp
+++ b/examples/dummyresource/resourcefactory.cpp
@@ -69,6 +69,7 @@ class DummySynchronizer : public Sink::Synchronizer {
69 Sink::ApplicationDomain::Mail::Ptr createMail(const QByteArray &ridBuffer, const QMap<QString, QVariant> &data) 69 Sink::ApplicationDomain::Mail::Ptr createMail(const QByteArray &ridBuffer, const QMap<QString, QVariant> &data)
70 { 70 {
71 auto mail = Sink::ApplicationDomain::Mail::Ptr::create(); 71 auto mail = Sink::ApplicationDomain::Mail::Ptr::create();
72 mail->setExtractedMessageId(ridBuffer);
72 mail->setExtractedSubject(data.value("subject").toString()); 73 mail->setExtractedSubject(data.value("subject").toString());
73 mail->setExtractedSender(Sink::ApplicationDomain::Mail::Contact{data.value("senderName").toString(), data.value("senderEmail").toString()}); 74 mail->setExtractedSender(Sink::ApplicationDomain::Mail::Contact{data.value("senderName").toString(), data.value("senderEmail").toString()});
74 mail->setExtractedDate(data.value("date").toDateTime()); 75 mail->setExtractedDate(data.value("date").toDateTime());
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 7144d41..fef76bd 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -44,6 +44,7 @@ auto_tests (
44 testaccounttest 44 testaccounttest
45 dummyresourcemailtest 45 dummyresourcemailtest
46 interresourcemovetest 46 interresourcemovetest
47 notificationtest
47) 48)
48generate_flatbuffers(dummyresourcetest calendar) 49generate_flatbuffers(dummyresourcetest calendar)
49target_link_libraries(dummyresourcetest sink_resource_dummy) 50target_link_libraries(dummyresourcetest sink_resource_dummy)
@@ -52,3 +53,4 @@ target_link_libraries(dummyresourcewritebenchmark sink_resource_dummy)
52target_link_libraries(querytest sink_resource_dummy) 53target_link_libraries(querytest sink_resource_dummy)
53target_link_libraries(modelinteractivitytest sink_resource_dummy) 54target_link_libraries(modelinteractivitytest sink_resource_dummy)
54target_link_libraries(inspectiontest sink_resource_dummy) 55target_link_libraries(inspectiontest sink_resource_dummy)
56target_link_libraries(notificationtest sink_resource_dummy)
diff --git a/tests/dummyresourcetest.cpp b/tests/dummyresourcetest.cpp
index eea63c0..17df160 100644
--- a/tests/dummyresourcetest.cpp
+++ b/tests/dummyresourcetest.cpp
@@ -136,12 +136,7 @@ private slots:
136 void testResourceSync() 136 void testResourceSync()
137 { 137 {
138 ::DummyResource resource(getContext()); 138 ::DummyResource resource(getContext());
139 auto job = resource.synchronizeWithSource(Sink::QueryBase()); 139 VERIFYEXEC(resource.synchronizeWithSource(Sink::QueryBase()));
140 // TODO pass in optional timeout?
141 auto future = job.exec();
142 future.waitForFinished();
143 QVERIFY(!future.errorCode());
144 QVERIFY(future.isFinished());
145 QVERIFY(!resource.error()); 140 QVERIFY(!resource.error());
146 auto processAllMessagesFuture = resource.processAllMessages().exec(); 141 auto processAllMessagesFuture = resource.processAllMessages().exec();
147 processAllMessagesFuture.waitForFinished(); 142 processAllMessagesFuture.waitForFinished();
@@ -152,7 +147,7 @@ private slots:
152 const auto query = Query().resourceFilter("sink.dummy.instance1"); 147 const auto query = Query().resourceFilter("sink.dummy.instance1");
153 148
154 // Ensure all local data is processed 149 // Ensure all local data is processed
155 Sink::Store::synchronize(query).exec().waitForFinished(); 150 VERIFYEXEC(Sink::Store::synchronize(query));
156 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1")); 151 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
157 152
158 auto model = Sink::Store::loadModel<Event>(query); 153 auto model = Sink::Store::loadModel<Event>(query);
diff --git a/tests/notificationtest.cpp b/tests/notificationtest.cpp
new file mode 100644
index 0000000..9433586
--- /dev/null
+++ b/tests/notificationtest.cpp
@@ -0,0 +1,124 @@
1#include <QtTest>
2
3#include <QString>
4#include <QSignalSpy>
5
6#include "store.h"
7#include "resourceconfig.h"
8#include "resourcecontrol.h"
9#include "modelresult.h"
10#include "log.h"
11#include "test.h"
12#include "testutils.h"
13#include "notifier.h"
14#include "notification.h"
15
16using namespace Sink;
17using namespace Sink::ApplicationDomain;
18
19/**
20 * Test of complete system using the dummy resource.
21 *
22 * This test requires the dummy resource installed.
23 */
24class NotificationTest : public QObject
25{
26 Q_OBJECT
27
28private slots:
29 void initTestCase()
30 {
31 Sink::Test::initTest();
32 ResourceConfig::addResource("sink.dummy.instance1", "sink.dummy");
33 }
34
35 void cleanup()
36 {
37 VERIFYEXEC(Sink::Store::removeDataFromDisk(QByteArray("sink.dummy.instance1")));
38 }
39
40 void testSyncNotifications()
41 {
42 auto query = Query().resourceFilter("sink.dummy.instance1");
43 query.setType<ApplicationDomain::Mail>();
44 query.filter("id1");
45 query.filter("id2");
46
47 QList<Sink::Notification> statusNotifications;
48 QList<Sink::Notification> infoNotifications;
49 Sink::Notifier notifier("sink.dummy.instance1");
50 notifier.registerHandler([&] (const Sink::Notification &n){
51 SinkLogCtx(Sink::Log::Context{"dummyresourcetest"}) << "Received notification " << n;
52 if (n.type == Notification::Status) {
53 statusNotifications << n;
54 }
55 if (n.type == Notification::Info) {
56 infoNotifications << n;
57 }
58 });
59
60 // Ensure all local data is processed
61 VERIFYEXEC(Sink::Store::synchronize(query));
62 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
63
64 //FIXME it can happen that we get a changereplay notification pair first.
65 QTRY_COMPARE(statusNotifications.size(), 5);
66 //Sync
67 QCOMPARE(statusNotifications.at(0).code, static_cast<int>(ApplicationDomain::Status::ConnectedStatus));
68 QCOMPARE(statusNotifications.at(1).code, static_cast<int>(Sink::ApplicationDomain::Status::BusyStatus));
69 QCOMPARE(statusNotifications.at(2).code, static_cast<int>(Sink::ApplicationDomain::Status::ConnectedStatus));
70 //Changereplay
71 QCOMPARE(statusNotifications.at(3).code, static_cast<int>(Sink::ApplicationDomain::Status::BusyStatus));
72 QCOMPARE(statusNotifications.at(4).code, static_cast<int>(Sink::ApplicationDomain::Status::ConnectedStatus));
73
74 QTRY_COMPARE(infoNotifications.size(), 2);
75 QCOMPARE(infoNotifications.at(0).code, static_cast<int>(ApplicationDomain::SyncStatus::SyncInProgress));
76 QCOMPARE(infoNotifications.at(0).entities, QList<QByteArray>{} << "id1" << "id2");
77 QCOMPARE(infoNotifications.at(1).code, static_cast<int>(ApplicationDomain::SyncStatus::SyncSuccess));
78 QCOMPARE(infoNotifications.at(1).entities, QList<QByteArray>{} << "id1" << "id2");
79
80 QCOMPARE(infoNotifications.at(1).code, static_cast<int>(ApplicationDomain::SyncStatus::SyncSuccess));
81 }
82
83 void testModelNotifications()
84 {
85 auto query = Query().resourceFilter("sink.dummy.instance1");
86 query.setType<ApplicationDomain::Mail>();
87 query.setFlags(Query::LiveQuery | Query::UpdateStatus);
88
89 VERIFYEXEC(Sink::Store::synchronize(query));
90 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
91
92 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query);
93 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
94 QVERIFY(model->rowCount() >= 1);
95
96 QSignalSpy changedSpy(model.data(), &QAbstractItemModel::dataChanged);
97 auto mail = model->index(0, 0, QModelIndex()).data(Sink::Store::DomainObjectRole).value<Mail::Ptr>();
98 auto newQuery = query;
99 newQuery.filter(mail->identifier());
100
101 QList<int> status;
102 QObject::connect(model.data(), &QAbstractItemModel::dataChanged, [&] (const QModelIndex &begin, const QModelIndex &end, const QVector<int> &roles) {
103 QVERIFY(begin.row() == end.row());
104 if (begin.row() == 0) {
105 status << model->data(begin, Store::StatusRole).value<int>();
106 // qWarning() << "New status: " << status.last() << roles;
107 }
108 });
109
110 //This will trigger a modification of all previous items as well.
111 VERIFYEXEC(Sink::Store::synchronize(newQuery));
112 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
113
114 QCOMPARE(status.size(), 3);
115 //Sync progress of item
116 QCOMPARE(status.at(0), static_cast<int>(ApplicationDomain::SyncStatus::SyncInProgress));
117 QCOMPARE(status.at(1), static_cast<int>(ApplicationDomain::SyncStatus::SyncSuccess));
118 //Modification triggered during sync
119 QCOMPARE(status.at(2), static_cast<int>(ApplicationDomain::SyncStatus::SyncSuccess));
120 }
121};
122
123QTEST_MAIN(NotificationTest)
124#include "notificationtest.moc"