File indexing completed on 2024-06-23 05:21:16
0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net> 0002 0003 This file is part of the Trojita Qt IMAP e-mail client, 0004 http://trojita.flaska.net/ 0005 0006 This program is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU General Public License as 0008 published by the Free Software Foundation; either version 2 of 0009 the License or (at your option) version 3 or any later version 0010 accepted by the membership of KDE e.V. (or its successor approved 0011 by the membership of KDE e.V.), which shall act as a proxy 0012 defined in Section 14 of version 3 of the license. 0013 0014 This program is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0017 GNU General Public License for more details. 0018 0019 You should have received a copy of the GNU General Public License 0020 along with this program. If not, see <http://www.gnu.org/licenses/>. 0021 */ 0022 0023 #include "SortTask.h" 0024 #include <algorithm> 0025 #include "Imap/Model/ItemRoles.h" 0026 #include "Imap/Model/Model.h" 0027 #include "Imap/Model/MailboxTree.h" 0028 #include "Imap/Model/TaskPresentationModel.h" 0029 #include "KeepMailboxOpenTask.h" 0030 0031 namespace Imap 0032 { 0033 namespace Mailbox 0034 { 0035 0036 0037 SortTask::SortTask(Model *model, const QModelIndex &mailbox, const QStringList &searchConditions, const QStringList &sortCriteria): 0038 ImapTask(model), mailboxIndex(mailbox), searchConditions(searchConditions), sortCriteria(sortCriteria), 0039 m_persistentSearch(false), m_firstUntaggedReceived(false), m_firstCommandCompleted(false) 0040 { 0041 conn = model->findTaskResponsibleFor(mailbox); 0042 conn->addDependentTask(this); 0043 if (searchConditions.isEmpty()) 0044 this->searchConditions << QStringLiteral("ALL"); 0045 } 0046 0047 void SortTask::perform() 0048 { 0049 parser = conn->parser; 0050 markAsActiveTask(); 0051 0052 IMAP_TASK_CHECK_ABORT_DIE; 0053 0054 if (! mailboxIndex.isValid()) { 0055 _failed(tr("Mailbox vanished before we could ask for threading info")); 0056 return; 0057 } 0058 0059 // We can be killed when appropriate 0060 KeepMailboxOpenTask *keepTask = dynamic_cast<KeepMailboxOpenTask*>(conn); 0061 Q_ASSERT(keepTask); 0062 keepTask->feelFreeToAbortCaller(this); 0063 0064 if (sortCriteria.isEmpty()) { 0065 if (model->accessParser(parser).capabilitiesFresh && 0066 model->accessParser(parser).capabilities.contains(QStringLiteral("ESEARCH"))) { 0067 // We always prefer ESEARCH over SEARCH, if only for its embedded reference to the command tag 0068 if (model->accessParser(parser).capabilities.contains(QStringLiteral("CONTEXT=SEARCH"))) { 0069 // Hurray, this IMAP server supports incremental ESEARCH updates 0070 m_persistentSearch = true; 0071 sortTag = parser->uidESearch("utf-8", searchConditions, 0072 QStringList() << QStringLiteral("ALL") << QStringLiteral("UPDATE")); 0073 } else { 0074 // ESORT without CONTEXT is still worth the effort, if only for the tag reference 0075 sortTag = parser->uidESearch("utf-8", searchConditions, QStringList() << QStringLiteral("ALL")); 0076 } 0077 } else { 0078 // Plain "old" SORT 0079 sortTag = parser->uidSearch(searchConditions, 0080 // It looks that Exchange 2003 does not support the UTF-8 charset in searches. 0081 // That is, of course, insane, and only illustrates how useless its support of IMAP really is. 0082 model->m_capabilitiesBlacklist.contains(QStringLiteral("X-NO-UTF8-SEARCH")) ? QByteArray() : "utf-8" 0083 ); 0084 } 0085 } else { 0086 // SEARCH and SORT combined 0087 if (model->accessParser(parser).capabilitiesFresh && 0088 model->accessParser(parser).capabilities.contains(QStringLiteral("ESORT"))) { 0089 // ESORT's better than regular SORT, if only for its embedded reference to the command tag 0090 if (model->accessParser(parser).capabilities.contains(QStringLiteral("CONTEXT=SORT"))) { 0091 // Hurray, this IMAP server supports incremental SORT updates 0092 m_persistentSearch = true; 0093 sortTag = parser->uidESort(sortCriteria, "utf-8", searchConditions, 0094 QStringList() << QStringLiteral("ALL") << QStringLiteral("UPDATE")); 0095 } else { 0096 // ESORT without CONTEXT is still worth the effort, if only for the tag reference 0097 sortTag = parser->uidESort(sortCriteria, "utf-8", searchConditions, QStringList() << QStringLiteral("ALL")); 0098 } 0099 } else { 0100 // Plain "old" SORT 0101 sortTag = parser->uidSort(sortCriteria, "utf-8", searchConditions); 0102 } 0103 } 0104 } 0105 0106 bool SortTask::handleStateHelper(const Imap::Responses::State *const resp) 0107 { 0108 if (resp->tag.isEmpty()) { 0109 if (resp->kind == Responses::NO && resp->respCode == Responses::NOUPDATE) { 0110 // * NO [NOUPDATE "tag"] means that the server won't be providing further updates for our SEARCH/SORT criteria 0111 const Responses::RespData<QString> *const untaggedTag = dynamic_cast<const Responses::RespData<QString>* const>( 0112 resp->respCodeData.data()); 0113 Q_ASSERT(untaggedTag); 0114 if (untaggedTag->data.toUtf8() == sortTag) { 0115 m_persistentSearch = false; 0116 model->m_taskModel->slotTaskMighHaveChanged(this); 0117 0118 if (m_firstCommandCompleted) { 0119 // The server decided that it will no longer inform us about the updated SORT order, and the original 0120 // response has been already received and processed. That means that we're done here and shall declare 0121 // ourselves as completed. 0122 _completed(); 0123 } 0124 // We actually support even more benevolent mode of operation where the server can tell us at any time that 0125 // this context updating is no longer supported. Yay for that; let's hope that it's reasonably bug-free now. 0126 return true; 0127 } 0128 } 0129 return false; 0130 } 0131 0132 if (resp->tag == sortTag) { 0133 m_firstCommandCompleted = true; 0134 if (resp->kind == Responses::OK) { 0135 emit sortingAvailable(sortResult); 0136 if (!m_persistentSearch || _aborted) { 0137 // This is a one-shot operation, we shall not remain as an active task, listening for further updates 0138 _completed(); 0139 } else { 0140 // got to prod the TaskPresentationModel 0141 model->m_taskModel->slotTaskMighHaveChanged(this); 0142 0143 // Even though we aren't "finished" at this point, the KeepMailboxOpenTask is now free to issue its IDLE thing, 0144 // as that won't interfere with our mode of operation. Let's kick it around. 0145 KeepMailboxOpenTask *keepTask = dynamic_cast<KeepMailboxOpenTask*>(conn); 0146 Q_ASSERT(keepTask); 0147 keepTask->activateTasks(); 0148 } 0149 } else { 0150 _failed(tr("Sorting command has failed")); 0151 } 0152 return true; 0153 } else if (resp->tag == cancelUpdateTag) { 0154 m_persistentSearch = false; 0155 model->m_taskModel->slotTaskMighHaveChanged(this); 0156 _completed(); 0157 return true; 0158 } else { 0159 return false; 0160 } 0161 } 0162 0163 bool SortTask::handleSort(const Imap::Responses::Sort *const resp) 0164 { 0165 sortResult = resp->numbers; 0166 return true; 0167 } 0168 0169 bool SortTask::handleSearch(const Imap::Responses::Search *const resp) 0170 { 0171 if (searchConditions == QStringList() << QStringLiteral("ALL")) { 0172 // We're really a SORT task, so we shouldn't process this stuff 0173 return false; 0174 } 0175 0176 // The actual data for the SEARCH response can be split into several responses. 0177 // Possible performance optimization might be to call sort & unique only after receiving the tagged OK, 0178 // but that'd also mean that one has to keep track of whether we're doing a SORT or SEARCH from there. 0179 // That just doesn't look like worth it. 0180 0181 sortResult += resp->items; 0182 std::sort(sortResult.begin(), sortResult.end()); 0183 sortResult.erase(std::unique(sortResult.begin(), sortResult.end()), sortResult.end()); 0184 return true; 0185 } 0186 0187 bool SortTask::handleESearch(const Responses::ESearch *const resp) 0188 { 0189 if (resp->tag != sortTag) 0190 return false; 0191 0192 auto allComparator = [](const auto &listData) { return listData.first == "ALL"; }; 0193 auto allIterator = std::find_if(resp->listData.constBegin(), resp->listData.constEnd(), allComparator); 0194 0195 if (allIterator != resp->listData.constEnd()) { 0196 m_firstUntaggedReceived = true; 0197 sortResult = allIterator->second; 0198 0199 if (!sortResult.isEmpty()) { 0200 // Only check when the result set is non-empty, https://bugs.kde.org/show_bug.cgi?id=350698 0201 if (resp->seqOrUids != Imap::Responses::ESearch::UIDS) { 0202 throw UnexpectedResponseReceived("ESEARCH response to a UID SEARCH / UID SORT command with matching tag uses " 0203 "sequence numbers instead of UIDs", *resp); 0204 } 0205 } 0206 0207 ++allIterator; 0208 if (std::find_if(allIterator, resp->listData.constEnd(), allComparator) != resp->listData.constEnd()) 0209 throw UnexpectedResponseReceived("ESEARCH contains the ALL key too many times", *resp); 0210 0211 if (!resp->incrementalContextData.isEmpty()) 0212 throw UnexpectedResponseReceived("ESEARCH contains both ALL result set and some incremental updates", *resp); 0213 0214 return true; 0215 } 0216 0217 Q_ASSERT(allIterator == resp->listData.constEnd()); 0218 0219 if (resp->incrementalContextData.isEmpty()) { 0220 sortResult.clear(); 0221 // This means that there have been no matches 0222 // FIXME: cover this in the test suite! 0223 return true; 0224 } else { 0225 if (resp->seqOrUids != Imap::Responses::ESearch::UIDS) { 0226 throw UnexpectedResponseReceived("ESEARCH response to a UID SEARCH / UID SORT command with matching tag uses " 0227 "sequence numbers instead of UIDs", *resp); 0228 } 0229 } 0230 0231 Q_ASSERT(!resp->incrementalContextData.isEmpty()); 0232 0233 if (!m_persistentSearch) 0234 throw UnexpectedResponseReceived("ESEARCH contains incremental responses even though we haven't requested that", *resp); 0235 0236 emit incrementalSortUpdate(resp->incrementalContextData); 0237 0238 return true; 0239 } 0240 0241 QVariant SortTask::taskData(const int role) const 0242 { 0243 return role == RoleTaskCompactName ? QVariant(tr("Sorting mailbox")) : QVariant(); 0244 } 0245 0246 void SortTask::_failed(const QString &errorMessage) 0247 { 0248 // FIXME: show this in the GUI 0249 emit sortingFailed(); 0250 ImapTask::_failed(errorMessage); 0251 } 0252 0253 bool SortTask::isPersistent() const 0254 { 0255 return m_persistentSearch; 0256 } 0257 0258 /** @short Return true if this task has already done its job and is now merely listening for further updates */ 0259 bool SortTask::isJustUpdatingNow() const 0260 { 0261 return isPersistent() && m_firstCommandCompleted && !_aborted; 0262 } 0263 0264 void SortTask::cancelSortingUpdates() 0265 { 0266 Q_ASSERT(m_persistentSearch); 0267 Q_ASSERT(!sortTag.isEmpty()); 0268 KeepMailboxOpenTask *keepTask = dynamic_cast<KeepMailboxOpenTask*>(conn); 0269 Q_ASSERT(keepTask); 0270 keepTask->breakOrCancelPossibleIdle(); 0271 cancelUpdateTag = parser->cancelUpdate(sortTag); 0272 } 0273 0274 void SortTask::abort() 0275 { 0276 if (cancelUpdateTag.isEmpty() && isJustUpdatingNow()) { 0277 cancelSortingUpdates(); 0278 } 0279 ImapTask::abort(); 0280 } 0281 0282 } 0283 }