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

0001 /*  This file is part of the KDE project
0002     SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
0003     SPDX-FileContributor: Kevin Krammer <krake@kdab.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "compactchangehelper.h"
0009 #include "filestore/entitycompactchangeattribute.h"
0010 #include "mixedmaildirresource_debug.h"
0011 
0012 #include <Akonadi/Collection>
0013 #include <Akonadi/CollectionModifyJob>
0014 #include <Akonadi/Item>
0015 #include <Akonadi/ItemFetchJob>
0016 #include <Akonadi/ItemModifyJob>
0017 #include <Akonadi/Session>
0018 
0019 #include <QMap>
0020 #include <QQueue>
0021 #include <QVariant>
0022 
0023 using namespace Akonadi;
0024 
0025 using OldIdItemMap = QMap<QString, Item>;
0026 using RevisionChangeMap = QMap<qint64, OldIdItemMap>;
0027 using CollectionRevisionMap = QMap<Collection::Id, RevisionChangeMap>;
0028 
0029 struct UpdateBatch {
0030     QQueue<Item> items;
0031     Collection collection;
0032 };
0033 
0034 class CompactChangeHelperPrivate
0035 {
0036     CompactChangeHelper *const q;
0037 
0038 public:
0039     explicit CompactChangeHelperPrivate(CompactChangeHelper *parent)
0040         : q(parent)
0041     {
0042     }
0043 
0044 public:
0045     Session *mSession = nullptr;
0046     CollectionRevisionMap mChangesByCollection;
0047     QQueue<UpdateBatch> mPendingUpdates;
0048     UpdateBatch mCurrentUpdate;
0049 
0050 public: // slots
0051     void processNextBatch();
0052     void processNextItem();
0053     void itemFetchResult(KJob *job);
0054 };
0055 
0056 void CompactChangeHelperPrivate::processNextBatch()
0057 {
0058     // qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "pendingUpdates.count=" << mPendingUpdates.count();
0059     if (mPendingUpdates.isEmpty()) {
0060         return;
0061     }
0062 
0063     mCurrentUpdate = mPendingUpdates.dequeue();
0064 
0065     processNextItem();
0066 }
0067 
0068 void CompactChangeHelperPrivate::processNextItem()
0069 {
0070     // qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "mCurrentUpdate.items.count=" << mCurrentUpdate.items.count();
0071     if (mCurrentUpdate.items.isEmpty()) {
0072         auto job = new CollectionModifyJob(mCurrentUpdate.collection, mSession);
0073         QObject::connect(job, &CollectionModifyJob::result, q, [this]() {
0074             processNextBatch();
0075         });
0076         return;
0077     }
0078 
0079     const Item nextItem = mCurrentUpdate.items.dequeue();
0080 
0081     Item item;
0082     item.setRemoteId(nextItem.remoteId());
0083 
0084     auto job = new ItemFetchJob(item);
0085     job->setProperty("oldRemoteId", QVariant(item.remoteId()));
0086     job->setProperty("newRemoteId", QVariant(nextItem.attribute<FileStore::EntityCompactChangeAttribute>()->remoteId()));
0087     QObject::connect(job, &ItemFetchJob::result, q, [this](KJob *job) {
0088         itemFetchResult(job);
0089     });
0090 }
0091 
0092 void CompactChangeHelperPrivate::itemFetchResult(KJob *job)
0093 {
0094     auto fetchJob = qobject_cast<ItemFetchJob *>(job);
0095     Q_ASSERT(fetchJob != nullptr);
0096 
0097     const QString oldRemoteId = fetchJob->property("oldRemoteId").toString();
0098     Q_ASSERT(!oldRemoteId.isEmpty());
0099 
0100     const QString newRemoteId = fetchJob->property("newRemoteId").toString();
0101     Q_ASSERT(!newRemoteId.isEmpty());
0102 
0103     if (fetchJob->error() != 0) {
0104         // qCCritical(MIXEDMAILDIRRESOURCE_LOG) << "Item fetch for remoteId=" << oldRemoteId
0105         //         << "new remoteId=" << newRemoteId << "failed:" << fetchJob->errorString();
0106         processNextItem();
0107         return;
0108     }
0109 
0110     // since we only need the item to modify its remote ID, we don't care
0111     // if it does not exist (anymore)
0112     if (fetchJob->items().isEmpty()) {
0113         processNextItem();
0114         return;
0115     }
0116 
0117     const Item item = fetchJob->items().at(0);
0118 
0119     Item updatedItem(item);
0120     updatedItem.setRemoteId(newRemoteId);
0121 
0122     auto modifyJob = new ItemModifyJob(updatedItem);
0123     QObject::connect(modifyJob, &ItemModifyJob::result, q, [this]() {
0124         processNextItem();
0125     });
0126 }
0127 
0128 CompactChangeHelper::CompactChangeHelper(const QByteArray &sessionId, QObject *parent)
0129     : QObject(parent)
0130     , d(new CompactChangeHelperPrivate(this))
0131 {
0132     d->mSession = new Session(sessionId, this);
0133 }
0134 
0135 CompactChangeHelper::~CompactChangeHelper() = default;
0136 
0137 void CompactChangeHelper::addChangedItems(const Item::List &items)
0138 {
0139     if (items.isEmpty()) {
0140         return;
0141     }
0142 
0143     qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "items.count=" << items.count() << "pendingUpdates.count=" << d->mPendingUpdates.count();
0144     UpdateBatch updateBatch;
0145 
0146     for (const Item &item : items) {
0147         const Collection collection = item.parentCollection();
0148         const qint64 revision = collection.remoteRevision().toLongLong();
0149 
0150         RevisionChangeMap &changesByRevision = d->mChangesByCollection[collection.id()];
0151         OldIdItemMap &changes = changesByRevision[revision];
0152         changes.insert(item.remoteId(), item);
0153 
0154         if (!updateBatch.collection.isValid()) {
0155             updateBatch.collection = collection;
0156         } else if (updateBatch.collection != collection) {
0157             d->mPendingUpdates << updateBatch;
0158             updateBatch.items.clear();
0159             updateBatch.collection = collection;
0160         }
0161 
0162         updateBatch.items << item;
0163     }
0164 
0165     if (updateBatch.collection.isValid()) {
0166         d->mPendingUpdates << updateBatch;
0167     }
0168     QMetaObject::invokeMethod(
0169         this,
0170         [this]() {
0171             d->processNextBatch();
0172         },
0173         Qt::QueuedConnection);
0174 }
0175 
0176 QString CompactChangeHelper::currentRemoteId(const Item &item) const
0177 {
0178     const Collection collection = item.parentCollection();
0179     const qint64 revision = collection.remoteRevision().toLongLong();
0180 
0181     QString remoteId = item.remoteId();
0182 
0183     const CollectionRevisionMap::const_iterator colIt = d->mChangesByCollection.constFind(collection.id());
0184     if (colIt != d->mChangesByCollection.constEnd()) {
0185         // find revision and iterate until the highest available one
0186         RevisionChangeMap::const_iterator revIt = colIt->constFind(revision);
0187         for (; revIt != colIt->constEnd(); ++revIt) {
0188             const OldIdItemMap::const_iterator idIt = revIt->constFind(remoteId);
0189             if (idIt != revIt->constEnd()) {
0190                 remoteId = idIt.value().attribute<FileStore::EntityCompactChangeAttribute>()->remoteId();
0191             } else {
0192                 break;
0193             }
0194         }
0195     }
0196 
0197     if (item.remoteId() != remoteId) {
0198         qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "item (id=" << item.id() << "remoteId=" << item.remoteId() << "), col(id=" << collection.id()
0199                                           << ", name=" << collection.name() << ", revision=" << revision
0200                                           << ") in compact change set (revisions=" << colIt->keys() << ": current remoteId=" << remoteId;
0201     }
0202 
0203     return remoteId;
0204 }
0205 
0206 void CompactChangeHelper::checkCollectionChanged(const Collection &collection)
0207 {
0208     const qint64 revision = collection.remoteRevision().toLongLong();
0209     // qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "col.id=" << collection.id() << ", remoteId=" << collection.remoteId()
0210     //         << "revision=" << revision;
0211 
0212     const CollectionRevisionMap::iterator colIt = d->mChangesByCollection.find(collection.id());
0213     if (colIt != d->mChangesByCollection.end()) {
0214         qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "matching change map found with" << colIt->count() << "entries";
0215         // remove all revisions until the seen one appears
0216         RevisionChangeMap::iterator revIt = colIt->begin();
0217         while (revIt != colIt->end() && revIt.key() <= revision) {
0218             qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "removing entry for revision" << revIt.key();
0219             revIt = colIt->erase(revIt);
0220         }
0221 
0222         if (revIt == colIt->end()) {
0223             qCDebug(MIXEDMAILDIRRESOURCE_LOG) << "all change maps gone";
0224             d->mChangesByCollection.erase(colIt);
0225         }
0226     }
0227 }
0228 
0229 #include "moc_compactchangehelper.cpp"