File indexing completed on 2024-06-02 05:21:12
0001 /* 0002 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> 0003 SPDX-FileContributor: Kevin Ottens <kevin@kdab.com> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "resourcetask.h" 0009 0010 #include <Akonadi/MessageFlags> 0011 0012 #include "imapresource_debug.h" 0013 #include "imapresource_trace.h" 0014 #include <KLocalizedString> 0015 0016 #include "collectionflagsattribute.h" 0017 #include "imapaclattribute.h" 0018 #include "imapflags.h" 0019 #include "sessionpool.h" 0020 0021 ResourceTask::ResourceTask(ActionIfNoSession action, ResourceStateInterface::Ptr resource, QObject *parent) 0022 : QObject(parent) 0023 , m_pool(nullptr) 0024 , m_sessionRequestId(0) 0025 , m_session(nullptr) 0026 , m_actionIfNoSession(action) 0027 , m_resource(resource) 0028 , mCancelled(false) 0029 { 0030 } 0031 0032 ResourceTask::~ResourceTask() 0033 { 0034 if (m_pool) { 0035 if (m_sessionRequestId) { 0036 m_pool->cancelSessionRequest(m_sessionRequestId); 0037 } 0038 if (m_session) { 0039 m_pool->releaseSession(m_session); 0040 } 0041 } 0042 } 0043 0044 void ResourceTask::start(SessionPool *pool) 0045 { 0046 qCDebug(IMAPRESOURCE_TRACE) << metaObject()->className(); 0047 m_pool = pool; 0048 connect(m_pool, &SessionPool::sessionRequestDone, this, &ResourceTask::onSessionRequested); 0049 0050 m_sessionRequestId = m_pool->requestSession(); 0051 0052 if (m_sessionRequestId <= 0) { 0053 m_sessionRequestId = 0; 0054 0055 abortTask(QString()); 0056 // In this case we were likely disconnected, try to get the resource online 0057 m_resource->scheduleConnectionAttempt(); 0058 } 0059 } 0060 0061 void ResourceTask::abortTask(const QString &errorString) 0062 { 0063 if (!mCancelled) { 0064 mCancelled = true; 0065 0066 switch (m_actionIfNoSession) { 0067 case CancelIfNoSession: 0068 qCDebug(IMAPRESOURCE_LOG) << "Cancelling this request."; 0069 m_resource->cancelTask(errorString.isEmpty() ? i18n("Unable to connect to the IMAP server.") : errorString); 0070 break; 0071 0072 case DeferIfNoSession: 0073 qCDebug(IMAPRESOURCE_LOG) << "Deferring this request."; 0074 m_resource->deferTask(); 0075 break; 0076 } 0077 } 0078 deleteLater(); 0079 } 0080 0081 void ResourceTask::onSessionRequested(qint64 requestId, KIMAP::Session *session, int errorCode, const QString &errorString) 0082 { 0083 if (requestId != m_sessionRequestId) { 0084 // Not for us, ignore 0085 return; 0086 } 0087 0088 disconnect(m_pool, &SessionPool::sessionRequestDone, this, &ResourceTask::onSessionRequested); 0089 m_sessionRequestId = 0; 0090 0091 if (errorCode != SessionPool::NoError) { 0092 abortTask(errorString); 0093 return; 0094 } 0095 0096 m_session = session; 0097 0098 connect(m_pool, &SessionPool::connectionLost, this, &ResourceTask::onConnectionLost); 0099 connect(m_pool, &SessionPool::disconnectDone, this, &ResourceTask::onPoolDisconnect); 0100 0101 qCDebug(IMAPRESOURCE_TRACE) << "starting: " << metaObject()->className(); 0102 doStart(m_session); 0103 } 0104 0105 void ResourceTask::onConnectionLost(KIMAP::Session *session) 0106 { 0107 if (session == m_session) { 0108 // Our session becomes invalid, so get rid of 0109 // the pointer, we don't need to release it once the 0110 // task is done 0111 m_session = nullptr; 0112 qCDebug(IMAPRESOURCE_TRACE) << metaObject()->className(); 0113 abortTask(i18n("Connection lost")); 0114 } 0115 } 0116 0117 void ResourceTask::onPoolDisconnect() 0118 { 0119 // All the sessions in the pool we used changed, 0120 // so get rid of the pointer, we don't need to 0121 // release our session anymore 0122 m_pool = nullptr; 0123 0124 qCDebug(IMAPRESOURCE_TRACE) << metaObject()->className(); 0125 abortTask(i18n("Connection lost")); 0126 } 0127 0128 QString ResourceTask::userName() const 0129 { 0130 return m_resource->userName(); 0131 } 0132 0133 QString ResourceTask::resourceName() const 0134 { 0135 return m_resource->resourceName(); 0136 } 0137 0138 QStringList ResourceTask::serverCapabilities() const 0139 { 0140 return m_resource->serverCapabilities(); 0141 } 0142 0143 QList<KIMAP::MailBoxDescriptor> ResourceTask::serverNamespaces() const 0144 { 0145 return m_resource->serverNamespaces(); 0146 } 0147 0148 bool ResourceTask::isAutomaticExpungeEnabled() const 0149 { 0150 return m_resource->isAutomaticExpungeEnabled(); 0151 } 0152 0153 bool ResourceTask::isSubscriptionEnabled() const 0154 { 0155 return m_resource->isSubscriptionEnabled(); 0156 } 0157 0158 bool ResourceTask::isDisconnectedModeEnabled() const 0159 { 0160 return m_resource->isDisconnectedModeEnabled(); 0161 } 0162 0163 int ResourceTask::intervalCheckTime() const 0164 { 0165 return m_resource->intervalCheckTime(); 0166 } 0167 0168 static Akonadi::Collection detachCollection(const Akonadi::Collection &collection) 0169 { 0170 // HACK: Attributes are accessed via a const function, and the implicitly shared private pointer thus doesn't detach. 0171 // We force a detach to avoid surprises. (RetrieveItemsTask used to write back the collection changes, even though the task was canceled) 0172 // Once this is fixed this function can go away. 0173 Akonadi::Collection col = collection; 0174 col.setId(col.id()); 0175 return col; 0176 } 0177 0178 Akonadi::Collection ResourceTask::collection() const 0179 { 0180 return detachCollection(m_resource->collection()); 0181 } 0182 0183 Akonadi::Item ResourceTask::item() const 0184 { 0185 return m_resource->item(); 0186 } 0187 0188 Akonadi::Item::List ResourceTask::items() const 0189 { 0190 return m_resource->items(); 0191 } 0192 0193 Akonadi::Collection ResourceTask::parentCollection() const 0194 { 0195 return detachCollection(m_resource->parentCollection()); 0196 } 0197 0198 Akonadi::Collection ResourceTask::sourceCollection() const 0199 { 0200 return detachCollection(m_resource->sourceCollection()); 0201 } 0202 0203 Akonadi::Collection ResourceTask::targetCollection() const 0204 { 0205 return detachCollection(m_resource->targetCollection()); 0206 } 0207 0208 QSet<QByteArray> ResourceTask::parts() const 0209 { 0210 return m_resource->parts(); 0211 } 0212 0213 QSet<QByteArray> ResourceTask::addedFlags() const 0214 { 0215 return m_resource->addedFlags(); 0216 } 0217 0218 QSet<QByteArray> ResourceTask::removedFlags() const 0219 { 0220 return m_resource->removedFlags(); 0221 } 0222 0223 QString ResourceTask::rootRemoteId() const 0224 { 0225 return m_resource->rootRemoteId(); 0226 } 0227 0228 QString ResourceTask::mailBoxForCollection(const Akonadi::Collection &collection) const 0229 { 0230 return m_resource->mailBoxForCollection(collection); 0231 } 0232 0233 void ResourceTask::setIdleCollection(const Akonadi::Collection &collection) 0234 { 0235 if (!mCancelled) { 0236 m_resource->setIdleCollection(collection); 0237 } 0238 } 0239 0240 void ResourceTask::applyCollectionChanges(const Akonadi::Collection &collection) 0241 { 0242 if (!mCancelled) { 0243 m_resource->applyCollectionChanges(collection); 0244 } 0245 } 0246 0247 void ResourceTask::itemRetrieved(const Akonadi::Item &item) 0248 { 0249 if (!mCancelled) { 0250 m_resource->itemRetrieved(item); 0251 emitPercent(100); 0252 } 0253 deleteLater(); 0254 } 0255 0256 void ResourceTask::itemsRetrieved(const Akonadi::Item::List &items) 0257 { 0258 if (!mCancelled) { 0259 m_resource->itemsRetrieved(items); 0260 } 0261 } 0262 0263 void ResourceTask::itemsRetrievedIncremental(const Akonadi::Item::List &changed, const Akonadi::Item::List &removed) 0264 { 0265 if (!mCancelled) { 0266 m_resource->itemsRetrievedIncremental(changed, removed); 0267 } 0268 } 0269 0270 void ResourceTask::itemsRetrievalDone() 0271 { 0272 if (!mCancelled) { 0273 m_resource->itemsRetrievalDone(); 0274 } 0275 deleteLater(); 0276 } 0277 0278 void ResourceTask::setTotalItems(int totalItems) 0279 { 0280 if (!mCancelled) { 0281 m_resource->setTotalItems(totalItems); 0282 } 0283 } 0284 0285 void ResourceTask::changeCommitted(const Akonadi::Item &item) 0286 { 0287 if (!mCancelled) { 0288 m_resource->itemChangeCommitted(item); 0289 } 0290 deleteLater(); 0291 } 0292 0293 void ResourceTask::changesCommitted(const Akonadi::Item::List &items) 0294 { 0295 if (!mCancelled) { 0296 m_resource->itemsChangesCommitted(items); 0297 } 0298 deleteLater(); 0299 } 0300 0301 void ResourceTask::searchFinished(const QList<qint64> &result, bool isRid) 0302 { 0303 if (!mCancelled) { 0304 m_resource->searchFinished(result, isRid); 0305 } 0306 deleteLater(); 0307 } 0308 0309 void ResourceTask::collectionsRetrieved(const Akonadi::Collection::List &collections) 0310 { 0311 if (!mCancelled) { 0312 m_resource->collectionsRetrieved(collections); 0313 } 0314 deleteLater(); 0315 } 0316 0317 void ResourceTask::collectionAttributesRetrieved(const Akonadi::Collection &col) 0318 { 0319 if (!mCancelled) { 0320 m_resource->collectionAttributesRetrieved(col); 0321 } 0322 deleteLater(); 0323 } 0324 0325 void ResourceTask::changeCommitted(const Akonadi::Collection &collection) 0326 { 0327 if (!mCancelled) { 0328 m_resource->collectionChangeCommitted(collection); 0329 } 0330 deleteLater(); 0331 } 0332 0333 void ResourceTask::changeCommitted(const Akonadi::Tag &tag) 0334 { 0335 if (!mCancelled) { 0336 m_resource->tagChangeCommitted(tag); 0337 } 0338 deleteLater(); 0339 } 0340 0341 void ResourceTask::changeProcessed() 0342 { 0343 if (!mCancelled) { 0344 m_resource->changeProcessed(); 0345 } 0346 deleteLater(); 0347 } 0348 0349 void ResourceTask::cancelTask(const QString &errorString) 0350 { 0351 qCDebug(IMAPRESOURCE_LOG) << "Cancel task: " << errorString; 0352 if (!mCancelled) { 0353 mCancelled = true; 0354 m_resource->cancelTask(errorString); 0355 } 0356 deleteLater(); 0357 } 0358 0359 void ResourceTask::deferTask() 0360 { 0361 if (!mCancelled) { 0362 mCancelled = true; 0363 m_resource->deferTask(); 0364 } 0365 deleteLater(); 0366 } 0367 0368 void ResourceTask::restartItemRetrieval(Akonadi::Collection::Id col) 0369 { 0370 if (!mCancelled) { 0371 m_resource->restartItemRetrieval(col); 0372 } 0373 deleteLater(); 0374 } 0375 0376 void ResourceTask::taskDone() 0377 { 0378 m_resource->taskDone(); 0379 deleteLater(); 0380 } 0381 0382 void ResourceTask::emitPercent(int percent) 0383 { 0384 m_resource->emitPercent(percent); 0385 } 0386 0387 void ResourceTask::emitError(const QString &message) 0388 { 0389 m_resource->emitError(message); 0390 } 0391 0392 void ResourceTask::emitWarning(const QString &message) 0393 { 0394 m_resource->emitWarning(message); 0395 } 0396 0397 void ResourceTask::synchronizeCollectionTree() 0398 { 0399 m_resource->synchronizeCollectionTree(); 0400 } 0401 0402 void ResourceTask::showInformationDialog(const QString &message, const QString &title, const QString &dontShowAgainName) 0403 { 0404 m_resource->showInformationDialog(message, title, dontShowAgainName); 0405 } 0406 0407 QList<QByteArray> ResourceTask::fromAkonadiToSupportedImapFlags(const QList<QByteArray> &flags, const Akonadi::Collection &collection) 0408 { 0409 QList<QByteArray> imapFlags = fromAkonadiFlags(flags); 0410 0411 const auto flagAttr = collection.attribute<Akonadi::CollectionFlagsAttribute>(); 0412 // the server does not support arbitrary flags, so filter out those it can't handle 0413 if (flagAttr && !flagAttr->flags().isEmpty() && !flagAttr->flags().contains("\\*")) { 0414 for (QList<QByteArray>::iterator it = imapFlags.begin(); it != imapFlags.end();) { 0415 if (flagAttr->flags().contains(*it)) { 0416 ++it; 0417 } else { 0418 qCDebug(IMAPRESOURCE_LOG) << "Server does not support flag" << *it; 0419 it = imapFlags.erase(it); 0420 } 0421 } 0422 } 0423 0424 return imapFlags; 0425 } 0426 0427 QList<QByteArray> ResourceTask::fromAkonadiFlags(const QList<QByteArray> &flags) 0428 { 0429 QList<QByteArray> newFlags; 0430 0431 for (const QByteArray &oldFlag : flags) { 0432 if (oldFlag == Akonadi::MessageFlags::Seen) { 0433 newFlags.append(ImapFlags::Seen); 0434 } else if (oldFlag == Akonadi::MessageFlags::Deleted) { 0435 newFlags.append(ImapFlags::Deleted); 0436 } else if (oldFlag == Akonadi::MessageFlags::Answered || oldFlag == Akonadi::MessageFlags::Replied) { 0437 newFlags.append(ImapFlags::Answered); 0438 } else if (oldFlag == Akonadi::MessageFlags::Flagged) { 0439 newFlags.append(ImapFlags::Flagged); 0440 } else { 0441 newFlags.append(oldFlag); 0442 } 0443 } 0444 0445 return newFlags; 0446 } 0447 0448 QSet<QByteArray> ResourceTask::toAkonadiFlags(const QList<QByteArray> &flags) 0449 { 0450 QSet<QByteArray> newFlags; 0451 0452 for (const QByteArray &oldFlag : flags) { 0453 if (oldFlag == ImapFlags::Seen) { 0454 newFlags.insert(Akonadi::MessageFlags::Seen); 0455 } else if (oldFlag == ImapFlags::Deleted) { 0456 newFlags.insert(Akonadi::MessageFlags::Deleted); 0457 } else if (oldFlag == ImapFlags::Answered) { 0458 newFlags.insert(Akonadi::MessageFlags::Answered); 0459 } else if (oldFlag == ImapFlags::Flagged) { 0460 newFlags.insert(Akonadi::MessageFlags::Flagged); 0461 } else if (oldFlag.isEmpty()) { 0462 // filter out empty flags, to avoid isNull/isEmpty confusions higher up 0463 continue; 0464 } else { 0465 newFlags.insert(oldFlag); 0466 } 0467 } 0468 0469 return newFlags; 0470 } 0471 0472 void ResourceTask::kill() 0473 { 0474 qCDebug(IMAPRESOURCE_TRACE) << metaObject()->className(); 0475 abortTask(i18n("killed")); 0476 } 0477 0478 const QChar ResourceTask::separatorCharacter() const 0479 { 0480 const QChar separator = m_resource->separatorCharacter(); 0481 if (!separator.isNull()) { 0482 return separator; 0483 } else { 0484 // If we request the separator before first folder listing, then try to guess 0485 // the separator: 0486 // If we create a toplevel folder, assume the separator to be '/'. This is not perfect, but detecting the right 0487 // IMAP separator is not straightforward for toplevel folders, and fixes bug 292418 and maybe other, where 0488 // subfolders end up with remote id's starting with "i" (the first letter of imap:// ...) 0489 0490 QString remoteId; 0491 // We don't always have parent collection set (for example for CollectionChangeTask), 0492 // in such cases however we can use current collection's remoteId to get the separator 0493 const Akonadi::Collection parent = parentCollection(); 0494 if (parent.isValid()) { 0495 remoteId = parent.remoteId(); 0496 } else { 0497 remoteId = collection().remoteId(); 0498 } 0499 return ((remoteId != rootRemoteId()) && !remoteId.isEmpty()) ? remoteId.at(0) : QLatin1Char('/'); 0500 } 0501 } 0502 0503 void ResourceTask::setSeparatorCharacter(QChar separator) 0504 { 0505 m_resource->setSeparatorCharacter(separator); 0506 } 0507 0508 bool ResourceTask::serverSupportsAnnotations() const 0509 { 0510 return serverCapabilities().contains(QLatin1StringView("METADATA")) || serverCapabilities().contains(QLatin1StringView("ANNOTATEMORE")); 0511 } 0512 0513 bool ResourceTask::serverSupportsCondstore() const 0514 { 0515 // Don't enable CONDSTORE for GMail (X-GM-EXT-1 is a GMail-specific capability) 0516 // because it breaks changes synchronization when using labels. 0517 return serverCapabilities().contains(QLatin1StringView("CONDSTORE")) && !serverCapabilities().contains(QLatin1StringView("X-GM-EXT-1")); 0518 } 0519 0520 int ResourceTask::batchSize() const 0521 { 0522 return m_resource->batchSize(); 0523 } 0524 0525 ResourceStateInterface::Ptr ResourceTask::resourceState() 0526 { 0527 return m_resource; 0528 } 0529 0530 KIMAP::Acl::Rights ResourceTask::myRights(const Akonadi::Collection &col) 0531 { 0532 const auto aclAttribute = col.attribute<Akonadi::ImapAclAttribute>(); 0533 if (aclAttribute) { 0534 // HACK, only return myrights if they are available 0535 if (aclAttribute->myRights() != KIMAP::Acl::None) { 0536 return aclAttribute->myRights(); 0537 } else { 0538 // This should be removed after 4.14, and myrights should be always used. 0539 return aclAttribute->rights().value(userName().toUtf8()); 0540 } 0541 } 0542 return KIMAP::Acl::None; 0543 } 0544 0545 void ResourceTask::setItemMergingMode(Akonadi::ItemSync::MergeMode mode) 0546 { 0547 m_resource->setItemMergingMode(mode); 0548 } 0549 0550 #include "moc_resourcetask.cpp"