File indexing completed on 2025-02-16 04:50:11

0001 /*
0002     SPDX-FileCopyrightText: 2015-2020 Krzysztof Nowicki <krissn@op.pl>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "ewssubscriptionmanager.h"
0008 
0009 #include "ewsgeteventsrequest.h"
0010 #include "ewsgetstreamingeventsrequest.h"
0011 #include "ewsresource_debug.h"
0012 #include "ewssettings.h"
0013 #include "ewssubscribedfoldersjob.h"
0014 #include "ewssubscriberequest.h"
0015 #include "ewsunsubscriberequest.h"
0016 #include <QPointer>
0017 
0018 // TODO: Allow customization
0019 static constexpr uint pollInterval = 10; /* seconds */
0020 
0021 static constexpr uint streamingTimeout = 30; /* minutes */
0022 
0023 static constexpr uint streamingConnTimeout = 60; /* seconds */
0024 
0025 EwsSubscriptionManager::EwsSubscriptionManager(EwsClient &client, const EwsId &rootId, EwsSettings *settings, QObject *parent)
0026     : QObject(parent)
0027     , mEwsClient(client)
0028     , mPollTimer(this)
0029     , mMsgRootId(rootId)
0030     , mFolderTreeChanged(false)
0031     , mSettings(settings)
0032 {
0033     mStreamingEvents = mEwsClient.serverVersion().supports(EwsServerVersion::StreamingSubscription);
0034     mStreamingTimer.setInterval(streamingConnTimeout * 1000);
0035     mStreamingTimer.setSingleShot(true);
0036     connect(&mStreamingTimer, &QTimer::timeout, this, &EwsSubscriptionManager::streamingConnectionTimeout);
0037 }
0038 
0039 EwsSubscriptionManager::~EwsSubscriptionManager()
0040 {
0041     cancelSubscription();
0042 }
0043 
0044 void EwsSubscriptionManager::start()
0045 {
0046     // Set-up change notification subscription (if needed)
0047     if (mSettings->eventSubscriptionId().isEmpty()) {
0048         setupSubscription();
0049     } else {
0050         reset();
0051     }
0052 
0053     if (!mStreamingEvents) {
0054         mPollTimer.setInterval(pollInterval * 1000);
0055         mPollTimer.setSingleShot(false);
0056         connect(&mPollTimer, &QTimer::timeout, this, &EwsSubscriptionManager::getEvents);
0057     }
0058 }
0059 
0060 void EwsSubscriptionManager::cancelSubscription()
0061 {
0062     if (!mSettings->eventSubscriptionId().isEmpty()) {
0063         QPointer<EwsUnsubscribeRequest> req = new EwsUnsubscribeRequest(mEwsClient, this);
0064         req->setSubscriptionId(mSettings->eventSubscriptionId());
0065         req->exec();
0066         mSettings->setEventSubscriptionId(QString());
0067         mSettings->setEventSubscriptionWatermark(QString());
0068         mSettings->save();
0069     }
0070 }
0071 
0072 void EwsSubscriptionManager::setupSubscription()
0073 {
0074     auto job = new EwsSubscribedFoldersJob(mEwsClient, mSettings, this);
0075     connect(job, &EwsRequest::result, this, &EwsSubscriptionManager::verifySubFoldersRequestFinished);
0076     job->start();
0077 }
0078 
0079 void EwsSubscriptionManager::verifySubFoldersRequestFinished(KJob *job)
0080 {
0081     if (!job->error()) {
0082         auto folderJob = qobject_cast<EwsSubscribedFoldersJob *>(job);
0083         Q_ASSERT(folderJob);
0084 
0085         setupSubscriptionReq(folderJob->folders());
0086     } else {
0087         Q_EMIT connectionError();
0088     }
0089 }
0090 
0091 void EwsSubscriptionManager::setupSubscriptionReq(const EwsId::List &ids)
0092 {
0093     auto req = new EwsSubscribeRequest(mEwsClient, this);
0094     // req->setAllFolders(true);
0095     QList<EwsEventType> events;
0096     events << EwsNewMailEvent;
0097     events << EwsMovedEvent;
0098     events << EwsCopiedEvent;
0099     events << EwsModifiedEvent;
0100     events << EwsDeletedEvent;
0101     events << EwsCreatedEvent;
0102     req->setEventTypes(events);
0103     if (mStreamingEvents) {
0104         req->setType(EwsSubscribeRequest::StreamingSubscription);
0105     } else {
0106         req->setType(EwsSubscribeRequest::PullSubscription);
0107     }
0108     req->setFolderIds(ids);
0109     req->setAllFolders(false);
0110     connect(req, &EwsRequest::result, this, &EwsSubscriptionManager::subscribeRequestFinished);
0111     req->start();
0112 }
0113 
0114 void EwsSubscriptionManager::reset()
0115 {
0116     mPollTimer.stop();
0117     getEvents();
0118     if (!mStreamingEvents) {
0119         mPollTimer.start();
0120     }
0121 }
0122 
0123 void EwsSubscriptionManager::resetSubscription()
0124 {
0125     mPollTimer.stop();
0126     cancelSubscription();
0127     setupSubscription();
0128 }
0129 
0130 void EwsSubscriptionManager::subscribeRequestFinished(KJob *job)
0131 {
0132     if (!job->error()) {
0133         auto req = qobject_cast<EwsSubscribeRequest *>(job);
0134         if (req) {
0135             mSettings->setEventSubscriptionId(req->response().subscriptionId());
0136             if (mStreamingEvents) {
0137                 getEvents();
0138             } else {
0139                 mSettings->setEventSubscriptionWatermark(req->response().watermark());
0140                 getEvents();
0141                 mPollTimer.start();
0142             }
0143             mSettings->save();
0144         }
0145     } else {
0146         Q_EMIT connectionError();
0147     }
0148 }
0149 
0150 void EwsSubscriptionManager::getEvents()
0151 {
0152     if (mStreamingEvents) {
0153         auto req = new EwsGetStreamingEventsRequest(mEwsClient, this);
0154         req->setSubscriptionId(mSettings->eventSubscriptionId());
0155         req->setTimeout(streamingTimeout);
0156         connect(req, &EwsRequest::result, this, &EwsSubscriptionManager::getEventsRequestFinished);
0157         connect(req, &EwsGetStreamingEventsRequest::eventsReceived, this, &EwsSubscriptionManager::streamingEventsReceived);
0158         req->start();
0159         mEventReq = req;
0160         mStreamingTimer.start();
0161     } else {
0162         auto req = new EwsGetEventsRequest(mEwsClient, this);
0163         req->setSubscriptionId(mSettings->eventSubscriptionId());
0164         req->setWatermark(mSettings->eventSubscriptionWatermark());
0165         connect(req, &EwsRequest::result, this, &EwsSubscriptionManager::getEventsRequestFinished);
0166         req->start();
0167         mEventReq = req;
0168     }
0169 }
0170 
0171 void EwsSubscriptionManager::getEventsRequestFinished(KJob *job)
0172 {
0173     mStreamingTimer.stop();
0174 
0175     mEventReq->deleteLater();
0176     mEventReq = nullptr;
0177 
0178     auto req = qobject_cast<EwsEventRequestBase *>(job);
0179     if (!req) {
0180         qCWarningNC(EWSRES_LOG) << QStringLiteral("Invalid EwsEventRequestBase job object.");
0181         reset();
0182         return;
0183     }
0184 
0185     if ((!req->responses().isEmpty())
0186         && ((req->responses()[0].responseCode() == QLatin1StringView("ErrorInvalidSubscription"))
0187             || (req->responses()[0].responseCode() == QLatin1StringView("ErrorSubscriptionNotFound")))) {
0188         mSettings->setEventSubscriptionId(QString());
0189         mSettings->setEventSubscriptionWatermark(QString());
0190         mSettings->save();
0191         resetSubscription();
0192         return;
0193     }
0194 
0195     if (!job->error()) {
0196         processEvents(req, true);
0197         if (mStreamingEvents) {
0198             getEvents();
0199         }
0200     } else {
0201         reset();
0202     }
0203 }
0204 
0205 void EwsSubscriptionManager::streamingEventsReceived(KJob *job)
0206 {
0207     mStreamingTimer.stop();
0208 
0209     auto req = qobject_cast<EwsEventRequestBase *>(job);
0210     if (!req) {
0211         qCWarningNC(EWSRES_LOG) << QStringLiteral("Invalid EwsEventRequestBase job object.");
0212         reset();
0213         return;
0214     }
0215 
0216     if (!job->error()) {
0217         processEvents(req, false);
0218         mStreamingTimer.start();
0219     }
0220 }
0221 
0222 void EwsSubscriptionManager::streamingConnectionTimeout()
0223 {
0224     if (mEventReq) {
0225         qCWarningNC(EWSRES_LOG) << QStringLiteral("Streaming request timeout - restarting");
0226         mEventReq->deleteLater();
0227         mEventReq = nullptr;
0228         getEvents();
0229     }
0230 }
0231 
0232 void EwsSubscriptionManager::processEvents(EwsEventRequestBase *req, bool finished)
0233 {
0234     bool moreEvents = false;
0235 
0236     const auto responses{req->responses()};
0237     for (const EwsGetEventsRequest::Response &resp : responses) {
0238         const auto notifications{resp.notifications()};
0239         for (const EwsGetEventsRequest::Notification &nfy : notifications) {
0240             const auto nfyEvents{nfy.events()};
0241             for (const EwsGetEventsRequest::Event &event : nfyEvents) {
0242                 mSettings->setEventSubscriptionWatermark(event.watermark());
0243                 switch (event.type()) {
0244                 case EwsCopiedEvent:
0245                 case EwsMovedEvent:
0246                     if (!event.itemIsFolder()) {
0247                         mUpdatedFolderIds.insert(event.oldParentFolderId());
0248                     }
0249                 /* fall through */
0250                 case EwsCreatedEvent:
0251                 case EwsDeletedEvent:
0252                 case EwsModifiedEvent:
0253                 case EwsNewMailEvent:
0254                     if (event.itemIsFolder()) {
0255                         mFolderTreeChanged = true;
0256                     } else {
0257                         mUpdatedFolderIds.insert(event.parentFolderId());
0258                     }
0259                     break;
0260                 case EwsStatusEvent:
0261                     // Do nothing
0262                     break;
0263                 default:
0264                     break;
0265                 }
0266             }
0267             if (nfy.hasMoreEvents()) {
0268                 moreEvents = true;
0269             }
0270         }
0271         if (mStreamingEvents) {
0272             auto req2 = qobject_cast<EwsGetStreamingEventsRequest *>(req);
0273             if (req2) {
0274                 req2->eventsProcessed(resp);
0275             }
0276         }
0277     }
0278 
0279     if (moreEvents && finished) {
0280         getEvents();
0281     } else {
0282         if (mFolderTreeChanged) {
0283             qCDebugNC(EWSRES_LOG) << QStringLiteral("Found modified folder tree");
0284             Q_EMIT folderTreeModified();
0285             mFolderTreeChanged = false;
0286         }
0287         if (!mUpdatedFolderIds.isEmpty()) {
0288             qCDebugNC(EWSRES_LOG) << QStringLiteral("Found %1 modified folders").arg(mUpdatedFolderIds.size());
0289             const auto updated = mUpdatedFolderIds.values();
0290             Q_EMIT foldersModified(EwsId::List(updated.cbegin(), updated.cend()));
0291             mUpdatedFolderIds.clear();
0292         }
0293     }
0294 }
0295 
0296 #include "moc_ewssubscriptionmanager.cpp"