File indexing completed on 2024-11-24 04:44:05

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 "retrievecollectionstask.h"
0009 
0010 #include "noinferiorsattribute.h"
0011 #include "noselectattribute.h"
0012 
0013 #include <Akonadi/CachePolicy>
0014 #include <Akonadi/EntityDisplayAttribute>
0015 #include <Akonadi/MessageParts>
0016 #include <Akonadi/SpecialCollectionAttribute>
0017 #include <Akonadi/VectorHelper>
0018 
0019 #include <KMime/Message>
0020 
0021 #include "imapresource_debug.h"
0022 #include <KLocalizedString>
0023 
0024 RetrieveCollectionsTask::RetrieveCollectionsTask(const ResourceStateInterface::Ptr &resource, QObject *parent)
0025     : ResourceTask(CancelIfNoSession, resource, parent)
0026 {
0027 }
0028 
0029 RetrieveCollectionsTask::~RetrieveCollectionsTask() = default;
0030 
0031 void RetrieveCollectionsTask::doStart(KIMAP::Session *session)
0032 {
0033     Akonadi::Collection root;
0034     root.setName(resourceName());
0035     root.setRemoteId(rootRemoteId());
0036     root.setContentMimeTypes(QStringList(Akonadi::Collection::mimeType()));
0037     root.setParentCollection(Akonadi::Collection::root());
0038     root.addAttribute(new NoSelectAttribute(true));
0039 
0040     Akonadi::CachePolicy policy;
0041     policy.setInheritFromParent(false);
0042     policy.setSyncOnDemand(true);
0043 
0044     // The first in the list of namespaces is User namespace
0045     // If the user namespace is empty, then make it possible for user to create
0046     // new folders as children of the root folder
0047     if (serverNamespaces().value(0).name.isEmpty()) {
0048         root.setRights(Akonadi::Collection::CanCreateCollection);
0049     }
0050 
0051     QStringList localParts;
0052     localParts << QLatin1StringView(Akonadi::MessagePart::Envelope) << QLatin1StringView(Akonadi::MessagePart::Header);
0053     int cacheTimeout = 60;
0054 
0055     if (isDisconnectedModeEnabled()) {
0056         // For disconnected mode we also cache the body
0057         // and we keep all data indefinitely
0058         localParts << QLatin1StringView(Akonadi::MessagePart::Body);
0059         cacheTimeout = -1;
0060     }
0061 
0062     policy.setLocalParts(localParts);
0063     policy.setCacheTimeout(cacheTimeout);
0064     policy.setIntervalCheckTime(intervalCheckTime());
0065 
0066     root.setCachePolicy(policy);
0067 
0068     m_reportedCollections.insert(QString(), root);
0069 
0070     // this is ugly, but the result of LSUB is unfortunately not a sub-set of LIST
0071     // it also contains subscribed but currently not available (eg. deleted) mailboxes
0072     // so we need to use both and exclude mailboxes in LSUB but not in LIST
0073     if (isSubscriptionEnabled()) {
0074         auto fullListJob = new KIMAP::ListJob(session);
0075         fullListJob->setOption(KIMAP::ListJob::IncludeUnsubscribed);
0076         fullListJob->setQueriedNamespaces(serverNamespaces());
0077         connect(fullListJob, &KIMAP::ListJob::mailBoxesReceived, this, &RetrieveCollectionsTask::onFullMailBoxesReceived);
0078         connect(fullListJob, &KIMAP::ListJob::result, this, &RetrieveCollectionsTask::onFullMailBoxesReceiveDone);
0079         fullListJob->start();
0080     }
0081 
0082     auto listJob = new KIMAP::ListJob(session);
0083     listJob->setIncludeUnsubscribed(!isSubscriptionEnabled());
0084     listJob->setQueriedNamespaces(serverNamespaces());
0085     connect(listJob, &KIMAP::ListJob::mailBoxesReceived, this, &RetrieveCollectionsTask::onMailBoxesReceived);
0086     connect(listJob, &KIMAP::ListJob::result, this, &RetrieveCollectionsTask::onMailBoxesReceiveDone);
0087     listJob->start();
0088 }
0089 
0090 void RetrieveCollectionsTask::onMailBoxesReceived(const QList<KIMAP::MailBoxDescriptor> &descriptors, const QList<QList<QByteArray>> &flags)
0091 {
0092     const QStringList contentTypes = {KMime::Message::mimeType(), Akonadi::Collection::mimeType()};
0093 
0094     if (!descriptors.isEmpty()) {
0095         // This is still not optimal way of getting the separator, but it's better
0096         // than guessing every time from RID of parent collection
0097         setSeparatorCharacter(descriptors.first().separator);
0098     }
0099 
0100     for (int i = 0; i < descriptors.size(); ++i) {
0101         KIMAP::MailBoxDescriptor descriptor = descriptors[i];
0102 
0103         // skip phantom mailboxes contained in LSUB but not LIST
0104         if (isSubscriptionEnabled() && !m_fullReportedCollections.contains(descriptor.name)) {
0105             qCDebug(IMAPRESOURCE_LOG) << "Got phantom mailbox: " << descriptor.name;
0106             continue;
0107         }
0108 
0109         const QString separator = descriptor.separator;
0110         Q_ASSERT(separator.size() == 1); // that's what the spec says
0111 
0112         const QString boxName = descriptor.name.endsWith(separator) ? descriptor.name.left(descriptor.name.size() - 1) : descriptor.name;
0113 
0114         const QStringList pathParts = boxName.split(separator);
0115 
0116         QString parentPath;
0117         QString currentPath;
0118 
0119         const int pathPartsSize(pathParts.size());
0120         for (int j = 0; j < pathPartsSize; ++j) {
0121             const bool isDummy = j != pathPartsSize - 1;
0122             const QString pathPart = pathParts.at(j);
0123             currentPath += separator + pathPart;
0124 
0125             if (m_reportedCollections.contains(currentPath)) {
0126                 if (m_dummyCollections.contains(currentPath) && !isDummy) {
0127                     qCDebug(IMAPRESOURCE_LOG) << "Received the real collection for a dummy one : " << currentPath;
0128 
0129                     // set the correct attributes for the collection, eg. noselect needs to be removed
0130                     Akonadi::Collection c = m_reportedCollections.value(currentPath);
0131                     c.setContentMimeTypes(contentTypes);
0132                     c.setRights(Akonadi::Collection::AllRights);
0133                     c.removeAttribute<NoSelectAttribute>();
0134 
0135                     m_dummyCollections.remove(currentPath);
0136                     m_reportedCollections.remove(currentPath);
0137                     m_reportedCollections.insert(currentPath, c);
0138                 }
0139                 parentPath = currentPath;
0140                 continue;
0141             }
0142 
0143             const QList<QByteArray> currentFlags = isDummy ? (QList<QByteArray>() << "\\noselect") : flags[i];
0144 
0145             Akonadi::Collection c;
0146             c.setName(pathPart);
0147             c.setRemoteId(separator + pathPart);
0148             const Akonadi::Collection parentCollection = m_reportedCollections.value(parentPath);
0149             c.setParentCollection(parentCollection);
0150             c.setContentMimeTypes(contentTypes);
0151 
0152             // If the folder is the Inbox, make some special settings.
0153             if (currentPath.compare(separator + QLatin1StringView("INBOX"), Qt::CaseInsensitive) == 0) {
0154                 auto attr = c.attribute<Akonadi::EntityDisplayAttribute>(Akonadi::Collection::AddIfMissing);
0155                 attr->setDisplayName(i18n("Inbox"));
0156                 attr->setIconName(QStringLiteral("mail-folder-inbox"));
0157                 c.attribute<Akonadi::SpecialCollectionAttribute>(Akonadi::Collection::AddIfMissing)->setCollectionType("inbox");
0158                 setIdleCollection(c);
0159             }
0160 
0161             // If the folder is the user top-level folder, mark it as well, even although it is not officially noted in the RFC
0162             if (currentPath == (separator + QLatin1StringView("user")) && currentFlags.contains("\\noselect")) {
0163                 auto attr = c.attribute<Akonadi::EntityDisplayAttribute>(Akonadi::Collection::AddIfMissing);
0164                 attr->setDisplayName(i18n("Shared Folders"));
0165                 attr->setIconName(QStringLiteral("x-mail-distribution-list"));
0166             }
0167 
0168             // If this folder is a noselect folder, make some special settings.
0169             if (currentFlags.contains("\\noselect")) {
0170                 qCDebug(IMAPRESOURCE_LOG) << "Dummy collection created: " << currentPath;
0171                 c.addAttribute(new NoSelectAttribute(true));
0172                 c.setContentMimeTypes(QStringList() << Akonadi::Collection::mimeType());
0173                 c.setRights(Akonadi::Collection::ReadOnly);
0174             } else {
0175                 // remove the noselect attribute explicitly, in case we had set it before (eg. for non-subscribed non-leaf folders)
0176                 c.removeAttribute<NoSelectAttribute>();
0177             }
0178 
0179             // If this folder is a noinferiors folder, it is not allowed to create subfolders inside.
0180             if (currentFlags.contains("\\noinferiors")) {
0181                 // qCDebug(IMAPRESOURCE_LOG) << "Noinferiors: " << currentPath;
0182                 c.addAttribute(new NoInferiorsAttribute(true));
0183                 c.setRights(c.rights() & ~Akonadi::Collection::CanCreateCollection);
0184             }
0185 
0186             m_reportedCollections.insert(currentPath, c);
0187 
0188             if (isDummy) {
0189                 m_dummyCollections.insert(currentPath, c);
0190             }
0191 
0192             parentPath = currentPath;
0193         }
0194     }
0195 }
0196 
0197 void RetrieveCollectionsTask::onMailBoxesReceiveDone(KJob *job)
0198 {
0199     if (job->error()) {
0200         cancelTask(job->errorString());
0201     } else {
0202         collectionsRetrieved(Akonadi::valuesToVector(m_reportedCollections));
0203     }
0204 }
0205 
0206 void RetrieveCollectionsTask::onFullMailBoxesReceived(const QList<KIMAP::MailBoxDescriptor> &descriptors, const QList<QList<QByteArray>> &flags)
0207 {
0208     Q_UNUSED(flags)
0209     for (const KIMAP::MailBoxDescriptor &descriptor : descriptors) {
0210         m_fullReportedCollections.insert(descriptor.name);
0211     }
0212 }
0213 
0214 void RetrieveCollectionsTask::onFullMailBoxesReceiveDone(KJob *job)
0215 {
0216     if (job->error()) {
0217         cancelTask(job->errorString());
0218     }
0219 }
0220 
0221 #include "moc_retrievecollectionstask.cpp"