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"