1 /*
2     SPDX-FileCopyrightText: 2017 Krzysztof Nowicki <krissn@op.pl>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #pragma once
8 
9 #include <functional>
10 
11 #include <QObject>
12 #include <QTimer>
13 
14 #include <Akonadi/Collection>
15 #include <Akonadi/CollectionFetchJob>
16 #include <Akonadi/Monitor>
17 
18 class StateMonitorBase : public QObject
19 {
20     Q_OBJECT
21 public:
StateMonitorBase(QObject * parent)22     explicit StateMonitorBase(QObject *parent)
23         : QObject(parent)
24     {
25     }
26 
27     ~StateMonitorBase() override = default;
28 Q_SIGNALS:
29     void stateReached();
30     void errorOccurred();
31 };
32 
33 template<typename T> class CollectionStateMonitor : public StateMonitorBase
34 {
35 public:
36     using StateComparisonFunc = std::function<bool(const Akonadi::Collection &, const T &)>;
37     CollectionStateMonitor(QObject *parent,
38                            const QHash<QString, T> &stateHash,
39                            const QString &inboxId,
40                            const StateComparisonFunc &comparisonFunc,
41                            int recheckInterval = 0);
42     ~CollectionStateMonitor() override = default;
monitor()43     Akonadi::Monitor &monitor()
44     {
45         return mMonitor;
46     }
47 
48     void forceRecheck();
49 
50 private:
51     void stateChanged(const Akonadi::Collection &col);
52 
53     Akonadi::Monitor mMonitor;
54     QSet<QString> mPending;
55     const QHash<QString, T> &mStateHash;
56     StateComparisonFunc mComparisonFunc;
57     const QString &mInboxId;
58     QTimer mRecheckTimer;
59 };
60 
61 template<typename T>
CollectionStateMonitor(QObject * parent,const QHash<QString,T> & stateHash,const QString & inboxId,const StateComparisonFunc & comparisonFunc,int recheckInterval)62 CollectionStateMonitor<T>::CollectionStateMonitor(QObject *parent,
63                                                   const QHash<QString, T> &stateHash,
64                                                   const QString &inboxId,
65                                                   const StateComparisonFunc &comparisonFunc,
66                                                   int recheckInterval)
67     : StateMonitorBase(parent)
68     , mMonitor(this)
69     , mPending(stateHash.keyBegin(), stateHash.keyEnd())
70     , mStateHash(stateHash)
71     , mComparisonFunc(comparisonFunc)
72     , mInboxId(inboxId)
73     , mRecheckTimer(this)
74 {
75     connect(&mMonitor, &Akonadi::Monitor::collectionAdded, this, [this](const Akonadi::Collection &col, const Akonadi::Collection &) {
76         stateChanged(col);
77     });
78     connect(&mMonitor, qOverload<const Akonadi::Collection &>(&Akonadi::Monitor::collectionChanged), this, [this](const Akonadi::Collection &col) {
79         stateChanged(col);
80     });
81     if (recheckInterval > 0) {
82         mRecheckTimer.setInterval(recheckInterval);
83         connect(&mRecheckTimer, &QTimer::timeout, this, &CollectionStateMonitor::forceRecheck);
84         mRecheckTimer.start();
85     }
86 }
87 
stateChanged(const Akonadi::Collection & col)88 template<typename T> void CollectionStateMonitor<T>::stateChanged(const Akonadi::Collection &col)
89 {
90     auto remoteId = col.remoteId();
91     auto state = mStateHash.find(remoteId);
92     if (state == mStateHash.end()) {
93         qDebug() << "Cannot find state for collection" << remoteId;
94         Q_EMIT errorOccurred();
95     }
96     if (mComparisonFunc(col, *state)) {
97         mPending.remove(remoteId);
98     } else {
99         mPending.insert(remoteId);
100     }
101     if (mPending.empty()) {
102         Q_EMIT stateReached();
103     }
104 }
105 
forceRecheck()106 template<typename T> void CollectionStateMonitor<T>::forceRecheck()
107 {
108     auto fetchJob = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this);
109     fetchJob->setFetchScope(mMonitor.collectionFetchScope());
110     if (fetchJob->exec()) {
111         const auto collections = fetchJob->collections();
112         for (const auto &col : collections) {
113             const auto remoteId = col.remoteId();
114             const auto state = mStateHash.find(remoteId);
115             if (state != mStateHash.end()) {
116                 stateChanged(col);
117             }
118         }
119     }
120 }
121 
122