File indexing completed on 2024-06-09 05:18:08

0001 /**
0002  * SPDX-FileCopyrightText: 2004 David Faure <faure@kde.org>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "expirejob.h"
0008 #include "collectionpage/attributes/expirecollectionattribute.h"
0009 #include "expiredeletejob.h"
0010 #include "expiremovejob.h"
0011 #include "kernel/mailkernel.h"
0012 
0013 #include <PimCommon/BroadcastStatus>
0014 using PimCommon::BroadcastStatus;
0015 #include "mailcommon_debug.h"
0016 
0017 #include <KLocalizedString>
0018 
0019 #include <Akonadi/ItemDeleteJob>
0020 #include <Akonadi/ItemFetchJob>
0021 #include <Akonadi/ItemFetchScope>
0022 #include <Akonadi/ItemModifyJob>
0023 #include <Akonadi/ItemMoveJob>
0024 #include <Akonadi/MessageFlags>
0025 #include <Akonadi/MessageParts>
0026 #include <Akonadi/MessageStatus>
0027 #include <KMime/Message>
0028 
0029 /*
0030  Testcases for folder expiry:
0031   Automatic expiry:
0032   - normal case (ensure folder has old mails and expiry settings, wait for auto-expiry)
0033   - having the folder selected when the expiry job would run (gets delayed)
0034   - selecting a folder while an expiry job is running for it (should interrupt)
0035   - exiting kmail while an expiry job is running (should abort & delete things cleanly)
0036   Manual expiry:
0037   - RMB / expire (for one folder)                   [KMMainWidget::slotExpireFolder()]
0038   - RMB on Local Folders / Expire All Folders       [KMFolderMgr::expireAll()]
0039   - Expire All Folders                              [KMMainWidget::slotExpireAll()]
0040 */
0041 
0042 using namespace MailCommon;
0043 ExpireJob::ExpireJob(const Akonadi::Collection &folder, bool immediate)
0044     : ScheduledJob(folder, immediate)
0045 {
0046 }
0047 
0048 ExpireJob::~ExpireJob()
0049 {
0050     qCDebug(MAILCOMMON_LOG);
0051 }
0052 
0053 void ExpireJob::kill()
0054 {
0055     ScheduledJob::kill();
0056 }
0057 
0058 void ExpireJob::execute()
0059 {
0060     const MailCommon::ExpireCollectionAttribute *expirationAttribute = mSrcFolder.attribute<MailCommon::ExpireCollectionAttribute>();
0061     if (expirationAttribute) {
0062         mMaxUnreadTime = 0;
0063         mMaxReadTime = 0;
0064         int unreadDays;
0065         int readDays;
0066         mExpireMessagesWithoutInvalidDate = expirationAttribute->expireMessagesWithValidDate();
0067         expirationAttribute->daysToExpire(unreadDays, readDays);
0068 
0069         if (unreadDays > 0) {
0070             qCDebug(MAILCOMMON_LOG) << "ExpireJob: deleting unread older than" << unreadDays << "days";
0071             mMaxUnreadTime = QDateTime::currentDateTime().toSecsSinceEpoch() - unreadDays * 3600 * 24;
0072         }
0073         if (readDays > 0) {
0074             qCDebug(MAILCOMMON_LOG) << "ExpireJob: deleting read older than" << readDays << "days";
0075             mMaxReadTime = QDateTime::currentDateTime().toSecsSinceEpoch() - readDays * 3600 * 24;
0076         }
0077 
0078         if ((mMaxUnreadTime == 0) && (mMaxReadTime == 0)) {
0079             qCDebug(MAILCOMMON_LOG) << "ExpireJob: nothing to do";
0080             deleteLater();
0081             return;
0082         }
0083     } else {
0084         deleteLater();
0085         return;
0086     }
0087     qCDebug(MAILCOMMON_LOG) << "ExpireJob: starting to expire in folder" << mSrcFolder.name();
0088     slotDoWork();
0089     // do nothing here, we might be deleted!
0090 }
0091 
0092 void ExpireJob::slotDoWork()
0093 {
0094     auto job = new Akonadi::ItemFetchJob(mSrcFolder, this);
0095     job->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Envelope);
0096     connect(job, &Akonadi::ItemFetchJob::result, this, &ExpireJob::itemFetchResult);
0097 }
0098 
0099 void ExpireJob::itemFetchResult(KJob *job)
0100 {
0101     if (job->error()) {
0102         qCWarning(MAILCOMMON_LOG) << job->errorString();
0103         deleteLater();
0104         return;
0105     }
0106 
0107     const Akonadi::Item::List items = qobject_cast<Akonadi::ItemFetchJob *>(job)->items();
0108     for (const Akonadi::Item &item : items) {
0109         if (!item.hasPayload<KMime::Message::Ptr>()) {
0110             continue;
0111         }
0112 
0113         const auto mb = item.payload<KMime::Message::Ptr>();
0114         Akonadi::MessageStatus status;
0115         status.setStatusFromFlags(item.flags());
0116         if ((status.isImportant() || status.isToAct() || status.isWatched()) && SettingsIf->excludeImportantMailFromExpiry()) {
0117             continue;
0118         }
0119 
0120         auto mailDate = mb->date(false);
0121         if (!mailDate) {
0122             if (mExpireMessagesWithoutInvalidDate) {
0123                 mRemovedMsgs.append(item);
0124             }
0125         } else {
0126             const time_t maxTime = status.isRead() ? mMaxReadTime : mMaxUnreadTime;
0127             if (mailDate->dateTime().toSecsSinceEpoch() < maxTime) {
0128                 mRemovedMsgs.append(item);
0129             }
0130         }
0131     }
0132 
0133     done();
0134 }
0135 
0136 void ExpireJob::done()
0137 {
0138     QString str;
0139     bool moving = false;
0140     if (!mRemovedMsgs.isEmpty()) {
0141         const int count = mRemovedMsgs.count();
0142 
0143         // The command shouldn't kill us because it opens the folder
0144         mCancellable = false;
0145 
0146         const MailCommon::ExpireCollectionAttribute *expirationAttribute = mSrcFolder.attribute<MailCommon::ExpireCollectionAttribute>();
0147         if (expirationAttribute) {
0148             const QString srcFolderName{mSrcFolder.name()};
0149             if (expirationAttribute->expireAction() == MailCommon::ExpireCollectionAttribute::ExpireDelete) {
0150                 // Expire by deletion, i.e. move to null target folder
0151                 qCDebug(MAILCOMMON_LOG) << "ExpireJob: finished expiring in folder" << srcFolderName << count << "messages to remove.";
0152                 auto job = new ExpireDeleteJob(this);
0153                 job->setRemovedMsgs(mRemovedMsgs);
0154                 job->setSourceFolderName(srcFolderName);
0155                 connect(job, &ExpireDeleteJob::expireDeleteDone, this, &ExpireJob::slotExpireDeleteDone);
0156                 moving = true;
0157                 str = i18np("Removing 1 old message from folder %2...", "Removing %1 old messages from folder %2...", count, srcFolderName);
0158                 job->start();
0159             } else {
0160                 // Expire by moving
0161                 mMoveToFolder = Kernel::self()->collectionFromId(expirationAttribute->expireToFolderId());
0162                 if (!mMoveToFolder.isValid()) {
0163                     str = i18n(
0164                         "Cannot expire messages from folder %1: destination "
0165                         "folder %2 not found",
0166                         srcFolderName,
0167                         expirationAttribute->expireToFolderId());
0168                     qCWarning(MAILCOMMON_LOG) << str;
0169                 } else {
0170                     qCDebug(MAILCOMMON_LOG) << "ExpireJob: finished expiring in folder" << srcFolderName << mRemovedMsgs.count() << "messages to move to"
0171                                             << mMoveToFolder.name();
0172 
0173                     auto job = new ExpireMoveJob(this);
0174                     job->setRemovedMsgs(mRemovedMsgs);
0175                     job->setSrcFolderName(srcFolderName);
0176                     job->setMoveToFolder(mMoveToFolder);
0177                     connect(job, &ExpireMoveJob::expireMovedDone, this, &ExpireJob::slotExpireDeleteDone);
0178                     job->start();
0179                     moving = true;
0180                     str = i18np("Moving 1 old message from folder %2 to folder %3...",
0181                                 "Moving %1 old messages from folder %2 to folder %3...",
0182                                 count,
0183                                 srcFolderName,
0184                                 mMoveToFolder.name());
0185                 }
0186             }
0187         }
0188         if (!str.isEmpty()) {
0189             BroadcastStatus::instance()->setStatusMsg(str);
0190         }
0191     }
0192 
0193     if (!moving) {
0194         deleteLater();
0195     }
0196 }
0197 
0198 void ExpireJob::slotExpireDeleteDone()
0199 {
0200     deleteLater();
0201 }
0202 
0203 #include "moc_expirejob.cpp"