Mailcommon

expirejob.cpp
1 /**
2  * SPDX-FileCopyrightText: 2004 David Faure <[email protected]>
3  *
4  * SPDX-License-Identifier: GPL-2.0-or-later
5  */
6 
7 #include "expirejob.h"
8 #include "collectionpage/attributes/expirecollectionattribute.h"
9 #include "kernel/mailkernel.h"
10 
11 #include <PimCommon/BroadcastStatus>
12 using PimCommon::BroadcastStatus;
13 #include "mailcommon_debug.h"
14 
15 #include <KLocalizedString>
16 
17 #include <Akonadi/ItemDeleteJob>
18 #include <Akonadi/ItemFetchJob>
19 #include <Akonadi/ItemFetchScope>
20 #include <Akonadi/ItemModifyJob>
21 #include <Akonadi/ItemMoveJob>
22 #include <Akonadi/MessageFlags>
23 #include <Akonadi/MessageParts>
24 #include <Akonadi/MessageStatus>
25 #include <KMime/Message>
26 
27 /*
28  Testcases for folder expiry:
29  Automatic expiry:
30  - normal case (ensure folder has old mails and expiry settings, wait for auto-expiry)
31  - having the folder selected when the expiry job would run (gets delayed)
32  - selecting a folder while an expiry job is running for it (should interrupt)
33  - exiting kmail while an expiry job is running (should abort & delete things cleanly)
34  Manual expiry:
35  - RMB / expire (for one folder) [KMMainWidget::slotExpireFolder()]
36  - RMB on Local Folders / Expire All Folders [KMFolderMgr::expireAll()]
37  - Expire All Folders [KMMainWidget::slotExpireAll()]
38 */
39 
40 using namespace MailCommon;
41 ExpireJob::ExpireJob(const Akonadi::Collection &folder, bool immediate)
42  : ScheduledJob(folder, immediate)
43 {
44 }
45 
46 ExpireJob::~ExpireJob()
47 {
48  qCDebug(MAILCOMMON_LOG);
49 }
50 
51 void ExpireJob::kill()
52 {
53  ScheduledJob::kill();
54 }
55 
56 void ExpireJob::execute()
57 {
58  mMaxUnreadTime = 0;
59  mMaxReadTime = 0;
60 
61  const MailCommon::ExpireCollectionAttribute *expirationAttribute = mSrcFolder.attribute<MailCommon::ExpireCollectionAttribute>();
62  if (expirationAttribute) {
63  int unreadDays;
64  int readDays;
65  mExpireMessagesWithoutInvalidDate = expirationAttribute->expireMessagesWithValidDate();
66  expirationAttribute->daysToExpire(unreadDays, readDays);
67 
68  if (unreadDays > 0) {
69  qCDebug(MAILCOMMON_LOG) << "ExpireJob: deleting unread older than" << unreadDays << "days";
70  mMaxUnreadTime = QDateTime::currentDateTime().toSecsSinceEpoch() - unreadDays * 3600 * 24;
71  }
72  if (readDays > 0) {
73  qCDebug(MAILCOMMON_LOG) << "ExpireJob: deleting read older than" << readDays << "days";
74  mMaxReadTime = QDateTime::currentDateTime().toSecsSinceEpoch() - readDays * 3600 * 24;
75  }
76 
77  if ((mMaxUnreadTime == 0) && (mMaxReadTime == 0)) {
78  qCDebug(MAILCOMMON_LOG) << "ExpireJob: nothing to do";
79  deleteLater();
80  return;
81  }
82  } else {
83  deleteLater();
84  return;
85  }
86  qCDebug(MAILCOMMON_LOG) << "ExpireJob: starting to expire in folder" << mSrcFolder.name();
87  slotDoWork();
88  // do nothing here, we might be deleted!
89 }
90 
91 void ExpireJob::slotDoWork()
92 {
93  auto job = new Akonadi::ItemFetchJob(mSrcFolder, this);
94  job->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Envelope);
95  connect(job, &Akonadi::ItemFetchJob::result, this, &ExpireJob::itemFetchResult);
96 }
97 
98 void ExpireJob::itemFetchResult(KJob *job)
99 {
100  if (job->error()) {
101  qCWarning(MAILCOMMON_LOG) << job->errorString();
102  deleteLater();
103  return;
104  }
105 
106  const Akonadi::Item::List items = qobject_cast<Akonadi::ItemFetchJob *>(job)->items();
107  for (const Akonadi::Item &item : items) {
108  if (!item.hasPayload<KMime::Message::Ptr>()) {
109  continue;
110  }
111 
112  const auto mb = item.payload<KMime::Message::Ptr>();
114  status.setStatusFromFlags(item.flags());
115  if ((status.isImportant() || status.isToAct() || status.isWatched()) && SettingsIf->excludeImportantMailFromExpiry()) {
116  continue;
117  }
118 
119  auto mailDate = mb->date(false);
120  if (!mailDate) {
121  if (mExpireMessagesWithoutInvalidDate) {
122  mRemovedMsgs.append(item);
123  }
124  } else {
125  const time_t maxTime = status.isRead() ? mMaxReadTime : mMaxUnreadTime;
126  if (mailDate->dateTime().toSecsSinceEpoch() < maxTime) {
127  mRemovedMsgs.append(item);
128  }
129  }
130  }
131 
132  done();
133 }
134 
135 void ExpireJob::done()
136 {
137  QString str;
138  bool moving = false;
139 
140  if (!mRemovedMsgs.isEmpty()) {
141  int count = mRemovedMsgs.count();
142 
143  // The command shouldn't kill us because it opens the folder
144  mCancellable = false;
145 
146  const MailCommon::ExpireCollectionAttribute *expirationAttribute = mSrcFolder.attribute<MailCommon::ExpireCollectionAttribute>();
147  if (expirationAttribute) {
148  if (expirationAttribute->expireAction() == MailCommon::ExpireCollectionAttribute::ExpireDelete) {
149  // Expire by deletion, i.e. move to null target folder
150  qCDebug(MAILCOMMON_LOG) << "ExpireJob: finished expiring in folder" << mSrcFolder.name() << count << "messages to remove.";
151  auto job = new Akonadi::ItemDeleteJob(mRemovedMsgs, this);
152  connect(job, &Akonadi::ItemDeleteJob::result, this, &ExpireJob::slotExpireDone);
153  moving = true;
154  str = i18np("Removing 1 old message from folder %2...", "Removing %1 old messages from folder %2...", count, mSrcFolder.name());
155  } else {
156  // Expire by moving
157  mMoveToFolder = Kernel::self()->collectionFromId(expirationAttribute->expireToFolderId());
158  if (!mMoveToFolder.isValid()) {
159  str = i18n(
160  "Cannot expire messages from folder %1: destination "
161  "folder %2 not found",
162  mSrcFolder.name(),
163  expirationAttribute->expireToFolderId());
164  qCWarning(MAILCOMMON_LOG) << str;
165  } else {
166  qCDebug(MAILCOMMON_LOG) << "ExpireJob: finished expiring in folder" << mSrcFolder.name() << mRemovedMsgs.count() << "messages to move to"
167  << mMoveToFolder.name();
168  auto job = new Akonadi::ItemMoveJob(mRemovedMsgs, mMoveToFolder, this);
169  connect(job, &Akonadi::ItemMoveJob::result, this, &ExpireJob::slotMoveDone);
170  moving = true;
171  str = i18np("Moving 1 old message from folder %2 to folder %3...",
172  "Moving %1 old messages from folder %2 to folder %3...",
173  count,
174  mSrcFolder.name(),
175  mMoveToFolder.name());
176  }
177  }
178  }
179  }
180  if (!str.isEmpty()) {
181  BroadcastStatus::instance()->setStatusMsg(str);
182  }
183 
184  if (!moving) {
185  deleteLater();
186  }
187 }
188 
189 void ExpireJob::slotMoveDone(KJob *job)
190 {
191  if (job->error()) {
192  qCCritical(MAILCOMMON_LOG) << job->error() << job->errorString();
193  }
194  auto itemjob = qobject_cast<Akonadi::ItemMoveJob *>(job);
195  if (itemjob) {
196  const Akonadi::Item::List lst = itemjob->items();
197  if (!lst.isEmpty()) {
198  Akonadi::Item::List newLst;
199  for (Akonadi::Item item : lst) {
200  if (!item.hasFlag(Akonadi::MessageFlags::Seen)) {
201  item.setFlag(Akonadi::MessageFlags::Seen);
202  newLst << item;
203  }
204  }
205  if (!newLst.isEmpty()) {
206  auto modifyJob = new Akonadi::ItemModifyJob(newLst, this);
207  modifyJob->disableRevisionCheck();
208  connect(modifyJob, &Akonadi::ItemModifyJob::result, this, &ExpireJob::slotExpireDone);
209  } else {
210  slotExpireDone(job);
211  }
212  }
213  } else {
214  slotExpireDone(job);
215  }
216 }
217 
218 void ExpireJob::slotExpireDone(KJob *job)
219 {
220  if (job->error()) {
221  qCCritical(MAILCOMMON_LOG) << job->error() << job->errorString();
222  }
223 
224  QString msg;
225  const int error = job->error();
226 
227  const MailCommon::ExpireCollectionAttribute *expirationAttribute = mSrcFolder.attribute<MailCommon::ExpireCollectionAttribute>();
228  if (expirationAttribute) {
229  switch (error) {
230  case KJob::NoError:
231  if (expirationAttribute->expireAction() == MailCommon::ExpireCollectionAttribute::ExpireDelete) {
232  msg = i18np("Removed 1 old message from folder %2.", "Removed %1 old messages from folder %2.", mRemovedMsgs.count(), mSrcFolder.name());
233  } else {
234  msg = i18np("Moved 1 old message from folder %2 to folder %3.",
235  "Moved %1 old messages from folder %2 to folder %3.",
236  mRemovedMsgs.count(),
237  mSrcFolder.name(),
238  mMoveToFolder.name());
239  }
240  break;
241 
243  if (expirationAttribute->expireAction() == MailCommon::ExpireCollectionAttribute::ExpireDelete) {
244  msg = i18n("Removing old messages from folder %1 was canceled.", mSrcFolder.name());
245  } else {
246  msg = i18n(
247  "Moving old messages from folder %1 to folder %2 was "
248  "canceled.",
249  mSrcFolder.name(),
250  mMoveToFolder.name());
251  }
252  break;
253 
254  default: // any other error
255  if (expirationAttribute->expireAction() == MailCommon::ExpireCollectionAttribute::ExpireDelete) {
256  msg = i18n("Removing old messages from folder %1 failed.", mSrcFolder.name());
257  } else {
258  msg = i18n("Moving old messages from folder %1 to folder %2 failed.", mSrcFolder.name(), mMoveToFolder.name());
259  }
260  break;
261  }
262 
263  BroadcastStatus::instance()->setStatusMsg(msg);
264  }
265  deleteLater();
266 }
bool isEmpty() const const
void result(KJob *job)
QDateTime currentDateTime()
QString i18n(const char *text, const TYPE &arg...)
const AKONADI_MIME_EXPORT char Envelope[]
bool isEmpty() const const
Q_SCRIPTABLE CaptureState status()
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
qint64 toSecsSinceEpoch() const const
int count() const const
virtual QString errorString() const
int error() const
Base class for scheduled jobs.
Definition: jobscheduler.h:126
const AKONADI_MIME_EXPORT char Seen[]
The filter dialog.
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon May 8 2023 03:58:16 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.