diff --git a/agents/mailfilteragent/CMakeLists.txt b/agents/mailfilteragent/CMakeLists.txt index f19778742..126108bb3 100644 --- a/agents/mailfilteragent/CMakeLists.txt +++ b/agents/mailfilteragent/CMakeLists.txt @@ -1,47 +1,46 @@ add_subdirectory( kconf_update ) add_definitions(-DTRANSLATION_DOMAIN=\"akonadi_mailfilter_agent\") set(akonadi_mailfilter_agent_SRCS dummykernel.cpp filterlogdialog.cpp filtermanager.cpp mailfilteragent.cpp mailfilterpurposemenuwidget.cpp ) qt5_add_dbus_adaptor(akonadi_mailfilter_agent_SRCS org.freedesktop.Akonadi.MailFilterAgent.xml mailfilteragent.h MailFilterAgent) ecm_qt_declare_logging_category(akonadi_mailfilter_agent_SRCS HEADER mailfilteragent_debug.h IDENTIFIER MAILFILTERAGENT_LOG CATEGORY_NAME org.kde.pim.mailfilteragent) add_executable(akonadi_mailfilter_agent ${akonadi_mailfilter_agent_SRCS}) if( APPLE ) set_target_properties(akonadi_mailfilter_agent PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${kmail_SOURCE_DIR}/agents/Info.plist.template) set_target_properties(akonadi_mailfilter_agent PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.mailfilter") set_target_properties(akonadi_mailfilter_agent PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi Email Filter") endif () target_link_libraries(akonadi_mailfilter_agent KF5::MailCommon KF5::MessageComposer KF5::PimCommon KF5::AkonadiCore KF5::AkonadiMime KF5::AkonadiAgentBase KF5::Mime KF5::IdentityManagement KF5::DBusAddons KF5::Notifications KF5::WindowSystem - KF5::IconThemes KF5::Libkdepim KF5::I18n ) install(TARGETS akonadi_mailfilter_agent ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES mailfilteragent.desktop DESTINATION "${KDE_INSTALL_DATAROOTDIR}/akonadi/agents") install(FILES akonadi_mailfilter_agent.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFY5RCDIR} ) diff --git a/agents/mailfilteragent/filtermanager.cpp b/agents/mailfilteragent/filtermanager.cpp index 6cb4da01d..e849b72ac 100644 --- a/agents/mailfilteragent/filtermanager.cpp +++ b/agents/mailfilteragent/filtermanager.cpp @@ -1,666 +1,663 @@ // /* Copyright: before 2012: missing, see KMail copyrights * Copyright (C) 2012 Andras Mantia * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "filtermanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mailfilteragent_debug.h" #include #include #include -#include #include #include #include #include #include // other headers #include #include #include #include using namespace MailCommon; class FilterManager::Private { public: Private(FilterManager *qq) : q(qq) , mRequiredPartsBasedOnAll(SearchRule::Envelope) , mTotalProgressCount(0) , mCurrentProgressCount(0) , mInboundFiltersExist(false) { - pixmapNotification = QIcon::fromTheme(QStringLiteral("view-filter")).pixmap(KIconLoader::SizeSmall, KIconLoader::SizeSmall); } void itemsFetchJobForFilterDone(KJob *job); void itemFetchJobForFilterDone(KJob *job); void moveJobResult(KJob *); void modifyJobResult(KJob *); void deleteJobResult(KJob *); void slotItemsFetchedForFilter(const Akonadi::Item::List &items); void showNotification(const QString &errorMsg, const QString &jobErrorString); bool isMatching(const Akonadi::Item &item, const MailCommon::MailFilter *filter); void beginFiltering(const Akonadi::Item &item) const; void endFiltering(const Akonadi::Item &item) const; bool atLeastOneFilterAppliesTo(const QString &accountId) const; bool atLeastOneIncomingFilterAppliesTo(const QString &accountId) const; FilterManager *q; QVector mFilters; QMap mRequiredParts; - QPixmap pixmapNotification; SearchRule::RequiredPart mRequiredPartsBasedOnAll; int mTotalProgressCount = 0; int mCurrentProgressCount = 0; bool mInboundFiltersExist = false; bool mAllFoldersFiltersExist = false; }; void FilterManager::Private::slotItemsFetchedForFilter(const Akonadi::Item::List &items) { FilterManager::FilterSet filterSet = FilterManager::Inbound; if (q->sender()->property("filterSet").isValid()) { filterSet = static_cast(q->sender()->property("filterSet").toInt()); } QVector listMailFilters; if (q->sender()->property("listFilters").isValid()) { const QStringList listFilters = q->sender()->property("listFilters").toStringList(); //TODO improve it for (const QString &filterId : listFilters) { for (MailCommon::MailFilter *filter : qAsConst(mFilters)) { if (filter->identifier() == filterId) { listMailFilters << filter; break; } } } } if (listMailFilters.isEmpty()) { listMailFilters = mFilters; } bool needsFullPayload = q->sender()->property("needsFullPayload").toBool(); for (const Akonadi::Item &item : qAsConst(items)) { ++mCurrentProgressCount; if ((mTotalProgressCount > 0) && (mCurrentProgressCount != mTotalProgressCount)) { const QString statusMsg = i18n("Filtering message %1 of %2", mCurrentProgressCount, mTotalProgressCount); Q_EMIT q->progressMessage(statusMsg); Q_EMIT q->percent(mCurrentProgressCount * 100 / mTotalProgressCount); } else { Q_EMIT q->percent(0); } const bool filterResult = q->process(listMailFilters, item, needsFullPayload, filterSet); if (mCurrentProgressCount == mTotalProgressCount) { mTotalProgressCount = 0; mCurrentProgressCount = 0; } if (!filterResult) { Q_EMIT q->filteringFailed(item); // something went horribly wrong (out of space?) //CommonKernel->emergencyExit( i18n( "Unable to process messages: " ) + QString::fromLocal8Bit( strerror( errno ) ) ); } } } void FilterManager::Private::itemsFetchJobForFilterDone(KJob *job) { if (job->error()) { qCCritical(MAILFILTERAGENT_LOG) << "Error while fetching items. " << job->error() << job->errorString(); } } void FilterManager::Private::itemFetchJobForFilterDone(KJob *job) { if (job->error()) { qCCritical(MAILFILTERAGENT_LOG) << "Error while fetching item. " << job->error() << job->errorString(); return; } const Akonadi::ItemFetchJob *fetchJob = qobject_cast(job); const Akonadi::Item::List items = fetchJob->items(); if (items.isEmpty()) { qCCritical(MAILFILTERAGENT_LOG) << "Error while fetching item: item not found"; return; } const QString resourceId = fetchJob->property("resourceId").toString(); bool needsFullPayload = q->requiredPart(resourceId) != SearchRule::Envelope; if (job->property("filterId").isValid()) { const QString filterId = job->property("filterId").toString(); // find correct filter object MailCommon::MailFilter *wantedFilter = nullptr; for (MailCommon::MailFilter *filter : qAsConst(mFilters)) { if (filter->identifier() == filterId) { wantedFilter = filter; break; } } if (!wantedFilter) { qCCritical(MAILFILTERAGENT_LOG) << "Cannot find filter object with id" << filterId; return; } if (!q->process(items.first(), needsFullPayload, wantedFilter)) { Q_EMIT q->filteringFailed(items.first()); } } else { const FilterManager::FilterSet set = static_cast(job->property("filterSet").toInt()); if (!q->process(items.first(), needsFullPayload, set, !resourceId.isEmpty(), resourceId)) { Q_EMIT q->filteringFailed(items.first()); } } } void FilterManager::Private::moveJobResult(KJob *job) { if (job->error()) { const Akonadi::ItemMoveJob *movejob = qobject_cast(job); if (movejob) { qCCritical(MAILFILTERAGENT_LOG) << "Error while moving items. " << job->error() << job->errorString() << " to destinationCollection.id() :" << movejob->destinationCollection().id(); } else { qCCritical(MAILFILTERAGENT_LOG) << "Error while moving items. " << job->error() << job->errorString(); } //Laurent: not real info and when we have 200 errors it's very long to click all the time on ok. showNotification(i18n("Error applying mail filter move"), job->errorString()); } } void FilterManager::Private::deleteJobResult(KJob *job) { if (job->error()) { qCCritical(MAILFILTERAGENT_LOG) << "Error while delete items. " << job->error() << job->errorString(); showNotification(i18n("Error applying mail filter delete"), job->errorString()); } } void FilterManager::Private::modifyJobResult(KJob *job) { if (job->error()) { qCCritical(MAILFILTERAGENT_LOG) << "Error while modifying items. " << job->error() << job->errorString(); showNotification(i18n("Error applying mail filter modifications"), job->errorString()); } } void FilterManager::Private::showNotification(const QString &errorMsg, const QString &jobErrorString) { KNotification *notify = new KNotification(QStringLiteral("mailfilterjoberror")); notify->setComponentName(QStringLiteral("akonadi_mailfilter_agent")); - notify->setPixmap(pixmapNotification); + notify->setIconName(QStringLiteral("view-filter")); notify->setText(errorMsg + QLatin1Char('\n') + jobErrorString); notify->sendEvent(); } bool FilterManager::Private::isMatching(const Akonadi::Item &item, const MailCommon::MailFilter *filter) { bool result = false; if (FilterLog::instance()->isLogging()) { QString logText(i18n("Evaluating filter rules: ")); logText.append(filter->pattern()->asString()); FilterLog::instance()->add(logText, FilterLog::PatternDescription); } if (filter->pattern()->matches(item)) { if (FilterLog::instance()->isLogging()) { FilterLog::instance()->add(i18n("Filter rules have matched."), FilterLog::PatternResult); } result = true; } return result; } void FilterManager::Private::beginFiltering(const Akonadi::Item &item) const { if (FilterLog::instance()->isLogging()) { FilterLog::instance()->addSeparator(); if (item.hasPayload()) { KMime::Message::Ptr msg = item.payload(); const QString subject = msg->subject()->asUnicodeString(); const QString from = msg->from()->asUnicodeString(); const QDateTime dateTime = msg->date()->dateTime(); const QString date = QLocale().toString(dateTime, QLocale::LongFormat); const QString logText(i18n("Begin filtering on message \"%1\" from \"%2\" at \"%3\" :", subject, from, date)); FilterLog::instance()->add(logText, FilterLog::PatternDescription); } } } void FilterManager::Private::endFiltering(const Akonadi::Item & /*item*/) const { } bool FilterManager::Private::atLeastOneFilterAppliesTo(const QString &accountId) const { for (const MailCommon::MailFilter *filter : qAsConst(mFilters)) { if (filter->applyOnAccount(accountId)) { return true; } } return false; } bool FilterManager::Private::atLeastOneIncomingFilterAppliesTo(const QString &accountId) const { for (const MailCommon::MailFilter *filter : qAsConst(mFilters)) { if (filter->applyOnInbound() && filter->applyOnAccount(accountId)) { return true; } } return false; } FilterManager::FilterManager(QObject *parent) : QObject(parent) , d(new Private(this)) { readConfig(); } FilterManager::~FilterManager() { clear(); delete d; } void FilterManager::clear() { qDeleteAll(d->mFilters); d->mFilters.clear(); } void FilterManager::readConfig() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); // use akonadi_mailfilter_agentrc config->reparseConfiguration(); clear(); QStringList emptyFilters; d->mFilters = FilterImporterExporter::readFiltersFromConfig(config, emptyFilters); d->mRequiredParts.clear(); d->mRequiredPartsBasedOnAll = SearchRule::Envelope; if (!d->mFilters.isEmpty()) { const Akonadi::AgentInstance::List agents = Akonadi::AgentManager::self()->instances(); for (const Akonadi::AgentInstance &agent : agents) { const QString id = agent.identifier(); auto it = std::max_element(d->mFilters.constBegin(), d->mFilters.constEnd(), [id](MailCommon::MailFilter *lhs, MailCommon::MailFilter *rhs) { return lhs->requiredPart(id) < rhs->requiredPart(id); }); d->mRequiredParts[id] = (*it)->requiredPart(id); d->mRequiredPartsBasedOnAll = qMax(d->mRequiredPartsBasedOnAll, d->mRequiredParts[id]); } } // check if at least one filter is to be applied on inbound mail for (auto i = d->mFilters.cbegin(), e = d->mFilters.cend(); i != e && (!d->mInboundFiltersExist || !d->mAllFoldersFiltersExist); ++i) { if ((*i)->applyOnInbound()) { d->mInboundFiltersExist = true; } if ((*i)->applyOnAllFoldersInbound()) { d->mAllFoldersFiltersExist = true; } } Q_EMIT filterListUpdated(); } void FilterManager::mailCollectionRemoved(const Akonadi::Collection &collection) { QVector::const_iterator end(d->mFilters.constEnd()); for (QVector::const_iterator it = d->mFilters.constBegin(); it != end; ++it) { (*it)->folderRemoved(collection, Akonadi::Collection()); } } void FilterManager::agentRemoved(const QString &identifier) { QVector::const_iterator end(d->mFilters.constEnd()); for (QVector::const_iterator it = d->mFilters.constBegin(); it != end; ++it) { (*it)->agentRemoved(identifier); } } void FilterManager::filter(const Akonadi::Item &item, FilterManager::FilterSet set, const QString &resourceId) { Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(item, this); job->setProperty("filterSet", static_cast(set)); job->setProperty("resourceId", resourceId); SearchRule::RequiredPart requestedPart = requiredPart(resourceId); if (requestedPart == SearchRule::CompleteMessage) { job->fetchScope().fetchFullPayload(true); } else if (requestedPart == SearchRule::Header) { job->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Header, true); } else { job->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Envelope, true); } job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); connect(job, &Akonadi::ItemFetchJob::result, this, [this](KJob *job) { d->itemFetchJobForFilterDone(job); }); } void FilterManager::filter(const Akonadi::Item &item, const QString &filterId, const QString &resourceId) { Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(item, this); job->setProperty("filterId", filterId); SearchRule::RequiredPart requestedPart = requiredPart(resourceId); if (requestedPart == SearchRule::CompleteMessage) { job->fetchScope().fetchFullPayload(true); } else if (requestedPart == SearchRule::Header) { job->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Header, true); } else { job->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Envelope, true); } job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); connect(job, &Akonadi::ItemFetchJob::result, this, [this](KJob *job) { d->itemFetchJobForFilterDone(job); }); } bool FilterManager::process(const Akonadi::Item &item, bool needsFullPayload, const MailFilter *filter) { if (!filter) { return false; } if (!filter->isEnabled()) { return true; } if (!item.hasPayload()) { qCCritical(MAILFILTERAGENT_LOG) << "Filter is null or item doesn't have correct payload."; return false; } if (d->isMatching(item, filter)) { // do the actual filtering stuff d->beginFiltering(item); ItemContext context(item, needsFullPayload); bool stopIt = false; bool applyOnOutbound = false; if (filter->execActions(context, stopIt, applyOnOutbound) == MailCommon::MailFilter::CriticalError) { return false; } d->endFiltering(item); if (!processContextItem(context)) { return false; } } return true; } bool FilterManager::processContextItem(ItemContext context) { const KMime::Message::Ptr msg = context.item().payload(); msg->assemble(); auto col = Akonadi::EntityTreeModel::updatedCollection(MailCommon::Kernel::self()->kernelIf()->collectionModel(), context.item().parentCollection()); const bool itemCanDelete = (col.rights() & Akonadi::Collection::CanDeleteItem); if (context.deleteItem()) { if (itemCanDelete) { Akonadi::ItemDeleteJob *deleteJob = new Akonadi::ItemDeleteJob(context.item(), this); connect(deleteJob, &Akonadi::ItemDeleteJob::result, this, [this](KJob *job) { d->deleteJobResult(job); }); } else { return false; } } else { if (context.moveTargetCollection().isValid() && context.item().storageCollectionId() != context.moveTargetCollection().id()) { if (itemCanDelete) { Akonadi::ItemMoveJob *moveJob = new Akonadi::ItemMoveJob(context.item(), context.moveTargetCollection(), this); connect(moveJob, &Akonadi::ItemMoveJob::result, this, [this](KJob *job) { d->moveJobResult(job); }); } else { return false; } } if (context.needsPayloadStore() || context.needsFlagStore()) { Akonadi::Item item = context.item(); //the item might be in a new collection with a different remote id, so don't try to force on it //the previous remote id. Example: move to another collection on another resource => new remoteId, but our context.item() //remoteid still holds the old one. Without clearing it, we try to enforce that on the new location, which is //anything but good (and the server replies with "NO Only resources can modify remote identifiers" item.setRemoteId(QString()); Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob(item, this); modifyJob->disableRevisionCheck(); //no conflict handling for mails as no other process could change the mail body and we don't care about flag conflicts //The below is a safety check to ignore modifying payloads if it was not requested, //as in that case we might change the payload to an invalid one modifyJob->setIgnorePayload(!context.needsFullPayload()); connect(modifyJob, &Akonadi::ItemModifyJob::result, this, [this](KJob *job) { d->modifyJobResult(job); }); } } return true; } bool FilterManager::process(const QVector< MailFilter * > &mailFilters, const Akonadi::Item &item, bool needsFullPayload, FilterManager::FilterSet set, bool account, const QString &accountId) { if (set == NoSet) { qCDebug(MAILFILTERAGENT_LOG) << "FilterManager: process() called with not filter set selected"; return false; } if (!item.hasPayload()) { qCCritical(MAILFILTERAGENT_LOG) << "Filter is null or item doesn't have correct payload."; return false; } bool stopIt = false; d->beginFiltering(item); ItemContext context(item, needsFullPayload); QVector::const_iterator end(mailFilters.constEnd()); const bool applyOnOutbound = ((set & Outbound) || (set & BeforeOutbound)); for (QVector::const_iterator it = mailFilters.constBegin(); !stopIt && it != end; ++it) { if ((*it)->isEnabled()) { const bool inboundOk = ((set & Inbound) && (*it)->applyOnInbound()); const bool outboundOk = ((set & Outbound) && (*it)->applyOnOutbound()); const bool beforeOutboundOk = ((set & BeforeOutbound) && (*it)->applyBeforeOutbound()); const bool explicitOk = ((set & Explicit) && (*it)->applyOnExplicit()); const bool allFoldersOk = ((set & AllFolders) && (*it)->applyOnAllFoldersInbound()); const bool accountOk = (!account || (*it)->applyOnAccount(accountId)); if ((inboundOk && accountOk) || (allFoldersOk && accountOk) || outboundOk || beforeOutboundOk || explicitOk) { // filter is applicable if (d->isMatching(context.item(), *it)) { // execute actions: if ((*it)->execActions(context, stopIt, applyOnOutbound) == MailCommon::MailFilter::CriticalError) { return false; } } } } } d->endFiltering(item); if (!processContextItem(context)) { return false; } return true; } bool FilterManager::process(const Akonadi::Item &item, bool needsFullPayload, FilterSet set, bool account, const QString &accountId) { return process(d->mFilters, item, needsFullPayload, set, account, accountId); } QString FilterManager::createUniqueName(const QString &name) const { QString uniqueName = name; int counter = 0; bool found = true; while (found) { found = false; for (const MailCommon::MailFilter *filter : qAsConst(d->mFilters)) { if (!filter->name().compare(uniqueName)) { found = true; ++counter; uniqueName = name; uniqueName += QLatin1String(" (") + QString::number(counter) + QLatin1String(")"); break; } } } return uniqueName; } MailCommon::SearchRule::RequiredPart FilterManager::requiredPart(const QString &id) const { if (id.isEmpty()) { return d->mRequiredPartsBasedOnAll; } return d->mRequiredParts.contains(id) ? d->mRequiredParts[id] : SearchRule::Envelope; } void FilterManager::dump() const { for (const MailCommon::MailFilter *filter : qAsConst(d->mFilters)) { qCDebug(MAILFILTERAGENT_LOG) << filter->asString(); } } void FilterManager::applySpecificFilters(const Akonadi::Item::List &selectedMessages, SearchRule::RequiredPart requiredPart, const QStringList &listFilters, FilterSet filterSet) { Q_EMIT progressMessage(i18n("Filtering messages")); d->mTotalProgressCount = selectedMessages.size(); d->mCurrentProgressCount = 0; Akonadi::ItemFetchJob *itemFetchJob = new Akonadi::ItemFetchJob(selectedMessages, this); if (requiredPart == SearchRule::CompleteMessage) { itemFetchJob->fetchScope().fetchFullPayload(true); } else if (requiredPart == SearchRule::Header) { itemFetchJob->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Header, true); } else { itemFetchJob->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Envelope, true); } itemFetchJob->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); itemFetchJob->setProperty("listFilters", QVariant::fromValue(listFilters)); itemFetchJob->setProperty("filterSet", QVariant::fromValue(static_cast(filterSet))); itemFetchJob->setProperty("needsFullPayload", requiredPart != SearchRule::Envelope); connect(itemFetchJob, &Akonadi::ItemFetchJob::itemsReceived, this, [this](const Akonadi::Item::List &lst) { d->slotItemsFetchedForFilter(lst); }); connect(itemFetchJob, &Akonadi::ItemFetchJob::result, this, [this](KJob *job) { d->itemsFetchJobForFilterDone(job); }); } void FilterManager::applyFilters(const Akonadi::Item::List &selectedMessages, FilterSet filterSet) { Q_EMIT progressMessage(i18n("Filtering messages")); d->mTotalProgressCount = selectedMessages.size(); d->mCurrentProgressCount = 0; Akonadi::ItemFetchJob *itemFetchJob = new Akonadi::ItemFetchJob(selectedMessages, this); SearchRule::RequiredPart requiredParts = requiredPart(QString()); if (requiredParts == SearchRule::CompleteMessage) { itemFetchJob->fetchScope().fetchFullPayload(true); } else if (requiredParts == SearchRule::Header) { itemFetchJob->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Header, true); } else { itemFetchJob->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Envelope, true); } itemFetchJob->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); itemFetchJob->setProperty("filterSet", QVariant::fromValue(static_cast(filterSet))); itemFetchJob->setProperty("needsFullPayload", requiredParts != SearchRule::Envelope); connect(itemFetchJob, &Akonadi::ItemFetchJob::itemsReceived, this, [this](const Akonadi::Item::List &lst) { d->slotItemsFetchedForFilter(lst); }); connect(itemFetchJob, &Akonadi::ItemFetchJob::result, this, [this](KJob *job) { d->itemsFetchJobForFilterDone(job); }); } bool FilterManager::hasAllFoldersFilter() const { return d->mAllFoldersFiltersExist; } #include "moc_filtermanager.cpp" diff --git a/agents/mailfilteragent/mailfilteragent.cpp b/agents/mailfilteragent/mailfilteragent.cpp index 8a70a7573..7a3d2be94 100644 --- a/agents/mailfilteragent/mailfilteragent.cpp +++ b/agents/mailfilteragent/mailfilteragent.cpp @@ -1,437 +1,435 @@ /* Copyright (c) 2011 Tobias Koenig 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 "mailfilteragent.h" #include "mailcommon/dbusoperators.h" #include "dummykernel.h" #include "filterlogdialog.h" #include "filtermanager.h" #include "mailfilteragentadaptor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mailfilteragent_debug.h" -#include #include #include #include #include #include #include #include #include #include #include bool MailFilterAgent::isFilterableCollection(const Akonadi::Collection &collection) const { if (!collection.contentMimeTypes().contains(KMime::Message::mimeType())) { return false; } return m_filterManager->hasAllFoldersFilter() || MailCommon::Kernel::folderIsInbox(collection); //TODO: check got filter attribute here } MailFilterAgent::MailFilterAgent(const QString &id) : Akonadi::AgentBase(id) , m_filterLogDialog(nullptr) { Kdelibs4ConfigMigrator migrate(QStringLiteral("mailfilteragent")); migrate.setConfigFiles(QStringList() << QStringLiteral("akonadi_mailfilter_agentrc") << QStringLiteral("akonadi_mailfilter_agent.notifyrc")); migrate.migrate(); Akonadi::AttributeFactory::registerAttribute(); mMailFilterKernel = new DummyKernel(this); CommonKernel->registerKernelIf(mMailFilterKernel); //register KernelIf early, it is used by the Filter classes CommonKernel->registerSettingsIf(mMailFilterKernel); //SettingsIf is used in FolderTreeWidget m_filterManager = new FilterManager(this); connect(m_filterManager, &FilterManager::percent, this, &MailFilterAgent::emitProgress); connect(m_filterManager, &FilterManager::progressMessage, this, &MailFilterAgent::emitProgressMessage); Akonadi::Monitor *collectionMonitor = new Akonadi::Monitor(this); collectionMonitor->setObjectName(QStringLiteral("MailFilterCollectionMonitor")); collectionMonitor->fetchCollection(true); collectionMonitor->ignoreSession(Akonadi::Session::defaultSession()); collectionMonitor->collectionFetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All); collectionMonitor->setMimeTypeMonitored(KMime::Message::mimeType()); connect(collectionMonitor, &Akonadi::Monitor::collectionAdded, this, &MailFilterAgent::mailCollectionAdded); connect(collectionMonitor, qOverload(&Akonadi::Monitor::collectionChanged), this, &MailFilterAgent::mailCollectionChanged); connect(collectionMonitor, &Akonadi::Monitor::collectionRemoved, this, &MailFilterAgent::mailCollectionRemoved); connect(Akonadi::AgentManager::self(), &Akonadi::AgentManager::instanceRemoved, this, &MailFilterAgent::slotInstanceRemoved); QTimer::singleShot(0, this, &MailFilterAgent::initializeCollections); qDBusRegisterMetaType >(); new MailFilterAgentAdaptor(this); KDBusConnectionPool::threadConnection().registerObject(QStringLiteral("/MailFilterAgent"), this, QDBusConnection::ExportAdaptors); const QString service = Akonadi::ServerManager::self()->agentServiceName(Akonadi::ServerManager::Agent, QStringLiteral("akonadi_mailfilter_agent")); KDBusConnectionPool::threadConnection().registerService(service); //Enabled or not filterlogdialog KSharedConfig::Ptr config = KSharedConfig::openConfig(); if (config->hasGroup("FilterLog")) { KConfigGroup group(config, "FilterLog"); if (group.readEntry("Enabled", false)) { - const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("view-filter")).pixmap(KIconLoader::SizeSmall, KIconLoader::SizeSmall); KNotification *notify = new KNotification(QStringLiteral("mailfilterlogenabled")); notify->setComponentName(QApplication::applicationDisplayName()); - notify->setPixmap(pixmap); + notify->setIconName(QStringLiteral("view-filter")); notify->setText(i18nc("Notification when the filter log was enabled", "Mail Filter Log Enabled")); notify->sendEvent(); } } changeRecorder()->itemFetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); changeRecorder()->itemFetchScope().setCacheOnly(true); changeRecorder()->fetchCollection(true); changeRecorder()->setChangeRecordingEnabled(false); mProgressCounter = 0; mProgressTimer = new QTimer(this); connect(mProgressTimer, &QTimer::timeout, this, [this]() { emitProgress(); }); itemMonitor = new Akonadi::Monitor(this); itemMonitor->setObjectName(QStringLiteral("MailFilterItemMonitor")); itemMonitor->itemFetchScope().setFetchRemoteIdentification(true); itemMonitor->itemFetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); connect(itemMonitor, &Akonadi::Monitor::itemChanged, this, &MailFilterAgent::slotItemChanged); } MailFilterAgent::~MailFilterAgent() { delete m_filterLogDialog; } void MailFilterAgent::configure(WId windowId) { Q_UNUSED(windowId); } void MailFilterAgent::initializeCollections() { m_filterManager->readConfig(); Akonadi::CollectionFetchJob *job = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this); job->fetchScope().setContentMimeTypes({ KMime::Message::mimeType() }); connect(job, &Akonadi::CollectionFetchJob::result, this, &MailFilterAgent::initialCollectionFetchingDone); } void MailFilterAgent::initialCollectionFetchingDone(KJob *job) { if (job->error()) { qCWarning(MAILFILTERAGENT_LOG) << job->errorString(); return; //TODO: proper error handling } const auto fetchJob = qobject_cast(job); const auto pop3ResourceMap = MailCommon::Kernel::pop3ResourceTargetCollection(); const auto lstCols = fetchJob->collections(); for (const Akonadi::Collection &collection : lstCols) { if (isFilterableCollection(collection)) { changeRecorder()->setCollectionMonitored(collection, true); } else { for (auto pop3ColId : pop3ResourceMap) { if (collection.id() == pop3ColId) { changeRecorder()->setCollectionMonitored(collection, true); break; } } } } Q_EMIT status(AgentBase::Idle, i18n("Ready")); Q_EMIT percent(100); QTimer::singleShot(2000, this, &MailFilterAgent::clearMessage); } void MailFilterAgent::clearMessage() { Q_EMIT status(AgentBase::Idle, QString()); } void MailFilterAgent::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) { /* The monitor mimetype filter would override the collection filter, therefor we have to check * for the mimetype of the item here. */ if (item.mimeType() != KMime::Message::mimeType()) { qCDebug(MAILFILTERAGENT_LOG) << "MailFilterAgent::itemAdded called for a non-message item!"; return; } if (item.remoteId().isEmpty()) { itemMonitor->setItemMonitored(item); } else { filterItem(item, collection); } } void MailFilterAgent::slotItemChanged(const Akonadi::Item &item) { if (item.remoteId().isEmpty()) { return; } // now we have the remoteId itemMonitor->setItemMonitored(item, false); filterItem(item, item.parentCollection()); } void MailFilterAgent::filterItem(const Akonadi::Item &item, const Akonadi::Collection &collection) { MailCommon::SearchRule::RequiredPart requiredPart = m_filterManager->requiredPart(collection.resource()); Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(item); connect(job, &Akonadi::ItemFetchJob::itemsReceived, this, &MailFilterAgent::itemsReceiviedForFiltering); if (requiredPart == MailCommon::SearchRule::CompleteMessage) { job->fetchScope().fetchFullPayload(); } else if (requiredPart == MailCommon::SearchRule::Header) { job->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Header, true); } else { job->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Envelope, true); } job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); job->fetchScope().fetchAttribute(); job->setProperty("resource", collection.resource()); //TODO: Error handling? } void MailFilterAgent::itemsReceiviedForFiltering(const Akonadi::Item::List &items) { if (items.isEmpty()) { qCDebug(MAILFILTERAGENT_LOG) << "MailFilterAgent::itemsReceiviedForFiltering items is empty!"; return; } Akonadi::Item item = items.first(); /* * happens when item no longer exists etc, and queue compression didn't happen yet */ if (!item.hasPayload()) { qCDebug(MAILFILTERAGENT_LOG) << "MailFilterAgent::itemsReceiviedForFiltering item has no payload!"; return; } Akonadi::MessageStatus status; status.setStatusFromFlags(item.flags()); if (status.isRead() || status.isSpam() || status.isIgnored()) { return; } QString resource = sender()->property("resource").toString(); const Akonadi::Pop3ResourceAttribute *pop3ResourceAttribute = item.attribute(); if (pop3ResourceAttribute) { resource = pop3ResourceAttribute->pop3AccountName(); } emitProgressMessage(i18n("Filtering in %1", Akonadi::AgentManager::self()->instance(resource).name())); m_filterManager->process(item, m_filterManager->requiredPart(resource), FilterManager::Inbound, true, resource); emitProgress(++mProgressCounter); mProgressTimer->start(1000); } void MailFilterAgent::mailCollectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &) { if (isFilterableCollection(collection)) { changeRecorder()->setCollectionMonitored(collection, true); } } void MailFilterAgent::mailCollectionChanged(const Akonadi::Collection &collection) { changeRecorder()->setCollectionMonitored(collection, isFilterableCollection(collection)); } void MailFilterAgent::mailCollectionRemoved(const Akonadi::Collection &collection) { changeRecorder()->setCollectionMonitored(collection, false); m_filterManager->mailCollectionRemoved(collection); } QString MailFilterAgent::createUniqueName(const QString &nameTemplate) { return m_filterManager->createUniqueName(nameTemplate); } void MailFilterAgent::filterItems(const QList< qint64 > &itemIds, int filterSet) { Akonadi::Item::List items; items.reserve(itemIds.count()); for (qint64 id : itemIds) { items << Akonadi::Item(id); } m_filterManager->applyFilters(items, static_cast(filterSet)); } void MailFilterAgent::filterCollections(const QList &collections, int filterSet) { for (qint64 id: collections) { auto ifj = new Akonadi::ItemFetchJob{ Akonadi::Collection{ id }, this }; ifj->setDeliveryOption(Akonadi::ItemFetchJob::EmitItemsInBatches); connect(ifj, &Akonadi::ItemFetchJob::itemsReceived, this, [=](const Akonadi::Item::List &items) { m_filterManager->applyFilters(items, static_cast(filterSet)); }); } } void MailFilterAgent::applySpecificFilters(const QList< qint64 > &itemIds, int requiresPart, const QStringList &listFilters) { Akonadi::Item::List items; items.reserve(itemIds.count()); for (qint64 id : itemIds) { items << Akonadi::Item(id); } m_filterManager->applySpecificFilters(items, static_cast(requiresPart), listFilters); } void MailFilterAgent::applySpecificFiltersOnCollections(const QList &colIds, const QStringList &listFilters, int filterSet) { // TODO: Actually calculate this based on the listFilters' requirements const auto requiresParts = MailCommon::SearchRule::CompleteMessage; for (qint64 id : colIds) { auto ifj = new Akonadi::ItemFetchJob{ Akonadi::Collection{ id }, this }; ifj->setDeliveryOption(Akonadi::ItemFetchJob::EmitItemsInBatches); connect(ifj, &Akonadi::ItemFetchJob::itemsReceived, this, [=](const Akonadi::Item::List &items) { m_filterManager->applySpecificFilters(items, requiresParts, listFilters, static_cast(filterSet)); }); } } void MailFilterAgent::filterItem(qint64 item, int filterSet, const QString &resourceId) { m_filterManager->filter(Akonadi::Item(item), static_cast(filterSet), resourceId); } void MailFilterAgent::filter(qint64 item, const QString &filterIdentifier, const QString &resourceId) { m_filterManager->filter(Akonadi::Item(item), filterIdentifier, resourceId); } void MailFilterAgent::reload() { const Akonadi::Collection::List collections = changeRecorder()->collectionsMonitored(); for (const Akonadi::Collection &collection : collections) { changeRecorder()->setCollectionMonitored(collection, false); } initializeCollections(); } void MailFilterAgent::showFilterLogDialog(qlonglong windowId) { if (!m_filterLogDialog) { m_filterLogDialog = new FilterLogDialog(nullptr); } KWindowSystem::setMainWindow(m_filterLogDialog->windowHandle(), windowId); m_filterLogDialog->show(); m_filterLogDialog->raise(); m_filterLogDialog->activateWindow(); m_filterLogDialog->setModal(false); } void MailFilterAgent::emitProgress(int p) { if (p == 0) { mProgressTimer->stop(); Q_EMIT status(AgentBase::Idle, QString()); } mProgressCounter = p; Q_EMIT percent(p); } void MailFilterAgent::emitProgressMessage(const QString &message) { Q_EMIT status(AgentBase::Running, message); } QString MailFilterAgent::printCollectionMonitored() const { QString printDebugCollection; const Akonadi::Collection::List collections = changeRecorder()->collectionsMonitored(); if (collections.isEmpty()) { printDebugCollection = QStringLiteral("No collection is monitored!"); } else { for (const Akonadi::Collection &collection : collections) { if (!printDebugCollection.isEmpty()) { printDebugCollection += QLatin1Char('\n'); } printDebugCollection += QStringLiteral("Collection name: %1\n").arg(collection.name()); printDebugCollection += QStringLiteral("Collection id: %1\n").arg(collection.id()); } } return printDebugCollection; } void MailFilterAgent::expunge(qint64 collectionId) { mMailFilterKernel->expunge(collectionId, false); } void MailFilterAgent::slotInstanceRemoved(const Akonadi::AgentInstance &instance) { m_filterManager->agentRemoved(instance.identifier()); } AKONADI_AGENT_MAIN(MailFilterAgent)