Changeset View
Changeset View
Standalone View
Standalone View
agents/unifiedmailboxagent/unifiedmailboxagent.cpp
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | Copyright (C) 2018 Daniel Vrátil <dvratil@kde.org> | ||||
3 | | ||||
4 | This program is free software; you can redistribute it and/or | ||||
5 | modify it under the terms of the GNU General Public | ||||
6 | License as published by the Free Software Foundation; either | ||||
7 | version 2 of the License, or (at your option) any later version. | ||||
8 | | ||||
9 | This program is distributed in the hope that it will be useful, | ||||
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||
12 | General Public License for more details. | ||||
13 | | ||||
14 | You should have received a copy of the GNU General Public License | ||||
15 | along with this program; see the file COPYING. If not, write to | ||||
16 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
17 | Boston, MA 02110-1301, USA. | ||||
18 | */ | ||||
19 | | ||||
20 | #include "unifiedmailboxagent.h" | ||||
21 | #include "unifiedmailbox.h" | ||||
22 | #include "unifiedmailboxagent_debug.h" | ||||
23 | #include "unifiedmailboxagentadaptor.h" | ||||
24 | #include "settingsdialog.h" | ||||
25 | #include "settings.h" | ||||
26 | #include "common.h" | ||||
27 | | ||||
28 | #include <AkonadiCore/ChangeRecorder> | ||||
29 | #include <AkonadiCore/Session> | ||||
30 | #include <AkonadiCore/CollectionFetchJob> | ||||
31 | #include <AkonadiCore/CollectionFetchScope> | ||||
32 | #include <AkonadiCore/CollectionDeleteJob> | ||||
33 | #include <AkonadiCore/SpecialCollectionAttribute> | ||||
34 | #include <AkonadiCore/EntityDisplayAttribute> | ||||
35 | #include <AkonadiCore/ItemFetchScope> | ||||
36 | #include <AkonadiCore/ItemFetchJob> | ||||
37 | #include <AkonadiCore/LinkJob> | ||||
38 | #include <AkonadiCore/UnlinkJob> | ||||
39 | #include <AkonadiCore/ServerManager> | ||||
40 | #include <Akonadi/KMime/SpecialMailCollections> | ||||
41 | | ||||
42 | #include <KIdentityManagement/IdentityManager> | ||||
43 | #include <KIdentityManagement/Identity> | ||||
44 | | ||||
45 | #include <KLocalizedString> | ||||
46 | #include <KDBusConnectionPool> | ||||
47 | | ||||
48 | #include <QPointer> | ||||
49 | #include <QTimer> | ||||
50 | | ||||
51 | #include <memory> | ||||
52 | #include <unordered_set> | ||||
53 | #include <chrono> | ||||
54 | | ||||
55 | | ||||
56 | UnifiedMailboxAgent::UnifiedMailboxAgent(const QString &id) | ||||
57 | : Akonadi::ResourceBase(id) | ||||
58 | , mBoxManager(config()) | ||||
59 | { | ||||
60 | setAgentName(i18n("Unified Mailboxes")); | ||||
61 | | ||||
62 | new UnifiedMailboxAgentAdaptor(this); | ||||
63 | KDBusConnectionPool::threadConnection().registerObject(QStringLiteral("/UnifiedMailboxAgent"), this, QDBusConnection::ExportAdaptors); | ||||
64 | const auto service = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Resource, identifier()); | ||||
65 | KDBusConnectionPool::threadConnection().registerService(service); | ||||
66 | | ||||
67 | connect(&mBoxManager, &UnifiedMailboxManager::updateBox, | ||||
68 | this, [this](const UnifiedMailbox *box) { | ||||
69 | if (!box->collectionId()) { | ||||
70 | qCWarning(agent_log) << "MailboxManager wants us to update Box but does not have its CollectionId!?"; | ||||
71 | return; | ||||
72 | } | ||||
73 | | ||||
74 | // Schedule collection sync for the box | ||||
75 | synchronizeCollection(box->collectionId().value()); | ||||
76 | }); | ||||
77 | | ||||
78 | auto &ifs = changeRecorder()->itemFetchScope(); | ||||
79 | ifs.setAncestorRetrieval(Akonadi::ItemFetchScope::None); | ||||
80 | ifs.setCacheOnly(true); | ||||
81 | ifs.fetchFullPayload(false); | ||||
82 | | ||||
83 | if (Settings::self()->enabled()) { | ||||
84 | QTimer::singleShot(0, this, &UnifiedMailboxAgent::delayedInit); | ||||
85 | } | ||||
86 | } | ||||
87 | | ||||
88 | void UnifiedMailboxAgent::configure(WId windowId) | ||||
89 | { | ||||
90 | QPointer<UnifiedMailboxAgent> agent(this); | ||||
91 | if (SettingsDialog(config(), mBoxManager, windowId).exec() && agent) { | ||||
92 | mBoxManager.saveBoxes(); | ||||
93 | synchronize(); | ||||
94 | Q_EMIT configurationDialogAccepted(); | ||||
95 | } else { | ||||
96 | mBoxManager.loadBoxes(); | ||||
97 | } | ||||
98 | } | ||||
99 | | ||||
100 | void UnifiedMailboxAgent::delayedInit() | ||||
101 | { | ||||
102 | qCDebug(agent_log) << "delayed init"; | ||||
103 | | ||||
104 | fixSpecialCollections(); | ||||
105 | mBoxManager.loadBoxes([this]() { | ||||
106 | // boxes loaded, let's sync up | ||||
107 | synchronize(); | ||||
108 | }); | ||||
109 | } | ||||
110 | | ||||
111 | | ||||
112 | bool UnifiedMailboxAgent::enabledAgent() const | ||||
113 | { | ||||
114 | return Settings::self()->enabled(); | ||||
115 | } | ||||
116 | | ||||
117 | void UnifiedMailboxAgent::setEnableAgent(bool enabled) | ||||
118 | { | ||||
119 | if (enabled != Settings::self()->enabled()) { | ||||
120 | Settings::self()->setEnabled(enabled); | ||||
121 | Settings::self()->save(); | ||||
122 | if (!enabled) { | ||||
123 | setOnline(false); | ||||
124 | auto fetch = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this); | ||||
125 | fetch->fetchScope().setResource(identifier()); | ||||
126 | connect(fetch, &Akonadi::CollectionFetchJob::collectionsReceived, | ||||
127 | this, [this](const Akonadi::Collection::List &cols) { | ||||
128 | for (const auto &col : cols) { | ||||
129 | new Akonadi::CollectionDeleteJob(col, this); | ||||
130 | } | ||||
131 | }); | ||||
132 | } else { | ||||
133 | setOnline(true); | ||||
134 | delayedInit(); | ||||
135 | } | ||||
136 | } | ||||
137 | } | ||||
138 | | ||||
139 | | ||||
140 | void UnifiedMailboxAgent::retrieveCollections() | ||||
141 | { | ||||
142 | if (!Settings::self()->enabled()) { | ||||
143 | collectionsRetrieved({}); | ||||
144 | return; | ||||
145 | } | ||||
146 | | ||||
147 | Akonadi::Collection::List collections; | ||||
148 | | ||||
149 | Akonadi::Collection topLevel; | ||||
150 | topLevel.setName(identifier()); | ||||
151 | topLevel.setRemoteId(identifier()); | ||||
152 | topLevel.setParentCollection(Akonadi::Collection::root()); | ||||
153 | topLevel.setContentMimeTypes({Akonadi::Collection::mimeType()}); | ||||
154 | topLevel.setRights(Akonadi::Collection::ReadOnly); | ||||
155 | auto displayAttr = topLevel.attribute<Akonadi::EntityDisplayAttribute>(Akonadi::Collection::AddIfMissing); | ||||
156 | displayAttr->setDisplayName(i18n("Unified Mailboxes")); | ||||
157 | displayAttr->setActiveIconName(QStringLiteral("globe")); | ||||
158 | collections.push_back(topLevel); | ||||
159 | | ||||
160 | for (const auto &boxIt : mBoxManager) { | ||||
161 | const auto &box = boxIt.second; | ||||
162 | Akonadi::Collection col; | ||||
163 | col.setName(box->id()); | ||||
164 | col.setRemoteId(box->id()); | ||||
165 | col.setParentCollection(topLevel); | ||||
166 | col.setContentMimeTypes({Common::MailMimeType}); | ||||
167 | col.setRights(Akonadi::Collection::CanChangeItem | Akonadi::Collection::CanDeleteItem); | ||||
168 | col.setVirtual(true); | ||||
169 | auto displayAttr = col.attribute<Akonadi::EntityDisplayAttribute>(Akonadi::Collection::AddIfMissing); | ||||
170 | displayAttr->setDisplayName(box->name()); | ||||
171 | displayAttr->setIconName(box->icon()); | ||||
172 | collections.push_back(std::move(col)); | ||||
173 | } | ||||
174 | | ||||
175 | collectionsRetrieved(std::move(collections)); | ||||
176 | | ||||
177 | // Add mapping between boxes and collections | ||||
178 | mBoxManager.discoverBoxCollections(); | ||||
179 | } | ||||
180 | | ||||
181 | void UnifiedMailboxAgent::retrieveItems(const Akonadi::Collection &c) | ||||
182 | { | ||||
183 | if (!Settings::self()->enabled()) { | ||||
184 | itemsRetrieved({}); | ||||
185 | return; | ||||
186 | } | ||||
187 | | ||||
188 | // First check that we have all Items from all source collections | ||||
189 | Q_EMIT status(Running, i18n("Synchronizing unified mailbox %1", c.displayName())); | ||||
190 | const auto unifiedBox = mBoxManager.unifiedMailboxFromCollection(c); | ||||
191 | if (!unifiedBox) { | ||||
192 | qCWarning(agent_log) << "Failed to retrieve box ID for collection " << c.id(); | ||||
193 | itemsRetrievedIncremental({}, {}); // fake incremental retrieval | ||||
194 | return; | ||||
195 | } | ||||
196 | | ||||
197 | const auto lastSeenEvent = QDateTime::fromSecsSinceEpoch(c.remoteRevision().toLongLong()); | ||||
198 | | ||||
199 | const auto sources = unifiedBox->sourceCollections(); | ||||
200 | for (auto source : sources) { | ||||
201 | auto fetch = new Akonadi::ItemFetchJob(Akonadi::Collection(source), this); | ||||
202 | fetch->setDeliveryOption(Akonadi::ItemFetchJob::EmitItemsInBatches); | ||||
203 | fetch->fetchScope().setFetchVirtualReferences(true); | ||||
204 | fetch->fetchScope().setCacheOnly(true); | ||||
205 | connect(fetch, &Akonadi::ItemFetchJob::itemsReceived, | ||||
206 | this, [this, c](const Akonadi::Item::List &items) { | ||||
207 | Akonadi::Item::List toLink; | ||||
208 | std::copy_if(items.cbegin(), items.cend(), std::back_inserter(toLink), | ||||
209 | [&c](const Akonadi::Item &item) { | ||||
210 | return !item.virtualReferences().contains(c); | ||||
211 | }); | ||||
212 | if (!toLink.isEmpty()) { | ||||
213 | new Akonadi::LinkJob(c, toLink, this); | ||||
214 | } | ||||
215 | }); | ||||
216 | } | ||||
217 | | ||||
218 | auto fetch = new Akonadi::ItemFetchJob(c, this); | ||||
219 | fetch->setDeliveryOption(Akonadi::ItemFetchJob::EmitItemsInBatches); | ||||
220 | fetch->fetchScope().setCacheOnly(true); | ||||
221 | fetch->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); | ||||
222 | connect(fetch, &Akonadi::ItemFetchJob::itemsReceived, | ||||
223 | this, [this, unifiedBox, c](const Akonadi::Item::List &items) { | ||||
224 | Akonadi::Item::List toUnlink; | ||||
225 | std::copy_if(items.cbegin(), items.cend(), std::back_inserter(toUnlink), | ||||
226 | [&unifiedBox](const Akonadi::Item &item) { | ||||
227 | return !unifiedBox->sourceCollections().contains(item.storageCollectionId()); | ||||
228 | }); | ||||
229 | if (!toUnlink.isEmpty()) { | ||||
230 | new Akonadi::UnlinkJob(c, toUnlink, this); | ||||
231 | } | ||||
232 | }); | ||||
233 | connect(fetch, &Akonadi::ItemFetchJob::result, | ||||
234 | this, [this]() { | ||||
235 | itemsRetrievedIncremental({}, {}); // fake incremental retrieval | ||||
236 | }); | ||||
237 | } | ||||
238 | | ||||
239 | bool UnifiedMailboxAgent::retrieveItem(const Akonadi::Item &item, const QSet<QByteArray> &parts) | ||||
240 | { | ||||
241 | // This method should never be called by Akonadi | ||||
242 | Q_UNUSED(parts); | ||||
243 | qCWarning(agent_log) << "retrieveItem() for item" << item.id() << "called but we can't own any items! This is a bug in Akonadi"; | ||||
244 | return false; | ||||
245 | } | ||||
246 | | ||||
247 | | ||||
248 | void UnifiedMailboxAgent::fixSpecialCollection(const QString &colId, Akonadi::SpecialMailCollections::Type type) | ||||
249 | { | ||||
250 | if (colId.isEmpty()) { | ||||
251 | return; | ||||
252 | } | ||||
253 | const auto id = colId.toLongLong(); | ||||
254 | // SpecialMailCollection requires the Collection to have a Resource set as well, so | ||||
255 | // we have to retrieve it first. | ||||
256 | connect(new Akonadi::CollectionFetchJob(Akonadi::Collection(id), Akonadi::CollectionFetchJob::Base, this), | ||||
257 | &Akonadi::CollectionFetchJob::collectionsReceived, | ||||
258 | this, [type](const Akonadi::Collection::List &cols) { | ||||
259 | if (cols.count() != 1) { | ||||
260 | qCWarning(agent_log) << "Identity special collection retrieval did not find a valid collection"; | ||||
261 | return; | ||||
262 | } | ||||
263 | Akonadi::SpecialMailCollections::self()->registerCollection(type, cols.first()); | ||||
264 | }); | ||||
265 | } | ||||
266 | | ||||
267 | void UnifiedMailboxAgent::fixSpecialCollections() | ||||
268 | { | ||||
269 | // This is a tiny hack to assign proper SpecialCollectionAttribute to special collections | ||||
270 | // assigned trough Identities. This should happen automatically in KMail when user changes | ||||
271 | // the special collections on the identity page, but until recent master (2018-07-24) this | ||||
272 | // wasn't the case and there's no automatic migration, so we need to fix up manually here. | ||||
273 | | ||||
274 | if (Settings::self()->fixedSpecialCollections()) { | ||||
275 | return; | ||||
276 | } | ||||
277 | | ||||
278 | qCDebug(agent_log) << "Fixing special collections assigned from Identities"; | ||||
279 | | ||||
280 | for (const auto &identity : *KIdentityManagement::IdentityManager::self()) { | ||||
281 | if (!identity.disabledFcc()) { | ||||
282 | fixSpecialCollection(identity.fcc(), Akonadi::SpecialMailCollections::SentMail); | ||||
283 | } | ||||
284 | fixSpecialCollection(identity.drafts(), Akonadi::SpecialMailCollections::Drafts); | ||||
285 | fixSpecialCollection(identity.templates(), Akonadi::SpecialMailCollections::Templates); | ||||
286 | } | ||||
287 | | ||||
288 | Settings::self()->setFixedSpecialCollections(true); | ||||
289 | } | ||||
290 | | ||||
291 | | ||||
292 | AKONADI_RESOURCE_MAIN(UnifiedMailboxAgent) |