File indexing completed on 2024-12-22 05:00:56

0001 /*
0002    SPDX-FileCopyrightText: 2018 Daniel Vrátil <dvratil@kde.org>
0003 
0004    SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "unifiedmailboxagent.h"
0008 #include "common.h"
0009 #include "settings.h"
0010 #include "settingsdialog.h"
0011 #include "unifiedmailbox.h"
0012 #include "unifiedmailboxagent_debug.h"
0013 #include "unifiedmailboxagentadaptor.h"
0014 
0015 #include <Akonadi/ChangeRecorder>
0016 #include <Akonadi/CollectionDeleteJob>
0017 #include <Akonadi/CollectionFetchJob>
0018 #include <Akonadi/CollectionFetchScope>
0019 #include <Akonadi/EntityDisplayAttribute>
0020 #include <Akonadi/ItemFetchJob>
0021 #include <Akonadi/ItemFetchScope>
0022 #include <Akonadi/LinkJob>
0023 #include <Akonadi/ServerManager>
0024 #include <Akonadi/Session>
0025 #include <Akonadi/SpecialCollectionAttribute>
0026 #include <Akonadi/UnlinkJob>
0027 
0028 #include <KIdentityManagementCore/Identity>
0029 #include <KIdentityManagementCore/IdentityManager>
0030 
0031 #include <KLocalizedString>
0032 #include <QDBusConnection>
0033 
0034 #include <QPointer>
0035 #include <QTimer>
0036 
0037 #include <chrono>
0038 #include <memory>
0039 #include <unordered_set>
0040 
0041 UnifiedMailboxAgent::UnifiedMailboxAgent(const QString &id)
0042     : Akonadi::ResourceBase(id)
0043     , mBoxManager(config())
0044 {
0045     setAgentName(i18n("Unified Mailboxes"));
0046 
0047     new UnifiedMailboxAgentAdaptor(this);
0048     QDBusConnection::sessionBus().registerObject(QStringLiteral("/UnifiedMailboxAgent"), this, QDBusConnection::ExportAdaptors);
0049     const auto service = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Resource, identifier());
0050     QDBusConnection::sessionBus().registerService(service);
0051 
0052     connect(&mBoxManager, &UnifiedMailboxManager::updateBox, this, [this](const UnifiedMailbox *box) {
0053         if (box->collectionId() <= -1) {
0054             qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "MailboxManager wants us to update Box but does not have its CollectionId!?";
0055             return;
0056         }
0057 
0058         // Schedule collection sync for the box
0059         synchronizeCollection(box->collectionId());
0060     });
0061 
0062     auto &ifs = changeRecorder()->itemFetchScope();
0063     ifs.setAncestorRetrieval(Akonadi::ItemFetchScope::None);
0064     ifs.setCacheOnly(true);
0065     ifs.fetchFullPayload(false);
0066 
0067     if (Settings::self()->enabled()) {
0068         QTimer::singleShot(0, this, &UnifiedMailboxAgent::delayedInit);
0069     }
0070 }
0071 
0072 void UnifiedMailboxAgent::configure(WId windowId)
0073 {
0074     QPointer<UnifiedMailboxAgent> agent(this);
0075     if (agent) {
0076         SettingsDialog(config(), mBoxManager, windowId).exec();
0077         synchronize();
0078         Q_EMIT configurationDialogAccepted();
0079     }
0080 }
0081 
0082 void UnifiedMailboxAgent::delayedInit()
0083 {
0084     qCDebug(UNIFIEDMAILBOXAGENT_LOG) << "delayed init";
0085 
0086     fixSpecialCollections();
0087     mBoxManager.loadBoxes([this]() {
0088         // boxes loaded, let's sync up
0089         synchronize();
0090     });
0091 }
0092 
0093 bool UnifiedMailboxAgent::enabledAgent() const
0094 {
0095     return Settings::self()->enabled();
0096 }
0097 
0098 void UnifiedMailboxAgent::setEnableAgent(bool enabled)
0099 {
0100     if (enabled != Settings::self()->enabled()) {
0101         Settings::self()->setEnabled(enabled);
0102         Settings::self()->save();
0103         if (!enabled) {
0104             setOnline(false);
0105             auto fetch = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this);
0106             fetch->fetchScope().setResource(identifier());
0107             connect(fetch, &Akonadi::CollectionFetchJob::collectionsReceived, this, [this](const Akonadi::Collection::List &cols) {
0108                 for (const auto &col : cols) {
0109                     new Akonadi::CollectionDeleteJob(col, this);
0110                 }
0111             });
0112         } else {
0113             setOnline(true);
0114             delayedInit();
0115         }
0116     }
0117 }
0118 
0119 void UnifiedMailboxAgent::retrieveCollections()
0120 {
0121     if (!Settings::self()->enabled()) {
0122         collectionsRetrieved({});
0123         return;
0124     }
0125 
0126     Akonadi::Collection::List collections;
0127 
0128     Akonadi::Collection topLevel;
0129     topLevel.setName(identifier());
0130     topLevel.setRemoteId(identifier());
0131     topLevel.setParentCollection(Akonadi::Collection::root());
0132     topLevel.setContentMimeTypes({Akonadi::Collection::mimeType()});
0133     topLevel.setRights(Akonadi::Collection::ReadOnly);
0134     auto topLevelDisplayAttr = topLevel.attribute<Akonadi::EntityDisplayAttribute>(Akonadi::Collection::AddIfMissing);
0135     topLevelDisplayAttr->setDisplayName(i18n("Unified Mailboxes"));
0136     topLevelDisplayAttr->setActiveIconName(QStringLiteral("globe"));
0137     collections.push_back(topLevel);
0138 
0139     for (const auto &boxIt : mBoxManager) {
0140         const auto &box = boxIt.second;
0141         Akonadi::Collection col;
0142         col.setName(box->id());
0143         col.setRemoteId(box->id());
0144         col.setParentCollection(topLevel);
0145         col.setContentMimeTypes({Common::MailMimeType});
0146         col.setRights(Akonadi::Collection::CanChangeItem | Akonadi::Collection::CanDeleteItem);
0147         col.setVirtual(true);
0148         auto displayAttr = col.attribute<Akonadi::EntityDisplayAttribute>(Akonadi::Collection::AddIfMissing);
0149         displayAttr->setDisplayName(box->name());
0150         displayAttr->setIconName(box->icon());
0151         collections.push_back(std::move(col));
0152     }
0153 
0154     collectionsRetrieved(std::move(collections));
0155 
0156     // Add mapping between boxes and collections
0157     mBoxManager.discoverBoxCollections();
0158 }
0159 
0160 void UnifiedMailboxAgent::retrieveItems(const Akonadi::Collection &c)
0161 {
0162     if (!Settings::self()->enabled()) {
0163         itemsRetrieved({});
0164         return;
0165     }
0166 
0167     // First check that we have all Items from all source collections
0168     Q_EMIT status(Running, i18n("Synchronizing unified mailbox %1", c.displayName()));
0169     const auto unifiedBox = mBoxManager.unifiedMailboxFromCollection(c);
0170     if (!unifiedBox) {
0171         qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Failed to retrieve box ID for collection " << c.id();
0172         itemsRetrievedIncremental({}, {}); // fake incremental retrieval
0173         return;
0174     }
0175 
0176     const auto sources = unifiedBox->sourceCollections();
0177     for (auto source : sources) {
0178         auto fetch = new Akonadi::ItemFetchJob(Akonadi::Collection(source), this);
0179         fetch->setDeliveryOption(Akonadi::ItemFetchJob::EmitItemsInBatches);
0180         fetch->fetchScope().setFetchVirtualReferences(true);
0181         fetch->fetchScope().setCacheOnly(true);
0182         connect(fetch, &Akonadi::ItemFetchJob::itemsReceived, this, [this, c](const Akonadi::Item::List &items) {
0183             Akonadi::Item::List toLink;
0184             std::copy_if(items.cbegin(), items.cend(), std::back_inserter(toLink), [&c](const Akonadi::Item &item) {
0185                 return !item.virtualReferences().contains(c);
0186             });
0187             if (!toLink.isEmpty()) {
0188                 new Akonadi::LinkJob(c, toLink, this);
0189             }
0190         });
0191     }
0192 
0193     auto fetch = new Akonadi::ItemFetchJob(c, this);
0194     fetch->setDeliveryOption(Akonadi::ItemFetchJob::EmitItemsInBatches);
0195     fetch->fetchScope().setCacheOnly(true);
0196     fetch->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
0197     connect(fetch, &Akonadi::ItemFetchJob::itemsReceived, this, [this, unifiedBox, c](const Akonadi::Item::List &items) {
0198         Akonadi::Item::List toUnlink;
0199         std::copy_if(items.cbegin(), items.cend(), std::back_inserter(toUnlink), [&unifiedBox](const Akonadi::Item &item) {
0200             return !unifiedBox->sourceCollections().contains(item.storageCollectionId());
0201         });
0202         if (!toUnlink.isEmpty()) {
0203             new Akonadi::UnlinkJob(c, toUnlink, this);
0204         }
0205     });
0206     connect(fetch, &Akonadi::ItemFetchJob::result, this, [this]() {
0207         itemsRetrievedIncremental({}, {}); // fake incremental retrieval
0208     });
0209 }
0210 
0211 bool UnifiedMailboxAgent::retrieveItems(const Akonadi::Item::List &items, const QSet<QByteArray> &parts)
0212 {
0213     Q_UNUSED(items)
0214     Q_UNUSED(parts)
0215     qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "retrieveItems() called but we can't own any items! This is a bug in Akonadi";
0216     return false;
0217 }
0218 
0219 bool UnifiedMailboxAgent::retrieveItem(const Akonadi::Item &item, const QSet<QByteArray> &parts)
0220 {
0221     // This method should never be called by Akonadi
0222     Q_UNUSED(parts)
0223     qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "retrieveItem() for item" << item.id() << "called but we can't own any items! This is a bug in Akonadi";
0224     return false;
0225 }
0226 
0227 void UnifiedMailboxAgent::fixSpecialCollection(const QString &colId, Akonadi::SpecialMailCollections::Type type)
0228 {
0229     if (colId.isEmpty()) {
0230         return;
0231     }
0232     const auto id = colId.toLongLong();
0233     // SpecialMailCollection requires the Collection to have a Resource set as well, so
0234     // we have to retrieve it first.
0235     connect(new Akonadi::CollectionFetchJob(Akonadi::Collection(id), Akonadi::CollectionFetchJob::Base, this),
0236             &Akonadi::CollectionFetchJob::collectionsReceived,
0237             this,
0238             [type](const Akonadi::Collection::List &cols) {
0239                 if (cols.count() != 1) {
0240                     qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Identity special collection retrieval did not find a valid collection";
0241                     return;
0242                 }
0243                 Akonadi::SpecialMailCollections::self()->registerCollection(type, cols.first());
0244             });
0245 }
0246 
0247 void UnifiedMailboxAgent::fixSpecialCollections()
0248 {
0249     // This is a tiny hack to assign proper SpecialCollectionAttribute to special collections
0250     // assigned trough Identities. This should happen automatically in KMail when user changes
0251     // the special collections on the identity page, but until recent master (2018-07-24) this
0252     // wasn't the case and there's no automatic migration, so we need to fix up manually here.
0253 
0254     if (Settings::self()->fixedSpecialCollections()) {
0255         return;
0256     }
0257 
0258     qCDebug(UNIFIEDMAILBOXAGENT_LOG) << "Fixing special collections assigned from Identities";
0259 
0260     for (const auto &identity : *KIdentityManagementCore::IdentityManager::self()) {
0261         if (!identity.disabledFcc()) {
0262             fixSpecialCollection(identity.fcc(), Akonadi::SpecialMailCollections::SentMail);
0263         }
0264         fixSpecialCollection(identity.drafts(), Akonadi::SpecialMailCollections::Drafts);
0265         fixSpecialCollection(identity.templates(), Akonadi::SpecialMailCollections::Templates);
0266     }
0267 
0268     Settings::self()->setFixedSpecialCollections(true);
0269 }
0270 
0271 AKONADI_RESOURCE_MAIN(UnifiedMailboxAgent)
0272 
0273 #include "moc_unifiedmailboxagent.cpp"