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