Akonadi

collectionscheduler.cpp
1 /*
2  SPDX-FileCopyrightText: 2014 Daniel Vr├ítil <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "collectionscheduler.h"
8 #include "akonadiserver_debug.h"
9 #include "storage/datastore.h"
10 #include "storage/selectquerybuilder.h"
11 
12 #include <chrono>
13 #include <private/tristate_p.h>
14 
15 #include <QDateTime>
16 #include <QTimer>
17 
18 using namespace std::literals::chrono_literals;
19 
20 namespace Akonadi
21 {
22 namespace Server
23 {
24 /**
25  * @warning: QTimer's methods are not virtual, so it's necessary to always call
26  * methods on pointer to PauseableTimer!
27  */
28 class PauseableTimer : public QTimer
29 {
30  Q_OBJECT
31 
32 public:
33  explicit PauseableTimer(QObject *parent = nullptr)
34  : QTimer(parent)
35  {
36  }
37 
38  void start(std::chrono::milliseconds interval)
39  {
40  mStarted = QDateTime::currentDateTimeUtc();
41  mPaused = QDateTime();
42  setInterval(interval);
43  QTimer::start(interval);
44  }
45 
46  void start()
47  {
48  start(std::chrono::milliseconds{interval()});
49  }
50 
51  void stop()
52  {
53  mStarted = QDateTime();
54  mPaused = QDateTime();
55  QTimer::stop();
56  }
57 
58  Q_INVOKABLE void pause()
59  {
60  if (!isActive() || isPaused()) {
61  return;
62  }
63 
65  QTimer::stop();
66  }
67 
68  Q_INVOKABLE void resume()
69  {
70  if (!isPaused()) {
71  return;
72  }
73 
74  const auto remainder = std::chrono::milliseconds{interval()} - std::chrono::seconds{mStarted.secsTo(mPaused)};
75  start(qMax(std::chrono::milliseconds{0}, remainder));
76  mPaused = QDateTime();
77  // Update mStarted so that pause() can be called repeatedly
78  mStarted = QDateTime::currentDateTimeUtc();
79  }
80 
81  bool isPaused() const
82  {
83  return mPaused.isValid();
84  }
85 
86 private:
87  QDateTime mStarted;
88  QDateTime mPaused;
89 };
90 
91 } // namespace Server
92 } // namespace Akonadi
93 
94 using namespace Akonadi::Server;
95 
96 CollectionScheduler::CollectionScheduler(const QString &threadName, QThread::Priority priority, QObject *parent)
97  : AkThread(threadName, priority, parent)
98 {
99 }
100 
101 CollectionScheduler::~CollectionScheduler()
102 {
103 }
104 
105 // Called in secondary thread
106 void CollectionScheduler::quit()
107 {
108  delete mScheduler;
109  mScheduler = nullptr;
110 
111  AkThread::quit();
112 }
113 
114 void CollectionScheduler::inhibit(bool inhibit)
115 {
116  if (inhibit) {
117  const bool success = QMetaObject::invokeMethod(mScheduler, &PauseableTimer::pause, Qt::QueuedConnection);
118  Q_ASSERT(success);
119  Q_UNUSED(success)
120  } else {
121  const bool success = QMetaObject::invokeMethod(mScheduler, &PauseableTimer::resume, Qt::QueuedConnection);
122  Q_ASSERT(success);
123  Q_UNUSED(success)
124  }
125 }
126 
127 int CollectionScheduler::minimumInterval() const
128 {
129  return mMinInterval;
130 }
131 
132 CollectionScheduler::TimePoint CollectionScheduler::nextScheduledTime(qint64 collectionId) const
133 {
134  QMutexLocker locker(&mScheduleLock);
135  const auto i = constFind(collectionId);
136  if (i != mSchedule.cend()) {
137  return i.key();
138  }
139  return {};
140 }
141 
142 std::chrono::milliseconds CollectionScheduler::currentTimerInterval() const
143 {
144  return std::chrono::milliseconds(mScheduler->isActive() ? mScheduler->interval() : 0);
145 }
146 
147 void CollectionScheduler::setMinimumInterval(int intervalMinutes)
148 {
149  // No mutex -- you can only call this before starting the thread
150  mMinInterval = intervalMinutes;
151 }
152 
153 void CollectionScheduler::collectionAdded(qint64 collectionId)
154 {
155  Collection collection = Collection::retrieveById(collectionId);
156  DataStore::self()->activeCachePolicy(collection);
157  if (shouldScheduleCollection(collection)) {
159  this,
160  [this, collection]() {
161  scheduleCollection(collection);
162  },
164  }
165 }
166 
167 void CollectionScheduler::collectionChanged(qint64 collectionId)
168 {
169  QMutexLocker locker(&mScheduleLock);
170  const auto it = constFind(collectionId);
171  if (it != mSchedule.cend()) {
172  const Collection &oldCollection = it.value();
173  Collection changed = Collection::retrieveById(collectionId);
174  DataStore::self()->activeCachePolicy(changed);
175  if (hasChanged(oldCollection, changed)) {
176  if (shouldScheduleCollection(changed)) {
177  locker.unlock();
178  // Scheduling the changed collection will automatically remove the old one
180  this,
181  [this, changed]() {
182  scheduleCollection(changed);
183  },
185  } else {
186  locker.unlock();
187  // If the collection should no longer be scheduled then remove it
188  collectionRemoved(collectionId);
189  }
190  }
191  } else {
192  // We don't know the collection yet, but maybe now it can be scheduled
193  collectionAdded(collectionId);
194  }
195 }
196 
197 void CollectionScheduler::collectionRemoved(qint64 collectionId)
198 {
199  QMutexLocker locker(&mScheduleLock);
200  auto it = find(collectionId);
201  if (it != mSchedule.end()) {
202  const bool reschedule = it == mSchedule.begin();
203  mSchedule.erase(it);
204 
205  // If we just remove currently scheduled collection, schedule the next one
206  if (reschedule) {
207  QMetaObject::invokeMethod(this, &CollectionScheduler::startScheduler, Qt::QueuedConnection);
208  }
209  }
210 }
211 
212 // Called in secondary thread
213 void CollectionScheduler::startScheduler()
214 {
215  QMutexLocker locker(&mScheduleLock);
216  // Don't restart timer if we are paused.
217  if (mScheduler->isPaused()) {
218  return;
219  }
220 
221  if (mSchedule.isEmpty()) {
222  // Stop the timer. It will be started again once some collection is scheduled
223  mScheduler->stop();
224  return;
225  }
226 
227  // Get next collection to expire and start the timer
228  const auto next = mSchedule.constBegin().key();
229  // TimePoint uses a signed representation internally (int64_t), so we get negative result when next is in the past
230  const auto delayUntilNext = std::chrono::duration_cast<std::chrono::milliseconds>(next - std::chrono::steady_clock::now());
231  mScheduler->start(qMax(std::chrono::milliseconds{0}, delayUntilNext));
232 }
233 
234 // Called in secondary thread
235 void CollectionScheduler::scheduleCollection(Collection collection, bool shouldStartScheduler)
236 {
237  DataStore::self()->activeCachePolicy(collection);
238 
239  QMutexLocker locker(&mScheduleLock);
240  auto i = find(collection.id());
241  if (i != mSchedule.end()) {
242  mSchedule.erase(i);
243  }
244 
245  if (!shouldScheduleCollection(collection)) {
246  return;
247  }
248 
249  const int expireMinutes = qMax(mMinInterval, collectionScheduleInterval(collection));
250  TimePoint nextCheck(std::chrono::steady_clock::now() + std::chrono::minutes(expireMinutes));
251 
252  // Check whether there's another check scheduled within a minute after this one.
253  // If yes, then delay this check so that it's scheduled together with the others
254  // This is a minor optimization to reduce wakeups and SQL queries
255  auto it = constLowerBound(nextCheck);
256  if (it != mSchedule.cend() && it.key() - nextCheck < 1min) {
257  nextCheck = it.key();
258 
259  // Also check whether there's another checked scheduled within a minute before
260  // this one.
261  } else if (it != mSchedule.cbegin()) {
262  --it;
263  if (nextCheck - it.key() < 1min) {
264  nextCheck = it.key();
265  }
266  }
267 
268  mSchedule.insert(nextCheck, collection);
269  if (shouldStartScheduler && !mScheduler->isActive()) {
270  locker.unlock();
271  startScheduler();
272  }
273 }
274 
275 CollectionScheduler::ScheduleMap::const_iterator CollectionScheduler::constFind(qint64 collectionId) const
276 {
277  return std::find_if(mSchedule.cbegin(), mSchedule.cend(), [collectionId](const Collection &c) {
278  return c.id() == collectionId;
279  });
280 }
281 
282 CollectionScheduler::ScheduleMap::iterator CollectionScheduler::find(qint64 collectionId)
283 {
284  return std::find_if(mSchedule.begin(), mSchedule.end(), [collectionId](const Collection &c) {
285  return c.id() == collectionId;
286  });
287 }
288 
289 // separate method so we call the const version of QMap::lowerBound
290 CollectionScheduler::ScheduleMap::const_iterator CollectionScheduler::constLowerBound(TimePoint timestamp) const
291 {
292  return mSchedule.lowerBound(timestamp);
293 }
294 
295 // Called in secondary thread
296 void CollectionScheduler::init()
297 {
298  AkThread::init();
299 
300  mScheduler = new PauseableTimer();
301  mScheduler->setSingleShot(true);
302  connect(mScheduler, &QTimer::timeout, this, &CollectionScheduler::schedulerTimeout);
303 
304  // Only retrieve enabled collections and referenced collections, we don't care
305  // about anything else
307  if (!qb.exec()) {
308  qCWarning(AKONADISERVER_LOG) << "Failed to query initial collections for scheduler!";
309  qCWarning(AKONADISERVER_LOG) << "Not a fatal error, no collections will be scheduled for sync or cache expiration!";
310  }
311 
312  const Collection::List collections = qb.result();
313  for (const Collection &collection : collections) {
314  scheduleCollection(collection);
315  }
316 
317  startScheduler();
318 }
319 
320 // Called in secondary thread
321 void CollectionScheduler::schedulerTimeout()
322 {
323  QMutexLocker locker(&mScheduleLock);
324 
325  // Call stop() explicitly to reset the timer
326  mScheduler->stop();
327 
328  const auto timestamp = mSchedule.constBegin().key();
329  const QList<Collection> collections = mSchedule.values(timestamp);
330  mSchedule.remove(timestamp);
331  locker.unlock();
332 
333  for (const Collection &collection : collections) {
334  collectionExpired(collection);
335  scheduleCollection(collection, false);
336  }
337 
338  startScheduler();
339 }
340 
341 #include "collectionscheduler.moc"
void start()
void stop(Ekos::AlignState mode)
Q_SCRIPTABLE Q_NOREPLY void start()
QVector< Collection > List
Describes a list of collections.
Definition: collection.h:84
QDateTime currentDateTimeUtc()
Definition: item.h:32
void timeout()
QVector< T > result()
Returns the result of this SELECT query.
QueuedConnection
Q_SCRIPTABLE Q_NOREPLY void pause()
const QList< QKeySequence > & find()
bool exec()
Executes the query, returns true on success.
Helper class for creating and executing database SELECT queries.
void stop()
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
const QList< QKeySequence > & next()
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Jun 30 2022 03:51:45 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.