diff --git a/agents/unifiedmailboxagent/settingsdialog.cpp b/agents/unifiedmailboxagent/settingsdialog.cpp index e33ffc609..8d99ebd94 100644 --- a/agents/unifiedmailboxagent/settingsdialog.cpp +++ b/agents/unifiedmailboxagent/settingsdialog.cpp @@ -1,146 +1,162 @@ /* 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 #include +namespace { + +static constexpr const char *DialogGroup = "__Dialog"; + +} + SettingsDialog::SettingsDialog(KSharedConfigPtr config, UnifiedMailboxManager &boxManager, WId, QWidget *parent) : QDialog(parent) , mBoxManager(boxManager) , mKernel(new MailKernel(config, this)) + , mConfig(config) { - 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 mailbox = std::make_unique(); - auto editor = new UnifiedMailboxEditor(mailbox.get(), this); + auto editor = new UnifiedMailboxEditor(mailbox.get(), mConfig, this); if (editor->exec()) { 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 mailbox = item->data().value(); - auto editor = new UnifiedMailboxEditor(mailbox, this); + auto editor = new UnifiedMailboxEditor(mailbox, mConfig, this); if (editor->exec()) { 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 mailbox = item->data().value(); if (KMessageBox::warningYesNo( 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(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(); + + const auto dlgGroup = config->group(DialogGroup); + if (dlgGroup.hasKey("geometry")) { + restoreGeometry(dlgGroup.readEntry("geometry", QByteArray())); + } else { + resize(500, 500); + } + } SettingsDialog::~SettingsDialog() { + auto dlgGroup = mConfig->group(DialogGroup); + dlgGroup.writeEntry("geometry", saveGeometry()); } void SettingsDialog::accept() { mBoxManager.saveBoxes(); QDialog::accept(); } void SettingsDialog::loadBoxes() { mBoxModel->clear(); for (const auto &mailboxIt : mBoxManager) { addBox(mailboxIt.second.get()); } } void SettingsDialog::addBox(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 index c6071678c..c4b835c17 100644 --- a/agents/unifiedmailboxagent/settingsdialog.h +++ b/agents/unifiedmailboxagent/settingsdialog.h @@ -1,55 +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 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(UnifiedMailbox *box); private: QStandardItemModel *mBoxModel = nullptr; UnifiedMailboxManager &mBoxManager; MailKernel *mKernel = nullptr; + KSharedConfigPtr mConfig; }; #endif diff --git a/agents/unifiedmailboxagent/unifiedmailbox.h b/agents/unifiedmailboxagent/unifiedmailbox.h index 102e56ce0..f241c90e7 100644 --- a/agents/unifiedmailboxagent/unifiedmailbox.h +++ b/agents/unifiedmailboxagent/unifiedmailbox.h @@ -1,82 +1,83 @@ /* 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 #include "utils.h" class KConfigGroup; class UnifiedMailboxManager; -class UnifiedMailbox { +class UnifiedMailbox +{ friend class UnifiedMailboxManager; public: UnifiedMailbox() = default; UnifiedMailbox(UnifiedMailbox &&) = default; UnifiedMailbox &operator=(UnifiedMailbox &&) = default; UnifiedMailbox(const UnifiedMailbox &) = delete; UnifiedMailbox &operator=(const UnifiedMailbox &) = delete; /** Compares two boxes by their ID **/ bool operator==(const UnifiedMailbox &other) const; void save(KConfigGroup &group) const; void load(const KConfigGroup &group); bool isSpecial() const; stdx::optional 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: void attachManager(UnifiedMailboxManager *manager); stdx::optional mCollectionId; QString mId; QString mName; QString mIcon; QSet mSources; UnifiedMailboxManager *mManager = nullptr; }; Q_DECLARE_METATYPE(UnifiedMailbox*) #endif diff --git a/agents/unifiedmailboxagent/unifiedmailboxeditor.cpp b/agents/unifiedmailboxagent/unifiedmailboxeditor.cpp index dd2ac9dc9..0679b1239 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxeditor.cpp +++ b/agents/unifiedmailboxagent/unifiedmailboxeditor.cpp @@ -1,169 +1,184 @@ /* 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 #include namespace { +static constexpr const char *EditorGroup = "__Editor"; + 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(KSharedConfigPtr config, QWidget* parent) + : UnifiedMailboxEditor({}, config, parent) { } -UnifiedMailboxEditor::UnifiedMailboxEditor(UnifiedMailbox *mailbox, QWidget *parent) +UnifiedMailboxEditor::UnifiedMailboxEditor(UnifiedMailbox *mailbox, KSharedConfigPtr config, QWidget *parent) : QDialog(parent) , mMailbox(mailbox) + , mConfig(config) { - resize(500, 900); - auto l = new QVBoxLayout; setLayout(l); auto f = new QFormLayout; l->addLayout(f); auto nameEdit = new QLineEdit(mMailbox->name()); f->addRow(i18n("Name:"), nameEdit); connect(nameEdit, &QLineEdit::textChanged, this, [this](const QString &name) { mMailbox->setName(name); }); 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()) { mMailbox->setIcon(iconName); iconButton->setIcon(QIcon::fromTheme(iconName)); } }); 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 = 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) { mMailbox->addSourceCollection(index.data(Akonadi::EntityTreeModel::CollectionIdRole).toLongLong()); } indexes = deselected.indexes(); for (const auto &index : indexes) { 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); + + const auto editorGroup = config->group(EditorGroup); + if (editorGroup.hasKey("geometry")) { + restoreGeometry(editorGroup.readEntry("geometry", QByteArray())); + } else { + resize(500, 900); + } +} + +UnifiedMailboxEditor::~UnifiedMailboxEditor() +{ + auto editorGrp = mConfig->group(EditorGroup); + editorGrp.writeEntry("geometry", saveGeometry()); } #include "unifiedmailboxeditor.moc" diff --git a/agents/unifiedmailboxagent/unifiedmailboxeditor.h b/agents/unifiedmailboxagent/unifiedmailboxeditor.h index 3dd17ce70..0b9e629cc 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxeditor.h +++ b/agents/unifiedmailboxagent/unifiedmailboxeditor.h @@ -1,34 +1,36 @@ /* 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); + explicit UnifiedMailboxEditor(KSharedConfigPtr config, QWidget *parent = nullptr); + explicit UnifiedMailboxEditor(UnifiedMailbox *mailbox, KSharedConfigPtr config, QWidget *parent = nullptr); + ~UnifiedMailboxEditor() override; private: UnifiedMailbox *mMailbox = nullptr; + KSharedConfigPtr mConfig; }; diff --git a/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp b/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp index 2aa8854bf..4f8422e31 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp +++ b/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp @@ -1,433 +1,433 @@ /* Copyright (C) 2018 Daniel Vrátil This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "unifiedmailboxmanager.h" #include "unifiedmailbox.h" #include "unifiedmailboxagent_debug.h" #include "utils.h" #include "common.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace { /** * A little RAII helper to make sure changeProcessed() and replayNext() gets * called on the ChangeRecorder whenever we are done with handling a change. */ class ReplayNextOnExit { public: ReplayNextOnExit(Akonadi::ChangeRecorder &recorder) : mRecorder(recorder) {} ~ReplayNextOnExit() { mRecorder.changeProcessed(); mRecorder.replayNext(); } private: Akonadi::ChangeRecorder &mRecorder; }; } // static bool UnifiedMailboxManager::isUnifiedMailbox(const Akonadi::Collection &col) { #ifdef UNIT_TESTS return col.parentCollection().name() == Common::AgentIdentifier; #else return col.resource() == Common::AgentIdentifier; #endif } UnifiedMailboxManager::UnifiedMailboxManager(KSharedConfigPtr config, QObject* parent) : QObject(parent) , mConfig(std::move(config)) { mMonitor.setObjectName(QStringLiteral("UnifiedMailboxChangeRecorder")); mMonitor.setConfig(&mMonitorSettings); mMonitor.setChangeRecordingEnabled(true); mMonitor.setTypeMonitored(Akonadi::Monitor::Items); mMonitor.setTypeMonitored(Akonadi::Monitor::Collections); mMonitor.itemFetchScope().setCacheOnly(true); mMonitor.itemFetchScope().setFetchRemoteIdentification(false); mMonitor.itemFetchScope().setFetchModificationTime(false); mMonitor.collectionFetchScope().fetchAttribute(); connect(&mMonitor, &Akonadi::Monitor::itemAdded, this, [this](const Akonadi::Item &item, const Akonadi::Collection &collection) { ReplayNextOnExit replayNext(mMonitor); qCDebug(agent_log) << "Item" << item.id() << "added to collection" << collection.id(); const auto box = unifiedMailboxForSource(collection.id()); if (!box) { qCWarning(agent_log) << "Failed to find unified mailbox for source collection " << collection.id(); return; } if (!box->collectionId()) { qCWarning(agent_log) << "Missing box->collection mapping for unified mailbox" << box->id(); return; } new Akonadi::LinkJob(Akonadi::Collection{box->collectionId().value()}, {item}, this); }); connect(&mMonitor, &Akonadi::Monitor::itemsRemoved, this, [this](const Akonadi::Item::List &items) { ReplayNextOnExit replayNext(mMonitor); // Monitor did the heavy lifting for us and already figured out that // we only monitor the source collection of the Items and translated // it into REMOVE change. // This relies on Akonadi never mixing Items from different sources or // destination during batch-moves. const auto parentId = items.first().parentCollection().id(); const auto box = unifiedMailboxForSource(parentId); if (!box) { qCWarning(agent_log) << "Received Remove notification for Items belonging to" << parentId << "which we don't monitor"; return; } if (!box->collectionId()) { qCWarning(agent_log) << "Missing box->collection mapping for unified mailbox" << box->id(); return; } new Akonadi::UnlinkJob(Akonadi::Collection{box->collectionId().value()}, items, this); }); connect(&mMonitor, &Akonadi::Monitor::itemsMoved, this, [this](const Akonadi::Item::List &items, const Akonadi::Collection &srcCollection, const Akonadi::Collection &dstCollection) { ReplayNextOnExit replayNext(mMonitor); if (const auto srcBox = unifiedMailboxForSource(srcCollection.id())) { // Move source collection was our source, unlink the Item from a box new Akonadi::UnlinkJob(Akonadi::Collection{srcBox->collectionId().value()}, items, this); } if (const auto dstBox = unifiedMailboxForSource(dstCollection.id())) { // Move destination collection is our source, link the Item into a box new Akonadi::LinkJob(Akonadi::Collection{dstBox->collectionId().value()}, items, this); } }); connect(&mMonitor, &Akonadi::Monitor::collectionRemoved, this, [this](const Akonadi::Collection &col) { ReplayNextOnExit replayNext(mMonitor); if (auto box = unifiedMailboxForSource(col.id())) { box->removeSourceCollection(col.id()); mMonitor.setCollectionMonitored(col, false); if (box->sourceCollections().isEmpty()) { removeBox(box->id()); } saveBoxes(); // No need to resync the box collection, the linked Items got removed by Akonadi } else { qCWarning(agent_log) << "Received notification about removal of Collection" << col.id() << "which we don't monitor"; } }); connect(&mMonitor, qOverload &>(&Akonadi::Monitor::collectionChanged), this, [this](const Akonadi::Collection &col, const QSet &parts) { ReplayNextOnExit replayNext(mMonitor); qCDebug(agent_log) << "Collection changed:" << parts; if (!parts.contains(Akonadi::SpecialCollectionAttribute().type())) { return; } if (col.hasAttribute()) { const auto srcBox = unregisterSpecialSourceCollection(col.id()); const auto dstBox = registerSpecialSourceCollection(col); if (srcBox == dstBox) { return; } saveBoxes(); if (srcBox && srcBox->sourceCollections().isEmpty()) { removeBox(srcBox->id()); return; } if (srcBox) { Q_EMIT updateBox(srcBox); } if (dstBox) { Q_EMIT updateBox(dstBox); } } else { if (const auto box = unregisterSpecialSourceCollection(col.id())) { saveBoxes(); if (box->sourceCollections().isEmpty()) { removeBox(box->id()); } else { Q_EMIT updateBox(box); } } } }); } UnifiedMailboxManager::~UnifiedMailboxManager() { } Akonadi::ChangeRecorder &UnifiedMailboxManager::changeRecorder() { return mMonitor; } void UnifiedMailboxManager::loadBoxes(FinishedCallback &&finishedCb) { qCDebug(agent_log) << "loading boxes"; const auto group = mConfig->group("UnifiedMailboxes"); const auto boxGroups = group.groupList(); for (const auto &boxGroupName : boxGroups) { const auto boxGroup = group.group(boxGroupName); auto box = std::make_unique(); box->load(boxGroup); insertBox(std::move(box)); } const auto cb = [this, finishedCb = std::move(finishedCb)]() { qCDebug(agent_log) << "Finished callback: enabling change recorder"; // Only now start processing changes from change recorder connect(&mMonitor, &Akonadi::ChangeRecorder::changesAdded, &mMonitor, &Akonadi::ChangeRecorder::replayNext, Qt::QueuedConnection); // And start replaying any potentially pending notification QTimer::singleShot(0, &mMonitor, &Akonadi::ChangeRecorder::replayNext); if (finishedCb) { finishedCb(); } }; qCDebug(agent_log) << "Loaded" << mMailboxes.size() << "boxes from config"; if (mMailboxes.empty()) { createDefaultBoxes(std::move(cb)); } else { discoverBoxCollections(std::move(cb)); } } void UnifiedMailboxManager::saveBoxes() { auto group = mConfig->group("UnifiedMailboxes"); const auto currentGroups = group.groupList(); for (const auto &groupName : currentGroups) { group.deleteGroup(groupName); } for (const auto &boxIt : mMailboxes) { auto boxGroup = group.group(boxIt.second->id()); boxIt.second->save(boxGroup); } mConfig->sync(); } void UnifiedMailboxManager::insertBox(std::unique_ptr box) { auto it = mMailboxes.emplace(std::make_pair(box->id(), std::move(box))); it.first->second->attachManager(this); } void UnifiedMailboxManager::removeBox(const QString &id) { auto box = std::find_if(mMailboxes.begin(), mMailboxes.end(), [&id](const std::pair> &box) { return box.second->id() == id; }); if (box == mMailboxes.end()) { return; } box->second->attachManager(nullptr); mMailboxes.erase(box); } UnifiedMailbox *UnifiedMailboxManager::unifiedMailboxForSource(qint64 source) const { const auto box = mSourceToBoxMap.find(source); if (box == mSourceToBoxMap.cend()) { return {}; } return box->second; } -UnifiedMailbox * UnifiedMailboxManager::unifiedMailboxFromCollection(const Akonadi::Collection &col) const +UnifiedMailbox *UnifiedMailboxManager::unifiedMailboxFromCollection(const Akonadi::Collection &col) const { if (!isUnifiedMailbox(col)) { return nullptr; } const auto box = mMailboxes.find(col.name()); if (box == mMailboxes.cend()) { return {}; } return box->second.get(); } void UnifiedMailboxManager::createDefaultBoxes(FinishedCallback &&finishedCb) { // First build empty boxes auto inbox = std::make_unique(); inbox->attachManager(this); inbox->setId(Common::InboxBoxId); inbox->setName(i18n("Inbox")); inbox->setIcon(QStringLiteral("mail-folder-inbox")); insertBox(std::move(inbox)); auto sent = std::make_unique(); sent->attachManager(this); sent->setId(Common::SentBoxId); sent->setName(i18n("Sent")); sent->setIcon(QStringLiteral("mail-folder-sent")); insertBox(std::move(sent)); auto drafts = std::make_unique(); drafts->attachManager(this); drafts->setId(Common::DraftsBoxId); drafts->setName(i18n("Drafts")); drafts->setIcon(QStringLiteral("document-properties")); insertBox(std::move(drafts)); auto list = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this); list->fetchScope().fetchAttribute(); list->fetchScope().setContentMimeTypes({QStringLiteral("message/rfc822")}); #ifdef UNIT_TESTS list->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::Parent); #else list->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::None); #endif connect(list, &Akonadi::CollectionFetchJob::collectionsReceived, this, [this](const Akonadi::Collection::List &list) { for (const auto &col : list) { if (isUnifiedMailbox(col)) { continue; } try { switch (Akonadi::SpecialMailCollections::self()->specialCollectionType(col)) { case Akonadi::SpecialMailCollections::Inbox: mMailboxes.at(Common::InboxBoxId)->addSourceCollection(col.id()); break; case Akonadi::SpecialMailCollections::SentMail: mMailboxes.at(Common::SentBoxId)->addSourceCollection(col.id()); break; case Akonadi::SpecialMailCollections::Drafts: mMailboxes.at(Common::DraftsBoxId)->addSourceCollection(col.id()); break; default: continue; } } catch (const std::out_of_range &) { qCWarning(agent_log) << "Failed to find a special unified mailbox for source collection" << col.id(); continue; } } }); connect(list, &Akonadi::CollectionFetchJob::result, this, [this, finishedCb = std::move(finishedCb)]() { saveBoxes(); if (finishedCb) { finishedCb(); } }); } void UnifiedMailboxManager::discoverBoxCollections(FinishedCallback &&finishedCb) { auto list = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this); #ifdef UNIT_TESTS list->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::Parent); #else list->fetchScope().setResource(Common::AgentIdentifier); #endif connect(list, &Akonadi::CollectionFetchJob::collectionsReceived, this, [this](const Akonadi::Collection::List &list) { for (const auto &col : list) { if (!isUnifiedMailbox(col) || col.parentCollection() == Akonadi::Collection::root()) { continue; } mMailboxes.at(col.name())->setCollectionId(col.id()); } }); if (finishedCb) { connect(list, &Akonadi::CollectionFetchJob::result, this, finishedCb); } } const UnifiedMailbox *UnifiedMailboxManager::registerSpecialSourceCollection(const Akonadi::Collection& col) { // This is slightly awkward, wold be better if we could use SpecialMailCollections, // but it also relies on Monitor internally, so there's a possible race condition // between our ChangeRecorder and SpecialMailCollections' Monitor auto attr = col.attribute(); Q_ASSERT(attr); if (!attr) { return {}; } decltype(mMailboxes)::iterator box; if (attr->collectionType() == Common::SpecialCollectionInbox) { box = mMailboxes.find(Common::InboxBoxId); } else if (attr->collectionType() == Common::SpecialCollectionSentMail) { box = mMailboxes.find(Common::SentBoxId); } else if (attr->collectionType() == Common::SpecialCollectionDrafts) { box = mMailboxes.find(Common::DraftsBoxId); } if (box == mMailboxes.end()) { return {}; } box->second->addSourceCollection(col.id()); return box->second.get(); } const UnifiedMailbox *UnifiedMailboxManager::unregisterSpecialSourceCollection(qint64 colId) { auto box = unifiedMailboxForSource(colId); if (!box) { return {}; } if (!box->isSpecial()) { qDebug() << colId << "does not belong to a special unified box" << box->id(); return {}; } box->removeSourceCollection(colId); return box; }