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

KDE's Doxygen guidelines are available online.