File indexing completed on 2024-12-22 04:57:39

0001 /*  This file is part of the KDE project
0002     SPDX-FileCopyrightText: 2007 Till Adam <adam@kde.org>
0003     SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
0004     SPDX-FileContributor: Kevin Krammer <krake@kdab.com>
0005     SPDX-FileCopyrightText: 2011 Kevin Krammer <kevin.krammer@gmx.at>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "mixedmaildirresource.h"
0011 #include "mixedmaildir_debug.h"
0012 
0013 #include "compactchangehelper.h"
0014 #include "createandsettagsjob.h"
0015 #include "mixedmaildirstore.h"
0016 #include "retrieveitemsjob.h"
0017 #include "settings.h"
0018 #include "settingsadaptor.h"
0019 
0020 #include "filestore/collectioncreatejob.h"
0021 #include "filestore/collectiondeletejob.h"
0022 #include "filestore/collectionfetchjob.h"
0023 #include "filestore/collectionmodifyjob.h"
0024 #include "filestore/collectionmovejob.h"
0025 #include "filestore/itemcreatejob.h"
0026 #include "filestore/itemdeletejob.h"
0027 #include "filestore/itemfetchjob.h"
0028 #include "filestore/itemmodifyjob.h"
0029 #include "filestore/itemmovejob.h"
0030 #include "filestore/storecompactjob.h"
0031 
0032 #include <Akonadi/MessageParts>
0033 #include <Akonadi/MessageStatus>
0034 
0035 #include <Akonadi/ChangeRecorder>
0036 #include <Akonadi/CollectionFetchScope>
0037 #include <Akonadi/ItemFetchJob>
0038 #include <Akonadi/ItemFetchScope>
0039 #include <Akonadi/ItemModifyJob>
0040 
0041 #include <KMime/Message>
0042 
0043 #include "mixedmaildirresource_debug.h"
0044 #include <KLocalizedString>
0045 
0046 #include <QDBusConnection>
0047 #include <QDir>
0048 
0049 #include <Akonadi/Tag>
0050 
0051 using namespace Akonadi;
0052 
0053 MixedMaildirResource::MixedMaildirResource(const QString &id)
0054     : ResourceBase(id)
0055     , mStore(new MixedMaildirStore())
0056     , mCompactHelper(nullptr)
0057 {
0058     Settings::instance(KSharedConfig::openConfig());
0059     new SettingsAdaptor(Settings::self());
0060     QDBusConnection::sessionBus().registerObject(QStringLiteral("/Settings"), Settings::self(), QDBusConnection::ExportAdaptors);
0061     connect(this, &MixedMaildirResource::reloadConfiguration, this, &MixedMaildirResource::reapplyConfiguration);
0062 
0063     // We need to enable this here, otherwise we neither get the remote ID of the
0064     // parent collection when a collection changes, nor the full item when an item
0065     // is added.
0066     changeRecorder()->fetchCollection(true);
0067     changeRecorder()->itemFetchScope().fetchFullPayload(true);
0068     changeRecorder()->itemFetchScope().setAncestorRetrieval(ItemFetchScope::All);
0069     changeRecorder()->collectionFetchScope().setAncestorRetrieval(CollectionFetchScope::All);
0070 
0071     setHierarchicalRemoteIdentifiersEnabled(true);
0072 
0073     if (ensureSaneConfiguration()) {
0074         const bool changeName = name().isEmpty() || name() == identifier() || name() == mStore->topLevelCollection().name();
0075         mStore->setPath(Settings::self()->path());
0076         if (changeName) {
0077             setName(mStore->topLevelCollection().name());
0078         }
0079     }
0080 
0081     const QByteArray compactHelperSessionId = id.toUtf8() + "-compacthelper";
0082     mCompactHelper = new CompactChangeHelper(compactHelperSessionId, this);
0083 }
0084 
0085 MixedMaildirResource::~MixedMaildirResource()
0086 {
0087     delete mStore;
0088     delete Settings::self();
0089 }
0090 
0091 void MixedMaildirResource::aboutToQuit()
0092 {
0093     // The settings may not have been saved if e.g. they have been modified via
0094     // DBus instead of the config dialog.
0095     Settings::self()->save();
0096 }
0097 
0098 void MixedMaildirResource::itemAdded(const Item &item, const Collection &collection)
0099 {
0100     /*  qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "item.id=" << item.id() << "col=" << collection.remoteId();*/
0101     if (!ensureSaneConfiguration()) {
0102         const QString message = i18nc("@info:status", "Unusable configuration.");
0103         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << message;
0104         cancelTask(message);
0105         return;
0106     }
0107 
0108     FileStore::ItemCreateJob *job = mStore->createItem(item, collection);
0109     connect(job, &FileStore::ItemCreateJob::result, this, &MixedMaildirResource::itemAddedResult);
0110 }
0111 
0112 void MixedMaildirResource::itemChanged(const Item &item, const QSet<QByteArray> &parts)
0113 {
0114     /*  qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "item.id=" << item.id() << "col=" << item.parentCollection().remoteId()
0115                << "parts=" << parts;*/
0116     if (!ensureSaneConfiguration()) {
0117         const QString message = i18nc("@info:status", "Unusable configuration.");
0118         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << message;
0119         cancelTask(message);
0120         return;
0121     }
0122 
0123     if (Settings::self()->readOnly()) {
0124         changeProcessed();
0125         return;
0126     }
0127 
0128     Item storeItem(item);
0129     storeItem.setRemoteId(mCompactHelper->currentRemoteId(item));
0130 
0131     FileStore::ItemModifyJob *job = mStore->modifyItem(storeItem);
0132     job->setIgnorePayload(!item.hasPayload<KMime::Message::Ptr>());
0133     job->setParts(parts);
0134     job->setProperty("originalRemoteId", storeItem.remoteId());
0135     connect(job, &FileStore::ItemModifyJob::result, this, &MixedMaildirResource::itemChangedResult);
0136 }
0137 
0138 void MixedMaildirResource::itemMoved(const Item &item, const Collection &source, const Collection &destination)
0139 {
0140     /*  qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "item.id=" << item.id() << "remoteId=" << item.remoteId()
0141                << "source=" << source.remoteId() << "dest=" << destination.remoteId();*/
0142     if (source == destination) {
0143         changeProcessed();
0144         return;
0145     }
0146 
0147     if (!ensureSaneConfiguration()) {
0148         const QString message = i18nc("@info:status", "Unusable configuration.");
0149         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << message;
0150         cancelTask(message);
0151         return;
0152     }
0153 
0154     Item moveItem = item;
0155     moveItem.setRemoteId(mCompactHelper->currentRemoteId(item));
0156     moveItem.setParentCollection(source);
0157 
0158     FileStore::ItemMoveJob *job = mStore->moveItem(moveItem, destination);
0159     job->setProperty("originalRemoteId", moveItem.remoteId());
0160     connect(job, &FileStore::ItemMoveJob::result, this, &MixedMaildirResource::itemMovedResult);
0161 }
0162 
0163 void MixedMaildirResource::itemRemoved(const Item &item)
0164 {
0165     /*  qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "item.id=" << item.id() << "col=" << collection.remoteId()
0166                << "collection.remoteRevision=" << item.parentCollection().remoteRevision();*/
0167     Q_ASSERT(!item.remoteId().isEmpty());
0168     Q_ASSERT(item.parentCollection().isValid());
0169     if (item.parentCollection().remoteId().isEmpty()) {
0170         const QString message =
0171             i18nc("@info:status", "Item %1 belongs to invalid collection %2. Maybe it was deleted meanwhile?", item.id(), item.parentCollection().id());
0172         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << message;
0173         cancelTask(message);
0174         return;
0175     }
0176 
0177     if (!ensureSaneConfiguration()) {
0178         const QString message = i18nc("@info:status", "Unusable configuration.");
0179         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << message;
0180         cancelTask(message);
0181         return;
0182     }
0183 
0184     Item storeItem(item);
0185     storeItem.setRemoteId(mCompactHelper->currentRemoteId(item));
0186     FileStore::ItemDeleteJob *job = mStore->deleteItem(storeItem);
0187     connect(job, &FileStore::ItemDeleteJob::result, this, &MixedMaildirResource::itemRemovedResult);
0188 }
0189 
0190 void MixedMaildirResource::retrieveCollections()
0191 {
0192     if (!ensureSaneConfiguration()) {
0193         const QString message = i18nc("@info:status", "Unusable configuration.");
0194         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << message;
0195         cancelTask(message);
0196         return;
0197     }
0198 
0199     FileStore::CollectionFetchJob *job = mStore->fetchCollections(mStore->topLevelCollection(), FileStore::CollectionFetchJob::Recursive);
0200     connect(job, &FileStore::CollectionFetchJob::result, this, &MixedMaildirResource::retrieveCollectionsResult);
0201 
0202     Q_EMIT status(Running, i18nc("@info:status", "Synchronizing email folders"));
0203 }
0204 
0205 void MixedMaildirResource::retrieveItems(const Collection &col)
0206 {
0207     if (!ensureSaneConfiguration()) {
0208         const QString message = i18nc("@info:status", "Unusable configuration.");
0209         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << message;
0210         cancelTask(message);
0211         return;
0212     }
0213 
0214     auto job = new RetrieveItemsJob(col, mStore, this);
0215     connect(job, &RetrieveItemsJob::result, this, &MixedMaildirResource::retrieveItemsResult);
0216 
0217     Q_EMIT status(Running, i18nc("@info:status", "Synchronizing email folder %1", col.name()));
0218 }
0219 
0220 bool MixedMaildirResource::retrieveItems(const Item::List &items, const QSet<QByteArray> &parts)
0221 {
0222     Q_UNUSED(parts)
0223 
0224     FileStore::ItemFetchJob *job = mStore->fetchItems(items);
0225     if (parts.contains(Item::FullPayload)) {
0226         job->fetchScope().fetchFullPayload(true);
0227     } else {
0228         for (const QByteArray &part : parts) {
0229             job->fetchScope().fetchPayloadPart(part, true);
0230         }
0231     }
0232     connect(job, &RetrieveItemsJob::result, this, &MixedMaildirResource::retrieveItemResult);
0233 
0234     return true;
0235 }
0236 
0237 void MixedMaildirResource::collectionAdded(const Collection &collection, const Collection &parent)
0238 {
0239     if (!ensureSaneConfiguration()) {
0240         const QString message = i18nc("@info:status", "Unusable configuration.");
0241         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << message;
0242         cancelTask(message);
0243         return;
0244     }
0245 
0246     FileStore::CollectionCreateJob *job = mStore->createCollection(collection, parent);
0247     connect(job, &RetrieveItemsJob::result, this, &MixedMaildirResource::collectionAddedResult);
0248 }
0249 
0250 void MixedMaildirResource::collectionChanged(const Collection &collection)
0251 {
0252     if (!ensureSaneConfiguration()) {
0253         const QString message = i18nc("@info:status", "Unusable configuration.");
0254         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << message;
0255         cancelTask(message);
0256         return;
0257     }
0258 
0259     // when the top level collection gets renamed, we do not rename the directory
0260     // but rename the resource.
0261     if (collection.remoteId() == mStore->topLevelCollection().remoteId()) {
0262         if (collection.name() != name()) {
0263             qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "TopLevel collection name differs from resource name: collection=" << collection.name()
0264                                               << "resource=" << name() << ". Renaming resource";
0265             setName(collection.name());
0266         }
0267         changeCommitted(collection);
0268         return;
0269     }
0270 
0271     mCompactHelper->checkCollectionChanged(collection);
0272 
0273     FileStore::CollectionModifyJob *job = mStore->modifyCollection(collection);
0274     connect(job, &RetrieveItemsJob::result, this, &MixedMaildirResource::collectionChangedResult);
0275 }
0276 
0277 void MixedMaildirResource::collectionChanged(const Collection &collection, const QSet<QByteArray> &changedAttributes)
0278 {
0279     if (!ensureSaneConfiguration()) {
0280         const QString message = i18nc("@info:status", "Unusable configuration.");
0281         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << message;
0282         cancelTask(message);
0283         return;
0284     }
0285 
0286     // when the top level collection gets renamed, we do not rename the directory
0287     // but rename the resource.
0288     if (collection.remoteId() == mStore->topLevelCollection().remoteId()) {
0289         if (collection.name() != name()) {
0290             qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "TopLevel collection name differs from resource name: collection=" << collection.name()
0291                                               << "resource=" << name() << ". Renaming resource";
0292             setName(collection.name());
0293         }
0294         changeCommitted(collection);
0295         return;
0296     }
0297 
0298     mCompactHelper->checkCollectionChanged(collection);
0299 
0300     Q_UNUSED(changedAttributes)
0301 
0302     FileStore::CollectionModifyJob *job = mStore->modifyCollection(collection);
0303     connect(job, &RetrieveItemsJob::result, this, &MixedMaildirResource::collectionChangedResult);
0304 }
0305 
0306 void MixedMaildirResource::collectionMoved(const Collection &collection, const Collection &source, const Collection &dest)
0307 {
0308     // qCDebug(MIXEDMAILDIR_LOG) << collection << source << dest;
0309 
0310     if (!ensureSaneConfiguration()) {
0311         const QString message = i18nc("@info:status", "Unusable configuration.");
0312         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << message;
0313         cancelTask(message);
0314         return;
0315     }
0316 
0317     if (collection.parentCollection() == Collection::root()) {
0318         const QString message = i18nc("@info:status", "Cannot move root maildir folder '%1'.", collection.remoteId());
0319         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << message;
0320         cancelTask(message);
0321         return;
0322     }
0323 
0324     if (source == dest) { // should not happen, but who knows...
0325         changeProcessed();
0326         return;
0327     }
0328 
0329     Collection moveCollection = collection;
0330     moveCollection.setParentCollection(source);
0331 
0332     FileStore::CollectionMoveJob *job = mStore->moveCollection(moveCollection, dest);
0333     connect(job, &RetrieveItemsJob::result, this, &MixedMaildirResource::collectionMovedResult);
0334 }
0335 
0336 void MixedMaildirResource::collectionRemoved(const Collection &collection)
0337 {
0338     if (!ensureSaneConfiguration()) {
0339         const QString message = i18nc("@info:status", "Unusable configuration.");
0340         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << message;
0341         cancelTask(message);
0342         return;
0343     }
0344 
0345     if (collection.parentCollection() == Collection::root()) {
0346         Q_EMIT error(i18n("Cannot delete top-level maildir folder '%1'.", Settings::self()->path()));
0347         changeProcessed();
0348         return;
0349     }
0350 
0351     FileStore::CollectionDeleteJob *job = mStore->deleteCollection(collection);
0352     connect(job, &RetrieveItemsJob::result, this, &MixedMaildirResource::collectionRemovedResult);
0353 }
0354 
0355 bool MixedMaildirResource::ensureDirExists()
0356 {
0357     QDir dir(Settings::self()->path());
0358     if (!dir.exists()) {
0359         if (!dir.mkpath(Settings::self()->path())) {
0360             const QString message = i18nc("@info:status", "Unable to create maildir '%1'.", Settings::self()->path());
0361             qCCritical(MIXEDMAILDIRRESOURCE_LOG) << message;
0362             Q_EMIT status(Broken, message);
0363             return false;
0364         }
0365     }
0366     return true;
0367 }
0368 
0369 bool MixedMaildirResource::ensureSaneConfiguration()
0370 {
0371     if (Settings::self()->path().isEmpty()) {
0372         const QString message = i18nc("@info:status", "No usable storage location configured.");
0373         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << message;
0374         Q_EMIT status(NotConfigured, message);
0375         return false;
0376     }
0377     return true;
0378 }
0379 
0380 void MixedMaildirResource::checkForInvalidatedIndexCollections(KJob *job)
0381 {
0382     // when operations invalidate the on-disk index, we need to make sure all index
0383     // data has been transferred into Akonadi by synchronizing the collections
0384     const QVariant var = job->property("onDiskIndexInvalidated");
0385     if (var.isValid()) {
0386         const auto collections = var.value<Collection::List>();
0387         qCDebug(MIXEDMAILDIR_LOG) << "On disk index of" << collections.count() << "collections invalidated after" << job->metaObject()->className();
0388 
0389         for (const Collection &collection : collections) {
0390             const Collection::Id id = collection.id();
0391             if (!mSynchronizedCollections.contains(id) && !mPendingSynchronizeCollections.contains(id)) {
0392                 qCDebug(MIXEDMAILDIR_LOG) << "Requesting sync of collection" << collection.name() << ", id=" << collection.id();
0393                 mPendingSynchronizeCollections << id;
0394                 synchronizeCollection(id);
0395             }
0396         }
0397     }
0398 }
0399 
0400 void MixedMaildirResource::reapplyConfiguration()
0401 {
0402     const bool changeName = name().isEmpty() || name() == identifier() || name() == mStore->topLevelCollection().name();
0403     if (changeName) {
0404         setName(mStore->topLevelCollection().name());
0405     }
0406 
0407     if (ensureSaneConfiguration() && ensureDirExists()) {
0408         const QString oldPath = mStore->path();
0409         mStore->setPath(Settings::self()->path());
0410 
0411         if (oldPath != mStore->path()) {
0412             mSynchronizedCollections.clear();
0413             mPendingSynchronizeCollections.clear();
0414         }
0415         synchronizeCollectionTree();
0416     }
0417 }
0418 
0419 void MixedMaildirResource::retrieveCollectionsResult(KJob *job)
0420 {
0421     if (job->error() != 0) {
0422         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << job->errorString();
0423         Q_EMIT status(Broken, job->errorString());
0424         cancelTask(job->errorString());
0425         return;
0426     }
0427 
0428     auto fetchJob = qobject_cast<FileStore::CollectionFetchJob *>(job);
0429     Q_ASSERT(fetchJob != nullptr);
0430 
0431     Collection topLevelCollection = mStore->topLevelCollection();
0432     if (!name().isEmpty() && name() != identifier()) {
0433         topLevelCollection.setName(name());
0434     }
0435 
0436     Collection::List collections;
0437     collections << topLevelCollection;
0438     collections << fetchJob->collections();
0439     collectionsRetrieved(collections);
0440 }
0441 
0442 void MixedMaildirResource::retrieveItemsResult(KJob *job)
0443 {
0444     if (job->error() != 0) {
0445         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << job->errorString();
0446         Q_EMIT status(Broken, job->errorString());
0447         cancelTask(job->errorString());
0448         return;
0449     }
0450 
0451     auto retrieveJob = qobject_cast<RetrieveItemsJob *>(job);
0452     Q_ASSERT(retrieveJob != nullptr);
0453 
0454     // messages marked as deleted have been deleted from mbox files but never got purged
0455     // TODO FileStore could provide deleteItems() to deleted all filtered items in one go
0456     KJob *deleteJob = nullptr;
0457     const auto itemsMarkedAsDeleted{retrieveJob->itemsMarkedAsDeleted()};
0458     qCDebug(MIXEDMAILDIR_LOG) << itemsMarkedAsDeleted.count() << "items marked as Deleted";
0459     for (const Item &item : itemsMarkedAsDeleted) {
0460         deleteJob = mStore->deleteItem(item);
0461     }
0462 
0463     if (deleteJob != nullptr) {
0464         // last item delete triggers mbox purge, i.e. store compact
0465         const bool connected = connect(deleteJob, &KJob::result, this, &MixedMaildirResource::itemsDeleted);
0466         Q_ASSERT(connected);
0467         Q_UNUSED(connected)
0468     }
0469 
0470     // if some items have tags, we need to complete the retrieval and schedule tagging
0471     // to a later time so we can then fetch the items to get their Akonadi URLs
0472     const Item::List items = retrieveJob->availableItems();
0473     const QVariant var = retrieveJob->property("remoteIdToTagList");
0474     if (var.isValid()) {
0475         const auto tagListHash = var.value<QHash<QString, QVariant>>();
0476         if (!tagListHash.isEmpty()) {
0477             qCDebug(MIXEDMAILDIRRESOURCE_LOG) << tagListHash.count() << "of" << items.count() << "items in collection" << retrieveJob->collection().remoteId()
0478                                               << "have tags";
0479 
0480             TagContextList taggedItems;
0481             for (const Item &item : items) {
0482                 const QVariant tagListVar = tagListHash[item.remoteId()];
0483                 if (tagListVar.isValid()) {
0484                     const QStringList tagList = tagListVar.toStringList();
0485                     if (!tagListHash.isEmpty()) {
0486                         TagContext tag;
0487                         tag.mItem = item;
0488                         tag.mTagList = tagList;
0489 
0490                         taggedItems << tag;
0491                     }
0492                 }
0493             }
0494 
0495             if (!taggedItems.isEmpty()) {
0496                 mTagContextByColId.insert(retrieveJob->collection().id(), taggedItems);
0497 
0498                 scheduleCustomTask(this, "restoreTags", QVariant::fromValue<Collection>(retrieveJob->collection()));
0499             }
0500         }
0501     }
0502 
0503     mSynchronizedCollections << retrieveJob->collection().id();
0504     mPendingSynchronizeCollections.remove(retrieveJob->collection().id());
0505 
0506     itemsRetrievalDone();
0507 }
0508 
0509 void MixedMaildirResource::retrieveItemResult(KJob *job)
0510 {
0511     if (job->error() != 0) {
0512         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << job->errorString();
0513         Q_EMIT status(Broken, job->errorString());
0514         cancelTask(job->errorString());
0515         return;
0516     }
0517 
0518     auto fetchJob = qobject_cast<FileStore::ItemFetchJob *>(job);
0519     Q_ASSERT(fetchJob != nullptr);
0520     Q_ASSERT(!fetchJob->items().isEmpty());
0521 
0522     itemsRetrieved(fetchJob->items());
0523 }
0524 
0525 void MixedMaildirResource::itemAddedResult(KJob *job)
0526 {
0527     if (job->error() != 0) {
0528         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << job->errorString();
0529         Q_EMIT status(Broken, job->errorString());
0530         cancelTask(job->errorString());
0531         return;
0532     }
0533 
0534     auto itemJob = qobject_cast<FileStore::ItemCreateJob *>(job);
0535     Q_ASSERT(itemJob != nullptr);
0536 
0537     /*  qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "item.id=" << itemJob->item().id() << "remoteId=" << itemJob->item().remoteId();*/
0538     changeCommitted(itemJob->item());
0539 
0540     checkForInvalidatedIndexCollections(job);
0541 }
0542 
0543 void MixedMaildirResource::itemChangedResult(KJob *job)
0544 {
0545     if (job->error() != 0) {
0546         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << job->errorString();
0547         Q_EMIT status(Broken, job->errorString());
0548         cancelTask(job->errorString());
0549         return;
0550     }
0551 
0552     auto itemJob = qobject_cast<FileStore::ItemModifyJob *>(job);
0553     Q_ASSERT(itemJob != nullptr);
0554 
0555     changeCommitted(itemJob->item());
0556 
0557     // const QString remoteId = itemJob->property("originalRemoteId").toString();
0558 
0559     const QVariant compactStoreVar = itemJob->property("compactStore");
0560     if (compactStoreVar.isValid() && compactStoreVar.toBool()) {
0561         scheduleCustomTask(this, "compactStore", QVariant());
0562     }
0563 
0564     checkForInvalidatedIndexCollections(job);
0565 }
0566 
0567 void MixedMaildirResource::itemMovedResult(KJob *job)
0568 {
0569     if (job->error() != 0) {
0570         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << job->errorString();
0571         Q_EMIT status(Broken, job->errorString());
0572         cancelTask(job->errorString());
0573         return;
0574     }
0575 
0576     auto itemJob = qobject_cast<FileStore::ItemMoveJob *>(job);
0577     Q_ASSERT(itemJob != nullptr);
0578 
0579     changeCommitted(itemJob->item());
0580 
0581     // const QString remoteId = itemJob->property("originalRemoteId").toString();
0582     //   qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "item.id=" << itemJob->item().id() << "remoteId=" << itemJob->item().remoteId()
0583     //            << "old remoteId=" << remoteId;
0584 
0585     const QVariant compactStoreVar = itemJob->property("compactStore");
0586     if (compactStoreVar.isValid() && compactStoreVar.toBool()) {
0587         scheduleCustomTask(this, "compactStore", QVariant());
0588     }
0589 
0590     checkForInvalidatedIndexCollections(job);
0591 }
0592 
0593 void MixedMaildirResource::itemRemovedResult(KJob *job)
0594 {
0595     if (job->error() != 0) {
0596         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << job->errorString();
0597         Q_EMIT status(Broken, job->errorString());
0598         cancelTask(job->errorString());
0599         return;
0600     }
0601 
0602     auto itemJob = qobject_cast<FileStore::ItemDeleteJob *>(job);
0603     Q_ASSERT(itemJob != nullptr);
0604 
0605     changeCommitted(itemJob->item());
0606 
0607     const QVariant compactStoreVar = itemJob->property("compactStore");
0608     if (compactStoreVar.isValid() && compactStoreVar.toBool()) {
0609         scheduleCustomTask(this, "compactStore", QVariant());
0610     }
0611 
0612     checkForInvalidatedIndexCollections(job);
0613 }
0614 
0615 void MixedMaildirResource::itemsDeleted(KJob *job)
0616 {
0617     Q_UNUSED(job)
0618     scheduleCustomTask(this, "compactStore", QVariant());
0619 }
0620 
0621 void MixedMaildirResource::collectionAddedResult(KJob *job)
0622 {
0623     if (job->error() != 0) {
0624         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << job->errorString();
0625         Q_EMIT status(Broken, job->errorString());
0626         cancelTask(job->errorString());
0627         return;
0628     }
0629 
0630     auto colJob = qobject_cast<FileStore::CollectionCreateJob *>(job);
0631     Q_ASSERT(colJob != nullptr);
0632 
0633     changeCommitted(colJob->collection());
0634 }
0635 
0636 void MixedMaildirResource::collectionChangedResult(KJob *job)
0637 {
0638     if (job->error() != 0) {
0639         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << job->errorString();
0640         Q_EMIT status(Broken, job->errorString());
0641         cancelTask(job->errorString());
0642         return;
0643     }
0644 
0645     auto colJob = qobject_cast<FileStore::CollectionModifyJob *>(job);
0646     Q_ASSERT(colJob != nullptr);
0647 
0648     changeCommitted(colJob->collection());
0649 
0650     checkForInvalidatedIndexCollections(job);
0651 }
0652 
0653 void MixedMaildirResource::collectionMovedResult(KJob *job)
0654 {
0655     if (job->error() != 0) {
0656         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << job->errorString();
0657         Q_EMIT status(Broken, job->errorString());
0658         cancelTask(job->errorString());
0659         return;
0660     }
0661 
0662     auto colJob = qobject_cast<FileStore::CollectionMoveJob *>(job);
0663     Q_ASSERT(colJob != nullptr);
0664 
0665     changeCommitted(colJob->collection());
0666 
0667     checkForInvalidatedIndexCollections(job);
0668 }
0669 
0670 void MixedMaildirResource::collectionRemovedResult(KJob *job)
0671 {
0672     if (job->error() != 0) {
0673         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << job->errorString();
0674         Q_EMIT status(Broken, job->errorString());
0675         cancelTask(job->errorString());
0676         return;
0677     }
0678 
0679     auto colJob = qobject_cast<FileStore::CollectionDeleteJob *>(job);
0680     Q_ASSERT(colJob != nullptr);
0681 
0682     changeCommitted(colJob->collection());
0683 }
0684 
0685 void MixedMaildirResource::compactStore(const QVariant &arg)
0686 {
0687     Q_UNUSED(arg)
0688 
0689     FileStore::StoreCompactJob *job = mStore->compactStore();
0690     connect(job, &RetrieveItemsJob::result, this, &MixedMaildirResource::compactStoreResult);
0691 }
0692 
0693 void MixedMaildirResource::compactStoreResult(KJob *job)
0694 {
0695     if (job->error() != 0) {
0696         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << job->errorString();
0697         Q_EMIT status(Broken, job->errorString());
0698         cancelTask(job->errorString());
0699         return;
0700     }
0701 
0702     auto compactJob = qobject_cast<FileStore::StoreCompactJob *>(job);
0703     Q_ASSERT(compactJob != nullptr);
0704 
0705     const Item::List items = compactJob->changedItems();
0706     qCDebug(MIXEDMAILDIR_LOG) << "Compacting store resulted in" << items.count() << "changed items";
0707 
0708     mCompactHelper->addChangedItems(items);
0709 
0710     taskDone();
0711 
0712     checkForInvalidatedIndexCollections(job);
0713 }
0714 
0715 void MixedMaildirResource::restoreTags(const QVariant &arg)
0716 {
0717     if (!arg.isValid()) {
0718         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << "Given variant is not valid";
0719         cancelTask();
0720         return;
0721     }
0722 
0723     const auto collection = arg.value<Collection>();
0724     if (!collection.isValid()) {
0725         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << "Given variant is not valid";
0726         cancelTask();
0727         return;
0728     }
0729 
0730     const TagContextList taggedItems = mTagContextByColId[collection.id()];
0731     mPendingTagContexts << taggedItems;
0732 
0733     QMetaObject::invokeMethod(this, &MixedMaildirResource::processNextTagContext, Qt::QueuedConnection);
0734     taskDone();
0735 }
0736 
0737 void MixedMaildirResource::processNextTagContext()
0738 {
0739     qCDebug(MIXEDMAILDIRRESOURCE_LOG) << mPendingTagContexts.count() << "items to go";
0740     if (mPendingTagContexts.isEmpty()) {
0741         return;
0742     }
0743 
0744     const TagContext tagContext = mPendingTagContexts.front();
0745     mPendingTagContexts.pop_front();
0746 
0747     auto fetchJob = new ItemFetchJob(tagContext.mItem);
0748     fetchJob->setProperty("tagList", tagContext.mTagList);
0749     connect(fetchJob, &ItemFetchJob::result, this, &MixedMaildirResource::tagFetchJobResult);
0750 }
0751 
0752 void MixedMaildirResource::tagFetchJobResult(KJob *job)
0753 {
0754     if (job->error() != 0) {
0755         qCCritical(MIXEDMAILDIRRESOURCE_LOG) << job->errorString();
0756         processNextTagContext();
0757         return;
0758     }
0759 
0760     auto fetchJob = qobject_cast<ItemFetchJob *>(job);
0761     Q_ASSERT(fetchJob != nullptr);
0762 
0763     Q_ASSERT(!fetchJob->items().isEmpty());
0764 
0765     const Item item = fetchJob->items().at(0);
0766     const QStringList tagList = job->property("tagList").toStringList();
0767     qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "Tagging item" << item.url() << "with" << tagList;
0768 
0769     Akonadi::Tag::List tags;
0770     for (const QString &tag : tagList) {
0771         if (tag.isEmpty()) {
0772             qCWarning(MIXEDMAILDIRRESOURCE_LOG) << "TagList for item" << item.url() << "contains an empty tag";
0773         } else {
0774             tags << Akonadi::Tag(tag);
0775         }
0776     }
0777     new CreateAndSetTagsJob(item, tags);
0778 
0779     processNextTagContext();
0780 }
0781 
0782 AKONADI_RESOURCE_MAIN(MixedMaildirResource)
0783 
0784 #include "moc_mixedmaildirresource.cpp"