diff --git a/CMakeLists.txt b/CMakeLists.txt index a68f03deb..899bc8abe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,168 +1,168 @@ cmake_minimum_required(VERSION 3.5) -set(PIM_VERSION "5.10.80") +set(PIM_VERSION "5.10.90") set(KDEPIM_RUNTIME_VERSION_NUMBER ${PIM_VERSION}) project(kdepim-runtime VERSION ${KDEPIM_RUNTIME_VERSION_NUMBER}) if (POLICY CMP0053) cmake_policy(SET CMP0053 NEW) endif() ############### KDEPIM-Runtime version ################ # KDEPIM_RUNTIME_VERSION # Version scheme: "x.y.z build". # # x is the version number. # y is the major release number. # z is the minor release number. # # "x.y.z" follow the kdelibs version kdepim is released with. # # If "z" is 0, it the version is "x.y" # # KDEPIM_DEV_VERSION # is empty for final versions. For development versions "build" is # something like "pre", "", "alpha2", "beta1", "beta2", "rc1", "rc2". # # Examples in chronological order: # # 3.0 # 3.0.1 # 3.1 # 3.1 beta1 # 3.1 beta2 # 3.1 rc1 # 3.1 # 3.1.1 # 3.2 pre # 3.2 set(KDEPIM_DEV_VERSION beta1) # add an extra space if(DEFINED KDEPIM_DEV_VERSION) set(KDEPIM_DEV_VERSION " ${KDEPIM_DEV_VERSION}") endif() set(KDEPIM_RUNTIME_VERSION "${KDEPIM_RUNTIME_VERSION_NUMBER}${KDEPIM_DEV_VERSION}") configure_file(kdepim-runtime-version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/kdepim-runtime-version.h @ONLY) set(KF5_MIN_VERSION "5.56.0") find_package(ECM ${KF5_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${kdepim-runtime_SOURCE_DIR}/cmake/ ${ECM_MODULE_PATH}) include(ECMSetupVersion) include(FeatureSummary) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMQtDeclareLoggingCategory) include(GenerateExportHeader) set(QT_REQUIRED_VERSION "5.10.0") set(KDEPIMRUNTIME_LIB_VERSION "${KDEPIM_RUNTIME_VERSION_NUMBER}") set(KDEPIMRUNTIME_LIB_SOVERSION "5") set(AKONADI_VERSION "5.10.80") set(KCONTACTS_LIB_VERSION "5.10.80") set(KCALENDARCORE_LIB_VERSION "5.10.80") set(IDENTITYMANAGEMENT_LIB_VERSION "5.10.80") set(KMAILTRANSPORT_LIB_VERSION "5.10.80") set(CALENDARUTILS_LIB_VERSION "5.10.80") set(KDAV_LIB_VERSION "5.10.80") set(KIMAP_LIB_VERSION "5.10.80") set(KMBOX_LIB_VERSION "5.10.80") set(AKONADICALENDAR_LIB_VERSION "5.10.80") set(KONTACTINTERFACE_LIB_VERSION "5.10.80") set(AKONADIKALARM_LIB_VERSION "5.10.80") set(KMIME_LIB_VERSION "5.10.80") set(XMLRPCCLIENT_LIB_VERSION "5.10.80") set(KCONTACTS_LIB_VERSION "5.10.80") set(AKONADIMIME_LIB_VERSION "5.10.80") set(AKONADICONTACT_LIB_VERSION "5.10.80") set(AKONADINOTE_LIB_VERSION "5.10.80") set(PIMCOMMON_LIB_VERSION "5.10.80") set(KGAPI_LIB_VERSION "5.10.80") set( SharedMimeInfo_MINIMUM_VERSION "1.3" ) find_package(SharedMimeInfo ${SharedMimeInfo_MINIMUM_VERSION} REQUIRED) find_package(Sasl2) set_package_properties(Sasl2 PROPERTIES TYPE REQUIRED) find_package(Qca-qt5) # QT5 package find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED TextToSpeech Network Widgets Test XmlPatterns DBus WebEngineWidgets NetworkAuth) if (NOT Qca-qt5_FOUND) message(STATUS "QCA not found, public key authentication will not be supported") else() add_definitions(-DHAVE_QCA) endif() # KF5 package find_package(KF5Config ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5ConfigWidgets ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5NotifyConfig ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5KIO ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5ItemModels ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Codecs ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5WindowSystem ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5TextWidgets ${KF5_MIN_VERSION} CONFIG REQUIRED) # for KPluralHandlingSpinBox find_package(KF5Notifications ${KF5_MIN_VERSION} CONFIG REQUIRED) # pop3, ews find_package(KF5DocTools ${KF5_MIN_VERSION} CONFIG REQUIRED) # pop3 find_package(KF5Holidays ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5DBusAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5IconThemes ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5KDELibs4Support ${KF5_MIN_VERSION} CONFIG REQUIRED) # KdepimLibs package find_package(KF5Akonadi ${AKONADI_VERSION} CONFIG REQUIRED) find_package(KPimKDAV ${KDAV_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Mime ${KMIME_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiMime ${AKONADIMIME_LIB_VERSION} CONFIG REQUIRED) find_package(KF5MailTransportAkonadi ${KMAILTRANSPORT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5IdentityManagement ${IDENTITYMANAGEMENT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiContact ${AKONADICONTACT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Contacts ${KCONTACTS_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AlarmCalendar ${AKONADIKALARM_LIB_VERSION} CONFIG REQUIRED) find_package(KF5CalendarCore ${KCALENDARCORE_LIB_VERSION} CONFIG REQUIRED) find_package(KF5CalendarUtils ${CALENDARUTILS_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Mbox ${KMBOX_LIB_VERSION} CONFIG REQUIRED) find_package(KF5IMAP ${KIMAP_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiNotes ${AKONADINOTE_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiCalendar ${AKONADICALENDAR_LIB_VERSION} CONFIG REQUIRED) find_package(KF5PimCommon ${PIMCOMMON_LIB_VERSION} CONFIG REQUIRED) find_package(KPimGAPI ${KGAPI_LIB_VERSION} CONFIG REQUIRED) option(KDEPIM_RUN_ISOLATED_TESTS "Run the isolated tests." FALSE) #add_definitions( -DQT_DISABLE_DEPRECATED_BEFORE=0x060000 ) add_subdirectory(resources) add_subdirectory(agents) add_subdirectory(defaultsetup) add_subdirectory(kioslave) add_subdirectory(migration) add_subdirectory(doc) ## install the MIME type spec file for KDEPIM specific MIME types install(FILES kdepim-mime.xml DESTINATION ${KDE_INSTALL_MIMEDIR}) update_xdg_mimetypes(${KDE_INSTALL_MIMEDIR}) install( FILES kdepim-runtime.renamecategories kdepim-runtime.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES ) diff --git a/resources/imap/imapresourcebase.cpp b/resources/imap/imapresourcebase.cpp index 35afdd332..a0fc0b417 100644 --- a/resources/imap/imapresourcebase.cpp +++ b/resources/imap/imapresourcebase.cpp @@ -1,752 +1,750 @@ /* Copyright (c) 2007 Till Adam Copyright (C) 2008 Omat Holding B.V. Copyright (C) 2009 Kevin Ottens Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "imapresourcebase.h" #include #include #include #include "imapresource_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "collectionannotationsattribute.h" #include "collectionflagsattribute.h" #include "imapaclattribute.h" #include "imapquotaattribute.h" #include "noselectattribute.h" #include "uidvalidityattribute.h" #include "uidnextattribute.h" #include "highestmodseqattribute.h" #include "settings.h" #include "imapaccount.h" #include "imapidlemanager.h" #include "resourcestate.h" #include "subscriptiondialog.h" #include "addcollectiontask.h" #include "additemtask.h" #include "changecollectiontask.h" #include "changeitemsflagstask.h" #include "changeitemtask.h" #include "expungecollectiontask.h" #include "movecollectiontask.h" #include "moveitemstask.h" #include "removecollectionrecursivetask.h" #include "retrievecollectionmetadatatask.h" #include "retrievecollectionstask.h" #include "retrieveitemtask.h" #include "retrieveitemstask.h" #include "searchtask.h" #include "settingspasswordrequester.h" #include "sessionpool.h" #include "sessionuiproxy.h" #include "imapflags.h" #include "resourceadaptor.h" #ifdef MAIL_SERIALIZER_PLUGIN_STATIC Q_IMPORT_PLUGIN(akonadi_serializer_mail) #endif Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(QWeakPointer) using namespace Akonadi; ImapResourceBase::ImapResourceBase(const QString &id) : ResourceBase(id) , m_pool(new SessionPool(2, this)) , m_settings(nullptr) , mSubscriptions(nullptr) , m_idle(nullptr) { QTimer::singleShot(0, this, &ImapResourceBase::updateResourceName); connect(m_pool, &SessionPool::connectDone, this, &ImapResourceBase::onConnectDone); connect(m_pool, &SessionPool::connectionLost, this, &ImapResourceBase::onConnectionLost); Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); // For QMetaObject::invokeMethod() qRegisterMetaType >(); changeRecorder()->fetchCollection(true); changeRecorder()->collectionFetchScope().setAncestorRetrieval(CollectionFetchScope::All); changeRecorder()->collectionFetchScope().setIncludeStatistics(true); changeRecorder()->collectionFetchScope().fetchAttribute(); changeRecorder()->itemFetchScope().fetchFullPayload(true); changeRecorder()->itemFetchScope().setAncestorRetrieval(ItemFetchScope::All); changeRecorder()->itemFetchScope().setFetchModificationTime(false); //(Andras) disable now, as tokoe reported problems with it and the mail filter: changeRecorder()->fetchChangedOnly( true ); setHierarchicalRemoteIdentifiersEnabled(true); setItemTransactionMode(ItemSync::MultipleTransactions); // we can recover from incomplete syncs, so we can use a faster mode ItemFetchScope scope(changeRecorder()->itemFetchScope()); scope.fetchFullPayload(false); scope.setAncestorRetrieval(ItemFetchScope::None); setItemSynchronizationFetchScope(scope); setDisableAutomaticItemDeliveryDone(true); setItemSyncBatchSize(100); connect(this, &AgentBase::reloadConfiguration, this, &ImapResourceBase::reconnect); m_statusMessageTimer = new QTimer(this); m_statusMessageTimer->setSingleShot(true); connect(m_statusMessageTimer, &QTimer::timeout, this, &ImapResourceBase::clearStatusMessage); connect(this, &AgentBase::error, this, &ImapResourceBase::showError); QMetaObject::invokeMethod(this, &ImapResourceBase::delayedInit, Qt::QueuedConnection); } void ImapResourceBase::delayedInit() { settings(); // make sure the D-Bus settings interface is up new ImapResourceBaseAdaptor(this); setNeedsNetwork(needsNetwork()); // Migration issue: trash folder had ID in config, but didn't have SpecialCollections attribute, fix that. if (!settings()->trashCollectionMigrated()) { const Akonadi::Collection::Id trashCollection = settings()->trashCollection(); if (trashCollection != -1) { Collection attributeCollection(trashCollection); SpecialCollections::setSpecialCollectionType("trash", attributeCollection); } settings()->setTrashCollectionMigrated(true); } } ImapResourceBase::~ImapResourceBase() { //Destroy everything that could cause callbacks immediately, otherwise the callbacks can result in a crash. delete m_idle; m_idle = nullptr; for (ResourceTask *task : qAsConst(m_taskList)) { delete task; } m_taskList.clear(); delete m_pool; delete m_settings; } void ImapResourceBase::aboutToQuit() { //TODO the resource would ideally have to signal when it's done with logging out etc, before the destructor gets called if (m_idle) { m_idle->stop(); } for (ResourceTask *task : qAsConst(m_taskList)) { task->kill(); } m_pool->disconnect(); } void ImapResourceBase::updateResourceName() { if (name() == identifier()) { const QString agentType = AgentManager::self()->instance(identifier()).type().identifier(); const QString agentsrcFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + QLatin1String("akonadi/agentsrc"); const QSettings agentsrc(agentsrcFile, QSettings::IniFormat); const int instanceCounter = agentsrc.value( QStringLiteral("InstanceCounters/%1/InstanceCounter").arg(agentType), -1).toInt(); if (instanceCounter > 0) { setName(QStringLiteral("%1 %2").arg(defaultName()).arg(instanceCounter)); } else { setName(defaultName()); } } } // ----------------------------------------------------------------------------- void ImapResourceBase::configure(WId windowId) { if (createConfigureDialog(windowId)->exec() == QDialog::Accepted) { Q_EMIT configurationDialogAccepted(); reconnect(); } else { Q_EMIT configurationDialogRejected(); } } // ---------------------------------------------------------------------------------- void ImapResourceBase::startConnect(const QVariant &) { if (settings()->imapServer().isEmpty()) { setOnline(false); Q_EMIT status(NotConfigured, i18n("No server configured yet.")); taskDone(); return; } m_pool->disconnect(); // reset all state, delete any old account ImapAccount *account = new ImapAccount; settings()->loadAccount(account); const bool result = m_pool->connect(account); Q_ASSERT(result); Q_UNUSED(result); } int ImapResourceBase::configureSubscription(qlonglong windowId) { if (mSubscriptions) { return 0; } if (!m_pool->account()) { return -2; } const QString password = settings()->password(); if (password.isEmpty()) { return -1; } mSubscriptions = new SubscriptionDialog(nullptr, SubscriptionDialog::AllowToEnableSubscription); if (windowId) { KWindowSystem::setMainWindow(mSubscriptions, windowId); } mSubscriptions->setWindowTitle(i18nc("@title:window", "Serverside Subscription")); mSubscriptions->setWindowIcon(QIcon::fromTheme(QStringLiteral("network-server"))); mSubscriptions->connectAccount(*m_pool->account(), password); mSubscriptions->setSubscriptionEnabled(settings()->subscriptionEnabled()); if (mSubscriptions->exec()) { settings()->setSubscriptionEnabled(mSubscriptions->subscriptionEnabled()); settings()->save(); Q_EMIT configurationDialogAccepted(); reconnect(); } delete mSubscriptions; return 0; } void ImapResourceBase::onConnectDone(int errorCode, const QString &errorString) { switch (errorCode) { case SessionPool::NoError: setOnline(true); taskDone(); Q_EMIT status(Idle, i18n("Connection established.")); synchronizeCollectionTree(); break; case SessionPool::PasswordRequestError: case SessionPool::EncryptionError: case SessionPool::LoginFailError: case SessionPool::CapabilitiesTestError: case SessionPool::IncompatibleServerError: setOnline(false); Q_EMIT status(Broken, errorString); - cancelTask(); + cancelTask(errorString); return; case SessionPool::CouldNotConnectError: + case SessionPool::CancelledError: // e.g. we got disconnected during login Q_EMIT status(Idle, i18n("Server is not available.")); deferTask(); setTemporaryOffline((m_pool->account() && m_pool->account()->timeout() > 0) ? m_pool->account()->timeout() : 300); return; case SessionPool::ReconnectNeededError: reconnect(); return; case SessionPool::NoAvailableSessionError: qFatal("Shouldn't happen"); return; - case SessionPool::CancelledError: - qCWarning(IMAPRESOURCE_LOG) << "Session login cancelled"; - return; } } void ImapResourceBase::onConnectionLost(KIMAP::Session */*session*/) { if (!m_pool->isConnected()) { reconnect(); } } ResourceStateInterface::Ptr ImapResourceBase::createResourceState(const TaskArguments &args) { return ResourceStateInterface::Ptr(new ResourceState(this, args)); } Settings *ImapResourceBase::settings() const { if (m_settings == nullptr) { m_settings = new Settings; } return m_settings; } // ---------------------------------------------------------------------------------- bool ImapResourceBase::retrieveItem(const Akonadi::Item &item, const QSet &parts) { // The collection name is empty here... //Q_EMIT status( AgentBase::Running, i18nc( "@info:status", "Retrieving item in '%1'", item.parentCollection().name() ) ); RetrieveItemTask *task = new RetrieveItemTask(createResourceState(TaskArguments(item, parts)), this); task->start(m_pool); queueTask(task); return true; } void ImapResourceBase::itemAdded(const Item &item, const Collection &collection) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Adding item in '%1'", collection.name())); startTask(new AddItemTask(createResourceState(TaskArguments(item, collection)), this)); } void ImapResourceBase::itemChanged(const Item &item, const QSet &parts) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Updating item in '%1'", item.parentCollection().name())); startTask(new ChangeItemTask(createResourceState(TaskArguments(item, parts)), this)); } void ImapResourceBase::itemsFlagsChanged(const Item::List &items, const QSet< QByteArray > &addedFlags, const QSet< QByteArray > &removedFlags) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Updating items")); startTask(new ChangeItemsFlagsTask(createResourceState(TaskArguments(items, addedFlags, removedFlags)), this)); } void ImapResourceBase::itemsRemoved(const Akonadi::Item::List &items) { const QString mailBox = ResourceStateInterface::mailBoxForCollection(items.first().parentCollection(), false); if (mailBox.isEmpty()) { // this item will be removed soon by its parent collection changeProcessed(); return; } Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Removing items")); startTask(new ChangeItemsFlagsTask(createResourceState(TaskArguments(items, QSet() << ImapFlags::Deleted, QSet())), this)); } void ImapResourceBase::itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &source, const Akonadi::Collection &destination) { if (items.first().parentCollection() != destination) { // should have been set by the server qCWarning(IMAPRESOURCE_LOG) << "Collections don't match: destination=" << destination.id() << "; items parent=" << items.first().parentCollection().id() << "; source collection=" << source.id(); //Q_ASSERT( false ); //TODO: Find out why this happens cancelTask(); return; } Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Moving items from '%1' to '%2'", source.name(), destination.name())); startTask(new MoveItemsTask(createResourceState(TaskArguments(items, source, destination)), this)); } // ---------------------------------------------------------------------------------- void ImapResourceBase::retrieveCollections() { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Retrieving folders")); startTask(new RetrieveCollectionsTask(createResourceState(TaskArguments()), this)); } void ImapResourceBase::retrieveCollectionAttributes(const Akonadi::Collection &col) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Retrieving extra folder information for '%1'", col.name())); startTask(new RetrieveCollectionMetadataTask(createResourceState(TaskArguments(col)), this)); } void ImapResourceBase::retrieveItems(const Collection &col) { synchronizeCollectionAttributes(col.id()); setItemStreamingEnabled(true); RetrieveItemsTask *task = new RetrieveItemsTask(createResourceState(TaskArguments(col)), this); connect(task, SIGNAL(status(int,QString)), SIGNAL(status(int,QString))); connect(this, &ResourceBase::retrieveNextItemSyncBatch, task, &RetrieveItemsTask::onReadyForNextBatch); startTask(task); } void ImapResourceBase::collectionAdded(const Collection &collection, const Collection &parent) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Creating folder '%1'", collection.name())); startTask(new AddCollectionTask(createResourceState(TaskArguments(collection, parent)), this)); } void ImapResourceBase::collectionChanged(const Collection &collection, const QSet &parts) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Updating folder '%1'", collection.name())); startTask(new ChangeCollectionTask(createResourceState(TaskArguments(collection, parts)), this)); } void ImapResourceBase::collectionRemoved(const Collection &collection) { //TODO Move this to the task const QString mailBox = ResourceStateInterface::mailBoxForCollection(collection, false); if (mailBox.isEmpty()) { // this collection will be removed soon by its parent collection changeProcessed(); return; } Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Removing folder '%1'", collection.name())); startTask(new RemoveCollectionRecursiveTask(createResourceState(TaskArguments(collection)), this)); } void ImapResourceBase::collectionMoved(const Akonadi::Collection &collection, const Akonadi::Collection &source, const Akonadi::Collection &destination) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Moving folder '%1' from '%2' to '%3'", collection.name(), source.name(), destination.name())); startTask(new MoveCollectionTask(createResourceState(TaskArguments(collection, source, destination)), this)); } void ImapResourceBase::addSearch(const QString &query, const QString &queryLanguage, const Collection &resultCollection) { Q_UNUSED(query); Q_UNUSED(queryLanguage); Q_UNUSED(resultCollection); } void ImapResourceBase::removeSearch(const Collection &resultCollection) { Q_UNUSED(resultCollection); } void ImapResourceBase::search(const QString &query, const Collection &collection) { QVariantMap arg; arg[QStringLiteral("query")] = query; arg[QStringLiteral("collection")] = QVariant::fromValue(collection); scheduleCustomTask(this, "doSearch", arg); } void ImapResourceBase::doSearch(const QVariant &arg) { const QVariantMap map = arg.toMap(); const QString query = map[QStringLiteral("query")].toString(); const Collection collection = map[QStringLiteral("collection")].value(); Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Searching...")); startTask(new SearchTask(createResourceState(TaskArguments(collection)), query, this)); } // ---------------------------------------------------------------------------------- void ImapResourceBase::scheduleConnectionAttempt() { // block all other tasks, until we are connected scheduleCustomTask(this, "startConnect", QVariant(), ResourceBase::Prepend); } void ImapResourceBase::doSetOnline(bool online) { qCDebug(IMAPRESOURCE_LOG) << "online=" << online; if (!online) { for (ResourceTask *task : qAsConst(m_taskList)) { task->kill(); delete task; } m_taskList.clear(); m_pool->cancelPasswordRequests(); if (m_pool->isConnected()) { m_pool->disconnect(); } if (m_idle) { m_idle->stop(); delete m_idle; m_idle = nullptr; } settings()->clearCachedPassword(); } else if (online && !m_pool->isConnected()) { scheduleConnectionAttempt(); } ResourceBase::doSetOnline(online); } QChar ImapResourceBase::separatorCharacter() const { return m_separatorCharacter; } void ImapResourceBase::setSeparatorCharacter(QChar separator) { m_separatorCharacter = separator; } bool ImapResourceBase::needsNetwork() const { const QString hostName = settings()->imapServer().section(QLatin1Char(':'), 0, 0); // ### is there a better way to do this? if (hostName == QLatin1String("127.0.0.1") || hostName == QLatin1String("localhost") || hostName == QHostInfo::localHostName()) { return false; } return true; } void ImapResourceBase::reconnect() { setNeedsNetwork(needsNetwork()); setOnline(false); // we are not connected initially setOnline(true); } // ---------------------------------------------------------------------------------- void ImapResourceBase::startIdleIfNeeded() { if (!m_idle) { startIdle(); } } void ImapResourceBase::startIdle() { delete m_idle; m_idle = nullptr; if (!m_pool->serverCapabilities().contains(QLatin1String("IDLE"))) { return; } //Without password we don't even have to try if (m_pool->account()->authenticationMode() != KIMAP::LoginJob::GSSAPI && settings()->password().isEmpty()) { return; } const QStringList ridPath = settings()->idleRidPath(); if (ridPath.size() < 2) { return; } Collection c, p; p.setParentCollection(Collection::root()); for (int i = ridPath.size() - 1; i > 0; --i) { p.setRemoteId(ridPath.at(i)); c.setParentCollection(p); p = c; } c.setRemoteId(ridPath.first()); Akonadi::CollectionFetchScope scope; scope.setResource(identifier()); scope.setAncestorRetrieval(Akonadi::CollectionFetchScope::All); Akonadi::CollectionFetchJob *fetch = new Akonadi::CollectionFetchJob(c, Akonadi::CollectionFetchJob::Base, this); fetch->setFetchScope(scope); connect(fetch, &KJob::result, this, &ImapResourceBase::onIdleCollectionFetchDone); } void ImapResourceBase::onIdleCollectionFetchDone(KJob *job) { if (job->error()) { qCWarning(IMAPRESOURCE_LOG) << "CollectionFetch for idling failed." << "error=" << job->error() << ", errorString=" << job->errorString(); return; } Akonadi::CollectionFetchJob *fetch = static_cast(job); //Can be empty if collection is not subscribed locally if (!fetch->collections().isEmpty()) { delete m_idle; m_idle = new ImapIdleManager(createResourceState(TaskArguments(fetch->collections().at(0))), m_pool, this); } else { qCWarning(IMAPRESOURCE_LOG) << "Failed to retrieve IDLE collection: no such collection"; } } // ---------------------------------------------------------------------------------- void ImapResourceBase::requestManualExpunge(qint64 collectionId) { if (!settings()->automaticExpungeEnabled()) { Collection collection(collectionId); Akonadi::CollectionFetchScope scope; scope.setResource(identifier()); scope.setAncestorRetrieval(Akonadi::CollectionFetchScope::All); scope.setListFilter(CollectionFetchScope::NoFilter); Akonadi::CollectionFetchJob *fetch = new Akonadi::CollectionFetchJob(collection, Akonadi::CollectionFetchJob::Base, this); fetch->setFetchScope(scope); connect(fetch, &KJob::result, this, &ImapResourceBase::onExpungeCollectionFetchDone); } } void ImapResourceBase::onExpungeCollectionFetchDone(KJob *job) { if (job->error() == 0) { Akonadi::CollectionFetchJob *fetch = static_cast(job); Akonadi::Collection collection = fetch->collections().at(0); scheduleCustomTask(this, "triggerCollectionExpunge", QVariant::fromValue(collection)); } else { qCWarning(IMAPRESOURCE_LOG) << "CollectionFetch for expunge failed." << "error=" << job->error() << ", errorString=" << job->errorString(); } } void ImapResourceBase::triggerCollectionExpunge(const QVariant &collectionVariant) { const Collection collection = collectionVariant.value(); ExpungeCollectionTask *task = new ExpungeCollectionTask(createResourceState(TaskArguments(collection)), this); task->start(m_pool); queueTask(task); } // ---------------------------------------------------------------------------------- void ImapResourceBase::abortActivity() { if (!m_taskList.isEmpty()) { m_pool->disconnect(SessionPool::CloseSession); scheduleConnectionAttempt(); } } void ImapResourceBase::queueTask(ResourceTask *task) { connect(task, &QObject::destroyed, this, &ImapResourceBase::taskDestroyed); m_taskList << task; } void ImapResourceBase::startTask(ResourceTask *task) { task->start(m_pool); queueTask(task); } void ImapResourceBase::taskDestroyed(QObject *task) { m_taskList.removeAll(static_cast(task)); } QStringList ImapResourceBase::serverCapabilities() const { return m_pool->serverCapabilities(); } void ImapResourceBase::cleanup() { settings()->cleanup(); ResourceBase::cleanup(); } QString ImapResourceBase::dumpResourceToString() const { QString ret; for (ResourceTask *task : qAsConst(m_taskList)) { if (!ret.isEmpty()) { ret += QLatin1String(", "); } ret += QLatin1String(task->metaObject()->className()); } return QLatin1String("IMAP tasks: ") + ret; } void ImapResourceBase::showError(const QString &message) { Q_EMIT status(Akonadi::AgentBase::Idle, message); m_statusMessageTimer->start(1000 * 10); } void ImapResourceBase::clearStatusMessage() { Q_EMIT status(Akonadi::AgentBase::Idle, QString()); } void ImapResourceBase::modifyCollection(const Collection &col) { Akonadi::CollectionModifyJob *modJob = new Akonadi::CollectionModifyJob(col, this); connect(modJob, &KJob::result, this, &ImapResourceBase::onCollectionModifyDone); } void ImapResourceBase::onCollectionModifyDone(KJob *job) { if (job->error()) { qCWarning(IMAPRESOURCE_LOG) << "Failed to modify collection: " << job->errorString(); } }