diff options
-rw-r--r-- | common/CMakeLists.txt | 1 | ||||
-rw-r--r-- | common/clientapi.cpp | 17 | ||||
-rw-r--r-- | common/clientapi.h | 25 | ||||
-rw-r--r-- | common/commands.cpp | 2 | ||||
-rw-r--r-- | common/commands.h | 1 | ||||
-rw-r--r-- | common/commands/inspection.fbs | 12 | ||||
-rw-r--r-- | common/commands/notification.fbs | 5 | ||||
-rw-r--r-- | common/genericresource.cpp | 39 | ||||
-rw-r--r-- | common/genericresource.h | 1 | ||||
-rw-r--r-- | common/listener.cpp | 3 | ||||
-rw-r--r-- | common/notification.h | 40 | ||||
-rw-r--r-- | common/resource.h | 2 | ||||
-rw-r--r-- | common/resourceaccess.cpp | 22 | ||||
-rw-r--r-- | common/resourceaccess.h | 4 | ||||
-rw-r--r-- | docs/resource.md | 7 | ||||
-rw-r--r-- | examples/dummyresource/resourcefactory.cpp | 20 | ||||
-rw-r--r-- | examples/dummyresource/resourcefactory.h | 1 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/inspectiontest.cpp | 64 |
19 files changed, 267 insertions, 1 deletions
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index f07772a..7e142df 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt | |||
@@ -51,6 +51,7 @@ generate_flatbuffers( | |||
51 | commands/synchronize | 51 | commands/synchronize |
52 | commands/notification | 52 | commands/notification |
53 | commands/revisionreplayed | 53 | commands/revisionreplayed |
54 | commands/inspection | ||
54 | domain/event | 55 | domain/event |
55 | domain/mail | 56 | domain/mail |
56 | domain/folder | 57 | domain/folder |
diff --git a/common/clientapi.cpp b/common/clientapi.cpp index 5c0bcb8..deab962 100644 --- a/common/clientapi.cpp +++ b/common/clientapi.cpp | |||
@@ -25,6 +25,7 @@ | |||
25 | #include <QEventLoop> | 25 | #include <QEventLoop> |
26 | #include <QAbstractItemModel> | 26 | #include <QAbstractItemModel> |
27 | #include <QDir> | 27 | #include <QDir> |
28 | #include <QUuid> | ||
28 | #include <functional> | 29 | #include <functional> |
29 | #include <memory> | 30 | #include <memory> |
30 | 31 | ||
@@ -289,10 +290,26 @@ KAsync::Job<QList<typename DomainType::Ptr> > Store::fetch(const Akonadi2::Query | |||
289 | }); | 290 | }); |
290 | } | 291 | } |
291 | 292 | ||
293 | template <class DomainType> | ||
294 | KAsync::Job<void> Resources::inspect(const Inspection &inspectionCommand) | ||
295 | { | ||
296 | auto resource = inspectionCommand.resourceIdentifier; | ||
297 | |||
298 | Trace() << "Sending inspection " << resource; | ||
299 | auto resourceAccess = QSharedPointer<Akonadi2::ResourceAccess>::create(resource); | ||
300 | resourceAccess->open(); | ||
301 | auto id = QUuid::createUuid().toByteArray(); | ||
302 | return resourceAccess->sendInspectionCommand(id, ApplicationDomain::getTypeName<DomainType>(), inspectionCommand.entityIdentifier, inspectionCommand.property, inspectionCommand.expectedValue) | ||
303 | .template then<void>([resourceAccess]() { | ||
304 | //TODO wait for inspection notification | ||
305 | }); | ||
306 | } | ||
307 | |||
292 | #define REGISTER_TYPE(T) template KAsync::Job<void> Store::remove<T>(const T &domainObject); \ | 308 | #define REGISTER_TYPE(T) template KAsync::Job<void> Store::remove<T>(const T &domainObject); \ |
293 | template KAsync::Job<void> Store::create<T>(const T &domainObject); \ | 309 | template KAsync::Job<void> Store::create<T>(const T &domainObject); \ |
294 | template KAsync::Job<void> Store::modify<T>(const T &domainObject); \ | 310 | template KAsync::Job<void> Store::modify<T>(const T &domainObject); \ |
295 | template QSharedPointer<QAbstractItemModel> Store::loadModel<T>(Query query); \ | 311 | template QSharedPointer<QAbstractItemModel> Store::loadModel<T>(Query query); \ |
312 | template KAsync::Job<void> Resources::inspect<T>(const Inspection &); \ | ||
296 | template KAsync::Job<T> Store::fetchOne<T>(const Query &); \ | 313 | template KAsync::Job<T> Store::fetchOne<T>(const Query &); \ |
297 | template KAsync::Job<QList<T::Ptr> > Store::fetchAll<T>(const Query &); \ | 314 | template KAsync::Job<QList<T::Ptr> > Store::fetchAll<T>(const Query &); \ |
298 | template KAsync::Job<QList<T::Ptr> > Store::fetch<T>(const Query &, int); \ | 315 | template KAsync::Job<QList<T::Ptr> > Store::fetch<T>(const Query &, int); \ |
diff --git a/common/clientapi.h b/common/clientapi.h index eff8e8d..d496715 100644 --- a/common/clientapi.h +++ b/common/clientapi.h | |||
@@ -111,6 +111,31 @@ public: | |||
111 | static KAsync::Job<QList<typename DomainType::Ptr> > fetch(const Akonadi2::Query &query, int minimumAmount = 0); | 111 | static KAsync::Job<QList<typename DomainType::Ptr> > fetch(const Akonadi2::Query &query, int minimumAmount = 0); |
112 | }; | 112 | }; |
113 | 113 | ||
114 | namespace Resources { | ||
115 | struct Inspection { | ||
116 | static Inspection PropertyInspection(const Akonadi2::ApplicationDomain::Entity &entity, const QByteArray &property, const QVariant &expectedValue) | ||
117 | { | ||
118 | Inspection inspection; | ||
119 | inspection.resourceIdentifier = entity.resourceInstanceIdentifier(); | ||
120 | inspection.entityIdentifier = entity.identifier(); | ||
121 | inspection.property = property; | ||
122 | inspection.expectedValue = expectedValue; | ||
123 | return inspection; | ||
124 | } | ||
125 | |||
126 | enum Type { | ||
127 | PropertyInspectionType | ||
128 | }; | ||
129 | |||
130 | QByteArray resourceIdentifier; | ||
131 | QByteArray entityIdentifier; | ||
132 | QByteArray property; | ||
133 | QVariant expectedValue; | ||
134 | }; | ||
135 | template <class DomainType> | ||
136 | KAsync::Job<void> inspect(const Inspection &inspectionCommand); | ||
137 | } | ||
138 | |||
114 | 139 | ||
115 | } | 140 | } |
116 | 141 | ||
diff --git a/common/commands.cpp b/common/commands.cpp index 7a0ae23..16fd742 100644 --- a/common/commands.cpp +++ b/common/commands.cpp | |||
@@ -59,6 +59,8 @@ QByteArray name(int commandId) | |||
59 | return "Ping"; | 59 | return "Ping"; |
60 | case RevisionReplayedCommand: | 60 | case RevisionReplayedCommand: |
61 | return "RevisionReplayed"; | 61 | return "RevisionReplayed"; |
62 | case InspectionCommand: | ||
63 | return "Inspection"; | ||
62 | case CustomCommand: | 64 | case CustomCommand: |
63 | return "Custom"; | 65 | return "Custom"; |
64 | }; | 66 | }; |
diff --git a/common/commands.h b/common/commands.h index c68ef90..bce278c 100644 --- a/common/commands.h +++ b/common/commands.h | |||
@@ -47,6 +47,7 @@ enum CommandIds { | |||
47 | NotificationCommand, | 47 | NotificationCommand, |
48 | PingCommand, | 48 | PingCommand, |
49 | RevisionReplayedCommand, | 49 | RevisionReplayedCommand, |
50 | InspectionCommand, | ||
50 | CustomCommand = 0xffff | 51 | CustomCommand = 0xffff |
51 | }; | 52 | }; |
52 | 53 | ||
diff --git a/common/commands/inspection.fbs b/common/commands/inspection.fbs new file mode 100644 index 0000000..aaae1ae --- /dev/null +++ b/common/commands/inspection.fbs | |||
@@ -0,0 +1,12 @@ | |||
1 | namespace Akonadi2.Commands; | ||
2 | |||
3 | table Inspection { | ||
4 | id: string; | ||
5 | type: int; | ||
6 | entityId: string; | ||
7 | domainType: string; | ||
8 | property: string; | ||
9 | expectedValue: string; | ||
10 | } | ||
11 | |||
12 | root_type Inspection; | ||
diff --git a/common/commands/notification.fbs b/common/commands/notification.fbs index 6684472..eb00986 100644 --- a/common/commands/notification.fbs +++ b/common/commands/notification.fbs | |||
@@ -1,9 +1,12 @@ | |||
1 | namespace Akonadi2; | 1 | namespace Akonadi2; |
2 | 2 | ||
3 | enum NotificationType : byte { Shutdown = 1, Status, Warning, Progress } | 3 | enum NotificationType : byte { Shutdown = 1, Status, Warning, Progress, Inspection } |
4 | enum NotificationCode : byte { Success = 0, Failure = 1, UserCode } | ||
4 | 5 | ||
5 | table Notification { | 6 | table Notification { |
6 | type: NotificationType = Status; | 7 | type: NotificationType = Status; |
8 | message: string; | ||
9 | code: int = 0; //Of type NotificationCode | ||
7 | } | 10 | } |
8 | 11 | ||
9 | root_type Notification; | 12 | root_type Notification; |
diff --git a/common/genericresource.cpp b/common/genericresource.cpp index 29acce4..90fc763 100644 --- a/common/genericresource.cpp +++ b/common/genericresource.cpp | |||
@@ -6,6 +6,7 @@ | |||
6 | #include "createentity_generated.h" | 6 | #include "createentity_generated.h" |
7 | #include "modifyentity_generated.h" | 7 | #include "modifyentity_generated.h" |
8 | #include "deleteentity_generated.h" | 8 | #include "deleteentity_generated.h" |
9 | #include "inspection_generated.h" | ||
9 | #include "domainadaptor.h" | 10 | #include "domainadaptor.h" |
10 | #include "commands.h" | 11 | #include "commands.h" |
11 | #include "index.h" | 12 | #include "index.h" |
@@ -13,6 +14,7 @@ | |||
13 | #include "definitions.h" | 14 | #include "definitions.h" |
14 | 15 | ||
15 | #include <QUuid> | 16 | #include <QUuid> |
17 | #include <QDataStream> | ||
16 | 18 | ||
17 | static int sBatchSize = 100; | 19 | static int sBatchSize = 100; |
18 | 20 | ||
@@ -112,6 +114,7 @@ private: | |||
112 | class CommandProcessor : public QObject | 114 | class CommandProcessor : public QObject |
113 | { | 115 | { |
114 | Q_OBJECT | 116 | Q_OBJECT |
117 | typedef std::function<KAsync::Job<void>(void const *, size_t)> InspectionFunction; | ||
115 | public: | 118 | public: |
116 | CommandProcessor(Akonadi2::Pipeline *pipeline, QList<MessageQueue*> commandQueues) | 119 | CommandProcessor(Akonadi2::Pipeline *pipeline, QList<MessageQueue*> commandQueues) |
117 | : QObject(), | 120 | : QObject(), |
@@ -135,6 +138,11 @@ public: | |||
135 | mLowerBoundRevision = revision; | 138 | mLowerBoundRevision = revision; |
136 | } | 139 | } |
137 | 140 | ||
141 | void setInspectionCommand(const InspectionFunction &f) | ||
142 | { | ||
143 | mInspect = f; | ||
144 | } | ||
145 | |||
138 | 146 | ||
139 | signals: | 147 | signals: |
140 | void error(int errorCode, const QString &errorMessage); | 148 | void error(int errorCode, const QString &errorMessage); |
@@ -176,6 +184,14 @@ private slots: | |||
176 | return mPipeline->modifiedEntity(queuedCommand->command()->Data(), queuedCommand->command()->size()); | 184 | return mPipeline->modifiedEntity(queuedCommand->command()->Data(), queuedCommand->command()->size()); |
177 | case Akonadi2::Commands::CreateEntityCommand: | 185 | case Akonadi2::Commands::CreateEntityCommand: |
178 | return mPipeline->newEntity(queuedCommand->command()->Data(), queuedCommand->command()->size()); | 186 | return mPipeline->newEntity(queuedCommand->command()->Data(), queuedCommand->command()->size()); |
187 | case Akonadi2::Commands::InspectionCommand: | ||
188 | if (mInspect) { | ||
189 | return mInspect(queuedCommand->command()->Data(), queuedCommand->command()->size()).then<qint64>([]() { | ||
190 | return -1; | ||
191 | }); | ||
192 | } else { | ||
193 | return KAsync::error<qint64>(-1, "Missing inspection command."); | ||
194 | } | ||
179 | default: | 195 | default: |
180 | return KAsync::error<qint64>(-1, "Unhandled command"); | 196 | return KAsync::error<qint64>(-1, "Unhandled command"); |
181 | } | 197 | } |
@@ -266,6 +282,7 @@ private: | |||
266 | bool mProcessingLock; | 282 | bool mProcessingLock; |
267 | //The lowest revision we no longer need | 283 | //The lowest revision we no longer need |
268 | qint64 mLowerBoundRevision; | 284 | qint64 mLowerBoundRevision; |
285 | InspectionFunction mInspect; | ||
269 | }; | 286 | }; |
270 | 287 | ||
271 | 288 | ||
@@ -279,6 +296,22 @@ GenericResource::GenericResource(const QByteArray &resourceInstanceIdentifier, c | |||
279 | mClientLowerBoundRevision(std::numeric_limits<qint64>::max()) | 296 | mClientLowerBoundRevision(std::numeric_limits<qint64>::max()) |
280 | { | 297 | { |
281 | mProcessor = new CommandProcessor(mPipeline.data(), QList<MessageQueue*>() << &mUserQueue << &mSynchronizerQueue); | 298 | mProcessor = new CommandProcessor(mPipeline.data(), QList<MessageQueue*>() << &mUserQueue << &mSynchronizerQueue); |
299 | mProcessor->setInspectionCommand([this](void const *command, size_t size) { | ||
300 | flatbuffers::Verifier verifier((const uint8_t *)command, size); | ||
301 | if (Akonadi2::Commands::VerifyInspectionBuffer(verifier)) { | ||
302 | auto buffer = Akonadi2::Commands::GetInspection(command); | ||
303 | int inspectionType = buffer->type(); | ||
304 | QByteArray entityId = QByteArray::fromRawData(reinterpret_cast<const char *>(buffer->entityId()->Data()), buffer->entityId()->size()); | ||
305 | QByteArray domainType = QByteArray::fromRawData(reinterpret_cast<const char *>(buffer->domainType()->Data()), buffer->domainType()->size()); | ||
306 | QByteArray property = QByteArray::fromRawData(reinterpret_cast<const char *>(buffer->property()->Data()), buffer->property()->size()); | ||
307 | QByteArray expectedValueString = QByteArray::fromRawData(reinterpret_cast<const char *>(buffer->expectedValue()->Data()), buffer->expectedValue()->size()); | ||
308 | QDataStream s(expectedValueString); | ||
309 | QVariant expectedValue; | ||
310 | s >> expectedValue; | ||
311 | return inspect(inspectionType, domainType, entityId, property, expectedValue); | ||
312 | } | ||
313 | return KAsync::error<void>(-1, "Invalid inspection command."); | ||
314 | }); | ||
282 | QObject::connect(mProcessor, &CommandProcessor::error, [this](int errorCode, const QString &msg) { onProcessorError(errorCode, msg); }); | 315 | QObject::connect(mProcessor, &CommandProcessor::error, [this](int errorCode, const QString &msg) { onProcessorError(errorCode, msg); }); |
283 | QObject::connect(mPipeline.data(), &Pipeline::revisionUpdated, this, &Resource::revisionUpdated); | 316 | QObject::connect(mPipeline.data(), &Pipeline::revisionUpdated, this, &Resource::revisionUpdated); |
284 | mSourceChangeReplay = new ChangeReplay(resourceInstanceIdentifier, [this](const QByteArray &type, const QByteArray &key, const QByteArray &value) { | 317 | mSourceChangeReplay = new ChangeReplay(resourceInstanceIdentifier, [this](const QByteArray &type, const QByteArray &key, const QByteArray &value) { |
@@ -301,6 +334,12 @@ GenericResource::~GenericResource() | |||
301 | delete mSourceChangeReplay; | 334 | delete mSourceChangeReplay; |
302 | } | 335 | } |
303 | 336 | ||
337 | KAsync::Job<void> GenericResource::inspect(int inspectionType, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) | ||
338 | { | ||
339 | Warning() << "Inspection not implemented"; | ||
340 | return KAsync::null<void>(); | ||
341 | } | ||
342 | |||
304 | void GenericResource::enableChangeReplay(bool enable) | 343 | void GenericResource::enableChangeReplay(bool enable) |
305 | { | 344 | { |
306 | if (enable) { | 345 | if (enable) { |
diff --git a/common/genericresource.h b/common/genericresource.h index f47c6f8..90b7c29 100644 --- a/common/genericresource.h +++ b/common/genericresource.h | |||
@@ -48,6 +48,7 @@ public: | |||
48 | virtual KAsync::Job<void> synchronizeWithSource(Akonadi2::Storage &mainStore, Akonadi2::Storage &synchronizationStore); | 48 | virtual KAsync::Job<void> synchronizeWithSource(Akonadi2::Storage &mainStore, Akonadi2::Storage &synchronizationStore); |
49 | virtual KAsync::Job<void> processAllMessages() Q_DECL_OVERRIDE; | 49 | virtual KAsync::Job<void> processAllMessages() Q_DECL_OVERRIDE; |
50 | virtual void setLowerBoundRevision(qint64 revision) Q_DECL_OVERRIDE; | 50 | virtual void setLowerBoundRevision(qint64 revision) Q_DECL_OVERRIDE; |
51 | virtual KAsync::Job<void> inspect(int inspectionType, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue); | ||
51 | 52 | ||
52 | int error() const; | 53 | int error() const; |
53 | 54 | ||
diff --git a/common/listener.cpp b/common/listener.cpp index 518aca9..5d55202 100644 --- a/common/listener.cpp +++ b/common/listener.cpp | |||
@@ -31,11 +31,13 @@ | |||
31 | #include "common/synchronize_generated.h" | 31 | #include "common/synchronize_generated.h" |
32 | #include "common/notification_generated.h" | 32 | #include "common/notification_generated.h" |
33 | #include "common/revisionreplayed_generated.h" | 33 | #include "common/revisionreplayed_generated.h" |
34 | #include "common/inspection_generated.h" | ||
34 | 35 | ||
35 | #include <QLocalServer> | 36 | #include <QLocalServer> |
36 | #include <QLocalSocket> | 37 | #include <QLocalSocket> |
37 | #include <QTimer> | 38 | #include <QTimer> |
38 | #include <QTime> | 39 | #include <QTime> |
40 | #include <QDataStream> | ||
39 | 41 | ||
40 | Listener::Listener(const QByteArray &resourceInstanceIdentifier, QObject *parent) | 42 | Listener::Listener(const QByteArray &resourceInstanceIdentifier, QObject *parent) |
41 | : QObject(parent), | 43 | : QObject(parent), |
@@ -241,6 +243,7 @@ void Listener::processCommand(int commandId, uint messageId, const QByteArray &c | |||
241 | } | 243 | } |
242 | break; | 244 | break; |
243 | } | 245 | } |
246 | case Akonadi2::Commands::InspectionCommand: | ||
244 | case Akonadi2::Commands::FetchEntityCommand: | 247 | case Akonadi2::Commands::FetchEntityCommand: |
245 | case Akonadi2::Commands::DeleteEntityCommand: | 248 | case Akonadi2::Commands::DeleteEntityCommand: |
246 | case Akonadi2::Commands::ModifyEntityCommand: | 249 | case Akonadi2::Commands::ModifyEntityCommand: |
diff --git a/common/notification.h b/common/notification.h new file mode 100644 index 0000000..8ffc24c --- /dev/null +++ b/common/notification.h | |||
@@ -0,0 +1,40 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | * | ||
4 | * This library is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU Lesser General Public | ||
6 | * License as published by the Free Software Foundation; either | ||
7 | * version 2.1 of the License, or (at your option) version 3, or any | ||
8 | * later version accepted by the membership of KDE e.V. (or its | ||
9 | * successor approved by the membership of KDE e.V.), which shall | ||
10 | * act as a proxy defined in Section 6 of version 3 of the license. | ||
11 | * | ||
12 | * This library is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * Lesser General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU Lesser General Public | ||
18 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. | ||
19 | */ | ||
20 | #pragma once | ||
21 | |||
22 | #include <akonadi2common_export.h> | ||
23 | #include <QString> | ||
24 | #include "notification_generated.h" | ||
25 | |||
26 | namespace Akonadi2 | ||
27 | { | ||
28 | |||
29 | /** | ||
30 | * A notification | ||
31 | */ | ||
32 | class AKONADI2COMMON_EXPORT ResourceNotification | ||
33 | { | ||
34 | public: | ||
35 | int type; | ||
36 | QString message; | ||
37 | int code; | ||
38 | }; | ||
39 | |||
40 | } | ||
diff --git a/common/resource.h b/common/resource.h index 4ed21b5..9a31d03 100644 --- a/common/resource.h +++ b/common/resource.h | |||
@@ -21,6 +21,7 @@ | |||
21 | #include <akonadi2common_export.h> | 21 | #include <akonadi2common_export.h> |
22 | 22 | ||
23 | #include <Async/Async> | 23 | #include <Async/Async> |
24 | #include "notification.h" | ||
24 | 25 | ||
25 | namespace Akonadi2 | 26 | namespace Akonadi2 |
26 | { | 27 | { |
@@ -55,6 +56,7 @@ public: | |||
55 | 56 | ||
56 | Q_SIGNALS: | 57 | Q_SIGNALS: |
57 | void revisionUpdated(qint64); | 58 | void revisionUpdated(qint64); |
59 | void notify(ResourceNotification); | ||
58 | 60 | ||
59 | private: | 61 | private: |
60 | class Private; | 62 | class Private; |
diff --git a/common/resourceaccess.cpp b/common/resourceaccess.cpp index 7be1259..65e9a8c 100644 --- a/common/resourceaccess.cpp +++ b/common/resourceaccess.cpp | |||
@@ -30,6 +30,7 @@ | |||
30 | #include "common/modifyentity_generated.h" | 30 | #include "common/modifyentity_generated.h" |
31 | #include "common/deleteentity_generated.h" | 31 | #include "common/deleteentity_generated.h" |
32 | #include "common/revisionreplayed_generated.h" | 32 | #include "common/revisionreplayed_generated.h" |
33 | #include "common/inspection_generated.h" | ||
33 | #include "common/entitybuffer.h" | 34 | #include "common/entitybuffer.h" |
34 | #include "log.h" | 35 | #include "log.h" |
35 | 36 | ||
@@ -37,6 +38,8 @@ | |||
37 | #include <QDebug> | 38 | #include <QDebug> |
38 | #include <QDir> | 39 | #include <QDir> |
39 | #include <QProcess> | 40 | #include <QProcess> |
41 | #include <QDataStream> | ||
42 | #include <QBuffer> | ||
40 | 43 | ||
41 | #undef Trace | 44 | #undef Trace |
42 | #define Trace() Akonadi2::Log::debugStream(Akonadi2::Log::DebugLevel::Trace, __LINE__, __FILE__, Q_FUNC_INFO, "ResourceAccess") | 45 | #define Trace() Akonadi2::Log::debugStream(Akonadi2::Log::DebugLevel::Trace, __LINE__, __FILE__, Q_FUNC_INFO, "ResourceAccess") |
@@ -338,6 +341,25 @@ KAsync::Job<void> ResourceAccess::sendRevisionReplayedCommand(qint64 revision) | |||
338 | return sendCommand(Akonadi2::Commands::RevisionReplayedCommand, fbb); | 341 | return sendCommand(Akonadi2::Commands::RevisionReplayedCommand, fbb); |
339 | } | 342 | } |
340 | 343 | ||
344 | KAsync::Job<void> ResourceAccess::sendInspectionCommand(const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) | ||
345 | { | ||
346 | flatbuffers::FlatBufferBuilder fbb; | ||
347 | auto id = fbb.CreateString(inspectionId.toStdString()); | ||
348 | auto domain = fbb.CreateString(domainType.toStdString()); | ||
349 | auto entity = fbb.CreateString(entityId.toStdString()); | ||
350 | auto prop = fbb.CreateString(property.toStdString()); | ||
351 | |||
352 | QByteArray array; | ||
353 | QDataStream s(&array, QIODevice::WriteOnly); | ||
354 | s << expectedValue; | ||
355 | |||
356 | auto expected = fbb.CreateString(array.toStdString()); | ||
357 | auto location = Akonadi2::Commands::CreateInspection (fbb, id, 0, entity, domain, prop, expected); | ||
358 | Akonadi2::Commands::FinishInspectionBuffer(fbb, location); | ||
359 | open(); | ||
360 | return sendCommand(Akonadi2::Commands::InspectionCommand, fbb); | ||
361 | } | ||
362 | |||
341 | void ResourceAccess::open() | 363 | void ResourceAccess::open() |
342 | { | 364 | { |
343 | if (d->socket && d->socket->isValid()) { | 365 | if (d->socket && d->socket->isValid()) { |
diff --git a/common/resourceaccess.h b/common/resourceaccess.h index 7f61b30..fe3fa99 100644 --- a/common/resourceaccess.h +++ b/common/resourceaccess.h | |||
@@ -27,6 +27,7 @@ | |||
27 | #include <Async/Async> | 27 | #include <Async/Async> |
28 | 28 | ||
29 | #include <flatbuffers/flatbuffers.h> | 29 | #include <flatbuffers/flatbuffers.h> |
30 | #include "notification.h" | ||
30 | 31 | ||
31 | namespace Akonadi2 | 32 | namespace Akonadi2 |
32 | { | 33 | { |
@@ -49,10 +50,12 @@ public: | |||
49 | virtual KAsync::Job<void> sendModifyCommand(const QByteArray &uid, qint64 revision, const QByteArray &resourceBufferType, const QByteArrayList &deletedProperties, const QByteArray &buffer) { return KAsync::null<void>(); }; | 50 | virtual KAsync::Job<void> sendModifyCommand(const QByteArray &uid, qint64 revision, const QByteArray &resourceBufferType, const QByteArrayList &deletedProperties, const QByteArray &buffer) { return KAsync::null<void>(); }; |
50 | virtual KAsync::Job<void> sendDeleteCommand(const QByteArray &uid, qint64 revision, const QByteArray &resourceBufferType) { return KAsync::null<void>(); }; | 51 | virtual KAsync::Job<void> sendDeleteCommand(const QByteArray &uid, qint64 revision, const QByteArray &resourceBufferType) { return KAsync::null<void>(); }; |
51 | virtual KAsync::Job<void> sendRevisionReplayedCommand(qint64 revision) {return KAsync::null<void>(); }; | 52 | virtual KAsync::Job<void> sendRevisionReplayedCommand(qint64 revision) {return KAsync::null<void>(); }; |
53 | virtual KAsync::Job<void> sendInspectionCommand(const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expecedValue) {return KAsync::null<void>(); }; | ||
52 | 54 | ||
53 | Q_SIGNALS: | 55 | Q_SIGNALS: |
54 | void ready(bool isReady); | 56 | void ready(bool isReady); |
55 | void revisionChanged(qint64 revision); | 57 | void revisionChanged(qint64 revision); |
58 | void notification(ResourceNotification revision); | ||
56 | 59 | ||
57 | public Q_SLOTS: | 60 | public Q_SLOTS: |
58 | virtual void open() = 0; | 61 | virtual void open() = 0; |
@@ -78,6 +81,7 @@ public: | |||
78 | KAsync::Job<void> sendModifyCommand(const QByteArray &uid, qint64 revision, const QByteArray &resourceBufferType, const QByteArrayList &deletedProperties, const QByteArray &buffer) Q_DECL_OVERRIDE; | 81 | KAsync::Job<void> sendModifyCommand(const QByteArray &uid, qint64 revision, const QByteArray &resourceBufferType, const QByteArrayList &deletedProperties, const QByteArray &buffer) Q_DECL_OVERRIDE; |
79 | KAsync::Job<void> sendDeleteCommand(const QByteArray &uid, qint64 revision, const QByteArray &resourceBufferType) Q_DECL_OVERRIDE; | 82 | KAsync::Job<void> sendDeleteCommand(const QByteArray &uid, qint64 revision, const QByteArray &resourceBufferType) Q_DECL_OVERRIDE; |
80 | KAsync::Job<void> sendRevisionReplayedCommand(qint64 revision) Q_DECL_OVERRIDE; | 83 | KAsync::Job<void> sendRevisionReplayedCommand(qint64 revision) Q_DECL_OVERRIDE; |
84 | KAsync::Job<void> sendInspectionCommand(const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expecedValue) Q_DECL_OVERRIDE; | ||
81 | /** | 85 | /** |
82 | * Tries to connect to server, and returns a connected socket on success. | 86 | * Tries to connect to server, and returns a connected socket on success. |
83 | */ | 87 | */ |
diff --git a/docs/resource.md b/docs/resource.md index c8f58e9..0988535 100644 --- a/docs/resource.md +++ b/docs/resource.md | |||
@@ -141,3 +141,10 @@ The remoteid mapping has to be updated in two places: | |||
141 | 141 | ||
142 | * New entities that are synchronized immediately get a localid assinged, that is then recorded together with the remoteid. This is required to be able to reference other entities directly in the command queue (i.e. for parent folders). | 142 | * New entities that are synchronized immediately get a localid assinged, that is then recorded together with the remoteid. This is required to be able to reference other entities directly in the command queue (i.e. for parent folders). |
143 | * Entities created by clients get a remoteid assigned during change replay, so the entity can be recognized during the next sync. | 143 | * Entities created by clients get a remoteid assigned during change replay, so the entity can be recognized during the next sync. |
144 | |||
145 | # Testing / Inspection | ||
146 | Resources new to be tested, which often requires inspections into the current state of the resource. This is difficult in an asynchronous system where the whole backend logic is encapsulated in a separate process without running tests in a vastly different setup from how it will be run in production. | ||
147 | |||
148 | To alleviate this inspection commands are introduced. Inspection commands are special commands that the resource processes just like all other commands, and that have the sole purpose of inspecting the current resource state. Because the command is processed with the same mechanism as other commands we can rely on ordering of commands in a way that a prior command is guaranteed to be executed once the inspection command is processed. | ||
149 | |||
150 | A typical inspection command could i.e. verify that a file has been created in the expected path after a create command. | ||
diff --git a/examples/dummyresource/resourcefactory.cpp b/examples/dummyresource/resourcefactory.cpp index a984097..27d5f17 100644 --- a/examples/dummyresource/resourcefactory.cpp +++ b/examples/dummyresource/resourcefactory.cpp | |||
@@ -134,6 +134,26 @@ void DummyResource::removeFromDisk(const QByteArray &instanceIdentifier) | |||
134 | Akonadi2::Storage(Akonadi2::storageLocation(), instanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite).removeFromDisk(); | 134 | Akonadi2::Storage(Akonadi2::storageLocation(), instanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite).removeFromDisk(); |
135 | } | 135 | } |
136 | 136 | ||
137 | KAsync::Job<void> DummyResource::inspect(int inspectionType, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) | ||
138 | { | ||
139 | |||
140 | Trace() << "Inspecting " << inspectionType << domainType << entityId << property << expectedValue; | ||
141 | if (property == "testInspection") { | ||
142 | Akonadi2::ResourceNotification n; | ||
143 | n.type = Akonadi2::NotificationType_Inspection; | ||
144 | if (expectedValue.toBool()) { | ||
145 | //Success | ||
146 | n.code = 0; | ||
147 | emit notify(n); | ||
148 | } else { | ||
149 | //Failure | ||
150 | n.code = 1; | ||
151 | emit notify(n); | ||
152 | } | ||
153 | } | ||
154 | return KAsync::null<void>(); | ||
155 | } | ||
156 | |||
137 | DummyResourceFactory::DummyResourceFactory(QObject *parent) | 157 | DummyResourceFactory::DummyResourceFactory(QObject *parent) |
138 | : Akonadi2::ResourceFactory(parent) | 158 | : Akonadi2::ResourceFactory(parent) |
139 | { | 159 | { |
diff --git a/examples/dummyresource/resourcefactory.h b/examples/dummyresource/resourcefactory.h index 2ed4c5d..3f67187 100644 --- a/examples/dummyresource/resourcefactory.h +++ b/examples/dummyresource/resourcefactory.h | |||
@@ -40,6 +40,7 @@ public: | |||
40 | KAsync::Job<void> synchronizeWithSource(Akonadi2::Storage &mainStore, Akonadi2::Storage &synchronizationStore) Q_DECL_OVERRIDE; | 40 | KAsync::Job<void> synchronizeWithSource(Akonadi2::Storage &mainStore, Akonadi2::Storage &synchronizationStore) Q_DECL_OVERRIDE; |
41 | using GenericResource::synchronizeWithSource; | 41 | using GenericResource::synchronizeWithSource; |
42 | static void removeFromDisk(const QByteArray &instanceIdentifier); | 42 | static void removeFromDisk(const QByteArray &instanceIdentifier); |
43 | KAsync::Job<void> inspect(int inspectionType, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) Q_DECL_OVERRIDE; | ||
43 | private: | 44 | private: |
44 | KAsync::Job<void> replay(Akonadi2::Storage &synchronizationStore, const QByteArray &type, const QByteArray &key, const QByteArray &value) Q_DECL_OVERRIDE; | 45 | KAsync::Job<void> replay(Akonadi2::Storage &synchronizationStore, const QByteArray &type, const QByteArray &key, const QByteArray &value) Q_DECL_OVERRIDE; |
45 | Akonadi2::ApplicationDomain::Event::Ptr createEvent(const QByteArray &rid, const QMap<QString, QVariant> &data, Akonadi2::Storage::Transaction &); | 46 | Akonadi2::ApplicationDomain::Event::Ptr createEvent(const QByteArray &rid, const QMap<QString, QVariant> &data, Akonadi2::Storage::Transaction &); |
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1e0f6b5..38e5512 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt | |||
@@ -51,12 +51,14 @@ auto_tests ( | |||
51 | databasepopulationandfacadequerybenchmark | 51 | databasepopulationandfacadequerybenchmark |
52 | dummyresourcewritebenchmark | 52 | dummyresourcewritebenchmark |
53 | modelinteractivitytest | 53 | modelinteractivitytest |
54 | inspectiontest | ||
54 | ) | 55 | ) |
55 | target_link_libraries(dummyresourcetest akonadi2_resource_dummy) | 56 | target_link_libraries(dummyresourcetest akonadi2_resource_dummy) |
56 | target_link_libraries(dummyresourcebenchmark akonadi2_resource_dummy) | 57 | target_link_libraries(dummyresourcebenchmark akonadi2_resource_dummy) |
57 | target_link_libraries(dummyresourcewritebenchmark akonadi2_resource_dummy) | 58 | target_link_libraries(dummyresourcewritebenchmark akonadi2_resource_dummy) |
58 | target_link_libraries(querytest akonadi2_resource_dummy) | 59 | target_link_libraries(querytest akonadi2_resource_dummy) |
59 | target_link_libraries(modelinteractivitytest akonadi2_resource_dummy) | 60 | target_link_libraries(modelinteractivitytest akonadi2_resource_dummy) |
61 | target_link_libraries(inspectiontest akonadi2_resource_dummy) | ||
60 | 62 | ||
61 | if (BUILD_MAILDIR) | 63 | if (BUILD_MAILDIR) |
62 | auto_tests ( | 64 | auto_tests ( |
diff --git a/tests/inspectiontest.cpp b/tests/inspectiontest.cpp new file mode 100644 index 0000000..e332844 --- /dev/null +++ b/tests/inspectiontest.cpp | |||
@@ -0,0 +1,64 @@ | |||
1 | #include <QtTest> | ||
2 | |||
3 | #include <QString> | ||
4 | |||
5 | #include "dummyresource/resourcefactory.h" | ||
6 | #include "clientapi.h" | ||
7 | #include "commands.h" | ||
8 | #include "entitybuffer.h" | ||
9 | #include "resourceconfig.h" | ||
10 | #include "modelresult.h" | ||
11 | #include "pipeline.h" | ||
12 | #include "log.h" | ||
13 | |||
14 | /** | ||
15 | * Test of inspection system using the dummy resource. | ||
16 | * | ||
17 | * This test requires the dummy resource installed. | ||
18 | */ | ||
19 | class InspectionTest : public QObject | ||
20 | { | ||
21 | Q_OBJECT | ||
22 | private Q_SLOTS: | ||
23 | void initTestCase() | ||
24 | { | ||
25 | Akonadi2::Log::setDebugOutputLevel(Akonadi2::Log::Trace); | ||
26 | auto factory = Akonadi2::ResourceFactory::load("org.kde.dummy"); | ||
27 | QVERIFY(factory); | ||
28 | DummyResource::removeFromDisk("org.kde.dummy.instance1"); | ||
29 | ResourceConfig::addResource("org.kde.dummy.instance1", "org.kde.dummy"); | ||
30 | } | ||
31 | |||
32 | void cleanup() | ||
33 | { | ||
34 | Akonadi2::Store::shutdown(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished(); | ||
35 | DummyResource::removeFromDisk("org.kde.dummy.instance1"); | ||
36 | auto factory = Akonadi2::ResourceFactory::load("org.kde.dummy"); | ||
37 | QVERIFY(factory); | ||
38 | Akonadi2::Store::start(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished(); | ||
39 | } | ||
40 | |||
41 | void init() | ||
42 | { | ||
43 | qDebug(); | ||
44 | qDebug() << "-----------------------------------------"; | ||
45 | qDebug(); | ||
46 | } | ||
47 | |||
48 | void testMarkMailAsRead() | ||
49 | { | ||
50 | using namespace Akonadi2; | ||
51 | using namespace Akonadi2::ApplicationDomain; | ||
52 | |||
53 | Mail mail(QByteArray("org.kde.dummy.instance1"), QByteArray("identifier"), 0, QSharedPointer<MemoryBufferAdaptor::MemoryBufferAdaptor>::create()); | ||
54 | |||
55 | auto inspectionCommand = Resources::Inspection::PropertyInspection(mail, "unread", true); | ||
56 | auto result = Resources::inspect<Mail>(inspectionCommand).exec(); | ||
57 | result.waitForFinished(); | ||
58 | QVERIFY(!result.errorCode()); | ||
59 | Akonadi2::Store::flushMessageQueue(QByteArrayList() << QByteArray("org.kde.dummy.instance1")).exec().waitForFinished(); | ||
60 | } | ||
61 | }; | ||
62 | |||
63 | QTEST_MAIN(InspectionTest) | ||
64 | #include "inspectiontest.moc" | ||