1 /*
2 * SPDX-FileCopyrightText: 2015 Daniel Vrátil <dvratil@redhat.com>
3 * SPDX-FileCopyrightText: 2016 Daniel Vrátil <dvratil@kde.org>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
8 #include "datastream_p_p.h"
9 #include "imapset_p.h"
10 #include "protocol_p.h"
11 #include "scope_p.h"
12
13 #include <type_traits>
14 #include <typeinfo>
15
16 #include <QHash>
17 #include <QJsonArray>
18 #include <QJsonObject>
19
20 #include <cassert>
21
22 // clazy:excludeall=function-args-by-value
23
24 #undef AKONADI_DECLARE_PRIVATE
25 #define AKONADI_DECLARE_PRIVATE(Class) \
26 inline Class##Private *Class::d_func() \
27 { \
28 return reinterpret_cast<Class##Private *>(d_ptr.data()); \
29 } \
30 inline const Class##Private *Class::d_func() const \
31 { \
32 return reinterpret_cast<const Class##Private *>(d_ptr.constData()); \
33 }
34
35 namespace Akonadi
36 {
37 namespace Protocol
38 {
operator <<(QDebug _dbg,Command::Type type)39 QDebug operator<<(QDebug _dbg, Command::Type type)
40 {
41 QDebug dbg(_dbg.noquote());
42
43 switch (type) {
44 case Command::Invalid:
45 return dbg << "Invalid";
46
47 case Command::Hello:
48 return dbg << "Hello";
49 case Command::Login:
50 return dbg << "Login";
51 case Command::Logout:
52 return dbg << "Logout";
53
54 case Command::Transaction:
55 return dbg << "Transaction";
56
57 case Command::CreateItem:
58 return dbg << "CreateItem";
59 case Command::CopyItems:
60 return dbg << "CopyItems";
61 case Command::DeleteItems:
62 return dbg << "DeleteItems";
63 case Command::FetchItems:
64 return dbg << "FetchItems";
65 case Command::LinkItems:
66 return dbg << "LinkItems";
67 case Command::ModifyItems:
68 return dbg << "ModifyItems";
69 case Command::MoveItems:
70 return dbg << "MoveItems";
71
72 case Command::CreateCollection:
73 return dbg << "CreateCollection";
74 case Command::CopyCollection:
75 return dbg << "CopyCollection";
76 case Command::DeleteCollection:
77 return dbg << "DeleteCollection";
78 case Command::FetchCollections:
79 return dbg << "FetchCollections";
80 case Command::FetchCollectionStats:
81 return dbg << "FetchCollectionStats";
82 case Command::ModifyCollection:
83 return dbg << "ModifyCollection";
84 case Command::MoveCollection:
85 return dbg << "MoveCollection";
86
87 case Command::Search:
88 return dbg << "Search";
89 case Command::SearchResult:
90 return dbg << "SearchResult";
91 case Command::StoreSearch:
92 return dbg << "StoreSearch";
93
94 case Command::CreateTag:
95 return dbg << "CreateTag";
96 case Command::DeleteTag:
97 return dbg << "DeleteTag";
98 case Command::FetchTags:
99 return dbg << "FetchTags";
100 case Command::ModifyTag:
101 return dbg << "ModifyTag";
102
103 case Command::FetchRelations:
104 return dbg << "FetchRelations";
105 case Command::ModifyRelation:
106 return dbg << "ModifyRelation";
107 case Command::RemoveRelations:
108 return dbg << "RemoveRelations";
109
110 case Command::SelectResource:
111 return dbg << "SelectResource";
112
113 case Command::StreamPayload:
114 return dbg << "StreamPayload";
115 case Command::ItemChangeNotification:
116 return dbg << "ItemChangeNotification";
117 case Command::CollectionChangeNotification:
118 return dbg << "CollectionChangeNotification";
119 case Command::TagChangeNotification:
120 return dbg << "TagChangeNotification";
121 case Command::RelationChangeNotification:
122 return dbg << "RelationChangeNotification";
123 case Command::SubscriptionChangeNotification:
124 return dbg << "SubscriptionChangeNotification";
125 case Command::DebugChangeNotification:
126 return dbg << "DebugChangeNotification";
127 case Command::CreateSubscription:
128 return dbg << "CreateSubscription";
129 case Command::ModifySubscription:
130 return dbg << "ModifySubscription";
131
132 case Command::_ResponseBit:
133 Q_ASSERT(false);
134 return dbg << static_cast<int>(type);
135 }
136
137 Q_ASSERT(false);
138 return dbg << static_cast<int>(type);
139 }
140
operator <<(DataStream & stream,const QSharedPointer<T> & ptr)141 template<typename T> DataStream &operator<<(DataStream &stream, const QSharedPointer<T> &ptr)
142 {
143 Protocol::serialize(stream, ptr);
144 return stream;
145 }
146
operator >>(DataStream & stream,QSharedPointer<T> & ptr)147 template<typename T> DataStream &operator>>(DataStream &stream, QSharedPointer<T> &ptr)
148 {
149 ptr = Protocol::deserialize(stream.device()).staticCast<T>();
150 return stream;
151 }
152
153 /******************************************************************************/
154
Command(quint8 type)155 Command::Command(quint8 type)
156 : mType(type)
157 {
158 }
159
operator ==(const Command & other) const160 bool Command::operator==(const Command &other) const
161 {
162 return mType == other.mType;
163 }
164
toJson(QJsonObject & json) const165 void Command::toJson(QJsonObject &json) const
166 {
167 json[QStringLiteral("response")] = static_cast<bool>(mType & Command::_ResponseBit);
168
169 #define case_label(x) \
170 case Command::x: { \
171 json[QStringLiteral("type")] = QStringLiteral(#x); \
172 } break;
173
174 switch (mType & ~Command::_ResponseBit) {
175 case_label(Invalid) case_label(Hello)
176
177 case_label(Login) case_label(Logout)
178
179 case_label(Transaction)
180
181 case_label(CreateItem) case_label(CopyItems) case_label(DeleteItems) case_label(FetchItems) case_label(LinkItems) case_label(ModifyItems)
182 case_label(MoveItems)
183
184 case_label(CreateCollection) case_label(CopyCollection) case_label(DeleteCollection) case_label(FetchCollections)
185 case_label(FetchCollectionStats) case_label(ModifyCollection) case_label(MoveCollection)
186
187 case_label(Search) case_label(SearchResult) case_label(StoreSearch)
188
189 case_label(CreateTag) case_label(DeleteTag) case_label(FetchTags) case_label(ModifyTag)
190
191 case_label(FetchRelations) case_label(ModifyRelation) case_label(RemoveRelations)
192
193 case_label(SelectResource)
194
195 case_label(StreamPayload) case_label(CreateSubscription) case_label(ModifySubscription)
196
197 case_label(DebugChangeNotification) case_label(ItemChangeNotification)
198 case_label(CollectionChangeNotification) case_label(TagChangeNotification)
199 case_label(RelationChangeNotification) case_label(SubscriptionChangeNotification)
200 }
201 #undef case_label
202 }
203
operator <<(DataStream & stream,const Command & cmd)204 DataStream &operator<<(DataStream &stream, const Command &cmd)
205 {
206 return stream << cmd.mType;
207 }
208
operator >>(DataStream & stream,Command & cmd)209 DataStream &operator>>(DataStream &stream, Command &cmd)
210 {
211 return stream >> cmd.mType;
212 }
213
operator <<(QDebug dbg,const Command & cmd)214 QDebug operator<<(QDebug dbg, const Command &cmd)
215 {
216 return dbg.noquote() << ((cmd.mType & Command::_ResponseBit) ? "Response:" : "Command:") << static_cast<Command::Type>(cmd.mType & ~Command::_ResponseBit)
217 << "\n";
218 }
219
toJson(const Akonadi::Protocol::Command * command,QJsonObject & json)220 void toJson(const Akonadi::Protocol::Command *command, QJsonObject &json)
221 {
222 #define case_notificationlabel(x, class) \
223 case Command::x: { \
224 static_cast<const Akonadi::Protocol::class *>(command)->toJson(json); \
225 } break;
226 #define case_commandlabel(x, cmd, resp) \
227 case Command::x: { \
228 static_cast<const Akonadi::Protocol::cmd *>(command)->toJson(json); \
229 } break; \
230 case Command::x | Command::_ResponseBit: { \
231 static_cast<const Akonadi::Protocol::resp *>(command)->toJson(json); \
232 } break;
233
234 switch (command->mType) {
235 case Command::Invalid:
236 break;
237
238 case Command::Hello | Command::_ResponseBit: {
239 static_cast<const Akonadi::Protocol::HelloResponse *>(command)->toJson(json);
240 } break;
241 case_commandlabel(Login, LoginCommand, LoginResponse) case_commandlabel(Logout, LogoutCommand, LogoutResponse)
242
243 case_commandlabel(Transaction, TransactionCommand, TransactionResponse)
244
245 case_commandlabel(CreateItem, CreateItemCommand, CreateItemResponse) case_commandlabel(CopyItems, CopyItemsCommand, CopyItemsResponse)
246 case_commandlabel(DeleteItems, DeleteItemsCommand, DeleteItemsResponse) case_commandlabel(FetchItems, FetchItemsCommand, FetchItemsResponse)
247 case_commandlabel(LinkItems, LinkItemsCommand, LinkItemsResponse) case_commandlabel(
248 ModifyItems,
249 ModifyItemsCommand,
250 ModifyItemsResponse) case_commandlabel(MoveItems, MoveItemsCommand, MoveItemsResponse)
251
252 case_commandlabel(CreateCollection, CreateCollectionCommand, CreateCollectionResponse) case_commandlabel(
253 CopyCollection,
254 CopyCollectionCommand,
255 CopyCollectionResponse) case_commandlabel(DeleteCollection, DeleteCollectionCommand, DeleteCollectionResponse)
256 case_commandlabel(FetchCollections, FetchCollectionsCommand, FetchCollectionsResponse) case_commandlabel(
257 FetchCollectionStats,
258 FetchCollectionStatsCommand,
259 FetchCollectionStatsResponse) case_commandlabel(ModifyCollection, ModifyCollectionCommand, ModifyCollectionResponse)
260 case_commandlabel(MoveCollection, MoveCollectionCommand, MoveCollectionResponse)
261
262 case_commandlabel(Search, SearchCommand, SearchResponse) case_commandlabel(
263 SearchResult,
264 SearchResultCommand,
265 SearchResultResponse) case_commandlabel(StoreSearch, StoreSearchCommand, StoreSearchResponse)
266
267 case_commandlabel(CreateTag, CreateTagCommand, CreateTagResponse)
268 case_commandlabel(DeleteTag, DeleteTagCommand, DeleteTagResponse)
269 case_commandlabel(FetchTags, FetchTagsCommand, FetchTagsResponse)
270 case_commandlabel(ModifyTag, ModifyTagCommand, ModifyTagResponse)
271
272 case_commandlabel(FetchRelations, FetchRelationsCommand, FetchRelationsResponse)
273 case_commandlabel(ModifyRelation, ModifyRelationCommand, ModifyRelationResponse)
274 case_commandlabel(RemoveRelations, RemoveRelationsCommand, RemoveRelationsResponse)
275
276 case_commandlabel(SelectResource, SelectResourceCommand, SelectResourceResponse)
277
278 case_commandlabel(StreamPayload, StreamPayloadCommand, StreamPayloadResponse)
279 case_commandlabel(
280 CreateSubscription,
281 CreateSubscriptionCommand,
282 CreateSubscriptionResponse) case_commandlabel(ModifySubscription,
283 ModifySubscriptionCommand,
284 ModifySubscriptionResponse)
285
286 case_notificationlabel(DebugChangeNotification, DebugChangeNotification)
287 case_notificationlabel(ItemChangeNotification, ItemChangeNotification)
288 case_notificationlabel(CollectionChangeNotification,
289 CollectionChangeNotification)
290 case_notificationlabel(TagChangeNotification,
291 TagChangeNotification)
292 case_notificationlabel(RelationChangeNotification,
293 RelationChangeNotification)
294 case_notificationlabel(SubscriptionChangeNotification,
295 SubscriptionChangeNotification)
296 }
297 #undef case_notificationlabel
298 #undef case_commandlabel
299 }
300
301 /******************************************************************************/
302
Response()303 Response::Response()
304 : Command(Command::Invalid | Command::_ResponseBit)
305 , mErrorCode(0)
306 {
307 }
308
Response(Command::Type type)309 Response::Response(Command::Type type)
310 : Command(type | Command::_ResponseBit)
311 , mErrorCode(0)
312 {
313 }
314
operator ==(const Response & other) const315 bool Response::operator==(const Response &other) const
316 {
317 return *static_cast<const Command *>(this) == static_cast<const Command &>(other) && mErrorCode == other.mErrorCode && mErrorMsg == other.mErrorMsg;
318 }
319
toJson(QJsonObject & json) const320 void Response::toJson(QJsonObject &json) const
321 {
322 static_cast<const Command *>(this)->toJson(json);
323 if (isError()) {
324 QJsonObject error;
325 error[QStringLiteral("code")] = errorCode();
326 error[QStringLiteral("message")] = errorMessage();
327 json[QStringLiteral("error")] = error;
328 } else {
329 json[QStringLiteral("error")] = false;
330 }
331 }
332
operator <<(DataStream & stream,const Response & cmd)333 DataStream &operator<<(DataStream &stream, const Response &cmd)
334 {
335 return stream << static_cast<const Command &>(cmd) << cmd.mErrorCode << cmd.mErrorMsg;
336 }
337
operator >>(DataStream & stream,Response & cmd)338 DataStream &operator>>(DataStream &stream, Response &cmd)
339 {
340 return stream >> static_cast<Command &>(cmd) >> cmd.mErrorCode >> cmd.mErrorMsg;
341 }
342
operator <<(QDebug dbg,const Response & resp)343 QDebug operator<<(QDebug dbg, const Response &resp)
344 {
345 return dbg.noquote() << static_cast<const Command &>(resp) << "Error code:" << resp.mErrorCode << "\n"
346 << "Error msg:" << resp.mErrorMsg << "\n";
347 }
348
349 /******************************************************************************/
350
351 class FactoryPrivate
352 {
353 public:
354 typedef CommandPtr (*CommandFactoryFunc)();
355 typedef ResponsePtr (*ResponseFactoryFunc)();
356
FactoryPrivate()357 FactoryPrivate()
358 {
359 // Session management
360 registerType<Command::Hello, Command /* invalid */, HelloResponse>();
361 registerType<Command::Login, LoginCommand, LoginResponse>();
362 registerType<Command::Logout, LogoutCommand, LogoutResponse>();
363
364 // Transactions
365 registerType<Command::Transaction, TransactionCommand, TransactionResponse>();
366
367 // Items
368 registerType<Command::CreateItem, CreateItemCommand, CreateItemResponse>();
369 registerType<Command::CopyItems, CopyItemsCommand, CopyItemsResponse>();
370 registerType<Command::DeleteItems, DeleteItemsCommand, DeleteItemsResponse>();
371 registerType<Command::FetchItems, FetchItemsCommand, FetchItemsResponse>();
372 registerType<Command::LinkItems, LinkItemsCommand, LinkItemsResponse>();
373 registerType<Command::ModifyItems, ModifyItemsCommand, ModifyItemsResponse>();
374 registerType<Command::MoveItems, MoveItemsCommand, MoveItemsResponse>();
375
376 // Collections
377 registerType<Command::CreateCollection, CreateCollectionCommand, CreateCollectionResponse>();
378 registerType<Command::CopyCollection, CopyCollectionCommand, CopyCollectionResponse>();
379 registerType<Command::DeleteCollection, DeleteCollectionCommand, DeleteCollectionResponse>();
380 registerType<Command::FetchCollections, FetchCollectionsCommand, FetchCollectionsResponse>();
381 registerType<Command::FetchCollectionStats, FetchCollectionStatsCommand, FetchCollectionStatsResponse>();
382 registerType<Command::ModifyCollection, ModifyCollectionCommand, ModifyCollectionResponse>();
383 registerType<Command::MoveCollection, MoveCollectionCommand, MoveCollectionResponse>();
384
385 // Search
386 registerType<Command::Search, SearchCommand, SearchResponse>();
387 registerType<Command::SearchResult, SearchResultCommand, SearchResultResponse>();
388 registerType<Command::StoreSearch, StoreSearchCommand, StoreSearchResponse>();
389
390 // Tag
391 registerType<Command::CreateTag, CreateTagCommand, CreateTagResponse>();
392 registerType<Command::DeleteTag, DeleteTagCommand, DeleteTagResponse>();
393 registerType<Command::FetchTags, FetchTagsCommand, FetchTagsResponse>();
394 registerType<Command::ModifyTag, ModifyTagCommand, ModifyTagResponse>();
395
396 // Relation
397 registerType<Command::FetchRelations, FetchRelationsCommand, FetchRelationsResponse>();
398 registerType<Command::ModifyRelation, ModifyRelationCommand, ModifyRelationResponse>();
399 registerType<Command::RemoveRelations, RemoveRelationsCommand, RemoveRelationsResponse>();
400
401 // Resources
402 registerType<Command::SelectResource, SelectResourceCommand, SelectResourceResponse>();
403
404 // Other...?
405 registerType<Command::StreamPayload, StreamPayloadCommand, StreamPayloadResponse>();
406 registerType<Command::ItemChangeNotification, ItemChangeNotification, Response /* invalid */>();
407 registerType<Command::CollectionChangeNotification, CollectionChangeNotification, Response /* invalid */>();
408 registerType<Command::TagChangeNotification, TagChangeNotification, Response /* invalid */>();
409 registerType<Command::RelationChangeNotification, RelationChangeNotification, Response /* invalid */>();
410 registerType<Command::SubscriptionChangeNotification, SubscriptionChangeNotification, Response /* invalid */>();
411 registerType<Command::DebugChangeNotification, DebugChangeNotification, Response /* invalid */>();
412 registerType<Command::CreateSubscription, CreateSubscriptionCommand, CreateSubscriptionResponse>();
413 registerType<Command::ModifySubscription, ModifySubscriptionCommand, ModifySubscriptionResponse>();
414 }
415
416 // clang has problem resolving the right qHash() overload for Command::Type,
417 // so use its underlying integer type instead
418 QHash<std::underlying_type<Command::Type>::type, QPair<CommandFactoryFunc, ResponseFactoryFunc>> registrar;
419
420 private:
commandFactoryFunc()421 template<typename T> static CommandPtr commandFactoryFunc()
422 {
423 return QSharedPointer<T>::create();
424 }
responseFactoryFunc()425 template<typename T> static ResponsePtr responseFactoryFunc()
426 {
427 return QSharedPointer<T>::create();
428 }
429
registerType()430 template<Command::Type T, typename CmdType, typename RespType> void registerType()
431 {
432 CommandFactoryFunc cmdFunc = &commandFactoryFunc<CmdType>;
433 ResponseFactoryFunc respFunc = &responseFactoryFunc<RespType>;
434 registrar.insert(T, qMakePair<CommandFactoryFunc, ResponseFactoryFunc>(cmdFunc, respFunc));
435 }
436 };
437
Q_GLOBAL_STATIC(FactoryPrivate,sFactoryPrivate)438 Q_GLOBAL_STATIC(FactoryPrivate, sFactoryPrivate) // NOLINT(readability-redundant-member-init)
439
440 CommandPtr Factory::command(Command::Type type)
441 {
442 auto iter = sFactoryPrivate->registrar.constFind(type);
443 if (iter == sFactoryPrivate->registrar.constEnd()) {
444 return QSharedPointer<Command>::create();
445 }
446 return iter->first();
447 }
448
response(Command::Type type)449 ResponsePtr Factory::response(Command::Type type)
450 {
451 auto iter = sFactoryPrivate->registrar.constFind(type);
452 if (iter == sFactoryPrivate->registrar.constEnd()) {
453 return QSharedPointer<Response>::create();
454 }
455 return iter->second();
456 }
457
458 /******************************************************************************/
459
460 /******************************************************************************/
461
operator ==(const ItemFetchScope & other) const462 bool ItemFetchScope::operator==(const ItemFetchScope &other) const
463 {
464 return mRequestedParts == other.mRequestedParts && mChangedSince == other.mChangedSince && mAncestorDepth == other.mAncestorDepth && mFlags == other.mFlags;
465 }
466
requestedPayloads() const467 QVector<QByteArray> ItemFetchScope::requestedPayloads() const
468 {
469 QVector<QByteArray> rv;
470 std::copy_if(mRequestedParts.begin(), mRequestedParts.end(), std::back_inserter(rv), [](const QByteArray &ba) {
471 return ba.startsWith("PLD:");
472 });
473 return rv;
474 }
475
setFetch(FetchFlags attributes,bool fetch)476 void ItemFetchScope::setFetch(FetchFlags attributes, bool fetch)
477 {
478 if (fetch) {
479 mFlags |= attributes;
480 if (attributes & FullPayload) {
481 if (!mRequestedParts.contains(AKONADI_PARAM_PLD_RFC822)) {
482 mRequestedParts << AKONADI_PARAM_PLD_RFC822;
483 }
484 }
485 } else {
486 mFlags &= ~attributes;
487 }
488 }
489
fetch(FetchFlags flags) const490 bool ItemFetchScope::fetch(FetchFlags flags) const
491 {
492 if (flags == None) {
493 return mFlags == None;
494 } else {
495 return mFlags & flags;
496 }
497 }
498
toJson(QJsonObject & json) const499 void ItemFetchScope::toJson(QJsonObject &json) const
500 {
501 json[QStringLiteral("flags")] = static_cast<int>(mFlags);
502 json[QStringLiteral("ChangedSince")] = mChangedSince.toString();
503 json[QStringLiteral("AncestorDepth")] = static_cast<std::underlying_type<AncestorDepth>::type>(mAncestorDepth);
504
505 QJsonArray requestedPartsArray;
506 for (const auto &part : std::as_const(mRequestedParts)) {
507 requestedPartsArray.append(QString::fromUtf8(part));
508 }
509 json[QStringLiteral("RequestedParts")] = requestedPartsArray;
510 }
511
operator <<(QDebug dbg,ItemFetchScope::AncestorDepth depth)512 QDebug operator<<(QDebug dbg, ItemFetchScope::AncestorDepth depth)
513 {
514 switch (depth) {
515 case ItemFetchScope::NoAncestor:
516 return dbg << "No ancestor";
517 case ItemFetchScope::ParentAncestor:
518 return dbg << "Parent ancestor";
519 case ItemFetchScope::AllAncestors:
520 return dbg << "All ancestors";
521 }
522 Q_UNREACHABLE();
523 }
524
operator <<(DataStream & stream,const ItemFetchScope & scope)525 DataStream &operator<<(DataStream &stream, const ItemFetchScope &scope)
526 {
527 return stream << scope.mRequestedParts << scope.mChangedSince << scope.mAncestorDepth << scope.mFlags;
528 }
529
operator >>(DataStream & stream,ItemFetchScope & scope)530 DataStream &operator>>(DataStream &stream, ItemFetchScope &scope)
531 {
532 return stream >> scope.mRequestedParts >> scope.mChangedSince >> scope.mAncestorDepth >> scope.mFlags;
533 }
534
operator <<(QDebug dbg,const ItemFetchScope & scope)535 QDebug operator<<(QDebug dbg, const ItemFetchScope &scope)
536 {
537 return dbg.noquote() << "FetchScope(\n"
538 << "Fetch Flags:" << scope.mFlags << "\n"
539 << "Changed Since:" << scope.mChangedSince << "\n"
540 << "Ancestor Depth:" << scope.mAncestorDepth << "\n"
541 << "Requested Parts:" << scope.mRequestedParts << ")\n";
542 }
543
544 /******************************************************************************/
545
ScopeContext(Type type,qint64 id)546 ScopeContext::ScopeContext(Type type, qint64 id)
547 {
548 if (type == ScopeContext::Tag) {
549 mTagCtx = id;
550 } else if (type == ScopeContext::Collection) {
551 mColCtx = id;
552 }
553 }
554
ScopeContext(Type type,const QString & ctx)555 ScopeContext::ScopeContext(Type type, const QString &ctx)
556 {
557 if (type == ScopeContext::Tag) {
558 mTagCtx = ctx;
559 } else if (type == ScopeContext::Collection) {
560 mColCtx = ctx;
561 }
562 }
563
operator ==(const ScopeContext & other) const564 bool ScopeContext::operator==(const ScopeContext &other) const
565 {
566 return mColCtx == other.mColCtx && mTagCtx == other.mTagCtx;
567 }
568
toJson(QJsonObject & json) const569 void ScopeContext::toJson(QJsonObject &json) const
570 {
571 if (isEmpty()) {
572 json[QStringLiteral("scopeContext")] = false;
573 } else if (hasContextId(ScopeContext::Tag)) {
574 json[QStringLiteral("scopeContext")] = QStringLiteral("tag");
575 json[QStringLiteral("TagID")] = contextId(ScopeContext::Tag);
576 } else if (hasContextId(ScopeContext::Collection)) {
577 json[QStringLiteral("scopeContext")] = QStringLiteral("collection");
578 json[QStringLiteral("ColID")] = contextId(ScopeContext::Collection);
579 } else if (hasContextRID(ScopeContext::Tag)) {
580 json[QStringLiteral("scopeContext")] = QStringLiteral("tagrid");
581 json[QStringLiteral("TagRID")] = contextRID(ScopeContext::Tag);
582 } else if (hasContextRID(ScopeContext::Collection)) {
583 json[QStringLiteral("scopeContext")] = QStringLiteral("colrid");
584 json[QStringLiteral("ColRID")] = contextRID(ScopeContext::Collection);
585 }
586 }
587
operator <<(DataStream & stream,const ScopeContext & context)588 DataStream &operator<<(DataStream &stream, const ScopeContext &context)
589 {
590 // We don't have a custom generic DataStream streaming operator for QVariant
591 // because it's very hard, esp. without access to QVariant private
592 // stuff, so we have to decompose it manually here.
593 QVariant::Type vType = context.mColCtx.type();
594 stream << vType;
595 if (vType == QVariant::LongLong) {
596 stream << context.mColCtx.toLongLong();
597 } else if (vType == QVariant::String) {
598 stream << context.mColCtx.toString();
599 }
600
601 vType = context.mTagCtx.type();
602 stream << vType;
603 if (vType == QVariant::LongLong) {
604 stream << context.mTagCtx.toLongLong();
605 } else if (vType == QVariant::String) {
606 stream << context.mTagCtx.toString();
607 }
608
609 return stream;
610 }
611
operator >>(DataStream & stream,ScopeContext & context)612 DataStream &operator>>(DataStream &stream, ScopeContext &context)
613 {
614 QVariant::Type vType;
615 qint64 id;
616 QString rid;
617
618 for (ScopeContext::Type type : {ScopeContext::Collection, ScopeContext::Tag}) {
619 stream >> vType;
620 if (vType == QVariant::LongLong) {
621 stream >> id;
622 context.setContext(type, id);
623 } else if (vType == QVariant::String) {
624 stream >> rid;
625 context.setContext(type, rid);
626 }
627 }
628
629 return stream;
630 }
631
operator <<(QDebug _dbg,const ScopeContext & ctx)632 QDebug operator<<(QDebug _dbg, const ScopeContext &ctx)
633 {
634 QDebug dbg(_dbg.noquote());
635 dbg << "ScopeContext(";
636 if (ctx.isEmpty()) {
637 dbg << "empty";
638 } else if (ctx.hasContextId(ScopeContext::Tag)) {
639 dbg << "Tag ID:" << ctx.contextId(ScopeContext::Tag);
640 } else if (ctx.hasContextId(ScopeContext::Collection)) {
641 dbg << "Col ID:" << ctx.contextId(ScopeContext::Collection);
642 } else if (ctx.hasContextRID(ScopeContext::Tag)) {
643 dbg << "Tag RID:" << ctx.contextRID(ScopeContext::Tag);
644 } else if (ctx.hasContextRID(ScopeContext::Collection)) {
645 dbg << "Col RID:" << ctx.contextRID(ScopeContext::Collection);
646 }
647 return dbg << ")\n";
648 }
649
650 /******************************************************************************/
651
ChangeNotification(Command::Type type)652 ChangeNotification::ChangeNotification(Command::Type type)
653 : Command(type)
654 {
655 }
656
operator ==(const ChangeNotification & other) const657 bool ChangeNotification::operator==(const ChangeNotification &other) const
658 {
659 return static_cast<const Command &>(*this) == other && mSessionId == other.mSessionId;
660 // metadata are not compared
661 }
662
itemsToUids(const QVector<FetchItemsResponse> & items)663 QList<qint64> ChangeNotification::itemsToUids(const QVector<FetchItemsResponse> &items)
664 {
665 QList<qint64> rv;
666 rv.reserve(items.size());
667 std::transform(items.cbegin(), items.cend(), std::back_inserter(rv), [](const FetchItemsResponse &item) {
668 return item.id();
669 });
670 return rv;
671 }
672
isRemove() const673 bool ChangeNotification::isRemove() const
674 {
675 switch (type()) {
676 case Command::Invalid:
677 return false;
678 case Command::ItemChangeNotification:
679 return static_cast<const class ItemChangeNotification *>(this)->operation() == ItemChangeNotification::Remove;
680 case Command::CollectionChangeNotification:
681 return static_cast<const class CollectionChangeNotification *>(this)->operation() == CollectionChangeNotification::Remove;
682 case Command::TagChangeNotification:
683 return static_cast<const class TagChangeNotification *>(this)->operation() == TagChangeNotification::Remove;
684 case Command::RelationChangeNotification:
685 return static_cast<const class RelationChangeNotification *>(this)->operation() == RelationChangeNotification::Remove;
686 case Command::SubscriptionChangeNotification:
687 return static_cast<const class SubscriptionChangeNotification *>(this)->operation() == SubscriptionChangeNotification::Remove;
688 case Command::DebugChangeNotification:
689 return false;
690 default:
691 Q_ASSERT_X(false, __FUNCTION__, "Unknown ChangeNotification type");
692 }
693
694 return false;
695 }
696
isMove() const697 bool ChangeNotification::isMove() const
698 {
699 switch (type()) {
700 case Command::Invalid:
701 return false;
702 case Command::ItemChangeNotification:
703 return static_cast<const class ItemChangeNotification *>(this)->operation() == ItemChangeNotification::Move;
704 case Command::CollectionChangeNotification:
705 return static_cast<const class CollectionChangeNotification *>(this)->operation() == CollectionChangeNotification::Move;
706 case Command::TagChangeNotification:
707 case Command::RelationChangeNotification:
708 case Command::SubscriptionChangeNotification:
709 case Command::DebugChangeNotification:
710 return false;
711 default:
712 Q_ASSERT_X(false, __FUNCTION__, "Unknown ChangeNotification type");
713 }
714
715 return false;
716 }
717
appendAndCompress(ChangeNotificationList & list,const ChangeNotificationPtr & msg)718 bool ChangeNotification::appendAndCompress(ChangeNotificationList &list, const ChangeNotificationPtr &msg)
719 {
720 // It is likely that compressible notifications are within the last few notifications, so avoid searching a list that is potentially huge
721 static const int maxCompressionSearchLength = 10;
722 int searchCounter = 0;
723 // There are often multiple Collection Modify notifications in the queue,
724 // so we optimize for this case.
725
726 if (msg->type() == Command::CollectionChangeNotification) {
727 const auto &cmsg = Protocol::cmdCast<class CollectionChangeNotification>(msg);
728 if (cmsg.operation() == CollectionChangeNotification::Modify) {
729 // We are iterating from end, since there's higher probability of finding
730 // matching notification
731 for (auto iter = list.end(), begin = list.begin(); iter != begin;) {
732 --iter;
733 if ((*iter)->type() == Protocol::Command::CollectionChangeNotification) {
734 auto &it = Protocol::cmdCast<class CollectionChangeNotification>(*iter);
735 const auto &msgCol = cmsg.collection();
736 const auto &itCol = it.collection();
737 if (msgCol.id() == itCol.id() && msgCol.remoteId() == itCol.remoteId() && msgCol.remoteRevision() == itCol.remoteRevision()
738 && msgCol.resource() == itCol.resource() && cmsg.destinationResource() == it.destinationResource()
739 && cmsg.parentCollection() == it.parentCollection() && cmsg.parentDestCollection() == it.parentDestCollection()) {
740 // both are modifications, merge them together and drop the new one
741 if (cmsg.operation() == CollectionChangeNotification::Modify && it.operation() == CollectionChangeNotification::Modify) {
742 const auto parts = it.changedParts();
743 it.setChangedParts(parts + cmsg.changedParts());
744 return false;
745 }
746
747 // we found Add notification, which means we can drop this modification
748 if (it.operation() == CollectionChangeNotification::Add) {
749 return false;
750 }
751 }
752 }
753 searchCounter++;
754 if (searchCounter >= maxCompressionSearchLength) {
755 break;
756 }
757 }
758 }
759 }
760
761 // All other cases are just append, as the compression becomes too expensive in large batches
762 list.append(msg);
763 return true;
764 }
765
toJson(QJsonObject & json) const766 void ChangeNotification::toJson(QJsonObject &json) const
767 {
768 static_cast<const Command *>(this)->toJson(json);
769 json[QStringLiteral("session")] = QString::fromUtf8(mSessionId);
770
771 QJsonArray metadata;
772 for (const auto &m : std::as_const(mMetaData)) {
773 metadata.append(QString::fromUtf8(m));
774 }
775 json[QStringLiteral("metadata")] = metadata;
776 }
777
operator <<(DataStream & stream,const ChangeNotification & ntf)778 DataStream &operator<<(DataStream &stream, const ChangeNotification &ntf)
779 {
780 return stream << static_cast<const Command &>(ntf) << ntf.mSessionId;
781 }
782
operator >>(DataStream & stream,ChangeNotification & ntf)783 DataStream &operator>>(DataStream &stream, ChangeNotification &ntf)
784 {
785 return stream >> static_cast<Command &>(ntf) >> ntf.mSessionId;
786 }
787
operator <<(QDebug dbg,const ChangeNotification & ntf)788 QDebug operator<<(QDebug dbg, const ChangeNotification &ntf)
789 {
790 return dbg.noquote() << static_cast<const Command &>(ntf) << "Session:" << ntf.mSessionId << "\n"
791 << "MetaData:" << ntf.mMetaData << "\n";
792 }
793
operator >>(DataStream & stream,ChangeNotification::Relation & relation)794 DataStream &operator>>(DataStream &stream, ChangeNotification::Relation &relation)
795 {
796 return stream >> relation.type >> relation.leftId >> relation.rightId;
797 }
798
operator <<(DataStream & stream,const ChangeNotification::Relation & relation)799 DataStream &operator<<(DataStream &stream, const ChangeNotification::Relation &relation)
800 {
801 return stream << relation.type << relation.leftId << relation.rightId;
802 }
803
operator <<(QDebug _dbg,const ChangeNotification::Relation & rel)804 QDebug operator<<(QDebug _dbg, const ChangeNotification::Relation &rel)
805 {
806 QDebug dbg(_dbg.noquote());
807 return dbg << "Left: " << rel.leftId << ", Right:" << rel.rightId << ", Type: " << rel.type;
808 }
809
810 } // namespace Protocol
811 } // namespace Akonadi
812
813 // Helpers for the generated code
814 namespace Akonadi
815 {
816 namespace Protocol
817 {
containerComparator(const Container<Value> & c1,const Container<Value> & c2)818 template<typename Value, template<typename> class Container> inline bool containerComparator(const Container<Value> &c1, const Container<Value> &c2)
819 {
820 return c1 == c2;
821 }
822
823 template<typename T, template<typename> class Container>
containerComparator(const Container<QSharedPointer<T>> & c1,const Container<QSharedPointer<T>> & c2)824 inline bool containerComparator(const Container<QSharedPointer<T>> &c1, const Container<QSharedPointer<T>> &c2)
825 {
826 if (c1.size() != c2.size()) {
827 return false;
828 }
829
830 for (auto it1 = c1.cbegin(), it2 = c2.cbegin(), end1 = c1.cend(); it1 != end1; ++it1, ++it2) {
831 if (**it1 != **it2) {
832 return false;
833 }
834 }
835 return true;
836 }
837
838 } // namespace Protocol
839 } // namespace Akonadi
840
841 /******************************************************************************/
842
843 // Here comes the generated protocol implementation
844 #include "protocol_gen.cpp"
845
846 /******************************************************************************/
847