Akonadi Mime

removeduplicatesjob.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>
3 SPDX-FileCopyrightText: 2010 Andras Mantia <andras@kdab.com>
4 SPDX-FileCopyrightText: 2012 Dan Vrátil <dvratil@redhat.com>
5
6 SPDX-License-Identifier: LGPL-2.1-or-later
7*/
8
9#include "removeduplicatesjob.h"
10#include "akonadi_mime_debug.h"
11#include <Akonadi/ItemDeleteJob>
12#include <Akonadi/ItemFetchJob>
13#include <Akonadi/ItemFetchScope>
14
15#include <KMime/Message>
16
17#include <KLocalizedString>
18
19class Akonadi::RemoveDuplicatesJobPrivate
20{
21public:
22 explicit RemoveDuplicatesJobPrivate(RemoveDuplicatesJob *parent)
23 : mParent(parent)
24 {
25 }
26
27 void fetchItem()
28 {
29 Akonadi::Collection collection = mFolders.value(mJobCount - 1);
30 qCDebug(AKONADIMIME_LOG) << "Processing collection" << collection.name() << "(" << collection.id() << ")";
31
32 auto job = new Akonadi::ItemFetchJob(collection, mParent);
33 job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
34 job->fetchScope().fetchFullPayload();
35 job->fetchScope().setIgnoreRetrievalErrors(true);
36 mParent->connect(job, &ItemFetchJob::result, mParent, [this](KJob *job) {
37 slotFetchDone(job);
38 });
39 mCurrentJob = job;
40
41 Q_EMIT mParent->description(mParent, i18n("Retrieving items…"));
42 }
43
44 void slotFetchDone(KJob *job)
45 {
46 mJobCount--;
47 if (job->error()) {
48 mParent->setError(job->error());
49 mParent->setErrorText(job->errorText());
50 mParent->emitResult();
51 return;
52 }
53
54 if (mKilled) {
55 mParent->emitResult();
56 return;
57 }
58
59 Q_EMIT mParent->description(mParent, i18n("Searching for duplicates…"));
60
61 auto fjob = static_cast<Akonadi::ItemFetchJob *>(job);
62 Akonadi::Item::List items = fjob->items();
63
64 // find duplicate mails with the same messageid
65 // if duplicates are found, check the content as well to be sure they are the same
66 QMap<QByteArray, uint> messageIds;
67 QMap<uint, QList<uint>> duplicates;
68 QMap<uint, uint> bodyHashes;
69 const int numberOfItems(items.size());
70 for (int i = 0; i < numberOfItems; ++i) {
71 Akonadi::Item item = items.at(i);
72 if (item.hasPayload<KMime::Message::Ptr>()) {
73 auto message = item.payload<KMime::Message::Ptr>();
74 const QByteArray idStr = message->messageID()->as7BitString(false);
75 // TODO: Maybe do some more check in case of idStr.isEmpty()
76 // like when the first message's body is different from the 2nd,
77 // but the 2nd is the same as the 3rd, etc.
78 // if ( !idStr.isEmpty() )
79 {
80 if (messageIds.contains(idStr)) {
81 const uint mainId = messageIds.value(idStr);
82 if (!bodyHashes.contains(mainId)) {
83 bodyHashes.insert(mainId, qHash(items.value(mainId).payload<KMime::Message::Ptr>()->encodedContent()));
84 }
85 const uint hash = qHash(message->encodedContent());
86 qCDebug(AKONADIMIME_LOG) << idStr << bodyHashes.value(mainId) << hash;
87 if (bodyHashes.value(mainId) == hash) {
88 duplicates[mainId].append(i);
89 }
90 } else {
91 messageIds.insert(idStr, i);
92 }
93 }
94 }
95 }
96
97 QMap<uint, QList<uint>>::ConstIterator end(duplicates.constEnd());
98 for (QMap<uint, QList<uint>>::ConstIterator it = duplicates.constBegin(); it != end; ++it) {
99 QList<uint>::ConstIterator dupEnd(it.value().constEnd());
100 for (QList<uint>::ConstIterator dupIt = it.value().constBegin(); dupIt != dupEnd; ++dupIt) {
101 mDuplicateItems.append(items.value(*dupIt));
102 }
103 }
104
105 if (mKilled) {
106 mParent->emitResult();
107 return;
108 }
109
110 if (mJobCount > 0) {
111 fetchItem();
112 } else {
113 if (mDuplicateItems.isEmpty()) {
114 qCDebug(AKONADIMIME_LOG) << "No duplicates, I'm done here";
115 mParent->emitResult();
116 return;
117 } else {
118 Q_EMIT mParent->description(mParent, i18n("Removing duplicates…"));
119 auto delCmd = new Akonadi::ItemDeleteJob(mDuplicateItems, mParent);
120 mParent->connect(delCmd, &ItemDeleteJob::result, mParent, [this](KJob *job) {
121 slotDeleteDone(job);
122 });
123 }
124 }
125 }
126
127 void slotDeleteDone(KJob *job)
128 {
129 qCDebug(AKONADIMIME_LOG) << "Job done";
130
131 mParent->setError(job->error());
132 mParent->setErrorText(job->errorText());
133 mParent->emitResult();
134 }
135
137 Akonadi::Item::List mDuplicateItems;
138 Akonadi::Job *mCurrentJob = nullptr;
139 int mJobCount = 0;
140 bool mKilled = false;
141
142private:
143 RemoveDuplicatesJob *const mParent;
144};
145
146using namespace Akonadi;
147
149 : Job(parent)
150 , d(new RemoveDuplicatesJobPrivate(this))
151{
152 d->mJobCount = 1;
153 d->mFolders << folder;
154}
155
157 : Job(parent)
158 , d(new RemoveDuplicatesJobPrivate(this))
159{
160 d->mFolders = folders;
161 d->mJobCount = d->mFolders.length();
162}
163
165
166void RemoveDuplicatesJob::doStart()
167{
168 qCDebug(AKONADIMIME_LOG) << " void RemoveDuplicatesJob::doStart()";
169
170 if (d->mFolders.isEmpty()) {
171 qCWarning(AKONADIMIME_LOG) << "No collections to process";
172 emitResult();
173 return;
174 }
175
176 d->fetchItem();
177}
178
179bool RemoveDuplicatesJob::doKill()
180{
181 qCDebug(AKONADIMIME_LOG) << "Killed!";
182
183 d->mKilled = true;
184 if (d->mCurrentJob) {
185 d->mCurrentJob->kill(EmitResult);
186 }
187
188 return true;
189}
190
191#include "moc_removeduplicatesjob.cpp"
QString name() const
bool hasPayload() const
T payload() const
Job that finds and removes duplicate messages in given collection.
~RemoveDuplicatesJob() override
Destroys the job.
RemoveDuplicatesJob(const Akonadi::Collection &folder, QObject *parent=nullptr)
Creates a new job that will remove duplicates in folder.
void setErrorText(const QString &errorText)
void description(KJob *job, const QString &title, const QPair< QString, QString > &field1=QPair< QString, QString >(), const QPair< QString, QString > &field2=QPair< QString, QString >())
void emitResult()
int error() const
void result(KJob *job)
void setError(int errorCode)
QString errorText() const
QString i18n(const char *text, const TYPE &arg...)
const QList< QKeySequence > & end()
KTEXTEDITOR_EXPORT size_t qHash(KTextEditor::Cursor cursor, size_t seed=0) noexcept
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
bool isEmpty() const const
qsizetype size() const const
T value(qsizetype i) const const
const_iterator constBegin() const const
const_iterator constEnd() const const
bool contains(const Key &key) const const
iterator insert(const Key &key, const T &value)
T value(const Key &key, const T &defaultValue) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:05:31 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.