diff --git a/src/job/expirejob.cpp b/src/job/expirejob.cpp index e7a44a7..461f095 100644 --- a/src/job/expirejob.cpp +++ b/src/job/expirejob.cpp @@ -1,294 +1,291 @@ /** * Copyright (c) 2004 David Faure * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #include "expirejob.h" #include "collectionpage/attributes/expirecollectionattribute.h" #include "kernel/mailkernel.h" #include "util/mailutil.h" #include using KPIM::BroadcastStatus; #include "mailcommon_debug.h" #include #include #include #include #include #include #include #include #include #include /* Testcases for folder expiry: Automatic expiry: - normal case (ensure folder has old mails and expiry settings, wait for auto-expiry) - having the folder selected when the expiry job would run (gets delayed) - selecting a folder while an expiry job is running for it (should interrupt) - exiting kmail while an expiry job is running (should abort & delete things cleanly) Manual expiry: - RMB / expire (for one folder) [KMMainWidget::slotExpireFolder()] - RMB on Local Folders / Expire All Folders [KMFolderMgr::expireAll()] - Expire All Folders [KMMainWidget::slotExpireAll()] */ namespace MailCommon { ExpireJob::ExpireJob(const Akonadi::Collection &folder, bool immediate) : ScheduledJob(folder, immediate) - , mMaxUnreadTime(0) - , mMaxReadTime(0) - , mMoveToFolder(0) { } ExpireJob::~ExpireJob() { qCDebug(MAILCOMMON_LOG); } void ExpireJob::kill() { ScheduledJob::kill(); } void ExpireJob::execute() { mMaxUnreadTime = 0; mMaxReadTime = 0; int unreadDays, readDays; const MailCommon::ExpireCollectionAttribute *expirationAttribute = mSrcFolder.attribute(); if (expirationAttribute) { expirationAttribute->daysToExpire(unreadDays, readDays); if (unreadDays > 0) { qCDebug(MAILCOMMON_LOG) << "ExpireJob: deleting unread older than" << unreadDays << "days"; mMaxUnreadTime = QDateTime::currentDateTime().toSecsSinceEpoch() - unreadDays * 3600 * 24; } if (readDays > 0) { qCDebug(MAILCOMMON_LOG) << "ExpireJob: deleting read older than" << readDays << "days"; mMaxReadTime = QDateTime::currentDateTime().toSecsSinceEpoch() - readDays * 3600 * 24; } if ((mMaxUnreadTime == 0) && (mMaxReadTime == 0)) { qCDebug(MAILCOMMON_LOG) << "ExpireJob: nothing to do"; deleteLater(); return; } } else { deleteLater(); return; } qCDebug(MAILCOMMON_LOG) << "ExpireJob: starting to expire in folder" << mSrcFolder.name(); slotDoWork(); // do nothing here, we might be deleted! } void ExpireJob::slotDoWork() { Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(mSrcFolder, this); job->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Envelope); connect(job, &Akonadi::ItemFetchJob::result, this, &ExpireJob::itemFetchResult); } void ExpireJob::itemFetchResult(KJob *job) { if (job->error()) { qCWarning(MAILCOMMON_LOG) << job->errorString(); deleteLater(); return; } const Akonadi::Item::List items = qobject_cast(job)->items(); for (const Akonadi::Item &item : items) { if (!item.hasPayload()) { continue; } const KMime::Message::Ptr mb = item.payload(); Akonadi::MessageStatus status; status.setStatusFromFlags(item.flags()); if ((status.isImportant() || status.isToAct() || status.isWatched()) && SettingsIf->excludeImportantMailFromExpiry()) { continue; } time_t maxTime = status.isRead() ? mMaxReadTime : mMaxUnreadTime; if (!mb->date(false)) { continue; } if (mb->date()->dateTime().toSecsSinceEpoch() < maxTime) { mRemovedMsgs.append(item); } } done(); } void ExpireJob::done() { QString str; bool moving = false; if (!mRemovedMsgs.isEmpty()) { int count = mRemovedMsgs.count(); // The command shouldn't kill us because it opens the folder mCancellable = false; const MailCommon::ExpireCollectionAttribute *expirationAttribute = mSrcFolder.attribute(); if (expirationAttribute) { if (expirationAttribute->expireAction() == MailCommon::ExpireCollectionAttribute::ExpireDelete) { // Expire by deletion, i.e. move to null target folder qCDebug(MAILCOMMON_LOG) << "ExpireJob: finished expiring in folder" << mSrcFolder.name() << count << "messages to remove."; Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob(mRemovedMsgs, this); connect(job, &Akonadi::ItemDeleteJob::result, this, &ExpireJob::slotExpireDone); moving = true; str = i18np("Removing 1 old message from folder %2...", "Removing %1 old messages from folder %2...", count, mSrcFolder.name()); } else { // Expire by moving mMoveToFolder = Kernel::self()->collectionFromId(expirationAttribute->expireToFolderId()); if (!mMoveToFolder.isValid()) { str = i18n("Cannot expire messages from folder %1: destination " "folder %2 not found", mSrcFolder.name(), expirationAttribute->expireToFolderId()); qCWarning(MAILCOMMON_LOG) << str; } else { qCDebug(MAILCOMMON_LOG) << "ExpireJob: finished expiring in folder" << mSrcFolder.name() << mRemovedMsgs.count() << "messages to move to" << mMoveToFolder.name(); Akonadi::ItemMoveJob *job = new Akonadi::ItemMoveJob(mRemovedMsgs, mMoveToFolder, this); connect(job, &Akonadi::ItemMoveJob::result, this, &ExpireJob::slotMoveDone); moving = true; str = i18np("Moving 1 old message from folder %2 to folder %3...", "Moving %1 old messages from folder %2 to folder %3...", count, mSrcFolder.name(), mMoveToFolder.name()); } } } } if (!str.isEmpty()) { BroadcastStatus::instance()->setStatusMsg(str); } if (!moving) { deleteLater(); } } void ExpireJob::slotMoveDone(KJob *job) { if (job->error()) { qCCritical(MAILCOMMON_LOG) << job->error() << job->errorString(); } Akonadi::ItemMoveJob *itemjob = qobject_cast(job); if (itemjob) { const Akonadi::Item::List lst = itemjob->items(); if (!lst.isEmpty()) { Akonadi::Item::List newLst; for (Akonadi::Item item : lst) { if (!item.hasFlag(Akonadi::MessageFlags::Seen)) { item.setFlag(Akonadi::MessageFlags::Seen); newLst << item; } } if (!newLst.isEmpty()) { Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob(newLst, this); modifyJob->disableRevisionCheck(); connect(modifyJob, &Akonadi::ItemModifyJob::result, this, &ExpireJob::slotExpireDone); } else { slotExpireDone(job); } } } else { slotExpireDone(job); } } void ExpireJob::slotExpireDone(KJob *job) { if (job->error()) { qCCritical(MAILCOMMON_LOG) << job->error() << job->errorString(); } QString msg; const int error = job->error(); const MailCommon::ExpireCollectionAttribute *expirationAttribute = mSrcFolder.attribute(); if (expirationAttribute) { switch (error) { case KJob::NoError: if (expirationAttribute->expireAction() == MailCommon::ExpireCollectionAttribute::ExpireDelete) { msg = i18np("Removed 1 old message from folder %2.", "Removed %1 old messages from folder %2.", mRemovedMsgs.count(), mSrcFolder.name()); } else { msg = i18np("Moved 1 old message from folder %2 to folder %3.", "Moved %1 old messages from folder %2 to folder %3.", mRemovedMsgs.count(), mSrcFolder.name(), mMoveToFolder.name()); } break; case Akonadi::Job::UserCanceled: if (expirationAttribute->expireAction() == MailCommon::ExpireCollectionAttribute::ExpireDelete) { msg = i18n("Removing old messages from folder %1 was canceled.", mSrcFolder.name()); } else { msg = i18n("Moving old messages from folder %1 to folder %2 was " "canceled.", mSrcFolder.name(), mMoveToFolder.name()); } break; default: //any other error if (expirationAttribute->expireAction() == MailCommon::ExpireCollectionAttribute::ExpireDelete) { msg = i18n("Removing old messages from folder %1 failed.", mSrcFolder.name()); } else { msg = i18n("Moving old messages from folder %1 to folder %2 failed.", mSrcFolder.name(), mMoveToFolder.name()); } break; } BroadcastStatus::instance()->setStatusMsg(msg); } deleteLater(); } } diff --git a/src/job/expirejob.h b/src/job/expirejob.h index 3de294e..379754a 100644 --- a/src/job/expirejob.h +++ b/src/job/expirejob.h @@ -1,91 +1,91 @@ /* -*- mode: C++ -*- * Copyright (c) 2004 David Faure * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #ifndef MAILCOMMON_EXPIREJOB_H #define MAILCOMMON_EXPIREJOB_H #include "jobscheduler.h" #include #include class KJob; namespace MailCommon { class ExpireJob : public ScheduledJob { Q_OBJECT public: explicit ExpireJob(const Akonadi::Collection &folder, bool immediate); ~ExpireJob() override; void execute() override; void kill() override; private: void slotDoWork(); void slotExpireDone(KJob *job); void slotMoveDone(KJob *job); void itemFetchResult(KJob *job); void done(); private: Akonadi::Item::List mRemovedMsgs; - qint64 mMaxUnreadTime; - qint64 mMaxReadTime; + qint64 mMaxUnreadTime = 0; + qint64 mMaxReadTime = 0; Akonadi::Collection mMoveToFolder; }; /// A scheduled "expire mails in this folder" task. class ScheduledExpireTask : public ScheduledTask { public: /// If immediate is set, the job will execute synchronously. This is used when /// the user requests explicitly that the operation should happen immediately. ScheduledExpireTask(const Akonadi::Collection &folder, bool immediate) : ScheduledTask(folder, immediate) { } ~ScheduledExpireTask() override { } ScheduledJob *run() override { return folder().isValid() ? new ExpireJob(folder(), isImmediate()) : nullptr; } int taskTypeId() const override { return 1; } }; } // namespace #endif diff --git a/src/job/jobscheduler.cpp b/src/job/jobscheduler.cpp index 8b618bc..be8d00b 100644 --- a/src/job/jobscheduler.cpp +++ b/src/job/jobscheduler.cpp @@ -1,251 +1,248 @@ /** * Copyright (c) 2004 David Faure * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #include "jobscheduler.h" #include "mailcommon_debug.h" namespace MailCommon { ScheduledTask::ScheduledTask(const Akonadi::Collection &folder, bool immediate) : mCurrentFolder(folder) , mImmediate(immediate) { } ScheduledTask::~ScheduledTask() { } JobScheduler::JobScheduler(QObject *parent) : QObject(parent) , mTimer(this) - , mPendingImmediateTasks(0) - , mCurrentTask(nullptr) - , mCurrentJob(nullptr) { connect(&mTimer, &QTimer::timeout, this, &JobScheduler::slotRunNextJob); // No need to start the internal timer yet, we wait for a task to be scheduled } JobScheduler::~JobScheduler() { qDeleteAll(mTaskList); mTaskList.clear(); delete mCurrentTask; mCurrentTask = nullptr; delete mCurrentJob; } void JobScheduler::registerTask(ScheduledTask *task) { bool immediate = task->isImmediate(); int typeId = task->taskTypeId(); if (typeId) { const Akonadi::Collection folder = task->folder(); // Search for an identical task already scheduled TaskList::Iterator end(mTaskList.end()); for (TaskList::Iterator it = mTaskList.begin(); it != end; ++it) { if ((*it)->taskTypeId() == typeId && (*it)->folder() == folder) { #ifdef DEBUG_SCHEDULER qCDebug(MAILCOMMON_LOG) << "JobScheduler: already having task type" << typeId << "for folder" << folder->label(); #endif delete task; if (!mCurrentTask && immediate) { ScheduledTask *task = *it; removeTask(it); runTaskNow(task); } return; } } // Note that scheduling an identical task as the one currently running is allowed. } if (!mCurrentTask && immediate) { runTaskNow(task); } else { #ifdef DEBUG_SCHEDULER qCDebug(MAILCOMMON_LOG) << "JobScheduler: adding task" << task << "(type" << task->taskTypeId() << ") for folder" << task->folder() << task->folder().name(); #endif mTaskList.append(task); if (immediate) { ++mPendingImmediateTasks; } if (!mCurrentTask && !mTimer.isActive()) { restartTimer(); } } } void JobScheduler::removeTask(TaskList::Iterator &it) { if ((*it)->isImmediate()) { --mPendingImmediateTasks; } mTaskList.erase(it); } void JobScheduler::interruptCurrentTask() { Q_ASSERT(mCurrentTask); #ifdef DEBUG_SCHEDULER qCDebug(MAILCOMMON_LOG) << "JobScheduler: interrupting job" << mCurrentJob << "for folder" << mCurrentTask->folder()->label(); #endif // File it again. This will either delete it or put it in mTaskList. registerTask(mCurrentTask); mCurrentTask = nullptr; mCurrentJob->kill(); // This deletes the job and calls slotJobFinished! } void JobScheduler::slotRunNextJob() { while (!mCurrentJob) { #ifdef DEBUG_SCHEDULER qCDebug(MAILCOMMON_LOG) << "JobScheduler: slotRunNextJob"; #endif Q_ASSERT(mCurrentTask == nullptr); ScheduledTask *task = nullptr; // Find a task suitable for being run TaskList::Iterator end(mTaskList.end()); for (TaskList::Iterator it = mTaskList.begin(); it != end; ++it) { // Remove if folder died const Akonadi::Collection folder = (*it)->folder(); if (!folder.isValid()) { #ifdef DEBUG_SCHEDULER qCDebug(MAILCOMMON_LOG) << " folder for task" << (*it) << "was deleted"; #endif removeTask(it); if (!mTaskList.isEmpty()) { slotRunNextJob(); // to avoid the mess with invalid iterators :) } else { mTimer.stop(); } return; } #ifdef DEBUG_SCHEDULER qCDebug(MAILCOMMON_LOG) << " looking at folder" << folder.name(); #endif task = *it; removeTask(it); break; } if (!task) { // found nothing to run, i.e. folder was opened return; // Timer keeps running, i.e. try again in 1 minute } runTaskNow(task); } // If nothing to do for that task, loop and find another one to run } void JobScheduler::restartTimer() { if (mPendingImmediateTasks > 0) { slotRunNextJob(); } else { #ifdef DEBUG_SCHEDULER mTimer.start(10000); // 10 seconds #else mTimer.start(1 * 60000); // 1 minute #endif } } void JobScheduler::runTaskNow(ScheduledTask *task) { Q_ASSERT(mCurrentTask == nullptr); if (mCurrentTask) { interruptCurrentTask(); } mCurrentTask = task; mTimer.stop(); mCurrentJob = mCurrentTask->run(); #ifdef DEBUG_SCHEDULER qCDebug(MAILCOMMON_LOG) << "JobScheduler: task" << mCurrentTask << "(type" << mCurrentTask->taskTypeId() << ")" << "for folder" << mCurrentTask->folder()->label() << "returned job" << mCurrentJob << (mCurrentJob ? mCurrentJob->className() : 0); #endif if (!mCurrentJob) { // nothing to do, e.g. folder deleted delete mCurrentTask; mCurrentTask = nullptr; if (!mTaskList.isEmpty()) { restartTimer(); } return; } // Register the job in the folder. This makes it autodeleted if the folder is deleted. #if 0 mCurrentTask->folder()->storage()->addJob(mCurrentJob); #endif connect(mCurrentJob, &ScheduledJob::finished, this, &JobScheduler::slotJobFinished); mCurrentJob->start(); } void JobScheduler::slotJobFinished() { // Do we need to test for mCurrentJob->error()? What do we do then? #ifdef DEBUG_SCHEDULER qCDebug(MAILCOMMON_LOG) << "JobScheduler: slotJobFinished"; #endif delete mCurrentTask; mCurrentTask = nullptr; mCurrentJob = nullptr; if (!mTaskList.isEmpty()) { restartTimer(); } } // D-Bus call to pause any background jobs void JobScheduler::pause() { mPendingImmediateTasks = 0; if (mCurrentJob && mCurrentJob->isCancellable()) { interruptCurrentTask(); } mTimer.stop(); } void JobScheduler::resume() { restartTimer(); } //// ScheduledJob::ScheduledJob(const Akonadi::Collection &folder, bool immediate) : mImmediate(immediate) { mCancellable = true; mSrcFolder = folder; } ScheduledJob::~ScheduledJob() { } } diff --git a/src/job/jobscheduler.h b/src/job/jobscheduler.h index 6a6c25b..9f53585 100644 --- a/src/job/jobscheduler.h +++ b/src/job/jobscheduler.h @@ -1,154 +1,154 @@ /* * Copyright (c) 2004 David Faure * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #ifndef MAILCOMMON_JOBSCHEDULER_H #define MAILCOMMON_JOBSCHEDULER_H #include "mailcommon_export.h" #include #include #include #include "folderjob.h" #include // If this define is set, JobScheduler will show debug output, and related kmkernel timers will be shortened // This is for debugging purposes only, don't commit with it. //#define DEBUG_SCHEDULER namespace MailCommon { class FolderJob; class ScheduledJob; /** * A scheduled task is some information about a folder job that should be run later. * As long as it's not running, it's called a "task", i.e. something that needs to be done. * Tasks are held in the JobScheduler. */ class MAILCOMMON_EXPORT ScheduledTask { public: /// Create a scheduled task for a given folder /// If @p immediate is true, the scheduler will run this task as soon /// as possible (but won't interrupt a currently running job for it) ScheduledTask(const Akonadi::Collection &folder, bool immediate); virtual ~ScheduledTask(); /// Run this task, i.e. create a job for it. /// Important: the job's execute() method must either call open() on the /// folder or storage immediately, or abort (deleting itself). /// Usually, that job should also be cancellable. /// Otherwise (if the open() is delayed) an unrelated open() could happen first /// and mess things up. /// If for some reason (e.g. folder deleted) nothing should be done, return 0. virtual ScheduledJob *run() = 0; /// An identifier for the type of task (a bit like QListViewItem::rtti) /// This allows to automatically prevent two identical tasks from being scheduled /// for the same folder. To circumvent this feature and make every task /// unique, return 0 here. virtual int taskTypeId() const = 0; /// The folder which this task is about, 0 if it was deleted meanwhile. Q_REQUIRED_RESULT Akonadi::Collection folder() const { return mCurrentFolder; } Q_REQUIRED_RESULT bool isImmediate() const { return mImmediate; } private: Akonadi::Collection mCurrentFolder; bool mImmediate; }; /** * The unique JobScheduler instance (owned by kmkernel) implements "background processing" * of folder operations (like expiration and compaction). Tasks (things to be done) * are registered with the JobScheduler, and it will execute them one at a time, * separated with a 1-minute timer. The jobs themselves should use timers to avoid * using too much CPU for too long. Tasks for opened folders are not executed until * the folder is closed. */ class MAILCOMMON_EXPORT JobScheduler : public QObject { Q_OBJECT public: explicit JobScheduler(QObject *parent); ~JobScheduler(); /// Register a task to be done for a given folder /// The ownership of the task is transferred to the JobScheduler void registerTask(ScheduledTask *task); // D-Bus calls, called from KMKernel void pause(); void resume(); private: /// Called by a timer to run the next job void slotRunNextJob(); /// Called when the current job terminates void slotJobFinished(); void restartTimer(); void interruptCurrentTask(); void runTaskNow(ScheduledTask *task); typedef QVector TaskList; void removeTask(TaskList::Iterator &it); private: TaskList mTaskList; // FIFO of tasks to be run QTimer mTimer; - int mPendingImmediateTasks; + int mPendingImmediateTasks = 0; /// Information about the currently running job, if any ScheduledTask *mCurrentTask = nullptr; ScheduledJob *mCurrentJob = nullptr; }; /** * Base class for scheduled jobs */ class MAILCOMMON_EXPORT ScheduledJob : public FolderJob { public: ScheduledJob(const Akonadi::Collection &folder, bool immediate); ~ScheduledJob(); protected: bool mImmediate; }; } #endif