diff --git a/agents/unifiedmailboxagent/settings.kcfg b/agents/unifiedmailboxagent/settings.kcfg
index 90e7aa616..45f8fb87a 100644
--- a/agents/unifiedmailboxagent/settings.kcfg
+++ b/agents/unifiedmailboxagent/settings.kcfg
@@ -1,18 +1,12 @@
-
- true
-
-
- 0
-
false
diff --git a/agents/unifiedmailboxagent/unifiedmailboxagent.cpp b/agents/unifiedmailboxagent/unifiedmailboxagent.cpp
index 1446ec551..bdd3a184e 100644
--- a/agents/unifiedmailboxagent/unifiedmailboxagent.cpp
+++ b/agents/unifiedmailboxagent/unifiedmailboxagent.cpp
@@ -1,239 +1,236 @@
/*
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 "common.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
UnifiedMailboxAgent::UnifiedMailboxAgent(const QString &id)
: Akonadi::ResourceBase(id)
, mBoxManager(config())
{
setAgentName(i18n("Unified Mailboxes"));
connect(&mBoxManager, &UnifiedMailboxManager::updateBox,
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(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 &boxIt : mBoxManager) {
const auto &box = boxIt.second;
Akonadi::Collection col;
col.setName(box->id());
col.setRemoteId(box->id());
col.setParentCollection(topLevel);
col.setContentMimeTypes({Common::MailMimeType});
col.setRights(Akonadi::Collection::CanChangeItem | Akonadi::Collection::CanDeleteItem);
col.setVirtual(true);
auto displayAttr = col.attribute(Akonadi::Collection::AddIfMissing);
displayAttr->setDisplayName(box->name());
displayAttr->setIconName(box->icon());
collections.push_back(std::move(col));
}
collectionsRetrieved(std::move(collections));
}
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 cf9f5f434..16fedb2e8 100644
--- a/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp
+++ b/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp
@@ -1,407 +1,405 @@
/*
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
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)
{
return col.resource() == Common::AgentIdentifier;
}
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() <= -1) {
qCWarning(agent_log) << "Missing box->collection mapping for unified mailbox" << box->id();
return;
}
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;
}
if (box->collectionId() <= -1) {
qCWarning(agent_log) << "Missing box->collection mapping for unified mailbox" << box->id();
}
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
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
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);
}
if (dstBox) {
Q_EMIT updateBox(dstBox);
}
} else {
if (const auto box = unregisterSpecialSourceCollection(col.id())) {
saveBoxes();
Q_EMIT updateBox(box);
}
}
});
}
UnifiedMailboxManager::~UnifiedMailboxManager()
{
}
void UnifiedMailboxManager::loadBoxes(FinishedCallback &&finishedCb)
{
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));
}
if (mMailboxes.empty()) {
createDefaultBoxes(std::move(finishedCb));
} else {
discoverBoxCollections([this, finishedCb = std::move(finishedCb)]() {
// 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 (finishedCb) {
finishedCb();
}
});
}
}
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;
}
for (const auto srcCol : box->second->sourceCollections()) {
mMonitor.setCollectionMonitored(Akonadi::Collection{srcCol}, false);
}
for (auto it = mSourceToBoxMap.begin(), end = mSourceToBoxMap.end(); it != end; ) {
if (it->second == box->second.get()) {
it = mSourceToBoxMap.erase(it);
} else {
++it;
}
}
mMailboxes.erase(box);
}
UnifiedMailbox *UnifiedMailboxManager::unifiedMailboxForSource(qint64 source) const
{
const auto box = mSourceToBoxMap.find(source);
if (box == mSourceToBoxMap.cend()) {
return {};
}
return box->second;
}
UnifiedMailbox * UnifiedMailboxManager::unifiedMailboxFromCollection(const Akonadi::Collection &col) const
{
if (!isUnifiedMailbox(col)) {
return nullptr;
}
const auto box = mMailboxes.find(col.name());
if (box == mMailboxes.cend()) {
return {};
}
return box->second.get();
}
void UnifiedMailboxManager::createDefaultBoxes(FinishedCallback &&finishedCb)
{
// First build empty boxes
auto inbox = std::make_unique();
inbox->attachManager(this);
inbox->setId(Common::InboxBoxId);
inbox->setName(i18n("Inbox"));
inbox->setIcon(QStringLiteral("mail-folder-inbox"));
insertBox(std::move(inbox));
auto sent = std::make_unique();
sent->attachManager(this);
sent->setId(Common::SentBoxId);
sent->setName(i18n("Sent"));
sent->setIcon(QStringLiteral("mail-folder-sent"));
insertBox(std::move(sent));
auto drafts = std::make_unique();
drafts->attachManager(this);
drafts->setId(Common::DraftsBoxId);
drafts->setName(i18n("Drafts"));
drafts->setIcon(QStringLiteral("document-properties"));
insertBox(std::move(drafts));
auto list = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this);
list->fetchScope().fetchAttribute();
list->fetchScope().setContentMimeTypes({QStringLiteral("message/rfc822")});
connect(list, &Akonadi::CollectionFetchJob::collectionsReceived,
this, [this](const Akonadi::Collection::List &list) {
for (const auto &col : list) {
if (isUnifiedMailbox(col)) {
continue;
}
try {
switch (Akonadi::SpecialMailCollections::self()->specialCollectionType(col)) {
case Akonadi::SpecialMailCollections::Inbox:
mMailboxes.at(Common::InboxBoxId)->addSourceCollection(col.id());
break;
case Akonadi::SpecialMailCollections::SentMail:
mMailboxes.at(Common::SentBoxId)->addSourceCollection(col.id());
break;
case Akonadi::SpecialMailCollections::Drafts:
mMailboxes.at(Common::DraftsBoxId)->addSourceCollection(col.id());
break;
default:
continue;
}
} catch (const std::out_of_range &) {
qCWarning(agent_log) << "Failed to find a special unified mailbox for source collection" << col.id();
continue;
}
}
});
connect(list, &Akonadi::CollectionFetchJob::finished,
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);
list->fetchScope().setResource(Common::AgentIdentifier);
connect(list, &Akonadi::CollectionFetchJob::collectionsReceived,
this, [this](const Akonadi::Collection::List &list) {
for (const auto &col : list) {
if (isUnifiedMailbox(col)) {
continue;
}
mMailboxes.at(col.name())->setCollectionId(col.id());
}
});
connect(list, &Akonadi::CollectionFetchJob::finished,
this, [finishedCb = std::move(finishedCb)]() {
if (finishedCb) {
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());
mMonitor.setCollectionMonitored(col);
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;
}