1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
2
3 This file is part of the Trojita Qt IMAP e-mail client,
4 http://trojita.flaska.net/
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License as
8 published by the Free Software Foundation; either version 2 of
9 the License or (at your option) version 3 or any later version
10 accepted by the membership of KDE e.V. (or its successor approved
11 by the membership of KDE e.V.), which shall act as a proxy
12 defined in Section 14 of version 3 of the license.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include <algorithm>
24 #include <QTextStream>
25 #include "Common/FindWithUnknown.h"
26 #include "Common/InvokeMethod.h"
27 #include "Common/MetaTypes.h"
28 #include "Imap/Encoders.h"
29 #include "Imap/Parser/Rfc5322HeaderParser.h"
30 #include "Imap/Tasks/KeepMailboxOpenTask.h"
31 #include "UiUtils/Formatting.h"
32 #include "ItemRoles.h"
33 #include "MailboxTree.h"
34 #include "Model.h"
35 #include "SpecialFlagNames.h"
36 #include <QtDebug>
37
38
39 namespace Imap
40 {
41 namespace Mailbox
42 {
43
TreeItem(TreeItem * parent)44 TreeItem::TreeItem(TreeItem *parent): m_parent(parent)
45 {
46 // These just have to be present in the context of TreeItem, otherwise they couldn't access the protected members
47 static_assert(static_cast<intptr_t>(alignof(TreeItem)) > TreeItem::TagMask,
48 "class TreeItem must be aligned at at least four bytes due to the FetchingState optimization");
49 static_assert(DONE <= TagMask, "Invalid masking for pointer tag access");
50 }
51
~TreeItem()52 TreeItem::~TreeItem()
53 {
54 qDeleteAll(m_children);
55 }
56
childrenCount(Model * const model)57 unsigned int TreeItem::childrenCount(Model *const model)
58 {
59 fetch(model);
60 return m_children.size();
61 }
62
child(int offset,Model * const model)63 TreeItem *TreeItem::child(int offset, Model *const model)
64 {
65 fetch(model);
66 if (offset >= 0 && offset < m_children.size())
67 return m_children[ offset ];
68 else
69 return 0;
70 }
71
row() const72 int TreeItem::row() const
73 {
74 return parent() ? parent()->m_children.indexOf(const_cast<TreeItem *>(this)) : 0;
75 }
76
setChildren(const TreeItemChildrenList & items)77 TreeItemChildrenList TreeItem::setChildren(const TreeItemChildrenList &items)
78 {
79 auto res = m_children;
80 m_children = items;
81 setFetchStatus(DONE);
82 return res;
83 }
84
isUnavailable() const85 bool TreeItem::isUnavailable() const
86 {
87 return accessFetchStatus() == UNAVAILABLE;
88 }
89
columnCount()90 unsigned int TreeItem::columnCount()
91 {
92 return 1;
93 }
94
specialColumnPtr(int row,int column) const95 TreeItem *TreeItem::specialColumnPtr(int row, int column) const
96 {
97 Q_UNUSED(row);
98 Q_UNUSED(column);
99 return 0;
100 }
101
toIndex(Model * const model) const102 QModelIndex TreeItem::toIndex(Model *const model) const
103 {
104 Q_ASSERT(model);
105 if (this == model->m_mailboxes)
106 return QModelIndex();
107 // void* != const void*, but I believe that it's safe in this context
108 return model->createIndex(row(), 0, const_cast<TreeItem *>(this));
109 }
110
111
TreeItemMailbox(TreeItem * parent)112 TreeItemMailbox::TreeItemMailbox(TreeItem *parent): TreeItem(parent), maintainingTask(0)
113 {
114 m_children.prepend(new TreeItemMsgList(this));
115 }
116
TreeItemMailbox(TreeItem * parent,Responses::List response)117 TreeItemMailbox::TreeItemMailbox(TreeItem *parent, Responses::List response):
118 TreeItem(parent), m_metadata(response.mailbox, response.separator, QStringList()), maintainingTask(0)
119 {
120 for (QStringList::const_iterator it = response.flags.constBegin(); it != response.flags.constEnd(); ++it)
121 m_metadata.flags.append(it->toUpper());
122 m_children.prepend(new TreeItemMsgList(this));
123 }
124
~TreeItemMailbox()125 TreeItemMailbox::~TreeItemMailbox()
126 {
127 if (maintainingTask) {
128 maintainingTask->dieIfInvalidMailbox();
129 }
130 }
131
fromMetadata(TreeItem * parent,const MailboxMetadata & metadata)132 TreeItemMailbox *TreeItemMailbox::fromMetadata(TreeItem *parent, const MailboxMetadata &metadata)
133 {
134 TreeItemMailbox *res = new TreeItemMailbox(parent);
135 res->m_metadata = metadata;
136 return res;
137 }
138
fetch(Model * const model)139 void TreeItemMailbox::fetch(Model *const model)
140 {
141 fetchWithCacheControl(model, false);
142 }
143
fetchWithCacheControl(Model * const model,bool forceReload)144 void TreeItemMailbox::fetchWithCacheControl(Model *const model, bool forceReload)
145 {
146 if (fetched() || isUnavailable())
147 return;
148
149 if (hasNoChildMailboxesAlreadyKnown()) {
150 setFetchStatus(DONE);
151 return;
152 }
153
154 if (! loading()) {
155 setFetchStatus(LOADING);
156 QModelIndex mailboxIndex = toIndex(model);
157 CALL_LATER(model, askForChildrenOfMailbox, Q_ARG(QModelIndex, mailboxIndex),
158 Q_ARG(Imap::Mailbox::CacheLoadingMode, forceReload ? LOAD_FORCE_RELOAD : LOAD_CACHED_IS_OK));
159 }
160 }
161
rescanForChildMailboxes(Model * const model)162 void TreeItemMailbox::rescanForChildMailboxes(Model *const model)
163 {
164 if (accessFetchStatus() != LOADING) {
165 setFetchStatus(NONE);
166 fetchWithCacheControl(model, true);
167 }
168 }
169
rowCount(Model * const model)170 unsigned int TreeItemMailbox::rowCount(Model *const model)
171 {
172 fetch(model);
173 return m_children.size();
174 }
175
data(Model * const model,int role)176 QVariant TreeItemMailbox::data(Model *const model, int role)
177 {
178 switch (role) {
179 case RoleIsFetched:
180 return fetched();
181 case RoleIsUnavailable:
182 return isUnavailable();
183 };
184
185 if (!parent())
186 return QVariant();
187
188 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(m_children[0]);
189 Q_ASSERT(list);
190
191 switch (role) {
192 case Qt::DisplayRole:
193 {
194 // this one is used only for a dumb view attached to the Model
195 QString res = separator().isEmpty() ? mailbox() : mailbox().split(separator(), QString::SkipEmptyParts).last();
196 return loading() ? res + QLatin1String(" [loading]") : res;
197 }
198 case RoleShortMailboxName:
199 return separator().isEmpty() ? mailbox() : mailbox().split(separator(), QString::SkipEmptyParts).last();
200 case RoleMailboxName:
201 return mailbox();
202 case RoleMailboxSeparator:
203 return separator();
204 case RoleMailboxHasChildMailboxes:
205 return hasChildMailboxes(model);
206 case RoleMailboxIsINBOX:
207 return mailbox().toUpper() == QLatin1String("INBOX");
208 case RoleMailboxIsSelectable:
209 return isSelectable();
210 case RoleMailboxNumbersFetched:
211 return list->numbersFetched();
212 case RoleTotalMessageCount:
213 {
214 if (! isSelectable())
215 return QVariant();
216 // At first, register that request for count
217 int res = list->totalMessageCount(model);
218 // ...and now that it's been sent, display a number if it's available, or if it was available before.
219 // It's better to return a value which is maybe already obsolete than to hide a value which might
220 // very well be still correct.
221 return list->numbersFetched() || res != -1 ? QVariant(res) : QVariant();
222 }
223 case RoleUnreadMessageCount:
224 {
225 if (! isSelectable())
226 return QVariant();
227 // This one is similar to the case of RoleTotalMessageCount
228 int res = list->unreadMessageCount(model);
229 return list->numbersFetched() || res != -1 ? QVariant(res): QVariant();
230 }
231 case RoleRecentMessageCount:
232 {
233 if (! isSelectable())
234 return QVariant();
235 // see above
236 int res = list->recentMessageCount(model);
237 return list->numbersFetched() || res != -1 ? QVariant(res): QVariant();
238 }
239 case RoleMailboxItemsAreLoading:
240 return list->loading() || (isSelectable() && ! list->numbersFetched());
241 case RoleMailboxUidValidity:
242 {
243 list->fetch(model);
244 return list->fetched() ? QVariant(syncState.uidValidity()) : QVariant();
245 }
246 case RoleMailboxIsSubscribed:
247 return QVariant::fromValue<bool>(m_metadata.flags.contains(QStringLiteral("\\SUBSCRIBED")));
248 default:
249 return QVariant();
250 }
251 }
252
hasChildren(Model * const model)253 bool TreeItemMailbox::hasChildren(Model *const model)
254 {
255 Q_UNUSED(model);
256 return true; // we have that "messages" thing built in
257 }
258
259 QLatin1String TreeItemMailbox::flagNoInferiors("\\NOINFERIORS");
260 QLatin1String TreeItemMailbox::flagHasNoChildren("\\HASNOCHILDREN");
261 QLatin1String TreeItemMailbox::flagHasChildren("\\HASCHILDREN");
262
hasNoChildMailboxesAlreadyKnown()263 bool TreeItemMailbox::hasNoChildMailboxesAlreadyKnown()
264 {
265 if (m_metadata.flags.contains(flagNoInferiors) ||
266 (m_metadata.flags.contains(flagHasNoChildren) &&
267 ! m_metadata.flags.contains(flagHasChildren)))
268 return true;
269 else
270 return false;
271 }
272
hasChildMailboxes(Model * const model)273 bool TreeItemMailbox::hasChildMailboxes(Model *const model)
274 {
275 if (fetched() || isUnavailable()) {
276 return m_children.size() > 1;
277 } else if (hasNoChildMailboxesAlreadyKnown()) {
278 return false;
279 } else if (m_metadata.flags.contains(flagHasChildren) && ! m_metadata.flags.contains(flagHasNoChildren)) {
280 return true;
281 } else {
282 fetch(model);
283 return m_children.size() > 1;
284 }
285 }
286
child(const int offset,Model * const model)287 TreeItem *TreeItemMailbox::child(const int offset, Model *const model)
288 {
289 // accessing TreeItemMsgList doesn't need fetch()
290 if (offset == 0)
291 return m_children[ 0 ];
292
293 return TreeItem::child(offset, model);
294 }
295
setChildren(const TreeItemChildrenList & items)296 TreeItemChildrenList TreeItemMailbox::setChildren(const TreeItemChildrenList &items)
297 {
298 // This function has to be special because we want to preserve m_children[0]
299
300 TreeItemMsgList *msgList = dynamic_cast<TreeItemMsgList *>(m_children[0]);
301 Q_ASSERT(msgList);
302 m_children.erase(m_children.begin());
303
304 auto list = TreeItem::setChildren(items); // this also adjusts m_loading and m_fetched
305 m_children.prepend(msgList);
306
307 return list;
308 }
309
handleFetchResponse(Model * const model,const Responses::Fetch & response,QList<TreeItemPart * > & changedParts,TreeItemMessage * & changedMessage,bool usingQresync)310 void TreeItemMailbox::handleFetchResponse(Model *const model,
311 const Responses::Fetch &response,
312 QList<TreeItemPart *> &changedParts,
313 TreeItemMessage *&changedMessage, bool usingQresync)
314 {
315 TreeItemMsgList *list = static_cast<TreeItemMsgList *>(m_children[0]);
316
317 Responses::Fetch::dataType::const_iterator uidRecord = response.data.find("UID");
318
319 // Previously, we would ignore any FETCH responses until we are fully synced. This is rather hard do to "properly",
320 // though.
321 // What we want to achieve is to never store data into a "wrong" message. Theoretically, we are prone to just this
322 // in case the server sends us unsolicited data before we are fully synced. When this happens for flags, it's a pretty
323 // harmless operation as we're going to re-fetch the flags for the concerned part of mailbox anyway (even with CONDSTORE,
324 // and this is never an issue with QRESYNC).
325 // It's worse when the data refer to some immutable piece of information like the bodystructure or body parts.
326 // If that happens, then we have to actively prevent the data from being stored because we cannot know whether we would
327 // be putting it into a correct bucket^Hmessage.
328 bool ignoreImmutableData = !list->fetched() && uidRecord == response.data.constEnd();
329
330 int number = response.number - 1;
331 if (number < 0 || number >= list->m_children.size())
332 throw UnknownMessageIndex(QStringLiteral("Got FETCH that is out of bounds -- got %1 messages").arg(
333 QString::number(list->m_children.size())).toUtf8().constData(), response);
334
335 TreeItemMessage *message = static_cast<TreeItemMessage *>(list->child(number, model));
336
337 // At first, have a look at the response and check the UID of the message
338 if (uidRecord != response.data.constEnd()) {
339 uint receivedUid = static_cast<const Responses::RespData<uint>&>(*(uidRecord.value())).data;
340 if (receivedUid == 0) {
341 throw MailboxException(QStringLiteral("Server claims that message #%1 has UID 0")
342 .arg(QString::number(response.number)).toUtf8().constData(), response);
343 } else if (message->uid() == receivedUid) {
344 // That's what we expect -> do nothing
345 } else if (message->uid() == 0) {
346 // This is the first time we see the UID, so let's take a note
347 message->m_uid = receivedUid;
348 changedMessage = message;
349 if (message->loading()) {
350 // The Model tried to ask for data for this message. That couldn't succeeded because the UID
351 // wasn't known at that point, so let's ask now
352 //
353 // FIXME: tweak this to keep a high watermark of "highest UID we requested an ENVELOPE for",
354 // issue bulk fetches in the same manner as we do the UID FETCH (FLAGS) when discovering UIDs,
355 // and at this place in code, only ask for the metadata when the UID is higher than the watermark.
356 // Optionally, simply ask for the ENVELOPE etc along with the FLAGS upon new message arrivals, maybe
357 // with some limit on the number of pending fetches. And make that dapandent on online/expensive modes.
358 message->setFetchStatus(NONE);
359 message->fetch(model);
360 }
361 if (syncState.uidNext() <= receivedUid) {
362 // Try to guess the UIDNEXT. We have to take an educated guess here, and I believe that this approach
363 // at least is not wrong. The server won't tell us the UIDNEXT (well, it could, but it doesn't have to),
364 // the only way of asking for it is via STATUS which is not allowed to reference the current mailbox and
365 // even if it was, it wouldn't be atomic. So, what could the UIDNEXT possibly be? It can't be smaller
366 // than the UID_of_highest_message, and it can't be the same, either, so it really has to be higher.
367 // Let's just increment it by one, this is our lower bound.
368 // Not guessing the UIDNEXT correctly would result at decreased performance at the next sync, and we
369 // can't really do better -> let's just set it now, along with the UID mapping.
370 syncState.setUidNext(receivedUid + 1);
371 list->setFetchStatus(LOADING);
372 }
373 } else {
374 throw MailboxException(QStringLiteral("FETCH response: UID consistency error for message #%1 -- expected UID %2, got UID %3").arg(
375 QString::number(response.number), QString::number(message->uid()), QString::number(receivedUid)
376 ).toUtf8().constData(), response);
377 }
378 } else if (! message->uid()) {
379 qDebug() << "FETCH: received a FETCH response for message #" << response.number << "whose UID is not yet known. This sucks.";
380 QList<uint> uidsInMailbox;
381 Q_FOREACH(TreeItem *node, list->m_children) {
382 uidsInMailbox << static_cast<TreeItemMessage *>(node)->uid();
383 }
384 qDebug() << "UIDs in the mailbox now: " << uidsInMailbox;
385 }
386
387 bool updatedFlags = false;
388
389 for (Responses::Fetch::dataType::const_iterator it = response.data.begin(); it != response.data.end(); ++ it) {
390 if (it.key() == "UID") {
391 // established above
392 Q_ASSERT(static_cast<const Responses::RespData<uint>&>(*(it.value())).data == message->uid());
393 } else if (it.key() == "FLAGS") {
394 // Only emit signals when the flags have actually changed
395 QStringList newFlags = model->normalizeFlags(static_cast<const Responses::RespData<QStringList>&>(*(it.value())).data);
396 bool forceChange = !message->m_flagsHandled || (message->m_flags != newFlags);
397 message->setFlags(list, newFlags);
398 if (forceChange) {
399 updatedFlags = true;
400 changedMessage = message;
401 }
402 } else if (it.key() == "MODSEQ") {
403 quint64 num = static_cast<const Responses::RespData<quint64>&>(*(it.value())).data;
404 if (num > syncState.highestModSeq()) {
405 syncState.setHighestModSeq(num);
406 if (list->accessFetchStatus() == DONE) {
407 // This means that everything is known already, so we are by definition OK to save stuff to disk.
408 // We can also skip rebuilding the UID map and save just the HIGHESTMODSEQ, i.e. the SyncState.
409 model->cache()->setMailboxSyncState(mailbox(), syncState);
410 } else {
411 // it's already marked as dirty -> nothing to do here
412 }
413 }
414 } else if (ignoreImmutableData) {
415 QByteArray buf;
416 QTextStream ss(&buf);
417 ss << response;
418 ss.flush();
419 qDebug() << "Ignoring FETCH response to a mailbox that isn't synced yet:" << buf;
420 continue;
421 } else if (it.key() == "ENVELOPE") {
422 message->data()->setEnvelope(static_cast<const Responses::RespData<Message::Envelope>&>(*(it.value())).data);
423 changedMessage = message;
424 } else if (it.key() == "BODYSTRUCTURE") {
425 if (message->data()->gotRemeberedBodyStructure() || message->fetched()) {
426 // The message structure is already known, so we are free to ignore it
427 } else {
428 // We had no idea about the structure of the message
429
430 // At first, save the bodystructure. This is needed so that our overridden rowCount() works properly.
431 // (The rowCount() gets called through QAIM::beginInsertRows(), for example.)
432 auto xtbIt = response.data.constFind("x-trojita-bodystructure");
433 Q_ASSERT(xtbIt != response.data.constEnd());
434 message->data()->setRememberedBodyStructure(
435 static_cast<const Responses::RespData<QByteArray>&>(*(xtbIt.value())).data);
436
437 // Now insert the children. We're of course assuming that the TreeItemMessage is now empty.
438 auto newChildren = static_cast<const Message::AbstractMessage &>(*(it.value())).createTreeItems(message);
439 Q_ASSERT(!newChildren.isEmpty());
440 Q_ASSERT(message->m_children.isEmpty());
441 QModelIndex messageIdx = message->toIndex(model);
442 model->beginInsertRows(messageIdx, 0, newChildren.size() - 1);
443 message->setChildren(newChildren);
444 model->endInsertRows();
445 }
446 } else if (it.key() == "x-trojita-bodystructure") {
447 // do nothing here, it's been already taken care of from the BODYSTRUCTURE handler
448 } else if (it.key() == "RFC822.SIZE") {
449 message->data()->setSize(static_cast<const Responses::RespData<quint64>&>(*(it.value())).data);
450 } else if (it.key().startsWith("BODY[HEADER.FIELDS (")) {
451 // Process any headers found in any such response bit
452 const QByteArray &rawHeaders = static_cast<const Responses::RespData<QByteArray>&>(*(it.value())).data;
453 message->processAdditionalHeaders(model, rawHeaders);
454 changedMessage = message;
455 } else if (it.key().startsWith("BODY[") || it.key().startsWith("BINARY[")) {
456 if (it.key()[ it.key().size() - 1 ] != ']')
457 throw UnknownMessageIndex("Can't parse such BODY[]/BINARY[]", response);
458 TreeItemPart *part = partIdToPtr(model, message, it.key());
459 if (! part)
460 throw UnknownMessageIndex("Got BODY[]/BINARY[] fetch that did not resolve to any known part", response);
461 const QByteArray &data = static_cast<const Responses::RespData<QByteArray>&>(*(it.value())).data;
462 if (it.key().startsWith("BODY[")) {
463
464 // Check whether we are supposed to be loading the raw, undecoded part as well.
465 // The check has to be done via a direct pointer access to m_partRaw to make sure that it does not
466 // get instantiated when not actually needed.
467 if (part->m_partRaw && part->m_partRaw->loading()) {
468 part->m_partRaw->m_data = data;
469 part->m_partRaw->setFetchStatus(DONE);
470 changedParts.append(part->m_partRaw);
471 if (message->uid()) {
472 model->cache()->forgetMessagePart(mailbox(), message->uid(), part->partId());
473 model->cache()->setMsgPart(mailbox(), message->uid(), part->partId() + ".X-RAW", data);
474 }
475 }
476
477 // Do not overwrite the part data if we were not asked to fetch it.
478 // One possibility is that it's already there because it was fetched before. The second option is that
479 // we were in fact asked to only fetch the raw data and the user is not itnerested in the processed data at all.
480 if (part->loading()) {
481 // got to decode the part data by hand
482 Imap::decodeContentTransferEncoding(data, part->encoding(), part->dataPtr());
483 part->setFetchStatus(DONE);
484 changedParts.append(part);
485 if (message->uid()
486 && model->cache()->messagePart(mailbox(), message->uid(), part->partId() + ".X-RAW").isNull()) {
487 // Do not store the data into cache if the raw data are already there
488 model->cache()->setMsgPart(mailbox(), message->uid(), part->partId(), part->m_data);
489 }
490 }
491
492 } else {
493 // A BINARY FETCH item is already decoded for us, yay
494 part->m_data = data;
495 part->setFetchStatus(DONE);
496 changedParts.append(part);
497 if (message->uid()) {
498 model->cache()->setMsgPart(mailbox(), message->uid(), part->partId(), part->m_data);
499 }
500 }
501 } else if (it.key() == "INTERNALDATE") {
502 message->data()->setInternalDate(static_cast<const Responses::RespData<QDateTime>&>(*(it.value())).data);
503 } else {
504 qDebug() << "TreeItemMailbox::handleFetchResponse: unknown FETCH identifier" << it.key();
505 }
506 }
507 if (message->uid()) {
508 if (message->data()->isComplete() && model->cache()->messageMetadata(mailbox(), message->uid()).uid == 0) {
509 model->cache()->setMessageMetadata(
510 mailbox(), message->uid(),
511 Imap::Mailbox::AbstractCache::MessageDataBundle(
512 message->uid(),
513 message->data()->envelope(),
514 message->data()->internalDate(),
515 message->data()->size(),
516 message->data()->rememberedBodyStructure(),
517 message->data()->hdrReferences(),
518 message->data()->hdrListPost(),
519 message->data()->hdrListPostNo()
520 ));
521 message->setFetchStatus(DONE);
522 }
523 if (updatedFlags) {
524 model->cache()->setMsgFlags(mailbox(), message->uid(), message->m_flags);
525 }
526 }
527 }
528
529 /** @short Save the sync state and the UID mapping into the cache
530
531 Please note that FLAGS are still being updated "asynchronously", i.e. immediately when an update arrives. The motivation
532 behind this is that both SyncState and UID mapping just absolutely have to be kept in sync due to the way they are used where
533 our syncing code simply expects both to match each other. There cannot ever be any 0 UIDs in the saved UID mapping, and the
534 number in EXISTS and the amount of cached UIDs is not supposed to differ or all bets are off.
535
536 The flags, on the other hand, are not critical -- if a message gets saved with the correct flags "too early", i.e. before
537 the corresponding SyncState and/or UIDs are saved, the wors case which could possibly happen are data which do not match the
538 old state any longer. But the old state is not important anyway because it's already gone on the server.
539 */
saveSyncStateAndUids(Model * model)540 void TreeItemMailbox::saveSyncStateAndUids(Model * model)
541 {
542 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList*>(m_children[0]);
543 if (list->m_unreadMessageCount != -1) {
544 syncState.setUnSeenCount(list->m_unreadMessageCount);
545 }
546 if (list->m_recentMessageCount != -1) {
547 syncState.setRecent(list->m_recentMessageCount);
548 }
549 model->cache()->setMailboxSyncState(mailbox(), syncState);
550 model->saveUidMap(list);
551 list->setFetchStatus(DONE);
552 }
553
554 /** @short Process the EXPUNGE response when the UIDs are already synced */
handleExpunge(Model * const model,const Responses::NumberResponse & resp)555 void TreeItemMailbox::handleExpunge(Model *const model, const Responses::NumberResponse &resp)
556 {
557 Q_ASSERT(resp.kind == Responses::EXPUNGE);
558 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(m_children[ 0 ]);
559 Q_ASSERT(list);
560 if (resp.number > static_cast<uint>(list->m_children.size()) || resp.number == 0) {
561 throw UnknownMessageIndex("EXPUNGE references message number which is out-of-bounds");
562 }
563 uint offset = resp.number - 1;
564
565 model->beginRemoveRows(list->toIndex(model), offset, offset);
566 auto it = list->m_children.begin() + offset;
567 TreeItemMessage *message = static_cast<TreeItemMessage *>(*it);
568 list->m_children.erase(it);
569 model->cache()->clearMessage(static_cast<TreeItemMailbox *>(list->parent())->mailbox(), message->uid());
570 for (int i = offset; i < list->m_children.size(); ++i) {
571 --static_cast<TreeItemMessage *>(list->m_children[i])->m_offset;
572 }
573 model->endRemoveRows();
574
575 --list->m_totalMessageCount;
576 list->recalcVariousMessageCountsOnExpunge(const_cast<Model *>(model), message);
577
578 delete message;
579
580 // The UID map is not synced at this time, though, and we defer a decision on when to do this to the context
581 // of the task which invoked this method. The idea is that this task has a better insight for potentially
582 // batching these changes to prevent useless hammering of the saveUidMap() etc.
583 // Previously, the code would simetimes do this twice in a row, which is kinda suboptimal...
584 }
585
handleVanished(Model * const model,const Responses::Vanished & resp)586 void TreeItemMailbox::handleVanished(Model *const model, const Responses::Vanished &resp)
587 {
588 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(m_children[ 0 ]);
589 Q_ASSERT(list);
590 QModelIndex listIndex = list->toIndex(model);
591
592 auto uids = resp.uids;
593 qSort(uids);
594 // Remove duplicates -- even that garbage can be present in a perfectly valid VANISHED :(
595 uids.erase(std::unique(uids.begin(), uids.end()), uids.end());
596
597 auto it = list->m_children.end();
598 while (!uids.isEmpty()) {
599 // We have to process each UID separately because the UIDs in the mailbox are not necessarily present
600 // in a continuous range; zeros might be present
601 uint uid = uids.last();
602 uids.pop_back();
603
604 if (uid == 0) {
605 qDebug() << "VANISHED informs about removal of UID zero...";
606 model->logTrace(listIndex.parent(), Common::LOG_MAILBOX_SYNC, QStringLiteral("TreeItemMailbox::handleVanished"),
607 QStringLiteral("VANISHED contains UID zero for increased fun"));
608 break;
609 }
610
611 if (list->m_children.isEmpty()) {
612 // Well, it'd be cool to throw an exception here but VANISHED is free to contain references to UIDs which are not here
613 // at all...
614 qDebug() << "VANISHED attempted to remove too many messages";
615 model->logTrace(listIndex.parent(), Common::LOG_MAILBOX_SYNC, QStringLiteral("TreeItemMailbox::handleVanished"),
616 QStringLiteral("VANISHED attempted to remove too many messages"));
617 break;
618 }
619
620 // Find a highest message with UID zero such as no message with non-zero UID higher than the current UID exists
621 // at a position after the target message
622 it = model->findMessageOrNextOneByUid(list, uid);
623
624 if (it == list->m_children.end()) {
625 // this is a legitimate situation, the UID of the last message in the mailbox which is getting expunged right now
626 // could very well be not know at this point
627 --it;
628 }
629 // there's a special case above guarding against an empty list
630 Q_ASSERT(it >= list->m_children.begin());
631
632 TreeItemMessage *msgCandidate = static_cast<TreeItemMessage*>(*it);
633 if (msgCandidate->uid() == uid) {
634 // will be deleted
635 } else if (resp.earlier == Responses::Vanished::EARLIER) {
636 // We don't have any such UID in our UID mapping, so we can safely ignore this one
637 continue;
638 } else if (msgCandidate->uid() == 0) {
639 // will be deleted
640 } else {
641 if (it != list->m_children.begin()) {
642 --it;
643 msgCandidate = static_cast<TreeItemMessage*>(*it);
644 if (msgCandidate->uid() == 0) {
645 // will be deleted
646 } else {
647 // VANISHED is free to refer to a non-existing UID...
648 QString str;
649 QTextStream ss(&str);
650 ss << "VANISHED refers to UID " << uid << " which wasn't found in the mailbox (found adjacent UIDs " <<
651 msgCandidate->uid() << " and " << static_cast<TreeItemMessage*>(*(it + 1))->uid() << " with " <<
652 static_cast<TreeItemMessage*>(*(list->m_children.end() - 1))->uid() << " at the end)";
653 ss.flush();
654 qDebug() << str.toUtf8().constData();
655 model->logTrace(listIndex.parent(), Common::LOG_MAILBOX_SYNC, QStringLiteral("TreeItemMailbox::handleVanished"), str);
656 continue;
657 }
658 } else {
659 // Again, VANISHED can refer to non-existing UIDs
660 QString str;
661 QTextStream ss(&str);
662 ss << "VANISHED refers to UID " << uid << " which is too low (lowest UID is " <<
663 static_cast<TreeItemMessage*>(list->m_children.front())->uid() << ")";
664 ss.flush();
665 qDebug() << str.toUtf8().constData();
666 model->logTrace(listIndex.parent(), Common::LOG_MAILBOX_SYNC, QStringLiteral("TreeItemMailbox::handleVanished"), str);
667 continue;
668 }
669 }
670
671 int row = msgCandidate->row();
672 Q_ASSERT(row == it - list->m_children.begin());
673 model->beginRemoveRows(listIndex, row, row);
674 it = list->m_children.erase(it);
675 for (auto furtherMessage = it; furtherMessage != list->m_children.end(); ++furtherMessage) {
676 --static_cast<TreeItemMessage *>(*furtherMessage)->m_offset;
677 }
678 model->endRemoveRows();
679
680 if (syncState.uidNext() <= uid) {
681 // We're informed about a message being deleted; this means that that UID must have been in the mailbox for some
682 // (possibly tiny) time and we can therefore use it to get an idea about the UIDNEXT
683 syncState.setUidNext(uid + 1);
684 }
685 model->cache()->clearMessage(mailbox(), uid);
686 delete msgCandidate;
687 }
688
689 if (resp.earlier == Responses::Vanished::EARLIER && static_cast<uint>(list->m_children.size()) < syncState.exists()) {
690 // Okay, there were some new arrivals which we failed to take into account because we had processed EXISTS
691 // before VANISHED (EARLIER). That means that we have to add some of that messages back right now.
692 int newArrivals = syncState.exists() - list->m_children.size();
693 Q_ASSERT(newArrivals > 0);
694 QModelIndex parent = list->toIndex(model);
695 int offset = list->m_children.size();
696 model->beginInsertRows(parent, offset, syncState.exists() - 1);
697 for (int i = 0; i < newArrivals; ++i) {
698 TreeItemMessage *msg = new TreeItemMessage(list);
699 msg->m_offset = i + offset;
700 list->m_children << msg;
701 // yes, we really have to add this message with UID 0 :(
702 }
703 model->endInsertRows();
704 }
705
706 list->m_totalMessageCount = list->m_children.size();
707 syncState.setExists(list->m_totalMessageCount);
708 list->recalcVariousMessageCounts(const_cast<Model *>(model));
709
710 if (list->accessFetchStatus() == DONE) {
711 // Previously, we were synced, so we got to save this update
712 saveSyncStateAndUids(model);
713 }
714 }
715
716 /** @short Process the EXISTS response
717
718 This function assumes that the mailbox is already synced.
719 */
handleExists(Model * const model,const Responses::NumberResponse & resp)720 void TreeItemMailbox::handleExists(Model *const model, const Responses::NumberResponse &resp)
721 {
722 Q_ASSERT(resp.kind == Responses::EXISTS);
723 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(m_children[0]);
724 Q_ASSERT(list);
725 // This is a bit tricky -- unfortunately, we can't assume anything about the UID of new arrivals. On the other hand,
726 // these messages can be referenced by (even unrequested) FETCH responses and deleted by EXPUNGE, so we really want
727 // to add them to the tree.
728 int newArrivals = resp.number - list->m_children.size();
729 if (newArrivals < 0) {
730 throw UnexpectedResponseReceived("EXISTS response attempted to decrease number of messages", resp);
731 }
732 syncState.setExists(resp.number);
733 if (newArrivals == 0) {
734 // remains unchanged...
735 return;
736 }
737
738 QModelIndex parent = list->toIndex(model);
739 int offset = list->m_children.size();
740 model->beginInsertRows(parent, offset, resp.number - 1);
741 for (int i = 0; i < newArrivals; ++i) {
742 TreeItemMessage *msg = new TreeItemMessage(list);
743 msg->m_offset = i + offset;
744 list->m_children << msg;
745 // yes, we really have to add this message with UID 0 :(
746 }
747 model->endInsertRows();
748 list->m_totalMessageCount = resp.number;
749 list->setFetchStatus(LOADING);
750 model->emitMessageCountChanged(this);
751 }
752
partIdToPtr(Model * const model,TreeItemMessage * message,const QByteArray & msgId)753 TreeItemPart *TreeItemMailbox::partIdToPtr(Model *const model, TreeItemMessage *message, const QByteArray &msgId)
754 {
755 QByteArray partIdentification;
756 if (msgId.startsWith("BODY[")) {
757 partIdentification = msgId.mid(5, msgId.size() - 6);
758 } else if (msgId.startsWith("BODY.PEEK[")) {
759 partIdentification = msgId.mid(10, msgId.size() - 11);
760 } else if (msgId.startsWith("BINARY.PEEK[")) {
761 partIdentification = msgId.mid(12, msgId.size() - 13);
762 } else if (msgId.startsWith("BINARY[")) {
763 partIdentification = msgId.mid(7, msgId.size() - 8);
764 } else {
765 throw UnknownMessageIndex(QByteArray("Fetch identifier doesn't start with reasonable prefix: " + msgId).constData());
766 }
767
768 TreeItem *item = message;
769 Q_ASSERT(item);
770 QList<QByteArray> separated = partIdentification.split('.');
771 for (QList<QByteArray>::const_iterator it = separated.constBegin(); it != separated.constEnd(); ++it) {
772 bool ok;
773 uint number = it->toUInt(&ok);
774 if (!ok) {
775 // It isn't a number, so let's check for that special modifiers
776 if (it + 1 != separated.constEnd()) {
777 // If it isn't at the very end, it's an error
778 throw UnknownMessageIndex(QByteArray("Part offset contains non-numeric identifiers in the middle: " + msgId).constData());
779 }
780 // Recognize the valid modifiers
781 if (*it == "HEADER")
782 item = item->specialColumnPtr(0, OFFSET_HEADER);
783 else if (*it == "TEXT")
784 item = item->specialColumnPtr(0, OFFSET_TEXT);
785 else if (*it == "MIME")
786 item = item->specialColumnPtr(0, OFFSET_MIME);
787 else
788 throw UnknownMessageIndex(QByteArray("Can't translate received offset of the message part to a number: " + msgId).constData());
789 break;
790 }
791
792 // Normal path: descending down and finding the correct part
793 TreeItemPart *part = dynamic_cast<TreeItemPart *>(item->child(0, model));
794 if (part && part->isTopLevelMultiPart())
795 item = part;
796 item = item->child(number - 1, model);
797 if (! item) {
798 throw UnknownMessageIndex(QStringLiteral(
799 "Offset of the message part not found: message %1 (UID %2), current number %3, full identification %4")
800 .arg(QString::number(message->row()), QString::number(message->uid()),
801 QString::number(number), QString::fromUtf8(msgId)).toUtf8().constData());
802 }
803 }
804 TreeItemPart *part = dynamic_cast<TreeItemPart *>(item);
805 return part;
806 }
807
isSelectable() const808 bool TreeItemMailbox::isSelectable() const
809 {
810 return !m_metadata.flags.contains(QStringLiteral("\\NOSELECT")) && !m_metadata.flags.contains(QStringLiteral("\\NONEXISTENT"));
811 }
812
813
814
TreeItemMsgList(TreeItem * parent)815 TreeItemMsgList::TreeItemMsgList(TreeItem *parent):
816 TreeItem(parent), m_numberFetchingStatus(NONE), m_totalMessageCount(-1),
817 m_unreadMessageCount(-1), m_recentMessageCount(-1)
818 {
819 if (!parent->parent())
820 setFetchStatus(DONE);
821 }
822
fetch(Model * const model)823 void TreeItemMsgList::fetch(Model *const model)
824 {
825 if (fetched() || isUnavailable())
826 return;
827
828 if (!loading()) {
829 setFetchStatus(LOADING);
830 // We can't ask right now, has to wait till the end of the event loop
831 CALL_LATER(model, askForMessagesInMailbox, Q_ARG(QModelIndex, toIndex(model)));
832 }
833 }
834
fetchNumbers(Model * const model)835 void TreeItemMsgList::fetchNumbers(Model *const model)
836 {
837 if (m_numberFetchingStatus == NONE) {
838 m_numberFetchingStatus = LOADING;
839 model->askForNumberOfMessages(this);
840 }
841 }
842
rowCount(Model * const model)843 unsigned int TreeItemMsgList::rowCount(Model *const model)
844 {
845 return childrenCount(model);
846 }
847
data(Model * const model,int role)848 QVariant TreeItemMsgList::data(Model *const model, int role)
849 {
850 if (role == RoleIsFetched)
851 return fetched();
852 if (role == RoleIsUnavailable)
853 return isUnavailable();
854
855 if (role != Qt::DisplayRole)
856 return QVariant();
857
858 if (!parent())
859 return QVariant();
860
861 if (loading())
862 return QLatin1String("[loading messages...]");
863
864 if (isUnavailable())
865 return QLatin1String("[offline]");
866
867 if (fetched())
868 return hasChildren(model) ? QStringLiteral("[%1 messages]").arg(childrenCount(model)) : QStringLiteral("[no messages]");
869
870 return QLatin1String("[messages?]");
871 }
872
hasChildren(Model * const model)873 bool TreeItemMsgList::hasChildren(Model *const model)
874 {
875 Q_UNUSED(model);
876 return true; // we can easily wait here
877 }
878
totalMessageCount(Model * const model)879 int TreeItemMsgList::totalMessageCount(Model *const model)
880 {
881 // Yes, the numbers can be accommodated by a full mailbox sync, but that's not really what we shall do from this context.
882 // Because we want to allow the old-school polling for message numbers, we have to look just at the numberFetched() state.
883 if (!numbersFetched())
884 fetchNumbers(model);
885 return m_totalMessageCount;
886 }
887
unreadMessageCount(Model * const model)888 int TreeItemMsgList::unreadMessageCount(Model *const model)
889 {
890 // See totalMessageCount()
891 if (!numbersFetched())
892 fetchNumbers(model);
893 return m_unreadMessageCount;
894 }
895
recentMessageCount(Model * const model)896 int TreeItemMsgList::recentMessageCount(Model *const model)
897 {
898 // See totalMessageCount()
899 if (!numbersFetched())
900 fetchNumbers(model);
901 return m_recentMessageCount;
902 }
903
recalcVariousMessageCounts(Model * model)904 void TreeItemMsgList::recalcVariousMessageCounts(Model *model)
905 {
906 m_unreadMessageCount = 0;
907 m_recentMessageCount = 0;
908 for (int i = 0; i < m_children.size(); ++i) {
909 TreeItemMessage *message = static_cast<TreeItemMessage *>(m_children[i]);
910 bool isRead, isRecent;
911 message->checkFlagsReadRecent(isRead, isRecent);
912 if (!message->m_flagsHandled)
913 message->m_wasUnread = ! isRead;
914 message->m_flagsHandled = true;
915 if (!isRead)
916 ++m_unreadMessageCount;
917 if (isRecent)
918 ++m_recentMessageCount;
919 }
920 m_totalMessageCount = m_children.size();
921 m_numberFetchingStatus = DONE;
922 model->emitMessageCountChanged(static_cast<TreeItemMailbox *>(parent()));
923 }
924
recalcVariousMessageCountsOnExpunge(Model * model,TreeItemMessage * expungedMessage)925 void TreeItemMsgList::recalcVariousMessageCountsOnExpunge(Model *model, TreeItemMessage *expungedMessage)
926 {
927 if (m_numberFetchingStatus != DONE) {
928 // In case the counts weren't synced before, we cannot really rely on them now -> go to the slow path
929 recalcVariousMessageCounts(model);
930 return;
931 }
932
933 bool isRead, isRecent;
934 expungedMessage->checkFlagsReadRecent(isRead, isRecent);
935 if (expungedMessage->m_flagsHandled) {
936 if (!isRead)
937 --m_unreadMessageCount;
938 if (isRecent)
939 --m_recentMessageCount;
940 }
941 model->emitMessageCountChanged(static_cast<TreeItemMailbox *>(parent()));
942 }
943
resetWasUnreadState()944 void TreeItemMsgList::resetWasUnreadState()
945 {
946 for (int i = 0; i < m_children.size(); ++i) {
947 TreeItemMessage *message = static_cast<TreeItemMessage *>(m_children[i]);
948 message->m_wasUnread = ! message->isMarkedAsRead();
949 }
950 }
951
numbersFetched() const952 bool TreeItemMsgList::numbersFetched() const
953 {
954 return m_numberFetchingStatus == DONE;
955 }
956
957
958
MessageDataPayload()959 MessageDataPayload::MessageDataPayload()
960 : m_size(0)
961 , m_hdrListPostNo(false)
962 , m_partHeader(nullptr)
963 , m_partText(nullptr)
964 , m_gotEnvelope(false)
965 , m_gotInternalDate(false)
966 , m_gotSize(false)
967 , m_gotBodystructure(false)
968 , m_gotHdrReferences(false)
969 , m_gotHdrListPost(false)
970 {
971 }
972
isComplete() const973 bool MessageDataPayload::isComplete() const
974 {
975 return m_gotEnvelope && m_gotInternalDate && m_gotSize && m_gotBodystructure;
976 }
977
gotEnvelope() const978 bool MessageDataPayload::gotEnvelope() const
979 {
980 return m_gotEnvelope;
981 }
982
gotInternalDate() const983 bool MessageDataPayload::gotInternalDate() const
984 {
985 return m_gotInternalDate;
986 }
987
gotSize() const988 bool MessageDataPayload::gotSize() const
989 {
990 return m_gotSize;
991 }
992
gotHdrReferences() const993 bool MessageDataPayload::gotHdrReferences() const
994 {
995 return m_gotHdrReferences;
996 }
997
gotHdrListPost() const998 bool MessageDataPayload::gotHdrListPost() const
999 {
1000 return m_gotHdrListPost;
1001 }
1002
envelope() const1003 const Message::Envelope &MessageDataPayload::envelope() const
1004 {
1005 return m_envelope;
1006 }
1007
setEnvelope(const Message::Envelope & envelope)1008 void MessageDataPayload::setEnvelope(const Message::Envelope &envelope)
1009 {
1010 m_envelope = envelope;
1011 m_gotEnvelope = true;
1012 }
1013
internalDate() const1014 const QDateTime &MessageDataPayload::internalDate() const
1015 {
1016 return m_internalDate;
1017 }
1018
setInternalDate(const QDateTime & internalDate)1019 void MessageDataPayload::setInternalDate(const QDateTime &internalDate)
1020 {
1021 m_internalDate = internalDate;
1022 m_gotInternalDate = true;
1023 }
1024
size() const1025 quint64 MessageDataPayload::size() const
1026 {
1027 return m_size;
1028 }
1029
setSize(quint64 size)1030 void MessageDataPayload::setSize(quint64 size)
1031 {
1032 m_size = size;
1033 m_gotSize = true;
1034 }
1035
hdrReferences() const1036 const QList<QByteArray> &MessageDataPayload::hdrReferences() const
1037 {
1038 return m_hdrReferences;
1039 }
1040
setHdrReferences(const QList<QByteArray> & hdrReferences)1041 void MessageDataPayload::setHdrReferences(const QList<QByteArray> &hdrReferences)
1042 {
1043 m_hdrReferences = hdrReferences;
1044 m_gotHdrReferences = true;
1045 }
1046
hdrListPost() const1047 const QList<QUrl> &MessageDataPayload::hdrListPost() const
1048 {
1049 return m_hdrListPost;
1050 }
1051
hdrListPostNo() const1052 bool MessageDataPayload::hdrListPostNo() const
1053 {
1054 return m_hdrListPostNo;
1055 }
1056
setHdrListPost(const QList<QUrl> & hdrListPost)1057 void MessageDataPayload::setHdrListPost(const QList<QUrl> &hdrListPost)
1058 {
1059 m_hdrListPost = hdrListPost;
1060 m_gotHdrListPost = true;
1061 }
1062
setHdrListPostNo(const bool hdrListPostNo)1063 void MessageDataPayload::setHdrListPostNo(const bool hdrListPostNo)
1064 {
1065 m_hdrListPostNo = hdrListPostNo;
1066 m_gotHdrListPost = true;
1067 }
1068
rememberedBodyStructure() const1069 const QByteArray &MessageDataPayload::rememberedBodyStructure() const
1070 {
1071 return m_rememberedBodyStructure;
1072 }
1073
setRememberedBodyStructure(const QByteArray & blob)1074 void MessageDataPayload::setRememberedBodyStructure(const QByteArray &blob)
1075 {
1076 m_rememberedBodyStructure = blob;
1077 m_gotBodystructure = true;
1078 }
1079
gotRemeberedBodyStructure() const1080 bool MessageDataPayload::gotRemeberedBodyStructure() const
1081 {
1082 return m_gotBodystructure;
1083 }
1084
partHeader() const1085 TreeItemPart *MessageDataPayload::partHeader() const
1086 {
1087 return m_partHeader.get();
1088 }
1089
setPartHeader(std::unique_ptr<TreeItemPart> part)1090 void MessageDataPayload::setPartHeader(std::unique_ptr<TreeItemPart> part)
1091 {
1092 m_partHeader = std::move(part);
1093 }
1094
partText() const1095 TreeItemPart *MessageDataPayload::partText() const
1096 {
1097 return m_partText.get();
1098 }
1099
setPartText(std::unique_ptr<TreeItemPart> part)1100 void MessageDataPayload::setPartText(std::unique_ptr<TreeItemPart> part)
1101 {
1102 m_partText = std::move(part);
1103 }
1104
1105
TreeItemMessage(TreeItem * parent)1106 TreeItemMessage::TreeItemMessage(TreeItem *parent):
1107 TreeItem(parent), m_offset(-1), m_uid(0), m_data(0), m_flagsHandled(false), m_wasUnread(false)
1108 {
1109 }
1110
~TreeItemMessage()1111 TreeItemMessage::~TreeItemMessage()
1112 {
1113 delete m_data;
1114 }
1115
fetch(Model * const model)1116 void TreeItemMessage::fetch(Model *const model)
1117 {
1118 if (fetched() || loading() || isUnavailable())
1119 return;
1120
1121 if (m_uid) {
1122 // Message UID is already known, which means that we can request data for this message
1123 model->askForMsgMetadata(this, Model::PRELOAD_PER_POLICY);
1124 } else {
1125 // The UID is not known yet, so we can't initiate a UID FETCH at this point. However, we mark
1126 // this message as "loading", which has the side effect that it will get re-fetched as soon as
1127 // the UID arrives -- see TreeItemMailbox::handleFetchResponse(), the section which deals with
1128 // setting previously unknown UIDs, and the similar code in ObtainSynchronizedMailboxTask.
1129 //
1130 // Even though this breaks the message preload done in Model::_askForMsgmetadata, chances are that
1131 // the UIDs will arrive rather soon for all of the pending messages, and the request for metadata
1132 // will therefore get queued roughly at the same time. This gives the KeepMailboxOpenTask a chance
1133 // to group similar requests together. To reiterate:
1134 // - Messages are attempted to get *preloaded* (ie. requesting metadata even for messages that are not
1135 // yet shown) as usual; this could fail because the UIDs might not be known yet.
1136 // - The actual FETCH could be batched by the KeepMailboxOpenTask anyway
1137 // - Hence, this should be still pretty fast and efficient
1138 setFetchStatus(LOADING);
1139 }
1140 }
1141
rowCount(Model * const model)1142 unsigned int TreeItemMessage::rowCount(Model *const model)
1143 {
1144 if (!data()->gotRemeberedBodyStructure()) {
1145 fetch(model);
1146 }
1147 return m_children.size();
1148 }
1149
setChildren(const TreeItemChildrenList & items)1150 TreeItemChildrenList TreeItemMessage::setChildren(const TreeItemChildrenList &items)
1151 {
1152 auto origStatus = accessFetchStatus();
1153 auto res = TreeItem::setChildren(items);
1154 setFetchStatus(origStatus);
1155 return res;
1156 }
1157
columnCount()1158 unsigned int TreeItemMessage::columnCount()
1159 {
1160 static_assert(OFFSET_HEADER < OFFSET_TEXT && OFFSET_MIME == OFFSET_TEXT + 1,
1161 "We need column 0 for regular children and columns 1 and 2 for OFFSET_HEADER and OFFSET_TEXT.");
1162 // Oh, and std::max is not constexpr in C++11.
1163 return OFFSET_MIME;
1164 }
1165
specialColumnPtr(int row,int column) const1166 TreeItem *TreeItemMessage::specialColumnPtr(int row, int column) const
1167 {
1168 // This is a nasty one -- we have an irregular shape...
1169
1170 // No extra columns on other rows
1171 if (row != 0)
1172 return 0;
1173
1174 switch (column) {
1175 case OFFSET_TEXT:
1176 if (!data()->partText()) {
1177 data()->setPartText(std::unique_ptr<TreeItemPart>(new TreeItemModifiedPart(const_cast<TreeItemMessage *>(this), OFFSET_TEXT)));
1178 }
1179 return data()->partText();
1180 case OFFSET_HEADER:
1181 if (!data()->partHeader()) {
1182 data()->setPartHeader(std::unique_ptr<TreeItemPart>(new TreeItemModifiedPart(const_cast<TreeItemMessage *>(this), OFFSET_HEADER)));
1183 }
1184 return data()->partHeader();
1185 default:
1186 return 0;
1187 }
1188 }
1189
row() const1190 int TreeItemMessage::row() const
1191 {
1192 Q_ASSERT(m_offset != -1);
1193 return m_offset;
1194 }
1195
data(Model * const model,int role)1196 QVariant TreeItemMessage::data(Model *const model, int role)
1197 {
1198 if (!parent())
1199 return QVariant();
1200
1201 // Special item roles which should not trigger fetching of message metadata
1202 switch (role) {
1203 case RoleMessageUid:
1204 return m_uid ? QVariant(m_uid) : QVariant();
1205 case RoleIsFetched:
1206 return fetched();
1207 case RoleIsUnavailable:
1208 return isUnavailable();
1209 case RoleMessageFlags:
1210 // The flags are already sorted by Model::normalizeFlags()
1211 return m_flags;
1212 case RoleMessageIsMarkedDeleted:
1213 return isMarkedAsDeleted();
1214 case RoleMessageIsMarkedRead:
1215 return isMarkedAsRead();
1216 case RoleMessageIsMarkedForwarded:
1217 return isMarkedAsForwarded();
1218 case RoleMessageIsMarkedReplied:
1219 return isMarkedAsReplied();
1220 case RoleMessageIsMarkedRecent:
1221 return isMarkedAsRecent();
1222 case RoleMessageIsMarkedFlagged:
1223 return isMarkedAsFlagged();
1224 case RoleMessageIsMarkedJunk:
1225 return isMarkedAsJunk();
1226 case RoleMessageIsMarkedNotJunk:
1227 return isMarkedAsNotJunk();
1228 case RoleMessageFuzzyDate:
1229 {
1230 // When the QML ListView is configured with its section.* properties, it will call the corresponding data() section *very*
1231 // often. The data are however only "needed" when the real items are visible, and when they are visible, the data() will
1232 // get called anyway and the correct stuff will ultimately arrive. This is why we don't call fetch() from here.
1233 //
1234 // FIXME: double-check the above once again! Maybe it was just a side effect of too fast updates of the currentIndex?
1235 if (!fetched()) {
1236 return QVariant();
1237 }
1238
1239 QDateTime timestamp = envelope(model).date;
1240 if (!timestamp.isValid())
1241 return QString();
1242
1243 if (timestamp.date() == QDate::currentDate())
1244 return Model::tr("Today");
1245
1246 int beforeDays = timestamp.date().daysTo(QDate::currentDate());
1247 if (beforeDays >= 0 && beforeDays < 7)
1248 return Model::tr("Last Week");
1249
1250 return QDate(timestamp.date().year(), timestamp.date().month(), 1).toString(
1251 Model::tr("MMMM yyyy", "The format specifiers (yyyy, etc) must not be translated, "
1252 "but their order can be changed to follow the local conventions. "
1253 "For valid specifiers see http://doc.qt.io/qt-5/qdate.html#toString"));
1254 }
1255 case RoleMessageWasUnread:
1256 return m_wasUnread;
1257 case RoleThreadRootWithUnreadMessages:
1258 // This one doesn't really make much sense here, but we do want to catch it to prevent a fetch request from this context
1259 qDebug() << "Warning: asked for RoleThreadRootWithUnreadMessages on TreeItemMessage. This does not make sense.";
1260 return QVariant();
1261 case RoleMailboxName:
1262 case RoleMailboxUidValidity:
1263 return parent()->parent()->data(model, role);
1264 case RolePartMimeType:
1265 return QByteArrayLiteral("message/rfc822");
1266 }
1267
1268 // Any other roles will result in fetching the data; however, we won't exit if the data isn't available yet
1269 fetch(model);
1270
1271 switch (role) {
1272 case Qt::DisplayRole:
1273 if (loading()) {
1274 return QStringLiteral("[loading UID %1...]").arg(QString::number(uid()));
1275 } else if (isUnavailable()) {
1276 return QStringLiteral("[offline UID %1]").arg(QString::number(uid()));
1277 } else {
1278 return QStringLiteral("UID %1: %2").arg(QString::number(uid()), data()->envelope().subject);
1279 }
1280 case Qt::ToolTipRole:
1281 if (fetched()) {
1282 QString buf;
1283 QTextStream stream(&buf);
1284 stream << data()->envelope();
1285 return UiUtils::Formatting::htmlEscaped(buf);
1286 } else {
1287 return QVariant();
1288 }
1289 case RoleMessageSize:
1290 return data()->gotSize() ? QVariant::fromValue(data()->size()) : QVariant();
1291 case RoleMessageInternalDate:
1292 return data()->gotInternalDate() ? data()->internalDate() : QVariant();
1293 case RoleMessageHeaderReferences:
1294 return data()->gotHdrReferences() ? QVariant::fromValue(data()->hdrReferences()) : QVariant();
1295 case RoleMessageHeaderListPost:
1296 if (data()->gotHdrListPost()) {
1297 QVariantList res;
1298 Q_FOREACH(const QUrl &url, data()->hdrListPost())
1299 res << url;
1300 return res;
1301 } else {
1302 return QVariant();
1303 }
1304 case RoleMessageHeaderListPostNo:
1305 return data()->gotHdrListPost() ? QVariant(data()->hdrListPostNo()) : QVariant();
1306 }
1307
1308 if (data()->gotEnvelope()) {
1309 // If the envelope is already available, we might be able to deliver some bits even prior to full fetch being done.
1310 //
1311 // Only those values which need ENVELOPE go here!
1312 switch (role) {
1313 case RoleMessageSubject:
1314 return data()->gotEnvelope() ? QVariant(data()->envelope().subject) : QVariant();
1315 case RoleMessageDate:
1316 return data()->gotEnvelope() ? envelope(model).date : QVariant();
1317 case RoleMessageFrom:
1318 return addresListToQVariant(envelope(model).from);
1319 case RoleMessageTo:
1320 return addresListToQVariant(envelope(model).to);
1321 case RoleMessageCc:
1322 return addresListToQVariant(envelope(model).cc);
1323 case RoleMessageBcc:
1324 return addresListToQVariant(envelope(model).bcc);
1325 case RoleMessageSender:
1326 return addresListToQVariant(envelope(model).sender);
1327 case RoleMessageReplyTo:
1328 return addresListToQVariant(envelope(model).replyTo);
1329 case RoleMessageInReplyTo:
1330 return QVariant::fromValue(envelope(model).inReplyTo);
1331 case RoleMessageMessageId:
1332 return envelope(model).messageId;
1333 case RoleMessageEnvelope:
1334 return QVariant::fromValue<Message::Envelope>(envelope(model));
1335 case RoleMessageHasAttachments:
1336 return hasAttachments(model);
1337 }
1338 }
1339
1340 return QVariant();
1341 }
1342
addresListToQVariant(const QList<Imap::Message::MailAddress> & addressList)1343 QVariantList TreeItemMessage::addresListToQVariant(const QList<Imap::Message::MailAddress> &addressList)
1344 {
1345 QVariantList res;
1346 foreach(const Imap::Message::MailAddress& address, addressList) {
1347 res.append(QVariant(QStringList() << address.name << address.adl << address.mailbox << address.host));
1348 }
1349 return res;
1350 }
1351
1352
1353 namespace {
1354
1355 /** @short Find a string based on d-ptr equality
1356
1357 This works because our flags always use implicit sharing. If they didn't use that, this method wouldn't work.
1358 */
containsStringByDPtr(const QStringList & haystack,const QString & needle)1359 bool containsStringByDPtr(const QStringList &haystack, const QString &needle)
1360 {
1361 const auto sentinel = const_cast<QString&>(needle).data_ptr();
1362 Q_FOREACH(const auto &item, haystack) {
1363 if (const_cast<QString&>(item).data_ptr() == sentinel)
1364 return true;
1365 }
1366 return false;
1367 }
1368
1369 }
1370
isMarkedAsDeleted() const1371 bool TreeItemMessage::isMarkedAsDeleted() const
1372 {
1373 return containsStringByDPtr(m_flags, FlagNames::deleted);
1374 }
1375
isMarkedAsRead() const1376 bool TreeItemMessage::isMarkedAsRead() const
1377 {
1378 return containsStringByDPtr(m_flags, FlagNames::seen);
1379 }
1380
isMarkedAsReplied() const1381 bool TreeItemMessage::isMarkedAsReplied() const
1382 {
1383 return containsStringByDPtr(m_flags, FlagNames::answered);
1384 }
1385
isMarkedAsForwarded() const1386 bool TreeItemMessage::isMarkedAsForwarded() const
1387 {
1388 return containsStringByDPtr(m_flags, FlagNames::forwarded);
1389 }
1390
isMarkedAsRecent() const1391 bool TreeItemMessage::isMarkedAsRecent() const
1392 {
1393 return containsStringByDPtr(m_flags, FlagNames::recent);
1394 }
1395
isMarkedAsFlagged() const1396 bool TreeItemMessage::isMarkedAsFlagged() const
1397 {
1398 return containsStringByDPtr(m_flags, FlagNames::flagged);
1399 }
1400
isMarkedAsJunk() const1401 bool TreeItemMessage::isMarkedAsJunk() const
1402 {
1403 return containsStringByDPtr(m_flags, FlagNames::junk);
1404 }
1405
isMarkedAsNotJunk() const1406 bool TreeItemMessage::isMarkedAsNotJunk() const
1407 {
1408 return containsStringByDPtr(m_flags, FlagNames::notjunk);
1409 }
1410
checkFlagsReadRecent(bool & isRead,bool & isRecent) const1411 void TreeItemMessage::checkFlagsReadRecent(bool &isRead, bool &isRecent) const
1412 {
1413 const auto dRead = const_cast<QString&>(FlagNames::seen).data_ptr();
1414 const auto dRecent = const_cast<QString&>(FlagNames::recent).data_ptr();
1415 auto end = m_flags.end();
1416 auto it = m_flags.begin();
1417 isRead = isRecent = false;
1418 while (it != end && !(isRead && isRecent)) {
1419 isRead |= const_cast<QString&>(*it).data_ptr() == dRead;
1420 isRecent |= const_cast<QString&>(*it).data_ptr() == dRecent;
1421 ++it;
1422 }
1423 }
1424
uid() const1425 uint TreeItemMessage::uid() const
1426 {
1427 return m_uid;
1428 }
1429
envelope(Model * const model)1430 Message::Envelope TreeItemMessage::envelope(Model *const model)
1431 {
1432 fetch(model);
1433 return data()->envelope();
1434 }
1435
internalDate(Model * const model)1436 QDateTime TreeItemMessage::internalDate(Model *const model)
1437 {
1438 fetch(model);
1439 return data()->internalDate();
1440 }
1441
size(Model * const model)1442 quint64 TreeItemMessage::size(Model *const model)
1443 {
1444 fetch(model);
1445 return data()->size();
1446 }
1447
setFlags(TreeItemMsgList * list,const QStringList & flags)1448 void TreeItemMessage::setFlags(TreeItemMsgList *list, const QStringList &flags)
1449 {
1450 // wasSeen is used to determine if the message was marked as read before this operation
1451 bool wasSeen = isMarkedAsRead();
1452 m_flags = flags;
1453 if (list->m_numberFetchingStatus == DONE) {
1454 bool isSeen = isMarkedAsRead();
1455 if (m_flagsHandled) {
1456 if (wasSeen && !isSeen) {
1457 ++list->m_unreadMessageCount;
1458 // leave the message as "was unread" so it persists in the view when read messages are hidden
1459 m_wasUnread = true;
1460 } else if (!wasSeen && isSeen) {
1461 --list->m_unreadMessageCount;
1462 }
1463 } else {
1464 // it's a new message
1465 m_flagsHandled = true;
1466 if (!isSeen) {
1467 ++list->m_unreadMessageCount;
1468 // mark the message as "was unread" so it shows up in the view when read messages are hidden
1469 m_wasUnread = true;
1470 }
1471 }
1472 }
1473 }
1474
1475 /** @short Process the data found in the headers passed along and file in auxiliary metadata
1476
1477 This function accepts a snippet containing some RFC5322 headers of a message, no matter what headers are actually
1478 present in the passed text. The headers are parsed and those recognized are used as a source of data to file
1479 the "auxiliary metadata" of this TreeItemMessage (basically anything not available in ENVELOPE, UID, FLAGS,
1480 INTERNALDATE etc).
1481 */
processAdditionalHeaders(Model * model,const QByteArray & rawHeaders)1482 void TreeItemMessage::processAdditionalHeaders(Model *model, const QByteArray &rawHeaders)
1483 {
1484 Imap::LowLevelParser::Rfc5322HeaderParser parser;
1485 bool ok = parser.parse(rawHeaders);
1486 if (!ok) {
1487 model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Rfc5322HeaderParser"),
1488 QStringLiteral("Unspecified error during RFC5322 header parsing"));
1489 }
1490
1491 data()->setHdrReferences(parser.references);
1492 QList<QUrl> hdrListPost;
1493 if (!parser.listPost.isEmpty()) {
1494 Q_FOREACH(const QByteArray &item, parser.listPost)
1495 hdrListPost << QUrl(QString::fromUtf8(item));
1496 data()->setHdrListPost(hdrListPost);
1497 }
1498 // That's right, this can only be set, not ever reset from this context.
1499 // This is because we absolutely want to support incremental header arrival.
1500 if (parser.listPostNo)
1501 data()->setHdrListPostNo(true);
1502 }
1503
hasAttachments(Model * const model)1504 bool TreeItemMessage::hasAttachments(Model *const model)
1505 {
1506 fetch(model);
1507
1508 if (!fetched())
1509 return false;
1510
1511 if (m_children.isEmpty()) {
1512 // strange, but why not, I guess
1513 return false;
1514 } else if (m_children.size() > 1) {
1515 // Again, very strange -- the message should have had a single multipart as a root node, but let's cope with this as well
1516 return true;
1517 } else {
1518 return hasNestedAttachments(model, static_cast<TreeItemPart*>(m_children[0]));
1519 }
1520 }
1521
1522 /** @short Walk the MIME tree starting at @arg part and check if there are any attachments below (or at there) */
hasNestedAttachments(Model * const model,TreeItemPart * part)1523 bool TreeItemMessage::hasNestedAttachments(Model *const model, TreeItemPart *part)
1524 {
1525 while (true) {
1526
1527 const QByteArray mimeType = part->mimeType();
1528
1529 if (mimeType == "multipart/signed" && part->childrenCount(model) == 2) {
1530 // "strip" the signature, look at what's inside
1531 part = static_cast<TreeItemPart*>(part->child(0, model));
1532 } else if (mimeType.startsWith("multipart/")) {
1533 // Return false iff no children is/has an attachment.
1534 // Originally this code was like this only for multipart/alternative, but in the end Stephan Platz lobbied for
1535 // treating ML signatures the same (which means multipart/mixed) has to be included, and there's also a RFC
1536 // which says that unrecognized multiparts should be treated exactly like a multipart/mixed.
1537 // As a bonus, this makes it possible to get rid of an extra branch for single-childed multiparts.
1538 for (uint i = 0; i < part->childrenCount(model); ++i) {
1539 if (hasNestedAttachments(model, static_cast<TreeItemPart*>(part->child(i, model)))) {
1540 return true;
1541 }
1542 }
1543 return false;
1544 } else if (mimeType == "text/html" || mimeType == "text/plain") {
1545 // See AttachmentView for details behind this.
1546 const QByteArray contentDisposition = part->bodyDisposition().toLower();
1547 const bool isInline = contentDisposition.isEmpty() || contentDisposition == "inline";
1548 const bool looksLikeAttachment = !part->fileName().isEmpty();
1549
1550 return looksLikeAttachment || !isInline;
1551 } else {
1552 // anything else must surely be an attachment
1553 return true;
1554 }
1555 }
1556 }
1557
1558
TreeItemPart(TreeItem * parent,const QByteArray & mimeType)1559 TreeItemPart::TreeItemPart(TreeItem *parent, const QByteArray &mimeType):
1560 TreeItem(parent), m_mimeType(mimeType.toLower()), m_octets(0), m_partMime(0), m_partRaw(0)
1561 {
1562 if (isTopLevelMultiPart()) {
1563 // Note that top-level multipart messages are special, their immediate contents
1564 // can't be fetched. That's why we have to update the status here.
1565 setFetchStatus(DONE);
1566 }
1567 }
1568
TreeItemPart(TreeItem * parent)1569 TreeItemPart::TreeItemPart(TreeItem *parent):
1570 TreeItem(parent), m_mimeType("text/plain"), m_octets(0), m_partMime(0), m_partRaw(0)
1571 {
1572 }
1573
~TreeItemPart()1574 TreeItemPart::~TreeItemPart()
1575 {
1576 delete m_partMime;
1577 delete m_partRaw;
1578 }
1579
childrenCount(Model * const model)1580 unsigned int TreeItemPart::childrenCount(Model *const model)
1581 {
1582 Q_UNUSED(model);
1583 return m_children.size();
1584 }
1585
child(const int offset,Model * const model)1586 TreeItem *TreeItemPart::child(const int offset, Model *const model)
1587 {
1588 Q_UNUSED(model);
1589 if (offset >= 0 && offset < m_children.size())
1590 return m_children[ offset ];
1591 else
1592 return 0;
1593 }
1594
setChildren(const TreeItemChildrenList & items)1595 TreeItemChildrenList TreeItemPart::setChildren(const TreeItemChildrenList &items)
1596 {
1597 FetchingState fetchStatus = accessFetchStatus();
1598 auto res = TreeItem::setChildren(items);
1599 setFetchStatus(fetchStatus);
1600 return res;
1601 }
1602
fetch(Model * const model)1603 void TreeItemPart::fetch(Model *const model)
1604 {
1605 if (fetched() || loading() || isUnavailable())
1606 return;
1607
1608 setFetchStatus(LOADING);
1609 model->askForMsgPart(this);
1610 }
1611
fetchFromCache(Model * const model)1612 void TreeItemPart::fetchFromCache(Model *const model)
1613 {
1614 if (fetched() || loading() || isUnavailable())
1615 return;
1616
1617 model->askForMsgPart(this, true);
1618 }
1619
rowCount(Model * const model)1620 unsigned int TreeItemPart::rowCount(Model *const model)
1621 {
1622 // no call to fetch() required
1623 Q_UNUSED(model);
1624 return m_children.size();
1625 }
1626
data(Model * const model,int role)1627 QVariant TreeItemPart::data(Model *const model, int role)
1628 {
1629 if (!parent())
1630 return QVariant();
1631
1632 // these data are available immediately
1633 switch (role) {
1634 case RoleIsFetched:
1635 return fetched();
1636 case RoleIsUnavailable:
1637 return isUnavailable();
1638 case RolePartMimeType:
1639 return m_mimeType;
1640 case RolePartCharset:
1641 return m_charset;
1642 case RolePartContentFormat:
1643 return m_contentFormat;
1644 case RolePartContentDelSp:
1645 return m_delSp;
1646 case RolePartEncoding:
1647 return m_encoding;
1648 case RolePartBodyFldId:
1649 return m_bodyFldId;
1650 case RolePartBodyDisposition:
1651 return m_bodyDisposition;
1652 case RolePartFileName:
1653 return m_fileName;
1654 case RolePartOctets:
1655 return QVariant::fromValue(m_octets);
1656 case RolePartId:
1657 return partId();
1658 case RolePartPathToPart:
1659 return pathToPart();
1660 case RolePartMultipartRelatedMainCid:
1661 if (!multipartRelatedStartPart().isEmpty())
1662 return multipartRelatedStartPart();
1663 else
1664 return QVariant();
1665 case RolePartMessageIndex:
1666 return QVariant::fromValue(message()->toIndex(model));
1667 case RoleMailboxName:
1668 case RoleMailboxUidValidity:
1669 return message()->parent()->parent()->data(model, role);
1670 case RoleMessageUid:
1671 return message()->uid();
1672 case RolePartIsTopLevelMultipart:
1673 return isTopLevelMultiPart();
1674 case RolePartForceFetchFromCache:
1675 fetchFromCache(model);
1676 return QVariant();
1677 case RolePartBufferPtr:
1678 return QVariant::fromValue(dataPtr());
1679 case RolePartBodyFldParam:
1680 return QVariant::fromValue(m_bodyFldParam);
1681 }
1682
1683
1684 fetch(model);
1685
1686 if (loading()) {
1687 if (role == Qt::DisplayRole) {
1688 return isTopLevelMultiPart() ?
1689 Model::tr("[loading %1...]").arg(QString::fromUtf8(m_mimeType)) :
1690 Model::tr("[loading %1: %2...]").arg(QString::fromUtf8(partId()), QString::fromUtf8(m_mimeType));
1691 } else {
1692 return QVariant();
1693 }
1694 }
1695
1696 switch (role) {
1697 case Qt::DisplayRole:
1698 return isTopLevelMultiPart() ?
1699 QString::fromUtf8(m_mimeType) :
1700 QStringLiteral("%1: %2").arg(QString::fromUtf8(partId()), QString::fromUtf8(m_mimeType));
1701 case Qt::ToolTipRole:
1702 return QStringLiteral("%1 bytes of data").arg(m_data.size());
1703 case RolePartData:
1704 return m_data;
1705 case RolePartUnicodeText:
1706 if (m_mimeType.startsWith("text/")) {
1707 return decodeByteArray(m_data, m_charset);
1708 } else {
1709 return QVariant();
1710 }
1711 default:
1712 return QVariant();
1713 }
1714 }
1715
hasChildren(Model * const model)1716 bool TreeItemPart::hasChildren(Model *const model)
1717 {
1718 // no need to fetch() here
1719 Q_UNUSED(model);
1720 return ! m_children.isEmpty();
1721 }
1722
1723 /** @short Returns true if we're a multipart, top-level item in the body of a message */
isTopLevelMultiPart() const1724 bool TreeItemPart::isTopLevelMultiPart() const
1725 {
1726 TreeItemMessage *msg = dynamic_cast<TreeItemMessage *>(parent());
1727 TreeItemPart *part = dynamic_cast<TreeItemPart *>(parent());
1728 return m_mimeType.startsWith("multipart/") && (msg || (part && part->m_mimeType.startsWith("message/")));
1729 }
1730
partId() const1731 QByteArray TreeItemPart::partId() const
1732 {
1733 if (isTopLevelMultiPart()) {
1734 return QByteArray();
1735 } else if (dynamic_cast<TreeItemMessage *>(parent())) {
1736 return QByteArray::number(row() + 1);
1737 } else {
1738 QByteArray parentId;
1739 TreeItemPart *parentPart = dynamic_cast<TreeItemPart *>(parent());
1740 Q_ASSERT(parentPart);
1741 if (parentPart->isTopLevelMultiPart()) {
1742 if (TreeItemPart *parentOfParent = dynamic_cast<TreeItemPart *>(parentPart->parent())) {
1743 Q_ASSERT(!parentOfParent->isTopLevelMultiPart());
1744 // grand parent: message/rfc822 with a part-id, parent: top-level multipart
1745 parentId = parentOfParent->partId();
1746 } else {
1747 // grand parent: TreeItemMessage, parent: some multipart, me: some part
1748 return QByteArray::number(row() + 1);
1749 }
1750 } else {
1751 parentId = parentPart->partId();
1752 }
1753 Q_ASSERT(!parentId.isEmpty());
1754 return parentId + '.' + QByteArray::number(row() + 1);
1755 }
1756 }
1757
partIdForFetch(const PartFetchingMode mode) const1758 QByteArray TreeItemPart::partIdForFetch(const PartFetchingMode mode) const
1759 {
1760 return QByteArray(mode == FETCH_PART_BINARY ? "BINARY" : "BODY") + ".PEEK[" + partId() + "]";
1761 }
1762
pathToPart() const1763 QByteArray TreeItemPart::pathToPart() const
1764 {
1765 TreeItemPart *part = dynamic_cast<TreeItemPart *>(parent());
1766 TreeItemMessage *msg = dynamic_cast<TreeItemMessage *>(parent());
1767 if (part)
1768 return part->pathToPart() + '/' + QByteArray::number(row());
1769 else if (msg)
1770 return '/' + QByteArray::number(row());
1771 else {
1772 Q_ASSERT(false);
1773 return QByteArray();
1774 }
1775 }
1776
message() const1777 TreeItemMessage *TreeItemPart::message() const
1778 {
1779 const TreeItemPart *part = this;
1780 while (part) {
1781 TreeItemMessage *message = dynamic_cast<TreeItemMessage *>(part->parent());
1782 if (message)
1783 return message;
1784 part = dynamic_cast<TreeItemPart *>(part->parent());
1785 }
1786 return 0;
1787 }
1788
dataPtr()1789 QByteArray *TreeItemPart::dataPtr()
1790 {
1791 return &m_data;
1792 }
1793
columnCount()1794 unsigned int TreeItemPart::columnCount()
1795 {
1796 if (isTopLevelMultiPart()) {
1797 // Because a top-level multipart doesn't have its own part number, one cannot really fetch from it
1798 return 1;
1799 }
1800
1801 // This one includes the OFFSET_MIME and OFFSET_RAW_CONTENTS, unlike the TreeItemMessage
1802 static_assert(OFFSET_MIME < OFFSET_RAW_CONTENTS, "The OFFSET_RAW_CONTENTS shall be the biggest one for tree invariants to work");
1803 return OFFSET_RAW_CONTENTS + 1;
1804 }
1805
specialColumnPtr(int row,int column) const1806 TreeItem *TreeItemPart::specialColumnPtr(int row, int column) const
1807 {
1808 if (row == 0 && !isTopLevelMultiPart()) {
1809 switch (column) {
1810 case OFFSET_MIME:
1811 if (!m_partMime) {
1812 m_partMime = new TreeItemModifiedPart(const_cast<TreeItemPart*>(this), OFFSET_MIME);
1813 }
1814 return m_partMime;
1815 case OFFSET_RAW_CONTENTS:
1816 if (!m_partRaw) {
1817 m_partRaw = new TreeItemModifiedPart(const_cast<TreeItemPart*>(this), OFFSET_RAW_CONTENTS);
1818 }
1819 return m_partRaw;
1820 }
1821 }
1822 return 0;
1823 }
1824
silentlyReleaseMemoryRecursive()1825 void TreeItemPart::silentlyReleaseMemoryRecursive()
1826 {
1827 Q_FOREACH(TreeItem *item, m_children) {
1828 TreeItemPart *part = dynamic_cast<TreeItemPart *>(item);
1829 Q_ASSERT(part);
1830 part->silentlyReleaseMemoryRecursive();
1831 }
1832 if (m_partMime) {
1833 m_partMime->silentlyReleaseMemoryRecursive();
1834 delete m_partMime;
1835 m_partMime = 0;
1836 }
1837 if (m_partRaw) {
1838 m_partRaw->silentlyReleaseMemoryRecursive();
1839 delete m_partRaw;
1840 m_partRaw = 0;
1841 }
1842 m_data.clear();
1843 setFetchStatus(NONE);
1844 qDeleteAll(m_children);
1845 m_children.clear();
1846 }
1847
1848
1849
TreeItemModifiedPart(TreeItem * parent,const PartModifier kind)1850 TreeItemModifiedPart::TreeItemModifiedPart(TreeItem *parent, const PartModifier kind):
1851 TreeItemPart(parent), m_modifier(kind)
1852 {
1853 }
1854
row() const1855 int TreeItemModifiedPart::row() const
1856 {
1857 // we're always at the very top
1858 return 0;
1859 }
1860
specialColumnPtr(int row,int column) const1861 TreeItem *TreeItemModifiedPart::specialColumnPtr(int row, int column) const
1862 {
1863 Q_UNUSED(row);
1864 Q_UNUSED(column);
1865 // no special children below the current special one
1866 return 0;
1867 }
1868
isTopLevelMultiPart() const1869 bool TreeItemModifiedPart::isTopLevelMultiPart() const
1870 {
1871 // we're special enough not to ever act like a "top-level multipart"
1872 return false;
1873 }
1874
columnCount()1875 unsigned int TreeItemModifiedPart::columnCount()
1876 {
1877 // no child items, either
1878 return 0;
1879 }
1880
partId() const1881 QByteArray TreeItemModifiedPart::partId() const
1882 {
1883 if (m_modifier == OFFSET_RAW_CONTENTS) {
1884 // This item is not directly fetcheable, so it does *not* make sense to ask for it.
1885 // We cannot really assert at this point, though, because this function is published via the MVC interface.
1886 return "application-bug-dont-fetch-this";
1887 } else if (TreeItemPart *part = dynamic_cast<TreeItemPart *>(parent())) {
1888 // The TreeItemPart is supposed to prevent creation of any special subparts if it's a top-level multipart
1889 Q_ASSERT(!part->isTopLevelMultiPart());
1890 return part->partId() + '.' + modifierToByteArray();
1891 } else {
1892 // Our parent is a message/rfc822, and it's definitely not nested -> no need for parent id here
1893 // Cannot assert() on a dynamic_cast<TreeItemMessage*> at this point because the part is already nullptr at this time
1894 Q_ASSERT(dynamic_cast<TreeItemMessage*>(parent()));
1895 return modifierToByteArray();
1896 }
1897 }
1898
kind() const1899 TreeItem::PartModifier TreeItemModifiedPart::kind() const
1900 {
1901 return m_modifier;
1902 }
1903
modifierToByteArray() const1904 QByteArray TreeItemModifiedPart::modifierToByteArray() const
1905 {
1906 switch (m_modifier) {
1907 case OFFSET_HEADER:
1908 return "HEADER";
1909 case OFFSET_TEXT:
1910 return "TEXT";
1911 case OFFSET_MIME:
1912 return "MIME";
1913 case OFFSET_RAW_CONTENTS:
1914 Q_ASSERT(!"Cannot get the fetch modifier for an OFFSET_RAW_CONTENTS item");
1915 // fall through
1916 default:
1917 Q_ASSERT(false);
1918 return QByteArray();
1919 }
1920 }
1921
pathToPart() const1922 QByteArray TreeItemModifiedPart::pathToPart() const
1923 {
1924 if (TreeItemPart *parentPart = dynamic_cast<TreeItemPart *>(parent())) {
1925 return parentPart->pathToPart() + "/" + modifierToByteArray();
1926 } else {
1927 Q_ASSERT(dynamic_cast<TreeItemMessage *>(parent()));
1928 return "/" + modifierToByteArray();
1929 }
1930 }
1931
toIndex(Model * const model) const1932 QModelIndex TreeItemModifiedPart::toIndex(Model *const model) const
1933 {
1934 Q_ASSERT(model);
1935 // see TreeItem::toIndex() for the const_cast explanation
1936 return model->createIndex(row(), static_cast<int>(kind()), const_cast<TreeItemModifiedPart *>(this));
1937 }
1938
partIdForFetch(const PartFetchingMode mode) const1939 QByteArray TreeItemModifiedPart::partIdForFetch(const PartFetchingMode mode) const
1940 {
1941 Q_UNUSED(mode);
1942 // Don't try to use BINARY for special message parts, it's forbidden. One can only use that for the "regular" MIME parts
1943 return TreeItemPart::partIdForFetch(FETCH_PART_IMAP);
1944 }
1945
TreeItemPartMultipartMessage(TreeItem * parent,const Message::Envelope & envelope)1946 TreeItemPartMultipartMessage::TreeItemPartMultipartMessage(TreeItem *parent, const Message::Envelope &envelope):
1947 TreeItemPart(parent, "message/rfc822"), m_envelope(envelope), m_partHeader(0), m_partText(0)
1948 {
1949 }
1950
~TreeItemPartMultipartMessage()1951 TreeItemPartMultipartMessage::~TreeItemPartMultipartMessage()
1952 {
1953 }
1954
1955 /** @short Overridden from TreeItemPart::data with added support for RoleMessageEnvelope */
data(Model * const model,int role)1956 QVariant TreeItemPartMultipartMessage::data(Model * const model, int role)
1957 {
1958 switch (role) {
1959 case RoleMessageEnvelope:
1960 return QVariant::fromValue<Message::Envelope>(m_envelope);
1961 case RoleMessageHeaderReferences:
1962 case RoleMessageHeaderListPost:
1963 case RoleMessageHeaderListPostNo:
1964 // FIXME: implement me; TreeItemPart has no path for this
1965 return QVariant();
1966 default:
1967 return TreeItemPart::data(model, role);
1968 }
1969 }
1970
specialColumnPtr(int row,int column) const1971 TreeItem *TreeItemPartMultipartMessage::specialColumnPtr(int row, int column) const
1972 {
1973 if (row != 0)
1974 return 0;
1975 switch (column) {
1976 case OFFSET_HEADER:
1977 if (!m_partHeader) {
1978 m_partHeader = new TreeItemModifiedPart(const_cast<TreeItemPartMultipartMessage*>(this), OFFSET_HEADER);
1979 }
1980 return m_partHeader;
1981 case OFFSET_TEXT:
1982 if (!m_partText) {
1983 m_partText = new TreeItemModifiedPart(const_cast<TreeItemPartMultipartMessage*>(this), OFFSET_TEXT);
1984 }
1985 return m_partText;
1986 default:
1987 return TreeItemPart::specialColumnPtr(row, column);
1988 }
1989 }
1990
silentlyReleaseMemoryRecursive()1991 void TreeItemPartMultipartMessage::silentlyReleaseMemoryRecursive()
1992 {
1993 TreeItemPart::silentlyReleaseMemoryRecursive();
1994 if (m_partHeader) {
1995 m_partHeader->silentlyReleaseMemoryRecursive();
1996 delete m_partHeader;
1997 m_partHeader = 0;
1998 }
1999 if (m_partText) {
2000 m_partText->silentlyReleaseMemoryRecursive();
2001 delete m_partText;
2002 m_partText = 0;
2003 }
2004 }
2005
2006 }
2007 }
2008