You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

269 lines
9.8 KiB

/*
SPDX-FileCopyrightText: 2018 Daniel Vrátil <dvratil@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "unifiedmailboxagent.h"
#include "unifiedmailbox.h"
#include "unifiedmailboxagent_debug.h"
#include "unifiedmailboxagentadaptor.h"
#include "settingsdialog.h"
#include "settings.h"
#include "common.h"
#include <AkonadiCore/ChangeRecorder>
#include <AkonadiCore/Session>
#include <AkonadiCore/CollectionFetchJob>
#include <AkonadiCore/CollectionFetchScope>
#include <AkonadiCore/CollectionDeleteJob>
#include <AkonadiCore/SpecialCollectionAttribute>
#include <AkonadiCore/EntityDisplayAttribute>
#include <AkonadiCore/ItemFetchScope>
#include <AkonadiCore/ItemFetchJob>
#include <AkonadiCore/LinkJob>
#include <AkonadiCore/UnlinkJob>
#include <AkonadiCore/ServerManager>
#include <KIdentityManagement/IdentityManager>
#include <KIdentityManagement/Identity>
#include <KLocalizedString>
#include <QDBusConnection>
#include <QPointer>
#include <QTimer>
#include <memory>
#include <unordered_set>
#include <chrono>
UnifiedMailboxAgent::UnifiedMailboxAgent(const QString &id)
: Akonadi::ResourceBase(id)
, mBoxManager(config())
{
setAgentName(i18n("Unified Mailboxes"));
new UnifiedMailboxAgentAdaptor(this);
QDBusConnection::sessionBus().registerObject(QStringLiteral("/UnifiedMailboxAgent"), this, QDBusConnection::ExportAdaptors);
const auto service = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Resource, identifier());
QDBusConnection::sessionBus().registerService(service);
connect(&mBoxManager, &UnifiedMailboxManager::updateBox,
this, [this](const UnifiedMailbox *box) {
if (box->collectionId() <= -1) {
qCWarning(UNIFIEDMAILBOXAGENT_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);
if (Settings::self()->enabled()) {
QTimer::singleShot(0, this, &UnifiedMailboxAgent::delayedInit);
}
}
void UnifiedMailboxAgent::configure(WId windowId)
{
QPointer<UnifiedMailboxAgent> agent(this);
if (agent) {
SettingsDialog(config(), mBoxManager, windowId).exec();
synchronize();
Q_EMIT configurationDialogAccepted();
}
}
void UnifiedMailboxAgent::delayedInit()
{
qCDebug(UNIFIEDMAILBOXAGENT_LOG) << "delayed init";
fixSpecialCollections();
mBoxManager.loadBoxes([this]() {
// boxes loaded, let's sync up
synchronize();
});
}
bool UnifiedMailboxAgent::enabledAgent() const
{
return Settings::self()->enabled();
}
void UnifiedMailboxAgent::setEnableAgent(bool enabled)
{
if (enabled != Settings::self()->enabled()) {
Settings::self()->setEnabled(enabled);
Settings::self()->save();
if (!enabled) {
setOnline(false);
auto fetch = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this);
fetch->fetchScope().setResource(identifier());
connect(fetch, &Akonadi::CollectionFetchJob::collectionsReceived,
this, [this](const Akonadi::Collection::List &cols) {
for (const auto &col : cols) {
new Akonadi::CollectionDeleteJob(col, this);
}
});
} else {
setOnline(true);
delayedInit();
}
}
}
void UnifiedMailboxAgent::retrieveCollections()
{
if (!Settings::self()->enabled()) {
collectionsRetrieved({});
return;
}
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 topLevelDisplayAttr = topLevel.attribute<Akonadi::EntityDisplayAttribute>(Akonadi::Collection::AddIfMissing);
topLevelDisplayAttr->setDisplayName(i18n("Unified Mailboxes"));
topLevelDisplayAttr->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::EntityDisplayAttribute>(Akonadi::Collection::AddIfMissing);
displayAttr->setDisplayName(box->name());
displayAttr->setIconName(box->icon());
collections.push_back(std::move(col));
}
collectionsRetrieved(std::move(collections));
// Add mapping between boxes and collections
mBoxManager.discoverBoxCollections();
}
void UnifiedMailboxAgent::retrieveItems(const Akonadi::Collection &c)
{
if (!Settings::self()->enabled()) {
itemsRetrieved({});
return;
}
// 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(UNIFIEDMAILBOXAGENT_LOG) << "Failed to retrieve box ID for collection " << c.id();
itemsRetrievedIncremental({}, {}); // fake incremental retrieval
return;
}
const auto sources = unifiedBox->sourceCollections();
for (auto source : sources) {
auto fetch = new Akonadi::ItemFetchJob(Akonadi::Collection(source), this);
fetch->setDeliveryOption(Akonadi::ItemFetchJob::EmitItemsInBatches);
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);
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<QByteArray> &parts)
{
// This method should never be called by Akonadi
Q_UNUSED(parts)
qCWarning(UNIFIEDMAILBOXAGENT_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(UNIFIEDMAILBOXAGENT_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(UNIFIEDMAILBOXAGENT_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)