File indexing completed on 2024-05-12 05:11:12

0001 /*
0002     SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>
0003     SPDX-FileCopyrightText: 2010 Andras Mantia <andras@kdab.com>
0004     SPDX-FileCopyrightText: 2012 Dan Vrátil <dvratil@redhat.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-or-later
0007 */
0008 
0009 #include "removeduplicatesjob.h"
0010 #include "akonadi_mime_debug.h"
0011 #include <Akonadi/ItemDeleteJob>
0012 #include <Akonadi/ItemFetchJob>
0013 #include <Akonadi/ItemFetchScope>
0014 
0015 #include <KMime/KMimeMessage>
0016 
0017 #include <KLocalizedString>
0018 
0019 class Akonadi::RemoveDuplicatesJobPrivate
0020 {
0021 public:
0022     explicit RemoveDuplicatesJobPrivate(RemoveDuplicatesJob *parent)
0023         : mParent(parent)
0024     {
0025     }
0026 
0027     void fetchItem()
0028     {
0029         Akonadi::Collection collection = mFolders.value(mJobCount - 1);
0030         qCDebug(AKONADIMIME_LOG) << "Processing collection" << collection.name() << "(" << collection.id() << ")";
0031 
0032         auto job = new Akonadi::ItemFetchJob(collection, mParent);
0033         job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
0034         job->fetchScope().fetchFullPayload();
0035         job->fetchScope().setIgnoreRetrievalErrors(true);
0036         mParent->connect(job, &ItemFetchJob::result, mParent, [this](KJob *job) {
0037             slotFetchDone(job);
0038         });
0039         mCurrentJob = job;
0040 
0041         Q_EMIT mParent->description(mParent, i18n("Retrieving items..."));
0042     }
0043 
0044     void slotFetchDone(KJob *job)
0045     {
0046         mJobCount--;
0047         if (job->error()) {
0048             mParent->setError(job->error());
0049             mParent->setErrorText(job->errorText());
0050             mParent->emitResult();
0051             return;
0052         }
0053 
0054         if (mKilled) {
0055             mParent->emitResult();
0056             return;
0057         }
0058 
0059         Q_EMIT mParent->description(mParent, i18n("Searching for duplicates..."));
0060 
0061         auto fjob = static_cast<Akonadi::ItemFetchJob *>(job);
0062         Akonadi::Item::List items = fjob->items();
0063 
0064         // find duplicate mails with the same messageid
0065         // if duplicates are found, check the content as well to be sure they are the same
0066         QMap<QByteArray, uint> messageIds;
0067         QMap<uint, QList<uint>> duplicates;
0068         QMap<uint, uint> bodyHashes;
0069         const int numberOfItems(items.size());
0070         for (int i = 0; i < numberOfItems; ++i) {
0071             Akonadi::Item item = items.at(i);
0072             if (item.hasPayload<KMime::Message::Ptr>()) {
0073                 auto message = item.payload<KMime::Message::Ptr>();
0074                 const QByteArray idStr = message->messageID()->as7BitString(false);
0075                 // TODO: Maybe do some more check in case of idStr.isEmpty()
0076                 // like when the first message's body is different from the 2nd,
0077                 // but the 2nd is the same as the 3rd, etc.
0078                 // if ( !idStr.isEmpty() )
0079                 {
0080                     if (messageIds.contains(idStr)) {
0081                         const uint mainId = messageIds.value(idStr);
0082                         if (!bodyHashes.contains(mainId)) {
0083                             bodyHashes.insert(mainId, qHash(items.value(mainId).payload<KMime::Message::Ptr>()->encodedContent()));
0084                         }
0085                         const uint hash = qHash(message->encodedContent());
0086                         qCDebug(AKONADIMIME_LOG) << idStr << bodyHashes.value(mainId) << hash;
0087                         if (bodyHashes.value(mainId) == hash) {
0088                             duplicates[mainId].append(i);
0089                         }
0090                     } else {
0091                         messageIds.insert(idStr, i);
0092                     }
0093                 }
0094             }
0095         }
0096 
0097         QMap<uint, QList<uint>>::ConstIterator end(duplicates.constEnd());
0098         for (QMap<uint, QList<uint>>::ConstIterator it = duplicates.constBegin(); it != end; ++it) {
0099             QList<uint>::ConstIterator dupEnd(it.value().constEnd());
0100             for (QList<uint>::ConstIterator dupIt = it.value().constBegin(); dupIt != dupEnd; ++dupIt) {
0101                 mDuplicateItems.append(items.value(*dupIt));
0102             }
0103         }
0104 
0105         if (mKilled) {
0106             mParent->emitResult();
0107             return;
0108         }
0109 
0110         if (mJobCount > 0) {
0111             fetchItem();
0112         } else {
0113             if (mDuplicateItems.isEmpty()) {
0114                 qCDebug(AKONADIMIME_LOG) << "No duplicates, I'm done here";
0115                 mParent->emitResult();
0116                 return;
0117             } else {
0118                 Q_EMIT mParent->description(mParent, i18n("Removing duplicates..."));
0119                 auto delCmd = new Akonadi::ItemDeleteJob(mDuplicateItems, mParent);
0120                 mParent->connect(delCmd, &ItemDeleteJob::result, mParent, [this](KJob *job) {
0121                     slotDeleteDone(job);
0122                 });
0123             }
0124         }
0125     }
0126 
0127     void slotDeleteDone(KJob *job)
0128     {
0129         qCDebug(AKONADIMIME_LOG) << "Job done";
0130 
0131         mParent->setError(job->error());
0132         mParent->setErrorText(job->errorText());
0133         mParent->emitResult();
0134     }
0135 
0136     Akonadi::Collection::List mFolders;
0137     Akonadi::Item::List mDuplicateItems;
0138     Akonadi::Job *mCurrentJob = nullptr;
0139     int mJobCount = 0;
0140     bool mKilled = false;
0141 
0142 private:
0143     RemoveDuplicatesJob *const mParent;
0144 };
0145 
0146 using namespace Akonadi;
0147 
0148 RemoveDuplicatesJob::RemoveDuplicatesJob(const Akonadi::Collection &folder, QObject *parent)
0149     : Job(parent)
0150     , d(new RemoveDuplicatesJobPrivate(this))
0151 {
0152     d->mJobCount = 1;
0153     d->mFolders << folder;
0154 }
0155 
0156 RemoveDuplicatesJob::RemoveDuplicatesJob(const Akonadi::Collection::List &folders, QObject *parent)
0157     : Job(parent)
0158     , d(new RemoveDuplicatesJobPrivate(this))
0159 {
0160     d->mFolders = folders;
0161     d->mJobCount = d->mFolders.length();
0162 }
0163 
0164 RemoveDuplicatesJob::~RemoveDuplicatesJob() = default;
0165 
0166 void RemoveDuplicatesJob::doStart()
0167 {
0168     qCDebug(AKONADIMIME_LOG) << " void RemoveDuplicatesJob::doStart()";
0169 
0170     if (d->mFolders.isEmpty()) {
0171         qCWarning(AKONADIMIME_LOG) << "No collections to process";
0172         emitResult();
0173         return;
0174     }
0175 
0176     d->fetchItem();
0177 }
0178 
0179 bool RemoveDuplicatesJob::doKill()
0180 {
0181     qCDebug(AKONADIMIME_LOG) << "Killed!";
0182 
0183     d->mKilled = true;
0184     if (d->mCurrentJob) {
0185         d->mCurrentJob->kill(EmitResult);
0186     }
0187 
0188     return true;
0189 }
0190 
0191 #include "moc_removeduplicatesjob.cpp"