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"