pop3resource.cpp 35.8 KB
Newer Older
Laurent Montel's avatar
Laurent Montel committed
1
/*
2
   SPDX-FileCopyrightText: 2010 Thomas McGuire <mcguire@kde.org>
Thomas McGuire's avatar
Thomas McGuire committed
3

4
   SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
Thomas McGuire's avatar
Thomas McGuire committed
5
*/
6

Thomas McGuire's avatar
Thomas McGuire committed
7
#include "pop3resource.h"
8
#include "jobs.h"
9

Laurent Montel's avatar
Laurent Montel committed
10
11
12
#include <CollectionFetchJob>
#include <ItemCreateJob>
#include <AttributeFactory>
13
14
15
#include <Akonadi/KMime/MessageFlags>
#include <Akonadi/KMime/SpecialMailCollectionsRequestJob>
#include <Akonadi/KMime/SpecialMailCollections>
16
#include <Akonadi/KMime/Pop3ResourceAttribute>
Laurent Montel's avatar
Laurent Montel committed
17
#include <kmime/kmime_util.h>
Laurent Montel's avatar
Laurent Montel committed
18
19
#include <MailTransport/PrecommandJob>
#include <MailTransport/Transport>
Thomas McGuire's avatar
Thomas McGuire committed
20

Laurent Montel's avatar
Laurent Montel committed
21
22
#include <kio/global.h>
#include <kio/job.h>
23
#include <KPasswordDialog>
Thomas McGuire's avatar
Thomas McGuire committed
24
#include <KMessageBox>
25
#include <KNotification>
Laurent Montel's avatar
Laurent Montel committed
26
#include "pop3resource_debug.h"
Thomas McGuire's avatar
Thomas McGuire committed
27

28
#include <QTimer>
Laurent Montel's avatar
Laurent Montel committed
29
30
#include <qt5keychain/keychain.h>
using namespace QKeychain;
31
using namespace Akonadi;
32
using namespace MailTransport;
Thomas McGuire's avatar
Thomas McGuire committed
33

Laurent Montel's avatar
Laurent Montel committed
34
POP3Resource::POP3Resource(const QString &id)
Laurent Montel's avatar
Laurent Montel committed
35
36
37
    : ResourceBase(id)
    , mState(Idle)
    , mIntervalTimer(new QTimer(this))
38
    , mSettings(KSharedConfig::openConfig())
Laurent Montel's avatar
Laurent Montel committed
39
{
Laurent Montel's avatar
Laurent Montel committed
40
    Akonadi::AttributeFactory::registerAttribute<Akonadi::Pop3ResourceAttribute>();
Laurent Montel's avatar
Laurent Montel committed
41
    setNeedsNetwork(true);
42
43
    mSettings.setResourceId(identifier());
    if (mSettings.name().isEmpty()) {
44
        if (name() == identifier()) {
45
            mSettings.setName(i18n("POP3 Account"));
46
        } else {
47
            mSettings.setName(name());
48
49
        }
    }
50
    setName(mSettings.name());
Laurent Montel's avatar
Laurent Montel committed
51
52
    resetState();

Laurent Montel's avatar
Laurent Montel committed
53
    connect(this, &POP3Resource::abortRequested, this, &POP3Resource::slotAbortRequested);
54
55
    connect(mIntervalTimer, &QTimer::timeout,
            this, &POP3Resource::intervalCheckTriggered);
Laurent Montel's avatar
Laurent Montel committed
56
    connect(this, &POP3Resource::reloadConfiguration, this, &POP3Resource::configurationChanged);
57
58
59
60
}

POP3Resource::~POP3Resource()
{
61
    mSettings.save();
62
63
}

Volker Krause's avatar
Volker Krause committed
64
65
void POP3Resource::configurationChanged()
{
Laurent Montel's avatar
Laurent Montel committed
66
    updateIntervalTimer();
67
    mPassword.clear();
Volker Krause's avatar
Volker Krause committed
68
69
}

70
71
void POP3Resource::updateIntervalTimer()
{
72
73
    if (mSettings.intervalCheckEnabled() && mState == Idle) {
        mIntervalTimer->start(mSettings.intervalCheckInterval() * 1000 * 60);
Laurent Montel's avatar
Laurent Montel committed
74
75
76
    } else {
        mIntervalTimer->stop();
    }
77
78
79
80
}

void POP3Resource::intervalCheckTriggered()
{
Laurent Montel's avatar
Laurent Montel committed
81
82
    Q_ASSERT(mState == Idle);
    if (isOnline()) {
Laurent Montel's avatar
Laurent Montel committed
83
        qCDebug(POP3RESOURCE_LOG) << "Starting interval mail check.";
Laurent Montel's avatar
Laurent Montel committed
84
85
86
87
88
        startMailCheck();
        mIntervalCheckInProgress = true;
    } else {
        mIntervalTimer->start();
    }
89
90
}

91
92
void POP3Resource::aboutToQuit()
{
Laurent Montel's avatar
Laurent Montel committed
93
94
95
    if (mState != Idle) {
        cancelSync(i18n("Mail check aborted."));
    }
96
97
98
99
}

void POP3Resource::slotAbortRequested()
{
Laurent Montel's avatar
Laurent Montel committed
100
101
102
    if (mState != Idle) {
        cancelSync(i18n("Mail check was canceled manually."), false /* no error */);
    }
103
104
}

Laurent Montel's avatar
Laurent Montel committed
105
void POP3Resource::retrieveItems(const Akonadi::Collection &collection)
106
{
Laurent Montel's avatar
Laurent Montel committed
107
    Q_UNUSED(collection)
Laurent Montel's avatar
Laurent Montel committed
108
    qCWarning(POP3RESOURCE_LOG) << "This should never be called, we don't have a collection!";
109
110
}

Laurent Montel's avatar
Laurent Montel committed
111
bool POP3Resource::retrieveItem(const Akonadi::Item &item, const QSet<QByteArray> &parts)
112
{
Laurent Montel's avatar
Laurent Montel committed
113
114
    Q_UNUSED(item)
    Q_UNUSED(parts)
Laurent Montel's avatar
Laurent Montel committed
115
    qCWarning(POP3RESOURCE_LOG) << "This should never be called, we don't have any item!";
Laurent Montel's avatar
Laurent Montel committed
116
    return false;
117
118
}

Laurent Montel's avatar
Laurent Montel committed
119
QString POP3Resource::buildLabelForPasswordDialog(const QString &detailedError) const
120
{
Laurent Montel's avatar
Laurent Montel committed
121
    const QString queryText = i18n("Please enter the username and password for account '%1'.",
Laurent Montel's avatar
Laurent Montel committed
122
                                   agentName()) + QLatin1String("<br>") + detailedError;
Laurent Montel's avatar
Laurent Montel committed
123
    return queryText;
124
125
}

Laurent Montel's avatar
Laurent Montel committed
126
void POP3Resource::walletOpenedForLoading(QKeychain::Job *baseJob)
127
{
Laurent Montel's avatar
Laurent Montel committed
128
129
130
131
132
133
134
135
136
    auto *job = qobject_cast<ReadPasswordJob *>(baseJob);
    bool passwordLoaded = false;
    Q_ASSERT(job);
    if (!job->error()) {
        mPassword = job->textData();
        passwordLoaded = true;
    } else {
        passwordLoaded = false;
        qCWarning(POP3RESOURCE_LOG) << "We have an error during reading password " << job->errorString();
137
    }
Laurent Montel's avatar
Laurent Montel committed
138
    if (!passwordLoaded) {
Laurent Montel's avatar
Laurent Montel committed
139
        const QString queryText = buildLabelForPasswordDialog(
Laurent Montel's avatar
Laurent Montel committed
140
            i18n("You are asked here because the password could not be loaded from the wallet."));
Laurent Montel's avatar
Laurent Montel committed
141
142
143
        showPasswordDialog(queryText);
    } else {
        advanceState(Connect);
144
145
146
    }
}

Laurent Montel's avatar
Laurent Montel committed
147
148
void POP3Resource::showPasswordDialog(const QString &queryText)
{
Laurent Montel's avatar
Laurent Montel committed
149
150
    QPointer<KPasswordDialog> dlg
        = new KPasswordDialog(
Laurent Montel's avatar
Laurent Montel committed
151
152
              nullptr,
              KPasswordDialog::ShowUsernameLine);
Laurent Montel's avatar
Laurent Montel committed
153
    dlg->setModal(true);
154
    dlg->setUsername(mSettings.login());
Laurent Montel's avatar
Laurent Montel committed
155
156
157
158
159
160
161
162
    dlg->setPassword(mPassword);
    dlg->setPrompt(queryText);
    dlg->setWindowTitle(name());
    dlg->addCommentLine(i18n("Account:"), name());

    bool gotIt = false;
    if (dlg->exec()) {
        mPassword = dlg->password();
163
164
        mSettings.setLogin(dlg->username());
        mSettings.save();
Laurent Montel's avatar
Laurent Montel committed
165
166
167
        if (!dlg->password().isEmpty()) {
            mSavePassword = true;
        }
168

Laurent Montel's avatar
Laurent Montel committed
169
170
171
172
173
174
175
        mAskAgain = false;
        advanceState(Connect);
        gotIt = true;
    }
    delete dlg;
    if (!gotIt) {
        cancelSync(i18n("No username and password supplied."));
176
177
178
    }
}

Laurent Montel's avatar
Laurent Montel committed
179
void POP3Resource::advanceState(State nextState)
180
{
Laurent Montel's avatar
Laurent Montel committed
181
182
    mState = nextState;
    doStateStep();
183
184
}

185
186
void POP3Resource::doStateStep()
{
Laurent Montel's avatar
Laurent Montel committed
187
    switch (mState) {
Laurent Montel's avatar
Laurent Montel committed
188
    case Idle:
Laurent Montel's avatar
Laurent Montel committed
189
        Q_ASSERT(false);
Laurent Montel's avatar
Laurent Montel committed
190
        qCWarning(POP3RESOURCE_LOG) << "State machine should not be called in idle state!";
Laurent Montel's avatar
Laurent Montel committed
191
        break;
Laurent Montel's avatar
Laurent Montel committed
192
193
    case FetchTargetCollection:
    {
Laurent Montel's avatar
Laurent Montel committed
194
        qCDebug(POP3RESOURCE_LOG) << "================ Starting state FetchTargetCollection ==========";
Laurent Montel's avatar
Laurent Montel committed
195
        Q_EMIT status(Running, i18n("Preparing transmission from \"%1\".", name()));
196
        Collection targetCollection(mSettings.targetCollection());
Laurent Montel's avatar
Laurent Montel committed
197
        if (targetCollection.isValid()) {
Laurent Montel's avatar
Laurent Montel committed
198
            auto *fetchJob = new CollectionFetchJob(targetCollection,
Laurent Montel's avatar
Laurent Montel committed
199
                                                                  CollectionFetchJob::Base);
Laurent Montel's avatar
Laurent Montel committed
200
201
202
            fetchJob->start();
            connect(fetchJob, &CollectionFetchJob::result, this, &POP3Resource::targetCollectionFetchJobFinished);
        } else {
Laurent Montel's avatar
Laurent Montel committed
203
            // No target collection set in the config? Try requesting a default inbox
Laurent Montel's avatar
Laurent Montel committed
204
            auto *requestJob = new SpecialMailCollectionsRequestJob(this);
Laurent Montel's avatar
Laurent Montel committed
205
206
            requestJob->requestDefaultCollection(SpecialMailCollections::Inbox);
            requestJob->start();
Laurent Montel's avatar
Laurent Montel committed
207
            connect(requestJob, &SpecialMailCollectionsRequestJob::result, this, &POP3Resource::localFolderRequestJobFinished);
Laurent Montel's avatar
Laurent Montel committed
208
209
        }
        break;
210
    }
Laurent Montel's avatar
Laurent Montel committed
211
    case Precommand:
Laurent Montel's avatar
Laurent Montel committed
212
        qCDebug(POP3RESOURCE_LOG) << "================ Starting state Precommand =====================";
213
214
        if (!mSettings.precommand().isEmpty()) {
            PrecommandJob *precommandJob = new PrecommandJob(mSettings.precommand(), this);
Laurent Montel's avatar
Laurent Montel committed
215
            connect(precommandJob, &PrecommandJob::result, this, &POP3Resource::precommandResult);
Laurent Montel's avatar
Laurent Montel committed
216
            precommandJob->start();
Laurent Montel's avatar
Laurent Montel committed
217
            Q_EMIT status(Running, i18n("Executing precommand."));
Laurent Montel's avatar
Laurent Montel committed
218
219
220
221
        } else {
            advanceState(RequestPassword);
        }
        break;
Laurent Montel's avatar
Laurent Montel committed
222
223
    case RequestPassword:
    {
Laurent Montel's avatar
Laurent Montel committed
224
        qCDebug(POP3RESOURCE_LOG) << "================ Starting state RequestPassword ================";
Laurent Montel's avatar
Laurent Montel committed
225
226

        // Don't show any wallet or password prompts when we are unit-testing
227
228
        if (!mSettings.unitTestPassword().isEmpty()) {
            mPassword = mSettings.unitTestPassword();
Laurent Montel's avatar
Laurent Montel committed
229
230
231
232
            advanceState(Connect);
            break;
        }

233
234
        const bool passwordNeeded = mSettings.authenticationMethod() != MailTransport::Transport::EnumAuthenticationType::GSSAPI;
        const bool loadPasswordFromWallet = !mAskAgain && passwordNeeded && !mSettings.login().isEmpty()
Laurent Montel's avatar
Laurent Montel committed
235
                                            && mPassword.isEmpty();
Laurent Montel's avatar
Laurent Montel committed
236
        if (loadPasswordFromWallet) {
Laurent Montel's avatar
Laurent Montel committed
237
238
239
240
            auto readJob = new ReadPasswordJob(QStringLiteral("pop3"), this);
            connect(readJob, &QKeychain::Job::finished, this, &POP3Resource::walletOpenedForLoading);
            readJob->setKey(identifier());
            readJob->start();
Laurent Montel's avatar
Laurent Montel committed
241
242
243
244
        } else if (passwordNeeded && (mPassword.isEmpty() || mAskAgain)) {
            QString detail;
            if (mAskAgain) {
                detail = i18n("You are asked here because the previous login was not successful.");
245
            } else if (mSettings.login().isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
246
247
248
249
250
251
252
253
254
                detail = i18n("You are asked here because the username you supplied is empty.");
            }

            showPasswordDialog(buildLabelForPasswordDialog(detail));
        } else {
            // No password needed or using previous password, go on with Connect
            advanceState(Connect);
        }

255
        break;
256
    }
Laurent Montel's avatar
Laurent Montel committed
257
    case Connect:
Laurent Montel's avatar
Laurent Montel committed
258
        qCDebug(POP3RESOURCE_LOG) << "================ Starting state Connect ========================";
Laurent Montel's avatar
Laurent Montel committed
259
        Q_ASSERT(!mPopSession);
260
        mPopSession = new POPSession(mSettings, mPassword);
Laurent Montel's avatar
Laurent Montel committed
261
        connect(mPopSession, &POPSession::slaveError, this, &POP3Resource::slotSessionError);
Laurent Montel's avatar
Laurent Montel committed
262
263
        advanceState(Login);
        break;
Laurent Montel's avatar
Laurent Montel committed
264
265
    case Login:
    {
Laurent Montel's avatar
Laurent Montel committed
266
        qCDebug(POP3RESOURCE_LOG) << "================ Starting state Login ==========================";
Laurent Montel's avatar
Laurent Montel committed
267

Laurent Montel's avatar
Laurent Montel committed
268
        auto *loginJob = new LoginJob(mPopSession);
Laurent Montel's avatar
Laurent Montel committed
269
        connect(loginJob, &LoginJob::result, this, &POP3Resource::loginJobResult);
Laurent Montel's avatar
Laurent Montel committed
270
271
        loginJob->start();
        break;
272
    }
Laurent Montel's avatar
Laurent Montel committed
273
274
    case List:
    {
Laurent Montel's avatar
Laurent Montel committed
275
        qCDebug(POP3RESOURCE_LOG) << "================ Starting state List ===========================";
Laurent Montel's avatar
Laurent Montel committed
276
        Q_EMIT status(Running, i18n("Fetching mail listing."));
Laurent Montel's avatar
Laurent Montel committed
277
        auto *listJob = new ListJob(mPopSession);
Laurent Montel's avatar
Laurent Montel committed
278
        connect(listJob, &ListJob::result, this, &POP3Resource::listJobResult);
Laurent Montel's avatar
Laurent Montel committed
279
        listJob->start();
Laurent Montel's avatar
Laurent Montel committed
280
        break;
281
    }
Laurent Montel's avatar
Laurent Montel committed
282
283
    case UIDList:
    {
Laurent Montel's avatar
Laurent Montel committed
284
        qCDebug(POP3RESOURCE_LOG) << "================ Starting state UIDList ========================";
Laurent Montel's avatar
Laurent Montel committed
285
        auto *uidListJob = new UIDListJob(mPopSession);
Laurent Montel's avatar
Laurent Montel committed
286
        connect(uidListJob, &UIDListJob::result, this, &POP3Resource::uidListJobResult);
Laurent Montel's avatar
Laurent Montel committed
287
        uidListJob->start();
Laurent Montel's avatar
Laurent Montel committed
288
        break;
289
    }
Laurent Montel's avatar
Laurent Montel committed
290
291
    case Download:
    {
Laurent Montel's avatar
Laurent Montel committed
292
        qCDebug(POP3RESOURCE_LOG) << "================ Starting state Download =======================";
Laurent Montel's avatar
Laurent Montel committed
293
294
295
296
297

        // 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<int> idsToDownload = mIdsToSizeMap.keys();
298
        const QStringList alreadyDownloadedUIDs = mSettings.seenUidList();
299
        foreach (const QString &uidOnServer, mIdsToUidsMap) {
Laurent Montel's avatar
Laurent Montel committed
300
301
302
303
304
            if (alreadyDownloadedUIDs.contains(uidOnServer)) {
                const int idOfUIDOnServer = mUidsToIdsMap.value(uidOnServer, -1);
                Q_ASSERT(idOfUIDOnServer != -1);
                idsToDownload.removeAll(idOfUIDOnServer);
            }
305
        }
Laurent Montel's avatar
Laurent Montel committed
306
        mIdsToDownload = idsToDownload;
Laurent Montel's avatar
Laurent Montel committed
307
        qCDebug(POP3RESOURCE_LOG) << "We are going to download" << mIdsToDownload.size() << "messages";
Laurent Montel's avatar
Laurent Montel committed
308
309
310
311

        // For proper progress, the job needs to know the sizes of the messages, so
        // put them into a list here
        QList<int> sizesOfMessagesToDownload;
312
        sizesOfMessagesToDownload.reserve(idsToDownload.count());
Laurent Montel's avatar
Laurent Montel committed
313
314
315
316
        foreach (int id, idsToDownload) {
            sizesOfMessagesToDownload.append(mIdsToSizeMap.value(id));
        }

Laurent Montel's avatar
Laurent Montel committed
317
318
319
        if (mIdsToDownload.empty()) {
            advanceState(CheckRemovingMessage);
        } else {
Laurent Montel's avatar
Laurent Montel committed
320
            auto *fetchJob = new FetchJob(mPopSession);
321
322
323
            fetchJob->setFetchIds(idsToDownload, sizesOfMessagesToDownload);
            connect(fetchJob, &FetchJob::result, this, &POP3Resource::fetchJobResult);
            connect(fetchJob, &FetchJob::messageFinished, this, &POP3Resource::messageFinished);
Laurent Montel's avatar
Laurent Montel committed
324
            //TODO wait kf6. For the moment we can't convert to new connect api.
Laurent Montel's avatar
Laurent Montel committed
325
326
            connect(fetchJob, SIGNAL(processedAmount(KJob*,KJob::Unit,qulonglong)),
                    this, SLOT(messageDownloadProgress(KJob*,KJob::Unit,qulonglong)));
Laurent Montel's avatar
Laurent Montel committed
327

328
329
            fetchJob->start();
        }
Laurent Montel's avatar
Laurent Montel committed
330
        break;
331
    }
Laurent Montel's avatar
Laurent Montel committed
332
    case Save:
Laurent Montel's avatar
Laurent Montel committed
333
334
        qCDebug(POP3RESOURCE_LOG) << "================ Starting state Save ===========================";
        qCDebug(POP3RESOURCE_LOG) << mPendingCreateJobs.size() << "item create jobs are pending";
Laurent Montel's avatar
Laurent Montel committed
335
        if (!mPendingCreateJobs.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
336
            Q_EMIT status(Running, i18n("Saving downloaded messages."));
Laurent Montel's avatar
Laurent Montel committed
337
338
        }

339
        if (shouldAdvanceToQuitState()) {
Laurent Montel's avatar
Laurent Montel committed
340
341
            advanceState(Quit);
        }
Laurent Montel's avatar
Laurent Montel committed
342
343
344
        break;
    case Quit:
    {
Laurent Montel's avatar
Laurent Montel committed
345
        qCDebug(POP3RESOURCE_LOG) << "================ Starting state Quit ===========================";
Laurent Montel's avatar
Laurent Montel committed
346
        auto *quitJob = new QuitJob(mPopSession);
Laurent Montel's avatar
Laurent Montel committed
347
        connect(quitJob, &QuitJob::result, this, &POP3Resource::quitJobResult);
Laurent Montel's avatar
Laurent Montel committed
348
        quitJob->start();
Laurent Montel's avatar
Laurent Montel committed
349
        break;
350
    }
Laurent Montel's avatar
Laurent Montel committed
351
    case SavePassword:
Laurent Montel's avatar
Laurent Montel committed
352
        qCDebug(POP3RESOURCE_LOG) << "================ Starting state SavePassword ===================";
Laurent Montel's avatar
Laurent Montel committed
353
        if (mSavePassword) {
Laurent Montel's avatar
Laurent Montel committed
354
            qCDebug(POP3RESOURCE_LOG) << "Writing password back to the wallet.";
Laurent Montel's avatar
Laurent Montel committed
355
            Q_EMIT status(Running, i18n("Saving password to the wallet."));
Laurent Montel's avatar
Laurent Montel committed
356
357
358
359
360
            auto writeJob = new WritePasswordJob(QStringLiteral("pop3"), this);
            connect(writeJob, &QKeychain::Job::finished, this, [this](QKeychain::Job *baseJob) {
                if (baseJob->error()) {
                    qCWarning(POP3RESOURCE_LOG) << "Error writing password using QKeychain:" << baseJob->errorString();
                }
Laurent Montel's avatar
Laurent Montel committed
361
                finish();
Laurent Montel's avatar
Laurent Montel committed
362
363
364
365
            });
            writeJob->setKey(identifier());
            writeJob->setTextData(mPassword);
            writeJob->start();
Laurent Montel's avatar
Laurent Montel committed
366
367
        } else {
            finish();
368
        }
Laurent Montel's avatar
Laurent Montel committed
369
        break;
Laurent Montel's avatar
Laurent Montel committed
370
    case CheckRemovingMessage:
371
        qCDebug(POP3RESOURCE_LOG) << "================ Starting state CheckRemovingMessage ===================";
372
        checkRemovingMessageFromServer();
373
374
        break;
    }
375
376
}

377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
void POP3Resource::checkRemovingMessageFromServer()
{
    const QList<int> 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);
    }
}

Laurent Montel's avatar
Laurent Montel committed
394
void POP3Resource::localFolderRequestJobFinished(KJob *job)
395
{
Laurent Montel's avatar
Laurent Montel committed
396
397
398
399
400
401
    if (job->error()) {
        cancelSync(i18n("Error while trying to get the local inbox folder, "
                        "aborting mail check.") + QLatin1Char('\n') + job->errorString());
        return;
    }
    if (mTestLocalInbox) {
Laurent Montel's avatar
Laurent Montel committed
402
        KMessageBox::information(nullptr,
Laurent Montel's avatar
Laurent Montel committed
403
404
405
406
407
408
409
410
411
                                 i18n("<qt>The folder you deleted was associated with the account "
                                      "<b>%1</b> which delivered mail into it. The folder the account "
                                      "delivers new mail into was reset to the main Inbox folder.</qt>", name()));
    }
    mTestLocalInbox = false;

    mTargetCollection = SpecialMailCollections::self()->defaultCollection(SpecialMailCollections::Inbox);
    Q_ASSERT(mTargetCollection.isValid());
    advanceState(Precommand);
412
413
}

Laurent Montel's avatar
Laurent Montel committed
414
void POP3Resource::targetCollectionFetchJobFinished(KJob *job)
415
{
Laurent Montel's avatar
Laurent Montel committed
416
417
418
    if (job->error()) {
        if (!mTestLocalInbox) {
            mTestLocalInbox = true;
419
            mSettings.setTargetCollection(-1);
Laurent Montel's avatar
Laurent Montel committed
420
421
422
423
424
425
426
427
428
429
            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;
Laurent Montel's avatar
Laurent Montel committed
430
    auto *fetchJob
Laurent Montel's avatar
Laurent Montel committed
431
        = qobject_cast<Akonadi::CollectionFetchJob *>(job);
Laurent Montel's avatar
Laurent Montel committed
432
433
434
435
436
437
    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;
438
    } else {
439
        mTargetCollection = fetchJob->collections().at(0);
Laurent Montel's avatar
Laurent Montel committed
440
        advanceState(Precommand);
441
    }
442
443
}

Laurent Montel's avatar
Laurent Montel committed
444
void POP3Resource::precommandResult(KJob *job)
445
{
Laurent Montel's avatar
Laurent Montel committed
446
    if (job->error()) {
Laurent Montel's avatar
Laurent Montel committed
447
448
        cancelSync(i18n("Error while executing precommand.")
                   +QLatin1Char('\n') + job->errorString());
Laurent Montel's avatar
Laurent Montel committed
449
450
451
452
        return;
    } else {
        advanceState(RequestPassword);
    }
453
454
}

Laurent Montel's avatar
Laurent Montel committed
455
void POP3Resource::loginJobResult(KJob *job)
456
{
Laurent Montel's avatar
Laurent Montel committed
457
    if (job->error()) {
Laurent Montel's avatar
Laurent Montel committed
458
        qCDebug(POP3RESOURCE_LOG) << job->error() << job->errorText();
459
        if (job->error() == KIO::ERR_CANNOT_LOGIN) {
Laurent Montel's avatar
Laurent Montel committed
460
461
            mAskAgain = true;
        }
462
        cancelSync(i18n("Unable to login to the server \"%1\".", mSettings.host())
Laurent Montel's avatar
Laurent Montel committed
463
                   +QLatin1Char('\n') + job->errorString());
Laurent Montel's avatar
Laurent Montel committed
464
465
466
    } else {
        advanceState(List);
    }
467
468
}

Laurent Montel's avatar
Laurent Montel committed
469
void POP3Resource::listJobResult(KJob *job)
470
{
Laurent Montel's avatar
Laurent Montel committed
471
    if (job->error()) {
Laurent Montel's avatar
Laurent Montel committed
472
473
        cancelSync(i18n("Error while getting the list of messages on the server.")
                   +QLatin1Char('\n') + job->errorString());
Laurent Montel's avatar
Laurent Montel committed
474
    } else {
Laurent Montel's avatar
Laurent Montel committed
475
        auto *listJob = qobject_cast<ListJob *>(job);
Laurent Montel's avatar
Laurent Montel committed
476
477
        Q_ASSERT(listJob);
        mIdsToSizeMap = listJob->idList();
Laurent Montel's avatar
Laurent Montel committed
478
        mIdsToSaveValid = false;
479
        qCDebug(POP3RESOURCE_LOG) << "IdsToSizeMap size" << mIdsToSizeMap.size();
Laurent Montel's avatar
Laurent Montel committed
480
481
        advanceState(UIDList);
    }
482
483
}

Laurent Montel's avatar
Laurent Montel committed
484
void POP3Resource::uidListJobResult(KJob *job)
485
{
Laurent Montel's avatar
Laurent Montel committed
486
    if (job->error()) {
Laurent Montel's avatar
Laurent Montel committed
487
488
        cancelSync(i18n("Error while getting list of unique mail identifiers from the server.")
                   +QLatin1Char('\n') + job->errorString());
Laurent Montel's avatar
Laurent Montel committed
489
    } else {
Laurent Montel's avatar
Laurent Montel committed
490
        auto *listJob = qobject_cast<UIDListJob *>(job);
Laurent Montel's avatar
Laurent Montel committed
491
492
493
        Q_ASSERT(listJob);
        mIdsToUidsMap = listJob->uidList();
        mUidsToIdsMap = listJob->idList();
494
495
        qCDebug(POP3RESOURCE_LOG) << "IdsToUidsMap size" << mIdsToUidsMap.size();
        qCDebug(POP3RESOURCE_LOG) << "UidsToIdsMap size" << mUidsToIdsMap.size();
Laurent Montel's avatar
Laurent Montel committed
496
497

        mUidListValid = !mIdsToUidsMap.isEmpty() || mIdsToSizeMap.isEmpty();
498
        if (mSettings.leaveOnServer() && !mUidListValid) {
Laurent Montel's avatar
Laurent Montel committed
499
            // FIXME: this needs a proper parent window
Laurent Montel's avatar
Laurent Montel committed
500
            KMessageBox::sorry(nullptr,
Laurent Montel's avatar
Laurent Montel committed
501
502
503
504
505
506
                               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()));
        }
507

Laurent Montel's avatar
Laurent Montel committed
508
509
        advanceState(Download);
    }
510
511
}

Laurent Montel's avatar
Laurent Montel committed
512
void POP3Resource::fetchJobResult(KJob *job)
513
{
Laurent Montel's avatar
Laurent Montel committed
514
    if (job->error()) {
Laurent Montel's avatar
Laurent Montel committed
515
516
        cancelSync(i18n("Error while fetching mails from the server.")
                   +QLatin1Char('\n') + job->errorString());
Laurent Montel's avatar
Laurent Montel committed
517
518
        return;
    } else {
Laurent Montel's avatar
Laurent Montel committed
519
        qCDebug(POP3RESOURCE_LOG) << "Downloaded" << mDownloadedIDs.size() << "mails";
520

Laurent Montel's avatar
Laurent Montel committed
521
        if (!mIdsToDownload.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
522
            qCWarning(POP3RESOURCE_LOG) << "We did not download all messages, there are still some remaining "
Laurent Montel's avatar
Laurent Montel committed
523
                                           "IDs, even though we requested their download:" << mIdsToDownload;
Laurent Montel's avatar
Laurent Montel committed
524
525
526
527
        }

        advanceState(Save);
    }
528
529
}

Laurent Montel's avatar
Laurent Montel committed
530
void POP3Resource::messageFinished(int messageId, KMime::Message::Ptr message)
531
{
Laurent Montel's avatar
Laurent Montel committed
532
533
534
535
536
537
    if (mState != Download) {
        // This can happen if the slave does not get notified in time about the fact
        // that the job was killed
        return;
    }

Laurent Montel's avatar
Laurent Montel committed
538
    //qCDebug(POP3RESOURCE_LOG) << "Got message" << messageId
Laurent Montel's avatar
Laurent Montel committed
539
540
541
    //         << "with subject" << message->subject()->asUnicodeString();

    Akonadi::Item item;
542
    item.setMimeType(QStringLiteral("message/rfc822"));
Laurent Montel's avatar
Laurent Montel committed
543
544
    item.setPayload<KMime::Message::Ptr>(message);

Laurent Montel's avatar
Laurent Montel committed
545
    auto *attr = item.attribute<Akonadi::Pop3ResourceAttribute>(Akonadi::Item::AddIfMissing);
Laurent Montel's avatar
Laurent Montel committed
546
    attr->setPop3AccountName(identifier());
547
    Akonadi::MessageFlags::copyMessageFlags(*message, item);
Laurent Montel's avatar
Laurent Montel committed
548
    auto *itemCreateJob = new ItemCreateJob(item, mTargetCollection);
Laurent Montel's avatar
Laurent Montel committed
549
550

    mPendingCreateJobs.insert(itemCreateJob, messageId);
Laurent Montel's avatar
Laurent Montel committed
551
    connect(itemCreateJob, &ItemCreateJob::result, this, &POP3Resource::itemCreateJobResult);
Laurent Montel's avatar
Laurent Montel committed
552
553
554

    mDownloadedIDs.append(messageId);
    mIdsToDownload.removeAll(messageId);
555
556
}

Laurent Montel's avatar
Laurent Montel committed
557
void POP3Resource::messageDownloadProgress(KJob *job, KJob::Unit unit, qulonglong totalBytes)
558
{
Laurent Montel's avatar
Laurent Montel committed
559
560
    Q_UNUSED(totalBytes)
    Q_UNUSED(unit)
Laurent Montel's avatar
Laurent Montel committed
561
562
563
564
    Q_ASSERT(unit == KJob::Bytes);
    QString statusMessage;
    const int totalMessages = mIdsToDownload.size() + mDownloadedIDs.size();
    int bytesRemainingOnServer = 0;
565
    foreach (const QString &alreadyDownloadedUID, mSettings.seenUidList()) {
Laurent Montel's avatar
Laurent Montel committed
566
567
568
569
570
571
        const int alreadyDownloadedID = mUidsToIdsMap.value(alreadyDownloadedUID, -1);
        if (alreadyDownloadedID != -1) {
            bytesRemainingOnServer += mIdsToSizeMap.value(alreadyDownloadedID);
        }
    }

572
    if (mSettings.leaveOnServer() && bytesRemainingOnServer > 0) {
Laurent Montel's avatar
Laurent Montel committed
573
574
575
576
577
578
579
580
581
582
583
584
        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());
    }
Laurent Montel's avatar
Laurent Montel committed
585
586
    Q_EMIT status(Running, statusMessage);
    Q_EMIT percent(job->percent());
587
588
}

Laurent Montel's avatar
Laurent Montel committed
589
void POP3Resource::itemCreateJobResult(KJob *job)
590
{
Laurent Montel's avatar
Laurent Montel committed
591
592
593
594
595
596
    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;
    }

Laurent Montel's avatar
Laurent Montel committed
597
    auto *createJob = qobject_cast<ItemCreateJob *>(job);
Laurent Montel's avatar
Laurent Montel committed
598
599
600
    Q_ASSERT(createJob);

    if (job->error()) {
Laurent Montel's avatar
Laurent Montel committed
601
602
        cancelSync(i18n("Unable to store downloaded mails.")
                   +QLatin1Char('\n') + job->errorString());
Laurent Montel's avatar
Laurent Montel committed
603
604
605
606
607
608
609
        return;
    }

    const int idOfMessageJustCreated = mPendingCreateJobs.value(createJob, -1);
    Q_ASSERT(idOfMessageJustCreated != -1);
    mPendingCreateJobs.remove(createJob);
    mIDsStored.append(idOfMessageJustCreated);
Laurent Montel's avatar
Laurent Montel committed
610
    //qCDebug(POP3RESOURCE_LOG) << "Just stored message with ID" << idOfMessageJustCreated
Laurent Montel's avatar
Laurent Montel committed
611
612
    //         << "on the Akonadi server";

Laurent Montel's avatar
Laurent Montel committed
613
614
615
    const QList<int> idToDeleteMessage = shouldDeleteId(idOfMessageJustCreated);
    if (!idToDeleteMessage.isEmpty()) {
        mIdsWaitingToDelete << idToDeleteMessage;
616
617
618
619
620
621
622
623
624
        if (!mDeleteJob) {
            mDeleteJob = new DeleteJob(mPopSession);
            mDeleteJob->setDeleteIds(mIdsWaitingToDelete);
            mIdsWaitingToDelete.clear();
            connect(mDeleteJob, &DeleteJob::result, this, &POP3Resource::deleteJobResult);
            mDeleteJob->start();
        }
    }

Laurent Montel's avatar
Laurent Montel committed
625
    // Have all create jobs finished? Go to the next state, then
626
627
    if (shouldAdvanceToQuitState()) {
        advanceState(Quit);
Laurent Montel's avatar
Laurent Montel committed
628
    }
629
630
}

Laurent Montel's avatar
Laurent Montel committed
631
int POP3Resource::idToTime(int id) const
632
{
Laurent Montel's avatar
Laurent Montel committed
633
634
    const QString uid = mIdsToUidsMap.value(id);
    if (!uid.isEmpty()) {
635
636
        const QList<QString> seenUIDs = mSettings.seenUidList();
        const QList<int> timeOfSeenUids = mSettings.seenUidTimeList();
Laurent Montel's avatar
Laurent Montel committed
637
638
639
640
        Q_ASSERT(seenUIDs.size() == timeOfSeenUids.size());
        const int index = seenUIDs.indexOf(uid);
        if (index != -1 && (index < timeOfSeenUids.size())) {
            return timeOfSeenUids.at(index);
Laurent Montel's avatar
Laurent Montel committed
641
642
643
644
645
        }
    }

    // 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
Laurent Montel's avatar
Laurent Montel committed
646
    return time(nullptr);
647
648
}

649
int POP3Resource::idOfOldestMessage(const QSet<int> &idList) const
650
{
Laurent Montel's avatar
Laurent Montel committed
651
    int timeOfOldestMessage = time(nullptr) + 999;
Laurent Montel's avatar
Laurent Montel committed
652
653
654
655
656
657
658
    int idOfOldestMessage = -1;
    foreach (int id, idList) {
        const int idTime = idToTime(id);
        if (idTime < timeOfOldestMessage) {
            timeOfOldestMessage = idTime;
            idOfOldestMessage = id;
        }
659
    }
Laurent Montel's avatar
Laurent Montel committed
660
661
    Q_ASSERT(idList.isEmpty() || idOfOldestMessage != -1);
    return idOfOldestMessage;
662
663
}

Laurent Montel's avatar
Laurent Montel committed
664
QList<int> POP3Resource::shouldDeleteId(int downloadedId) const
665
{
Laurent Montel's avatar
Laurent Montel committed
666
    QList<int> idsToDeleteFromServer;
Laurent Montel's avatar
Laurent Montel committed
667
668
    // By default, we delete all messages. But if we have "leave on server"
    // rules, we can save some messages.
669
    if (mSettings.leaveOnServer()) {
Laurent Montel's avatar
Laurent Montel committed
670
        idsToDeleteFromServer = mIdsToSizeMap.keys();
Laurent Montel's avatar
Laurent Montel committed
671
672
673
        if (!mIdsToSaveValid) {
            mIdsToSaveValid = true;
            mIdsToSave.clear();
Albert Astals Cid's avatar
Albert Astals Cid committed
674

675
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
Laurent Montel's avatar
Laurent Montel committed
676
            const QSet<int> idsOnServer = QSet<int>::fromList(idsToDeleteFromServer);
677
678
679
#else
            const QSet<int> idsOnServer(idsToDeleteFromServer.constBegin(), idsToDeleteFromServer.constEnd());
#endif
Albert Astals Cid's avatar
Albert Astals Cid committed
680
681
682

            // If the time-limited leave rule is checked, add the newer messages to
            // the list of messages to keep
683
            if (mSettings.leaveOnServerDays() > 0) {
Albert Astals Cid's avatar
Albert Astals Cid committed
684
                const int secondsPerDay = 86400;
685
                time_t timeLimit = time(nullptr) - (secondsPerDay * mSettings.leaveOnServerDays());
Albert Astals Cid's avatar
Albert Astals Cid committed
686
687
688
                foreach (int idToDelete, idsOnServer) {
                    const int msgTime = idToTime(idToDelete);
                    if (msgTime >= timeLimit) {
Laurent Montel's avatar
Laurent Montel committed
689
                        mIdsToSave << idToDelete;
Albert Astals Cid's avatar
Albert Astals Cid committed
690
691
692
                    } else {
                        qCDebug(POP3RESOURCE_LOG) << "Message" << idToDelete << "is too old and will be deleted.";
                    }
Laurent Montel's avatar
Laurent Montel committed
693
694
                }
            }
Albert Astals Cid's avatar
Albert Astals Cid committed
695
696
697
698
            // 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 {
Laurent Montel's avatar
Laurent Montel committed
699
                mIdsToSave = idsOnServer;
Albert Astals Cid's avatar
Albert Astals Cid committed
700
            }
701

Albert Astals Cid's avatar
Albert Astals Cid committed
702
703
704
            //
            // Delete more old messages if there are more than mLeaveOnServerCount
            //
705
706
            if (mSettings.leaveOnServerCount() > 0) {
                const int numToDelete = mIdsToSave.count() - mSettings.leaveOnServerCount();
Laurent Montel's avatar
Laurent Montel committed
707
                if (numToDelete > 0 && numToDelete < mIdsToSave.count()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
708
709
                    // Get rid of the first numToDelete messages
                    for (int i = 0; i < numToDelete; i++) {
Laurent Montel's avatar
Laurent Montel committed
710
                        mIdsToSave.remove(idOfOldestMessage(mIdsToSave));
Albert Astals Cid's avatar
Albert Astals Cid committed
711
                    }
Laurent Montel's avatar
Laurent Montel committed
712
713
                } else if (numToDelete >= mIdsToSave.count()) {
                    mIdsToSave.clear();
Laurent Montel's avatar
Laurent Montel committed
714
715
                }
            }
716

Albert Astals Cid's avatar
Albert Astals Cid committed
717
718
719
            //
            // Delete more old messages until we're under mLeaveOnServerSize MBs
            //
720
721
            if (mSettings.leaveOnServerSize() > 0) {
                const qint64 limitInBytes = mSettings.leaveOnServerSize() * (1024 * 1024);
Albert Astals Cid's avatar
Albert Astals Cid committed
722
                qint64 sizeOnServerAfterDeletion = 0;
Laurent Montel's avatar
Laurent Montel committed
723
                for (int id : qAsConst(mIdsToSave)) {
Albert Astals Cid's avatar
Albert Astals Cid committed
724
725
726
                    sizeOnServerAfterDeletion += mIdsToSizeMap.value(id);
                }
                while (sizeOnServerAfterDeletion > limitInBytes) {
Laurent Montel's avatar
Laurent Montel committed
727
728
                    int oldestId = idOfOldestMessage(mIdsToSave);
                    mIdsToSave.remove(oldestId);
Albert Astals Cid's avatar
Albert Astals Cid committed
729
730
                    sizeOnServerAfterDeletion -= mIdsToSizeMap.value(oldestId);
                }
Laurent Montel's avatar
Laurent Montel committed
731
732
            }
        }
Laurent Montel's avatar
Laurent Montel committed
733
734
735
736
737
        // Now save the messages from deletion
        //
        foreach (int idToSave, mIdsToSave) {
            idsToDeleteFromServer.removeAll(idToSave);
        }
738
        if (downloadedId != -1 && !mIdsToSave.contains(downloadedId)) {
Laurent Montel's avatar
Laurent Montel committed
739
740
741
            idsToDeleteFromServer << downloadedId;
        }
    } else {
742
743
        if (downloadedId != -1) {
            idsToDeleteFromServer << downloadedId;
744
745
        } else {
            idsToDeleteFromServer << mIdsToSizeMap.keys();
746
        }
747
    }
Laurent Montel's avatar
Laurent Montel committed
748
    return idsToDeleteFromServer;
749
750
}

Laurent Montel's avatar
Laurent Montel committed
751
void POP3Resource::deleteJobResult(KJob *job)
752
{
Laurent Montel's avatar
Laurent Montel committed
753
    if (job->error()) {
Laurent Montel's avatar
Laurent Montel committed
754
755
        cancelSync(i18n("Failed to delete the messages from the server.")
                   +QLatin1Char('\n') + job->errorString());
Laurent Montel's avatar
Laurent Montel committed
756
757
758
        return;
    }

Laurent Montel's avatar
Laurent Montel committed
759
    auto *finishedDeleteJob = qobject_cast<DeleteJob *>(job);
760
761
762
    Q_ASSERT(finishedDeleteJob);
    Q_ASSERT(finishedDeleteJob == mDeleteJob);
    mDeletedIDs = finishedDeleteJob->deletedIDs();
Laurent Montel's avatar
Laurent Montel committed
763
764
765

    // Remove all deleted messages from the list of already downloaded messages,
    // as it is no longer necessary to store them (they just waste space)
766
767
    QList<QString> seenUIDs = mSettings.seenUidList();
    QList<int> timeOfSeenUids = mSettings.seenUidTimeList();
Laurent Montel's avatar
Laurent Montel committed
768
769
    Q_ASSERT(seenUIDs.size() == timeOfSeenUids.size());
    foreach (int deletedId, mDeletedIDs) {
Laurent Montel's avatar
Laurent Montel committed
770
        const QString deletedUID = mIdsToUidsMap.value(deletedId);
Laurent Montel's avatar
Laurent Montel committed
771
772
773
774
        if (!deletedUID.isEmpty()) {
            int index = seenUIDs.indexOf(deletedUID);
            if (index != -1) {
                // TEST
Laurent Montel's avatar
Laurent Montel committed
775
                qCDebug(POP3RESOURCE_LOG) << "Removing UID" << deletedUID << "from the seen UID list, as it was deleted.";
Laurent Montel's avatar
Laurent Montel committed
776
777
778
779
                seenUIDs.removeAt(index);
                timeOfSeenUids.removeAt(index);
            }
        }
780
781
        mIdsToUidsMap.remove(deletedId);
        mIdsToSizeMap.remove(deletedId);
782
    }
783
784
785
    mSettings.setSeenUidList(seenUIDs);
    mSettings.setSeenUidTimeList(timeOfSeenUids);
    mSettings.save();
786

Laurent Montel's avatar
Laurent Montel committed
787
    mDeleteJob = nullptr;
788
789
790
791
792
793
794
    if (!mIdsWaitingToDelete.isEmpty()) {
        mDeleteJob = new DeleteJob(mPopSession);
        mDeleteJob->setDeleteIds(mIdsWaitingToDelete);
        mIdsWaitingToDelete.clear();
        connect(mDeleteJob, &DeleteJob::result, this, &POP3Resource::deleteJobResult);
        mDeleteJob->start();
    }
795

796
    if (shouldAdvanceToQuitState()) {
Laurent Montel's avatar
Laurent Montel committed
797
        advanceState(Quit);
Laurent Montel's avatar
Laurent Montel committed
798
    } else if (mDeleteJob == nullptr) {
799
800
        advanceState(Save);
    }
801
802
}

803
void POP3Resource::finish()
804
{
Laurent Montel's avatar
Laurent Montel committed
805
    qCDebug(POP3RESOURCE_LOG) << "================= Mail check finished. =============================";
Laurent Montel's avatar
Laurent Montel committed
806
807
808
809
810
    saveSeenUIDList();
    if (!mIntervalCheckInProgress) {
        collectionsRetrieved(Akonadi::Collection::List());
    }
    if (mDownloadedIDs.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
811
        Q_EMIT status(Idle, i18n("Finished mail check, no message downloaded."));
Laurent Montel's avatar
Laurent Montel committed
812
    } else {
Laurent Montel's avatar
Laurent Montel committed
813
814
815
        Q_EMIT status(Idle, i18np("Finished mail check, 1 message downloaded.",
                                  "Finished mail check, %1 messages downloaded.",
                                  mDownloadedIDs.size()));
Laurent Montel's avatar
Laurent Montel committed
816
    }
Laurent Montel's avatar
Laurent Montel committed
817
818

    resetState();
819
820
}

821
822
823
824
825
bool POP3Resource::shouldAdvanceToQuitState() const
{
    return mState == Save && mPendingCreateJobs.isEmpty() && mIdsWaitingToDelete.isEmpty() && !mDeleteJob;
}

Laurent Montel's avatar
Laurent Montel committed
826
void POP3Resource::quitJobResult(KJob *job)
827
{
Laurent Montel's avatar
Laurent Montel committed
828
    if (job->error()) {
Laurent Montel's avatar
Laurent Montel committed
829
830
        cancelSync(i18n("Unable to complete the mail fetch.")
                   +QLatin1Char('\n') + job->errorString());
Laurent Montel's avatar
Laurent Montel committed
831
832
        return;
    }
833

Laurent Montel's avatar
Laurent Montel committed
834
    advanceState(SavePassword);
835
836
}

Laurent Montel's avatar
Laurent Montel committed
837
void POP3Resource::slotSessionError(int errorCode, const QString &errorMessage)
838
{
Laurent Montel's avatar
Laurent Montel committed
839
    qCWarning(POP3RESOURCE_LOG) << "Error in our session, unrelated to a currently running job!";
Laurent Montel's avatar
Laurent Montel committed
840
    cancelSync(KIO::buildErrorString(errorCode, errorMessage));
841
842
843
844
}

void POP3Resource::saveSeenUIDList()
{
845
846
    QList<QString> seenUIDs = mSettings.seenUidList();
    QList<int> timeOfSeenUIDs = mSettings.seenUidTimeList();
Laurent Montel's avatar
Laurent Montel committed
847
848
849
850
851
852
853
854
855
856
857
858

    //
    // 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<int> idsOfMessagesDownloadedButNotDeleted = mIDsStored;
    foreach (int deletedId, mDeletedIDs) {
        idsOfMessagesDownloadedButNotDeleted.removeAll(deletedId);
    }
    QList<QString> uidsOfMessagesDownloadedButNotDeleted;
    foreach (int id, idsOfMessagesDownloadedButNotDeleted) {
Laurent Montel's avatar
USe 0    
Laurent Montel committed
859
        const QString uid = mIdsToUidsMap.value(id);
Laurent Montel's avatar
Laurent Montel committed
860
861
862
        if (!uid.isEmpty()) {
            uidsOfMessagesDownloadedButNotDeleted.append(uid);
        }
863
    }
Laurent Montel's avatar
Laurent Montel committed
864
865
866
867
    Q_ASSERT(seenUIDs.size() == timeOfSeenUIDs.size());
    foreach (const QString &uid, uidsOfMessagesDownloadedButNotDeleted) {
        if (!seenUIDs.contains(uid)) {
            seenUIDs.append(uid);
Laurent Montel's avatar
Laurent Montel committed
868
            timeOfSeenUIDs.append(time(nullptr));
Laurent Montel's avatar
Laurent Montel committed
869
        }
870
    }
Laurent Montel's avatar
Laurent Montel committed
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894

    //
    // 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<QString>::iterator uidIt = seenUIDs.begin();
        QList<int>::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 {
Laurent Montel's avatar
Laurent Montel committed
895
        qCWarning(POP3RESOURCE_LOG) << "UID list from server is not valid.";
896
    }
Laurent Montel's avatar
Laurent Montel committed
897
898
899
900

    //
    // Now save it in the settings
    //
Laurent Montel's avatar
Laurent Montel committed
901
    qCDebug(POP3RESOURCE_LOG) << "The seen UID list has" << seenUIDs.size() << "entries";