swift
Loading...
Searching...
No Matches
datacache.cpp
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#include "misc/datacache.h"
7
8#include <chrono>
9#include <memory>
10#include <utility>
11
12#include <QByteArray>
13#include <QDir>
14#include <QFile>
15#include <QFlags>
16#include <QIODevice>
17#include <QJsonDocument>
18#include <QJsonValue>
19#include <QMutexLocker>
20#include <QStandardPaths>
21#include <QTimer>
22#include <Qt>
23
24#include "misc/atomicfile.h"
25#include "misc/identifier.h"
26#include "misc/logmessage.h"
27#include "misc/processinfo.h"
28
29namespace swift::misc
30{
31
32 using private_ns::CValuePage;
34
35 class CDataCacheRevision::LockGuard
36 {
37 public:
38 LockGuard(const LockGuard &) = delete;
39 LockGuard &operator=(const LockGuard &) = delete;
40 LockGuard(LockGuard &&other) noexcept : m_movedFrom(true) { *this = std::move(other); }
41 LockGuard &operator=(LockGuard &&other) noexcept
42 {
43 auto tuple = std::tie(other.m_movedFrom, other.m_keepPromises, other.m_rev);
44 std::tie(m_movedFrom, m_keepPromises, m_rev).swap(tuple);
45 return *this;
46 }
47
48 ~LockGuard()
49 {
50 if (!m_movedFrom)
51 {
52 m_rev->finishUpdate(m_keepPromises);
53 }
54 }
55
56 operator bool() const { return !m_movedFrom; }
57
58 private:
59 LockGuard() : m_movedFrom(true) {}
60 LockGuard(CDataCacheRevision *rev) : m_movedFrom(!rev), m_rev(rev) {}
61 LockGuard &keepPromises()
62 {
63 m_keepPromises = true;
64 return *this;
65 }
66 friend class CDataCacheRevision;
67
68 bool m_movedFrom = false;
69 bool m_keepPromises = false;
70 CDataCacheRevision *m_rev = nullptr;
71 };
72
73 CDataCache::CDataCache() : CValueCache(1), m_serializer(new CDataCacheSerializer { this, revisionFileName() })
74 {
75 if (!QDir::root().mkpath(persistentStore()))
76 {
77 CLogMessage(this).error(u"Failed to create directory '%1'") << persistentStore();
78 }
79
80 connect(this, &CValueCache::valuesChangedByLocal, this, &CDataCache::saveToStoreAsync);
81 connect(this, &CValueCache::valuesChangedByLocal, this, [=, this](CValueCachePacket values) {
82 values.setSaved();
83 changeValuesFromRemote(values, CIdentifier());
84 });
85 connect(&m_watcher, &QFileSystemWatcher::fileChanged, this, &CDataCache::loadFromStoreAsync);
86 connect(m_serializer, &CDataCacheSerializer::valuesLoadedFromStore, this, &CDataCache::changeValuesFromRemote,
88
89 if (!QFile::exists(revisionFileName()))
90 {
91 const bool res = QFile(revisionFileName()).open(QFile::WriteOnly);
92 SWIFT_VERIFY_X(res, Q_FUNC_INFO, "Could not open revision file");
93 }
94 m_serializer->loadFromStore({}, false, true); // load pinned values
95 singleShot(0, this,
96 [this] // only start the serializer if the main thread event loop runs
97 {
98 m_serializer->start();
99 m_watcher.addPath(revisionFileName());
100 loadFromStoreAsync();
101 });
102 }
103
104 CDataCache::~CDataCache() { m_serializer->quitAndWait(); }
105
106 CDataCache *CDataCache::instance()
107 {
108 static std::unique_ptr<CDataCache> cache(new CDataCache);
109 static auto dummy = (connect(qApp, &QObject::destroyed, cache.get(), [] { cache.reset(); }), nullptr);
110 Q_UNUSED(dummy) // declared as static to get thread-safe initialization
111 return cache.get();
112 }
113
114 const QString &CDataCache::persistentStore()
115 {
116 static const QString dir = CFileUtils::appendFilePaths(getCacheRootDirectory(), relativeFilePath());
117 return dir;
118 }
119
120 const QString &CDataCache::revisionFileName()
121 {
122 static const QString rev = CFileUtils::appendFilePaths(persistentStore(), ".rev");
123 return rev;
124 }
125
126 QString CDataCache::filenameForKey(const QString &key)
127 {
128 return CFileUtils::appendFilePaths(persistentStore(), instance()->CValueCache::filenameForKey(key));
129 }
130
131 QStringList CDataCache::enumerateStore() const { return enumerateFiles(persistentStore()); }
132
133 bool CDataCache::synchronize(const QString &key)
134 {
135 constexpr auto timeout = std::chrono::seconds(1);
136 constexpr auto ready = std::future_status::ready;
137 constexpr auto zero = std::chrono::seconds::zero();
138
139 std::future<void> future = m_revision.promiseLoadedValue(key, getTimestampSync(key));
140 if (future.valid())
141 {
142 std::future_status s {};
143 do
144 {
145 s = future.wait_for(timeout);
146 }
147 while (s != ready && m_revision.isNewerValueAvailable(key, getTimestampSync(key)));
148 if (s != ready)
149 {
150 s = future.wait_for(zero);
151 }
152 if (s != ready)
153 {
154 return false;
155 }
156
159 // maybe this happens if a cache is written and this takes a while, maybe we can
160 // use a write in prgress flag or such?
161 try
162 {
163 future.get();
164 }
165 catch (const std::future_error &)
166 {
167 return false;
168 } // broken promise
169 return true;
170 }
171 return false;
172 }
173
174 void CDataCache::setTimeToLive(const QString &key, int ttl)
175 {
176 singleShot(0, m_serializer, [this, key, ttl] { m_revision.setTimeToLive(key, ttl); });
177 }
178
179 void CDataCache::renewTimestamp(const QString &key, qint64 timestamp)
180 {
181 singleShot(0, m_serializer, [this, key, timestamp] { m_revision.overrideTimestamp(key, timestamp); });
182 }
183
184 qint64 CDataCache::getTimestampOnDisk(const QString &key) { return m_revision.getTimestampOnDisk(key); }
185
186 void CDataCache::pinValue(const QString &key)
187 {
188 singleShot(0, m_serializer, [this, key] { m_revision.pinValue(key); });
189 }
190
191 void CDataCache::deferValue(const QString &key)
192 {
193 singleShot(0, m_serializer, [this, key] { m_revision.deferValue(key); });
194 }
195
196 void CDataCache::admitValue(const QString &key, bool triggerLoad)
197 {
198 m_revision.admitValue(key);
199 if (triggerLoad)
200 {
201 loadFromStoreAsync();
202 }
203 }
204
205 void CDataCache::sessionValue(const QString &key)
206 {
207 singleShot(0, m_serializer, [this, key] { m_revision.sessionValue(key); });
208 }
209
210 const QString &CDataCache::relativeFilePath()
211 {
212 static const QString p("/data/cache/core");
213 return p;
214 }
215
216 void CDataCache::saveToStoreAsync(const swift::misc::CValueCachePacket &values)
217 {
218 singleShot(0, m_serializer,
219 [this, values] { m_serializer->saveToStore(values.toVariantMap(), getAllValuesWithTimestamps()); });
220 }
221
222 void CDataCache::loadFromStoreAsync()
223 {
224 singleShot(0, m_serializer, [this] { m_serializer->loadFromStore(getAllValuesWithTimestamps()); });
225 }
226
227 void CDataCache::connectPage(CValuePage *page)
228 {
229 auto *queue = new CDataPageQueue(page);
230 connect(page, &CValuePage::valuesWantToCache, this, &CDataCache::changeValues);
231 connect(this, &CDataCache::valuesChanged, queue, &CDataPageQueue::queueValuesFromCache, Qt::DirectConnection);
232 }
233
234 void CDataPageQueue::queueValuesFromCache(const CValueCachePacket &values, QObject *changedBy)
235 {
236 QMutexLocker lock(&m_mutex);
237 if (m_queue.isEmpty())
238 {
239 singleShot(0, this, [this] { setQueuedValuesFromCache(); });
240 }
241 m_queue.push_back(std::make_pair(values, changedBy));
242 }
243
244 void CDataPageQueue::setQueuedValuesFromCache()
245 {
246 QMutexLocker lock(&m_mutex);
247 decltype(m_queue) queue;
248 std::swap(m_queue, queue);
249 lock.unlock();
250
251 for (const auto &pair : std::as_const(queue))
252 {
253 m_page->setValuesFromCache(pair.first, pair.second);
254 }
255 }
256
257 void CDataPageQueue::setQueuedValueFromCache(const QString &key)
258 {
259 QMutexLocker lock(&m_mutex);
260
261 decltype(m_queue) filtered;
262 for (auto &pair : m_queue)
263 {
264 if (pair.first.contains(key))
265 {
266 filtered.push_back({ pair.first.takeByKey(key), pair.second });
267 }
268 }
269 lock.unlock();
270 for (const auto &pair : filtered)
271 {
272 m_page->setValuesFromCache(pair.first, pair.second);
273 }
274 }
275
276 const QStringList &CDataCacheSerializer::getLogCategories()
277 {
278 static const QStringList cats { swift::misc::CLogCategories::cache() };
279 return cats;
280 }
281
282 CDataCacheSerializer::CDataCacheSerializer(CDataCache *owner, const QString &revisionFileName)
283 : CContinuousWorker(owner, QStringLiteral("CDataCacheSerializer '%1'").arg(revisionFileName)), m_cache(owner),
284 m_revisionFileName(revisionFileName)
285 {}
286
287 const QString &CDataCacheSerializer::persistentStore() const { return CDataCache::persistentStore(); }
288
289 void CDataCacheSerializer::saveToStore(const swift::misc::CVariantMap &values,
290 const swift::misc::CValueCachePacket &baseline)
291 {
292 m_cache->m_revision.notifyPendingWrite();
293 auto lock =
294 loadFromStore(baseline, true); // last-minute check for remote changes before clobbering the revision file
295 for (const auto &key : values.keys())
296 {
297 m_deferredChanges.remove(key);
298 } // ignore changes that we are about to overwrite
299
300 if (!lock)
301 {
302 return;
303 }
304 m_cache->m_revision.writeNewRevision(baseline.toTimestampMap());
305
306 auto msg = m_cache->saveToFiles(persistentStore(), values, baseline.toTimestampMapString(values.keys()));
307 msg.setCategories(this);
308 CLogMessage::preformatted(msg);
309
310 applyDeferredChanges(); // apply changes which we grabbed at the last minute above
311 }
312
313 CDataCacheRevision::LockGuard CDataCacheSerializer::loadFromStore(const CValueCachePacket &baseline, bool defer,
314 bool pinsOnly)
315 {
316 auto lock = m_cache->m_revision.beginUpdate(baseline.toTimestampMap(), !pinsOnly, pinsOnly);
317 if (lock && m_cache->m_revision.isPendingRead())
318 {
319 CValueCachePacket newValues;
320 if (!m_cache->m_revision.isFound())
321 {
322 m_cache->loadFromFiles(persistentStore(), {}, {}, newValues, {}, true);
323 m_cache->m_revision.regenerate(newValues);
324 newValues.clear();
325 }
326 auto msg =
327 m_cache->loadFromFiles(persistentStore(), m_cache->m_revision.keysWithNewerTimestamps(),
328 baseline.toVariantMap(), newValues, m_cache->m_revision.timestampsAsString());
329 newValues.setTimestamps(m_cache->m_revision.newerTimestamps());
330
331 auto missingKeys = m_cache->m_revision.keysWithNewerTimestamps() - newValues.keys();
332 if (!missingKeys.isEmpty())
333 {
334 m_cache->m_revision.writeNewRevision({}, missingKeys);
335 }
336
337 msg.setCategories(this);
338 CLogMessage::preformatted(msg);
339 m_deferredChanges.insert(newValues);
340 }
341
342 if (!defer)
343 {
344 applyDeferredChanges();
345 }
346 return lock;
347 }
348
349 void CDataCacheSerializer::applyDeferredChanges()
350 {
351 if (!m_deferredChanges.isEmpty())
352 {
353 m_deferredChanges.setSaved();
354 emit valuesLoadedFromStore(m_deferredChanges, CIdentifier::null());
355 deliverPromises(m_cache->m_revision.loadedValuePromises());
356 m_deferredChanges.clear();
357 }
358 }
359
360 void CDataCacheSerializer::deliverPromises(std::vector<std::promise<void>> i_promises)
361 {
363 [promises = std::make_shared<decltype(i_promises)>(std::move(i_promises))]() {
364 for (auto &promise : *promises)
365 {
366 promise.set_value();
367 }
368 });
369 }
370
371 class SWIFT_MISC_EXPORT CDataCacheRevision::Session
372 {
373 public:
374 // cppcheck-suppress missingReturn
375 Session(const QString &filename) : m_filename(filename) {}
376 void updateSession();
377 const QUuid &uuid() const { return m_uuid; }
378
379 private:
380 const QString m_filename;
381 QUuid m_uuid;
382 };
383
384 CDataCacheRevision::CDataCacheRevision(const QString &basename)
385 : m_basename(basename), m_session(std::make_unique<Session>(m_basename + "/.session"))
386 {}
387
388 CDataCacheRevision::~CDataCacheRevision() = default; // Explicitly in cpp file
389
390 CDataCacheRevision::LockGuard CDataCacheRevision::beginUpdate(const QMap<QString, qint64> &timestamps,
391 bool updateUuid, bool pinsOnly)
392 {
393 QMutexLocker lock(&m_mutex);
394
395 Q_ASSERT(!m_updateInProgress);
396 Q_ASSERT(!m_lockFile.isLocked());
397
398 if (!m_lockFile.lock())
399 {
400 CLogMessage(this).error(u"Failed to lock %1: %2") << m_basename << CFileUtils::lockFileError(m_lockFile);
401 return {};
402 }
403 m_updateInProgress = true;
404 LockGuard guard(this);
405
406 m_timestamps.clear();
407 m_originalTimestamps.clear();
408
409 QFile revisionFile(CFileUtils::appendFilePaths(m_basename, ".rev"));
410 if (m_found = revisionFile.exists(); m_found)
411 {
412 if (!revisionFile.open(QFile::ReadOnly | QFile::Text))
413 {
414 CLogMessage(this).error(u"Failed to open %1: %2")
415 << revisionFile.fileName() << revisionFile.errorString();
416 return {};
417 }
418
419 auto json = QJsonDocument::fromJson(revisionFile.readAll()).object();
420 if (json.contains("uuid") && json.contains("timestamps"))
421 {
422 m_originalTimestamps = fromJson(json.value("timestamps").toObject());
423
424 QUuid id(json.value("uuid").toString());
425 if (id == m_uuid && m_admittedQueue.isEmpty())
426 {
427 if (m_pendingWrite)
428 {
429 return guard;
430 }
431 return {};
432 }
433 if (updateUuid)
434 {
435 m_uuid = id;
436 }
437
438 auto timesToLive = fromJson(json.value("ttl").toObject());
439 for (auto it = m_originalTimestamps.cbegin(); it != m_originalTimestamps.cend(); ++it)
440 {
441 auto current = timestamps.value(it.key(), -1);
442 auto ttl = timesToLive.value(it.key(), -1);
443 if (current < it.value() && (ttl < 0 || QDateTime::currentMSecsSinceEpoch() < it.value() + ttl))
444 {
445 m_timestamps.insert(it.key(), it.value());
446 }
447 }
448 if (m_timestamps.isEmpty())
449 {
450 if (m_pendingWrite)
451 {
452 return guard;
453 }
454 return {};
455 }
456
457 if (pinsOnly)
458 {
459 auto pins = fromJson(json.value("pins").toArray());
460 for (const auto &key : m_timestamps.keys()) // clazy:exclude=container-anti-pattern,range-loop
461 {
462 if (!pins.contains(key))
463 {
464 m_timestamps.remove(key);
465 }
466 }
467 }
468
469 auto deferrals = fromJson(json.value("deferrals").toArray());
470 m_admittedValues.unite(m_admittedQueue);
471 if (updateUuid)
472 {
473 m_admittedQueue.clear();
474 }
475 else if (!m_admittedQueue.isEmpty())
476 {
477 m_admittedQueue.intersect(QSet<QString>(m_timestamps.keyBegin(), m_timestamps.keyEnd()));
478 }
479
480 for (const auto &key : m_timestamps.keys()) // clazy:exclude=container-anti-pattern,range-loop
481 {
482 if (deferrals.contains(key) && !m_admittedValues.contains(key))
483 {
484 m_timestamps.remove(key);
485 }
486 }
487
488 m_session->updateSession();
489 auto sessionIds = sessionFromJson(json.value("session").toObject());
490 for (auto it = sessionIds.cbegin(); it != sessionIds.cend(); ++it)
491 {
492 m_sessionValues[it.key()] = it.value();
493 if (it.value() != m_session->uuid())
494 {
495 m_timestamps.remove(it.key());
496 m_originalTimestamps.remove(it.key());
497 }
498 }
499 }
500 else if (revisionFile.size() > 0)
501 {
502 CLogMessage(this).error(u"Invalid format of %1") << revisionFile.fileName();
503
504 if (m_pendingWrite)
505 {
506 return guard;
507 }
508 return {};
509 }
510 else
511 {
512 m_found = false;
513 }
514 }
515
516 m_pendingRead = true;
517 return guard;
518 }
519
520 void CDataCacheRevision::writeNewRevision(const QMap<QString, qint64> &i_timestamps,
521 const QSet<QString> &excludeKeys)
522 {
523 QMutexLocker lock(&m_mutex);
524
525 Q_ASSERT(m_updateInProgress);
526 Q_ASSERT(m_lockFile.isLocked());
527
528 CAtomicFile revisionFile(CFileUtils::appendFilePaths(m_basename, ".rev"));
529 if (!revisionFile.open(QFile::WriteOnly | QFile::Text))
530 {
531 CLogMessage(this).error(u"Failed to open %1: %2") << revisionFile.fileName() << revisionFile.errorString();
532 return;
533 }
534
535 m_uuid = CIdentifier().toUuid();
536 auto timestamps = m_originalTimestamps;
537 for (auto it = i_timestamps.cbegin(); it != i_timestamps.cend(); ++it)
538 {
539 if (it.value())
540 {
541 timestamps.insert(it.key(), it.value());
542 }
543 }
544 for (const auto &key : excludeKeys)
545 {
546 timestamps.remove(key);
547 }
548
549 for (auto it = timestamps.cbegin(); it != timestamps.cend(); ++it)
550 {
551 if (m_sessionValues.contains(it.key()))
552 {
553 m_sessionValues[it.key()] = m_session->uuid();
554 }
555 }
556
557 QJsonObject json;
558 json.insert("uuid", m_uuid.toString());
559 json.insert("timestamps", toJson(timestamps));
560 json.insert("ttl", toJson(m_timesToLive));
561 json.insert("pins", toJson(m_pinnedValues));
562 json.insert("deferrals", toJson(m_deferredValues));
563 json.insert("session", toJson(m_sessionValues));
564 revisionFile.write(QJsonDocument(json).toJson());
565
566 if (!revisionFile.checkedClose())
567 {
568 static const QString advice =
569 QStringLiteral("If this error persists, try restarting your computer or delete the file manually.");
570 CLogMessage(this).error(u"Failed to replace %1: %2 (%3)")
571 << revisionFile.fileName() << revisionFile.errorString() << advice;
572 }
573 }
574
575 void CDataCacheRevision::regenerate(const CValueCachePacket &keys)
576 {
577 QMutexLocker lock(&m_mutex);
578
579 Q_ASSERT(m_updateInProgress);
580 Q_ASSERT(m_lockFile.isLocked());
581
582 writeNewRevision(m_originalTimestamps = keys.toTimestampMap());
583 }
584
585 void CDataCacheRevision::finishUpdate(bool keepPromises)
586 {
587 QMutexLocker lock(&m_mutex);
588
589 Q_ASSERT(m_updateInProgress);
590 Q_ASSERT(m_lockFile.isLocked());
591
592 m_updateInProgress = false;
593 m_pendingRead = false;
594 m_pendingWrite = false;
595 if (!keepPromises)
596 {
597 breakPromises();
598 }
599 m_lockFile.unlock();
600 }
601
602 bool CDataCacheRevision::isFound() const
603 {
604 QMutexLocker lock(&m_mutex);
605
606 Q_ASSERT(m_updateInProgress);
607 return m_found;
608 }
609
610 bool CDataCacheRevision::isPendingRead() const
611 {
612 QMutexLocker lock(&m_mutex);
613
614 Q_ASSERT(m_updateInProgress);
615 return !m_timestamps.isEmpty() || !m_found;
616 }
617
618 void CDataCacheRevision::notifyPendingWrite()
619 {
620 QMutexLocker lock(&m_mutex);
621
622 m_pendingWrite = true;
623 }
624
625 QSet<QString> CDataCacheRevision::keysWithNewerTimestamps() const
626 {
627 QMutexLocker lock(&m_mutex);
628
629 Q_ASSERT(m_updateInProgress);
630 return { m_timestamps.keyBegin(), m_timestamps.keyEnd() };
631 }
632
633 const QMap<QString, qint64> &CDataCacheRevision::newerTimestamps() const
634 {
635 QMutexLocker lock(&m_mutex);
636
637 Q_ASSERT(m_updateInProgress);
638 return m_timestamps;
639 }
640
641 bool CDataCacheRevision::isNewerValueAvailable(const QString &key, qint64 timestamp)
642 {
643 QMutexLocker lock(&m_mutex);
644
645 // Temporary guard object returned by beginUpdate is deleted at the end of the full expression,
646 // don't try to split the conditional into multiple statements.
647 // If a future is still waiting for the next update to begin, we don't want to break its associated promise.
648 return (m_updateInProgress || m_pendingWrite || beginUpdate({ { key, timestamp } }, false).keepPromises()) &&
649 (m_timestamps.contains(key) || m_admittedQueue.contains(key));
650 }
651
652 std::future<void> CDataCacheRevision::promiseLoadedValue(const QString &key, qint64 currentTimestamp)
653 {
654 QMutexLocker lock(&m_mutex);
655
656 if (isNewerValueAvailable(key, currentTimestamp))
657 {
658 std::promise<void> promise;
659 auto future = promise.get_future();
660 m_promises.push_back(std::move(promise));
661 return future;
662 }
663 return {};
664 }
665
666 std::vector<std::promise<void>> CDataCacheRevision::loadedValuePromises()
667 {
668 QMutexLocker lock(&m_mutex);
669
670 Q_ASSERT(m_updateInProgress);
671 return std::move(m_promises); // move into the return value, so m_promises becomes empty
672 }
673
674 void CDataCacheRevision::breakPromises()
675 {
676 QMutexLocker lock(&m_mutex);
677
678 if (!m_promises.empty())
679 {
680 CLogMessage(this).debug() << "Breaking" << m_promises.size() << "promises";
681 m_promises.clear();
682 }
683 }
684
685 QString CDataCacheRevision::timestampsAsString() const
686 {
687 QMutexLocker lock(&m_mutex);
688
689 QStringList result;
690 for (auto it = m_timestamps.cbegin(); it != m_timestamps.cend(); ++it)
691 {
692 result.push_back(it.key() + "(" +
693 QDateTime::fromMSecsSinceEpoch(it.value(), QTimeZone::UTC).toString(Qt::ISODate) + ")");
694 }
695 return result.join(",");
696 }
697
698 void CDataCacheRevision::setTimeToLive(const QString &key, int ttl)
699 {
700 QMutexLocker lock(&m_mutex);
701
702 Q_ASSERT(!m_updateInProgress);
703 m_timesToLive.insert(key, ttl);
704 }
705
706 void CDataCacheRevision::overrideTimestamp(const QString &key, qint64 timestamp)
707 {
708 QMutexLocker lock(&m_mutex);
709
710 Q_ASSERT(!m_updateInProgress);
711 Q_ASSERT(!m_lockFile.isLocked());
712
713 if (!m_lockFile.lock())
714 {
715 CLogMessage(this).error(u"Failed to lock %1: %2") << m_basename << CFileUtils::lockFileError(m_lockFile);
716 m_lockFile.unlock();
717 return;
718 }
719
720 CAtomicFile revisionFile(CFileUtils::appendFilePaths(m_basename, ".rev"));
721 if (revisionFile.exists())
722 {
723 if (!revisionFile.open(QFile::ReadWrite | QFile::Text))
724 {
725 CLogMessage(this).error(u"Failed to open %1: %2")
726 << revisionFile.fileName() << revisionFile.errorString();
727 m_lockFile.unlock();
728 return;
729 }
730
731 auto json = QJsonDocument::fromJson(revisionFile.readAll()).object();
732 auto timestamps = json.value("timestamps").toObject();
733 timestamps.insert(key, timestamp);
734 json.insert("timestamps", timestamps);
735
736 if (revisionFile.seek(0) && revisionFile.resize(0) && revisionFile.write(QJsonDocument(json).toJson()))
737 {
738 if (!revisionFile.checkedClose())
739 {
740 static const QString advice = QStringLiteral(
741 "If this error persists, try restarting your computer or delete the file manually.");
742 CLogMessage(this).error(u"Failed to replace %1: %2 (%3)")
743 << revisionFile.fileName() << revisionFile.errorString() << advice;
744 }
745 }
746 else
747 {
748 CLogMessage(this).error(u"Failed to write to %1: %2")
749 << revisionFile.fileName() << revisionFile.errorString();
750 }
751 }
752 m_lockFile.unlock();
753 }
754
755 qint64 CDataCacheRevision::getTimestampOnDisk(const QString &key)
756 {
757 QMutexLocker lock(&m_mutex);
758
759 if (m_lockFile.isLocked())
760 {
761 return m_originalTimestamps.value(key);
762 }
763
764 if (!m_lockFile.lock())
765 {
766 CLogMessage(this).error(u"Failed to lock %1: %2") << m_basename << CFileUtils::lockFileError(m_lockFile);
767 m_lockFile.unlock();
768 return 0;
769 }
770
771 qint64 result = 0;
772 QFile revisionFile(CFileUtils::appendFilePaths(m_basename, ".rev"));
773 if (revisionFile.exists())
774 {
775 if (revisionFile.open(QFile::ReadOnly | QFile::Text))
776 {
777 auto json = QJsonDocument::fromJson(revisionFile.readAll()).object();
778 result = static_cast<qint64>(json.value("timestamps").toObject().value(key).toDouble());
779 }
780 else
781 {
782 CLogMessage(this).error(u"Failed to open %1: %2")
783 << revisionFile.fileName() << revisionFile.errorString();
784 }
785 }
786 m_lockFile.unlock();
787 return result;
788 }
789
790 void CDataCacheRevision::pinValue(const QString &key)
791 {
792 QMutexLocker lock(&m_mutex);
793
794 Q_ASSERT(!m_updateInProgress);
795 m_pinnedValues.insert(key);
796 }
797
798 void CDataCacheRevision::deferValue(const QString &key)
799 {
800 QMutexLocker lock(&m_mutex);
801
802 Q_ASSERT(!m_updateInProgress);
803 m_deferredValues.insert(key);
804 }
805
806 void CDataCacheRevision::admitValue(const QString &key)
807 {
808 QMutexLocker lock(&m_mutex);
809
810 m_admittedQueue.insert(key);
811 }
812
813 void CDataCacheRevision::sessionValue(const QString &key)
814 {
815 QMutexLocker lock(&m_mutex);
816
817 Q_ASSERT(!m_updateInProgress);
818 m_sessionValues[key]; // clazy:exclude=detaching-member
819 }
820
821 QJsonObject CDataCacheRevision::toJson(const QMap<QString, qint64> &timestamps)
822 {
823 QJsonObject result;
824 for (auto it = timestamps.begin(); it != timestamps.end(); ++it)
825 {
826 result.insert(it.key(), it.value());
827 }
828 return result;
829 }
830
831 QMap<QString, qint64> CDataCacheRevision::fromJson(const QJsonObject &timestamps)
832 {
834 for (auto it = timestamps.begin(); it != timestamps.end(); ++it)
835 {
836 result.insert(it.key(), static_cast<qint64>(it.value().toDouble()));
837 }
838 return result;
839 }
840
841 QJsonArray CDataCacheRevision::toJson(const QSet<QString> &pins)
842 {
843 QJsonArray result;
844 for (const auto &pin : pins)
845 {
846 result.push_back(pin);
847 }
848 return result;
849 }
850
851 QSet<QString> CDataCacheRevision::fromJson(const QJsonArray &pins)
852 {
853 QSet<QString> result;
854 for (auto pin : pins)
855 {
856 result.insert(pin.toString());
857 }
858 return result;
859 }
860
861 QJsonObject CDataCacheRevision::toJson(const QMap<QString, QUuid> &timestamps)
862 {
863 QJsonObject result;
864 for (auto it = timestamps.begin(); it != timestamps.end(); ++it)
865 {
866 result.insert(it.key(), it.value().toString());
867 }
868 return result;
869 }
870
871 QMap<QString, QUuid> CDataCacheRevision::sessionFromJson(const QJsonObject &session)
872 {
874 for (auto it = session.begin(); it != session.end(); ++it)
875 {
876 result.insert(it.key(), QUuid(it.value().toString()));
877 }
878 return result;
879 }
880
881 void CDataCacheRevision::Session::updateSession()
882 {
883 CAtomicFile file(m_filename);
884 bool ok = file.open(QIODevice::ReadWrite | QFile::Text);
885 if (!ok)
886 {
887 CLogMessage(this).error(u"Failed to open session file %1: %2") << m_filename << file.errorString();
888 return;
889 }
890 auto json = QJsonDocument::fromJson(file.readAll()).object();
891 QUuid id(json.value("uuid").toString());
892 CSequence<CProcessInfo> apps;
893 auto status = apps.convertFromJsonNoThrow(json.value("apps").toObject(), this,
894 QStringLiteral("Error in %1 apps object").arg(m_filename));
895 apps.removeIf([](const CProcessInfo &pi) { return !pi.exists(); });
896
897 if (apps.isEmpty())
898 {
899 id = CIdentifier().toUuid();
900 }
901 m_uuid = id;
902
903 CProcessInfo currentProcess = CProcessInfo::currentProcess();
904 Q_ASSERT(currentProcess.exists());
905 apps.replaceOrAdd(currentProcess);
906 json.insert("apps", apps.toJson());
907 json.insert("uuid", m_uuid.toString());
908 if (file.seek(0) && file.resize(0) && file.write(QJsonDocument(json).toJson()))
909 {
910 if (!file.checkedClose())
911 {
912 static const QString advice =
913 QStringLiteral("If this error persists, try restarting your computer or delete the file manually.");
914 CLogMessage(this).error(u"Failed to replace %1: %2 (%3)")
915 << file.fileName() << file.errorString() << advice;
916 }
917 }
918 else
919 {
920 CLogMessage(this).error(u"Failed to write to %1: %2") << file.fileName() << file.errorString();
921 }
922 }
923
924} // namespace swift::misc
925
Worker which performs (de)serialization on behalf of CDataCache, in a separate thread so that the mai...
Definition datacache.h:201
auto keys() const
Return a range of all keys (does not allocate a temporary container).
Definition dictionary.h:435
static const QString & cache()
Cache.
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
QString toTimestampMapString(const QStringList &keys) const
Return map of timestamps converted to string.
QMap< QString, qint64 > toTimestampMap() const
Discard values and return as map of timestamps.
CVariantMap toVariantMap() const
Discard timestamps and return as variant map.
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
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
qint64 currentMSecsSinceEpoch()
QDateTime fromMSecsSinceEpoch(qint64 msecs)
QDir root()
bool exists(const QString &fileName)
bool open(FILE *fh, QIODeviceBase::OpenMode mode, QFileDevice::FileHandleFlags handleFlags)
void fileChanged(const QString &path)
void push_back(const QJsonValue &value)
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
QJsonObject::iterator begin()
QJsonObject::iterator end()
QJsonObject::iterator insert(QLatin1StringView key, const QJsonValue &value)
QJsonValue value(QLatin1StringView key) const const
QJsonObject toObject() const const
QString toString() const const
void push_back(QList< T >::parameter_type value)
QMap< Key, T >::iterator begin()
QMap< Key, T >::const_iterator cbegin() const const
QMap< Key, T >::const_iterator cend() const const
QMap< Key, T >::iterator end()
QMap< Key, T >::iterator insert(QMap< Key, T >::const_iterator pos, const Key &key, const T &value)
QMap< Key, T >::size_type remove(const Key &key)
T value(const Key &key, const T &defaultValue) const const
void destroyed(QObject *obj)
QSet< T >::iterator insert(QSet< T >::const_iterator it, const T &value)
QString join(QChar separator) const const
DirectConnection
PreciseTimer
QFuture< typename qValueType< Iterator >::value_type > filtered(Iterator begin, Iterator end, KeepFunctor &&filterFunction)
QFuture< QtFuture::ArgsType< Signal > > connect(Sender *sender, Signal signal)
#define SWIFT_MISC_EXPORT
Export a class or function from the library.
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition verify.h:26