diff --git a/agents/unifiedmailboxagent/CMakeLists.txt b/agents/unifiedmailboxagent/CMakeLists.txt index 0e2b53217..0cede6572 100644 --- a/agents/unifiedmailboxagent/CMakeLists.txt +++ b/agents/unifiedmailboxagent/CMakeLists.txt @@ -1,47 +1,50 @@ add_definitions(-DTRANSLATION_DOMAIN=\"akonadi_unifiedmailbox_agent\") +set(CMAKE_CXX_STANDARD 14) + if(BUILD_TESTING) #add_subdirectory(tests) #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 ) 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/settingsdialog.cpp b/agents/unifiedmailboxagent/settingsdialog.cpp index 8e3bf395b..378d73343 100644 --- a/agents/unifiedmailboxagent/settingsdialog.cpp +++ b/agents/unifiedmailboxagent/settingsdialog.cpp @@ -1,145 +1,146 @@ /* 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 "unifiedmailbox.h" #include "mailkernel.h" #include #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); + auto mailbox = std::make_unique(); + auto editor = new UnifiedMailboxEditor(mailbox.get(), this); if (editor->exec()) { - auto box = editor->box(); - box.setId(box.name()); // assign ID - addBox(box); - mBoxManager.insertBox(std::move(box)); + mailbox->setId(mailbox->name()); // assign ID + addBox(mailbox.get()); + mBoxManager.insertBox(std::move(mailbox)); } }); 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); + auto mailbox = item->data().value(); + auto editor = new UnifiedMailboxEditor(mailbox, 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)); + item->setText(mailbox->name()); + item->setIcon(QIcon::fromTheme(mailbox->icon())); } } }); 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(); + const auto mailbox = item->data().value(); if (KMessageBox::warningYesNo( - this, i18n("Do you really want to remove unified mailbox %1?", box.name()), + this, i18n("Do you really want to remove unified mailbox %1?", mailbox->name()), i18n("Really Remove?"), KStandardGuiItem::remove(), KStandardGuiItem::cancel()) == KMessageBox::Yes) { mBoxModel->removeRow(item->row()); - mBoxManager.removeBox(box.name()); + mBoxManager.removeBox(mailbox->id()); } } }); 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); + for (const auto &mailboxIt : mBoxManager) { + addBox(mailboxIt.second.get()); } } -void SettingsDialog::addBox(const UnifiedMailbox &box) +void SettingsDialog::addBox(UnifiedMailbox *box) { - auto item = new QStandardItem(QIcon::fromTheme(box.icon()), box.name()); + 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 index 9c83cfb03..c6071678c 100644 --- a/agents/unifiedmailboxagent/settingsdialog.h +++ b/agents/unifiedmailboxagent/settingsdialog.h @@ -1,55 +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); + void addBox(UnifiedMailbox *box); private: QStandardItemModel *mBoxModel = nullptr; UnifiedMailboxManager &mBoxManager; MailKernel *mKernel = nullptr; }; #endif diff --git a/agents/unifiedmailboxagent/unifiedmailbox.cpp b/agents/unifiedmailboxagent/unifiedmailbox.cpp new file mode 100644 index 000000000..260954a81 --- /dev/null +++ b/agents/unifiedmailboxagent/unifiedmailbox.cpp @@ -0,0 +1,121 @@ +/* + 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 "unifiedmailbox.h" +#include "unifiedmailboxmanager.h" + +UnifiedMailbox::UnifiedMailbox(UnifiedMailboxManager *manager) + : mManager(manager) +{ +} + +bool UnifiedMailbox::isSpecial() const +{ + return mId == QLatin1String("inbox") + || mId == QLatin1String("sent-mail") + || mId == QLatin1String("drafts"); +} + +void UnifiedMailbox::setCollectionId(qint64 id) +{ + mCollectionId = id; +} + +qint64 UnifiedMailbox::collectionId() const +{ + return mCollectionId; +} + +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); + if (mManager) { + mManager->mMonitor.setCollectionMonitored(Akonadi::Collection{source}); + } +} + +void UnifiedMailbox::removeSourceCollection(qint64 source) +{ + mSources.remove(source); + if (mManager) { + mManager->mMonitor.setCollectionMonitored(Akonadi::Collection{source}, false); + } +} + +void UnifiedMailbox::setSourceCollections(const QSet &sources) +{ + const auto updateMonitor = [this](bool monitor) { + if (mManager) { + for (const auto source : mSources) { + mManager->mMonitor.setCollectionMonitored(Akonadi::Collection{source}, monitor); + } + } + }; + + updateMonitor(false); + mSources = sources; + updateMonitor(true); +} + +QSet UnifiedMailbox::sourceCollections() const +{ + return mSources; +} + +void UnifiedMailbox::attachManager(UnifiedMailboxManager *manager) +{ + Q_ASSERT(manager); + if (mManager != manager) { + mManager = manager; + // Force that we start monitoring all the collections + setSourceCollections(mSources); + } +} + + diff --git a/agents/unifiedmailboxagent/unifiedmailbox.h b/agents/unifiedmailboxagent/unifiedmailbox.h new file mode 100644 index 000000000..229091f7b --- /dev/null +++ b/agents/unifiedmailboxagent/unifiedmailbox.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 UNIFIEDMAILBOX_H +#define UNIFIEDMAILBOX_H + +#include +#include +#include + +class UnifiedMailboxManager; +class UnifiedMailbox { + friend class UnifiedMailboxManager; +public: + UnifiedMailbox() = default; + UnifiedMailbox(UnifiedMailbox &&) = default; + UnifiedMailbox &operator=(UnifiedMailbox &&) = default; + + UnifiedMailbox(const UnifiedMailbox &) = delete; + UnifiedMailbox &operator=(const UnifiedMailbox &) = delete; + + bool isSpecial() const; + + qint64 collectionId() const; + void setCollectionId(qint64 id); + + 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: + UnifiedMailbox(UnifiedMailboxManager *manager); + + void attachManager(UnifiedMailboxManager *manager); + + qint64 mCollectionId = -1; + QString mId; + QString mName; + QString mIcon; + QSet mSources; + + UnifiedMailboxManager *mManager = nullptr; +}; + +Q_DECLARE_METATYPE(UnifiedMailbox*) + + +#endif diff --git a/agents/unifiedmailboxagent/unifiedmailboxagent.cpp b/agents/unifiedmailboxagent/unifiedmailboxagent.cpp index 723b27ff4..e6c7bf8ef 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxagent.cpp +++ b/agents/unifiedmailboxagent/unifiedmailboxagent.cpp @@ -1,244 +1,245 @@ /* 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 "settingsdialog.h" #include "settings.h" #include #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")); connect(&mBoxManager, &UnifiedMailboxManager::updateBox, - this, [this](const UnifiedMailbox &box) { - const auto colId = mBoxManager.collectionIdFromUnifiedMailbox(box.id()); - if (colId <= -1) { + this, [this](const UnifiedMailbox *box) { + if (box->collectionId() <= -1) { qCWarning(agent_log) << "MailboxManager wants us to update Box but does not have its CollectionId!?"; return; } // Schedule collection sync for the box - synchronizeCollection(colId); + synchronizeCollection(box->collectionId()); }); 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(); }); }); } 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::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); - for (const auto &box : mBoxManager) { + for (const auto &boxIt : mBoxManager) { + const auto &box = boxIt.second; Akonadi::Collection col; - col.setName(box.id()); - col.setRemoteId(box.id()); + col.setName(box->id()); + col.setRemoteId(box->id()); col.setParentCollection(topLevel); col.setContentMimeTypes({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()); + displayAttr->setDisplayName(box->name()); + displayAttr->setIconName(box->icon()); collections.push_back(std::move(col)); } collectionsRetrieved(std::move(collections)); } void UnifiedMailboxAgent::retrieveItems(const Akonadi::Collection &c) { // 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); // Optimize: we could've only missed events that occured since the last time we saw one // TODO: fetch->fetchScope().setFetchChangedSince(lastSeenEvent); 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); // TODO: fetch->fetchScope().setFetchChangedSince(lastSeenEvent); 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/unifiedmailboxeditor.cpp b/agents/unifiedmailboxagent/unifiedmailboxeditor.cpp index 52d452079..dd2ac9dc9 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxeditor.cpp +++ b/agents/unifiedmailboxagent/unifiedmailboxeditor.cpp @@ -1,172 +1,169 @@ /* 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 "unifiedmailbox.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 !UnifiedMailboxManager::isUnifiedMailbox(col); } }; } UnifiedMailboxEditor::UnifiedMailboxEditor(QWidget* parent) : UnifiedMailboxEditor({}, parent) { } -UnifiedMailboxEditor::UnifiedMailboxEditor(UnifiedMailbox mailbox, QWidget *parent) +UnifiedMailboxEditor::UnifiedMailboxEditor(UnifiedMailbox *mailbox, QWidget *parent) : QDialog(parent) - , mBox(std::move(mailbox)) + , mMailbox(mailbox) { resize(500, 900); auto l = new QVBoxLayout; setLayout(l); auto f = new QFormLayout; l->addLayout(f); - auto nameEdit = new QLineEdit(mBox.name()); + auto nameEdit = new QLineEdit(mMailbox->name()); f->addRow(i18n("Name:"), nameEdit); connect(nameEdit, &QLineEdit::textChanged, this, [this](const QString &name) { - mBox.setName(name); + mMailbox->setName(name); }); - auto iconButton = new QPushButton(QIcon::fromTheme(mBox.icon(), QIcon::fromTheme(QStringLiteral("folder-mail"))), + auto iconButton = new QPushButton(QIcon::fromTheme(mMailbox->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); + mMailbox->setIcon(iconName); iconButton->setIcon(QIcon::fromTheme(iconName)); } }); - mBox.setIcon(iconButton->icon().name()); + mMailbox->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(); + const auto sources = mMailbox->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()); + mMailbox->addSourceCollection(index.data(Akonadi::EntityTreeModel::CollectionIdRole).toLongLong()); } indexes = deselected.indexes(); for (const auto &index : indexes) { - mBox.removeSourceCollection(index.data(Akonadi::EntityTreeModel::CollectionIdRole).toLongLong()); + mMailbox->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 index 511b70e6c..3dd17ce70 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxeditor.h +++ b/agents/unifiedmailboxagent/unifiedmailboxeditor.h @@ -1,35 +1,34 @@ /* 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 UnifiedMailbox; class UnifiedMailboxEditor : public QDialog { Q_OBJECT public: explicit UnifiedMailboxEditor(QWidget *parent = nullptr); - explicit UnifiedMailboxEditor(UnifiedMailbox mailbox, QWidget *parent = nullptr); - - UnifiedMailbox box() const; + explicit UnifiedMailboxEditor(UnifiedMailbox *mailbox, QWidget *parent = nullptr); private: - UnifiedMailbox mBox; + UnifiedMailbox *mMailbox = nullptr; }; diff --git a/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp b/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp index 94398d585..97fce85c0 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp +++ b/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp @@ -1,475 +1,413 @@ /* 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 #include #include #include #include #include #include #include #include #include #include -#include "utils.h" 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; }; } -bool UnifiedMailbox::isSpecial() const -{ - return mId == QLatin1String("inbox") - || mId == QLatin1String("sent-mail") - || mId == QLatin1String("drafts"); -} - -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; -} - // static bool UnifiedMailboxManager::isUnifiedMailbox(const Akonadi::Collection &col) { return col.resource() == QLatin1String("akonadi_unifiedmailbox_agent"); } 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; } - const auto boxId = collectionIdFromUnifiedMailbox(box->id()); - qCDebug(agent_log) << "Unified box:" << box->name() << ", collection" << boxId; - if (boxId <= -1) { + if (box->collectionId() <= -1) { qCWarning(agent_log) << "Missing box->collection mapping for unified mailbox" << box->id(); return; } - new Akonadi::LinkJob(Akonadi::Collection{boxId}, {item}, this); + new Akonadi::LinkJob(Akonadi::Collection{box->collectionId()}, {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; } - const auto boxId = collectionIdFromUnifiedMailbox(box->id()); - if (boxId <= -1) { + if (box->collectionId() <= -1) { qCWarning(agent_log) << "Missing box->collection mapping for unified mailbox" << box->id(); } - new Akonadi::UnlinkJob(Akonadi::Collection{boxId}, items, this); + new Akonadi::UnlinkJob(Akonadi::Collection{box->collectionId()}, 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 - const auto srcBoxId = collectionIdFromUnifiedMailbox(srcBox->id()); - new Akonadi::UnlinkJob(Akonadi::Collection{srcBoxId}, items, this); + new Akonadi::UnlinkJob(Akonadi::Collection{srcBox->collectionId()}, items, this); } if (const auto dstBox = unifiedMailboxForSource(dstCollection.id())) { // Move destination collection is our source, link the Item into a box - const auto dstBoxId = collectionIdFromUnifiedMailbox(dstBox->id()); - new Akonadi::LinkJob(Akonadi::Collection{dstBoxId}, items, this); + new Akonadi::LinkJob(Akonadi::Collection{dstBox->collectionId()}, items, this); } //TODO Settings::self()->setLastSeenEvent(std::chrono::steady_clock::now().time_since_epoch().count()); }); 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 &>::of(&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) { - Q_EMIT updateBox(*srcBox); + Q_EMIT updateBox(srcBox); } if (dstBox) { - Q_EMIT updateBox(*dstBox); + Q_EMIT updateBox(dstBox); } } else { if (const auto box = unregisterSpecialSourceCollection(col.id())) { saveBoxes(); - Q_EMIT updateBox(*box); + Q_EMIT updateBox(box); } } }); } UnifiedMailboxManager::~UnifiedMailboxManager() { } void UnifiedMailboxManager::loadBoxes(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"))); + auto box = std::make_unique(); + 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); - mMonitor.setCollectionMonitored(Akonadi::Collection(source)); - } + box->setSourceCollections(listToSet(std::move(sources))); insertBox(std::move(box)); } - if (mBoxes.isEmpty()) { + if (mMailboxes.empty()) { createDefaultBoxes(std::move(cb)); } else { discoverBoxCollections([this, cb = std::move(cb)]() { // 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(); if (cb) { 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())); + for (const auto &boxIt : mMailboxes) { + const auto &box = boxIt.second; + auto boxGroup = group.group(box->id()); + boxGroup.writeEntry("name", box->name()); + boxGroup.writeEntry("icon", box->icon()); + boxGroup.writeEntry("sources", setToList(box->sourceCollections())); } mConfig->sync(); } -void UnifiedMailboxManager::insertBox(UnifiedMailbox box) +void UnifiedMailboxManager::insertBox(std::unique_ptr box) { - mBoxes.insert(box.id(), box); - for (const auto srcCol : box.sourceCollections()) { - mMonitor.setCollectionMonitored(Akonadi::Collection{srcCol}, false); - } + auto it = mMailboxes.emplace(std::make_pair(box->id(), std::move(box))); + it.first->second->attachManager(this); } -void UnifiedMailboxManager::removeBox(const QString &name) +void UnifiedMailboxManager::removeBox(const QString &id) { - auto box = mBoxes.find(name); - if (box == mBoxes.end()) { + auto box = std::find_if(mMailboxes.begin(), mMailboxes.end(), + [&id](const std::pair> &box) { + return box.second->id() == id; + }); + if (box == mMailboxes.end()) { return; } - for (const auto srcCol : box->sourceCollections()) { + for (const auto srcCol : box->second->sourceCollections()) { mMonitor.setCollectionMonitored(Akonadi::Collection{srcCol}, false); } - mBoxes.erase(box); -} - -const UnifiedMailbox *UnifiedMailboxManager::unifiedMailboxForSource(qint64 source) const -{ - for (const auto &box : mBoxes) { - if (box.mSources.contains(source)) { - return &box; + for (auto it = mSourceToBoxMap.begin(), end = mSourceToBoxMap.end(); it != end; ) { + if (it->second == box->second.get()) { + it = mSourceToBoxMap.erase(it); + } else { + ++it; } } - - return nullptr; + mMailboxes.erase(box); } -UnifiedMailbox *UnifiedMailboxManager::unifiedMailboxForSource(qint64 source) +UnifiedMailbox *UnifiedMailboxManager::unifiedMailboxForSource(qint64 source) const { - for (auto &box : mBoxes) { - if (box.mSources.contains(source)) { - return &box; - } + const auto box = mSourceToBoxMap.find(source); + if (box == mSourceToBoxMap.cend()) { + return {}; } - - return nullptr; + return box->second; } -const UnifiedMailbox * UnifiedMailboxManager::unifiedMailboxFromCollection(const Akonadi::Collection &col) const +UnifiedMailbox * UnifiedMailboxManager::unifiedMailboxFromCollection(const Akonadi::Collection &col) const { if (!isUnifiedMailbox(col)) { return nullptr; } - const auto box = mBoxes.find(col.name()); - if (box == mBoxes.cend()) { - return nullptr; + const auto box = mMailboxes.find(col.name()); + if (box == mMailboxes.cend()) { + return {}; } - return &(*box); -} - - -qint64 UnifiedMailboxManager::collectionIdFromUnifiedMailbox(const QString &id) const -{ - return mBoxId.value(id, -1); + return box->second.get(); } void UnifiedMailboxManager::createDefaultBoxes(LoadCallback &&cb) { // First build empty boxes - UnifiedMailbox inbox; - inbox.setId(QStringLiteral("inbox")); - inbox.setName(i18n("Inbox")); - inbox.setIcon(QStringLiteral("mail-folder-inbox")); + auto inbox = std::make_unique(); + inbox->attachManager(this); + 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")); + auto sent = std::make_unique(); + sent->attachManager(this); + 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")); + auto drafts = std::make_unique(); + drafts->attachManager(this); + 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 (isUnifiedMailbox(col)) { 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: + try { + switch (Akonadi::SpecialMailCollections::self()->specialCollectionType(col)) { + case Akonadi::SpecialMailCollections::Inbox: + mMailboxes.at(QStringLiteral("inbox"))->addSourceCollection(col.id()); + break; + case Akonadi::SpecialMailCollections::SentMail: + mMailboxes.at(QStringLiteral("sent-mail"))->addSourceCollection(col.id()); + break; + case Akonadi::SpecialMailCollections::Drafts: + mMailboxes.at(QStringLiteral("drafts"))->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, 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()); + if (col.name() == QLatin1String("akonadi_unifiedmailbox_agent")) { + continue; + } + + mMailboxes.at(col.name())->setCollectionId(col.id()); } }); connect(list, &Akonadi::CollectionFetchJob::finished, this, [cb = std::move(cb)]() { if (cb) { cb(); } }); } 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(mBoxes)::iterator box; + decltype(mMailboxes)::iterator box; if (attr->collectionType() == "inbox") { - box = mBoxes.find(QStringLiteral("inbox")); + box = mMailboxes.find(QStringLiteral("inbox")); } else if (attr->collectionType() == "sent-mail") { - box = mBoxes.find(QStringLiteral("sent-mail")); + box = mMailboxes.find(QStringLiteral("sent-mail")); } else if (attr->collectionType() == "drafts") { - box = mBoxes.find(QStringLiteral("drafts")); + box = mMailboxes.find(QStringLiteral("drafts")); } - if (box == mBoxes.end()) { + if (box == mMailboxes.end()) { return {}; } - box->addSourceCollection(col.id()); + box->second->addSourceCollection(col.id()); mMonitor.setCollectionMonitored(col); - return &(*box); + return box->second.get(); } const UnifiedMailbox *UnifiedMailboxManager::unregisterSpecialSourceCollection(qint64 colId) { auto box = unifiedMailboxForSource(colId); if (!box->isSpecial()) { return {}; } box->removeSourceCollection(colId); mMonitor.setCollectionMonitored(Akonadi::Collection{colId}, false); return box; } diff --git a/agents/unifiedmailboxagent/unifiedmailboxmanager.h b/agents/unifiedmailboxagent/unifiedmailboxmanager.h index 154d0bfb5..f7f7dde5e 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxmanager.h +++ b/agents/unifiedmailboxagent/unifiedmailboxmanager.h @@ -1,121 +1,88 @@ /* 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 "utils.h" + #include #include #include #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; - - bool isSpecial() const; - - 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 UnifiedMailbox; class UnifiedMailboxManager : public QObject { Q_OBJECT + friend class UnifiedMailbox; 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); + void insertBox(std::unique_ptr box); + void removeBox(const QString &id); - // FIXME: std::optional :-( - const UnifiedMailbox *unifiedMailboxForSource(qint64 source) const; - UnifiedMailbox *unifiedMailboxForSource(qint64 source); - const UnifiedMailbox *unifiedMailboxFromCollection(const Akonadi::Collection &col) const; - qint64 collectionIdFromUnifiedMailbox(const QString &id) const; + UnifiedMailbox *unifiedMailboxForSource(qint64 source) const; + UnifiedMailbox *unifiedMailboxFromCollection(const Akonadi::Collection &col) const; - inline QHash::const_iterator begin() const + inline auto begin() const { - return mBoxes.begin(); + return mMailboxes.begin(); } - inline QHash::const_iterator end() const + inline auto end() const { - return mBoxes.end(); + return mMailboxes.end(); } void discoverBoxCollections(LoadCallback &&cb); static bool isUnifiedMailbox(const Akonadi::Collection &col); Q_SIGNALS: - void updateBox(const UnifiedMailbox &box); + void updateBox(const UnifiedMailbox *box); private: void createDefaultBoxes(LoadCallback &&cb); const UnifiedMailbox *unregisterSpecialSourceCollection(qint64 colId); const UnifiedMailbox *registerSpecialSourceCollection(const Akonadi::Collection &col); - QHash mBoxes; - QHash mBoxId; + // Using unordered_map because Qt containers do not support movable-only types + std::unordered_map> mMailboxes; + std::unordered_map mSourceToBoxMap; Akonadi::ChangeRecorder mMonitor; QSettings mMonitorSettings; KSharedConfigPtr mConfig; }; #endif diff --git a/agents/unifiedmailboxagent/utils.h b/agents/unifiedmailboxagent/utils.h index bc76096c7..760938f5a 100644 --- a/agents/unifiedmailboxagent/utils.h +++ b/agents/unifiedmailboxagent/utils.h @@ -1,29 +1,56 @@ /* 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 UTILS_H_ +#define UTILS_H_ + #include +#include +#include template QList setToList(QSet &&set) { QList rv; rv.reserve(set.size()); std::copy(set.cbegin(), set.cend(), std::back_inserter(rv)); return rv; } + +template +QSet listToSet(QList &&list) +{ + QSet rv; + rv.reserve(list.size()); + for (auto t : list) { + rv.insert(std::move(t)); + } + return rv; +} + +namespace std { + template<> + struct hash { + size_t operator()(const QString &str) const { + return qHash(str); + } + }; +} + +#endif