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"