File indexing completed on 2025-02-16 04:50:20
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 "retrievecollectionmetadatatask.h" 0009 0010 #include <KIMAP/GetAclJob> 0011 #include <KIMAP/GetMetaDataJob> 0012 #include <KIMAP/GetQuotaRootJob> 0013 #include <KIMAP/MyRightsJob> 0014 #include <KIMAP/RFCCodecs> 0015 #include <KIMAP/Session> 0016 #include <KLocalizedString> 0017 0018 #include "imapresource_debug.h" 0019 0020 #include "collectionmetadatahelper.h" 0021 #include "imapaclattribute.h" 0022 #include "imapquotaattribute.h" 0023 #include "noselectattribute.h" 0024 #include <Akonadi/CollectionAnnotationsAttribute> 0025 #include <Akonadi/CollectionQuotaAttribute> 0026 #include <Akonadi/EntityDisplayAttribute> 0027 0028 RetrieveCollectionMetadataTask::RetrieveCollectionMetadataTask(const ResourceStateInterface::Ptr &resource, QObject *parent) 0029 : ResourceTask(CancelIfNoSession, resource, parent) 0030 { 0031 } 0032 0033 RetrieveCollectionMetadataTask::~RetrieveCollectionMetadataTask() = default; 0034 0035 void RetrieveCollectionMetadataTask::doStart(KIMAP::Session *session) 0036 { 0037 qCDebug(IMAPRESOURCE_LOG) << collection().remoteId(); 0038 0039 // Prevent fetching metadata from noselect folders. 0040 if (collection().hasAttribute("noselect")) { 0041 NoSelectAttribute *noselect = static_cast<NoSelectAttribute *>(collection().attribute("noselect")); 0042 if (noselect->noSelect()) { 0043 qCDebug(IMAPRESOURCE_LOG) << "No Select folder"; 0044 endTaskIfNeeded(); 0045 return; 0046 } 0047 } 0048 0049 m_session = session; 0050 m_collection = collection(); 0051 const QString mailBox = mailBoxForCollection(m_collection); 0052 const QStringList capabilities = serverCapabilities(); 0053 0054 m_pendingMetaDataJobs = 0; 0055 0056 // First get the annotations from the mailbox if it's supported 0057 if (capabilities.contains(QLatin1StringView("METADATA")) || capabilities.contains(QLatin1StringView("ANNOTATEMORE"))) { 0058 auto meta = new KIMAP::GetMetaDataJob(session); 0059 meta->setMailBox(mailBox); 0060 if (capabilities.contains(QLatin1StringView("METADATA"))) { 0061 meta->setServerCapability(KIMAP::MetaDataJobBase::Metadata); 0062 meta->addRequestedEntry("/shared"); 0063 meta->setDepth(KIMAP::GetMetaDataJob::AllLevels); 0064 } else { 0065 meta->setServerCapability(KIMAP::MetaDataJobBase::Annotatemore); 0066 meta->addEntry("*", "value.shared"); 0067 } 0068 connect(meta, &KJob::result, this, &RetrieveCollectionMetadataTask::onGetMetaDataDone); 0069 m_pendingMetaDataJobs++; 0070 meta->start(); 0071 } 0072 0073 // Get the ACLs from the mailbox if it's supported 0074 if (capabilities.contains(QLatin1StringView("ACL"))) { 0075 auto rights = new KIMAP::MyRightsJob(session); 0076 rights->setMailBox(mailBox); 0077 connect(rights, &KJob::result, this, &RetrieveCollectionMetadataTask::onRightsReceived); 0078 m_pendingMetaDataJobs++; 0079 rights->start(); 0080 } 0081 0082 // Get the QUOTA info from the mailbox if it's supported 0083 if (capabilities.contains(QLatin1StringView("QUOTA"))) { 0084 auto quota = new KIMAP::GetQuotaRootJob(session); 0085 quota->setMailBox(mailBox); 0086 connect(quota, &KJob::result, this, &RetrieveCollectionMetadataTask::onQuotasReceived); 0087 m_pendingMetaDataJobs++; 0088 quota->start(); 0089 } 0090 0091 // the server does not have any of the capabilities needed to get extra info, so this 0092 // step is done here 0093 if (m_pendingMetaDataJobs == 0) { 0094 endTaskIfNeeded(); 0095 } 0096 } 0097 0098 void RetrieveCollectionMetadataTask::onGetMetaDataDone(KJob *job) 0099 { 0100 m_pendingMetaDataJobs--; 0101 if (job->error()) { 0102 qCWarning(IMAPRESOURCE_LOG) << "Get metadata failed: " << job->errorString(); 0103 endTaskIfNeeded(); 0104 return; // Well, no metadata for us then... 0105 } 0106 0107 auto meta = qobject_cast<KIMAP::GetMetaDataJob *>(job); 0108 QMap<QByteArray, QByteArray> rawAnnotations = meta->allMetaData(); 0109 0110 // filter out unused and annoying Cyrus annotation /vendor/cmu/cyrus-imapd/lastupdate 0111 // which contains the current date and time and thus constantly changes for no good 0112 // reason which triggers a change notification and thus a bunch of Akonadi operations 0113 rawAnnotations.remove("/shared/vendor/cmu/cyrus-imapd/lastupdate"); 0114 rawAnnotations.remove("/private/vendor/cmu/cyrus-imapd/lastupdate"); 0115 0116 // Store the mailbox metadata 0117 auto annotationsAttribute = m_collection.attribute<Akonadi::CollectionAnnotationsAttribute>(Akonadi::Collection::AddIfMissing); 0118 const QMap<QByteArray, QByteArray> oldAnnotations = annotationsAttribute->annotations(); 0119 if (oldAnnotations != rawAnnotations) { 0120 annotationsAttribute->setAnnotations(rawAnnotations); 0121 } 0122 0123 endTaskIfNeeded(); 0124 } 0125 0126 void RetrieveCollectionMetadataTask::onGetAclDone(KJob *job) 0127 { 0128 m_pendingMetaDataJobs--; 0129 if (job->error()) { 0130 qCWarning(IMAPRESOURCE_LOG) << "GetACL failed: " << job->errorString(); 0131 endTaskIfNeeded(); 0132 return; // Well, no metadata for us then... 0133 } 0134 0135 auto acl = qobject_cast<KIMAP::GetAclJob *>(job); 0136 0137 // Store the mailbox ACLs 0138 auto const aclAttribute = m_collection.attribute<Akonadi::ImapAclAttribute>(Akonadi::Collection::AddIfMissing); 0139 const QMap<QByteArray, KIMAP::Acl::Rights> oldRights = aclAttribute->rights(); 0140 if (oldRights != acl->allRights()) { 0141 aclAttribute->setRights(acl->allRights()); 0142 } 0143 0144 endTaskIfNeeded(); 0145 } 0146 0147 void RetrieveCollectionMetadataTask::onRightsReceived(KJob *job) 0148 { 0149 m_pendingMetaDataJobs--; 0150 if (job->error()) { 0151 qCWarning(IMAPRESOURCE_LOG) << "MyRights failed: " << job->errorString(); 0152 endTaskIfNeeded(); 0153 return; // Well, no metadata for us then... 0154 } 0155 0156 auto rightsJob = qobject_cast<KIMAP::MyRightsJob *>(job); 0157 0158 const KIMAP::Acl::Rights imapRights = rightsJob->rights(); 0159 0160 // Default value in case we have nothing better available 0161 KIMAP::Acl::Rights parentRights = KIMAP::Acl::CreateMailbox | KIMAP::Acl::Create; 0162 0163 // FIXME I don't think we have the parent's acl's available 0164 if (collection().parentCollection().attribute<Akonadi::ImapAclAttribute>()) { 0165 parentRights = myRights(collection().parentCollection()); 0166 } 0167 0168 // qCDebug(IMAPRESOURCE_LOG) << collection.remoteId() 0169 // << "imapRights:" << imapRights 0170 // << "newRights:" << newRights 0171 // << "oldRights:" << collection.rights(); 0172 0173 const bool isNewCollection = !m_collection.hasAttribute<Akonadi::ImapAclAttribute>(); 0174 const bool accessRevoked = CollectionMetadataHelper::applyRights(m_collection, imapRights, parentRights); 0175 if (accessRevoked && !isNewCollection) { 0176 // write access revoked 0177 const QString collectionName = m_collection.displayName(); 0178 0179 showInformationDialog(i18n("<p>Your access rights to folder <b>%1</b> have been restricted, " 0180 "it will no longer be possible to add messages to this folder.</p>", 0181 collectionName), 0182 i18n("Access rights revoked"), 0183 QStringLiteral("ShowRightsRevokedWarning")); 0184 } 0185 0186 // Store the mailbox ACLs 0187 auto aclAttribute = m_collection.attribute<Akonadi::ImapAclAttribute>(Akonadi::Collection::AddIfMissing); 0188 const KIMAP::Acl::Rights oldRights = aclAttribute->myRights(); 0189 if (oldRights != imapRights) { 0190 aclAttribute->setMyRights(imapRights); 0191 } 0192 0193 // The a right is required to list acl's 0194 if (imapRights & KIMAP::Acl::Admin) { 0195 auto acl = new KIMAP::GetAclJob(m_session); 0196 acl->setMailBox(mailBoxForCollection(m_collection)); 0197 connect(acl, &KJob::result, this, &RetrieveCollectionMetadataTask::onGetAclDone); 0198 m_pendingMetaDataJobs++; 0199 acl->start(); 0200 } 0201 0202 endTaskIfNeeded(); 0203 } 0204 0205 void RetrieveCollectionMetadataTask::onQuotasReceived(KJob *job) 0206 { 0207 m_pendingMetaDataJobs--; 0208 const QString &mailBox = mailBoxForCollection(m_collection); 0209 if (job->error()) { 0210 qCWarning(IMAPRESOURCE_LOG) << "Quota retrieval for mailbox " << mailBox << " failed: " << job->errorString(); 0211 endTaskIfNeeded(); 0212 return; // Well, no metadata for us then... 0213 } 0214 0215 auto quotaJob = qobject_cast<KIMAP::GetQuotaRootJob *>(job); 0216 0217 QList<QByteArray> allRoots = quotaJob->roots(); 0218 QList<QByteArray> newRoots; 0219 QList<QMap<QByteArray, qint64>> newLimits; 0220 QList<QMap<QByteArray, qint64>> newUsages; 0221 qint64 newCurrent = -1; 0222 qint64 newMax = -1; 0223 newRoots.reserve(allRoots.count()); 0224 newLimits.reserve(allRoots.count()); 0225 newUsages.reserve(allRoots.count()); 0226 0227 for (const QByteArray &root : std::as_const(allRoots)) { 0228 const QMap<QByteArray, qint64> limit = quotaJob->allLimits(root); 0229 const QMap<QByteArray, qint64> usage = quotaJob->allUsages(root); 0230 0231 // Process IMAP Quota roots with associated quotas only 0232 if (!limit.isEmpty() && !usage.isEmpty()) { 0233 newRoots << root; 0234 newLimits << limit; 0235 newUsages << usage; 0236 0237 const QString &decodedRoot = QString::fromUtf8(KIMAP::decodeImapFolderName(root)); 0238 0239 if (decodedRoot == mailBox) { 0240 newCurrent = newUsages.last()["STORAGE"] * 1024; 0241 newMax = newLimits.last()["STORAGE"] * 1024; 0242 } 0243 } 0244 } 0245 0246 // If usage and limit were not set, retrieve them from the first root, if exists 0247 if ((newCurrent == -1 && newMax == -1) && !newRoots.isEmpty()) { 0248 newCurrent = newUsages.first()["STORAGE"] * 1024; 0249 newMax = newLimits.first()["STORAGE"] * 1024; 0250 } 0251 0252 // Store the mailbox IMAP Quotas 0253 auto imapQuotaAttribute = m_collection.attribute<Akonadi::ImapQuotaAttribute>(Akonadi::Collection::AddIfMissing); 0254 const QList<QByteArray> oldRoots = imapQuotaAttribute->roots(); 0255 const QList<QMap<QByteArray, qint64>> oldLimits = imapQuotaAttribute->limits(); 0256 const QList<QMap<QByteArray, qint64>> oldUsages = imapQuotaAttribute->usages(); 0257 0258 if (oldRoots != newRoots || oldLimits != newLimits || oldUsages != newUsages) { 0259 imapQuotaAttribute->setQuotas(newRoots, newLimits, newUsages); 0260 } 0261 0262 // Store the collection Quota 0263 auto quotaAttribute = m_collection.attribute<Akonadi::CollectionQuotaAttribute>(Akonadi::Collection::AddIfMissing); 0264 qint64 oldCurrent = quotaAttribute->currentValue(); 0265 qint64 oldMax = quotaAttribute->maximumValue(); 0266 0267 if (oldCurrent != newCurrent || oldMax != newMax) { 0268 quotaAttribute->setCurrentValue(newCurrent); 0269 quotaAttribute->setMaximumValue(newMax); 0270 } 0271 0272 endTaskIfNeeded(); 0273 } 0274 0275 void RetrieveCollectionMetadataTask::endTaskIfNeeded() 0276 { 0277 if (m_pendingMetaDataJobs <= 0) { 0278 collectionAttributesRetrieved(m_collection); 0279 } 0280 } 0281 0282 #include "moc_retrievecollectionmetadatatask.cpp"