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"