diff --git a/agents/unifiedmailboxagent/unifiedmailboxagent.cpp b/agents/unifiedmailboxagent/unifiedmailboxagent.cpp index da6176120..723b27ff4 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxagent.cpp +++ b/agents/unifiedmailboxagent/unifiedmailboxagent.cpp @@ -1,232 +1,244 @@ /* 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 #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) { + qCWarning(agent_log) << "MailboxManager wants us to update Box but does not have its CollectionId!?"; + return; + } + + // Schedule collection sync for the box + synchronizeCollection(colId); + }); + 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) { Akonadi::Collection col; 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()); 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/unifiedmailboxmanager.cpp b/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp index d39e80f16..94398d585 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp +++ b/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp @@ -1,391 +1,475 @@ /* 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 "unifiedmailboxagent_debug.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) { qCWarning(agent_log) << "Missing box->collection mapping for unified mailbox" << box->id(); return; } new Akonadi::LinkJob(Akonadi::Collection{boxId}, {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) { qCWarning(agent_log) << "Missing box->collection mapping for unified mailbox" << box->id(); } new Akonadi::UnlinkJob(Akonadi::Collection{boxId}, 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); - const auto srcBox = unifiedMailboxForSource(srcCollection.id()); - const auto dstBox = unifiedMailboxForSource(dstCollection.id()); - qCDebug(agent_log ) << "Items moved away from " << srcCollection.id() << "(box" << srcBox << ") to " << dstCollection.id() << "(Box" << dstBox << ")"; - if (srcBox) { + 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); } - if (dstBox) { + 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); } //TODO Settings::self()->setLastSeenEvent(std::chrono::steady_clock::now().time_since_epoch().count()); }); - connect(&mMonitor, &Akonadi::Monitor::collectionAdded, - this, [this](const Akonadi::Collection &col) { - ReplayNextOnExit replayNext(mMonitor); - if (isUnifiedMailbox(col)) { - mBoxId.insert(col.name(), col.id()); - } else { - // TODO: Potentially a new special collection: we should auto-add it to our box - } - }); connect(&mMonitor, &Akonadi::Monitor::collectionRemoved, this, [this](const Akonadi::Collection &col) { ReplayNextOnExit replayNext(mMonitor); - // TODO: If it was a source collection for one of our boxes, remove it from box's sources - }); + 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()) { - // TODO: Remove collection from whichever special mailbox it may - // have belonged to before and add it to correct special mailbox + const auto srcBox = unregisterSpecialSourceCollection(col.id()); + const auto dstBox = registerSpecialSourceCollection(col); + if (srcBox == dstBox) { + return; + } + + saveBoxes(); + if (srcBox) { + Q_EMIT updateBox(*srcBox); + } + if (dstBox) { + Q_EMIT updateBox(*dstBox); + } } else { - // TODO: Check whether it's a source of one of our special - // mailboxes and remove it from there + if (const auto box = unregisterSpecialSourceCollection(col.id())) { + saveBoxes(); + 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"))); QList sources = boxGroup.readEntry("sources", QList{}); for (auto source : sources) { box.addSourceCollection(source); mMonitor.setCollectionMonitored(Akonadi::Collection(source)); } insertBox(std::move(box)); } if (mBoxes.isEmpty()) { 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())); } + mConfig->sync(); } void UnifiedMailboxManager::insertBox(UnifiedMailbox box) { mBoxes.insert(box.id(), box); + for (const auto srcCol : box.sourceCollections()) { + mMonitor.setCollectionMonitored(Akonadi::Collection{srcCol}, false); + } } void UnifiedMailboxManager::removeBox(const QString &name) { - mBoxes.remove(name); + auto box = mBoxes.find(name); + if (box == mBoxes.end()) { + return; + } + + for (const auto srcCol : box->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; } } return nullptr; } +UnifiedMailbox *UnifiedMailboxManager::unifiedMailboxForSource(qint64 source) +{ + for (auto &box : mBoxes) { + if (box.mSources.contains(source)) { + return &box; + } + } + + return nullptr; +} + 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; } return &(*box); } qint64 UnifiedMailboxManager::collectionIdFromUnifiedMailbox(const QString &id) const { return mBoxId.value(id, -1); } 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")); 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 (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: 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, [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; + if (attr->collectionType() == "inbox") { + box = mBoxes.find(QStringLiteral("inbox")); + } else if (attr->collectionType() == "sent-mail") { + box = mBoxes.find(QStringLiteral("sent-mail")); + } else if (attr->collectionType() == "drafts") { + box = mBoxes.find(QStringLiteral("drafts")); + } + if (box == mBoxes.end()) { + return {}; + } + + box->addSourceCollection(col.id()); + mMonitor.setCollectionMonitored(col); + return &(*box); +} + +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 0b15f3033..154d0bfb5 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxmanager.h +++ b/agents/unifiedmailboxagent/unifiedmailboxmanager.h @@ -1,112 +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. */ #ifndef UNIFIEDBOXMANAGER_H #define UNIFIEDBOXMANAGER_H #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 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; + UnifiedMailbox *unifiedMailboxForSource(qint64 source); const UnifiedMailbox *unifiedMailboxFromCollection(const Akonadi::Collection &col) const; qint64 collectionIdFromUnifiedMailbox(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); static bool isUnifiedMailbox(const Akonadi::Collection &col); + +Q_SIGNALS: + 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; Akonadi::ChangeRecorder mMonitor; QSettings mMonitorSettings; KSharedConfigPtr mConfig; }; #endif