diff --git a/agents/unifiedmailboxagent/CMakeLists.txt b/agents/unifiedmailboxagent/CMakeLists.txt index 8a21518b9..1680f57b8 100644 --- a/agents/unifiedmailboxagent/CMakeLists.txt +++ b/agents/unifiedmailboxagent/CMakeLists.txt @@ -1,49 +1,51 @@ add_definitions(-DTRANSLATION_DOMAIN=\"akonadi_unifiedmailbox_agent\") set(CMAKE_CXX_STANDARD 14) if(BUILD_TESTING) add_subdirectory(autotests) endif() set(unifiedmailbox_agent_SRCS unifiedmailbox.cpp unifiedmailboxagent.cpp unifiedmailboxmanager.cpp unifiedmailboxeditor.cpp settingsdialog.cpp mailkernel.cpp ) ecm_qt_declare_logging_category(unifiedmailbox_agent_SRCS HEADER unifiedmailboxagent_debug.h IDENTIFIER agent_log CATEGORY_NAME org.kde.pim.unifiedmailboxagent) kconfig_add_kcfg_files(unifiedmailbox_agent_SRCS settings.kcfgc ) +qt5_add_dbus_adaptor(unifiedmailbox_agent_SRCS org.freedesktop.Akonadi.UnifiedMailboxAgent.xml unifiedmailboxagent.h UnifiedMailboxAgent) add_executable(akonadi_unifiedmailbox_agent ${unifiedmailbox_agent_SRCS}) target_link_libraries(akonadi_unifiedmailbox_agent KF5::AkonadiAgentBase KF5::AkonadiMime KF5::AkonadiWidgets KF5::Mime KF5::I18n KF5::IdentityManagement KF5::WidgetsAddons KF5::IconThemes KF5::ItemModels KF5::MailCommon + KF5::DBusAddons ) if( APPLE ) set_target_properties(akonadi_unifiedmailbox_agent PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${kmail_SOURCE_DIR}/agents/Info.plist.template) set_target_properties(akonadi_unifiedmailbox_agent PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.KF5::UnifiedMailbox") set_target_properties(akonadi_unifiedmailbox_agent PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE PIM Unified Mailbox") endif () install(TARGETS akonadi_unifiedmailbox_agent ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) install(FILES unifiedmailboxagent.desktop DESTINATION "${KDE_INSTALL_DATAROOTDIR}/akonadi/agents") diff --git a/agents/unifiedmailboxagent/org.freedesktop.Akonadi.UnifiedMailboxAgent.xml b/agents/unifiedmailboxagent/org.freedesktop.Akonadi.UnifiedMailboxAgent.xml new file mode 100644 index 000000000..3db29beef --- /dev/null +++ b/agents/unifiedmailboxagent/org.freedesktop.Akonadi.UnifiedMailboxAgent.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/agents/unifiedmailboxagent/settings.kcfg b/agents/unifiedmailboxagent/settings.kcfg index 45f8fb87a..cf85f8ca3 100644 --- a/agents/unifiedmailboxagent/settings.kcfg +++ b/agents/unifiedmailboxagent/settings.kcfg @@ -1,12 +1,15 @@ - + false - + + + false + diff --git a/agents/unifiedmailboxagent/unifiedmailboxagent.cpp b/agents/unifiedmailboxagent/unifiedmailboxagent.cpp index bfdf9e6ad..a20c7d5cf 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxagent.cpp +++ b/agents/unifiedmailboxagent/unifiedmailboxagent.cpp @@ -1,239 +1,292 @@ /* Copyright (C) 2018 Daniel Vrátil 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "unifiedmailboxagent.h" #include "unifiedmailbox.h" #include "unifiedmailboxagent_debug.h" +#include "unifiedmailboxagentadaptor.h" #include "settingsdialog.h" #include "settings.h" #include "common.h" #include #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include #include +#include #include #include #include #include #include UnifiedMailboxAgent::UnifiedMailboxAgent(const QString &id) : Akonadi::ResourceBase(id) , mBoxManager(config()) { setAgentName(i18n("Unified Mailboxes")); + new UnifiedMailboxAgentAdaptor(this); + KDBusConnectionPool::threadConnection().registerObject(QStringLiteral("/UnifiedMailboxAgent"), this, QDBusConnection::ExportAdaptors); + const auto service = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Resource, identifier()); + KDBusConnectionPool::threadConnection().registerService(service); + connect(&mBoxManager, &UnifiedMailboxManager::updateBox, this, [this](const UnifiedMailbox *box) { if (!box->collectionId()) { qCWarning(agent_log) << "MailboxManager wants us to update Box but does not have its CollectionId!?"; return; } // Schedule collection sync for the box synchronizeCollection(box->collectionId().value()); }); auto &ifs = changeRecorder()->itemFetchScope(); ifs.setAncestorRetrieval(Akonadi::ItemFetchScope::None); ifs.setCacheOnly(true); ifs.fetchFullPayload(false); - QTimer::singleShot(0, this, [this]() { - qCDebug(agent_log) << "delayed init"; - - fixSpecialCollections(); - mBoxManager.loadBoxes([this]() { - // boxes loaded, let's sync up - synchronize(); - }); - }); + if (Settings::self()->enabled()) { + QTimer::singleShot(0, this, &UnifiedMailboxAgent::delayedInit); + } } void UnifiedMailboxAgent::configure(WId windowId) { QPointer agent(this); if (SettingsDialog(config(), mBoxManager, windowId).exec() && agent) { mBoxManager.saveBoxes(); synchronize(); Q_EMIT configurationDialogAccepted(); } else { mBoxManager.loadBoxes(); } } +void UnifiedMailboxAgent::delayedInit() +{ + qCDebug(agent_log) << "delayed init"; + + fixSpecialCollections(); + mBoxManager.loadBoxes([this]() { + // boxes loaded, let's sync up + synchronize(); + }); +} + + +bool UnifiedMailboxAgent::enabledAgent() const +{ + return Settings::self()->enabled(); +} + +void UnifiedMailboxAgent::setEnableAgent(bool enabled) +{ + if (enabled != Settings::self()->enabled()) { + Settings::self()->setEnabled(enabled); + Settings::self()->save(); + if (!enabled) { + setOnline(false); + auto fetch = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this); + fetch->fetchScope().setResource(identifier()); + connect(fetch, &Akonadi::CollectionFetchJob::collectionsReceived, + this, [this](const Akonadi::Collection::List &cols) { + for (const auto &col : cols) { + new Akonadi::CollectionDeleteJob(col, this); + } + }); + } else { + setOnline(true); + delayedInit(); + } + } +} + + void UnifiedMailboxAgent::retrieveCollections() { + if (!Settings::self()->enabled()) { + collectionsRetrieved({}); + return; + } + Akonadi::Collection::List collections; Akonadi::Collection topLevel; topLevel.setName(identifier()); topLevel.setRemoteId(identifier()); topLevel.setParentCollection(Akonadi::Collection::root()); topLevel.setContentMimeTypes({Akonadi::Collection::mimeType()}); topLevel.setRights(Akonadi::Collection::ReadOnly); auto displayAttr = topLevel.attribute(Akonadi::Collection::AddIfMissing); displayAttr->setDisplayName(i18n("Unified Mailboxes")); displayAttr->setActiveIconName(QStringLiteral("globe")); collections.push_back(topLevel); for (const auto &boxIt : mBoxManager) { const auto &box = boxIt.second; Akonadi::Collection col; col.setName(box->id()); col.setRemoteId(box->id()); col.setParentCollection(topLevel); col.setContentMimeTypes({Common::MailMimeType}); col.setRights(Akonadi::Collection::CanChangeItem | Akonadi::Collection::CanDeleteItem); col.setVirtual(true); auto displayAttr = col.attribute(Akonadi::Collection::AddIfMissing); displayAttr->setDisplayName(box->name()); displayAttr->setIconName(box->icon()); collections.push_back(std::move(col)); } collectionsRetrieved(std::move(collections)); // Add mapping between boxes and collections mBoxManager.discoverBoxCollections(); } void UnifiedMailboxAgent::retrieveItems(const Akonadi::Collection &c) { + if (!Settings::self()->enabled()) { + itemsRetrieved({}); + return; + } + // First check that we have all Items from all source collections Q_EMIT status(Running, i18n("Synchronizing unified mailbox %1", c.displayName())); const auto unifiedBox = mBoxManager.unifiedMailboxFromCollection(c); if (!unifiedBox) { qCWarning(agent_log) << "Failed to retrieve box ID for collection " << c.id(); itemsRetrievedIncremental({}, {}); // fake incremental retrieval return; } const auto lastSeenEvent = QDateTime::fromSecsSinceEpoch(c.remoteRevision().toLongLong()); const auto sources = unifiedBox->sourceCollections(); for (auto source : sources) { auto fetch = new Akonadi::ItemFetchJob(Akonadi::Collection(source), this); fetch->setDeliveryOption(Akonadi::ItemFetchJob::EmitItemsInBatches); fetch->fetchScope().setFetchVirtualReferences(true); fetch->fetchScope().setCacheOnly(true); connect(fetch, &Akonadi::ItemFetchJob::itemsReceived, this, [this, c](const Akonadi::Item::List &items) { Akonadi::Item::List toLink; std::copy_if(items.cbegin(), items.cend(), std::back_inserter(toLink), [&c](const Akonadi::Item &item) { return !item.virtualReferences().contains(c); }); if (!toLink.isEmpty()) { new Akonadi::LinkJob(c, toLink, this); } }); } auto fetch = new Akonadi::ItemFetchJob(c, this); fetch->setDeliveryOption(Akonadi::ItemFetchJob::EmitItemsInBatches); fetch->fetchScope().setCacheOnly(true); fetch->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); connect(fetch, &Akonadi::ItemFetchJob::itemsReceived, this, [this, unifiedBox, c](const Akonadi::Item::List &items) { Akonadi::Item::List toUnlink; std::copy_if(items.cbegin(), items.cend(), std::back_inserter(toUnlink), [&unifiedBox](const Akonadi::Item &item) { return !unifiedBox->sourceCollections().contains(item.storageCollectionId()); }); if (!toUnlink.isEmpty()) { new Akonadi::UnlinkJob(c, toUnlink, this); } }); connect(fetch, &Akonadi::ItemFetchJob::result, this, [this]() { itemsRetrievedIncremental({}, {}); // fake incremental retrieval }); } bool UnifiedMailboxAgent::retrieveItem(const Akonadi::Item &item, const QSet &parts) { // This method should never be called by Akonadi Q_UNUSED(parts); qCWarning(agent_log) << "retrieveItem() for item" << item.id() << "called but we can't own any items! This is a bug in Akonadi"; return false; } void UnifiedMailboxAgent::fixSpecialCollection(const QString &colId, Akonadi::SpecialMailCollections::Type type) { if (colId.isEmpty()) { return; } const auto id = colId.toLongLong(); // SpecialMailCollection requires the Collection to have a Resource set as well, so // we have to retrieve it first. connect(new Akonadi::CollectionFetchJob(Akonadi::Collection(id), Akonadi::CollectionFetchJob::Base, this), &Akonadi::CollectionFetchJob::collectionsReceived, this, [type](const Akonadi::Collection::List &cols) { if (cols.count() != 1) { qCWarning(agent_log) << "Identity special collection retrieval did not find a valid collection"; return; } Akonadi::SpecialMailCollections::self()->registerCollection(type, cols.first()); }); } void UnifiedMailboxAgent::fixSpecialCollections() { // This is a tiny hack to assign proper SpecialCollectionAttribute to special collections // assigned trough Identities. This should happen automatically in KMail when user changes // the special collections on the identity page, but until recent master (2018-07-24) this // wasn't the case and there's no automatic migration, so we need to fix up manually here. if (Settings::self()->fixedSpecialCollections()) { return; } qCDebug(agent_log) << "Fixing special collections assigned from Identities"; for (const auto &identity : *KIdentityManagement::IdentityManager::self()) { if (!identity.disabledFcc()) { fixSpecialCollection(identity.fcc(), Akonadi::SpecialMailCollections::SentMail); } fixSpecialCollection(identity.drafts(), Akonadi::SpecialMailCollections::Drafts); fixSpecialCollection(identity.templates(), Akonadi::SpecialMailCollections::Templates); } Settings::self()->setFixedSpecialCollections(true); } AKONADI_RESOURCE_MAIN(UnifiedMailboxAgent) diff --git a/agents/unifiedmailboxagent/unifiedmailboxagent.h b/agents/unifiedmailboxagent/unifiedmailboxagent.h index 62f229fbb..5774389c1 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxagent.h +++ b/agents/unifiedmailboxagent/unifiedmailboxagent.h @@ -1,59 +1,63 @@ /* Copyright (C) 2018 Daniel Vrátil 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef UNIFIEDMAILBOXAGENT_H #define UNIFIEDMAILBOXAGENT_H #include #include #include "unifiedmailboxmanager.h" #include #include /* Despite its name, this is actually a Resource, but it acts as an Agent: it * listens to notifications about Items that belong to other resources and acts * on them. * The only reason this agent has to implement ResourceBase is to be able to own * the virtual unified collections into which content of other collections is * linked. */ class UnifiedMailboxAgent : public Akonadi::ResourceBase { Q_OBJECT public: explicit UnifiedMailboxAgent(const QString &id); ~UnifiedMailboxAgent() override = default; void configure(WId windowId) override; + void setEnableAgent(bool enable); + bool enabledAgent() const; + void retrieveCollections() override; void retrieveItems(const Akonadi::Collection &collection) override; bool retrieveItem(const Akonadi::Item &item, const QSet &parts) override; - private: + void delayedInit(); + void fixSpecialCollections(); void fixSpecialCollection(const QString &colId, Akonadi::SpecialMailCollections::Type type); UnifiedMailboxManager mBoxManager; }; #endif diff --git a/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp b/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp index f3f959c4a..2aa8854bf 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp +++ b/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp @@ -1,428 +1,433 @@ /* Copyright (C) 2018 Daniel Vrátil 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "unifiedmailboxmanager.h" #include "unifiedmailbox.h" #include "unifiedmailboxagent_debug.h" #include "utils.h" #include "common.h" #include #include #include #include #include #include #include #include #include #include #include +#include namespace { /** * A little RAII helper to make sure changeProcessed() and replayNext() gets * called on the ChangeRecorder whenever we are done with handling a change. */ class ReplayNextOnExit { public: ReplayNextOnExit(Akonadi::ChangeRecorder &recorder) : mRecorder(recorder) {} ~ReplayNextOnExit() { mRecorder.changeProcessed(); mRecorder.replayNext(); } private: Akonadi::ChangeRecorder &mRecorder; }; } // static bool UnifiedMailboxManager::isUnifiedMailbox(const Akonadi::Collection &col) { #ifdef UNIT_TESTS return col.parentCollection().name() == Common::AgentIdentifier; #else return col.resource() == Common::AgentIdentifier; #endif } UnifiedMailboxManager::UnifiedMailboxManager(KSharedConfigPtr config, QObject* parent) : QObject(parent) , mConfig(std::move(config)) { mMonitor.setObjectName(QStringLiteral("UnifiedMailboxChangeRecorder")); mMonitor.setConfig(&mMonitorSettings); mMonitor.setChangeRecordingEnabled(true); mMonitor.setTypeMonitored(Akonadi::Monitor::Items); mMonitor.setTypeMonitored(Akonadi::Monitor::Collections); mMonitor.itemFetchScope().setCacheOnly(true); mMonitor.itemFetchScope().setFetchRemoteIdentification(false); mMonitor.itemFetchScope().setFetchModificationTime(false); mMonitor.collectionFetchScope().fetchAttribute(); connect(&mMonitor, &Akonadi::Monitor::itemAdded, this, [this](const Akonadi::Item &item, const Akonadi::Collection &collection) { ReplayNextOnExit replayNext(mMonitor); qCDebug(agent_log) << "Item" << item.id() << "added to collection" << collection.id(); const auto box = unifiedMailboxForSource(collection.id()); if (!box) { qCWarning(agent_log) << "Failed to find unified mailbox for source collection " << collection.id(); return; } if (!box->collectionId()) { qCWarning(agent_log) << "Missing box->collection mapping for unified mailbox" << box->id(); return; } new Akonadi::LinkJob(Akonadi::Collection{box->collectionId().value()}, {item}, this); }); connect(&mMonitor, &Akonadi::Monitor::itemsRemoved, this, [this](const Akonadi::Item::List &items) { ReplayNextOnExit replayNext(mMonitor); // Monitor did the heavy lifting for us and already figured out that // we only monitor the source collection of the Items and translated // it into REMOVE change. // This relies on Akonadi never mixing Items from different sources or // destination during batch-moves. const auto parentId = items.first().parentCollection().id(); const auto box = unifiedMailboxForSource(parentId); if (!box) { qCWarning(agent_log) << "Received Remove notification for Items belonging to" << parentId << "which we don't monitor"; return; } if (!box->collectionId()) { qCWarning(agent_log) << "Missing box->collection mapping for unified mailbox" << box->id(); return; } new Akonadi::UnlinkJob(Akonadi::Collection{box->collectionId().value()}, items, this); }); connect(&mMonitor, &Akonadi::Monitor::itemsMoved, this, [this](const Akonadi::Item::List &items, const Akonadi::Collection &srcCollection, const Akonadi::Collection &dstCollection) { ReplayNextOnExit replayNext(mMonitor); if (const auto srcBox = unifiedMailboxForSource(srcCollection.id())) { // Move source collection was our source, unlink the Item from a box new Akonadi::UnlinkJob(Akonadi::Collection{srcBox->collectionId().value()}, items, this); } if (const auto dstBox = unifiedMailboxForSource(dstCollection.id())) { // Move destination collection is our source, link the Item into a box new Akonadi::LinkJob(Akonadi::Collection{dstBox->collectionId().value()}, items, this); } }); connect(&mMonitor, &Akonadi::Monitor::collectionRemoved, this, [this](const Akonadi::Collection &col) { ReplayNextOnExit replayNext(mMonitor); if (auto box = unifiedMailboxForSource(col.id())) { box->removeSourceCollection(col.id()); mMonitor.setCollectionMonitored(col, false); if (box->sourceCollections().isEmpty()) { removeBox(box->id()); } saveBoxes(); // No need to resync the box collection, the linked Items got removed by Akonadi } else { qCWarning(agent_log) << "Received notification about removal of Collection" << col.id() << "which we don't monitor"; } }); connect(&mMonitor, qOverload &>(&Akonadi::Monitor::collectionChanged), this, [this](const Akonadi::Collection &col, const QSet &parts) { ReplayNextOnExit replayNext(mMonitor); + qCDebug(agent_log) << "Collection changed:" << parts; if (!parts.contains(Akonadi::SpecialCollectionAttribute().type())) { return; } if (col.hasAttribute()) { const auto srcBox = unregisterSpecialSourceCollection(col.id()); const auto dstBox = registerSpecialSourceCollection(col); if (srcBox == dstBox) { return; } saveBoxes(); if (srcBox && srcBox->sourceCollections().isEmpty()) { removeBox(srcBox->id()); return; } if (srcBox) { Q_EMIT updateBox(srcBox); } if (dstBox) { Q_EMIT updateBox(dstBox); } } else { if (const auto box = unregisterSpecialSourceCollection(col.id())) { saveBoxes(); if (box->sourceCollections().isEmpty()) { removeBox(box->id()); } else { Q_EMIT updateBox(box); } } } }); } UnifiedMailboxManager::~UnifiedMailboxManager() { } Akonadi::ChangeRecorder &UnifiedMailboxManager::changeRecorder() { return mMonitor; } void UnifiedMailboxManager::loadBoxes(FinishedCallback &&finishedCb) { + qCDebug(agent_log) << "loading boxes"; const auto group = mConfig->group("UnifiedMailboxes"); const auto boxGroups = group.groupList(); for (const auto &boxGroupName : boxGroups) { const auto boxGroup = group.group(boxGroupName); auto box = std::make_unique(); box->load(boxGroup); insertBox(std::move(box)); } const auto cb = [this, finishedCb = std::move(finishedCb)]() { + qCDebug(agent_log) << "Finished callback: enabling change recorder"; // Only now start processing changes from change recorder connect(&mMonitor, &Akonadi::ChangeRecorder::changesAdded, &mMonitor, &Akonadi::ChangeRecorder::replayNext, Qt::QueuedConnection); // And start replaying any potentially pending notification - mMonitor.replayNext(); + QTimer::singleShot(0, &mMonitor, &Akonadi::ChangeRecorder::replayNext); if (finishedCb) { finishedCb(); } }; + qCDebug(agent_log) << "Loaded" << mMailboxes.size() << "boxes from config"; + if (mMailboxes.empty()) { createDefaultBoxes(std::move(cb)); } else { discoverBoxCollections(std::move(cb)); } } void UnifiedMailboxManager::saveBoxes() { auto group = mConfig->group("UnifiedMailboxes"); const auto currentGroups = group.groupList(); for (const auto &groupName : currentGroups) { group.deleteGroup(groupName); } for (const auto &boxIt : mMailboxes) { auto boxGroup = group.group(boxIt.second->id()); boxIt.second->save(boxGroup); } mConfig->sync(); } void UnifiedMailboxManager::insertBox(std::unique_ptr box) { auto it = mMailboxes.emplace(std::make_pair(box->id(), std::move(box))); it.first->second->attachManager(this); } void UnifiedMailboxManager::removeBox(const QString &id) { auto box = std::find_if(mMailboxes.begin(), mMailboxes.end(), [&id](const std::pair> &box) { return box.second->id() == id; }); if (box == mMailboxes.end()) { return; } box->second->attachManager(nullptr); mMailboxes.erase(box); } UnifiedMailbox *UnifiedMailboxManager::unifiedMailboxForSource(qint64 source) const { const auto box = mSourceToBoxMap.find(source); if (box == mSourceToBoxMap.cend()) { return {}; } return box->second; } UnifiedMailbox * UnifiedMailboxManager::unifiedMailboxFromCollection(const Akonadi::Collection &col) const { if (!isUnifiedMailbox(col)) { return nullptr; } const auto box = mMailboxes.find(col.name()); if (box == mMailboxes.cend()) { return {}; } return box->second.get(); } void UnifiedMailboxManager::createDefaultBoxes(FinishedCallback &&finishedCb) { // First build empty boxes auto inbox = std::make_unique(); inbox->attachManager(this); inbox->setId(Common::InboxBoxId); inbox->setName(i18n("Inbox")); inbox->setIcon(QStringLiteral("mail-folder-inbox")); insertBox(std::move(inbox)); auto sent = std::make_unique(); sent->attachManager(this); sent->setId(Common::SentBoxId); sent->setName(i18n("Sent")); sent->setIcon(QStringLiteral("mail-folder-sent")); insertBox(std::move(sent)); auto drafts = std::make_unique(); drafts->attachManager(this); drafts->setId(Common::DraftsBoxId); drafts->setName(i18n("Drafts")); drafts->setIcon(QStringLiteral("document-properties")); insertBox(std::move(drafts)); auto list = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this); list->fetchScope().fetchAttribute(); list->fetchScope().setContentMimeTypes({QStringLiteral("message/rfc822")}); #ifdef UNIT_TESTS list->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::Parent); #else list->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::None); #endif connect(list, &Akonadi::CollectionFetchJob::collectionsReceived, this, [this](const Akonadi::Collection::List &list) { for (const auto &col : list) { if (isUnifiedMailbox(col)) { continue; } try { switch (Akonadi::SpecialMailCollections::self()->specialCollectionType(col)) { case Akonadi::SpecialMailCollections::Inbox: mMailboxes.at(Common::InboxBoxId)->addSourceCollection(col.id()); break; case Akonadi::SpecialMailCollections::SentMail: mMailboxes.at(Common::SentBoxId)->addSourceCollection(col.id()); break; case Akonadi::SpecialMailCollections::Drafts: mMailboxes.at(Common::DraftsBoxId)->addSourceCollection(col.id()); break; default: continue; } } catch (const std::out_of_range &) { qCWarning(agent_log) << "Failed to find a special unified mailbox for source collection" << col.id(); continue; } } }); - connect(list, &Akonadi::CollectionFetchJob::finished, + connect(list, &Akonadi::CollectionFetchJob::result, this, [this, finishedCb = std::move(finishedCb)]() { saveBoxes(); if (finishedCb) { finishedCb(); } }); } void UnifiedMailboxManager::discoverBoxCollections(FinishedCallback &&finishedCb) { auto list = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this); #ifdef UNIT_TESTS list->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::Parent); #else list->fetchScope().setResource(Common::AgentIdentifier); #endif connect(list, &Akonadi::CollectionFetchJob::collectionsReceived, this, [this](const Akonadi::Collection::List &list) { for (const auto &col : list) { if (!isUnifiedMailbox(col) || col.parentCollection() == Akonadi::Collection::root()) { continue; } mMailboxes.at(col.name())->setCollectionId(col.id()); } }); if (finishedCb) { - connect(list, &Akonadi::CollectionFetchJob::finished, - this, finishedCb); + connect(list, &Akonadi::CollectionFetchJob::result, this, finishedCb); } } const UnifiedMailbox *UnifiedMailboxManager::registerSpecialSourceCollection(const Akonadi::Collection& col) { // This is slightly awkward, wold be better if we could use SpecialMailCollections, // but it also relies on Monitor internally, so there's a possible race condition // between our ChangeRecorder and SpecialMailCollections' Monitor auto attr = col.attribute(); Q_ASSERT(attr); if (!attr) { return {}; } decltype(mMailboxes)::iterator box; if (attr->collectionType() == Common::SpecialCollectionInbox) { box = mMailboxes.find(Common::InboxBoxId); } else if (attr->collectionType() == Common::SpecialCollectionSentMail) { box = mMailboxes.find(Common::SentBoxId); } else if (attr->collectionType() == Common::SpecialCollectionDrafts) { box = mMailboxes.find(Common::DraftsBoxId); } if (box == mMailboxes.end()) { return {}; } box->second->addSourceCollection(col.id()); return box->second.get(); } const UnifiedMailbox *UnifiedMailboxManager::unregisterSpecialSourceCollection(qint64 colId) { auto box = unifiedMailboxForSource(colId); if (!box) { return {}; } if (!box->isSpecial()) { qDebug() << colId << "does not belong to a special unified box" << box->id(); return {}; } box->removeSourceCollection(colId); return box; } diff --git a/src/configuredialog/configureplugins/configurepluginslistwidget.cpp b/src/configuredialog/configureplugins/configurepluginslistwidget.cpp index c3b1929a9..27e8a7115 100644 --- a/src/configuredialog/configureplugins/configurepluginslistwidget.cpp +++ b/src/configuredialog/configureplugins/configurepluginslistwidget.cpp @@ -1,357 +1,359 @@ /* Copyright (C) 2016-2018 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 "configurepluginslistwidget.h" #include "kmail_debug.h" #include "util.h" #include "../../plugininterface/kmailplugininterface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include namespace { QString pluginEditorGroupName() { return QStringLiteral("plugineditorgroupname"); } QString viewerPluginGroupName() { return QStringLiteral("viewerplugingroupname"); } QString pluginEditorCheckBeforeGroupName() { return QStringLiteral("plugineditorcheckbeforegroupname"); } QString pluginEditorInitGroupName() { return QStringLiteral("plugineditorinitgroupname"); } QString pluginEditorConvertTextGroupName() { return QStringLiteral("plugineditorconvertTextgroupname"); } QString kmailPluginToolsGroupName() { return QStringLiteral("kmailplugintoolsgroupname"); } QString networkUrlInterceptorGroupName() { return QStringLiteral("networkurlinterceptorgroupname"); } QString headerStyleGroupName() { return QStringLiteral("headerstylegroupname"); } QString agentAkonadiGroupName() { return QStringLiteral("agentakonadigroupname"); } } ConfigurePluginsListWidget::ConfigurePluginsListWidget(QWidget *parent) : PimCommon::ConfigurePluginsListWidget(parent) { connect(this, &ConfigurePluginsListWidget::configureClicked, this, &ConfigurePluginsListWidget::slotConfigureClicked); } ConfigurePluginsListWidget::~ConfigurePluginsListWidget() { } void ConfigurePluginsListWidget::save() { PimCommon::ConfigurePluginsListWidget::savePlugins(MessageComposer::PluginEditorManager::self()->configGroupName(), MessageComposer::PluginEditorManager::self()->configPrefixSettingKey(), mPluginEditorItems); PimCommon::ConfigurePluginsListWidget::savePlugins(MessageViewer::ViewerPluginManager::self()->configGroupName(), MessageViewer::ViewerPluginManager::self()->configPrefixSettingKey(), mPluginMessageViewerItems); PimCommon::ConfigurePluginsListWidget::savePlugins(MessageComposer::PluginEditorInitManager::self()->configGroupName(), MessageComposer::PluginEditorInitManager::self()->configPrefixSettingKey(), mPluginEditorInitItems); PimCommon::ConfigurePluginsListWidget::savePlugins(MessageComposer::PluginEditorCheckBeforeSendManager::self()->configGroupName(), MessageComposer::PluginEditorCheckBeforeSendManager::self()->configPrefixSettingKey(), mPluginCheckBeforeSendItems); PimCommon::ConfigurePluginsListWidget::savePlugins(KMailPluginInterface::self()->configGroupName(), KMailPluginInterface::self()->configPrefixSettingKey(), mPluginGenericItems); PimCommon::ConfigurePluginsListWidget::savePlugins(WebEngineViewer::NetworkUrlInterceptorPluginManager::self()->configGroupName(), WebEngineViewer::NetworkUrlInterceptorPluginManager::self()->configPrefixSettingKey(), mPluginWebEngineItems); PimCommon::ConfigurePluginsListWidget::savePlugins(MessageViewer::HeaderStylePluginManager::self()->configGroupName(), MessageViewer::HeaderStylePluginManager::self()->configPrefixSettingKey(), mPluginHeaderStyleItems); PimCommon::ConfigurePluginsListWidget::savePlugins(MessageComposer::PluginEditorConvertTextManager::self()->configGroupName(), MessageComposer::PluginEditorConvertTextManager::self()->configPrefixSettingKey(), mPluginConvertTextItems); saveAkonadiAgent(); } void ConfigurePluginsListWidget::saveAkonadiAgent() { for (PluginItem *item : qAsConst(mAgentPluginsItems)) { for (const PimCommon::PluginUtilData &data : qAsConst(mPluginUtilDataList)) { if (item->mIdentifier == data.mIdentifier) { changeAgentActiveState(data.mExtraInfo.at(0), data.mExtraInfo.at(1), item->checkState(0) == Qt::Checked); break; } } } } void ConfigurePluginsListWidget::doLoadFromGlobalSettings() { initialize(); } void ConfigurePluginsListWidget::doResetToDefaultsOther() { changeState(mPluginEditorItems); changeState(mPluginMessageViewerItems); changeState(mPluginCheckBeforeSendItems); changeState(mPluginGenericItems); changeState(mPluginWebEngineItems); changeState(mPluginHeaderStyleItems); changeState(mAgentPluginsItems); changeState(mPluginEditorInitItems); changeState(mPluginConvertTextItems); } void ConfigurePluginsListWidget::initialize() { mListWidget->clear(); //Load CheckBeforeSend PimCommon::ConfigurePluginsListWidget::fillTopItems(MessageComposer::PluginEditorCheckBeforeSendManager::self()->pluginsDataList(), i18n("Check Before Send Plugins"), MessageComposer::PluginEditorCheckBeforeSendManager::self()->configGroupName(), MessageComposer::PluginEditorCheckBeforeSendManager::self()->configPrefixSettingKey(), mPluginCheckBeforeSendItems, pluginEditorCheckBeforeGroupName()); PimCommon::ConfigurePluginsListWidget::fillTopItems(MessageComposer::PluginEditorInitManager::self()->pluginsDataList(), i18n("Composer Plugins"), MessageComposer::PluginEditorInitManager::self()->configGroupName(), MessageComposer::PluginEditorInitManager::self()->configPrefixSettingKey(), mPluginEditorInitItems, pluginEditorInitGroupName()); //Load generic plugins //Necessary to initialize pluging when we load it outside kmail KMailPluginInterface::self()->initializePlugins(); PimCommon::ConfigurePluginsListWidget::fillTopItems(KMailPluginInterface::self()->pluginsDataList(), i18n("Tools Plugins"), KMailPluginInterface::self()->configGroupName(), KMailPluginInterface::self()->configPrefixSettingKey(), mPluginGenericItems, kmailPluginToolsGroupName()); //Load plugin editor PimCommon::ConfigurePluginsListWidget::fillTopItems(MessageComposer::PluginEditorManager::self()->pluginsDataList(), i18n("Editor Plugins"), MessageComposer::PluginEditorManager::self()->configGroupName(), MessageComposer::PluginEditorManager::self()->configPrefixSettingKey(), mPluginEditorItems, pluginEditorGroupName()); //Load messageviewer plugin PimCommon::ConfigurePluginsListWidget::fillTopItems(MessageViewer::ViewerPluginManager::self()->pluginsDataList(), i18n("Message Viewer"), MessageViewer::ViewerPluginManager::self()->configGroupName(), MessageViewer::ViewerPluginManager::self()->configPrefixSettingKey(), mPluginMessageViewerItems, viewerPluginGroupName()); //Load webengineplugin PimCommon::ConfigurePluginsListWidget::fillTopItems(WebEngineViewer::NetworkUrlInterceptorPluginManager::self()->pluginsDataList(), i18n("Webengine Plugins"), WebEngineViewer::NetworkUrlInterceptorPluginManager::self()->configGroupName(), WebEngineViewer::NetworkUrlInterceptorPluginManager::self()->configPrefixSettingKey(), mPluginWebEngineItems, networkUrlInterceptorGroupName()); //Load headerstyle PimCommon::ConfigurePluginsListWidget::fillTopItems(MessageViewer::HeaderStylePluginManager::self()->pluginsDataList(), i18n("Header Style Plugins"), MessageViewer::HeaderStylePluginManager::self()->configGroupName(), MessageViewer::HeaderStylePluginManager::self()->configPrefixSettingKey(), mPluginHeaderStyleItems, headerStyleGroupName()); // //Load headerstyle PimCommon::ConfigurePluginsListWidget::fillTopItems(MessageComposer::PluginEditorConvertTextManager::self()->pluginsDataList(), i18n("Convertor Text Plugins"), MessageComposer::PluginEditorConvertTextManager::self()->configGroupName(), MessageComposer::PluginEditorConvertTextManager::self()->configPrefixSettingKey(), mPluginConvertTextItems, pluginEditorConvertTextGroupName()); //Load Agent Plugin initializeAgentPlugins(); mListWidget->expandAll(); } void ConfigurePluginsListWidget::initializeAgentPlugins() { mPluginUtilDataList.clear(); mPluginUtilDataList.reserve(4); mPluginUtilDataList << createAgentPluginData(QStringLiteral("akonadi_sendlater_agent"), QStringLiteral("/SendLaterAgent")); mPluginUtilDataList << createAgentPluginData(QStringLiteral("akonadi_archivemail_agent"), QStringLiteral("/ArchiveMailAgent")); mPluginUtilDataList << createAgentPluginData(QStringLiteral("akonadi_newmailnotifier_agent"), QStringLiteral("/NewMailNotifierAgent")); mPluginUtilDataList << createAgentPluginData(QStringLiteral("akonadi_followupreminder_agent"), QStringLiteral("/FollowUpReminder")); + mPluginUtilDataList << createAgentPluginData(QStringLiteral("akonadi_unifiedmailbox_agent"), QStringLiteral("/UnifiedMailboxAgent")); PimCommon::ConfigurePluginsListWidget::fillTopItems(mPluginUtilDataList, i18n("Akonadi Agents"), QString(), QString(), mAgentPluginsItems, agentAkonadiGroupName()); } PimCommon::PluginUtilData ConfigurePluginsListWidget::createAgentPluginData(const QString &agentIdentifier, const QString &path) { PimCommon::PluginUtilData data; data.mEnableByDefault = true; data.mHasConfigureDialog = true; const Akonadi::AgentType::List lstAgent = Akonadi::AgentManager::self()->types(); for (const Akonadi::AgentType &type : lstAgent) { if (type.identifier() == agentIdentifier) { data.mExtraInfo << agentIdentifier; data.mExtraInfo << path; const bool enabled = agentActivateState(agentIdentifier, path); data.mEnableByDefault = enabled; data.mName = type.name(); data.mDescription = type.description(); data.mIdentifier = type.identifier(); break; } } return data; } bool ConfigurePluginsListWidget::agentActivateState(const QString &agentIdentifier, const QString &pathName) { const QString service = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Agent, agentIdentifier); QDBusInterface interface(service, pathName); if (interface.isValid()) { QDBusReply enabled = interface.call(QStringLiteral("enabledAgent")); if (enabled.isValid()) { return enabled; } else { qCDebug(KMAIL_LOG) << agentIdentifier << "doesn't have enabledAgent function"; return false; } } else { qCDebug(KMAIL_LOG) << agentIdentifier << "does not exist when trying to activate the agent state"; } return false; } void ConfigurePluginsListWidget::changeAgentActiveState(const QString &agentIdentifier, const QString &path, bool enable) { if (!agentIdentifier.isEmpty() && !path.isEmpty()) { const QString service = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Agent, agentIdentifier); QDBusInterface interface(service, path); if (interface.isValid()) { interface.call(QStringLiteral("setEnableAgent"), enable); } else { qCDebug(KMAIL_LOG) << agentIdentifier << "does not exist when trying to change the agent active state"; } } } void ConfigurePluginsListWidget::slotConfigureClicked(const QString &configureGroupName, const QString &identifier) { if (!configureGroupName.isEmpty() && !identifier.isEmpty()) { if (configureGroupName == headerStyleGroupName()) { MessageViewer::HeaderStylePlugin *plugin = MessageViewer::HeaderStylePluginManager::self()->pluginFromIdentifier(identifier); plugin->showConfigureDialog(this); } else if (configureGroupName == networkUrlInterceptorGroupName()) { WebEngineViewer::NetworkPluginUrlInterceptor *plugin = WebEngineViewer::NetworkUrlInterceptorPluginManager::self()->pluginFromIdentifier(identifier); plugin->showConfigureDialog(this); } else if (configureGroupName == viewerPluginGroupName()) { MessageViewer::ViewerPlugin *plugin = MessageViewer::ViewerPluginManager::self()->pluginFromIdentifier(identifier); plugin->showConfigureDialog(this); } else if (configureGroupName == pluginEditorGroupName()) { MessageComposer::PluginEditor *plugin = MessageComposer::PluginEditorManager::self()->pluginFromIdentifier(identifier); plugin->showConfigureDialog(this); } else if (configureGroupName == kmailPluginToolsGroupName()) { PimCommon::GenericPlugin *plugin = KMailPluginInterface::self()->pluginFromIdentifier(identifier); plugin->showConfigureDialog(this); } else if (configureGroupName == pluginEditorInitGroupName()) { MessageComposer::PluginEditorInit *plugin = MessageComposer::PluginEditorInitManager::self()->pluginFromIdentifier(identifier); plugin->showConfigureDialog(this); } else if (configureGroupName == pluginEditorCheckBeforeGroupName()) { MessageComposer::PluginEditorCheckBeforeSend *plugin = MessageComposer::PluginEditorCheckBeforeSendManager::self()->pluginFromIdentifier(identifier); plugin->showConfigureDialog(this); } else if (configureGroupName == pluginEditorConvertTextGroupName()) { MessageComposer::PluginEditorConvertText *plugin = MessageComposer::PluginEditorConvertTextManager::self()->pluginFromIdentifier(identifier); plugin->showConfigureDialog(this); } else if (configureGroupName == agentAkonadiGroupName()) { for (const PimCommon::PluginUtilData &data : qAsConst(mPluginUtilDataList)) { if (data.mIdentifier == identifier) { const QString service = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Agent, data.mExtraInfo.at(0)); QDBusInterface interface(service, data.mExtraInfo.at(1)); if (interface.isValid()) { interface.call(QStringLiteral("showConfigureDialog"), static_cast(winId())); } else { qCDebug(KMAIL_LOG) << " interface does not exist when trying to configure the plugin"; } break; } } } else { qCWarning(KMAIL_LOG) << "Unknown configureGroupName" << configureGroupName; } } } void ConfigurePluginsListWidget::defaults() { doResetToDefaultsOther(); }