swift
datacache.h
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Copyright (C) 2015 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #ifndef SWIFT_MISC_DATACACHE_H
7 #define SWIFT_MISC_DATACACHE_H
8 
9 #include <future>
10 #include <memory>
11 #include <utility>
12 #include <vector>
13 
14 #include <QDateTime>
15 #include <QFileSystemWatcher>
16 #include <QJsonArray>
17 #include <QJsonObject>
18 #include <QList>
19 #include <QLockFile>
20 #include <QMap>
21 #include <QMutex>
22 #include <QObject>
23 #include <QPointer>
24 #include <QSet>
25 #include <QString>
26 #include <QStringList>
27 #include <QUuid>
28 #include <QtGlobal>
29 
30 #include "misc/identifier.h"
31 #include "misc/propertyindexref.h"
32 #include "misc/statusmessage.h"
33 #include "misc/swiftmiscexport.h"
34 #include "misc/threadutils.h"
35 #include "misc/valuecache.h"
36 #include "misc/valuecacheprivate.h"
37 #include "misc/variantmap.h"
38 #include "misc/worker.h"
39 
40 namespace swift::misc
41 {
42  namespace private_ns
43  {
48  class SWIFT_MISC_EXPORT CDataPageQueue : public QObject
49  {
50  Q_OBJECT
51 
52  public:
54  CDataPageQueue(CValuePage *parent) : QObject(parent), m_page(parent) {}
55 
58  void queueValuesFromCache(const swift::misc::CValueCachePacket &values, QObject *changedBy);
59 
62 
64  void setQueuedValueFromCache(const QString &key);
65 
66  private:
67  CValuePage *m_page = nullptr;
68  QList<std::pair<CValueCachePacket, QObject *>> m_queue;
69  QMutex m_mutex;
70  };
71  } // namespace private_ns
72 
73  class CDataCache;
74 
80  {
81  public:
83  CDataCacheRevision(const QString &basename);
84 
87 
93 
95  class LockGuard;
96 
102  LockGuard beginUpdate(const QMap<QString, qint64> &timestamps, bool updateUuid = true, bool pinsOnly = false);
103 
105  void writeNewRevision(const QMap<QString, qint64> &timestamps, const QSet<QString> &excludeKeys = {});
106 
108  void regenerate(const CValueCachePacket &keys);
109 
112  void finishUpdate(bool keepPromises = false);
113 
115  bool isFound() const;
116 
118  bool isPendingRead() const;
119 
122 
124  QSet<QString> keysWithNewerTimestamps() const;
125 
128 
130  bool isNewerValueAvailable(const QString &key, qint64 timestamp);
131 
134  std::future<void> promiseLoadedValue(const QString &key, qint64 currentTimestamp);
135 
137  std::vector<std::promise<void>> loadedValuePromises();
138 
141 
143  QString timestampsAsString() const;
144 
146  void setTimeToLive(const QString &key, int ttl);
147 
149  void overrideTimestamp(const QString &key, qint64 timestamp);
150 
152  qint64 getTimestampOnDisk(const QString &key);
153 
155  void pinValue(const QString &key);
156 
158  void deferValue(const QString &key);
159 
161  void admitValue(const QString &key);
162 
164  void sessionValue(const QString &key);
165 
166  private:
167  mutable QRecursiveMutex m_mutex;
168  bool m_updateInProgress = false;
169  bool m_found = false;
170  bool m_pendingRead = false;
171  bool m_pendingWrite = false;
172  QString m_basename;
173  QLockFile m_lockFile { m_basename + "/.lock" };
174  QUuid m_uuid;
175  QMap<QString, qint64> m_timestamps;
176  QMap<QString, qint64> m_originalTimestamps;
177  QMap<QString, qint64> m_timesToLive;
178  QSet<QString> m_pinnedValues;
179  QSet<QString> m_deferredValues;
180  QSet<QString> m_admittedValues;
181  QSet<QString> m_admittedQueue;
182  QMap<QString, QUuid> m_sessionValues;
183  std::vector<std::promise<void>> m_promises;
184 
185  class Session;
186  std::unique_ptr<Session> m_session;
187 
188  static QJsonObject toJson(const QMap<QString, qint64> &timestamps);
189  static QMap<QString, qint64> fromJson(const QJsonObject &timestamps);
190  static QJsonArray toJson(const QSet<QString> &pins);
191  static QSet<QString> fromJson(const QJsonArray &pins);
192  static QJsonObject toJson(const QMap<QString, QUuid> &session);
193  static QMap<QString, QUuid> sessionFromJson(const QJsonObject &session);
194  };
195 
201  {
202  Q_OBJECT
203 
204  public:
206  static const QStringList &getLogCategories();
207 
209  CDataCacheSerializer(CDataCache *owner, const QString &revisionFileName);
210 
213 
220  CDataCacheRevision::LockGuard loadFromStore(const swift::misc::CValueCachePacket &baseline, bool defer = false,
221  bool pinsOnly = false);
222 
223  signals:
226  const swift::misc::CIdentifier &originator);
227 
228  private:
229  const QString &persistentStore() const;
230  void applyDeferredChanges();
231  void deliverPromises(std::vector<std::promise<void>>);
232 
233  CDataCache *const m_cache = nullptr;
234  QUuid m_revision;
235  const QString m_revisionFileName;
236  swift::misc::CValueCachePacket m_deferredChanges;
237  };
238 
245  {
246  Q_OBJECT
247 
248  public:
250  virtual ~CDataCache() override;
251 
253  static CDataCache *instance();
254 
256  static const QString &persistentStore();
257 
259  static const QString &revisionFileName();
260 
262  static QString filenameForKey(const QString &key);
263 
265  QStringList enumerateStore() const;
266 
268  bool synchronize(const QString &key);
269 
271  void setTimeToLive(const QString &key, int ttl);
272 
274  void renewTimestamp(const QString &key, qint64 timestamp);
275 
277  qint64 getTimestampOnDisk(const QString &key);
278 
280  void pinValue(const QString &key);
281 
283  void deferValue(const QString &key);
284 
286  void admitValue(const QString &key, bool triggerLoad);
287 
289  void sessionValue(const QString &key);
290 
292  static const QString &relativeFilePath();
293 
294  private:
295  CDataCache();
296 
297  void saveToStoreAsync(const swift::misc::CValueCachePacket &values);
298  void loadFromStoreAsync();
299 
300  virtual void connectPage(private_ns::CValuePage *page) override;
301 
302  QFileSystemWatcher m_watcher;
303 
304  CDataCacheSerializer *m_serializer = nullptr;
305  CDataCacheRevision m_revision { persistentStore() + "/" };
306  friend class CDataCacheSerializer; // to access m_revision and protected members of CValueCache
307  };
308 
313  template <typename Trait>
314  class CData : public CCached<typename Trait::type>
315  {
316  public:
319  template <typename T>
320  CData(T *owner)
321  : CData::CCached(CDataCache::instance(), Trait::key(), Trait::humanReadable(), Trait::isValid,
322  Trait::defaultValue(), owner)
323  {
324  if (!this->isInitialized())
325  {
326  this->onOwnerNameChanged([this, owner] { private_ns::reconstruct(this, owner); });
327  return;
328  }
329  if (Trait::timeToLive() >= 0)
330  {
331  CDataCache::instance()->setTimeToLive(this->getKey(), Trait::timeToLive());
332  }
333  if (Trait::isPinned()) { CDataCache::instance()->pinValue(this->getKey()); }
334  if (Trait::isDeferred()) { CDataCache::instance()->deferValue(this->getKey()); }
335  if (Trait::isSession()) { CDataCache::instance()->sessionValue(this->getKey()); }
336  static_assert(!(Trait::isPinned() && Trait::isDeferred()), "trait can not be both pinned and deferred");
337  }
338 
343  template <typename T, typename F>
344  CData(T *owner, F slot) : CData(owner)
345  {
346  this->setNotifySlot(slot);
347  }
348 
350  CStatusMessage set(const typename Trait::type &value, qint64 timestamp = 0)
351  {
352  CDataCache::instance()->admitValue(this->getKey(), false);
353  return CCached<typename Trait::type>::set(value, timestamp);
354  }
355 
357  CStatusMessage setProperty(CPropertyIndexRef index, const CVariant &value, qint64 timestamp = 0)
358  {
359  CDataCache::instance()->admitValue(this->getKey(), false);
360  return CCached<typename Trait::type>::setProperty(index, value, timestamp);
361  }
362 
364  CStatusMessage setDefault() { return this->set(Trait::defaultValue()); }
365 
367  QString getFilename() const { return CDataCache::filenameForKey(this->getKey()); }
368 
370  bool isStale() const
371  {
372  return Trait::timeToLive() >= 0 &&
373  this->getTimestamp() + Trait::timeToLive() > QDateTime::currentMSecsSinceEpoch();
374  }
375 
377  void renewTimestamp(qint64 timestamp)
378  {
379  return CDataCache::instance()->renewTimestamp(this->getKey(), timestamp);
380  }
381 
383  QDateTime getAvailableTimestamp() const
384  {
385  if (Trait::isDeferred())
386  {
387  return QDateTime::fromMSecsSinceEpoch(CDataCache::instance()->getTimestampOnDisk(this->getKey()));
388  }
389  return this->getTimestamp();
390  }
391 
393  void admit()
394  {
395  if (Trait::isDeferred()) { CDataCache::instance()->admitValue(this->getKey(), true); }
396  }
397 
400  void synchronize()
401  {
402  auto *queue = this->m_page->template findChild<private_ns::CDataPageQueue *>();
403  Q_ASSERT(queue);
404  this->admit();
405  const QString key(this->getKey());
408  key); // if load was in progress when admit() was called, synchronize with the next load
409 
410  // run in page thread
412  if (CThreadUtils::isInThisThread(this->m_page)) { queue->setQueuedValueFromCache(key); }
413  else
414  {
415  QPointer<QObject> myself(queue);
416  QTimer::singleShot(0, queue, [=] {
417  if (!myself) { return; }
418  queue->setQueuedValueFromCache(key);
419  });
420  }
421  }
422 
425  CStatusMessage setAndSave(const typename Trait::type &value, qint64 timestamp = 0) = delete;
427  qint64 timestamp = 0) = delete;
429 
431  CStatusMessage save() = delete;
432  };
433 
438  template <typename Trait>
439  class CDataReadOnly : public swift::misc::CData<Trait>
440  {
441  public:
443  using CData<Trait>::CData;
444 
447  CStatusMessage set(const typename Trait::type &value, qint64 timestamp = 0) = delete;
448  CStatusMessage setProperty(CPropertyIndexRef index, const CVariant &value, qint64 timestamp = 0) = delete;
450  void renewTimestamp(qint64 timestamp) = delete;
452  };
453 
457  template <typename T>
458  struct TDataTrait
459  {
461  using type = T;
462 
464  static const char *key()
465  {
466  qFatal("Not implemented");
467  return "";
468  }
469 
471  static const QString &humanReadable()
472  {
473  static const QString name;
474  return name;
475  }
476 
479  static bool isValid(const T &value, QString &reason)
480  {
481  Q_UNUSED(value);
482  Q_UNUSED(reason);
483  return true;
484  }
485 
488  static const T &defaultValue()
489  {
490  static const T def {};
491  return def;
492  }
493 
496  static int timeToLive() { return -1; }
497 
500  static constexpr bool isPinned() { return false; }
501 
504  static constexpr bool isDeferred() { return false; }
505 
509  static constexpr bool isSession() { return false; }
510 
512  TDataTrait() = delete;
513 
515  TDataTrait(const TDataTrait &) = delete;
516 
518  TDataTrait &operator=(const TDataTrait &) = delete;
519  };
520 } // namespace swift::misc
521 
522 #endif // SWIFT_MISC_DATACACHE_H
Provides access to one of the values stored in a CValueCache.
Definition: valuecache.h:353
CStatusMessage setProperty(CPropertyIndexRef index, const CVariant &value, qint64 timestamp=0)
Write a property of the value. Must be called from the thread in which the owner lives.
Definition: valuecache.h:426
bool isInitialized() const
Can be false if key contains OwnerName% and owner's objectName was empty.
Definition: valuecache.h:465
CStatusMessage set(const T &value, qint64 timestamp=0)
Write a new value. Must be called from the thread in which the owner lives.
Definition: valuecache.h:411
const QString & getKey() const
Get the key string of this value.
Definition: valuecache.h:445
void setNotifySlot(F slot)
Set a callback to be called when the value is changed by another source.
Definition: valuecache.h:384
QDateTime getTimestamp() const
Return the time when this value was updated.
Definition: valuecache.h:449
Base class for a long-lived worker object which lives in its own thread.
Definition: worker.h:275
Singleton derived class of CValueCache, for core dynamic data.
Definition: datacache.h:245
static CDataCache * instance()
Return the singleton instance.
void sessionValue(const QString &key)
Method used for implementing session values.
static const QString & revisionFileName()
Revision file name.
virtual ~CDataCache()
Destructor.
void setTimeToLive(const QString &key, int ttl)
Method used for implementing TTL.
static const QString & persistentStore()
The directory where core data are stored.
void deferValue(const QString &key)
Method used for implementing deferring values.
bool synchronize(const QString &key)
Method used for implementing CData::synchronize.
qint64 getTimestampOnDisk(const QString &key)
Method used for implementing loading timestamp without value.
QStringList enumerateStore() const
Return all files where data may be stored.
static const QString & relativeFilePath()
Relative file path in application data directory.
void admitValue(const QString &key, bool triggerLoad)
Method used for implementing deferring values.
static QString filenameForKey(const QString &key)
Return the filename where the value with the given key may be stored.
void pinValue(const QString &key)
Method used for implementing pinning values.
void renewTimestamp(const QString &key, qint64 timestamp)
Method used for implementing timestamp renewal.
Encapsulates metastate about how the version of the cache in memory compares to the one on disk.
Definition: datacache.h:80
std::future< void > promiseLoadedValue(const QString &key, qint64 currentTimestamp)
Return a future which will be made ready when the value is loaded. Future is invalid if value is not ...
void setTimeToLive(const QString &key, int ttl)
Set TTL value that will be written to the revision file.
void admitValue(const QString &key)
Set the flag which will cause a deferred-load value to be loaded.
void writeNewRevision(const QMap< QString, qint64 > &timestamps, const QSet< QString > &excludeKeys={})
During update, writes a new revision file with new timestamps.
QSet< QString > keysWithNewerTimestamps() const
During update, returns keys which have on-disk timestamps newer than in-memory. Guaranteed not empty.
CDataCacheRevision & operator=(const CDataCacheRevision &)=delete
Non-copyable.
void pinValue(const QString &key)
Set the flag which will cause the value to be pre-loaded.
void breakPromises()
Abandon all promises.
LockGuard beginUpdate(const QMap< QString, qint64 > &timestamps, bool updateUuid=true, bool pinsOnly=false)
Get the state of the disk cache, and prepare to update any values which are out of date....
void overrideTimestamp(const QString &key, qint64 timestamp)
Causes the new timestamp to be written to the revision file.
bool isPendingRead() const
True if beginUpdate found some values with timestamps newer than in memory.
void regenerate(const CValueCachePacket &keys)
Write a new revision file with keys deduced from the available JSON files.
void finishUpdate(bool keepPromises=false)
Release the revision file lock and mark everything up-to-date (called by LockGuard destructor).
const QMap< QString, qint64 > & newerTimestamps() const
During update, returns timestamps which have on-disk timestamps newer than in-memory....
CDataCacheRevision(const CDataCacheRevision &)=delete
Non-copyable.
void notifyPendingWrite()
Call before beginUpdate if there is a write pending, so update will start even if there is nothing to...
bool isNewerValueAvailable(const QString &key, qint64 timestamp)
During update, returns true if the on-disk timestamp of this key is newer than in-memory.
CDataCacheRevision(const QString &basename)
Construct the single instance of the revision metastate.
void deferValue(const QString &key)
Set the flag which will cause the value to be deferred-loaded.
QString timestampsAsString() const
Keys with timestamps.
void sessionValue(const QString &key)
Set the flag which will cause a value to be reset when starting a new session.
bool isFound() const
Existing revision file was found.
qint64 getTimestampOnDisk(const QString &key)
Read the revision file to get a timestamp.
std::vector< std::promise< void > > loadedValuePromises()
Returns (by move) the container of promises to load values.
Worker which performs (de)serialization on behalf of CDataCache, in a separate thread so that the mai...
Definition: datacache.h:201
CDataCacheSerializer(CDataCache *owner, const QString &revisionFileName)
Constructor.
static const QStringList & getLogCategories()
Log categories.
void valuesLoadedFromStore(const swift::misc::CValueCachePacket &values, const swift::misc::CIdentifier &originator)
Signal back to the cache when values have been loaded.
CDataCacheRevision::LockGuard loadFromStore(const swift::misc::CValueCachePacket &baseline, bool defer=false, bool pinsOnly=false)
Load values from persistent store. Called once per second. Also called by saveToStore,...
void saveToStore(const swift::misc::CVariantMap &values, const swift::misc::CValueCachePacket &baseline)
Save values to persistent store. Called whenever a value is changed locally.
Class template for accessing a specific value in the CDataCache.
Definition: datacache.h:315
CStatusMessage setProperty(CPropertyIndexRef index, const CVariant &value, qint64 timestamp=0)
Write a property of the value. Must be called from the thread in which the owner lives.
Definition: datacache.h:357
CStatusMessage set(const typename Trait::type &value, qint64 timestamp=0)
Write a new value. Must be called from the thread in which the owner lives.
Definition: datacache.h:350
QString getFilename() const
Return the file that is used for persistence for this value.
Definition: datacache.h:367
CStatusMessage setAndSave(const typename Trait::type &value, qint64 timestamp=0)=delete
Data cache doesn't support setAndSave (because set() already causes save anyway).
void admit()
If the value is load-deferred, trigger the deferred load (async).
Definition: datacache.h:393
CStatusMessage save()=delete
Data cache doesn't support save (because currently set value is saved already).
CStatusMessage setAndSaveProperty(CPropertyIndexRef index, const CVariant &value, qint64 timestamp=0)=delete
Data cache doesn't support setAndSave (because set() already causes save anyway).
CData(T *owner)
Constructor.
Definition: datacache.h:320
void renewTimestamp(qint64 timestamp)
Don't change the value, but write a new timestamp, to extend the life of the value.
Definition: datacache.h:377
QDateTime getAvailableTimestamp() const
Get the timestamp of the value, or of the deferred value that is available to be loaded.
Definition: datacache.h:383
CData(T *owner, F slot)
Constructor.
Definition: datacache.h:344
bool isStale() const
True if the current timestamp is older than the TTL (time to live).
Definition: datacache.h:370
void synchronize()
If the value is currently being loaded, wait for it to finish loading, and call the notification slot...
Definition: datacache.h:400
CStatusMessage setDefault()
Reset the data to its default value.
Definition: datacache.h:364
Class template for read-only access to a specific value in the CDataCache.
Definition: datacache.h:440
CStatusMessage setProperty(CPropertyIndexRef index, const CVariant &value, qint64 timestamp=0)=delete
Deleted mutators.
CStatusMessage set(const typename Trait::type &value, qint64 timestamp=0)=delete
Deleted mutators.
void renewTimestamp(qint64 timestamp)=delete
Deleted mutators.
CStatusMessage setDefault()=delete
Deleted mutators.
Value object encapsulating information identifying a component of a modular distributed swift process...
Definition: identifier.h:29
Non-owning reference to a CPropertyIndex with a subset of its features.
Streamable status message, e.g.
static bool isInThisThread(const QObject *toBeTested)
Is the current thread the object's thread?
Definition: threadutils.cpp:16
Manages a map of { QString, CVariant } pairs, which can be distributed among multiple processes.
Definition: valuecache.h:154
Value class used for signalling changed values in the cache.
Definition: valuecache.h:67
Wrapper around QVariant which provides transparent access to CValueObject methods of the contained ob...
Definition: variant.h:66
Map of { QString, CVariant } pairs.
Definition: variantmap.h:35
Decorator for CValuePage which allows incoming remote changes to be queued to allow for more flexibil...
Definition: datacache.h:49
void setQueuedValueFromCache(const QString &key)
Synchronize with one specific change in the queue, leave the rest for later.
void queueValuesFromCache(const swift::misc::CValueCachePacket &values, QObject *changedBy)
Add to the queue to synchronize with a change caused by another page.
CDataPageQueue(CValuePage *parent)
Constructor.
Definition: datacache.h:54
void setQueuedValuesFromCache()
Synchronize with changes queued by queueValuesFromCache.
Free functions in swift::misc.
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition: threadutils.h:30
Base class for traits to be used as template argument to swift::misc::CData.
Definition: datacache.h:459
static const QString & humanReadable()
Optional human readable name.
Definition: datacache.h:471
TDataTrait(const TDataTrait &)=delete
Deleted copy constructor.
TDataTrait()=delete
Deleted default constructor.
static constexpr bool isSession()
If true, then upon starting an application, value will be overwritten with the default if there are n...
Definition: datacache.h:509
static constexpr bool isPinned()
If true, then value will be synchronously loaded when CDataCache is constructed. Good for small,...
Definition: datacache.h:500
static bool isValid(const T &value, QString &reason)
Validator function. Return true if the argument is valid, false otherwise. Default implementation jus...
Definition: datacache.h:479
static const T & defaultValue()
Return the value to use in case the supplied value does not satisfy the validator....
Definition: datacache.h:488
static int timeToLive()
Number of milliseconds after which cached value becomes stale. Default is -1 which means value never ...
Definition: datacache.h:496
TDataTrait & operator=(const TDataTrait &)=delete
Deleted copy assignment operator.
static constexpr bool isDeferred()
If true, then value will not be loaded until it is explicitly admitted. Good for large values the loa...
Definition: datacache.h:504
T type
Data type of the value.
Definition: datacache.h:461
static const char * key()
Key string of the value. Reimplemented in derived class.
Definition: datacache.h:464
#define SWIFT_MISC_EXPORT
Export a class or function from the library.