diff --git a/agents/unifiedmailboxagent/CMakeLists.txt b/agents/unifiedmailboxagent/CMakeLists.txt index 3b2eb4d46..0e2b53217 100644 --- a/agents/unifiedmailboxagent/CMakeLists.txt +++ b/agents/unifiedmailboxagent/CMakeLists.txt @@ -1,38 +1,47 @@ add_definitions(-DTRANSLATION_DOMAIN=\"akonadi_unifiedmailbox_agent\") if(BUILD_TESTING) #add_subdirectory(tests) #add_subdirectory(autotests) endif() set(unifiedmailbox_agent_SRCS 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 ) 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 ) 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/mailkernel.cpp b/agents/unifiedmailboxagent/mailkernel.cpp new file mode 100644 index 000000000..d13d713f0 --- /dev/null +++ b/agents/unifiedmailboxagent/mailkernel.cpp @@ -0,0 +1,144 @@ +/* + 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 "mailkernel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MailKernel::MailKernel(KSharedConfigPtr config, QObject *parent) + : QObject(parent) + , mConfig(config) +{ + mMessageSender = new MessageComposer::AkonadiSender(this); + mIdentityManager = new KIdentityManagement::IdentityManager(true, this); + Akonadi::Session *session = new Akonadi::Session("UnifiedMailbox Kernel ETM", this); + + mFolderCollectionMonitor = new MailCommon::FolderCollectionMonitor(session, this); + + mEntityTreeModel = new Akonadi::EntityTreeModel(folderCollectionMonitor(), this); + mEntityTreeModel->setListFilter(Akonadi::CollectionFetchScope::Enabled); + mEntityTreeModel->setItemPopulationStrategy(Akonadi::EntityTreeModel::LazyPopulation); + + mCollectionModel = new Akonadi::EntityMimeTypeFilterModel(this); + mCollectionModel->setSourceModel(mEntityTreeModel); + mCollectionModel->addMimeTypeInclusionFilter(Akonadi::Collection::mimeType()); + mCollectionModel->setHeaderGroup(Akonadi::EntityTreeModel::CollectionTreeHeaders); + mCollectionModel->setDynamicSortFilter(true); + mCollectionModel->setSortCaseSensitivity(Qt::CaseInsensitive); + + CommonKernel->registerKernelIf(this); + CommonKernel->registerSettingsIf(this); +} + +MailKernel::~MailKernel() +{ + CommonKernel->registerKernelIf(nullptr); + CommonKernel->registerSettingsIf(nullptr); +} + +KIdentityManagement::IdentityManager *MailKernel::identityManager() +{ + return mIdentityManager; +} + +MessageComposer::MessageSender *MailKernel::msgSender() +{ + return mMessageSender; +} + +Akonadi::EntityMimeTypeFilterModel *MailKernel::collectionModel() const +{ + return mCollectionModel; +} + +KSharedConfig::Ptr MailKernel::config() +{ + return mConfig; +} + +void MailKernel::syncConfig() +{ + Q_ASSERT(false); +} + +MailCommon::JobScheduler *MailKernel::jobScheduler() const +{ + Q_ASSERT(false); + return nullptr; +} + +Akonadi::ChangeRecorder *MailKernel::folderCollectionMonitor() const +{ + return mFolderCollectionMonitor->monitor(); +} + +void MailKernel::updateSystemTray() +{ + Q_ASSERT(false); +} + +bool MailKernel::showPopupAfterDnD() +{ + return false; +} + +qreal MailKernel::closeToQuotaThreshold() +{ + return 80; +} + +QStringList MailKernel::customTemplates() +{ + Q_ASSERT(false); + return QStringList(); +} + +bool MailKernel::excludeImportantMailFromExpiry() +{ + Q_ASSERT(false); + return true; +} + +Akonadi::Collection::Id MailKernel::lastSelectedFolder() +{ + Q_ASSERT(false); + return Akonadi::Collection::Id(); +} + +void MailKernel::setLastSelectedFolder(Akonadi::Collection::Id col) +{ + Q_UNUSED(col); +} + +void MailKernel::expunge(Akonadi::Collection::Id id, bool sync) +{ + Akonadi::Collection col(id); + if (col.isValid()) { + mFolderCollectionMonitor->expunge(Akonadi::Collection(col), sync); + } +} + diff --git a/agents/unifiedmailboxagent/mailkernel.h b/agents/unifiedmailboxagent/mailkernel.h new file mode 100644 index 000000000..09d7276d7 --- /dev/null +++ b/agents/unifiedmailboxagent/mailkernel.h @@ -0,0 +1,74 @@ +/* + 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 MAILKERNEL_H +#define MAILKERNEL_H + +#include +#include + +namespace Akonadi { +class EntityTreeModel; +class EntityMimeTypeFilterModel; +} + +namespace MailCommon { +class FolderCollectionMonitor; +} + +class MailKernel : public QObject + , public MailCommon::IKernel + , public MailCommon::ISettings +{ + Q_OBJECT +public: + explicit MailKernel(KSharedConfigPtr config, QObject *parent = nullptr); + ~MailKernel() override; + + KIdentityManagement::IdentityManager *identityManager() override; + MessageComposer::MessageSender *msgSender() override; + + Akonadi::EntityMimeTypeFilterModel *collectionModel() const override; + KSharedConfig::Ptr config() override; + void syncConfig() override; + MailCommon::JobScheduler *jobScheduler() const override; + Akonadi::ChangeRecorder *folderCollectionMonitor() const override; + void updateSystemTray() override; + + qreal closeToQuotaThreshold() override; + bool excludeImportantMailFromExpiry() override; + QStringList customTemplates() override; + Akonadi::Collection::Id lastSelectedFolder() override; + void setLastSelectedFolder(Akonadi::Collection::Id col) override; + bool showPopupAfterDnD() override; + void expunge(Akonadi::Collection::Id id, bool sync) override; + +private: + Q_DISABLE_COPY(MailKernel) + + KSharedConfigPtr mConfig; + KIdentityManagement::IdentityManager *mIdentityManager = nullptr; + MessageComposer::MessageSender *mMessageSender = nullptr; + MailCommon::FolderCollectionMonitor *mFolderCollectionMonitor = nullptr; + Akonadi::EntityTreeModel *mEntityTreeModel = nullptr; + Akonadi::EntityMimeTypeFilterModel *mCollectionModel = nullptr; +}; + +#endif + diff --git a/agents/unifiedmailboxagent/settingsdialog.cpp b/agents/unifiedmailboxagent/settingsdialog.cpp new file mode 100644 index 000000000..8e3bf395b --- /dev/null +++ b/agents/unifiedmailboxagent/settingsdialog.cpp @@ -0,0 +1,145 @@ +/* + 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 "settingsdialog.h" +#include "unifiedmailboxmanager.h" +#include "unifiedmailboxeditor.h" +#include "mailkernel.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +SettingsDialog::SettingsDialog(KSharedConfigPtr config, UnifiedMailboxManager &boxManager, WId windowId, QWidget *parent) + : QDialog(parent) + , mBoxManager(boxManager) + , mKernel(new MailKernel(config, this)) +{ + resize(500, 500); + + auto l = new QVBoxLayout; + setLayout(l); + + auto h = new QHBoxLayout; + l->addLayout(h); + mBoxModel = new QStandardItemModel(this); + auto view = new QListView(this); + view->setEditTriggers(QListView::NoEditTriggers); + view->setModel(mBoxModel); + h->addWidget(view); + + auto v = new QVBoxLayout; + h->addLayout(v); + auto addButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add-symbolic")), i18n("Add")); + v->addWidget(addButton); + connect(addButton, &QPushButton::clicked, + this, [this]() { + auto editor = new UnifiedMailboxEditor(this); + if (editor->exec()) { + auto box = editor->box(); + box.setId(box.name()); // assign ID + addBox(box); + mBoxManager.insertBox(std::move(box)); + } + }); + auto editButton = new QPushButton(QIcon::fromTheme(QStringLiteral("entry-edit")), i18n("Modify")); + editButton->setEnabled(false); + v->addWidget(editButton); + connect(editButton, &QPushButton::clicked, + this, [this, view]() { + const auto indexes = view->selectionModel()->selectedIndexes(); + if (!indexes.isEmpty()) { + auto item = mBoxModel->itemFromIndex(indexes[0]); + auto editor = new UnifiedMailboxEditor(item->data().value(), this); + if (editor->exec()) { + auto box = editor->box(); + item->setText(box.name()); + item->setIcon(QIcon::fromTheme(box.icon())); + item->setData(QVariant::fromValue(box)); + mBoxManager.insertBox(std::move(box)); + } + } + }); + auto removeButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-remove-symbolic")), i18n("Remove")); + removeButton->setEnabled(false); + v->addWidget(removeButton); + connect(removeButton, &QPushButton::clicked, + this, [this, view]() { + const auto indexes = view->selectionModel()->selectedIndexes(); + if (!indexes.isEmpty()) { + auto item = mBoxModel->itemFromIndex(indexes[0]); + const auto box = item->data().value(); + if (KMessageBox::warningYesNo( + this, i18n("Do you really want to remove unified mailbox %1?", box.name()), + i18n("Really Remove?"), KStandardGuiItem::remove(), KStandardGuiItem::cancel()) == KMessageBox::Yes) + { + mBoxModel->removeRow(item->row()); + mBoxManager.removeBox(box.name()); + } + } + }); + v->addStretch(1); + + connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, + this, [view, editButton, removeButton]() { + const bool hasSelection = view->selectionModel()->hasSelection(); + editButton->setEnabled(hasSelection); + removeButton->setEnabled(hasSelection); + }); + + auto box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + connect(box, &QDialogButtonBox::accepted, this, &SettingsDialog::accept); + connect(box, &QDialogButtonBox::rejected, this, &SettingsDialog::reject); + l->addWidget(box); + + loadBoxes(); +} + +SettingsDialog::~SettingsDialog() +{ +} + +void SettingsDialog::accept() +{ + mBoxManager.saveBoxes(); + + QDialog::accept(); +} + +void SettingsDialog::loadBoxes() +{ + mBoxModel->clear(); + for (const auto &box : mBoxManager) { + addBox(box); + } +} + +void SettingsDialog::addBox(const UnifiedMailbox &box) +{ + auto item = new QStandardItem(QIcon::fromTheme(box.icon()), box.name()); + item->setData(QVariant::fromValue(box)); + mBoxModel->appendRow(item); +} diff --git a/agents/unifiedmailboxagent/settingsdialog.h b/agents/unifiedmailboxagent/settingsdialog.h new file mode 100644 index 000000000..9c83cfb03 --- /dev/null +++ b/agents/unifiedmailboxagent/settingsdialog.h @@ -0,0 +1,55 @@ +/* + 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 SETTINGSDIALOG_H_ +#define SETTINGSDIALOG_H_ + +#include + +#include + +#include "unifiedmailboxmanager.h" + +class QStandardItemModel; +class MailKernel; + +class SettingsDialog : public QDialog +{ + Q_OBJECT +public: + explicit SettingsDialog(KSharedConfigPtr config, UnifiedMailboxManager &manager, + WId windowId, QWidget *parent = nullptr); + ~SettingsDialog() override; + +public Q_SLOTS: + void accept() override; + +private: + void loadBoxes(); + void addBox(const UnifiedMailbox &box); + +private: + QStandardItemModel *mBoxModel = nullptr; + UnifiedMailboxManager &mBoxManager; + MailKernel *mKernel = nullptr; +}; + + + +#endif diff --git a/agents/unifiedmailboxagent/unifiedmailboxagent.cpp b/agents/unifiedmailboxagent/unifiedmailboxagent.cpp index 3465831ee..273d51979 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxagent.cpp +++ b/agents/unifiedmailboxagent/unifiedmailboxagent.cpp @@ -1,369 +1,325 @@ /* 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 "unifiedmailboxagent_debug.h" +#include "settingsdialog.h" #include "settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include + +#include #include #include namespace { static const auto MailMimeType = QStringLiteral("message/rfc822"); static const auto Inbox = QStringLiteral("inbox"); static const auto Sent = QStringLiteral("sent-mail"); static const auto Drafts = QStringLiteral("drafts"); } UnifiedMailboxAgent::UnifiedMailboxAgent(const QString &id) : Akonadi::ResourceBase(id) + , mBoxManager(config()) { setAgentName(i18n("Unified Mailboxes")); // HACK: Act as if we were an Agent instead of a Resource and listen to changes // in everyone's Items instead of just ours changeRecorder()->setResourceMonitored(id.toUtf8(), false); // We are only interested in emails... changeRecorder()->setMimeTypeMonitored(MailMimeType); changeRecorder()->setTypeMonitored(Akonadi::Monitor::Items); auto &ifs = changeRecorder()->itemFetchScope(); ifs.setAncestorRetrieval(Akonadi::ItemFetchScope::None); ifs.setCacheOnly(true); ifs.fetchFullPayload(false); - scheduleCustomTask(this, "init", {}); + QMetaObject::invokeMethod(this, "delayedInit", Qt::QueuedConnection); } void UnifiedMailboxAgent::configure(WId windowId) { + QPointer agent(this); + if (SettingsDialog(config(), mBoxManager, windowId).exec() && agent) { + QMetaObject::invokeMethod(this, "delayedInit", Qt::QueuedConnection); + Q_EMIT configurationDialogAccepted(); + } } -void UnifiedMailboxAgent::init(const QVariant & /* dummy */) +void UnifiedMailboxAgent::delayedInit() { qCDebug(agent_log) << "init"; fixSpecialCollections(); + mBoxManager.loadBoxes([this]() { + // start monitoring all source collections + for (const auto &box : mBoxManager) { + const auto sources = box.sourceCollections(); + for (auto source : sources) { + changeRecorder()->setCollectionMonitored(Akonadi::Collection(source), true); + } + } - // First discover our boxes - Q_EMIT status(Running, i18n("Discovering Unified MailBoxes...")); - auto list = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this); - list->fetchScope().setResource(identifier()); - connect(list, &Akonadi::CollectionFetchJob::collectionsReceived, - this, [this](const Akonadi::Collection::List &list) { - for (const auto &col : list) { - Q_ASSERT(col.resource() == identifier()); - mBoxIdToName.insert(col.id(), col.name()); - mBoxNameToId.insert(col.name(), col.id()); - } - }); - - // No boxes yet? Schedule collection tree sync and then try again - if (mBoxIdToName.isEmpty()) { - Q_EMIT status(Idle); - qCDebug(agent_log) << "No boxes found, requesting collection tree sync"; + // boxes are loaded, schedule tree sync and afterwards check for missing items synchronizeCollectionTree(); - scheduleCustomTask(this, "init", {}); - taskDone(); - return; - } - qCDebug(agent_log) << "Found" << mBoxIdToName.count() << "boxes"; - - // Now build mapping between regular collections and boxes - Q_EMIT status(Running, i18n("Discovering local mailboxes...")); - list = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this); - list->fetchScope().fetchAttribute(); - list->fetchScope().setContentMimeTypes({MailMimeType}); - connect(list, &Akonadi::CollectionFetchJob::collectionsReceived, - this, [this](const Akonadi::Collection::List &list) { - for (const auto &col : list) { - if (col.resource() == identifier()) { - continue; - } - // TODO: Support custom collections too - if (!col.hasAttribute()) { - continue; - } - - const auto *attr = col.attribute(); - qCDebug(agent_log) << "Found Collection with special attribute: " << attr->collectionType(); - if (QString::fromLatin1(attr->collectionType()) == Inbox) { - const auto boxId = mBoxNameToId.value(Inbox, -1); - Q_ASSERT(boxId > -1); - mCollectionToBox.insert(col.id(), boxId); - changeRecorder()->setCollectionMonitored(col); - qCDebug(agent_log) << "Added collection" << col.id() << "to Inbox box"; - } else if (QString::fromLatin1(attr->collectionType()) == Sent) { - const auto boxId = mBoxNameToId.value(Sent, -1); - Q_ASSERT(boxId > -1); - mCollectionToBox.insert(col.id(), boxId); - changeRecorder()->setCollectionMonitored(col); - qCDebug(agent_log) << "Added collection" << col.id() << "to Sent box"; - } else if (QString::fromLatin1(attr->collectionType()) == Drafts) { - const auto boxId = mBoxNameToId.value(Drafts, -1); - Q_ASSERT(boxId > -1); - mCollectionToBox.insert(col.id(), boxId); - changeRecorder()->setCollectionMonitored(col); - qCDebug(agent_log) << "Added collection" << col.id() << "to Drafts box"; - } - } - }); - // Once we have full mapping between collections and mappings, check if there - // are any items that we may have missed and must be linked or unlinked to/from - // the boxes. - connect(list, &Akonadi::CollectionFetchJob::result, this, &UnifiedMailboxAgent::checkForMissingItems); + scheduleCustomTask(this, "rediscoverLocalBoxes", {}); + scheduleCustomTask(this, "checkForMissingItems", {}); + }); } -void UnifiedMailboxAgent::checkForMissingItems() +void UnifiedMailboxAgent::checkForMissingItems(const QVariant & /* dummy */) { const auto lastSeenEvent = QDateTime::fromTime_t(Settings::self()->lastSeenEvent()); qCDebug(agent_log) << "Checking for missing Items in boxes, last seen event was on" << lastSeenEvent; // First check all source Collections that we monitor and verify that all items // are linked into a box that matches the given Collection const auto collections = changeRecorder()->collectionsMonitored(); Q_EMIT status(Running, i18n("Checking if unified boxes are up to date")); for (const auto &collection : collections) { - const Akonadi::Collection box{unifiedBoxForCollection(collection)}; - if (!box.isValid()) { + auto unifiedBox = mBoxManager.unifiedMailboxForSource(collection.id()); + if (!unifiedBox) { qCWarning(agent_log) << "Failed to retrieve box ID for collection " << collection.id(); continue; } + Akonadi::Collection unifiedCollection(mBoxManager.collectionIdForUnifiedMailbox(unifiedBox->id())); qCDebug(agent_log) << "\tChecking source collection" << collection.id(); auto fetch = new Akonadi::ItemFetchJob(collection, this); fetch->setDeliveryOption(Akonadi::ItemFetchJob::EmitItemsInBatches); // Optimize: we could've only missed events that occured since the last time we saw one - fetch->fetchScope().setFetchChangedSince(lastSeenEvent); + //fetch->fetchScope().setFetchChangedSince(lastSeenEvent); fetch->fetchScope().setFetchVirtualReferences(true); fetch->fetchScope().setCacheOnly(true); connect(fetch, &Akonadi::ItemFetchJob::itemsReceived, - this, [this, box](const Akonadi::Item::List &items) { + this, [this, unifiedCollection](const Akonadi::Item::List &items) { Akonadi::Item::List toLink; std::copy_if(items.cbegin(), items.cend(), std::back_inserter(toLink), - [&box](const Akonadi::Item &item) { - return !item.virtualReferences().contains(box); + [&unifiedCollection](const Akonadi::Item &item) { + return !item.virtualReferences().contains(unifiedCollection); }); if (!toLink.isEmpty()) { - new Akonadi::LinkJob(box, toLink, this); + new Akonadi::LinkJob(unifiedCollection, toLink, this); } }); } // Then check content of all boxes and verify that each Item still belongs to // a Collection that is part of the box - for (const qint64 boxId : mBoxNameToId) { - qCDebug(agent_log) << "\tChecking box" << boxId; + for (const auto &box : mBoxManager) { + qCDebug(agent_log) << "\tChecking box" << box.name(); std::unordered_set boxSources; - const auto sourceList = mCollectionToBox.keys(); + const auto sourceList = box.sourceCollections(); boxSources.reserve(sourceList.size()); std::copy(sourceList.cbegin(), sourceList.cend(), std::inserter(boxSources, boxSources.end())); - auto fetch = new Akonadi::ItemFetchJob(Akonadi::Collection(boxId), this); + Akonadi::Collection unifiedCollection(mBoxManager.collectionIdForUnifiedMailbox(box.id())); + auto fetch = new Akonadi::ItemFetchJob(unifiedCollection, this); fetch->setDeliveryOption(Akonadi::ItemFetchJob::EmitItemsInBatches); // TODO: Does this optimization here make sense? - fetch->fetchScope().setFetchChangedSince(lastSeenEvent); + //fetch->fetchScope().setFetchChangedSince(lastSeenEvent); fetch->fetchScope().setCacheOnly(true); + fetch->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); connect(fetch, &Akonadi::ItemFetchJob::itemsReceived, - this, [this, boxSources = std::move(boxSources), boxId](const Akonadi::Item::List &items) { + this, [this, boxSources = std::move(boxSources), unifiedCollection](const Akonadi::Item::List &items) { Akonadi::Item::List toUnlink; std::copy_if(items.cbegin(), items.cend(), std::back_inserter(toUnlink), [&boxSources](const Akonadi::Item &item) { return boxSources.find(item.storageCollectionId()) == boxSources.cend(); }); if (!toUnlink.isEmpty()) { - new Akonadi::UnlinkJob(Akonadi::Collection{boxId}, toUnlink, this); + new Akonadi::UnlinkJob(unifiedCollection, toUnlink, this); } }); } Q_EMIT status(Idle); taskDone(); } +void UnifiedMailboxAgent::rediscoverLocalBoxes(const QVariant &) +{ + mBoxManager.discoverBoxCollections([this]() { + taskDone(); + }); +} + + void UnifiedMailboxAgent::retrieveCollections() { 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); - // Now create unified folders for Inbox, Sent and Drafts - // TODO: Templates? - struct Folder { - QString id; - QString name; - QString icon; - } folders[] = { - {Inbox, i18n("Inbox"), QStringLiteral("mail-folder-inbox")}, - {Sent, i18n("Sent"), QStringLiteral("mail-folder-sent")}, - {Drafts, i18n("Drafts"), QStringLiteral("document-properties")} - }; - for (const auto &folder : folders) { + for (const auto &box : mBoxManager) { Akonadi::Collection col; - col.setName(folder.id); - col.setRemoteId(folder.id); + col.setName(box.id()); + col.setRemoteId(box.id()); col.setParentCollection(topLevel); col.setContentMimeTypes({MailMimeType}); col.setRights(Akonadi::Collection::CanChangeItem); col.setVirtual(true); auto displayAttr = col.attribute(Akonadi::Collection::AddIfMissing); - displayAttr->setDisplayName(folder.name); - displayAttr->setIconName(folder.icon); + displayAttr->setDisplayName(box.name()); + displayAttr->setIconName(box.icon()); collections.push_back(std::move(col)); } // TODO: Support custom folders collectionsRetrieved(std::move(collections)); + + scheduleCustomTask(this, "rediscoverLocalBoxes", {}); } void UnifiedMailboxAgent::retrieveItems(const Akonadi::Collection &c) { // TODO: Check if our box is up-to-date cancelTask(); } 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; } -qint64 UnifiedMailboxAgent::unifiedBoxForCollection(const Akonadi::Collection& col) const -{ - const auto boxId = mCollectionToBox.constFind(col.id()); - if (boxId == mCollectionToBox.cend()) { - qCWarning(agent_log) << "Missing ID for unified box for collection" << col.id() << col.name(); - return -1; - } - return *boxId; -} - - void UnifiedMailboxAgent::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) { // We are subscribed only to our source collections so we will only be notified // when an item is added into one of them. - const auto boxId = unifiedBoxForCollection(collection); + const auto box = mBoxManager.unifiedMailboxForSource(collection.id()); + if (!box) { + qCWarning(agent_log) << "Failed to find unified mailbox for source collection " << collection.id(); + cancelTask(); + return; + } + + const auto boxId = mBoxManager.collectionIdForUnifiedMailbox(box->id()); if (boxId > -1) { new Akonadi::LinkJob(Akonadi::Collection{boxId}, {item}, this); } changeProcessed(); Settings::self()->setLastSeenEvent(std::chrono::steady_clock::now().time_since_epoch().count()); } void UnifiedMailboxAgent::itemsMoved(const Akonadi::Item::List&items, const Akonadi::Collection &sourceCollection, const Akonadi::Collection &destinationCollection) { - const auto srcBoxId = unifiedBoxForCollection(sourceCollection); - const auto dstBoxId = unifiedBoxForCollection(destinationCollection); - if (srcBoxId != -1) { + const auto srcBox = mBoxManager.unifiedMailboxForSource(sourceCollection.id()); + const auto dstBox = mBoxManager.unifiedMailboxForSource(destinationCollection.id()); + if (srcBox) { // Move source collection was our source, unlink the Item from a box + const auto srcBoxId = mBoxManager.collectionIdForUnifiedMailbox(srcBox->id()); new Akonadi::UnlinkJob(Akonadi::Collection{srcBoxId}, items, this); } - if (dstBoxId != -1) { + if (dstBox) { // Move destination collection is our source, link the Item into a box + const auto dstBoxId = mBoxManager.collectionIdForUnifiedMailbox(dstBox->id()); new Akonadi::LinkJob(Akonadi::Collection{dstBoxId}, items, this); } changeProcessed(); Settings::self()->setLastSeenEvent(std::chrono::steady_clock::now().time_since_epoch().count()); } 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. auto fetch = new Akonadi::CollectionFetchJob(Akonadi::Collection(id), Akonadi::CollectionFetchJob::Base, this); connect(fetch, &Akonadi::CollectionFetchJob::collectionsReceived, this, [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 34b0e0f08..b57c8cb7a 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxagent.h +++ b/agents/unifiedmailboxagent/unifiedmailboxagent.h @@ -1,72 +1,69 @@ /* 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 , public Akonadi::AgentBase::ObserverV4 { Q_OBJECT public: explicit UnifiedMailboxAgent(const QString &id); ~UnifiedMailboxAgent() override = default; void configure(WId windowId) override; void retrieveCollections() override; void retrieveItems(const Akonadi::Collection &collection) override; bool retrieveItem(const Akonadi::Item &item, const QSet &parts) override; void itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) override; void itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &sourceCollection, const Akonadi::Collection &destinationCollection) override; private Q_SLOTS: - void init(const QVariant & /* dummy */ = {}); - void checkForMissingItems(); + void delayedInit(); + void checkForMissingItems(const QVariant & /* dummy */); + void rediscoverLocalBoxes(const QVariant & /* dummy */); private: void fixSpecialCollections(); void fixSpecialCollection(const QString &colId, Akonadi::SpecialMailCollections::Type type); - // TODO: std::optional - qint64 unifiedBoxForCollection(const Akonadi::Collection &col) const; - - QHash mCollectionToBox; - QHash mBoxIdToName; - QHash mBoxNameToId; + UnifiedMailboxManager mBoxManager; }; #endif diff --git a/agents/unifiedmailboxagent/unifiedmailboxeditor.cpp b/agents/unifiedmailboxagent/unifiedmailboxeditor.cpp new file mode 100644 index 000000000..b367ed576 --- /dev/null +++ b/agents/unifiedmailboxagent/unifiedmailboxeditor.cpp @@ -0,0 +1,172 @@ +/* + 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 "unifiedmailboxeditor.h" +#include "mailkernel.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace { + +class SelfFilterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + explicit SelfFilterProxyModel(QObject *parent = nullptr) + : QSortFilterProxyModel(parent) + {} + + QVariant data(const QModelIndex &index, int role) const override + { + if (role == Qt::CheckStateRole) { + // Make top-level collections uncheckable + const Akonadi::Collection col = data(index, Akonadi::EntityTreeModel::CollectionRole).value(); + if (col.parentCollection() == Akonadi::Collection::root()) { + return {}; + } + } + + return QSortFilterProxyModel::data(index, role); + } + + Qt::ItemFlags flags(const QModelIndex &index) const override + { + // Make top-level collections uncheckable + const Akonadi::Collection col = data(index, Akonadi::EntityTreeModel::CollectionRole).value(); + if (col.parentCollection() == Akonadi::Collection::root()) { + return QSortFilterProxyModel::flags(index) & ~Qt::ItemIsUserCheckable; + } else { + return QSortFilterProxyModel::flags(index); + } + } + + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override + { + // Hide ourselves + const auto sourceIndex = sourceModel()->index(source_row, 0, source_parent); + const Akonadi::Collection col = sourceModel()->data(sourceIndex, Akonadi::EntityTreeModel::CollectionRole).value(); + return col.resource() != QLatin1String("akonadi_unifiedmailbox_agent"); + } +}; + +} + +UnifiedMailboxEditor::UnifiedMailboxEditor(QWidget* parent) + : UnifiedMailboxEditor({}, parent) +{ +} + +UnifiedMailboxEditor::UnifiedMailboxEditor(UnifiedMailbox mailbox, QWidget *parent) + : QDialog(parent) + , mBox(std::move(mailbox)) +{ + resize(500, 900); + + auto l = new QVBoxLayout; + setLayout(l); + + auto f = new QFormLayout; + l->addLayout(f); + auto nameEdit = new QLineEdit(mBox.name()); + f->addRow(i18n("Name:"), nameEdit); + connect(nameEdit, &QLineEdit::textChanged, + this, [this](const QString &name) { + mBox.setName(name); + }); + + auto iconButton = new QPushButton(QIcon::fromTheme(mBox.icon(), QIcon::fromTheme(QStringLiteral("folder-mail"))), + i18n("Pick icon...")); + f->addRow(i18n("Icon:"), iconButton); + connect(iconButton, &QPushButton::clicked, + this, [iconButton, this]() { + const auto iconName = KIconDialog::getIcon(); + if (!iconName.isEmpty()) { + mBox.setIcon(iconName); + iconButton->setIcon(QIcon::fromTheme(iconName)); + } + }); + mBox.setIcon(iconButton->icon().name()); + + l->addSpacing(10); + + auto ftw = new MailCommon::FolderTreeWidget(nullptr, nullptr, + MailCommon::FolderTreeWidget::TreeViewOptions(MailCommon::FolderTreeWidget::UseDistinctSelectionModel | + MailCommon::FolderTreeWidget::HideStatistics)); + l->addWidget(ftw); + + auto ftv = ftw->folderTreeView(); + auto sourceModel = ftv->model(); + auto selectionModel = ftw->selectionModel(); + + auto checkable = new KCheckableProxyModel(this); + checkable->setSourceModel(sourceModel); + checkable->setSelectionModel(selectionModel); + const auto sources = mBox.sourceCollections(); + for (const auto source : sources) { + const auto index = Akonadi::EntityTreeModel::modelIndexForCollection(selectionModel->model(), Akonadi::Collection(source)); + selectionModel->select(index, QItemSelectionModel::Select); + } + connect(checkable->selectionModel(), &QItemSelectionModel::selectionChanged, + this, [this](const QItemSelection &selected, const QItemSelection &deselected) { + auto indexes = selected.indexes(); + for (const auto &index : indexes) { + mBox.addSourceCollection(index.data(Akonadi::EntityTreeModel::CollectionIdRole).toLongLong()); + } + indexes = deselected.indexes(); + for (const auto &index : indexes) { + mBox.removeSourceCollection(index.data(Akonadi::EntityTreeModel::CollectionIdRole).toLongLong()); + } + }); + + auto selfFilter = new SelfFilterProxyModel(this); + selfFilter->setSourceModel(checkable); + + ftv->setModel(selfFilter); + ftv->expandAll(); + + auto box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + connect(box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(box, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(nameEdit, &QLineEdit::textChanged, + box, [box](const QString &name) { + box->button(QDialogButtonBox::Ok)->setEnabled(!name.isEmpty()); + }); + box->button(QDialogButtonBox::Ok)->setEnabled(!nameEdit->text().isEmpty()); + l->addWidget(box); +} + +UnifiedMailbox UnifiedMailboxEditor::box() const +{ + return mBox; +} + +#include "unifiedmailboxeditor.moc" diff --git a/agents/unifiedmailboxagent/unifiedmailboxeditor.h b/agents/unifiedmailboxagent/unifiedmailboxeditor.h new file mode 100644 index 000000000..511b70e6c --- /dev/null +++ b/agents/unifiedmailboxagent/unifiedmailboxeditor.h @@ -0,0 +1,35 @@ +/* + 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 + +#include "unifiedmailboxmanager.h" + +class UnifiedMailboxEditor : public QDialog +{ + Q_OBJECT +public: + explicit UnifiedMailboxEditor(QWidget *parent = nullptr); + explicit UnifiedMailboxEditor(UnifiedMailbox mailbox, QWidget *parent = nullptr); + + UnifiedMailbox box() const; + +private: + UnifiedMailbox mBox; +}; diff --git a/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp b/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp new file mode 100644 index 000000000..4850c8b28 --- /dev/null +++ b/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp @@ -0,0 +1,239 @@ +/* + 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 +#include +#include + +#include +#include +#include +#include + +#include + +#include "utils.h" + +void UnifiedMailbox::setId(const QString &id) +{ + mId = id; +} + +QString UnifiedMailbox::id() const +{ + return mId; +} + +void UnifiedMailbox::setName(const QString &name) +{ + mName = name; +} + +QString UnifiedMailbox::name() const +{ + return mName; +} + +void UnifiedMailbox::setIcon(const QString &icon) +{ + mIcon = icon; +} + +QString UnifiedMailbox::icon() const +{ + return mIcon; +} + +void UnifiedMailbox::addSourceCollection(qint64 source) +{ + mSources.insert(source); +} + +void UnifiedMailbox::removeSourceCollection(qint64 source) +{ + mSources.remove(source); +} + +void UnifiedMailbox::setSourceCollections(const QSet &sources) +{ + mSources = sources; +} + +QSet UnifiedMailbox::sourceCollections() const +{ + return mSources; +} + +UnifiedMailboxManager::UnifiedMailboxManager(KSharedConfigPtr config, QObject* parent) + : QObject(parent), mConfig(std::move(config)) +{ +} + +UnifiedMailboxManager::~UnifiedMailboxManager() +{ +} + +void UnifiedMailboxManager::loadBoxes(LoadCallback &&cb) +{ + auto general = mConfig->group("General"); + if (general.readEntry("DiscoverDefaultBoxes", true)) { + discoverDefaultBoxes(std::move(cb)); + general.writeEntry("DiscoverDefaultBoxes", false); + } else { + loadBoxesFromConfig(std::move(cb)); + } +} + +void UnifiedMailboxManager::loadBoxesFromConfig(LoadCallback &&cb) +{ + const auto group = mConfig->group("UnifiedMailboxes"); + const auto boxGroups = group.groupList(); + for (const auto &boxGroupName : boxGroups) { + const auto boxGroup = group.group(boxGroupName); + UnifiedMailbox box; + box.setId(boxGroupName); + box.setName(boxGroup.readEntry("name")); + box.setIcon(boxGroup.readEntry("icon", QStringLiteral("folder-mail"))); + QList sources = boxGroup.readEntry("sources", QList{}); + for (auto source : sources) { + box.addSourceCollection(source); + } + insertBox(std::move(box)); + } + + 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 &box : mBoxes) { + auto boxGroup = group.group(box.id()); + boxGroup.writeEntry("name", box.name()); + boxGroup.writeEntry("icon", box.icon()); + boxGroup.writeEntry("sources", setToList(box.sourceCollections())); + } +} + +void UnifiedMailboxManager::insertBox(UnifiedMailbox box) +{ + mBoxes.insert(box.id(), box); + Q_EMIT boxesChanged(); +} + +void UnifiedMailboxManager::removeBox(const QString &name) +{ + mBoxes.remove(name); + Q_EMIT boxesChanged(); +} + +const UnifiedMailbox *UnifiedMailboxManager::unifiedMailboxForSource(qint64 source) const +{ + for (const auto &box : mBoxes) { + if (box.mSources.contains(source)) { + return &box; + } + } + + return nullptr; +} + +qint64 UnifiedMailboxManager::collectionIdForUnifiedMailbox(const QString &id) const +{ + return mBoxId.value(id, -1); +} + +void UnifiedMailboxManager::discoverDefaultBoxes(LoadCallback &&cb) +{ + // First build empty boxes + UnifiedMailbox inbox; + inbox.setId(QStringLiteral("inbox")); + inbox.setName(i18n("Inbox")); + inbox.setIcon(QStringLiteral("mail-folder-inbox")); + insertBox(std::move(inbox)); + + UnifiedMailbox sent; + sent.setId(QStringLiteral("sent-mail")); + sent.setName(i18n("Sent")); + sent.setIcon(QStringLiteral("mail-folder-sent")); + insertBox(std::move(sent)); + + UnifiedMailbox drafts; + drafts.setId(QStringLiteral("drafts")); + 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")}); + connect(list, &Akonadi::CollectionFetchJob::collectionsReceived, + this, [this](const Akonadi::Collection::List &list) { + for (const auto &col : list) { + if (col.resource() == QLatin1String("akonadi_unifiedmailbox_agent")) { + continue; + } + + switch (Akonadi::SpecialMailCollections::self()->specialCollectionType(col)) { + case Akonadi::SpecialMailCollections::Inbox: + mBoxes.find(QStringLiteral("inbox"))->addSourceCollection(col.id()); + break; + case Akonadi::SpecialMailCollections::SentMail: + mBoxes.find(QStringLiteral("sent-mail"))->addSourceCollection(col.id()); + break; + case Akonadi::SpecialMailCollections::Drafts: + mBoxes.find(QStringLiteral("drafts"))->addSourceCollection(col.id()); + break; + default: + continue; + } + } + }); + connect(list, &Akonadi::CollectionFetchJob::finished, + this, [this, cb = std::move(cb)]() { + saveBoxes(); + if (cb) { + cb(); + } + }); +} + +void UnifiedMailboxManager::discoverBoxCollections(LoadCallback &&cb) +{ + auto list = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this); + list->fetchScope().setResource(QStringLiteral("akonadi_unifiedmailbox_agent")); + connect(list, &Akonadi::CollectionFetchJob::collectionsReceived, + this, [this](const Akonadi::Collection::List &list) { + for (const auto &col : list) { + mBoxId.insert(col.name(), col.id()); + } + }); + connect(list, &Akonadi::CollectionFetchJob::finished, + this, [this, cb = std::move(cb)]() { + if (cb) { + cb(); + } + }); +} diff --git a/agents/unifiedmailboxagent/unifiedmailboxmanager.h b/agents/unifiedmailboxagent/unifiedmailboxmanager.h new file mode 100644 index 000000000..0a630a3f4 --- /dev/null +++ b/agents/unifiedmailboxagent/unifiedmailboxmanager.h @@ -0,0 +1,108 @@ +/* + 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 UNIFIEDBOXMANAGER_H +#define UNIFIEDBOXMANAGER_H + +#include +#include + +#include + +#include +#include + +class UnifiedMailboxManager; +class UnifiedMailbox { + friend class UnifiedMailboxManager; +public: + UnifiedMailbox() = default; + UnifiedMailbox(UnifiedMailbox &&) = default; + UnifiedMailbox(const UnifiedMailbox &) = default; + UnifiedMailbox &operator=(const UnifiedMailbox &) = default; + UnifiedMailbox &operator=(UnifiedMailbox &&) = default; + + QString id() const; + void setId(const QString &id); + + QString name() const; + void setName(const QString &name); + + QString icon() const; + void setIcon(const QString &icon); + + void addSourceCollection(qint64 source); + void removeSourceCollection(qint64 source); + void setSourceCollections(const QSet &sources); + QSet sourceCollections() const; + +private: + QString mId; + QString mName; + QString mIcon; + QSet mSources; +}; + +Q_DECLARE_METATYPE(UnifiedMailbox) + + +class UnifiedMailboxManager : public QObject +{ + Q_OBJECT +public: + using LoadCallback = std::function; + + explicit UnifiedMailboxManager(KSharedConfigPtr config, QObject *parent = nullptr); + ~UnifiedMailboxManager() override; + + void loadBoxes(LoadCallback &&cb); + void saveBoxes(); + + void insertBox(UnifiedMailbox box); + void removeBox(const QString &name); + + // FIXME: std::optional :-( + const UnifiedMailbox *unifiedMailboxForSource(qint64 source) const; + qint64 collectionIdForUnifiedMailbox(const QString &id) const; + + inline QHash::const_iterator begin() const + { + return mBoxes.begin(); + } + + inline QHash::const_iterator end() const + { + return mBoxes.end(); + } + + void discoverBoxCollections(LoadCallback &&cb); + +Q_SIGNALS: + void boxesChanged(); + +private: + void discoverDefaultBoxes(LoadCallback &&cb); + void loadBoxesFromConfig(LoadCallback &&cb); + + QHash mBoxes; + QHash mBoxId; + + KSharedConfigPtr mConfig; +}; +#endif diff --git a/agents/unifiedmailboxagent/utils.h b/agents/unifiedmailboxagent/utils.h new file mode 100644 index 000000000..bc76096c7 --- /dev/null +++ b/agents/unifiedmailboxagent/utils.h @@ -0,0 +1,29 @@ +/* + 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 + +template +QList setToList(QSet &&set) +{ + QList rv; + rv.reserve(set.size()); + std::copy(set.cbegin(), set.cend(), std::back_inserter(rv)); + return rv; +}