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"