1 /*
2 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
3 SPDX-FileContributor: Kevin Ottens <kevin@kdab.com>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
8 #include "resourcetask.h"
9
10 #include <Akonadi/KMime/MessageFlags>
11
12 #include "imapresource_debug.h"
13 #include "imapresource_trace.h"
14 #include <KLocalizedString>
15
16 #include "collectionflagsattribute.h"
17 #include "imapflags.h"
18 #include "sessionpool.h"
19 #include <imapaclattribute.h>
20
ResourceTask(ActionIfNoSession action,ResourceStateInterface::Ptr resource,QObject * parent)21 ResourceTask::ResourceTask(ActionIfNoSession action, ResourceStateInterface::Ptr resource, QObject *parent)
22 : QObject(parent)
23 , m_pool(nullptr)
24 , m_sessionRequestId(0)
25 , m_session(nullptr)
26 , m_actionIfNoSession(action)
27 , m_resource(resource)
28 , mCancelled(false)
29 {
30 }
31
~ResourceTask()32 ResourceTask::~ResourceTask()
33 {
34 if (m_pool) {
35 if (m_sessionRequestId) {
36 m_pool->cancelSessionRequest(m_sessionRequestId);
37 }
38 if (m_session) {
39 m_pool->releaseSession(m_session);
40 }
41 }
42 }
43
start(SessionPool * pool)44 void ResourceTask::start(SessionPool *pool)
45 {
46 qCDebug(IMAPRESOURCE_TRACE) << metaObject()->className();
47 m_pool = pool;
48 connect(m_pool, &SessionPool::sessionRequestDone, this, &ResourceTask::onSessionRequested);
49
50 m_sessionRequestId = m_pool->requestSession();
51
52 if (m_sessionRequestId <= 0) {
53 m_sessionRequestId = 0;
54
55 abortTask(QString());
56 // In this case we were likely disconnected, try to get the resource online
57 m_resource->scheduleConnectionAttempt();
58 }
59 }
60
abortTask(const QString & errorString)61 void ResourceTask::abortTask(const QString &errorString)
62 {
63 if (!mCancelled) {
64 mCancelled = true;
65
66 switch (m_actionIfNoSession) {
67 case CancelIfNoSession:
68 qCDebug(IMAPRESOURCE_LOG) << "Cancelling this request.";
69 m_resource->cancelTask(errorString.isEmpty() ? i18n("Unable to connect to the IMAP server.") : errorString);
70 break;
71
72 case DeferIfNoSession:
73 qCDebug(IMAPRESOURCE_LOG) << "Deferring this request.";
74 m_resource->deferTask();
75 break;
76 }
77 }
78 deleteLater();
79 }
80
onSessionRequested(qint64 requestId,KIMAP::Session * session,int errorCode,const QString & errorString)81 void ResourceTask::onSessionRequested(qint64 requestId, KIMAP::Session *session, int errorCode, const QString &errorString)
82 {
83 if (requestId != m_sessionRequestId) {
84 // Not for us, ignore
85 return;
86 }
87
88 disconnect(m_pool, &SessionPool::sessionRequestDone, this, &ResourceTask::onSessionRequested);
89 m_sessionRequestId = 0;
90
91 if (errorCode != SessionPool::NoError) {
92 abortTask(errorString);
93 return;
94 }
95
96 m_session = session;
97
98 connect(m_pool, &SessionPool::connectionLost, this, &ResourceTask::onConnectionLost);
99 connect(m_pool, &SessionPool::disconnectDone, this, &ResourceTask::onPoolDisconnect);
100
101 qCDebug(IMAPRESOURCE_TRACE) << "starting: " << metaObject()->className();
102 doStart(m_session);
103 }
104
onConnectionLost(KIMAP::Session * session)105 void ResourceTask::onConnectionLost(KIMAP::Session *session)
106 {
107 if (session == m_session) {
108 // Our session becomes invalid, so get rid of
109 // the pointer, we don't need to release it once the
110 // task is done
111 m_session = nullptr;
112 qCDebug(IMAPRESOURCE_TRACE) << metaObject()->className();
113 abortTask(i18n("Connection lost"));
114 }
115 }
116
onPoolDisconnect()117 void ResourceTask::onPoolDisconnect()
118 {
119 // All the sessions in the pool we used changed,
120 // so get rid of the pointer, we don't need to
121 // release our session anymore
122 m_pool = nullptr;
123
124 qCDebug(IMAPRESOURCE_TRACE) << metaObject()->className();
125 abortTask(i18n("Connection lost"));
126 }
127
userName() const128 QString ResourceTask::userName() const
129 {
130 return m_resource->userName();
131 }
132
resourceName() const133 QString ResourceTask::resourceName() const
134 {
135 return m_resource->resourceName();
136 }
137
serverCapabilities() const138 QStringList ResourceTask::serverCapabilities() const
139 {
140 return m_resource->serverCapabilities();
141 }
142
serverNamespaces() const143 QList<KIMAP::MailBoxDescriptor> ResourceTask::serverNamespaces() const
144 {
145 return m_resource->serverNamespaces();
146 }
147
isAutomaticExpungeEnabled() const148 bool ResourceTask::isAutomaticExpungeEnabled() const
149 {
150 return m_resource->isAutomaticExpungeEnabled();
151 }
152
isSubscriptionEnabled() const153 bool ResourceTask::isSubscriptionEnabled() const
154 {
155 return m_resource->isSubscriptionEnabled();
156 }
157
isDisconnectedModeEnabled() const158 bool ResourceTask::isDisconnectedModeEnabled() const
159 {
160 return m_resource->isDisconnectedModeEnabled();
161 }
162
intervalCheckTime() const163 int ResourceTask::intervalCheckTime() const
164 {
165 return m_resource->intervalCheckTime();
166 }
167
detachCollection(const Akonadi::Collection & collection)168 static Akonadi::Collection detachCollection(const Akonadi::Collection &collection)
169 {
170 // HACK: Attributes are accessed via a const function, and the implicitly shared private pointer thus doesn't detach.
171 // We force a detach to avoid surprises. (RetrieveItemsTask used to write back the collection changes, even though the task was canceled)
172 // Once this is fixed this function can go away.
173 Akonadi::Collection col = collection;
174 col.setId(col.id());
175 return col;
176 }
177
collection() const178 Akonadi::Collection ResourceTask::collection() const
179 {
180 return detachCollection(m_resource->collection());
181 }
182
item() const183 Akonadi::Item ResourceTask::item() const
184 {
185 return m_resource->item();
186 }
187
items() const188 Akonadi::Item::List ResourceTask::items() const
189 {
190 return m_resource->items();
191 }
192
parentCollection() const193 Akonadi::Collection ResourceTask::parentCollection() const
194 {
195 return detachCollection(m_resource->parentCollection());
196 }
197
sourceCollection() const198 Akonadi::Collection ResourceTask::sourceCollection() const
199 {
200 return detachCollection(m_resource->sourceCollection());
201 }
202
targetCollection() const203 Akonadi::Collection ResourceTask::targetCollection() const
204 {
205 return detachCollection(m_resource->targetCollection());
206 }
207
parts() const208 QSet<QByteArray> ResourceTask::parts() const
209 {
210 return m_resource->parts();
211 }
212
addedFlags() const213 QSet<QByteArray> ResourceTask::addedFlags() const
214 {
215 return m_resource->addedFlags();
216 }
217
removedFlags() const218 QSet<QByteArray> ResourceTask::removedFlags() const
219 {
220 return m_resource->removedFlags();
221 }
222
rootRemoteId() const223 QString ResourceTask::rootRemoteId() const
224 {
225 return m_resource->rootRemoteId();
226 }
227
mailBoxForCollection(const Akonadi::Collection & collection) const228 QString ResourceTask::mailBoxForCollection(const Akonadi::Collection &collection) const
229 {
230 return m_resource->mailBoxForCollection(collection);
231 }
232
setIdleCollection(const Akonadi::Collection & collection)233 void ResourceTask::setIdleCollection(const Akonadi::Collection &collection)
234 {
235 if (!mCancelled) {
236 m_resource->setIdleCollection(collection);
237 }
238 }
239
applyCollectionChanges(const Akonadi::Collection & collection)240 void ResourceTask::applyCollectionChanges(const Akonadi::Collection &collection)
241 {
242 if (!mCancelled) {
243 m_resource->applyCollectionChanges(collection);
244 }
245 }
246
itemRetrieved(const Akonadi::Item & item)247 void ResourceTask::itemRetrieved(const Akonadi::Item &item)
248 {
249 if (!mCancelled) {
250 m_resource->itemRetrieved(item);
251 emitPercent(100);
252 }
253 deleteLater();
254 }
255
itemsRetrieved(const Akonadi::Item::List & items)256 void ResourceTask::itemsRetrieved(const Akonadi::Item::List &items)
257 {
258 if (!mCancelled) {
259 m_resource->itemsRetrieved(items);
260 }
261 }
262
itemsRetrievedIncremental(const Akonadi::Item::List & changed,const Akonadi::Item::List & removed)263 void ResourceTask::itemsRetrievedIncremental(const Akonadi::Item::List &changed, const Akonadi::Item::List &removed)
264 {
265 if (!mCancelled) {
266 m_resource->itemsRetrievedIncremental(changed, removed);
267 }
268 }
269
itemsRetrievalDone()270 void ResourceTask::itemsRetrievalDone()
271 {
272 if (!mCancelled) {
273 m_resource->itemsRetrievalDone();
274 }
275 deleteLater();
276 }
277
setTotalItems(int totalItems)278 void ResourceTask::setTotalItems(int totalItems)
279 {
280 if (!mCancelled) {
281 m_resource->setTotalItems(totalItems);
282 }
283 }
284
changeCommitted(const Akonadi::Item & item)285 void ResourceTask::changeCommitted(const Akonadi::Item &item)
286 {
287 if (!mCancelled) {
288 m_resource->itemChangeCommitted(item);
289 }
290 deleteLater();
291 }
292
changesCommitted(const Akonadi::Item::List & items)293 void ResourceTask::changesCommitted(const Akonadi::Item::List &items)
294 {
295 if (!mCancelled) {
296 m_resource->itemsChangesCommitted(items);
297 }
298 deleteLater();
299 }
300
searchFinished(const QVector<qint64> & result,bool isRid)301 void ResourceTask::searchFinished(const QVector<qint64> &result, bool isRid)
302 {
303 if (!mCancelled) {
304 m_resource->searchFinished(result, isRid);
305 }
306 deleteLater();
307 }
308
collectionsRetrieved(const Akonadi::Collection::List & collections)309 void ResourceTask::collectionsRetrieved(const Akonadi::Collection::List &collections)
310 {
311 if (!mCancelled) {
312 m_resource->collectionsRetrieved(collections);
313 }
314 deleteLater();
315 }
316
collectionAttributesRetrieved(const Akonadi::Collection & col)317 void ResourceTask::collectionAttributesRetrieved(const Akonadi::Collection &col)
318 {
319 if (!mCancelled) {
320 m_resource->collectionAttributesRetrieved(col);
321 }
322 deleteLater();
323 }
324
changeCommitted(const Akonadi::Collection & collection)325 void ResourceTask::changeCommitted(const Akonadi::Collection &collection)
326 {
327 if (!mCancelled) {
328 m_resource->collectionChangeCommitted(collection);
329 }
330 deleteLater();
331 }
332
changeCommitted(const Akonadi::Tag & tag)333 void ResourceTask::changeCommitted(const Akonadi::Tag &tag)
334 {
335 if (!mCancelled) {
336 m_resource->tagChangeCommitted(tag);
337 }
338 deleteLater();
339 }
340
changeProcessed()341 void ResourceTask::changeProcessed()
342 {
343 if (!mCancelled) {
344 m_resource->changeProcessed();
345 }
346 deleteLater();
347 }
348
cancelTask(const QString & errorString)349 void ResourceTask::cancelTask(const QString &errorString)
350 {
351 qCDebug(IMAPRESOURCE_LOG) << "Cancel task: " << errorString;
352 if (!mCancelled) {
353 mCancelled = true;
354 m_resource->cancelTask(errorString);
355 }
356 deleteLater();
357 }
358
deferTask()359 void ResourceTask::deferTask()
360 {
361 if (!mCancelled) {
362 mCancelled = true;
363 m_resource->deferTask();
364 }
365 deleteLater();
366 }
367
restartItemRetrieval(Akonadi::Collection::Id col)368 void ResourceTask::restartItemRetrieval(Akonadi::Collection::Id col)
369 {
370 if (!mCancelled) {
371 m_resource->restartItemRetrieval(col);
372 }
373 deleteLater();
374 }
375
taskDone()376 void ResourceTask::taskDone()
377 {
378 m_resource->taskDone();
379 deleteLater();
380 }
381
emitPercent(int percent)382 void ResourceTask::emitPercent(int percent)
383 {
384 m_resource->emitPercent(percent);
385 }
386
emitError(const QString & message)387 void ResourceTask::emitError(const QString &message)
388 {
389 m_resource->emitError(message);
390 }
391
emitWarning(const QString & message)392 void ResourceTask::emitWarning(const QString &message)
393 {
394 m_resource->emitWarning(message);
395 }
396
synchronizeCollectionTree()397 void ResourceTask::synchronizeCollectionTree()
398 {
399 m_resource->synchronizeCollectionTree();
400 }
401
showInformationDialog(const QString & message,const QString & title,const QString & dontShowAgainName)402 void ResourceTask::showInformationDialog(const QString &message, const QString &title, const QString &dontShowAgainName)
403 {
404 m_resource->showInformationDialog(message, title, dontShowAgainName);
405 }
406
fromAkonadiToSupportedImapFlags(const QList<QByteArray> & flags,const Akonadi::Collection & collection)407 QList<QByteArray> ResourceTask::fromAkonadiToSupportedImapFlags(const QList<QByteArray> &flags, const Akonadi::Collection &collection)
408 {
409 QList<QByteArray> imapFlags = fromAkonadiFlags(flags);
410
411 const auto flagAttr = collection.attribute<Akonadi::CollectionFlagsAttribute>();
412 // the server does not support arbitrary flags, so filter out those it can't handle
413 if (flagAttr && !flagAttr->flags().isEmpty() && !flagAttr->flags().contains("\\*")) {
414 for (QList<QByteArray>::iterator it = imapFlags.begin(); it != imapFlags.end();) {
415 if (flagAttr->flags().contains(*it)) {
416 ++it;
417 } else {
418 qCDebug(IMAPRESOURCE_LOG) << "Server does not support flag" << *it;
419 it = imapFlags.erase(it);
420 }
421 }
422 }
423
424 return imapFlags;
425 }
426
fromAkonadiFlags(const QList<QByteArray> & flags)427 QList<QByteArray> ResourceTask::fromAkonadiFlags(const QList<QByteArray> &flags)
428 {
429 QList<QByteArray> newFlags;
430
431 for (const QByteArray &oldFlag : flags) {
432 if (oldFlag == Akonadi::MessageFlags::Seen) {
433 newFlags.append(ImapFlags::Seen);
434 } else if (oldFlag == Akonadi::MessageFlags::Deleted) {
435 newFlags.append(ImapFlags::Deleted);
436 } else if (oldFlag == Akonadi::MessageFlags::Answered || oldFlag == Akonadi::MessageFlags::Replied) {
437 newFlags.append(ImapFlags::Answered);
438 } else if (oldFlag == Akonadi::MessageFlags::Flagged) {
439 newFlags.append(ImapFlags::Flagged);
440 } else {
441 newFlags.append(oldFlag);
442 }
443 }
444
445 return newFlags;
446 }
447
toAkonadiFlags(const QList<QByteArray> & flags)448 QSet<QByteArray> ResourceTask::toAkonadiFlags(const QList<QByteArray> &flags)
449 {
450 QSet<QByteArray> newFlags;
451
452 for (const QByteArray &oldFlag : flags) {
453 if (oldFlag == ImapFlags::Seen) {
454 newFlags.insert(Akonadi::MessageFlags::Seen);
455 } else if (oldFlag == ImapFlags::Deleted) {
456 newFlags.insert(Akonadi::MessageFlags::Deleted);
457 } else if (oldFlag == ImapFlags::Answered) {
458 newFlags.insert(Akonadi::MessageFlags::Answered);
459 } else if (oldFlag == ImapFlags::Flagged) {
460 newFlags.insert(Akonadi::MessageFlags::Flagged);
461 } else if (oldFlag.isEmpty()) {
462 // filter out empty flags, to avoid isNull/isEmpty confusions higher up
463 continue;
464 } else {
465 newFlags.insert(oldFlag);
466 }
467 }
468
469 return newFlags;
470 }
471
kill()472 void ResourceTask::kill()
473 {
474 qCDebug(IMAPRESOURCE_TRACE) << metaObject()->className();
475 abortTask(i18n("killed"));
476 }
477
separatorCharacter() const478 const QChar ResourceTask::separatorCharacter() const
479 {
480 const QChar separator = m_resource->separatorCharacter();
481 if (!separator.isNull()) {
482 return separator;
483 } else {
484 // If we request the separator before first folder listing, then try to guess
485 // the separator:
486 // If we create a toplevel folder, assume the separator to be '/'. This is not perfect, but detecting the right
487 // IMAP separator is not straightforward for toplevel folders, and fixes bug 292418 and maybe other, where
488 // subfolders end up with remote id's starting with "i" (the first letter of imap:// ...)
489
490 QString remoteId;
491 // We don't always have parent collection set (for example for CollectionChangeTask),
492 // in such cases however we can use current collection's remoteId to get the separator
493 const Akonadi::Collection parent = parentCollection();
494 if (parent.isValid()) {
495 remoteId = parent.remoteId();
496 } else {
497 remoteId = collection().remoteId();
498 }
499 return ((remoteId != rootRemoteId()) && !remoteId.isEmpty()) ? remoteId.at(0) : QLatin1Char('/');
500 }
501 }
502
setSeparatorCharacter(QChar separator)503 void ResourceTask::setSeparatorCharacter(QChar separator)
504 {
505 m_resource->setSeparatorCharacter(separator);
506 }
507
serverSupportsAnnotations() const508 bool ResourceTask::serverSupportsAnnotations() const
509 {
510 return serverCapabilities().contains(QLatin1String("METADATA")) || serverCapabilities().contains(QLatin1String("ANNOTATEMORE"));
511 }
512
serverSupportsCondstore() const513 bool ResourceTask::serverSupportsCondstore() const
514 {
515 // Don't enable CONDSTORE for GMail (X-GM-EXT-1 is a GMail-specific capability)
516 // because it breaks changes synchronization when using labels.
517 return serverCapabilities().contains(QLatin1String("CONDSTORE")) && !serverCapabilities().contains(QLatin1String("X-GM-EXT-1"));
518 }
519
batchSize() const520 int ResourceTask::batchSize() const
521 {
522 return m_resource->batchSize();
523 }
524
resourceState()525 ResourceStateInterface::Ptr ResourceTask::resourceState()
526 {
527 return m_resource;
528 }
529
myRights(const Akonadi::Collection & col)530 KIMAP::Acl::Rights ResourceTask::myRights(const Akonadi::Collection &col)
531 {
532 const auto aclAttribute = col.attribute<Akonadi::ImapAclAttribute>();
533 if (aclAttribute) {
534 // HACK, only return myrights if they are available
535 if (aclAttribute->myRights() != KIMAP::Acl::None) {
536 return aclAttribute->myRights();
537 } else {
538 // This should be removed after 4.14, and myrights should be always used.
539 return aclAttribute->rights().value(userName().toUtf8());
540 }
541 }
542 return KIMAP::Acl::None;
543 }
544
setItemMergingMode(Akonadi::ItemSync::MergeMode mode)545 void ResourceTask::setItemMergingMode(Akonadi::ItemSync::MergeMode mode)
546 {
547 m_resource->setItemMergingMode(mode);
548 }
549