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"