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