File indexing completed on 2024-11-17 04:45:06

0001 /*
0002     SPDX-FileCopyrightText: 2007 Till Adam <adam@kde.org>
0003     SPDX-FileCopyrightText: 2008 Omat Holding B.V. <info@omat.nl>
0004     SPDX-FileCopyrightText: 2009 Kevin Ottens <ervin@kde.org>
0005 
0006     SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
0007     SPDX-FileContributor: Kevin Ottens <kevin@kdab.com>
0008 
0009     SPDX-License-Identifier: LGPL-2.0-or-later
0010 */
0011 
0012 #include "imapidlemanager.h"
0013 
0014 #include "imapresource_debug.h"
0015 
0016 #include <KIMAP/IdleJob>
0017 #include <KIMAP/SelectJob>
0018 #include <KIMAP/Session>
0019 
0020 #include <QTimer>
0021 
0022 #include "imapresource.h"
0023 #include "sessionpool.h"
0024 
0025 #include <chrono>
0026 
0027 namespace
0028 {
0029 // RFC2177 says clients should restart IDLE every 29 minutes, as
0030 // servers MAY consider clients inactive after 30 minutes.
0031 // TODO: Make configurable to support less RF-conformant servers
0032 static const auto IdleTimeout = std::chrono::minutes(29);
0033 }
0034 
0035 ImapIdleManager::ImapIdleManager(ResourceStateInterface::Ptr state, SessionPool *pool, ImapResourceBase *parent)
0036     : QObject(parent)
0037     , m_sessionRequestId(0)
0038     , m_pool(pool)
0039     , m_resource(parent)
0040     , m_state(state)
0041     , m_lastMessageCount(-1)
0042     , m_lastRecentCount(-1)
0043 {
0044     connect(pool, &SessionPool::sessionRequestDone, this, &ImapIdleManager::onSessionRequestDone);
0045     m_sessionRequestId = m_pool->requestSession();
0046 
0047     m_idleTimeout = new QTimer(this);
0048     m_idleTimeout->setSingleShot(true);
0049     connect(m_idleTimeout, &QTimer::timeout, this, &ImapIdleManager::restartIdle);
0050 }
0051 
0052 ImapIdleManager::~ImapIdleManager()
0053 {
0054     stop();
0055     if (m_pool) {
0056         if (m_sessionRequestId) {
0057             m_pool->cancelSessionRequest(m_sessionRequestId);
0058         }
0059         if (m_session) {
0060             m_pool->releaseSession(m_session);
0061         }
0062     }
0063 }
0064 
0065 void ImapIdleManager::stop()
0066 {
0067     m_idleTimeout->stop();
0068     if (m_idle) {
0069         m_idle->stop();
0070         disconnect(m_idle, nullptr, this, nullptr);
0071         m_idle = nullptr;
0072     }
0073     if (m_pool) {
0074         disconnect(m_pool, nullptr, this, nullptr);
0075     }
0076 }
0077 
0078 KIMAP::Session *ImapIdleManager::session() const
0079 {
0080     return m_session;
0081 }
0082 
0083 void ImapIdleManager::reconnect()
0084 {
0085     qCDebug(IMAPRESOURCE_LOG) << "attempting to reconnect IDLE session";
0086     if (m_session == nullptr && m_pool->isConnected() && m_sessionRequestId == 0) {
0087         m_sessionRequestId = m_pool->requestSession();
0088     }
0089 }
0090 
0091 void ImapIdleManager::onSessionRequestDone(qint64 requestId, KIMAP::Session *session, int errorCode, const QString & /*errorString*/)
0092 {
0093     if (requestId != m_sessionRequestId || session == nullptr || errorCode != SessionPool::NoError) {
0094         return;
0095     }
0096 
0097     m_session = session;
0098     m_sessionRequestId = 0;
0099 
0100     connect(m_pool, &SessionPool::connectionLost, this, &ImapIdleManager::onConnectionLost);
0101     connect(m_pool, &SessionPool::disconnectDone, this, &ImapIdleManager::onPoolDisconnect);
0102 
0103     startIdle();
0104 }
0105 
0106 void ImapIdleManager::startIdle()
0107 {
0108     const auto idleMailBox = m_state->mailBoxForCollection(m_state->collection());
0109     if (m_session->selectedMailBox() != idleMailBox) {
0110         auto select = new KIMAP::SelectJob(m_session);
0111         select->setMailBox(idleMailBox);
0112         connect(select, &KIMAP::SelectJob::result, this, &ImapIdleManager::onSelectDone);
0113         select->start();
0114     }
0115 
0116     m_idle = new KIMAP::IdleJob(m_session);
0117     connect(m_idle.data(), &KIMAP::IdleJob::mailBoxStats, this, &ImapIdleManager::onStatsReceived);
0118     connect(m_idle.data(), &KIMAP::IdleJob::mailBoxMessageFlagsChanged, this, &ImapIdleManager::onFlagsChanged);
0119     connect(m_idle.data(), &KIMAP::IdleJob::result, this, &ImapIdleManager::onIdleStopped);
0120     m_idle->start();
0121     m_idleTimeout->start(std::chrono::milliseconds(IdleTimeout).count());
0122 }
0123 
0124 void ImapIdleManager::restartIdle()
0125 {
0126     qCDebug(IMAPRESOURCE_LOG) << "Restarting IDLE to prevent server from disconnecting me!";
0127     if (m_idle) {
0128         m_idle->stop(); // this will invoke onIdleStopped(), which will automatically reconnect
0129     }
0130 }
0131 
0132 void ImapIdleManager::onConnectionLost(KIMAP::Session *session)
0133 {
0134     if (session == m_session) {
0135         // Our session becomes invalid, so get ride of
0136         // the pointer, we don't need to release it once the
0137         // task is done
0138         m_session = nullptr;
0139         QMetaObject::invokeMethod(this, &ImapIdleManager::reconnect, Qt::QueuedConnection);
0140     }
0141 }
0142 
0143 void ImapIdleManager::onPoolDisconnect()
0144 {
0145     // All the sessions in the pool we used changed,
0146     // so get ride of the pointer, we don't need to
0147     // release our session anymore
0148     m_pool = nullptr;
0149 }
0150 
0151 void ImapIdleManager::onSelectDone(KJob *job)
0152 {
0153     auto select = static_cast<KIMAP::SelectJob *>(job);
0154 
0155     m_lastMessageCount = select->messageCount();
0156     m_lastRecentCount = select->recentCount();
0157 }
0158 
0159 void ImapIdleManager::onIdleStopped()
0160 {
0161     qCDebug(IMAPRESOURCE_LOG) << "IDLE dropped maybe we should reconnect?";
0162     m_idle = nullptr;
0163     if (m_session) {
0164         qCDebug(IMAPRESOURCE_LOG) << "Restarting the IDLE session!";
0165         startIdle();
0166     }
0167 }
0168 
0169 void ImapIdleManager::onStatsReceived(KIMAP::IdleJob *job, const QString &mailBox, int messageCount, int recentCount)
0170 {
0171     qCDebug(IMAPRESOURCE_LOG) << "IDLE stats received:" << job << mailBox << messageCount << recentCount;
0172     qCDebug(IMAPRESOURCE_LOG) << "Cached information:" << m_state->collection().remoteId() << m_state->collection().id() << m_lastMessageCount
0173                               << m_lastRecentCount;
0174 
0175     // It seems we're not in sync with the cache, resync is needed
0176     if (messageCount != m_lastMessageCount || recentCount != m_lastRecentCount) {
0177         m_lastMessageCount = messageCount;
0178         m_lastRecentCount = recentCount;
0179 
0180         qCDebug(IMAPRESOURCE_LOG) << "Resync needed for" << mailBox << m_state->collection().id();
0181         m_resource->synchronizeCollection(m_state->collection().id());
0182     }
0183 }
0184 
0185 void ImapIdleManager::onFlagsChanged(KIMAP::IdleJob *job)
0186 {
0187     Q_UNUSED(job)
0188     qCDebug(IMAPRESOURCE_LOG) << "IDLE flags changed in" << m_session->selectedMailBox();
0189     m_resource->synchronizeCollection(m_state->collection().id());
0190 }
0191 
0192 #include "moc_imapidlemanager.cpp"