Akonadi Mime

removeduplicatesjob.cpp
1 /*
2  SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, <[email protected]>
3  SPDX-FileCopyrightText: 2010 Andras Mantia <[email protected]>
4  SPDX-FileCopyrightText: 2012 Dan Vrátil <[email protected]>
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/KMimeMessage>
16 
17 #include <KLocalizedString>
18 
19 class Akonadi::RemoveDuplicatesJobPrivate
20 {
21 public:
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 
136  Akonadi::Collection::List mFolders;
137  Akonadi::Item::List mDuplicateItems;
138  Akonadi::Job *mCurrentJob = nullptr;
139  int mJobCount = 0;
140  bool mKilled = false;
141 
142 private:
143  RemoveDuplicatesJob *const mParent;
144 };
145 
146 using 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 
166 void 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 
179 bool 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"
QMap::const_iterator constBegin() const const
Job that finds and removes duplicate messages in given collection.
bool contains(const Key &key) const const
void result(KJob *job)
const T value(const Key &key, const T &defaultValue) const const
RemoveDuplicatesJob(const Akonadi::Collection &folder, QObject *parent=nullptr)
Creates a new job that will remove duplicates in folder.
bool hasPayload() const
QMap::iterator insert(const Key &key, const T &value)
int size() const const
QString i18n(const char *text, const TYPE &arg...)
QMap::const_iterator constEnd() const const
~RemoveDuplicatesJob() override
Destroys the job.
void description(KJob *job, const QString &title, const QPair< QString, QString > &field1=QPair< QString, QString >(), const QPair< QString, QString > &field2=QPair< QString, QString >())
QString errorText() const
const T & at(int i) const const
KCALENDARCORE_EXPORT uint qHash(const KCalendarCore::Period &key)
void emitResult()
int error() const
T payload() const
QString message
const QList< QKeySequence > & end()
T value(int i) const const
QString name() const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Dec 11 2023 03:51:33 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.