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"