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"