14 #include <QCoreApplication>
15 #include <QDBusMetaType>
17 #include <QDirIterator>
21 #include <QJsonDocument>
23 #include <QMetaMethod>
24 #include <QMutexLocker>
25 #include <QStandardPaths>
39 using private_ns::CValuePage;
43 bool isSafeToIncrement(
const T &value)
45 return value < std::numeric_limits<T>::max();
49 std::pair<QString &, std::atomic<bool> &> getCacheRootDirectoryMutable()
52 static std::atomic<bool> frozen {
false };
53 return { dir, frozen };
58 Q_ASSERT_X(!getCacheRootDirectoryMutable().second, Q_FUNC_INFO,
"Too late to call this function");
59 getCacheRootDirectoryMutable().first = dir;
62 const QString &CValueCache::getCacheRootDirectory()
64 getCacheRootDirectoryMutable().second =
true;
65 return getCacheRootDirectoryMutable().first;
73 : m_saved(saved), m_valuesChanged(valuesChanged)
75 for (
auto it = values.cbegin(); it != values.cend(); ++it)
77 implementationOf(*this).insert(CDictionary::cend(), it.key(), std::make_pair(it.value(), timestamp));
81 void CValueCachePacket::insert(
const QString &key,
const CVariant &value, qint64 timestamp)
83 CDictionary::insert(key, std::make_pair(value, timestamp));
86 void CValueCachePacket::insert(
const CVariantMap &values, qint64 timestamp)
88 for (
auto it = values.cbegin(); it != values.cend(); ++it)
90 CDictionary::insert(it.key(), std::make_pair(it.value(), timestamp));
94 CVariantMap CValueCachePacket::toVariantMap()
const
97 for (
auto it = cbegin(); it != cend(); ++it)
107 for (
auto it = cbegin(); it != cend(); ++it)
114 QString CValueCachePacket::toTimestampMapString(
const QStringList &keys)
const
117 for (
const auto &key : keys)
119 QString time = contains(key) ?
120 QDateTime::fromMSecsSinceEpoch(value(key).second, Qt::UTC).toString(Qt::ISODate) :
122 result.push_back(key +
" (" + time +
")");
124 return result.join(
",");
129 for (
auto it = times.cbegin(); it != times.cend(); ++it)
131 if (!contains(it.key())) {
continue; }
132 (*this)[it.key()].second = it.value();
136 CValueCachePacket CValueCachePacket::takeByKey(
const QString &key)
140 copy.removeByKeyIf([=](
const QString &key2) {
return key2 != key; });
144 void CValueCachePacket::registerMetadata()
146 MetaType::registerMetadata();
147 qDBusRegisterMetaType<value_type>();
154 const QStringList &CValueCache::getLogCategories()
156 static const QStringList cats({
"swift.valuecache", CLogCategories::services() });
160 CValueCache::CValueCache(
int fileSplitDepth, QObject *parent) : QObject(parent), m_fileSplitDepth(fileSplitDepth)
162 Q_ASSERT_X(fileSplitDepth >= 0, Q_FUNC_INFO,
"Negative value not allowed, use 0 for maximum split depth");
163 Q_ASSERT_X(QThread::currentThread() == qApp->thread(), Q_FUNC_INFO,
"Cache constructed in wrong thread");
166 struct CValueCache::Element
168 Element(
const QString &key) : m_key(key) {}
171 int m_pendingChanges = 0;
172 bool m_saved =
false;
173 std::atomic<qint64> m_timestamp { 0 };
176 CValueCache::Element &CValueCache::getElement(
const QString &key)
178 QMutexLocker lock(&m_mutex);
179 return getElement(key, std::as_const(m_elements).lowerBound(key));
184 QMutexLocker lock(&m_mutex);
185 if (pos != m_elements.cend() && pos.key() == key) {
return **pos; }
186 Q_ASSERT(pos == std::as_const(m_elements).lowerBound(key));
187 return **m_elements.insert(pos, key, ElementPtr(
new Element(key)));
190 std::tuple<CVariant, qint64, bool> CValueCache::getValue(
const QString &key)
192 QMutexLocker lock(&m_mutex);
193 const auto &element = getElement(key);
194 return std::make_tuple(element.m_value, element.m_timestamp.load(), element.m_saved);
197 CVariantMap CValueCache::getAllValues(
const QString &keyPrefix)
const
199 QMutexLocker lock(&m_mutex);
201 for (
const auto &element : elementsStartingWith(keyPrefix))
203 if (!element->m_value.isValid()) {
continue; }
209 CVariantMap CValueCache::getAllValues(
const QStringList &keys)
const
211 QMutexLocker lock(&m_mutex);
213 for (
const auto &key : keys)
215 auto it = m_elements.constFind(key);
216 if (it == m_elements.cend()) {
continue; }
217 if (!(*it)->m_value.isValid()) {
continue; }
218 map.insert(key, (*it)->m_value);
223 CValueCachePacket CValueCache::getAllValuesWithTimestamps(
const QString &keyPrefix)
const
225 QMutexLocker lock(&m_mutex);
226 CValueCachePacket map;
227 for (
const auto &element : elementsStartingWith(keyPrefix))
229 if (!element->m_value.isValid()) {
continue; }
230 map.insert(element->m_key, element->m_value, element->m_timestamp);
235 QStringList CValueCache::getAllUnsavedKeys(
const QString &keyPrefix)
const
237 QMutexLocker lock(&m_mutex);
239 for (
const auto &element : elementsStartingWith(keyPrefix))
241 if (element->m_value.isValid() && !element->m_saved) { keys.push_back(element->m_key); }
246 void CValueCache::insertValues(
const CValueCachePacket &values)
248 QMutexLocker lock(&m_mutex);
249 changeValues(values);
252 void CValueCache::changeValues(
const CValueCachePacket &values)
254 QMutexLocker lock(&m_mutex);
255 if (values.isEmpty()) {
return; }
257 auto out = std::as_const(m_elements).lowerBound(values.cbegin().key());
258 auto end = std::as_const(m_elements).upperBound((values.cend() - 1).key());
259 for (
auto in = values.cbegin(); in != values.cend(); ++in)
261 while (out !=
end && out.key() < in.key()) { ++out; }
262 auto &element = getElement(in.key(), out);
264 if (values.valuesChanged())
266 Q_ASSERT(isSafeToIncrement(element.m_pendingChanges));
267 element.m_pendingChanges++;
268 element.m_value = in.value();
269 element.m_timestamp = in.timestamp();
271 element.m_saved = values.isSaved();
273 if (values.valuesChanged()) { emit valuesChanged(values, sender()); }
274 emit valuesChangedByLocal(values);
276 if (!isSignalConnected(QMetaMethod::fromSignal(&CValueCache::valuesChangedByLocal)))
278 changeValuesFromRemote(values, CIdentifier());
282 void CValueCache::changeValuesFromRemote(
const CValueCachePacket &values,
const CIdentifier &originator)
284 QMutexLocker lock(&m_mutex);
285 if (values.isEmpty()) {
return; }
286 if (!values.valuesChanged())
288 if (values.isSaved()) { emit valuesSaveRequested(values); }
291 CValueCachePacket ratifiedChanges(values.isSaved());
292 CValueCachePacket ackedChanges(values.isSaved());
294 auto out = std::as_const(m_elements).lowerBound(values.cbegin().key());
295 auto end = std::as_const(m_elements).upperBound((values.cend() - 1).key());
296 for (
auto in = values.cbegin(); in != values.cend(); ++in)
298 while (out !=
end && out.key() < in.key()) { ++out; }
299 auto &element = getElement(in.key(), out);
301 if (originator.hasApplicationProcessId())
303 element.m_pendingChanges--;
304 Q_ASSERT(element.m_pendingChanges >= 0);
305 ackedChanges.insert(in.key(), in.value(), in.timestamp());
307 else if (element.m_pendingChanges ==
310 element.m_value = in.value();
311 element.m_timestamp = in.timestamp();
312 element.m_saved = values.isSaved();
313 ratifiedChanges.insert(in.key(), in.value(), in.timestamp());
316 if (!ratifiedChanges.isEmpty())
318 if (ratifiedChanges.isSaved()) { emit valuesSaveRequested(ratifiedChanges); }
319 emit valuesChanged(ratifiedChanges,
nullptr);
321 if (!ackedChanges.isEmpty() && ackedChanges.isSaved()) { emit valuesSaveRequested(ackedChanges); }
324 QJsonObject CValueCache::saveToJson(
const QString &keyPrefix)
const
326 return getAllValues(keyPrefix).toMemoizedJson();
329 void CValueCache::loadFromJson(
const QJsonObject &json)
332 map.convertFromMemoizedJson(json);
333 if (!map.isEmpty()) { insertValues({ map, QDateTime::currentMSecsSinceEpoch() }); }
336 CStatusMessageList CValueCache::loadFromJsonNoThrow(
const QJsonObject &json,
const CLogCategoryList &categories,
337 const QString &prefix)
340 auto messages = map.convertFromMemoizedJsonNoThrow(json, categories, prefix);
341 if (!map.isEmpty()) { insertValues({ map, QDateTime::currentMSecsSinceEpoch() }); }
345 CStatusMessage CValueCache::saveToFiles(
const QString &dir,
const QString &keyPrefix)
347 QMutexLocker lock(&m_mutex);
348 auto values = getAllValues(keyPrefix);
349 auto status = saveToFiles(dir, values);
350 if (status.isSuccess()) { markAllAsSaved(keyPrefix); }
354 CStatusMessage CValueCache::saveToFiles(
const QString &dir,
const QStringList &keys)
356 QMutexLocker lock(&m_mutex);
357 auto values = getAllValues(keys);
358 auto status = saveToFiles(dir, values);
359 if (status.isSuccess()) { markAllAsSaved(keys); }
363 CStatusMessage CValueCache::saveToFiles(
const QString &dir,
const CVariantMap &values,
364 const QString &keysMessage)
const
367 for (
auto it = values.cbegin(); it != values.cend(); ++it)
369 Q_ASSERT(it.value().isValid());
370 namespaces[it.key().section(
'/', 0, m_fileSplitDepth - 1)].insert(it.key(), it.value());
372 if (!QDir::root().mkpath(dir)) {
return CStatusMessage(
this).error(u
"Failed to create directory '%1'") << dir; }
373 for (
auto it = namespaces.cbegin(); it != namespaces.cend(); ++it)
375 CAtomicFile file(dir +
"/" + it.key() +
".json");
376 if (!QDir::root().mkpath(QFileInfo(file).path()))
378 return CStatusMessage(
this).error(u
"Failed to create directory '%1'") << QFileInfo(file).path();
380 if (!file.open(QFile::ReadWrite | QFile::Text))
382 return CStatusMessage(
this).error(u
"Failed to open %1: %2") << file.fileName() << file.errorString();
384 auto json = QJsonDocument::fromJson(file.readAll());
385 if (json.isArray() || (json.isNull() && !json.isEmpty()))
387 return CStatusMessage(
this).error(u
"Invalid JSON format in %1") << file.fileName();
389 auto object = json.object();
390 json.setObject(it->mergeToMemoizedJson(
object));
392 if (!(file.seek(0) && file.resize(0) && file.write(json.toJson()) > 0 && file.checkedClose()))
394 return CStatusMessage(
this).error(u
"Failed to write to %1: %2")
395 << file.fileName() << file.errorString();
398 return CStatusMessage(
this).info(u
"Written '%1' to value cache in '%2'")
399 << (keysMessage.isEmpty() ? values.keys().to<QStringList>().join(
",") : keysMessage) << dir;
402 CStatusMessage CValueCache::loadFromFiles(
const QString &dir)
404 QMutexLocker lock(&m_mutex);
405 CValueCachePacket values;
406 auto status = loadFromFiles(dir, {}, getAllValues(), values);
408 insertValues(values);
412 CStatusMessage CValueCache::loadFromFiles(
const QString &dir,
const QSet<QString> &keys,
413 const CVariantMap ¤tValues, CValueCachePacket &o_values,
414 const QString &keysMessage,
bool keysOnly)
const
416 if (!QDir(dir).exists()) {
return CStatusMessage(
this).warning(u
"No such directory '%1'") << dir; }
417 if (!QDir(dir).isReadable())
419 return CStatusMessage(
this).error(u
"Failed to read from directory '%1'") << dir;
423 for (
const auto &key : keys)
425 keysInFiles[key.section(
'/', 0, m_fileSplitDepth - 1) +
".json"].push_back(key);
429 QDirIterator iter(dir, {
"*.json" }, QDir::Files, QDirIterator::Subdirectories);
430 while (iter.hasNext()) { keysInFiles.insert(QDir(dir).relativeFilePath(iter.next()), {}); }
433 for (
auto it = keysInFiles.cbegin(); it != keysInFiles.cend(); ++it)
435 QFile file(QDir(dir).absoluteFilePath(it.key()));
436 if (!file.exists()) {
continue; }
437 if (!file.open(QFile::ReadOnly | QFile::Text))
439 return CStatusMessage(
this).error(u
"Failed to open %1: %2") << file.fileName() << file.errorString();
441 auto json = QJsonDocument::fromJson(file.readAll());
442 if (json.isArray() || (json.isNull() && !json.isEmpty()))
444 return CStatusMessage(
this).error(u
"Invalid JSON format in %1") << file.fileName();
450 for (
const auto &key : json.object().keys()) { temp.insert(key, {}); }
454 const QString messagePrefix = QStringLiteral(
"Parsing %1").arg(it.key());
455 auto messages = temp.convertFromMemoizedJsonNoThrow(json.object(), it.value(),
this, messagePrefix);
456 if (it.value().isEmpty())
458 messages.push_back(temp.convertFromMemoizedJsonNoThrow(json.object(),
this, messagePrefix));
460 if (!messages.isEmpty())
464 CLogMessage::preformatted(messages);
467 temp.removeDuplicates(currentValues);
468 o_values.insert(temp, QFileInfo(file).lastModified().toMSecsSinceEpoch());
470 return CStatusMessage(
this).info(u
"Loaded cache values '%1' from '%2' '%3'")
471 << (keysMessage.isEmpty() ? o_values.keys().to<QStringList>().join(
",") : keysMessage) << dir
472 << (ok ?
"successfully" :
"with errors");
475 void CValueCache::backupFile(QFile &file)
const
477 QDir dir = getCacheRootDirectory();
478 QString relative =
"backups/" + dir.relativeFilePath(file.fileName());
479 QString absolute = dir.absoluteFilePath(relative);
480 absolute +=
"." + QDateTime::currentDateTime().toString(QStringLiteral(
"yyMMddhhmmss"));
481 if (QFile::exists(absolute)) {
return; }
482 if (!dir.mkpath(QFileInfo(relative).path()))
484 CLogMessage(
this).error(u
"Failed to create %1") << QFileInfo(absolute).path();
487 if (!file.copy(absolute))
489 CLogMessage(
this).error(u
"Failed to back up %1: %2") << QFileInfo(file).fileName() << file.errorString();
492 CLogMessage(
this).info(u
"Backed up %1 to %2") << QFileInfo(file).fileName() << dir.absoluteFilePath(
"backups");
495 void CValueCache::markAllAsSaved(
const QString &keyPrefix)
497 QMutexLocker lock(&m_mutex);
498 for (
const auto &element : elementsStartingWith(keyPrefix)) { element->m_saved =
true; }
501 void CValueCache::markAllAsSaved(
const QStringList &keys)
503 QMutexLocker lock(&m_mutex);
504 for (
const auto &key : keys) { getElement(key).m_saved =
true; }
507 QString CValueCache::filenameForKey(
const QString &key)
const
509 return key.section(
'/', 0, m_fileSplitDepth - 1) +
".json";
512 QStringList CValueCache::enumerateFiles(
const QString &dir)
const
514 auto values = getAllValues();
516 for (
auto it = values.begin(); it != values.end(); ++it) { files.insert(dir +
"/" + filenameForKey(it.key())); }
517 return files.values();
520 void CValueCache::clearAllValues(
const QString &keyPrefix)
522 QMutexLocker lock(&m_mutex);
523 auto values = getAllValues(keyPrefix);
524 for (
auto it = values.begin(); it != values.end(); ++it) { it.value() = CVariant(); }
525 changeValues({ values, 0 });
528 QString CValueCache::getHumanReadableName(
const QString &key)
const
530 QMutexLocker lock(&m_mutex);
531 return m_humanReadable.value(key, key);
534 QString CValueCache::getHumanReadableWithKey(
const QString &key)
const
536 QMutexLocker lock(&m_mutex);
537 QString hr = m_humanReadable.value(key);
538 return hr.isEmpty() ? key : QStringLiteral(
"%1 (%2)").arg(hr, key);
541 void CValueCache::setHumanReadableName(
const QString &key,
const QString &name)
543 QMutexLocker lock(&m_mutex);
544 if (!m_humanReadable.contains(key)) { m_humanReadable.insert(key, name); }
547 CValueCache::BatchGuard CValueCache::batchChanges(QObject *owner)
549 Q_ASSERT(QThread::currentThread() == owner->thread());
551 auto &page = CValuePage::getPageFor(owner,
this);
556 CValueCache::BatchGuard::~BatchGuard()
560 if (std::uncaught_exceptions() > 0) { m_page->abandonBatch(); }
561 else { m_page->endBatch(); }
565 void CValueCache::connectPage(CValuePage *page)
567 connect(page, &CValuePage::valuesWantToCache,
this, &CValueCache::changeValues);
568 connect(
this, &CValueCache::valuesChanged, page, &CValuePage::setValuesFromCache);
575 const QStringList &CValuePage::getLogCategories() {
return CValueCache::getLogCategories(); }
577 CValuePage::CValuePage(QObject *parent, CValueCache *cache) : QObject(parent), m_cache(cache)
579 m_cache->connectPage(
this);
582 CValuePage &CValuePage::getPageFor(QObject *parent, CValueCache *cache)
584 auto pages = parent->findChildren<CValuePage *>(
"", Qt::FindDirectChildrenOnly);
586 std::find_if(pages.cbegin(), pages.cend(), [cache](CValuePage *page) { return page->m_cache == cache; });
587 if (it == pages.cend()) {
return *
new CValuePage(parent, cache); }
591 struct CValuePage::Element
593 Element(
const QString &key,
const QString &name,
int metaType,
const Validator &validator,
594 const CVariant &defaultValue)
595 : m_key(key), m_name(name), m_metaType(metaType), m_validator(
validator), m_default(defaultValue)
598 const QString m_name;
599 const QString m_nameWithKey = m_name.isEmpty() ? m_key : QStringLiteral(
"%1 (%2)").arg(m_name, m_key);
600 LockFree<CVariant> m_value;
601 std::atomic<qint64> m_timestamp { 0 };
602 const int m_metaType = QMetaType::UnknownType;
603 const Validator m_validator;
604 const CVariant m_default;
605 NotifySlot m_notifySlot;
606 int m_pendingChanges = 0;
607 bool m_saved =
false;
610 CValuePage::Element &CValuePage::createElement(
const QString &keyTemplate,
const QString &name,
int metaType,
611 const Validator &validator,
const CVariant &defaultValue)
613 if (parent()->objectName().isEmpty() && keyTemplate.contains(
"%OwnerName%"))
615 static Element dummy(
"",
"", QMetaType::UnknownType,
nullptr, {});
619 QString key = keyTemplate;
620 key.replace(
"%Application%", QFileInfo(QCoreApplication::applicationFilePath()).completeBaseName(),
621 Qt::CaseInsensitive);
622 key.replace(
"%OwnerClass%", QString(parent()->metaObject()->
className()).replace(
"::",
"/"),
623 Qt::CaseInsensitive);
624 key.replace(
"%OwnerName%", parent()->objectName(), Qt::CaseInsensitive);
627 Q_ASSERT_X(!m_elements.contains(key),
"CValuePage",
628 "Can't have two CCached in the same object referring to the same value");
629 Q_ASSERT_X(defaultValue.isValid() ? defaultValue.userType() == metaType :
true,
"CValuePage",
630 "Metatype mismatch for default value");
631 Q_ASSERT_X(defaultValue.isValid() && validator ?
validator(defaultValue, unused) :
true,
"CValuePage",
632 "Validator rejects default value");
635 auto &element = *(m_elements[key] = ElementPtr(
new Element(key, name, metaType, validator, defaultValue)));
636 std::forward_as_tuple(element.m_value.uniqueWrite(), element.m_timestamp, element.m_saved) =
637 m_cache->getValue(key);
639 auto status = validate(element, element.m_value.read(), CStatusMessage::SeverityDebug);
640 if (!status.isEmpty())
642 element.m_value.uniqueWrite() = defaultValue;
644 if (status.getSeverity() == CStatusMessage::SeverityDebug)
646 QMutexLocker lock(&m_cache->m_warnedKeysMutex);
647 if (!m_cache->m_warnedKeys.contains(key))
649 m_cache->m_warnedKeys.insert(key);
650 CLogMessage::preformatted(status);
653 else { CLogMessage::preformatted(status); }
659 void CValuePage::setNotifySlot(Element &element,
const NotifySlot &slot) { element.m_notifySlot = slot; }
661 bool CValuePage::isInitialized(
const Element &element)
const {
return !element.m_key.isEmpty(); }
663 bool CValuePage::isValid(
const Element &element,
int typeId)
const
665 auto reader = element.m_value.read();
666 return reader->isValid() && reader->userType() == typeId;
669 const CVariant &CValuePage::getValue(
const Element &element)
const
671 Q_ASSERT_X(!element.m_key.isEmpty(), Q_FUNC_INFO,
672 "Empty key suggests an attempt to use value before objectName available for %%OwnerName%%");
673 Q_ASSERT(QThread::currentThread() == thread());
675 return element.m_value.read();
678 CVariant CValuePage::getValueCopy(
const Element &element)
const
680 Q_ASSERT_X(!element.m_key.isEmpty(), Q_FUNC_INFO,
681 "Empty key suggests an attempt to use value before objectName available for %%OwnerName%%");
682 return element.m_value.read();
685 CStatusMessage CValuePage::setValue(Element &element, CVariant value, qint64 timestamp,
bool save)
687 Q_ASSERT_X(!element.m_key.isEmpty(), Q_FUNC_INFO,
688 "Empty key suggests an attempt to use value before objectName available for %%OwnerName%%");
689 Q_ASSERT(QThread::currentThread() == thread());
691 if (timestamp == 0) { timestamp = QDateTime::currentMSecsSinceEpoch(); }
692 if (!value.isValid()) { value = element.m_value.read(); }
694 bool changed = element.m_timestamp != timestamp || element.m_value.read() != value;
695 if (!changed && !save)
697 return CStatusMessage(
this).info(u
"Value '%1' not set, same timestamp and value") << element.m_nameWithKey;
700 auto status = validate(element, value, CStatusMessage::SeverityError);
701 if (status.isSuccess())
705 element.m_saved = save;
706 emit valuesWantToCache({ { { element.m_key, value } }, 0, save,
false });
708 else if (m_batchMode > 0) { m_batchedValues[element.m_key] = value; }
711 Q_ASSERT(isSafeToIncrement(element.m_pendingChanges));
712 element.m_pendingChanges++;
713 element.m_saved = save;
715 element.m_timestamp = timestamp;
716 element.m_value.uniqueWrite() = value;
717 emit valuesWantToCache({ { { element.m_key, value } }, timestamp, save });
720 status = CStatusMessage(
this).info(u
"Set value '%1'") << element.m_nameWithKey;
725 const QString &CValuePage::getKey(
const Element &element)
const {
return element.m_key; }
727 qint64 CValuePage::getTimestamp(
const Element &element)
const
729 Q_ASSERT_X(!element.m_key.isEmpty(), Q_FUNC_INFO,
730 "Empty key suggests an attempt to use value before objectName available for %%OwnerName%%");
731 return element.m_timestamp;
734 bool CValuePage::isSaved(
const Element &element)
const {
return element.m_saved && !element.m_pendingChanges; }
736 bool CValuePage::isSaving(
const Element &element)
const {
return element.m_saved && element.m_pendingChanges; }
738 void CValuePage::setValuesFromCache(
const CValueCachePacket &values, QObject *changedBy)
740 Q_ASSERT(QThread::currentThread() == thread());
741 Q_ASSERT_X(values.valuesChanged(), Q_FUNC_INFO,
"packet with unchanged values should not reach here");
743 CSequence<NotifySlot *> notifySlots;
746 [changedBy,
this, ¬ifySlots, &values](
const QString &,
const ElementPtr &element,
747 CValueCachePacket::const_iterator it) {
748 if (changedBy ==
this)
750 element->m_pendingChanges--;
751 Q_ASSERT(element->m_pendingChanges >= 0);
753 else if (element->m_pendingChanges ==
756 auto error = validate(*element, it.value(), CStatusMessage::SeverityError);
757 if (error.isSuccess())
759 element->m_value.uniqueWrite() = it.value();
760 element->m_timestamp = it.timestamp();
761 element->m_saved = values.isSaved();
762 if (element->m_notifySlot.first &&
763 (!element->m_notifySlot.second || !notifySlots.containsBy([&](
auto slot) {
764 return slot->second == element->m_notifySlot.second;
767 notifySlots.push_back(&element->m_notifySlot);
770 else { CLogMessage::preformatted(error); }
774 for (
auto slot : notifySlots) { slot->first(parent()); }
777 void CValuePage::beginBatch()
779 Q_ASSERT(QThread::currentThread() == thread());
781 Q_ASSERT(isSafeToIncrement(m_batchMode));
782 if (m_batchMode <= 0) { m_batchedValues.clear(); }
786 void CValuePage::abandonBatch()
788 Q_ASSERT(QThread::currentThread() == thread());
790 Q_ASSERT(m_batchMode >= 0);
794 void CValuePage::endBatch()
796 Q_ASSERT(QThread::currentThread() == thread());
798 Q_ASSERT(m_batchMode >= 0);
801 if (m_batchMode <= 0 && !m_batchedValues.isEmpty())
803 qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
805 m_elements, m_batchedValues,
806 [timestamp](
const QString &,
const ElementPtr &element, CVariantMap::const_iterator it) {
807 Q_ASSERT(isSafeToIncrement(element->m_pendingChanges));
808 element->m_pendingChanges++;
809 element->m_value.uniqueWrite() = it.value();
810 element->m_timestamp = timestamp;
811 element->m_saved =
false;
813 emit valuesWantToCache({ m_batchedValues, timestamp });
817 CStatusMessage CValuePage::validate(
const Element &element,
const CVariant &value,
821 if (!value.isValid())
823 return CStatusMessage(
this, invalidSeverity, u
"Empty cache value %1",
true) << element.m_nameWithKey;
825 else if (value.userType() != element.m_metaType)
827 return CStatusMessage(
this).error(u
"Expected %1 but got %2 for %3")
828 << QMetaType::typeName(element.m_metaType) << value.typeName() << element.m_nameWithKey;
830 else if (element.m_validator && !element.m_validator(value, reason))
832 if (reason.isEmpty())
834 return CStatusMessage(
this).error(u
"%1 is not valid for %2")
835 << value.toQString() << element.m_nameWithKey;
839 return CStatusMessage(
this).error(u
"%1 (%2 for %3)")
840 << reason << value.toQString() << element.m_nameWithKey;
static const QString & normalizedApplicationDataDirectory()
swift application data directory for one specific installation (a version)
CValueCachePacket(bool saved=false, bool valuesChanged=true)
Constructor.
Free functions in swift::misc.
SWIFT_MISC_EXPORT void setMockCacheRootDirectory(const QString &path)
Overwrite the default root directory for cache and settings, for testing purposes.
QString className(const QObject *object)
Class name as from QMetaObject::className with namespace.
void forEachIntersection(const Map1 &map1, const Map2 &map2, F functor)
Call a functor for each {key,value1,value2} triple in the keywise intersection of two maps.
QMap< Key, Value > & implementationOf(QMap< Key, Value > &dict)
Identity function for API consistency with CDictionary::implementationOf.
T::const_iterator end(const LockFreeReader< T > &reader)
Non-member begin() and end() for so LockFree containers can be used in ranged for loops.
StatusSeverity
Status severities.
bool validator(int value, QString &)
Is value between 0 - 100?