diff --git a/resources/mbox/deleteditemsattribute.cpp b/resources/mbox/deleteditemsattribute.cpp index 0be5e3033..8c63b9ccf 100644 --- a/resources/mbox/deleteditemsattribute.cpp +++ b/resources/mbox/deleteditemsattribute.cpp @@ -1,104 +1,104 @@ /* Copyright (c) 2009 Bertjan Broeksema This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "deleteditemsattribute.h" +#include "helper_p.h" DeletedItemsAttribute::DeletedItemsAttribute() { } DeletedItemsAttribute::DeletedItemsAttribute(const DeletedItemsAttribute &other) : Akonadi::Attribute() { if (&other == this) { return; } mDeletedItemOffsets = other.mDeletedItemOffsets; } DeletedItemsAttribute::~DeletedItemsAttribute() { } void DeletedItemsAttribute::addDeletedItemOffset(quint64 offset) { mDeletedItemOffsets.insert(offset); } DeletedItemsAttribute *DeletedItemsAttribute::clone() const { return new DeletedItemsAttribute(*this); } QSet DeletedItemsAttribute::deletedItemOffsets() const { return mDeletedItemOffsets; } KMBox::MBoxEntry::List DeletedItemsAttribute::deletedItemEntries() const { KMBox::MBoxEntry::List entries; entries.reserve(mDeletedItemOffsets.count()); foreach (quint64 offset, mDeletedItemOffsets) { entries << KMBox::MBoxEntry(offset); } return entries; } void DeletedItemsAttribute::deserialize(const QByteArray &data) { const QList offsets = data.split(','); mDeletedItemOffsets.clear(); mDeletedItemOffsets.reserve(offsets.count()); for (const QByteArray &offset : offsets) { mDeletedItemOffsets.insert(offset.toULongLong()); } } QByteArray DeletedItemsAttribute::serialized() const { QByteArray serialized; - foreach (quint64 offset, mDeletedItemOffsets) { - serialized += QByteArray::number(offset); - serialized += ','; + for (quint64 offset : qAsConst(mDeletedItemOffsets)) { + serialized += QByteArray::number(offset) + ','; } serialized.chop(1); // Remove the last ',' return serialized; } int DeletedItemsAttribute::offsetCount() const { return mDeletedItemOffsets.size(); } QByteArray DeletedItemsAttribute::type() const { static const QByteArray sType("DeletedMboxItems"); return sType; } bool DeletedItemsAttribute::operator==(const DeletedItemsAttribute &other) const { return mDeletedItemOffsets == other.deletedItemOffsets(); } diff --git a/resources/pop3/pop3resource.cpp b/resources/pop3/pop3resource.cpp index 591116ec6..66aed42e9 100644 --- a/resources/pop3/pop3resource.cpp +++ b/resources/pop3/pop3resource.cpp @@ -1,1043 +1,1044 @@ /* Copyright 2009 Thomas McGuire This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "pop3resource.h" #include "accountdialog.h" #include "settings.h" #include "jobs.h" +#include "helper_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pop3resource_debug.h" #include using namespace Akonadi; using namespace MailTransport; using namespace KWallet; POP3Resource::POP3Resource(const QString &id) : ResourceBase(id), mState(Idle), mPopSession(nullptr), mAskAgain(false), mIntervalTimer(new QTimer(this)), mTestLocalInbox(false), mWallet(nullptr), mIdsToSaveValid(false), mDeleteJob(nullptr) { Akonadi::AttributeFactory::registerAttribute(); setNeedsNetwork(true); Settings::self()->setResourceId(identifier()); resetState(); connect(this, &POP3Resource::abortRequested, this, &POP3Resource::slotAbortRequested); connect(mIntervalTimer, &QTimer::timeout, this, &POP3Resource::intervalCheckTriggered); connect(this, &POP3Resource::reloadConfiguration, this, &POP3Resource::configurationChanged); } POP3Resource::~POP3Resource() { Settings::self()->save(); delete mWallet; mWallet = nullptr; } void POP3Resource::configurationChanged() { Settings::self()->save(); updateIntervalTimer(); } void POP3Resource::updateIntervalTimer() { if (Settings::self()->intervalCheckEnabled() && mState == Idle) { mIntervalTimer->start(Settings::self()->intervalCheckInterval() * 1000 * 60); } else { mIntervalTimer->stop(); } } void POP3Resource::intervalCheckTriggered() { Q_ASSERT(mState == Idle); if (isOnline()) { qCDebug(POP3RESOURCE_LOG) << "Starting interval mail check."; startMailCheck(); mIntervalCheckInProgress = true; } else { mIntervalTimer->start(); } } void POP3Resource::aboutToQuit() { if (mState != Idle) { cancelSync(i18n("Mail check aborted.")); } } void POP3Resource::slotAbortRequested() { if (mState != Idle) { cancelSync(i18n("Mail check was canceled manually."), false /* no error */); } } void POP3Resource::configure(WId windowId) { QPointer accountDialog(new AccountDialog(this, windowId)); if (accountDialog->exec() == QDialog::Accepted) { updateIntervalTimer(); Q_EMIT configurationDialogAccepted(); } else { Q_EMIT configurationDialogRejected(); } delete accountDialog; } void POP3Resource::retrieveItems(const Akonadi::Collection &collection) { Q_UNUSED(collection); qCWarning(POP3RESOURCE_LOG) << "This should never be called, we don't have a collection!"; } bool POP3Resource::retrieveItem(const Akonadi::Item &item, const QSet &parts) { Q_UNUSED(item); Q_UNUSED(parts); qCWarning(POP3RESOURCE_LOG) << "This should never be called, we don't have any item!"; return false; } QString POP3Resource::buildLabelForPasswordDialog(const QString &detailedError) const { const QString queryText = i18n("Please enter the username and password for account '%1'.", agentName()) + QLatin1String("
") + detailedError; return queryText; } void POP3Resource::walletOpenedForLoading(bool success) { bool passwordLoaded = success; if (success) { if (mWallet && mWallet->isOpen() && mWallet->hasFolder(QStringLiteral("pop3"))) { mWallet->setFolder(QStringLiteral("pop3")); if (mWallet->hasEntry(identifier())) { mWallet->readPassword(identifier(), mPassword); } else { passwordLoaded = false; } } else { passwordLoaded = false; } } delete mWallet; mWallet = nullptr; if (!passwordLoaded) { const QString queryText = buildLabelForPasswordDialog( i18n("You are asked here because the password could not be loaded from the wallet.")); showPasswordDialog(queryText); } else { advanceState(Connect); } } void POP3Resource::walletOpenedForSaving(bool success) { if (success) { if (mWallet && mWallet->isOpen()) { if (!mWallet->hasFolder(QStringLiteral("pop3"))) { mWallet->createFolder(QStringLiteral("pop3")); } mWallet->setFolder(QStringLiteral("pop3")); mWallet->writePassword(identifier(), mPassword); } } else { qCWarning(POP3RESOURCE_LOG) << "Unable to write the password to the wallet."; } delete mWallet; mWallet = nullptr; finish(); } void POP3Resource::showPasswordDialog(const QString &queryText) { QPointer dlg = new KPasswordDialog( nullptr, KPasswordDialog::ShowUsernameLine); dlg->setModal(true); dlg->setUsername(Settings::self()->login()); dlg->setPassword(mPassword); dlg->setPrompt(queryText); dlg->setWindowTitle(name()); dlg->addCommentLine(i18n("Account:"), name()); bool gotIt = false; if (dlg->exec()) { mPassword = dlg->password(); Settings::self()->setLogin(dlg->username()); Settings::self()->save(); if (!dlg->password().isEmpty()) { mSavePassword = true; } mAskAgain = false; advanceState(Connect); gotIt = true; } delete dlg; if (!gotIt) { cancelSync(i18n("No username and password supplied.")); } } void POP3Resource::advanceState(State nextState) { mState = nextState; doStateStep(); } void POP3Resource::doStateStep() { switch (mState) { case Idle: { Q_ASSERT(false); qCWarning(POP3RESOURCE_LOG) << "State machine should not be called in idle state!"; break; } case FetchTargetCollection: { qCDebug(POP3RESOURCE_LOG) << "================ Starting state FetchTargetCollection =========="; Q_EMIT status(Running, i18n("Preparing transmission from \"%1\".", name())); Collection targetCollection(Settings::self()->targetCollection()); if (targetCollection.isValid()) { CollectionFetchJob *fetchJob = new CollectionFetchJob(targetCollection, CollectionFetchJob::Base); fetchJob->start(); connect(fetchJob, &CollectionFetchJob::result, this, &POP3Resource::targetCollectionFetchJobFinished); } else { // No target collection set in the config? Try requesting a default inbox SpecialMailCollectionsRequestJob *requestJob = new SpecialMailCollectionsRequestJob(this); requestJob->requestDefaultCollection(SpecialMailCollections::Inbox); requestJob->start(); connect(requestJob, &SpecialMailCollectionsRequestJob::result, this, &POP3Resource::localFolderRequestJobFinished); } break; } case Precommand: { qCDebug(POP3RESOURCE_LOG) << "================ Starting state Precommand ====================="; if (!Settings::self()->precommand().isEmpty()) { PrecommandJob *precommandJob = new PrecommandJob(Settings::self()->precommand(), this); connect(precommandJob, &PrecommandJob::result, this, &POP3Resource::precommandResult); precommandJob->start(); Q_EMIT status(Running, i18n("Executing precommand.")); } else { advanceState(RequestPassword); } break; } case RequestPassword: { qCDebug(POP3RESOURCE_LOG) << "================ Starting state RequestPassword ================"; // Don't show any wallet or password prompts when we are unit-testing if (!Settings::self()->unitTestPassword().isEmpty()) { mPassword = Settings::self()->unitTestPassword(); advanceState(Connect); break; } const bool passwordNeeded = Settings::self()->authenticationMethod() != MailTransport::Transport::EnumAuthenticationType::GSSAPI; const bool loadPasswordFromWallet = !mAskAgain && passwordNeeded && !Settings::self()->login().isEmpty() && mPassword.isEmpty(); if (loadPasswordFromWallet) { mWallet = Wallet::openWallet(Wallet::NetworkWallet(), winIdForDialogs(), Wallet::Asynchronous); } if (loadPasswordFromWallet && mWallet) { connect(mWallet, &KWallet::Wallet::walletOpened, this, &POP3Resource::walletOpenedForLoading); } else if (passwordNeeded && (mPassword.isEmpty() || mAskAgain)) { QString detail; if (mAskAgain) { detail = i18n("You are asked here because the previous login was not successful."); } else if (Settings::self()->login().isEmpty()) { detail = i18n("You are asked here because the username you supplied is empty."); } else if (!mWallet) { detail = i18n("You are asked here because the wallet password storage is disabled."); } showPasswordDialog(buildLabelForPasswordDialog(detail)); } else { // No password needed or using previous password, go on with Connect advanceState(Connect); } break; } case Connect: { qCDebug(POP3RESOURCE_LOG) << "================ Starting state Connect ========================"; Q_ASSERT(!mPopSession); mPopSession = new POPSession(mPassword); connect(mPopSession, &POPSession::slaveError, this, &POP3Resource::slotSessionError); advanceState(Login); break; } case Login: { qCDebug(POP3RESOURCE_LOG) << "================ Starting state Login =========================="; LoginJob *loginJob = new LoginJob(mPopSession); connect(loginJob, &LoginJob::result, this, &POP3Resource::loginJobResult); loginJob->start(); break; } case List: { qCDebug(POP3RESOURCE_LOG) << "================ Starting state List ==========================="; Q_EMIT status(Running, i18n("Fetching mail listing.")); ListJob *listJob = new ListJob(mPopSession); connect(listJob, &ListJob::result, this, &POP3Resource::listJobResult); listJob->start(); } break; case UIDList: { qCDebug(POP3RESOURCE_LOG) << "================ Starting state UIDList ========================"; UIDListJob *uidListJob = new UIDListJob(mPopSession); connect(uidListJob, &UIDListJob::result, this, &POP3Resource::uidListJobResult); uidListJob->start(); } break; case Download: { qCDebug(POP3RESOURCE_LOG) << "================ Starting state Download ======================="; // Determine which mails we want to download. Those are all mails which are // currently on ther server, minus the ones we have already downloaded (we // remember which UIDs we have downloaded in the settings) QList idsToDownload = mIdsToSizeMap.keys(); const QList alreadyDownloadedUIDs = Settings::self()->seenUidList(); foreach (const QString &uidOnServer, mIdsToUidsMap) { if (alreadyDownloadedUIDs.contains(uidOnServer)) { const int idOfUIDOnServer = mUidsToIdsMap.value(uidOnServer, -1); Q_ASSERT(idOfUIDOnServer != -1); idsToDownload.removeAll(idOfUIDOnServer); } } mIdsToDownload = idsToDownload; qCDebug(POP3RESOURCE_LOG) << "We are going to download" << mIdsToDownload.size() << "messages"; // For proper progress, the job needs to know the sizes of the messages, so // put them into a list here QList sizesOfMessagesToDownload; sizesOfMessagesToDownload.reserve(idsToDownload.count()); foreach (int id, idsToDownload) { sizesOfMessagesToDownload.append(mIdsToSizeMap.value(id)); } if (mIdsToDownload.empty()) { advanceState(CheckRemovingMessage); } else { FetchJob *fetchJob = new FetchJob(mPopSession); fetchJob->setFetchIds(idsToDownload, sizesOfMessagesToDownload); connect(fetchJob, &FetchJob::result, this, &POP3Resource::fetchJobResult); connect(fetchJob, &FetchJob::messageFinished, this, &POP3Resource::messageFinished); connect(fetchJob, SIGNAL(processedAmount(KJob*,KJob::Unit,qulonglong)), this, SLOT(messageDownloadProgress(KJob*,KJob::Unit,qulonglong))); fetchJob->start(); } } break; case Save: { qCDebug(POP3RESOURCE_LOG) << "================ Starting state Save ==========================="; qCDebug(POP3RESOURCE_LOG) << mPendingCreateJobs.size() << "item create jobs are pending"; if (!mPendingCreateJobs.isEmpty()) { Q_EMIT status(Running, i18n("Saving downloaded messages.")); } if (shouldAdvanceToQuitState()) { advanceState(Quit); } } break; case Quit: { qCDebug(POP3RESOURCE_LOG) << "================ Starting state Quit ==========================="; QuitJob *quitJob = new QuitJob(mPopSession); connect(quitJob, &QuitJob::result, this, &POP3Resource::quitJobResult); quitJob->start(); } break; case SavePassword: { qCDebug(POP3RESOURCE_LOG) << "================ Starting state SavePassword ==================="; if (mSavePassword) { qCDebug(POP3RESOURCE_LOG) << "Writing password back to the wallet."; Q_EMIT status(Running, i18n("Saving password to the wallet.")); mWallet = Wallet::openWallet(Wallet::NetworkWallet(), winIdForDialogs(), Wallet::Asynchronous); if (mWallet) { connect(mWallet, &KWallet::Wallet::walletOpened, this, &POP3Resource::walletOpenedForSaving); } else { finish(); } } else { finish(); } break; } case CheckRemovingMessage: { checkRemovingMessageFromServer(); break; } } } void POP3Resource::checkRemovingMessageFromServer() { const QList idToDeleteMessage = shouldDeleteId(-1); if (!idToDeleteMessage.isEmpty()) { mIdsWaitingToDelete << idToDeleteMessage; if (!mDeleteJob) { mDeleteJob = new DeleteJob(mPopSession); mDeleteJob->setDeleteIds(mIdsWaitingToDelete); mIdsWaitingToDelete.clear(); connect(mDeleteJob, &DeleteJob::result, this, &POP3Resource::deleteJobResult); mDeleteJob->start(); } } else { advanceState(Save); } } void POP3Resource::localFolderRequestJobFinished(KJob *job) { if (job->error()) { cancelSync(i18n("Error while trying to get the local inbox folder, " "aborting mail check.") + QLatin1Char('\n') + job->errorString()); return; } if (mTestLocalInbox) { KMessageBox::information(nullptr, i18n("The folder you deleted was associated with the account " "%1 which delivered mail into it. The folder the account " "delivers new mail into was reset to the main Inbox folder.", name())); } mTestLocalInbox = false; mTargetCollection = SpecialMailCollections::self()->defaultCollection(SpecialMailCollections::Inbox); Q_ASSERT(mTargetCollection.isValid()); advanceState(Precommand); } void POP3Resource::targetCollectionFetchJobFinished(KJob *job) { if (job->error()) { if (!mTestLocalInbox) { mTestLocalInbox = true; Settings::self()->setTargetCollection(-1); advanceState(FetchTargetCollection); return; } else { cancelSync(i18n("Error while trying to get the folder for incoming mail, " "aborting mail check.") + QLatin1Char('\n') + job->errorString()); mTestLocalInbox = false; return; } } mTestLocalInbox = false; Akonadi::CollectionFetchJob *fetchJob = qobject_cast(job); Q_ASSERT(fetchJob); Q_ASSERT(fetchJob->collections().size() <= 1); if (fetchJob->collections().isEmpty()) { cancelSync(i18n("Could not find folder for incoming mail, aborting mail check.")); return; } else { mTargetCollection = fetchJob->collections().at(0); advanceState(Precommand); } } void POP3Resource::precommandResult(KJob *job) { if (job->error()) { cancelSync(i18n("Error while executing precommand.") + QLatin1Char('\n') + job->errorString()); return; } else { advanceState(RequestPassword); } } void POP3Resource::loginJobResult(KJob *job) { if (job->error()) { qCDebug(POP3RESOURCE_LOG) << job->error() << job->errorText(); if (job->error() == KIO::ERR_COULD_NOT_LOGIN) { mAskAgain = true; } cancelSync(i18n("Unable to login to the server %1.", Settings::self()->host()) + QLatin1Char('\n') + job->errorString()); } else { advanceState(List); } } void POP3Resource::listJobResult(KJob *job) { if (job->error()) { cancelSync(i18n("Error while getting the list of messages on the server.") + QLatin1Char('\n') + job->errorString()); } else { ListJob *listJob = qobject_cast(job); Q_ASSERT(listJob); mIdsToSizeMap = listJob->idList(); mIdsToSaveValid = false; qCDebug(POP3RESOURCE_LOG) << "IdsToSizeMap:" << mIdsToSizeMap; advanceState(UIDList); } } void POP3Resource::uidListJobResult(KJob *job) { if (job->error()) { cancelSync(i18n("Error while getting list of unique mail identifiers from the server.") + QLatin1Char('\n') + job->errorString()); } else { UIDListJob *listJob = qobject_cast(job); Q_ASSERT(listJob); mIdsToUidsMap = listJob->uidList(); mUidsToIdsMap = listJob->idList(); qCDebug(POP3RESOURCE_LOG) << "IdToUidMap:" << mIdsToUidsMap; qCDebug(POP3RESOURCE_LOG) << "UidToIdMap:" << mUidsToIdsMap; mUidListValid = !mIdsToUidsMap.isEmpty() || mIdsToSizeMap.isEmpty(); if (Settings::self()->leaveOnServer() && !mUidListValid) { // FIXME: this needs a proper parent window KMessageBox::sorry(nullptr, i18n("Your POP3 server (Account: %1) does not support " "the UIDL command: this command is required to determine, in a reliable way, " "which of the mails on the server KMail has already seen before;\n" "the feature to leave the mails on the server will therefore not " "work properly.", name())); } advanceState(Download); } } void POP3Resource::fetchJobResult(KJob *job) { if (job->error()) { cancelSync(i18n("Error while fetching mails from the server.") + QLatin1Char('\n') + job->errorString()); return; } else { qCDebug(POP3RESOURCE_LOG) << "Downloaded" << mDownloadedIDs.size() << "mails"; if (!mIdsToDownload.isEmpty()) { qCWarning(POP3RESOURCE_LOG) << "We did not download all messages, there are still some remaining " "IDs, even though we requested their download:" << mIdsToDownload; } advanceState(Save); } } void POP3Resource::messageFinished(int messageId, KMime::Message::Ptr message) { if (mState != Download) { // This can happen if the slave does not get notified in time about the fact // that the job was killed return; } //qCDebug(POP3RESOURCE_LOG) << "Got message" << messageId // << "with subject" << message->subject()->asUnicodeString(); Akonadi::Item item; item.setMimeType(QStringLiteral("message/rfc822")); item.setPayload(message); Akonadi::Pop3ResourceAttribute *attr = item.attribute(Akonadi::Item::AddIfMissing); attr->setPop3AccountName(identifier()); Akonadi::MessageFlags::copyMessageFlags(*message, item); ItemCreateJob *itemCreateJob = new ItemCreateJob(item, mTargetCollection); mPendingCreateJobs.insert(itemCreateJob, messageId); connect(itemCreateJob, &ItemCreateJob::result, this, &POP3Resource::itemCreateJobResult); mDownloadedIDs.append(messageId); mIdsToDownload.removeAll(messageId); } void POP3Resource::messageDownloadProgress(KJob *job, KJob::Unit unit, qulonglong totalBytes) { Q_UNUSED(totalBytes); Q_UNUSED(unit); Q_ASSERT(unit == KJob::Bytes); QString statusMessage; const int totalMessages = mIdsToDownload.size() + mDownloadedIDs.size(); int bytesRemainingOnServer = 0; foreach (const QString &alreadyDownloadedUID, Settings::self()->seenUidList()) { const int alreadyDownloadedID = mUidsToIdsMap.value(alreadyDownloadedUID, -1); if (alreadyDownloadedID != -1) { bytesRemainingOnServer += mIdsToSizeMap.value(alreadyDownloadedID); } } if (Settings::self()->leaveOnServer() && bytesRemainingOnServer > 0) { statusMessage = i18n("Fetching message %1 of %2 (%3 of %4 KB) for %5 " "(%6 KB remain on the server).", mDownloadedIDs.size() + 1, totalMessages, job->processedAmount(KJob::Bytes) / 1024, job->totalAmount(KJob::Bytes) / 1024, name(), bytesRemainingOnServer / 1024); } else { statusMessage = i18n("Fetching message %1 of %2 (%3 of %4 KB) for %5", mDownloadedIDs.size() + 1, totalMessages, job->processedAmount(KJob::Bytes) / 1024, job->totalAmount(KJob::Bytes) / 1024, name()); } Q_EMIT status(Running, statusMessage); Q_EMIT percent(job->percent()); } void POP3Resource::itemCreateJobResult(KJob *job) { if (mState != Download && mState != Save) { // This can happen if the slave does not get notified in time about the fact // that the job was killed return; } ItemCreateJob *createJob = qobject_cast(job); Q_ASSERT(createJob); if (job->error()) { cancelSync(i18n("Unable to store downloaded mails.") + QLatin1Char('\n') + job->errorString()); return; } const int idOfMessageJustCreated = mPendingCreateJobs.value(createJob, -1); Q_ASSERT(idOfMessageJustCreated != -1); mPendingCreateJobs.remove(createJob); mIDsStored.append(idOfMessageJustCreated); //qCDebug(POP3RESOURCE_LOG) << "Just stored message with ID" << idOfMessageJustCreated // << "on the Akonadi server"; const QList idToDeleteMessage = shouldDeleteId(idOfMessageJustCreated); if (!idToDeleteMessage.isEmpty()) { mIdsWaitingToDelete << idToDeleteMessage; if (!mDeleteJob) { mDeleteJob = new DeleteJob(mPopSession); mDeleteJob->setDeleteIds(mIdsWaitingToDelete); mIdsWaitingToDelete.clear(); connect(mDeleteJob, &DeleteJob::result, this, &POP3Resource::deleteJobResult); mDeleteJob->start(); } } // Have all create jobs finished? Go to the next state, then if (shouldAdvanceToQuitState()) { advanceState(Quit); } } int POP3Resource::idToTime(int id) const { const QString uid = mIdsToUidsMap.value(id); if (!uid.isEmpty()) { const int index = Settings::self()->seenUidList().indexOf(uid); if (index != -1) { return Settings::self()->seenUidTimeList().at(index); } } // If we don't find any mail, either we have no UID, or it is not in the seen UID // list. In that case, we assume that the mail is new, i.e. from now return time(nullptr); } int POP3Resource::idOfOldestMessage(const QSet &idList) const { int timeOfOldestMessage = time(nullptr) + 999; int idOfOldestMessage = -1; foreach (int id, idList) { const int idTime = idToTime(id); if (idTime < timeOfOldestMessage) { timeOfOldestMessage = idTime; idOfOldestMessage = id; } } Q_ASSERT(idList.isEmpty() || idOfOldestMessage != -1); return idOfOldestMessage; } QList POP3Resource::shouldDeleteId(int downloadedId) const { QList idsToDeleteFromServer; // By default, we delete all messages. But if we have "leave on server" // rules, we can save some messages. if (Settings::self()->leaveOnServer()) { idsToDeleteFromServer = mIdsToSizeMap.keys(); if (!mIdsToSaveValid) { mIdsToSaveValid = true; mIdsToSave.clear(); const QSet idsOnServer = QSet::fromList(idsToDeleteFromServer); // If the time-limited leave rule is checked, add the newer messages to // the list of messages to keep if (Settings::self()->leaveOnServerDays() > 0) { const int secondsPerDay = 86400; time_t timeLimit = time(nullptr) - (secondsPerDay * Settings::self()->leaveOnServerDays()); foreach (int idToDelete, idsOnServer) { const int msgTime = idToTime(idToDelete); if (msgTime >= timeLimit) { mIdsToSave << idToDelete; } else { qCDebug(POP3RESOURCE_LOG) << "Message" << idToDelete << "is too old and will be deleted."; } } } // Otherwise, add all messages to the list of messages to keep - this may // be reduced in the following number-limited leave rule and size-limited // leave rule checks else { mIdsToSave = idsOnServer; } // // Delete more old messages if there are more than mLeaveOnServerCount // if (Settings::self()->leaveOnServerCount() > 0) { const int numToDelete = mIdsToSave.count() - Settings::self()->leaveOnServerCount(); if (numToDelete > 0 && numToDelete < mIdsToSave.count()) { // Get rid of the first numToDelete messages for (int i = 0; i < numToDelete; i++) { mIdsToSave.remove(idOfOldestMessage(mIdsToSave)); } } else if (numToDelete >= mIdsToSave.count()) { mIdsToSave.clear(); } } // // Delete more old messages until we're under mLeaveOnServerSize MBs // if (Settings::self()->leaveOnServerSize() > 0) { const qint64 limitInBytes = Settings::self()->leaveOnServerSize() * (1024 * 1024); qint64 sizeOnServerAfterDeletion = 0; - foreach (int id, mIdsToSave) { + for (int id : qAsConst(mIdsToSave)) { sizeOnServerAfterDeletion += mIdsToSizeMap.value(id); } while (sizeOnServerAfterDeletion > limitInBytes) { int oldestId = idOfOldestMessage(mIdsToSave); mIdsToSave.remove(oldestId); sizeOnServerAfterDeletion -= mIdsToSizeMap.value(oldestId); } } } // Now save the messages from deletion // foreach (int idToSave, mIdsToSave) { idsToDeleteFromServer.removeAll(idToSave); } if (downloadedId != -1 && !mIdsToSave.contains(downloadedId)) { idsToDeleteFromServer << downloadedId; } } else { if (downloadedId != -1) { idsToDeleteFromServer << downloadedId; } } return idsToDeleteFromServer; } void POP3Resource::deleteJobResult(KJob *job) { if (job->error()) { cancelSync(i18n("Failed to delete the messages from the server.") + QLatin1Char('\n') + job->errorString()); return; } DeleteJob *finishedDeleteJob = qobject_cast(job); Q_ASSERT(finishedDeleteJob); Q_ASSERT(finishedDeleteJob == mDeleteJob); mDeletedIDs = finishedDeleteJob->deletedIDs(); // Remove all deleted messages from the list of already downloaded messages, // as it is no longer necessary to store them (they just waste space) QList seenUIDs = Settings::self()->seenUidList(); QList timeOfSeenUids = Settings::self()->seenUidTimeList(); Q_ASSERT(seenUIDs.size() == timeOfSeenUids.size()); foreach (int deletedId, mDeletedIDs) { const QString deletedUID = mIdsToUidsMap.value(deletedId); if (!deletedUID.isEmpty()) { int index = seenUIDs.indexOf(deletedUID); if (index != -1) { // TEST qCDebug(POP3RESOURCE_LOG) << "Removing UID" << deletedUID << "from the seen UID list, as it was deleted."; seenUIDs.removeAt(index); timeOfSeenUids.removeAt(index); } } } Settings::self()->setSeenUidList(seenUIDs); Settings::self()->setSeenUidTimeList(timeOfSeenUids); Settings::self()->save(); mDeleteJob = nullptr; if (!mIdsWaitingToDelete.isEmpty()) { mDeleteJob = new DeleteJob(mPopSession); mDeleteJob->setDeleteIds(mIdsWaitingToDelete); mIdsWaitingToDelete.clear(); connect(mDeleteJob, &DeleteJob::result, this, &POP3Resource::deleteJobResult); mDeleteJob->start(); } if (shouldAdvanceToQuitState()) { advanceState(Quit); } } void POP3Resource::finish() { qCDebug(POP3RESOURCE_LOG) << "================= Mail check finished. ============================="; saveSeenUIDList(); if (!mIntervalCheckInProgress) { collectionsRetrieved(Akonadi::Collection::List()); } if (mDownloadedIDs.isEmpty()) { Q_EMIT status(Idle, i18n("Finished mail check, no message downloaded.")); } else Q_EMIT status(Idle, i18np("Finished mail check, 1 message downloaded.", "Finished mail check, %1 messages downloaded.", mDownloadedIDs.size())); resetState(); } bool POP3Resource::shouldAdvanceToQuitState() const { return mState == Save && mPendingCreateJobs.isEmpty() && mIdsWaitingToDelete.isEmpty() && !mDeleteJob; } void POP3Resource::quitJobResult(KJob *job) { if (job->error()) { cancelSync(i18n("Unable to complete the mail fetch.") + QLatin1Char('\n') + job->errorString()); return; } advanceState(SavePassword); } void POP3Resource::slotSessionError(int errorCode, const QString &errorMessage) { qCWarning(POP3RESOURCE_LOG) << "Error in our session, unrelated to a currently running job!"; cancelSync(KIO::buildErrorString(errorCode, errorMessage)); } void POP3Resource::saveSeenUIDList() { QList seenUIDs = Settings::self()->seenUidList(); QList timeOfSeenUIDs = Settings::self()->seenUidTimeList(); // // Find the messages that we have successfully stored, but did not actually get // deleted. // Those messages, we have to remember, so we don't download them again. // QList idsOfMessagesDownloadedButNotDeleted = mIDsStored; foreach (int deletedId, mDeletedIDs) { idsOfMessagesDownloadedButNotDeleted.removeAll(deletedId); } QList uidsOfMessagesDownloadedButNotDeleted; foreach (int id, idsOfMessagesDownloadedButNotDeleted) { const QString uid = mIdsToUidsMap.value(id); if (!uid.isEmpty()) { uidsOfMessagesDownloadedButNotDeleted.append(uid); } } Q_ASSERT(seenUIDs.size() == timeOfSeenUIDs.size()); foreach (const QString &uid, uidsOfMessagesDownloadedButNotDeleted) { if (!seenUIDs.contains(uid)) { seenUIDs.append(uid); timeOfSeenUIDs.append(time(nullptr)); } } // // If we have a valid UID list from the server, delete those UIDs that are in // the seenUidList but are not on the server. // This can happen if someone else than this resource deleted the mails from the // server which we kept here. // if (mUidListValid) { QList::iterator uidIt = seenUIDs.begin(); QList::iterator timeIt = timeOfSeenUIDs.begin(); while (uidIt != seenUIDs.end()) { const QString curSeenUID = *uidIt; if (!mUidsToIdsMap.contains(curSeenUID)) { // Ok, we have a UID in the seen UID list that is not anymore on the server. // Therefore remove it from the seen UID list, it is not needed there anymore, // it just wastes space. uidIt = seenUIDs.erase(uidIt); timeIt = timeOfSeenUIDs.erase(timeIt); } else { ++uidIt; ++timeIt; } } } else { qCWarning(POP3RESOURCE_LOG) << "UID list from server is not valid."; } // // Now save it in the settings // qCDebug(POP3RESOURCE_LOG) << "The seen UID list has" << seenUIDs.size() << "entries"; Settings::self()->setSeenUidList(seenUIDs); Settings::self()->setSeenUidTimeList(timeOfSeenUIDs); Settings::self()->save(); } void POP3Resource::cancelSync(const QString &errorMessage, bool error) { if (error) { cancelTask(errorMessage); qCWarning(POP3RESOURCE_LOG) << "============== ERROR DURING POP3 SYNC =========================="; qCWarning(POP3RESOURCE_LOG) << errorMessage; KMessageBox::error(nullptr, errorMessage); } else { qCDebug(POP3RESOURCE_LOG) << "Canceled the sync, but no error."; cancelTask(); } saveSeenUIDList(); resetState(); } void POP3Resource::resetState() { mState = Idle; mTargetCollection = Collection(-1); mIdsToSizeMap.clear(); mIdsToUidsMap.clear(); mUidsToIdsMap.clear(); mDownloadedIDs.clear(); mIdsToDownload.clear(); mPendingCreateJobs.clear(); mIDsStored.clear(); mDeletedIDs.clear(); mIdsWaitingToDelete.clear(); if (mDeleteJob) { mDeleteJob->deleteLater(); mDeleteJob = nullptr; } mUidListValid = false; mIntervalCheckInProgress = false; mSavePassword = false; updateIntervalTimer(); delete mWallet; mWallet = nullptr; if (mPopSession) { // Closing the POP session means the KIO slave will get disconnected, which // automatically issues the QUIT command. // Delete the POP session later, otherwise the scheduler makes us crash mPopSession->abortCurrentJob(); mPopSession->deleteLater(); mPopSession = nullptr; } } void POP3Resource::startMailCheck() { resetState(); mIntervalTimer->stop(); Q_EMIT percent(0); // Otherwise the value from the last sync is taken advanceState(FetchTargetCollection); } void POP3Resource::retrieveCollections() { if (mState == Idle) { startMailCheck(); } else { cancelSync( i18n("Mail check already in progress, unable to start a second check.")); } } void POP3Resource::clearCachedPassword() { mPassword.clear(); } void POP3Resource::cleanup() { if (mWallet && mWallet->isOpen() && mWallet->hasFolder(QStringLiteral("pop3"))) { mWallet->setFolder(QStringLiteral("pop3")); if (mWallet->hasEntry(identifier())) { mWallet->removeEntry(identifier()); } } Akonadi::AgentBase::cleanup(); } void POP3Resource::doSetOnline(bool online) { ResourceBase::doSetOnline(online); if (online) { Q_EMIT status(Idle, i18n("Ready")); } else { if (mState != Idle) { cancelSync(i18n("Mail check aborted after going offline."), false /* no error */); } Q_EMIT status(Idle, i18n("Offline")); delete mWallet; mWallet = nullptr; clearCachedPassword(); } } AKONADI_RESOURCE_MAIN(POP3Resource) diff --git a/resources/shared/singlefileresource/collectionflagsattribute.cpp b/resources/shared/singlefileresource/collectionflagsattribute.cpp index 63b75c6c3..a09572970 100644 --- a/resources/shared/singlefileresource/collectionflagsattribute.cpp +++ b/resources/shared/singlefileresource/collectionflagsattribute.cpp @@ -1,68 +1,68 @@ /* Copyright (C) 2008 Omat Holding B.V. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "collectionflagsattribute.h" - +#include "helper_p.h" #include #include using namespace Akonadi; CollectionFlagsAttribute::CollectionFlagsAttribute(const QList &flags) : mFlags(flags) { } void CollectionFlagsAttribute::setFlags(const QList &flags) { mFlags = flags; } QList CollectionFlagsAttribute::flags() const { return mFlags; } QByteArray CollectionFlagsAttribute::type() const { static const QByteArray sType("collectionflags"); return sType; } Akonadi::Attribute *CollectionFlagsAttribute::clone() const { return new CollectionFlagsAttribute(mFlags); } QByteArray CollectionFlagsAttribute::serialized() const { QByteArray result; - foreach (const QByteArray &flag, mFlags) { + for (const QByteArray &flag : qAsConst(mFlags)) { result += flag + ' '; } result.chop(1); return result; } void CollectionFlagsAttribute::deserialize(const QByteArray &data) { mFlags = data.split(' '); }