summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2017-03-24 22:15:18 +0100
committerChristian Mollekopf <chrigi_1@fastmail.fm>2017-03-24 22:15:18 +0100
commit9ea268a6d0f4054c31b2729ecd6cfcc9d07a2d6a (patch)
tree41fef7daac9e5ae64d61452a3f38c82243d29fdd
parent84d70933c0cd0987d5fee5a78f413fec82bb1288 (diff)
downloadsink-9ea268a6d0f4054c31b2729ecd6cfcc9d07a2d6a.tar.gz
sink-9ea268a6d0f4054c31b2729ecd6cfcc9d07a2d6a.zip
Implemented notification support in the model.
This will allow us to fold things like progress and sync status directly into the model. Usecases are mail download progress and folder sync progress. Ideally we would also solve the resource/account state through this.
-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"