diff options
-rw-r--r-- | common/commands/notification.fbs | 3 | ||||
-rw-r--r-- | common/domain/applicationdomaintype.h | 48 | ||||
-rw-r--r-- | common/listener.cpp | 3 | ||||
-rw-r--r-- | common/modelresult.cpp | 90 | ||||
-rw-r--r-- | common/modelresult.h | 12 | ||||
-rw-r--r-- | common/notification.cpp | 2 | ||||
-rw-r--r-- | common/notification.h | 1 | ||||
-rw-r--r-- | common/query.h | 9 | ||||
-rw-r--r-- | common/resourceaccess.cpp | 7 | ||||
-rw-r--r-- | common/store.h | 8 | ||||
-rw-r--r-- | common/synchronizer.cpp | 6 | ||||
-rw-r--r-- | common/synchronizer.h | 2 | ||||
-rw-r--r-- | examples/dummyresource/resourcefactory.cpp | 1 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/dummyresourcetest.cpp | 9 | ||||
-rw-r--r-- | tests/notificationtest.cpp | 124 |
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 | ||
3 | table Notification { | 3 | table Notification { |
4 | type: int = 0; //See notification.h | 4 | type: int = 0; //See notification.h |
5 | identifier: string; //An identifier that links back to the something related to the notification (e.g. an entity id or a command id) | 5 | identifier: string; //An identifier that links back to the something related to the notification (e.g. a command id) |
6 | message: string; | 6 | message: string; |
7 | code: int = 0; //See notification.h | 7 | code: int = 0; //See notification.h |
8 | entities: [string]; //A list of entities this applies to | ||
8 | } | 9 | } |
9 | 10 | ||
10 | root_type Notification; | 11 | root_type Notification; |
diff --git a/common/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 @@ | |||
93 | namespace Sink { | 93 | namespace Sink { |
94 | namespace ApplicationDomain { | 94 | namespace ApplicationDomain { |
95 | 95 | ||
96 | enum ErrorCode { | 96 | enum 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 | ||
104 | enum SuccessCode { | 104 | enum SINK_EXPORT SuccessCode { |
105 | TransmissionSuccess | 105 | TransmissionSuccess |
106 | }; | 106 | }; |
107 | 107 | ||
108 | enum 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 | */ | ||
124 | enum SINK_EXPORT Status { | ||
125 | OfflineStatus, | ||
126 | ConnectedStatus, | ||
127 | BusyStatus, | ||
128 | ErrorStatus | ||
129 | }; | ||
130 | |||
108 | struct SINK_EXPORT Error { | 131 | struct 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 | */ | ||
116 | struct BLOB { | 144 | struct 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 | ||
411 | SINK_EXPORT QDebug operator<< (QDebug d, const Mail::Contact &c); | 439 | SINK_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 | */ | ||
422 | enum SINK_EXPORT Status { | ||
423 | OfflineStatus, | ||
424 | ConnectedStatus, | ||
425 | BusyStatus, | ||
426 | ErrorStatus | ||
427 | }; | ||
428 | |||
429 | struct SINK_EXPORT Identity : public ApplicationDomainType { | 441 | struct 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 ¬ification) | |||
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 | |||
30 | using namespace Sink; | ||
31 | |||
32 | static uint getInternalIdentifer(const QByteArray &resourceId, const QByteArray &entityId) | ||
33 | { | ||
34 | return qHash(resourceId + entityId); | ||
35 | } | ||
27 | 36 | ||
28 | static uint qHash(const Sink::ApplicationDomain::ApplicationDomainType &type) | 37 | static uint qHash(const Sink::ApplicationDomain::ApplicationDomainType &type) |
29 | { | 38 | { |
30 | // Q_ASSERT(!type.resourceInstanceIdentifier().isEmpty()); | ||
31 | Q_ASSERT(!type.identifier().isEmpty()); | 39 | Q_ASSERT(!type.identifier().isEmpty()); |
32 | return qHash(type.resourceInstanceIdentifier() + type.identifier()); | 40 | return getInternalIdentifer(type.resourceInstanceIdentifier(), type.identifier()); |
33 | } | 41 | } |
34 | 42 | ||
35 | static qint64 getIdentifier(const QModelIndex &idx) | 43 | static qint64 getIdentifier(const QModelIndex &idx) |
@@ -44,6 +52,79 @@ template <class T, class Ptr> | |||
44 | ModelResult<T, Ptr>::ModelResult(const Sink::Query &query, const QList<QByteArray> &propertyColumns, const Sink::Log::Context &ctx) | 52 | ModelResult<T, Ptr>::ModelResult(const Sink::Query &query, const QList<QByteArray> &propertyColumns, const Sink::Log::Context &ctx) |
45 | : QAbstractItemModel(), mLogCtx(ctx.subContext("modelresult")), mPropertyColumns(propertyColumns), mQuery(query) | 53 | : QAbstractItemModel(), mLogCtx(ctx.subContext("modelresult")), mPropertyColumns(propertyColumns), mQuery(query) |
46 | { | 54 | { |
55 | if (query.flags().testFlag(Sink::Query::UpdateStatus)) { | ||
56 | mNotifier.reset(new Sink::Notifier{query}); | ||
57 | mNotifier->registerHandler([this](const Notification ¬ification) { | ||
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 | ||
49 | template <class T, class Ptr> | 130 | template <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 | ||
33 | namespace Sink { | ||
34 | class Notifier; | ||
35 | } | ||
36 | |||
33 | template <class T, class Ptr> | 37 | template <class T, class Ptr> |
34 | class ModelResult : public QAbstractItemModel | 38 | class ModelResult : public QAbstractItemModel |
35 | { | 39 | { |
36 | public: | 40 | public: |
41 | //Update the copy in store.h as well if you modify this | ||
37 | enum Roles | 42 | enum Roles |
38 | { | 43 | { |
39 | DomainObjectRole = Qt::UserRole + 1, | 44 | DomainObjectRole = Qt::UserRole + 1, |
40 | ChildrenFetchedRole, | 45 | ChildrenFetchedRole, |
41 | DomainObjectBaseRole | 46 | DomainObjectBaseRole, |
47 | StatusRole, //ApplicationDomain::SyncStatus | ||
48 | WarningRole, //ApplicationDomain::Warning, only if status == warning || status == error | ||
49 | ProgressRole //ApplicationDomain::Progress | ||
42 | }; | 50 | }; |
43 | 51 | ||
44 | ModelResult(const Sink::Query &query, const QList<QByteArray> &propertyColumns, const Sink::Log::Context &); | 52 | ModelResult(const Sink::Query &query, const QList<QByteArray> &propertyColumns, const Sink::Log::Context &); |
@@ -77,9 +85,11 @@ private: | |||
77 | QSet<qint64 /* entity id */> mEntityChildrenFetched; | 85 | QSet<qint64 /* entity id */> mEntityChildrenFetched; |
78 | QSet<qint64 /* entity id */> mEntityChildrenFetchComplete; | 86 | QSet<qint64 /* entity id */> mEntityChildrenFetchComplete; |
79 | QSet<qint64 /* entity id */> mEntityAllChildrenFetched; | 87 | QSet<qint64 /* entity id */> mEntityAllChildrenFetched; |
88 | QMap<qint64 /* entity id */, int /* Status */> mEntityStatus; | ||
80 | QList<QByteArray> mPropertyColumns; | 89 | QList<QByteArray> mPropertyColumns; |
81 | Sink::Query mQuery; | 90 | Sink::Query mQuery; |
82 | std::function<void(const Ptr &)> loadEntities; | 91 | std::function<void(const Ptr &)> loadEntities; |
83 | typename Sink::ResultEmitter<Ptr>::Ptr mEmitter; | 92 | typename Sink::ResultEmitter<Ptr>::Ptr mEmitter; |
84 | async::ThreadBoundary threadBoundary; | 93 | async::ThreadBoundary threadBoundary; |
94 | QScopedPointer<Sink::Notifier> mNotifier; | ||
85 | }; | 95 | }; |
diff --git a/common/notification.cpp b/common/notification.cpp index b399d50..806d04a 100644 --- a/common/notification.cpp +++ b/common/notification.cpp | |||
@@ -21,6 +21,6 @@ | |||
21 | 21 | ||
22 | QDebug operator<<(QDebug dbg, const Sink::Notification &n) | 22 | QDebug 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 | */ |
49 | QString SINK_EXPORT getTemporaryFilePath(); | 49 | QString SINK_EXPORT getTemporaryFilePath(); |
50 | 50 | ||
51 | // Must be the same as in ModelResult | ||
51 | enum Roles | 52 | enum Roles |
52 | { | 53 | { |
53 | DomainObjectRole = Qt::UserRole + 1, // Must be the same as in ModelResult | 54 | DomainObjectRole = Qt::UserRole + 1, |
54 | ChildrenFetchedRole, | 55 | ChildrenFetchedRole, |
55 | DomainObjectBaseRole | 56 | DomainObjectBaseRole, |
57 | StatusRole, //ApplicationDomain::SyncStatus | ||
58 | WarningRole, //ApplicationDomain::Warning, only if status == warning || status == error | ||
59 | ProgressRole //ApplicationDomain::Progress | ||
56 | }; | 60 | }; |
57 | 61 | ||
58 | /** | 62 | /** |
diff --git a/common/synchronizer.cpp b/common/synchronizer.cpp index 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 | ||
295 | void Synchronizer::emitNotification(Notification::NoticationType type, int code, const QString &message, const QByteArray &id) | 295 | void 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 | ||
185 | protected: | 185 | protected: |
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 | ) |
48 | generate_flatbuffers(dummyresourcetest calendar) | 49 | generate_flatbuffers(dummyresourcetest calendar) |
49 | target_link_libraries(dummyresourcetest sink_resource_dummy) | 50 | target_link_libraries(dummyresourcetest sink_resource_dummy) |
@@ -52,3 +53,4 @@ target_link_libraries(dummyresourcewritebenchmark sink_resource_dummy) | |||
52 | target_link_libraries(querytest sink_resource_dummy) | 53 | target_link_libraries(querytest sink_resource_dummy) |
53 | target_link_libraries(modelinteractivitytest sink_resource_dummy) | 54 | target_link_libraries(modelinteractivitytest sink_resource_dummy) |
54 | target_link_libraries(inspectiontest sink_resource_dummy) | 55 | target_link_libraries(inspectiontest sink_resource_dummy) |
56 | target_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 | |||
16 | using namespace Sink; | ||
17 | using namespace Sink::ApplicationDomain; | ||
18 | |||
19 | /** | ||
20 | * Test of complete system using the dummy resource. | ||
21 | * | ||
22 | * This test requires the dummy resource installed. | ||
23 | */ | ||
24 | class NotificationTest : public QObject | ||
25 | { | ||
26 | Q_OBJECT | ||
27 | |||
28 | private slots: | ||
29 | void initTestCase() | ||
30 | { | ||
31 | Sink::Test::initTest(); | ||
32 | ResourceConfig::addResource("sink.dummy.instance1", "sink.dummy"); | ||
33 | } | ||
34 | |||
35 | void cleanup() | ||
36 | { | ||
37 | VERIFYEXEC(Sink::Store::removeDataFromDisk(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 | |||
123 | QTEST_MAIN(NotificationTest) | ||
124 | #include "notificationtest.moc" | ||