File indexing completed on 2024-11-17 04:45: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 "changecollectiontask.h" 0009 0010 #include <KIMAP/RenameJob> 0011 #include <KIMAP/Session> 0012 #include <KIMAP/SetAclJob> 0013 #include <KIMAP/SetMetaDataJob> 0014 #include <KIMAP/SubscribeJob> 0015 #include <KIMAP/UnsubscribeJob> 0016 0017 #include "imapaclattribute.h" 0018 #include "imapquotaattribute.h" 0019 #include <Akonadi/CollectionAnnotationsAttribute> 0020 0021 #include "imapresource_debug.h" 0022 #include <KLocalizedString> 0023 0024 ChangeCollectionTask::ChangeCollectionTask(const ResourceStateInterface::Ptr &resource, QObject *parent) 0025 : ResourceTask(DeferIfNoSession, resource, parent) 0026 { 0027 } 0028 0029 ChangeCollectionTask::~ChangeCollectionTask() = default; 0030 0031 void ChangeCollectionTask::syncEnabledState(bool enable) 0032 { 0033 m_syncEnabledState = enable; 0034 } 0035 0036 void ChangeCollectionTask::doStart(KIMAP::Session *session) 0037 { 0038 if (collection().remoteId().isEmpty()) { 0039 emitError(i18n("Cannot modify IMAP folder '%1', it does not exist on the server.", collection().name())); 0040 changeProcessed(); 0041 return; 0042 } 0043 0044 m_collection = collection(); 0045 m_pendingJobs = 0; 0046 0047 if (parts().contains("AccessRights")) { 0048 auto aclAttribute = m_collection.attribute<Akonadi::ImapAclAttribute>(); 0049 0050 if (aclAttribute == nullptr) { 0051 emitWarning(i18n("ACLs for '%1' need to be retrieved from the IMAP server first. Skipping ACL change", collection().name())); 0052 } else { 0053 KIMAP::Acl::Rights imapRights = aclAttribute->rights().value(userName().toUtf8()); 0054 Akonadi::Collection::Rights newRights = collection().rights(); 0055 0056 if (newRights & Akonadi::Collection::CanChangeItem) { 0057 imapRights |= KIMAP::Acl::Write; 0058 } else { 0059 imapRights &= ~KIMAP::Acl::Write; 0060 } 0061 0062 if (newRights & Akonadi::Collection::CanCreateItem) { 0063 imapRights |= KIMAP::Acl::Insert; 0064 } else { 0065 imapRights &= ~KIMAP::Acl::Insert; 0066 } 0067 0068 if (newRights & Akonadi::Collection::CanDeleteItem) { 0069 imapRights |= KIMAP::Acl::DeleteMessage; 0070 } else { 0071 imapRights &= ~KIMAP::Acl::DeleteMessage; 0072 } 0073 0074 if (newRights & (Akonadi::Collection::CanChangeCollection | Akonadi::Collection::CanCreateCollection)) { 0075 imapRights |= KIMAP::Acl::CreateMailbox; 0076 imapRights |= KIMAP::Acl::Create; 0077 } else { 0078 imapRights &= ~KIMAP::Acl::CreateMailbox; 0079 imapRights &= ~KIMAP::Acl::Create; 0080 } 0081 0082 if (newRights & Akonadi::Collection::CanDeleteCollection) { 0083 imapRights |= KIMAP::Acl::DeleteMailbox; 0084 } else { 0085 imapRights &= ~KIMAP::Acl::DeleteMailbox; 0086 } 0087 0088 if ((newRights & Akonadi::Collection::CanDeleteItem) && (newRights & Akonadi::Collection::CanDeleteCollection)) { 0089 imapRights |= KIMAP::Acl::Delete; 0090 } else { 0091 imapRights &= ~KIMAP::Acl::Delete; 0092 } 0093 0094 qCDebug(IMAPRESOURCE_LOG) << "imapRights:" << imapRights << "newRights:" << newRights; 0095 0096 auto job = new KIMAP::SetAclJob(session); 0097 job->setMailBox(mailBoxForCollection(collection())); 0098 job->setRights(KIMAP::SetAclJob::Change, imapRights); 0099 job->setIdentifier(userName().toUtf8()); 0100 0101 connect(job, &KIMAP::SetAclJob::result, this, &ChangeCollectionTask::onSetAclDone); 0102 0103 job->start(); 0104 0105 m_pendingJobs++; 0106 } 0107 } 0108 0109 if (parts().contains("collectionannotations") && serverSupportsAnnotations()) { 0110 Akonadi::Collection c = collection(); 0111 auto annotationsAttribute = c.attribute<Akonadi::CollectionAnnotationsAttribute>(); 0112 0113 if (annotationsAttribute) { // No annotations it seems... server is lying to us? 0114 QMap<QByteArray, QByteArray> annotations = annotationsAttribute->annotations(); 0115 qCDebug(IMAPRESOURCE_LOG) << "All annotations: " << annotations; 0116 0117 const auto annotationKeys{annotations.keys()}; 0118 for (const QByteArray &entry : annotationKeys) { 0119 auto job = new KIMAP::SetMetaDataJob(session); 0120 if (serverCapabilities().contains(QLatin1StringView("METADATA"))) { 0121 job->setServerCapability(KIMAP::MetaDataJobBase::Metadata); 0122 } else { 0123 job->setServerCapability(KIMAP::MetaDataJobBase::Annotatemore); 0124 } 0125 job->setMailBox(mailBoxForCollection(collection())); 0126 0127 if (!entry.startsWith("/shared") && !entry.startsWith("/private")) { 0128 // Support for legacy annotations that don't include the prefix 0129 job->addMetaData(QByteArray("/shared") + entry, annotations[entry]); 0130 } else { 0131 job->addMetaData(entry, annotations[entry]); 0132 } 0133 0134 qCDebug(IMAPRESOURCE_LOG) << "Job got entry:" << entry << "value:" << annotations[entry]; 0135 0136 connect(job, &KIMAP::SetMetaDataJob::result, this, &ChangeCollectionTask::onSetMetaDataDone); 0137 0138 job->start(); 0139 0140 m_pendingJobs++; 0141 } 0142 } 0143 } 0144 0145 if (parts().contains("imapacl")) { 0146 Akonadi::Collection c = collection(); 0147 auto aclAttribute = c.attribute<Akonadi::ImapAclAttribute>(); 0148 0149 if (aclAttribute) { 0150 const QMap<QByteArray, KIMAP::Acl::Rights> rights = aclAttribute->rights(); 0151 const QMap<QByteArray, KIMAP::Acl::Rights> oldRights = aclAttribute->oldRights(); 0152 const QList<QByteArray> oldIds = oldRights.keys(); 0153 const QList<QByteArray> ids = rights.keys(); 0154 0155 // remove all ACL entries that have been deleted 0156 for (const QByteArray &oldId : oldIds) { 0157 if (!ids.contains(oldId)) { 0158 auto job = new KIMAP::SetAclJob(session); 0159 job->setMailBox(mailBoxForCollection(collection())); 0160 job->setIdentifier(oldId); 0161 job->setRights(KIMAP::SetAclJob::Remove, oldRights[oldId]); 0162 0163 connect(job, &KIMAP::SetAclJob::result, this, &ChangeCollectionTask::onSetAclDone); 0164 0165 job->start(); 0166 0167 m_pendingJobs++; 0168 } 0169 } 0170 0171 for (const QByteArray &id : ids) { 0172 auto job = new KIMAP::SetAclJob(session); 0173 job->setMailBox(mailBoxForCollection(collection())); 0174 job->setIdentifier(id); 0175 job->setRights(KIMAP::SetAclJob::Change, rights[id]); 0176 0177 connect(job, &KIMAP::SetAclJob::result, this, &ChangeCollectionTask::onSetAclDone); 0178 0179 job->start(); 0180 0181 m_pendingJobs++; 0182 } 0183 } 0184 } 0185 0186 // Check if we need to rename the mailbox 0187 // This one goes last on purpose, we don't want the previous jobs 0188 // we triggered to act on the wrong mailbox name 0189 if (parts().contains("NAME")) { 0190 const QChar separator = separatorCharacter(); 0191 m_collection.setName(m_collection.name().remove(separator)); 0192 m_collection.setRemoteId(separator + m_collection.name()); 0193 0194 const QString oldMailBox = mailBoxForCollection(collection()); 0195 const QString newMailBox = mailBoxForCollection(m_collection); 0196 0197 if (oldMailBox != newMailBox) { 0198 auto renameJob = new KIMAP::RenameJob(session); 0199 renameJob->setSourceMailBox(oldMailBox); 0200 renameJob->setDestinationMailBox(newMailBox); 0201 connect(renameJob, &KIMAP::RenameJob::result, this, &ChangeCollectionTask::onRenameDone); 0202 0203 renameJob->start(); 0204 0205 m_pendingJobs++; 0206 } 0207 } 0208 0209 if (m_syncEnabledState && isSubscriptionEnabled() && parts().contains("ENABLED")) { 0210 if (collection().enabled()) { 0211 auto job = new KIMAP::SubscribeJob(session); 0212 job->setMailBox(mailBoxForCollection(collection())); 0213 connect(job, &KIMAP::SubscribeJob::result, this, &ChangeCollectionTask::onSubscribeDone); 0214 job->start(); 0215 } else { 0216 auto job = new KIMAP::UnsubscribeJob(session); 0217 job->setMailBox(mailBoxForCollection(collection())); 0218 connect(job, &KIMAP::UnsubscribeJob::result, this, &ChangeCollectionTask::onSubscribeDone); 0219 job->start(); 0220 } 0221 m_pendingJobs++; 0222 } 0223 0224 // we scheduled no change on the server side, probably we got only 0225 // unsupported part, so just declare the task done 0226 if (m_pendingJobs == 0) { 0227 changeCommitted(collection()); 0228 } 0229 } 0230 0231 void ChangeCollectionTask::onRenameDone(KJob *job) 0232 { 0233 if (job->error()) { 0234 const QString prevRid = collection().remoteId(); 0235 Q_ASSERT(!prevRid.isEmpty()); 0236 0237 emitWarning(i18n("Failed to rename the folder, restoring folder list.")); 0238 0239 m_collection.setName(prevRid.mid(1)); 0240 m_collection.setRemoteId(prevRid); 0241 0242 endTaskIfNeeded(); 0243 } else { 0244 auto renameJob = static_cast<KIMAP::RenameJob *>(job); 0245 auto subscribeJob = new KIMAP::SubscribeJob(renameJob->session()); 0246 subscribeJob->setMailBox(renameJob->destinationMailBox()); 0247 connect(subscribeJob, &KIMAP::SubscribeJob::result, this, &ChangeCollectionTask::onSubscribeDone); 0248 subscribeJob->start(); 0249 } 0250 } 0251 0252 void ChangeCollectionTask::onSubscribeDone(KJob *job) 0253 { 0254 if (job->error() && isSubscriptionEnabled()) { 0255 emitWarning( 0256 i18n("Failed to subscribe to the renamed folder '%1' on the IMAP server. " 0257 "It will disappear on next sync. Use the subscription dialog to overcome that", 0258 m_collection.name())); 0259 } 0260 0261 endTaskIfNeeded(); 0262 } 0263 0264 void ChangeCollectionTask::onSetAclDone(KJob *job) 0265 { 0266 if (job->error()) { 0267 emitWarning(i18n("Failed to write some ACLs for '%1' on the IMAP server. %2", collection().name(), job->errorText())); 0268 } 0269 0270 endTaskIfNeeded(); 0271 } 0272 0273 void ChangeCollectionTask::onSetMetaDataDone(KJob *job) 0274 { 0275 if (job->error()) { 0276 emitWarning(i18n("Failed to write some annotations for '%1' on the IMAP server. %2", collection().name(), job->errorText())); 0277 } 0278 0279 endTaskIfNeeded(); 0280 } 0281 0282 void ChangeCollectionTask::endTaskIfNeeded() 0283 { 0284 if (--m_pendingJobs == 0) { 0285 // the others have ended, we're done, the next one can go 0286 changeCommitted(m_collection); 0287 } 0288 } 0289 0290 #include "moc_changecollectiontask.cpp"