1 /******************************************************************************
2 *
3 * SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 *
7 *******************************************************************************/
8
9 #include "messageitem.h"
10 #include "messageitem_p.h"
11
12 #include <PimCommonAkonadi/AnnotationDialog>
13
14 #include "messagelist_debug.h"
15 #include <Akonadi/EntityAnnotationsAttribute>
16 #include <Akonadi/Item>
17 #include <Akonadi/TagAttribute>
18 #include <Akonadi/TagFetchJob>
19 #include <Akonadi/TagFetchScope>
20 #include <KIconLoader>
21 #include <KLocalizedString>
22 #include <QIcon>
23 #include <QPointer>
24 using namespace MessageList::Core;
25
26 Q_GLOBAL_STATIC(TagCache, s_tagCache)
27
28 class MessageItem::Tag::TagPrivate
29 {
30 public:
TagPrivate()31 TagPrivate()
32 : mPriority(0) // Initialize it
33 {
34 }
35
36 QPixmap mPixmap;
37 QString mName;
38 QString mId; ///< The unique id of this tag
39 QColor mTextColor;
40 QColor mBackgroundColor;
41 QFont mFont;
42 int mPriority;
43 };
44
Tag(const QPixmap & pix,const QString & tagName,const QString & tagId)45 MessageItem::Tag::Tag(const QPixmap &pix, const QString &tagName, const QString &tagId)
46 : d(new TagPrivate)
47 {
48 d->mPixmap = pix;
49 d->mName = tagName;
50 d->mId = tagId;
51 }
52
53 MessageItem::Tag::~Tag() = default;
54
pixmap() const55 const QPixmap &MessageItem::Tag::pixmap() const
56 {
57 return d->mPixmap;
58 }
59
name() const60 const QString &MessageItem::Tag::name() const
61 {
62 return d->mName;
63 }
64
id() const65 const QString &MessageItem::Tag::id() const
66 {
67 return d->mId;
68 }
69
textColor() const70 const QColor &MessageItem::Tag::textColor() const
71 {
72 return d->mTextColor;
73 }
74
backgroundColor() const75 const QColor &MessageItem::Tag::backgroundColor() const
76 {
77 return d->mBackgroundColor;
78 }
79
font() const80 const QFont &MessageItem::Tag::font() const
81 {
82 return d->mFont;
83 }
84
priority() const85 int MessageItem::Tag::priority() const
86 {
87 return d->mPriority;
88 }
89
setTextColor(const QColor & textColor)90 void MessageItem::Tag::setTextColor(const QColor &textColor)
91 {
92 d->mTextColor = textColor;
93 }
94
setBackgroundColor(const QColor & backgroundColor)95 void MessageItem::Tag::setBackgroundColor(const QColor &backgroundColor)
96 {
97 d->mBackgroundColor = backgroundColor;
98 }
99
setFont(const QFont & font)100 void MessageItem::Tag::setFont(const QFont &font)
101 {
102 d->mFont = font;
103 }
104
setPriority(int priority)105 void MessageItem::Tag::setPriority(int priority)
106 {
107 d->mPriority = priority;
108 }
109
110 class MessageItemPrivateSettings
111 {
112 public:
113 QColor mColorUnreadMessage;
114 QColor mColorImportantMessage;
115 QColor mColorToDoMessage;
116 QFont mFont;
117 QFont mFontUnreadMessage;
118 QFont mFontImportantMessage;
119 QFont mFontToDoMessage;
120
121 // Keep those two invalid. They are here purely so that MessageItem can return
122 // const reference to them
123 QColor mColor;
124 QColor mBackgroundColor;
125 };
126
Q_GLOBAL_STATIC(MessageItemPrivateSettings,s_settings)127 Q_GLOBAL_STATIC(MessageItemPrivateSettings, s_settings)
128
129 MessageItemPrivate::MessageItemPrivate(MessageItem *qq)
130 : ItemPrivate(qq)
131 , mThreadingStatus(MessageItem::ParentMissing)
132 , mEncryptionState(MessageItem::NotEncrypted)
133 , mSignatureState(MessageItem::NotSigned)
134 , mAboutToBeRemoved(false)
135 , mSubjectIsPrefixed(false)
136 , mTagList(nullptr)
137 {
138 }
139
~MessageItemPrivate()140 MessageItemPrivate::~MessageItemPrivate()
141 {
142 s_tagCache->cancelRequest(this);
143 invalidateTagCache();
144 }
145
invalidateTagCache()146 void MessageItemPrivate::invalidateTagCache()
147 {
148 if (mTagList) {
149 qDeleteAll(*mTagList);
150 delete mTagList;
151 mTagList = nullptr;
152 }
153 }
154
invalidateAnnotationCache()155 void MessageItemPrivate::invalidateAnnotationCache()
156 {
157 }
158
bestTag() const159 const MessageItem::Tag *MessageItemPrivate::bestTag() const
160 {
161 const MessageItem::Tag *best = nullptr;
162 const auto tagList{getTagList()};
163 for (const MessageItem::Tag *tag : tagList) {
164 if (!best || tag->priority() < best->priority()) {
165 best = tag;
166 }
167 }
168 return best;
169 }
170
fillTagList(const Akonadi::Tag::List & taglist)171 void MessageItemPrivate::fillTagList(const Akonadi::Tag::List &taglist)
172 {
173 Q_ASSERT(!mTagList);
174 mTagList = new QList<MessageItem::Tag *>;
175
176 // TODO: The tag pointers here could be shared between all items, there really is no point in
177 // creating them for each item that has tags
178
179 // Priority sort this and make bestTag more efficient
180
181 for (const Akonadi::Tag &tag : taglist) {
182 QString symbol = QStringLiteral("mail-tagged");
183 const auto attr = tag.attribute<Akonadi::TagAttribute>();
184 if (attr) {
185 if (!attr->iconName().isEmpty()) {
186 symbol = attr->iconName();
187 }
188 }
189 auto messageListTag = new MessageItem::Tag(QIcon::fromTheme(symbol).pixmap(KIconLoader::SizeSmall), tag.name(), tag.url().url());
190
191 if (attr) {
192 messageListTag->setTextColor(attr->textColor());
193 messageListTag->setBackgroundColor(attr->backgroundColor());
194 if (!attr->font().isEmpty()) {
195 QFont font;
196 if (font.fromString(attr->font())) {
197 messageListTag->setFont(font);
198 }
199 }
200 if (attr->priority() != -1) {
201 messageListTag->setPriority(attr->priority());
202 } else {
203 messageListTag->setPriority(0xFFFF);
204 }
205 }
206
207 mTagList->append(messageListTag);
208 }
209 }
210
getTagList() const211 QList<MessageItem::Tag *> MessageItemPrivate::getTagList() const
212 {
213 if (!mTagList) {
214 s_tagCache->retrieveTags(mAkonadiItem.tags(), const_cast<MessageItemPrivate *>(this));
215 return QList<MessageItem::Tag *>();
216 }
217
218 return *mTagList;
219 }
220
tagListInitialized() const221 bool MessageItemPrivate::tagListInitialized() const
222 {
223 return mTagList != nullptr;
224 }
225
MessageItem()226 MessageItem::MessageItem()
227 : Item(Message, new MessageItemPrivate(this))
228 , ModelInvariantIndex()
229 {
230 }
231
MessageItem(MessageItemPrivate * dd)232 MessageItem::MessageItem(MessageItemPrivate *dd)
233 : Item(Message, dd)
234 , ModelInvariantIndex()
235 {
236 }
237
~MessageItem()238 MessageItem::~MessageItem()
239 {
240 }
241
tagList() const242 QList<MessageItem::Tag *> MessageItem::tagList() const
243 {
244 Q_D(const MessageItem);
245 return d->getTagList();
246 }
247
hasAnnotation() const248 bool MessageItem::hasAnnotation() const
249 {
250 Q_D(const MessageItem);
251 // TODO check for note entry?
252 return d->mAkonadiItem.hasAttribute<Akonadi::EntityAnnotationsAttribute>();
253 }
254
annotation() const255 QString MessageItem::annotation() const
256 {
257 Q_D(const MessageItem);
258 if (d->mAkonadiItem.hasAttribute<Akonadi::EntityAnnotationsAttribute>()) {
259 auto attr = d->mAkonadiItem.attribute<Akonadi::EntityAnnotationsAttribute>();
260 const auto annotations = attr->annotations();
261 QByteArray annot = annotations.value("/private/comment");
262 if (!annot.isEmpty()) {
263 return QString::fromLatin1(annot);
264 }
265 annot = annotations.value("/shared/comment");
266 if (!annot.isEmpty()) {
267 return QString::fromLatin1(annot);
268 }
269 }
270 return QString();
271 }
272
editAnnotation(QWidget * parent)273 void MessageItem::editAnnotation(QWidget *parent)
274 {
275 Q_D(MessageItem);
276 QPointer<PimCommon::AnnotationEditDialog> mAnnotationDialog = new PimCommon::AnnotationEditDialog(d->mAkonadiItem, parent);
277 // FIXME make async
278 if (mAnnotationDialog->exec()) {
279 // invalidate the cached mHasAnnotation value
280 }
281 delete mAnnotationDialog;
282 }
283
findTagInternal(const QString & szTagId) const284 const MessageItem::Tag *MessageItemPrivate::findTagInternal(const QString &szTagId) const
285 {
286 const auto tagList{getTagList()};
287 for (const MessageItem::Tag *tag : tagList) {
288 if (tag->id() == szTagId) {
289 return tag;
290 }
291 }
292 return nullptr;
293 }
294
findTag(const QString & szTagId) const295 const MessageItem::Tag *MessageItem::findTag(const QString &szTagId) const
296 {
297 Q_D(const MessageItem);
298 return d->findTagInternal(szTagId);
299 }
300
tagListDescription() const301 QString MessageItem::tagListDescription() const
302 {
303 QString ret;
304
305 const auto tags{tagList()};
306 for (const Tag *tag : tags) {
307 if (!ret.isEmpty()) {
308 ret += QLatin1String(", ");
309 }
310 ret += tag->name();
311 }
312
313 return ret;
314 }
315
invalidateTagCache()316 void MessageItem::invalidateTagCache()
317 {
318 Q_D(MessageItem);
319 d->invalidateTagCache();
320 }
321
invalidateAnnotationCache()322 void MessageItem::invalidateAnnotationCache()
323 {
324 Q_D(MessageItem);
325 d->invalidateAnnotationCache();
326 }
327
textColor() const328 const QColor &MessageItem::textColor() const
329 {
330 Q_D(const MessageItem);
331 const Tag *bestTag = d->bestTag();
332 if (bestTag != nullptr && bestTag->textColor().isValid()) {
333 return bestTag->textColor();
334 }
335
336 Akonadi::MessageStatus messageStatus = status();
337 if (!messageStatus.isRead()) {
338 return s_settings->mColorUnreadMessage;
339 } else if (messageStatus.isImportant()) {
340 return s_settings->mColorImportantMessage;
341 } else if (messageStatus.isToAct()) {
342 return s_settings->mColorToDoMessage;
343 } else {
344 return s_settings->mColor;
345 }
346 }
347
backgroundColor() const348 const QColor &MessageItem::backgroundColor() const
349 {
350 Q_D(const MessageItem);
351 const Tag *bestTag = d->bestTag();
352 if (bestTag) {
353 return bestTag->backgroundColor();
354 } else {
355 return s_settings->mBackgroundColor;
356 }
357 }
358
font() const359 const QFont &MessageItem::font() const
360 {
361 Q_D(const MessageItem);
362 // for performance reasons we don't want font retrieval to trigger
363 // full tags loading, as the font is used for geometry calculation
364 // and thus this method called for each item
365 if (d->tagListInitialized()) {
366 const Tag *bestTag = d->bestTag();
367 if (bestTag && bestTag->font() != QFont()) {
368 return bestTag->font();
369 }
370 }
371
372 // from KDE3: "important" overrides "new" overrides "unread" overrides "todo"
373 Akonadi::MessageStatus messageStatus = status();
374 if (messageStatus.isImportant()) {
375 return s_settings->mFontImportantMessage;
376 } else if (!messageStatus.isRead()) {
377 return s_settings->mFontUnreadMessage;
378 } else if (messageStatus.isToAct()) {
379 return s_settings->mFontToDoMessage;
380 } else {
381 return s_settings->mFont;
382 }
383 }
384
signatureState() const385 MessageItem::SignatureState MessageItem::signatureState() const
386 {
387 Q_D(const MessageItem);
388 return d->mSignatureState;
389 }
390
setSignatureState(SignatureState state)391 void MessageItem::setSignatureState(SignatureState state)
392 {
393 Q_D(MessageItem);
394 d->mSignatureState = state;
395 }
396
encryptionState() const397 MessageItem::EncryptionState MessageItem::encryptionState() const
398 {
399 Q_D(const MessageItem);
400 return d->mEncryptionState;
401 }
402
setEncryptionState(EncryptionState state)403 void MessageItem::setEncryptionState(EncryptionState state)
404 {
405 Q_D(MessageItem);
406 d->mEncryptionState = state;
407 }
408
messageIdMD5() const409 QByteArray MessageItem::messageIdMD5() const
410 {
411 Q_D(const MessageItem);
412 return d->mMessageIdMD5;
413 }
414
setMessageIdMD5(const QByteArray & md5)415 void MessageItem::setMessageIdMD5(const QByteArray &md5)
416 {
417 Q_D(MessageItem);
418 d->mMessageIdMD5 = md5;
419 }
420
inReplyToIdMD5() const421 QByteArray MessageItem::inReplyToIdMD5() const
422 {
423 Q_D(const MessageItem);
424 return d->mInReplyToIdMD5;
425 }
426
setInReplyToIdMD5(const QByteArray & md5)427 void MessageItem::setInReplyToIdMD5(const QByteArray &md5)
428 {
429 Q_D(MessageItem);
430 d->mInReplyToIdMD5 = md5;
431 }
432
referencesIdMD5() const433 QByteArray MessageItem::referencesIdMD5() const
434 {
435 Q_D(const MessageItem);
436 return d->mReferencesIdMD5;
437 }
438
setReferencesIdMD5(const QByteArray & md5)439 void MessageItem::setReferencesIdMD5(const QByteArray &md5)
440 {
441 Q_D(MessageItem);
442 d->mReferencesIdMD5 = md5;
443 }
444
setSubjectIsPrefixed(bool subjectIsPrefixed)445 void MessageItem::setSubjectIsPrefixed(bool subjectIsPrefixed)
446 {
447 Q_D(MessageItem);
448 d->mSubjectIsPrefixed = subjectIsPrefixed;
449 }
450
subjectIsPrefixed() const451 bool MessageItem::subjectIsPrefixed() const
452 {
453 Q_D(const MessageItem);
454 return d->mSubjectIsPrefixed;
455 }
456
strippedSubjectMD5() const457 QByteArray MessageItem::strippedSubjectMD5() const
458 {
459 Q_D(const MessageItem);
460 return d->mStrippedSubjectMD5;
461 }
462
setStrippedSubjectMD5(const QByteArray & md5)463 void MessageItem::setStrippedSubjectMD5(const QByteArray &md5)
464 {
465 Q_D(MessageItem);
466 d->mStrippedSubjectMD5 = md5;
467 }
468
aboutToBeRemoved() const469 bool MessageItem::aboutToBeRemoved() const
470 {
471 Q_D(const MessageItem);
472 return d->mAboutToBeRemoved;
473 }
474
setAboutToBeRemoved(bool aboutToBeRemoved)475 void MessageItem::setAboutToBeRemoved(bool aboutToBeRemoved)
476 {
477 Q_D(MessageItem);
478 d->mAboutToBeRemoved = aboutToBeRemoved;
479 }
480
threadingStatus() const481 MessageItem::ThreadingStatus MessageItem::threadingStatus() const
482 {
483 Q_D(const MessageItem);
484 return d->mThreadingStatus;
485 }
486
setThreadingStatus(ThreadingStatus threadingStatus)487 void MessageItem::setThreadingStatus(ThreadingStatus threadingStatus)
488 {
489 Q_D(MessageItem);
490 d->mThreadingStatus = threadingStatus;
491 }
492
uniqueId() const493 unsigned long MessageItem::uniqueId() const
494 {
495 Q_D(const MessageItem);
496 return d->mAkonadiItem.id();
497 }
498
akonadiItem() const499 Akonadi::Item MessageList::Core::MessageItem::akonadiItem() const
500 {
501 Q_D(const MessageItem);
502 return d->mAkonadiItem;
503 }
504
setAkonadiItem(const Akonadi::Item & item)505 void MessageList::Core::MessageItem::setAkonadiItem(const Akonadi::Item &item)
506 {
507 Q_D(MessageItem);
508 d->mAkonadiItem = item;
509 }
510
topmostMessage()511 MessageItem *MessageItem::topmostMessage()
512 {
513 if (!parent()) {
514 return this;
515 }
516 if (parent()->type() == Item::Message) {
517 return static_cast<MessageItem *>(parent())->topmostMessage();
518 }
519 return this;
520 }
521
accessibleTextForField(Theme::ContentItem::Type field)522 QString MessageItem::accessibleTextForField(Theme::ContentItem::Type field)
523 {
524 switch (field) {
525 case Theme::ContentItem::Subject:
526 return d_ptr->mSubject;
527 case Theme::ContentItem::Sender:
528 return d_ptr->mSender;
529 case Theme::ContentItem::Receiver:
530 return d_ptr->mReceiver;
531 case Theme::ContentItem::SenderOrReceiver:
532 return senderOrReceiver();
533 case Theme::ContentItem::Date:
534 return formattedDate();
535 case Theme::ContentItem::Size:
536 return formattedSize();
537 case Theme::ContentItem::RepliedStateIcon:
538 return status().isReplied() ? i18nc("Status of an item", "Replied") : QString();
539 case Theme::ContentItem::ReadStateIcon:
540 return status().isRead() ? i18nc("Status of an item", "Read") : i18nc("Status of an item", "Unread");
541 case Theme::ContentItem::CombinedReadRepliedStateIcon:
542 return accessibleTextForField(Theme::ContentItem::ReadStateIcon) + accessibleTextForField(Theme::ContentItem::RepliedStateIcon);
543 default:
544 return QString();
545 }
546 }
547
accessibleText(const Theme * theme,int columnIndex)548 QString MessageItem::accessibleText(const Theme *theme, int columnIndex)
549 {
550 QStringList rowsTexts;
551 const QList<Theme::Row *> rows = theme->column(columnIndex)->messageRows();
552 rowsTexts.reserve(rows.count());
553
554 for (Theme::Row *row : rows) {
555 QStringList leftStrings;
556 QStringList rightStrings;
557 const auto leftItems = row->leftItems();
558 leftStrings.reserve(leftItems.count());
559 for (Theme::ContentItem *contentItem : std::as_const(leftItems)) {
560 leftStrings.append(accessibleTextForField(contentItem->type()));
561 }
562
563 const auto rightItems = row->rightItems();
564 rightStrings.reserve(rightItems.count());
565 for (Theme::ContentItem *contentItem : rightItems) {
566 rightStrings.insert(rightStrings.begin(), accessibleTextForField(contentItem->type()));
567 }
568
569 rowsTexts.append((leftStrings + rightStrings).join(QLatin1Char(' ')));
570 }
571
572 return rowsTexts.join(QLatin1Char(' '));
573 }
574
subTreeToList(QVector<MessageItem * > & list)575 void MessageItem::subTreeToList(QVector<MessageItem *> &list)
576 {
577 list.append(this);
578 const auto childList = childItems();
579 if (!childList) {
580 return;
581 }
582 for (const auto child : std::as_const(*childList)) {
583 Q_ASSERT(child->type() == Item::Message);
584 static_cast<MessageItem *>(child)->subTreeToList(list);
585 }
586 }
587
setUnreadMessageColor(const QColor & color)588 void MessageItem::setUnreadMessageColor(const QColor &color)
589 {
590 s_settings->mColorUnreadMessage = color;
591 }
592
setImportantMessageColor(const QColor & color)593 void MessageItem::setImportantMessageColor(const QColor &color)
594 {
595 s_settings->mColorImportantMessage = color;
596 }
597
setToDoMessageColor(const QColor & color)598 void MessageItem::setToDoMessageColor(const QColor &color)
599 {
600 s_settings->mColorToDoMessage = color;
601 }
602
setGeneralFont(const QFont & font)603 void MessageItem::setGeneralFont(const QFont &font)
604 {
605 s_settings->mFont = font;
606 }
607
setUnreadMessageFont(const QFont & font)608 void MessageItem::setUnreadMessageFont(const QFont &font)
609 {
610 s_settings->mFontUnreadMessage = font;
611 }
612
setImportantMessageFont(const QFont & font)613 void MessageItem::setImportantMessageFont(const QFont &font)
614 {
615 s_settings->mFontImportantMessage = font;
616 }
617
setToDoMessageFont(const QFont & font)618 void MessageItem::setToDoMessageFont(const QFont &font)
619 {
620 s_settings->mFontToDoMessage = font;
621 }
622
FakeItemPrivate(FakeItem * qq)623 FakeItemPrivate::FakeItemPrivate(FakeItem *qq)
624 : MessageItemPrivate(qq)
625 {
626 }
627
FakeItem()628 FakeItem::FakeItem()
629 : MessageItem(new FakeItemPrivate(this))
630 {
631 }
632
~FakeItem()633 FakeItem::~FakeItem()
634 {
635 Q_D(const FakeItem);
636 qDeleteAll(d->mFakeTags);
637 }
638
tagList() const639 QList<MessageItem::Tag *> FakeItem::tagList() const
640 {
641 Q_D(const FakeItem);
642 return d->mFakeTags;
643 }
644
setFakeTags(const QList<MessageItem::Tag * > & tagList)645 void FakeItem::setFakeTags(const QList<MessageItem::Tag *> &tagList)
646 {
647 Q_D(FakeItem);
648 d->mFakeTags = tagList;
649 }
650
hasAnnotation() const651 bool FakeItem::hasAnnotation() const
652 {
653 return true;
654 }
655
TagCache()656 TagCache::TagCache()
657 : QObject()
658 , mMonitor(new Akonadi::Monitor(this))
659 {
660 mCache.setMaxCost(100);
661 mMonitor->setObjectName(QStringLiteral("MessageListTagCacheMonitor"));
662 mMonitor->setTypeMonitored(Akonadi::Monitor::Tags);
663 mMonitor->tagFetchScope().fetchAttribute<Akonadi::TagAttribute>();
664 connect(mMonitor, &Akonadi::Monitor::tagAdded, this, &TagCache::onTagAdded);
665 connect(mMonitor, &Akonadi::Monitor::tagRemoved, this, &TagCache::onTagRemoved);
666 connect(mMonitor, &Akonadi::Monitor::tagChanged, this, &TagCache::onTagChanged);
667 }
668
onTagAdded(const Akonadi::Tag & tag)669 void TagCache::onTagAdded(const Akonadi::Tag &tag)
670 {
671 mCache.insert(tag.id(), new Akonadi::Tag(tag));
672 }
673
onTagChanged(const Akonadi::Tag & tag)674 void TagCache::onTagChanged(const Akonadi::Tag &tag)
675 {
676 mCache.remove(tag.id());
677 }
678
onTagRemoved(const Akonadi::Tag & tag)679 void TagCache::onTagRemoved(const Akonadi::Tag &tag)
680 {
681 mCache.remove(tag.id());
682 }
683
retrieveTags(const Akonadi::Tag::List & tags,MessageItemPrivate * m)684 void TagCache::retrieveTags(const Akonadi::Tag::List &tags, MessageItemPrivate *m)
685 {
686 // Retrieval is in progress
687 if (mRequests.key(m)) {
688 return;
689 }
690 Akonadi::Tag::List toFetch;
691 Akonadi::Tag::List available;
692 for (const Akonadi::Tag &tag : tags) {
693 if (mCache.contains(tag.id())) {
694 available << *mCache.object(tag.id());
695 } else {
696 toFetch << tag;
697 }
698 }
699 // Because fillTagList expects to be called once we either fetch all or none
700 if (!toFetch.isEmpty()) {
701 auto tagFetchJob = new Akonadi::TagFetchJob(tags, this);
702 tagFetchJob->fetchScope().fetchAttribute<Akonadi::TagAttribute>();
703 connect(tagFetchJob, &Akonadi::TagFetchJob::result, this, &TagCache::onTagsFetched);
704 mRequests.insert(tagFetchJob, m);
705 } else {
706 m->fillTagList(available);
707 }
708 }
709
cancelRequest(MessageItemPrivate * m)710 void TagCache::cancelRequest(MessageItemPrivate *m)
711 {
712 const QList<KJob *> keys = mRequests.keys(m);
713 for (KJob *job : keys) {
714 mRequests.remove(job);
715 }
716 }
717
onTagsFetched(KJob * job)718 void TagCache::onTagsFetched(KJob *job)
719 {
720 if (job->error()) {
721 qCWarning(MESSAGELIST_LOG) << "Failed to fetch tags: " << job->errorString();
722 return;
723 }
724 auto fetchJob = static_cast<Akonadi::TagFetchJob *>(job);
725 const auto tags{fetchJob->tags()};
726 for (const Akonadi::Tag &tag : tags) {
727 mCache.insert(tag.id(), new Akonadi::Tag(tag));
728 }
729 if (auto m = mRequests.take(fetchJob)) {
730 m->fillTagList(fetchJob->tags());
731 }
732 }
733