File indexing completed on 2024-11-17 04:45:07
0001 /* 0002 SPDX-FileCopyrightText: 2007 Till Adam <adam@kde.org> 0003 SPDX-FileCopyrightText: 2008 Omat Holding B.V. <info@omat.nl> 0004 SPDX-FileCopyrightText: 2009 Kevin Ottens <ervin@kde.org> 0005 0006 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> 0007 SPDX-FileContributor: Kevin Ottens <kevin@kdab.com> 0008 0009 SPDX-License-Identifier: LGPL-2.0-or-later 0010 */ 0011 0012 #include "imapresourcebase.h" 0013 0014 #include <QHostInfo> 0015 #include <QSettings> 0016 0017 #include "imapresource_debug.h" 0018 #include <KLocalizedString> 0019 #include <QIcon> 0020 0021 #include <kwindowsystem.h> 0022 0023 #include <Akonadi/MessageParts> 0024 0025 #include <Akonadi/AgentManager> 0026 #include <Akonadi/AttributeFactory> 0027 #include <Akonadi/ChangeRecorder> 0028 #include <Akonadi/CollectionAnnotationsAttribute> 0029 #include <Akonadi/CollectionFetchJob> 0030 #include <Akonadi/CollectionFetchScope> 0031 #include <Akonadi/CollectionModifyJob> 0032 #include <Akonadi/ItemFetchJob> 0033 #include <Akonadi/ItemFetchScope> 0034 #include <Akonadi/Session> 0035 #include <Akonadi/SpecialCollections> 0036 0037 #include <QStandardPaths> 0038 0039 #include "collectionflagsattribute.h" 0040 #include "highestmodseqattribute.h" 0041 #include "imapaclattribute.h" 0042 #include "imapquotaattribute.h" 0043 #include "noselectattribute.h" 0044 #include "uidnextattribute.h" 0045 #include "uidvalidityattribute.h" 0046 0047 #include "imapaccount.h" 0048 #include "imapidlemanager.h" 0049 #include "settings.h" 0050 #include "subscriptiondialog.h" 0051 0052 #include "addcollectiontask.h" 0053 #include "additemtask.h" 0054 #include "changecollectiontask.h" 0055 #include "changeitemsflagstask.h" 0056 #include "changeitemtask.h" 0057 #include "expungecollectiontask.h" 0058 #include "movecollectiontask.h" 0059 #include "moveitemstask.h" 0060 #include "removecollectionrecursivetask.h" 0061 #include "retrievecollectionmetadatatask.h" 0062 #include "retrievecollectionstask.h" 0063 #include "retrieveitemstask.h" 0064 #include "retrieveitemtask.h" 0065 #include "searchtask.h" 0066 0067 #include "imapflags.h" 0068 #include "sessionpool.h" 0069 0070 #include "resourceadaptor.h" 0071 0072 #ifdef MAIL_SERIALIZER_PLUGIN_STATIC 0073 0074 Q_IMPORT_PLUGIN(akonadi_serializer_mail) 0075 #endif 0076 0077 Q_DECLARE_METATYPE(QList<qint64>) 0078 Q_DECLARE_METATYPE(QWeakPointer<QObject>) 0079 0080 using namespace Akonadi; 0081 0082 ImapResourceBase::ImapResourceBase(const QString &id) 0083 : ResourceBase(id) 0084 , m_pool(new SessionPool(2, this)) 0085 , m_settings(nullptr) 0086 { 0087 QTimer::singleShot(0, this, &ImapResourceBase::updateResourceName); 0088 0089 connect(m_pool, &SessionPool::connectDone, this, &ImapResourceBase::onConnectDone); 0090 connect(m_pool, &SessionPool::connectionLost, this, &ImapResourceBase::onConnectionLost); 0091 0092 Akonadi::AttributeFactory::registerAttribute<UidValidityAttribute>(); 0093 Akonadi::AttributeFactory::registerAttribute<UidNextAttribute>(); 0094 Akonadi::AttributeFactory::registerAttribute<NoSelectAttribute>(); 0095 Akonadi::AttributeFactory::registerAttribute<HighestModSeqAttribute>(); 0096 0097 Akonadi::AttributeFactory::registerAttribute<CollectionFlagsAttribute>(); 0098 0099 Akonadi::AttributeFactory::registerAttribute<ImapAclAttribute>(); 0100 Akonadi::AttributeFactory::registerAttribute<ImapQuotaAttribute>(); 0101 0102 // For QMetaObject::invokeMethod() 0103 qRegisterMetaType<QList<qint64>>(); 0104 0105 changeRecorder()->fetchCollection(true); 0106 changeRecorder()->collectionFetchScope().setAncestorRetrieval(CollectionFetchScope::All); 0107 changeRecorder()->collectionFetchScope().setIncludeStatistics(true); 0108 changeRecorder()->collectionFetchScope().fetchAttribute<CollectionAnnotationsAttribute>(); 0109 changeRecorder()->itemFetchScope().fetchFullPayload(true); 0110 changeRecorder()->itemFetchScope().setAncestorRetrieval(ItemFetchScope::All); 0111 changeRecorder()->itemFetchScope().setFetchModificationTime(false); 0112 //(Andras) disable now, as tokoe reported problems with it and the mail filter: changeRecorder()->fetchChangedOnly( true ); 0113 0114 setHierarchicalRemoteIdentifiersEnabled(true); 0115 setItemTransactionMode(ItemSync::MultipleTransactions); // we can recover from incomplete syncs, so we can use a faster mode 0116 setDisableAutomaticItemDeliveryDone(true); 0117 setItemSyncBatchSize(100); 0118 0119 connect(this, &AgentBase::reloadConfiguration, this, &ImapResourceBase::reconnect); 0120 0121 m_statusMessageTimer = new QTimer(this); 0122 m_statusMessageTimer->setSingleShot(true); 0123 connect(m_statusMessageTimer, &QTimer::timeout, this, &ImapResourceBase::clearStatusMessage); 0124 connect(this, &AgentBase::error, this, &ImapResourceBase::showError); 0125 0126 new ImapResourceBaseAdaptor(this); 0127 0128 QMetaObject::invokeMethod(this, &ImapResourceBase::delayedInit, Qt::QueuedConnection); 0129 } 0130 0131 void ImapResourceBase::delayedInit() 0132 { 0133 setNeedsNetwork(needsNetwork()); 0134 0135 // Migration issue: trash folder had ID in config, but didn't have SpecialCollections attribute, fix that. 0136 if (!settings()->trashCollectionMigrated()) { 0137 const Akonadi::Collection::Id trashCollection = settings()->trashCollection(); 0138 if (trashCollection != -1) { 0139 Collection attributeCollection(trashCollection); 0140 SpecialCollections::setSpecialCollectionType("trash", attributeCollection); 0141 } 0142 settings()->setTrashCollectionMigrated(true); 0143 } 0144 } 0145 0146 ImapResourceBase::~ImapResourceBase() 0147 { 0148 // Destroy everything that could cause callbacks immediately, otherwise the callbacks can result in a crash. 0149 0150 delete m_idle; 0151 m_idle = nullptr; 0152 0153 for (ResourceTask *task : std::as_const(m_taskList)) { 0154 delete task; 0155 } 0156 m_taskList.clear(); 0157 0158 delete m_pool; 0159 delete m_settings; 0160 } 0161 0162 void ImapResourceBase::aboutToQuit() 0163 { 0164 // TODO the resource would ideally have to signal when it's done with logging out etc, before the destructor gets called 0165 if (m_idle) { 0166 m_idle->stop(); 0167 } 0168 0169 for (ResourceTask *task : std::as_const(m_taskList)) { 0170 task->kill(); 0171 } 0172 0173 m_pool->disconnect(); 0174 } 0175 0176 void ImapResourceBase::updateResourceName() 0177 { 0178 if (name() == identifier()) { 0179 const QString agentType = AgentManager::self()->instance(identifier()).type().identifier(); 0180 const QString agentsrcFile = 0181 QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + QLatin1StringView("akonadi/agentsrc"); 0182 0183 const QSettings agentsrc(agentsrcFile, QSettings::IniFormat); 0184 const int instanceCounter = agentsrc.value(QStringLiteral("InstanceCounters/%1/InstanceCounter").arg(agentType), -1).toInt(); 0185 0186 if (instanceCounter > 0) { 0187 setName(QStringLiteral("%1 %2").arg(defaultName()).arg(instanceCounter)); 0188 } else { 0189 setName(defaultName()); 0190 } 0191 } 0192 } 0193 0194 // ----------------------------------------------------------------------------- 0195 0196 void ImapResourceBase::configure(WId windowId) 0197 { 0198 if (createConfigureDialog(windowId)->exec() == QDialog::Accepted) { 0199 Q_EMIT configurationDialogAccepted(); 0200 reconnect(); 0201 } else { 0202 Q_EMIT configurationDialogRejected(); 0203 } 0204 } 0205 0206 // ---------------------------------------------------------------------------------- 0207 0208 void ImapResourceBase::startConnect(const QVariant &) 0209 { 0210 if (settings()->imapServer().isEmpty()) { 0211 setOnline(false); 0212 Q_EMIT status(NotConfigured, i18n("No server configured yet.")); 0213 taskDone(); 0214 return; 0215 } 0216 0217 m_pool->disconnect(); // reset all state, delete any old account 0218 auto account = new ImapAccount; 0219 settings()->loadAccount(account); 0220 0221 const bool result = m_pool->connect(account); 0222 Q_ASSERT(result); 0223 Q_UNUSED(result) 0224 } 0225 0226 int ImapResourceBase::configureSubscription(qlonglong windowId) 0227 { 0228 if (mSubscriptions) { 0229 return 0; 0230 } 0231 0232 if (!m_pool->account()) { 0233 return -2; 0234 } 0235 const QString password = settings()->password(); 0236 if (password.isEmpty()) { 0237 return -1; 0238 } 0239 0240 mSubscriptions.reset(new SubscriptionDialog(nullptr, SubscriptionDialog::AllowToEnableSubscription)); 0241 if (windowId) { 0242 mSubscriptions->setAttribute(Qt::WA_NativeWindow, true); 0243 KWindowSystem::setMainWindow(mSubscriptions->windowHandle(), windowId); 0244 } 0245 mSubscriptions->setWindowTitle(i18nc("@title:window", "Serverside Subscription")); 0246 mSubscriptions->setWindowIcon(QIcon::fromTheme(QStringLiteral("network-server"))); 0247 mSubscriptions->connectAccount(*m_pool->account(), password); 0248 mSubscriptions->setSubscriptionEnabled(settings()->subscriptionEnabled()); 0249 connect(mSubscriptions.get(), &SubscriptionDialog::accepted, this, [this]() { 0250 settings()->setSubscriptionEnabled(mSubscriptions->subscriptionEnabled()); 0251 settings()->save(); 0252 Q_EMIT configurationDialogAccepted(); 0253 reconnect(); 0254 }); 0255 connect(mSubscriptions.get(), &SubscriptionDialog::finished, this, [this]() { 0256 mSubscriptions.reset(); 0257 }); 0258 mSubscriptions->show(); 0259 0260 return 0; 0261 } 0262 0263 void ImapResourceBase::onConnectDone(int errorCode, const QString &errorString) 0264 { 0265 switch (errorCode) { 0266 case SessionPool::NoError: 0267 setOnline(true); 0268 taskDone(); 0269 Q_EMIT status(Idle, i18n("Connection established.")); 0270 0271 synchronizeCollectionTree(); 0272 break; 0273 0274 case SessionPool::PasswordRequestError: 0275 case SessionPool::EncryptionError: 0276 case SessionPool::LoginFailError: 0277 case SessionPool::CapabilitiesTestError: 0278 case SessionPool::IncompatibleServerError: 0279 setOnline(false); 0280 Q_EMIT status(Broken, errorString); 0281 cancelTask(errorString); 0282 return; 0283 0284 case SessionPool::CouldNotConnectError: 0285 case SessionPool::CancelledError: // e.g. we got disconnected during login 0286 Q_EMIT status(Idle, i18n("Server is not available.")); 0287 deferTask(); 0288 setTemporaryOffline((m_pool->account() && m_pool->account()->timeout() > 0) ? m_pool->account()->timeout() : 300); 0289 return; 0290 0291 case SessionPool::ReconnectNeededError: 0292 reconnect(); 0293 return; 0294 0295 case SessionPool::NoAvailableSessionError: 0296 qFatal("Shouldn't happen"); 0297 return; 0298 } 0299 } 0300 0301 void ImapResourceBase::onConnectionLost(KIMAP::Session * /*session*/) 0302 { 0303 if (!m_pool->isConnected() && isOnline()) { 0304 reconnect(); 0305 } 0306 } 0307 0308 ResourceStateInterface::Ptr ImapResourceBase::createResourceState(const TaskArguments &args) 0309 { 0310 return ResourceStateInterface::Ptr(new ResourceState(this, args)); 0311 } 0312 0313 Settings *ImapResourceBase::settings() const 0314 { 0315 if (m_settings == nullptr) { 0316 m_settings = new Settings; 0317 } 0318 0319 return m_settings; 0320 } 0321 0322 // ---------------------------------------------------------------------------------- 0323 0324 bool ImapResourceBase::retrieveItem(const Akonadi::Item &item, const QSet<QByteArray> &parts) 0325 { 0326 // The collection name is empty here... 0327 // Q_EMIT status( AgentBase::Running, i18nc( "@info:status", "Retrieving item in '%1'", item.parentCollection().name() ) ); 0328 0329 auto task = new RetrieveItemTask(createResourceState(TaskArguments(item, parts)), this); 0330 task->start(m_pool); 0331 queueTask(task); 0332 return true; 0333 } 0334 0335 void ImapResourceBase::itemAdded(const Item &item, const Collection &collection) 0336 { 0337 Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Adding item in '%1'", collection.name())); 0338 0339 startTask(new AddItemTask(createResourceState(TaskArguments(item, collection)), this)); 0340 } 0341 0342 void ImapResourceBase::itemChanged(const Item &item, const QSet<QByteArray> &parts) 0343 { 0344 Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Updating item in '%1'", item.parentCollection().name())); 0345 0346 startTask(new ChangeItemTask(createResourceState(TaskArguments(item, parts)), this)); 0347 } 0348 0349 void ImapResourceBase::itemsFlagsChanged(const Item::List &items, const QSet<QByteArray> &addedFlags, const QSet<QByteArray> &removedFlags) 0350 { 0351 Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Updating items")); 0352 0353 startTask(new ChangeItemsFlagsTask(createResourceState(TaskArguments(items, addedFlags, removedFlags)), this)); 0354 } 0355 0356 void ImapResourceBase::itemsRemoved(const Akonadi::Item::List &items) 0357 { 0358 const QString mailBox = ResourceStateInterface::mailBoxForCollection(items.first().parentCollection(), false); 0359 if (mailBox.isEmpty()) { 0360 // this item will be removed soon by its parent collection 0361 changeProcessed(); 0362 return; 0363 } 0364 0365 Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Removing items")); 0366 0367 startTask(new ChangeItemsFlagsTask(createResourceState(TaskArguments(items, QSet<QByteArray>() << ImapFlags::Deleted, QSet<QByteArray>())), this)); 0368 } 0369 0370 void ImapResourceBase::itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &source, const Akonadi::Collection &destination) 0371 { 0372 if (items.first().parentCollection() != destination) { // should have been set by the server 0373 qCWarning(IMAPRESOURCE_LOG) << "Collections don't match: destination=" << destination.id() << "; items parent=" << items.first().parentCollection().id() 0374 << "; source collection=" << source.id(); 0375 // Q_ASSERT( false ); 0376 // TODO: Find out why this happens 0377 cancelTask(); 0378 return; 0379 } 0380 0381 Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Moving items from '%1' to '%2'", source.name(), destination.name())); 0382 0383 startTask(new MoveItemsTask(createResourceState(TaskArguments(items, source, destination)), this)); 0384 } 0385 0386 // ---------------------------------------------------------------------------------- 0387 0388 void ImapResourceBase::retrieveCollections() 0389 { 0390 Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Retrieving folders")); 0391 0392 startTask(new RetrieveCollectionsTask(createResourceState(TaskArguments()), this)); 0393 } 0394 0395 void ImapResourceBase::retrieveCollectionAttributes(const Akonadi::Collection &col) 0396 { 0397 Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Retrieving extra folder information for '%1'", col.name())); 0398 startTask(new RetrieveCollectionMetadataTask(createResourceState(TaskArguments(col)), this)); 0399 } 0400 0401 void ImapResourceBase::retrieveItems(const Collection &col) 0402 { 0403 synchronizeCollectionAttributes(col.id()); 0404 0405 setItemStreamingEnabled(true); 0406 0407 auto task = new RetrieveItemsTask(createResourceState(TaskArguments(col)), this); 0408 connect(task, SIGNAL(status(int, QString)), SIGNAL(status(int, QString))); 0409 connect(this, &ResourceBase::retrieveNextItemSyncBatch, task, &RetrieveItemsTask::onReadyForNextBatch); 0410 startTask(task); 0411 } 0412 0413 void ImapResourceBase::collectionAdded(const Collection &collection, const Collection &parent) 0414 { 0415 Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Creating folder '%1'", collection.name())); 0416 startTask(new AddCollectionTask(createResourceState(TaskArguments(collection, parent)), this)); 0417 } 0418 0419 void ImapResourceBase::collectionChanged(const Collection &collection, const QSet<QByteArray> &parts) 0420 { 0421 Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Updating folder '%1'", collection.name())); 0422 startTask(new ChangeCollectionTask(createResourceState(TaskArguments(collection, parts)), this)); 0423 } 0424 0425 void ImapResourceBase::collectionRemoved(const Collection &collection) 0426 { 0427 // TODO Move this to the task 0428 const QString mailBox = ResourceStateInterface::mailBoxForCollection(collection, false); 0429 if (mailBox.isEmpty()) { 0430 // this collection will be removed soon by its parent collection 0431 changeProcessed(); 0432 return; 0433 } 0434 Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Removing folder '%1'", collection.name())); 0435 0436 startTask(new RemoveCollectionRecursiveTask(createResourceState(TaskArguments(collection)), this)); 0437 } 0438 0439 void ImapResourceBase::collectionMoved(const Akonadi::Collection &collection, const Akonadi::Collection &source, const Akonadi::Collection &destination) 0440 { 0441 Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Moving folder '%1' from '%2' to '%3'", collection.name(), source.name(), destination.name())); 0442 startTask(new MoveCollectionTask(createResourceState(TaskArguments(collection, source, destination)), this)); 0443 } 0444 0445 void ImapResourceBase::addSearch(const QString &query, const QString &queryLanguage, const Collection &resultCollection) 0446 { 0447 Q_UNUSED(query) 0448 Q_UNUSED(queryLanguage) 0449 Q_UNUSED(resultCollection) 0450 } 0451 0452 void ImapResourceBase::removeSearch(const Collection &resultCollection) 0453 { 0454 Q_UNUSED(resultCollection) 0455 } 0456 0457 void ImapResourceBase::search(const QString &query, const Collection &collection) 0458 { 0459 QVariantMap arg; 0460 arg[QStringLiteral("query")] = query; 0461 arg[QStringLiteral("collection")] = QVariant::fromValue(collection); 0462 scheduleCustomTask(this, "doSearch", arg); 0463 } 0464 0465 void ImapResourceBase::doSearch(const QVariant &arg) 0466 { 0467 const QVariantMap map = arg.toMap(); 0468 const QString query = map[QStringLiteral("query")].toString(); 0469 const auto collection = map[QStringLiteral("collection")].value<Collection>(); 0470 0471 Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Searching...")); 0472 startTask(new SearchTask(createResourceState(TaskArguments(collection)), query, this)); 0473 } 0474 0475 // ---------------------------------------------------------------------------------- 0476 0477 void ImapResourceBase::scheduleConnectionAttempt() 0478 { 0479 // block all other tasks, until we are connected 0480 scheduleCustomTask(this, "startConnect", QVariant(), ResourceBase::Prepend); 0481 } 0482 0483 void ImapResourceBase::doSetOnline(bool online) 0484 { 0485 qCDebug(IMAPRESOURCE_LOG) << "online=" << online; 0486 if (!online) { 0487 for (ResourceTask *task : std::as_const(m_taskList)) { 0488 task->kill(); 0489 delete task; 0490 } 0491 m_taskList.clear(); 0492 m_pool->cancelPasswordRequests(); 0493 if (m_pool->isConnected()) { 0494 m_pool->disconnect(); 0495 } 0496 if (m_idle) { 0497 m_idle->stop(); 0498 delete m_idle; 0499 m_idle = nullptr; 0500 } 0501 settings()->clearCachedPassword(); 0502 } else if (online && !m_pool->isConnected()) { 0503 scheduleConnectionAttempt(); 0504 } 0505 ResourceBase::doSetOnline(online); 0506 } 0507 0508 QChar ImapResourceBase::separatorCharacter() const 0509 { 0510 return m_separatorCharacter; 0511 } 0512 0513 void ImapResourceBase::setSeparatorCharacter(QChar separator) 0514 { 0515 m_separatorCharacter = separator; 0516 } 0517 0518 bool ImapResourceBase::needsNetwork() const 0519 { 0520 const QString hostName = settings()->imapServer().section(QLatin1Char(':'), 0, 0); 0521 // ### is there a better way to do this? 0522 if (hostName == QLatin1StringView("127.0.0.1") || hostName == QLatin1StringView("localhost") || hostName == QHostInfo::localHostName()) { 0523 return false; 0524 } 0525 return true; 0526 } 0527 0528 void ImapResourceBase::reconnect() 0529 { 0530 setNeedsNetwork(needsNetwork()); 0531 setOnline(false); // we are not connected initially 0532 setOnline(true); 0533 } 0534 0535 // ---------------------------------------------------------------------------------- 0536 0537 void ImapResourceBase::startIdleIfNeeded() 0538 { 0539 if (!m_idle) { 0540 startIdle(); 0541 } 0542 } 0543 0544 void ImapResourceBase::startIdle() 0545 { 0546 delete m_idle; 0547 m_idle = nullptr; 0548 0549 if (!m_pool->serverCapabilities().contains(QLatin1StringView("IDLE"))) { 0550 return; 0551 } 0552 0553 // Without password we don't even have to try 0554 if (m_pool->account()->authenticationMode() != KIMAP::LoginJob::GSSAPI && settings()->password().isEmpty()) { 0555 return; 0556 } 0557 0558 const QStringList ridPath = settings()->idleRidPath(); 0559 if (ridPath.size() < 2) { 0560 return; 0561 } 0562 0563 Collection c, p; 0564 p.setParentCollection(Collection::root()); 0565 for (int i = ridPath.size() - 1; i > 0; --i) { 0566 p.setRemoteId(ridPath.at(i)); 0567 c.setParentCollection(p); 0568 p = c; 0569 } 0570 c.setRemoteId(ridPath.first()); 0571 0572 Akonadi::CollectionFetchScope scope; 0573 scope.setResource(identifier()); 0574 scope.setAncestorRetrieval(Akonadi::CollectionFetchScope::All); 0575 0576 auto fetch = new Akonadi::CollectionFetchJob(c, Akonadi::CollectionFetchJob::Base, this); 0577 fetch->setFetchScope(scope); 0578 0579 connect(fetch, &KJob::result, this, &ImapResourceBase::onIdleCollectionFetchDone); 0580 } 0581 0582 void ImapResourceBase::onIdleCollectionFetchDone(KJob *job) 0583 { 0584 if (job->error()) { 0585 qCWarning(IMAPRESOURCE_LOG) << "CollectionFetch for idling failed." 0586 << "error=" << job->error() << ", errorString=" << job->errorString(); 0587 return; 0588 } 0589 auto fetch = static_cast<Akonadi::CollectionFetchJob *>(job); 0590 // Can be empty if collection is not subscribed locally 0591 if (!fetch->collections().isEmpty()) { 0592 delete m_idle; 0593 m_idle = new ImapIdleManager(createResourceState(TaskArguments(fetch->collections().at(0))), m_pool, this); 0594 } else { 0595 qCWarning(IMAPRESOURCE_LOG) << "Failed to retrieve IDLE collection: no such collection"; 0596 } 0597 } 0598 0599 // ---------------------------------------------------------------------------------- 0600 0601 void ImapResourceBase::requestManualExpunge(qint64 collectionId) 0602 { 0603 if (!settings()->automaticExpungeEnabled()) { 0604 Collection collection(collectionId); 0605 0606 Akonadi::CollectionFetchScope scope; 0607 scope.setResource(identifier()); 0608 scope.setAncestorRetrieval(Akonadi::CollectionFetchScope::All); 0609 scope.setListFilter(CollectionFetchScope::NoFilter); 0610 0611 auto fetch = new Akonadi::CollectionFetchJob(collection, Akonadi::CollectionFetchJob::Base, this); 0612 fetch->setFetchScope(scope); 0613 0614 connect(fetch, &KJob::result, this, &ImapResourceBase::onExpungeCollectionFetchDone); 0615 } 0616 } 0617 0618 void ImapResourceBase::onExpungeCollectionFetchDone(KJob *job) 0619 { 0620 if (job->error() == 0) { 0621 auto fetch = static_cast<Akonadi::CollectionFetchJob *>(job); 0622 Akonadi::Collection collection = fetch->collections().at(0); 0623 0624 scheduleCustomTask(this, "triggerCollectionExpunge", QVariant::fromValue(collection)); 0625 } else { 0626 qCWarning(IMAPRESOURCE_LOG) << "CollectionFetch for expunge failed." 0627 << "error=" << job->error() << ", errorString=" << job->errorString(); 0628 } 0629 } 0630 0631 void ImapResourceBase::triggerCollectionExpunge(const QVariant &collectionVariant) 0632 { 0633 const auto collection = collectionVariant.value<Collection>(); 0634 0635 auto task = new ExpungeCollectionTask(createResourceState(TaskArguments(collection)), this); 0636 task->start(m_pool); 0637 queueTask(task); 0638 } 0639 0640 // ---------------------------------------------------------------------------------- 0641 0642 void ImapResourceBase::abortActivity() 0643 { 0644 if (!m_taskList.isEmpty()) { 0645 m_pool->disconnect(SessionPool::CloseSession); 0646 scheduleConnectionAttempt(); 0647 } 0648 } 0649 0650 void ImapResourceBase::queueTask(ResourceTask *task) 0651 { 0652 connect(task, &QObject::destroyed, this, &ImapResourceBase::taskDestroyed); 0653 m_taskList << task; 0654 } 0655 0656 void ImapResourceBase::startTask(ResourceTask *task) 0657 { 0658 task->start(m_pool); 0659 queueTask(task); 0660 } 0661 0662 void ImapResourceBase::taskDestroyed(QObject *task) 0663 { 0664 m_taskList.removeAll(static_cast<ResourceTask *>(task)); 0665 } 0666 0667 QStringList ImapResourceBase::serverCapabilities() const 0668 { 0669 return m_pool->serverCapabilities(); 0670 } 0671 0672 void ImapResourceBase::cleanup() 0673 { 0674 settings()->cleanup(); 0675 0676 ResourceBase::cleanup(); 0677 } 0678 0679 QString ImapResourceBase::dumpResourceToString() const 0680 { 0681 QString ret; 0682 for (ResourceTask *task : std::as_const(m_taskList)) { 0683 if (!ret.isEmpty()) { 0684 ret += QLatin1StringView(", "); 0685 } 0686 ret += QLatin1StringView(task->metaObject()->className()); 0687 } 0688 return QLatin1StringView("IMAP tasks: ") + ret; 0689 } 0690 0691 void ImapResourceBase::showError(const QString &message) 0692 { 0693 Q_EMIT status(Akonadi::AgentBase::Idle, message); 0694 m_statusMessageTimer->start(1000 * 10); 0695 } 0696 0697 void ImapResourceBase::clearStatusMessage() 0698 { 0699 Q_EMIT status(Akonadi::AgentBase::Idle, QString()); 0700 } 0701 0702 void ImapResourceBase::modifyCollection(const Collection &col) 0703 { 0704 auto modJob = new Akonadi::CollectionModifyJob(col, this); 0705 connect(modJob, &KJob::result, this, &ImapResourceBase::onCollectionModifyDone); 0706 } 0707 0708 void ImapResourceBase::onCollectionModifyDone(KJob *job) 0709 { 0710 if (job->error()) { 0711 qCWarning(IMAPRESOURCE_LOG) << "Failed to modify collection: " << job->errorString(); 0712 } 0713 } 0714 0715 #include "moc_imapresourcebase.cpp"