diff --git a/autotests/testsession.cpp b/autotests/testsession.cpp index 8888e87..bd38d7f 100644 --- a/autotests/testsession.cpp +++ b/autotests/testsession.cpp @@ -1,349 +1,365 @@ /* This file is part of the KDE project Copyright (C) 2008 Kevin Ottens Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Kevin Ottens 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 #include #include #include "session.h" #include "job.h" #include "kimap2test/fakeserver.h" #include "kimap2test/mockjob.h" Q_DECLARE_METATYPE(KIMAP2::Session::State) Q_DECLARE_METATYPE(KJob *) class SessionTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { qRegisterMetaType(); } void shouldStartDisconnected() { FakeServer fakeServer; fakeServer.setScenario(QList() << FakeServer::greeting() ); fakeServer.startAndWait(); KIMAP2::Session s(QStringLiteral("127.0.0.1"), 5989); QSignalSpy spy(&s, SIGNAL(stateChanged(KIMAP2::Session::State,KIMAP2::Session::State))); QCOMPARE((int)s.state(), (int)KIMAP2::Session::Disconnected); QTest::qWait(600); QCOMPARE((int)s.state(), (int)KIMAP2::Session::NotAuthenticated); QCOMPARE(spy.count(), 1); // NotAuthenticated QList arguments = spy.takeFirst(); QCOMPARE((int)qvariant_cast(arguments.at(0)), (int)KIMAP2::Session::NotAuthenticated); QCOMPARE((int)qvariant_cast(arguments.at(1)), (int)KIMAP2::Session::Disconnected); } void shouldFailForInvalidHosts() { KIMAP2::Session s(QStringLiteral("0.0.0.0"), 1234); s.setTimeout(1); // 1 second timout QSignalSpy spyFail(&s, SIGNAL(connectionFailed())); QSignalSpy spyState(&s, SIGNAL(stateChanged(KIMAP2::Session::State,KIMAP2::Session::State))); QCOMPARE((int)s.state(), (int)KIMAP2::Session::Disconnected); QTest::qWait(500); QCOMPARE((int)s.state(), (int)KIMAP2::Session::Disconnected); QCOMPARE(spyFail.count(), 1); QCOMPARE(spyState.count(), 0); // Wait 800ms more. So now it's 1.3 seconds, check that the socket timeout has correctly been // disabled, and that it hadn't fired unexpectedly. QTest::qWait(800); QCOMPARE(spyFail.count(), 1); } /** Checks that the timeout works when the connection succeeds, but the server doesn't sends anything back to the client. This could happen for example if we connected to a non-IMAP server. */ void shouldTimeoutOnNoGreeting() { FakeServer fakeServer; fakeServer.setScenario(QList()); fakeServer.startAndWait(); KIMAP2::Session s(QStringLiteral("127.0.0.1"), 5989); s.setTimeout(2); QSignalSpy spyFail(&s, SIGNAL(connectionFailed())); QSignalSpy spyState(&s, SIGNAL(stateChanged(KIMAP2::Session::State,KIMAP2::Session::State))); QCOMPARE((int)s.state(), (int)KIMAP2::Session::Disconnected); // Wait 1.8 second. Since the timeout is set to 2 seconds, the socket should be still // disconnected at this point, yet the connectionFailed() signal shouldn't have been emitted. QTest::qWait(1800); QCOMPARE((int)s.state(), (int)KIMAP2::Session::Disconnected); QCOMPARE(spyFail.count(), 0); QCOMPARE(spyState.count(), 0); // Wait 0.5 second more. Now we are at 2.3 seconds, the socket should have timed out, and the // connectionFailed() signal should have been emitted. QTest::qWait(500); QCOMPARE((int)s.state(), (int)KIMAP2::Session::Disconnected); QCOMPARE(spyFail.count(), 1); QCOMPARE(spyState.count(), 0); } void shouldSupportPreauth() { FakeServer fakeServer; fakeServer.setScenario(QList() << FakeServer::preauth() ); fakeServer.startAndWait(); KIMAP2::Session s(QStringLiteral("127.0.0.1"), 5989); QSignalSpy spy(&s, SIGNAL(stateChanged(KIMAP2::Session::State,KIMAP2::Session::State))); QCOMPARE((int)s.state(), (int)KIMAP2::Session::Disconnected); QTest::qWait(500); QCOMPARE((int)s.state(), (int)KIMAP2::Session::Authenticated); QCOMPARE(spy.count(), 1); // Authenticated QList arguments = spy.takeFirst(); QCOMPARE((int)qvariant_cast(arguments.at(0)), (int)KIMAP2::Session::Authenticated); QCOMPARE((int)qvariant_cast(arguments.at(1)), (int)KIMAP2::Session::Disconnected); } void shouldRespectStartOrder() { FakeServer fakeServer; fakeServer.setScenario(QList() << FakeServer::greeting() ); fakeServer.startAndWait(); KIMAP2::Session s(QStringLiteral("127.0.0.1"), 5989); MockJob *j1 = new MockJob(&s); connect(j1, SIGNAL(result(KJob*)), this, SLOT(jobDone(KJob*))); MockJob *j2 = new MockJob(&s); connect(j2, SIGNAL(result(KJob*)), this, SLOT(jobDone(KJob*))); MockJob *j3 = new MockJob(&s); connect(j3, SIGNAL(result(KJob*)), this, SLOT(jobDone(KJob*))); MockJob *j4 = new MockJob(&s); connect(j4, SIGNAL(result(KJob*)), this, SLOT(jobDone(KJob*))); j4->start(); j2->start(); j3->start(); j1->start(); QTRY_COMPARE(m_jobs.size(), 4); QCOMPARE(m_jobs[0], j4); QCOMPARE(m_jobs[1], j2); QCOMPARE(m_jobs[2], j3); QCOMPARE(m_jobs[3], j1); } void shouldManageQueueSize() { FakeServer fakeServer; fakeServer.setScenario(QList() << FakeServer::greeting() ); fakeServer.startAndWait(); KIMAP2::Session s(QStringLiteral("127.0.0.1"), 5989); QSignalSpy queueSpy(&s, SIGNAL(jobQueueSizeChanged(int))); QCOMPARE(s.jobQueueSize(), 0); MockJob *j1 = new MockJob(&s); MockJob *j2 = new MockJob(&s); MockJob *j3 = new MockJob(&s); MockJob *j4 = new MockJob(&s); connect(j4, SIGNAL(result(KJob*)), &m_eventLoop, SLOT(quit())); QCOMPARE(s.jobQueueSize(), 0); j1->start(); QCOMPARE(s.jobQueueSize(), 1); QCOMPARE(queueSpy.size(), 1); QCOMPARE(queueSpy.at(0).at(0).toInt(), 1); j2->start(); QCOMPARE(s.jobQueueSize(), 2); QCOMPARE(queueSpy.size(), 2); QCOMPARE(queueSpy.at(1).at(0).toInt(), 2); j3->start(); QCOMPARE(s.jobQueueSize(), 3); QCOMPARE(queueSpy.size(), 3); QCOMPARE(queueSpy.at(2).at(0).toInt(), 3); j4->start(); QCOMPARE(s.jobQueueSize(), 4); QCOMPARE(queueSpy.size(), 4); QCOMPARE(queueSpy.at(3).at(0).toInt(), 4); queueSpy.clear(); m_eventLoop.exec(); QCOMPARE(s.jobQueueSize(), 0); QCOMPARE(queueSpy.at(0).at(0).toInt(), 3); QCOMPARE(queueSpy.at(1).at(0).toInt(), 2); QCOMPARE(queueSpy.at(2).at(0).toInt(), 1); QCOMPARE(queueSpy.at(3).at(0).toInt(), 0); } void shouldTimeoutOnNoReply() { FakeServer fakeServer; fakeServer.setScenario(QList() << FakeServer::preauth() << "C: A000001 DUMMY" << "S: * DUMMY" << "S: * DUMMY" << "S: * DUMMY" << "S: * DUMMY" << "S: * DUMMY" << "S: * DUMMY" << "S: * DUMMY" << "S: * DUMMY" << "S: * DUMMY" << "S: * DUMMY" << "S: * DUMMY" << "S: * DUMMY" << "S: * DUMMY" // We never get a OK or anything, so the job can't normally complete ); fakeServer.startAndWait(); KIMAP2::Session s(QStringLiteral("127.0.0.1"), 5989); QSignalSpy spyFail(&s, SIGNAL(connectionFailed())); QSignalSpy spyState(&s, SIGNAL(stateChanged(KIMAP2::Session::State,KIMAP2::Session::State))); MockJob *mock = new MockJob(&s); mock->setCommand("DUMMY"); mock->exec(); // We expect to get an error here due to some timeout QVERIFY(mock->error() != 0); QCOMPARE(spyFail.count(), 0); QCOMPARE(spyState.count(), 2); // Authenticated, Disconnected } void shouldFailFirstJobOnConnectionFailed() { qRegisterMetaType(); FakeServer fakeServer; fakeServer.setScenario(QList()); fakeServer.startAndWait(); KIMAP2::Session s(QStringLiteral("127.0.0.1"), 5989); s.setTimeout(1); MockJob *j1 = new MockJob(&s); QSignalSpy spyResult1(j1, SIGNAL(result(KJob*))); QSignalSpy spyDestroyed1(j1, SIGNAL(destroyed())); MockJob *j2 = new MockJob(&s); QSignalSpy spyResult2(j2, SIGNAL(result(KJob*))); QSignalSpy spyDestroyed2(j2, SIGNAL(destroyed())); MockJob *j3 = new MockJob(&s); QSignalSpy spyResult3(j3, SIGNAL(result(KJob*))); QSignalSpy spyDestroyed3(j3, SIGNAL(destroyed())); j1->start(); j2->start(); j3->start(); QCOMPARE(s.jobQueueSize(), 3); QTest::qWait(1100); // Check that only the first job has emitted it's result QCOMPARE(spyResult1.count(), 1); QCOMPARE(spyResult2.count(), 1); QCOMPARE(spyResult3.count(), 1); // Check that all jobs have been deleted QCOMPARE(spyDestroyed1.count(), 1); QCOMPARE(spyDestroyed2.count(), 1); QCOMPARE(spyDestroyed3.count(), 1); QCOMPARE(s.jobQueueSize(), 0); } void shouldCloseOnInconsistency() { FakeServer fakeServer; fakeServer.setScenario(QList() << FakeServer::preauth() << "C: A000001 DUMMY" << "S: * DUMMY %)" ); fakeServer.startAndWait(); KIMAP2::Session s(QStringLiteral("127.0.0.1"), 5989); QSignalSpy spyFail(&s, SIGNAL(connectionFailed())); QSignalSpy spyState(&s, SIGNAL(stateChanged(KIMAP2::Session::State,KIMAP2::Session::State))); MockJob *mock = new MockJob(&s); mock->setTimeout(5000); mock->setCommand("DUMMY"); mock->setAutoDelete(false); mock->exec(); // We expect to get an error here due to the inconsistency QVERIFY(mock->error() != 0); QCOMPARE(spyFail.count(), 0); QCOMPARE(spyState.count(), 2); // Authenticated, Disconnected delete mock; } + void shouldAbortJobWhenDisconnected() + { + KIMAP2::Session session(QStringLiteral("0.0.0.0"), 1234); + + //The first job get's aborted when the session disconnects + { + MockJob *job = new MockJob(&session); + QVERIFY(!job->exec()); + } + //The second job needs to be aborted by the session automatically + { + MockJob *job = new MockJob(&session); + QVERIFY(!job->exec()); + } + } + public Q_SLOTS: void jobDone(KJob *job) { m_jobs << job; if (m_expectedCalls == m_jobs.size()) { m_eventLoop.quit(); } } private: QEventLoop m_eventLoop; int m_expectedCalls; QList m_jobs; }; QTEST_GUILESS_MAIN(SessionTest) #include "testsession.moc" diff --git a/src/session.cpp b/src/session.cpp index 6a935bb..5db7d65 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -1,625 +1,629 @@ /* Copyright (c) 2009 Kevin Ottens Copyright (c) 2017 Christian Mollekopf Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Kevin Ottens 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 "session.h" #include "session_p.h" #include #include "kimap_debug.h" #include "job.h" #include "message_p.h" #include "sessionlogger_p.h" #include "rfccodecs.h" #include "imapstreamparser.h" Q_DECLARE_METATYPE(QSsl::SslProtocol) Q_DECLARE_METATYPE(QSslSocket::SslMode) static const int _kimap_sslVersionId = qRegisterMetaType(); using namespace KIMAP2; Session::Session(const QString &hostName, quint16 port, QObject *parent) : QObject(parent), d(new SessionPrivate(this)) { if (!qEnvironmentVariableIsEmpty("KIMAP2_LOGFILE")) { d->logger.reset(new SessionLogger); qCInfo(KIMAP2_LOG) << "Logging traffic to: " << QLatin1String(qgetenv("KIMAP2_LOGFILE")); } if (qEnvironmentVariableIsSet("KIMAP2_TRAFFIC")) { d->dumpTraffic = true; qCInfo(KIMAP2_LOG) << "Dumping traffic."; } if (qEnvironmentVariableIsSet("KIMAP2_TIMING")) { d->trackTime = true; qCInfo(KIMAP2_LOG) << "Tracking timings."; } - d->isSocketConnected = false; d->state = Disconnected; d->jobRunning = false; d->hostName = hostName; d->port = port; connect(d->socket.data(), &QIODevice::readyRead, d, &SessionPrivate::readMessage); connect(d->socket.data(), &QSslSocket::connected, d, &SessionPrivate::socketConnected); connect(d->socket.data(), static_cast&)>(&QSslSocket::sslErrors), d, &SessionPrivate::handleSslErrors); connect(d->socket.data(), static_cast(&QSslSocket::error), d, &SessionPrivate::socketError); connect(d->socket.data(), &QIODevice::bytesWritten, d, &SessionPrivate::socketActivity); connect(d->socket.data(), &QSslSocket::encryptedBytesWritten, d, &SessionPrivate::socketActivity); connect(d->socket.data(), &QIODevice::readyRead, d, &SessionPrivate::socketActivity); connect(d->socket.data(), &QAbstractSocket::stateChanged, [this](QAbstractSocket::SocketState state) { qCDebug(KIMAP2_LOG) << "Socket state changed: " << state; //The disconnected signal will not fire if we fail to lookup the host, but this will. if (state == QAbstractSocket::UnconnectedState) { d->socketDisconnected(); } if (state == QAbstractSocket::HostLookupState) { d->hostLookupInProgress = true; } else { d->hostLookupInProgress = false; } }); d->socketTimer.setSingleShot(true); connect(&d->socketTimer, &QTimer::timeout, d, &SessionPrivate::onSocketTimeout); d->socketProgressTimer.setSingleShot(false); connect(&d->socketProgressTimer, &QTimer::timeout, d, &SessionPrivate::onSocketProgressTimeout); d->startSocketTimer(); - QMetaObject::invokeMethod(d, "reconnect", Qt::QueuedConnection); + d->reconnect(); } Session::~Session() { //Make sure all jobs know we're done d->clearJobQueue(); delete d; } QString Session::hostName() const { return d->hostName; } quint16 Session::port() const { return d->port; } Session::State Session::state() const { return d->state; } bool Session::isConnected() const { return (d->state == Authenticated || d->state == Selected); } QString Session::userName() const { return d->userName; } QByteArray Session::serverGreeting() const { return d->greeting; } int Session::jobQueueSize() const { return d->queue.size() + (d->jobRunning ? 1 : 0); } void Session::close() { d->closeSocket(); } void Session::ignoreErrors(const QList &errors) { d->socket->ignoreSslErrors(errors); } void Session::setTimeout(int timeout) { d->setSocketTimeout(timeout * 1000); } int Session::timeout() const { return d->socketTimeout() / 1000; } QString Session::selectedMailBox() const { return QString::fromUtf8(d->currentMailBox); } SessionPrivate::SessionPrivate(Session *session) : QObject(session), q(session), state(Session::Disconnected), hostLookupInProgress(false), logger(Q_NULLPTR), currentJob(Q_NULLPTR), tagCount(0), socketTimerInterval(30000), // By default timeouts on 30s socketProgressInterval(3000), // mention we're still alive every 3s socket(new QSslSocket), stream(new ImapStreamParser(socket.data())), accumulatedWaitTime(0), accumulatedProcessingTime(0), trackTime(false), dumpTraffic(false) { stream->onResponseReceived([this](const Message &message) { responseReceived(message); }); } SessionPrivate::~SessionPrivate() { } void SessionPrivate::handleSslErrors(const QList &errors) { emit q->sslErrors(errors); } void SessionPrivate::addJob(Job *job) { queue.append(job); emit q->jobQueueSizeChanged(q->jobQueueSize()); QObject::connect(job, &KJob::result, this, &SessionPrivate::jobDone); QObject::connect(job, &QObject::destroyed, this, &SessionPrivate::jobDestroyed); startNext(); } void SessionPrivate::startNext() { QMetaObject::invokeMethod(this, "doStartNext"); } void SessionPrivate::doStartNext() { - if (queue.isEmpty() || jobRunning || !isSocketConnected) { + //Wait until we are ready to process + if (queue.isEmpty() + || jobRunning + || socket->state() == QSslSocket::ConnectingState + || socket->state() == QSslSocket::HostLookupState) { + return; + } + + currentJob = queue.dequeue(); + + //Since we aren't connecting we may never get back. Cancel the job + if (socket->state() == QSslSocket::UnconnectedState) { + qCDebug(KIMAP2_LOG) << "Cancelling job due to lack of connection: " << currentJob->metaObject()->className(); + currentJob->connectionLost(); return; } if (trackTime) { time.start(); } restartSocketTimer(); jobRunning = true; - - currentJob = queue.dequeue(); - qCDebug(KIMAP2_LOG) << "Starting job: " << currentJob->metaObject()->className(); currentJob->doStart(); } void SessionPrivate::jobDone(KJob *job) { Q_UNUSED(job); Q_ASSERT(job == currentJob); qCDebug(KIMAP2_LOG) << "Job done: " << job->metaObject()->className(); stopSocketTimer(); jobRunning = false; currentJob = Q_NULLPTR; emit q->jobQueueSizeChanged(q->jobQueueSize()); startNext(); } void SessionPrivate::jobDestroyed(QObject *job) { queue.removeAll(static_cast(job)); if (currentJob == job) { currentJob = Q_NULLPTR; } } void SessionPrivate::responseReceived(const Message &response) { if (dumpTraffic) { qCInfo(KIMAP2_LOG) << "S: " << QString::fromLatin1(response.toString()); } if (logger && q->isConnected()) { logger->dataReceived(response.toString()); } QByteArray tag; QByteArray code; if (response.content.size() >= 1) { tag = response.content[0].toString(); } if (response.content.size() >= 2) { code = response.content[1].toString(); } // BYE may arrive as part of a LOGOUT sequence or before the server closes the connection after an error. // In any case we should wait until the server closes the connection, so we don't have to do anything. if (code == "BYE") { Message simplified = response; if (simplified.content.size() >= 2) { simplified.content.removeFirst(); // Strip the tag simplified.content.removeFirst(); // Strip the code } qCDebug(KIMAP2_LOG) << "Received BYE: " << simplified.toString(); return; } switch (state) { case Session::Disconnected: stopSocketTimer(); if (code == "OK") { Message simplified = response; simplified.content.removeFirst(); // Strip the tag simplified.content.removeFirst(); // Strip the code greeting = simplified.toString().trimmed(); // Save the server greeting setState(Session::NotAuthenticated); } else if (code == "PREAUTH") { Message simplified = response; simplified.content.removeFirst(); // Strip the tag simplified.content.removeFirst(); // Strip the code greeting = simplified.toString().trimmed(); // Save the server greeting setState(Session::Authenticated); } else { //We have been rejected closeSocket(); } return; case Session::NotAuthenticated: if (code == "OK" && tag == authTag) { setState(Session::Authenticated); } break; case Session::Authenticated: if (code == "OK" && tag == selectTag) { setState(Session::Selected); currentMailBox = upcomingMailBox; } break; case Session::Selected: if ((code == "OK" && tag == closeTag) || (code != "OK" && tag == selectTag)) { setState(Session::Authenticated); currentMailBox = QByteArray(); } else if (code == "OK" && tag == selectTag) { currentMailBox = upcomingMailBox; } break; } if (tag == authTag) { authTag.clear(); } if (tag == selectTag) { selectTag.clear(); } if (tag == closeTag) { closeTag.clear(); } // If a job is running forward it the response if (currentJob) { restartSocketTimer(); currentJob->handleResponse(response); } else { qCWarning(KIMAP2_LOG) << "A message was received from the server with no job to handle it:" << response.toString() << '(' + response.toString().toHex() + ')'; } } void SessionPrivate::setState(Session::State s) { if (s != state) { Session::State oldState = state; state = s; emit q->stateChanged(state, oldState); } } QByteArray SessionPrivate::sendCommand(const QByteArray &command, const QByteArray &args) { QByteArray tag = 'A' + QByteArray::number(++tagCount).rightJustified(6, '0'); QByteArray payload = tag + ' ' + command; if (!args.isEmpty()) { payload += ' ' + args; } sendData(payload); if (command == "LOGIN" || command == "AUTHENTICATE") { authTag = tag; } else if (command == "SELECT" || command == "EXAMINE") { selectTag = tag; upcomingMailBox = args; upcomingMailBox.remove(0, 1); upcomingMailBox = upcomingMailBox.left(upcomingMailBox.indexOf('\"')); upcomingMailBox = KIMAP2::decodeImapFolderName(upcomingMailBox); } else if (command == "CLOSE") { closeTag = tag; } return tag; } void SessionPrivate::sendData(const QByteArray &data) { restartSocketTimer(); if (dumpTraffic) { qCInfo(KIMAP2_LOG) << "C: " << data; } if (logger && q->isConnected()) { logger->dataSent(data); } dataQueue.enqueue(data + "\r\n"); QMetaObject::invokeMethod(this, "writeDataQueue"); } void SessionPrivate::socketConnected() { qCInfo(KIMAP2_LOG) << "Socket connected."; - isSocketConnected = true; startNext(); } void SessionPrivate::socketDisconnected() { - qCInfo(KIMAP2_LOG) << "Socket disconnected." << isSocketConnected; + qCInfo(KIMAP2_LOG) << "Socket disconnected."; stopSocketTimer(); if (logger && q->isConnected()) { logger->disconnectionOccured(); } - isSocketConnected = false; - if (state != Session::Disconnected) { setState(Session::Disconnected); } else { //If we timeout during host lookup we don't receive an explicit host lookup error if (hostLookupInProgress) { socketError(QAbstractSocket::HostNotFoundError); hostLookupInProgress = false; } emit q->connectionFailed(); } clearJobQueue(); } void SessionPrivate::socketActivity() { //This slot can be called after the job has already finished, in that case we don't want to restart the timer if (currentJob) { restartSocketTimer(); } } void SessionPrivate::socketError(QAbstractSocket::SocketError error) { qCDebug(KIMAP2_LOG) << "Socket error: " << error; stopSocketTimer(); if (currentJob) { qCWarning(KIMAP2_LOG) << "Socket error:" << error; currentJob->setSocketError(error); } else if (!queue.isEmpty()) { qCWarning(KIMAP2_LOG) << "Socket error:" << error; currentJob = queue.takeFirst(); currentJob->setSocketError(error); } - if (isSocketConnected) { - closeSocket(); - } + closeSocket(); } void SessionPrivate::clearJobQueue() { if (!currentJob && !queue.isEmpty()) { currentJob = queue.takeFirst(); } if (currentJob) { currentJob->connectionLost(); } QQueue queueCopy = queue; // copy because jobDestroyed calls removeAll qDeleteAll(queueCopy); queue.clear(); emit q->jobQueueSizeChanged(0); } void SessionPrivate::startSsl(QSsl::SslProtocol protocol) { socket->setProtocol(protocol); connect(socket.data(), &QSslSocket::encrypted, this, &SessionPrivate::sslConnected); if (socket->state() == QAbstractSocket::ConnectedState) { qCDebug(KIMAP2_LOG) << "Starting client encryption"; Q_ASSERT(socket->mode() == QSslSocket::UnencryptedMode); socket->startClientEncryption(); } else { qCWarning(KIMAP2_LOG) << "The socket is not yet connected"; } } void SessionPrivate::sslConnected() { qCDebug(KIMAP2_LOG) << "ssl is connected"; emit encryptionNegotiationResult(true); } void SessionPrivate::setSocketTimeout(int ms) { bool timerActive = socketTimer.isActive(); if (timerActive) { stopSocketTimer(); } socketTimerInterval = ms; if (timerActive) { startSocketTimer(); } } int SessionPrivate::socketTimeout() const { return socketTimerInterval; } void SessionPrivate::startSocketTimer() { if (socketTimerInterval < 0) { return; } Q_ASSERT(!socketTimer.isActive()); socketTimer.start(socketTimerInterval); socketProgressTimer.start(socketProgressInterval); } void SessionPrivate::stopSocketTimer() { socketTimer.stop(); socketProgressTimer.stop(); } void SessionPrivate::restartSocketTimer() { stopSocketTimer(); startSocketTimer(); } void SessionPrivate::onSocketTimeout() { qCWarning(KIMAP2_LOG) << "Aborting on socket timeout. " << socketTimerInterval; if (!currentJob && !queue.isEmpty()) { currentJob = queue.takeFirst(); } if (currentJob) { qCWarning(KIMAP2_LOG) << "Current job: " << currentJob->metaObject()->className(); currentJob->setErrorMessage("Aborting on socket timeout. Interval " + QString::number(socketTimerInterval) + " ms"); } socket->abort(); socketProgressTimer.stop(); } QString SessionPrivate::getStateName() const { if (hostLookupInProgress) { return "Host lookup"; } switch (state) { case Session::Disconnected: return "Disconnected"; case Session::NotAuthenticated: return "NotAuthenticated"; case Session::Authenticated: return "Authenticated"; case Session::Selected: default: break; } return "Unknown State"; } void SessionPrivate::onSocketProgressTimeout() { if (currentJob) { qCDebug(KIMAP2_LOG) << "Processing job: " << currentJob->metaObject()->className() << "Current state: " << getStateName() << (socket ? socket->state() : QAbstractSocket::UnconnectedState); } else { qCDebug(KIMAP2_LOG) << "Next job: " << (queue.isEmpty() ? "No job" : queue.head()->metaObject()->className()) << "Current state: " << getStateName() << (socket ? socket->state() : QAbstractSocket::UnconnectedState); } } void SessionPrivate::writeDataQueue() { while (!dataQueue.isEmpty()) { socket->write(dataQueue.dequeue()); } } void SessionPrivate::readMessage() { if (trackTime) { accumulatedWaitTime += time.elapsed(); time.start(); } stream->parseStream(); if (stream->error()) { qCWarning(KIMAP2_LOG) << "Error while parsing, closing connection."; qCDebug(KIMAP2_LOG) << "Current buffer: " << stream->currentBuffer(); socket->close(); } if (trackTime) { accumulatedProcessingTime += time.elapsed(); time.start(); qCDebug(KIMAP2_LOG) << "Wait vs process vs total: " << accumulatedWaitTime << accumulatedProcessingTime << accumulatedWaitTime + accumulatedProcessingTime; } } void SessionPrivate::closeSocket() { qCDebug(KIMAP2_LOG) << "Closing socket."; socket->close(); } void SessionPrivate::reconnect() { if (socket->state() == QSslSocket::ConnectedState && socket->state() == QSslSocket::ConnectingState) { qCDebug(KIMAP2_LOG) << "Reconnecting to: " << hostName << port; } else { qCDebug(KIMAP2_LOG) << "Connecting to: " << hostName << port; } socket->connectToHost(hostName, port); } #include "moc_session.cpp" #include "moc_session_p.cpp"