diff --git a/src/blackmisc/datacache.cpp b/src/blackmisc/datacache.cpp index 97982b261..d1144c43a 100644 --- a/src/blackmisc/datacache.cpp +++ b/src/blackmisc/datacache.cpp @@ -14,6 +14,7 @@ #include "blackmisc/directoryutils.h" #include "blackmisc/identifier.h" #include "blackmisc/logmessage.h" +#include "blackmisc/processinfo.h" #include #include @@ -165,6 +166,11 @@ namespace BlackMisc if (triggerLoad) { loadFromStoreAsync(); } } + void CDataCache::sessionValue(const QString &key) + { + QTimer::singleShot(0, &m_serializer, [this, key] { m_revision.sessionValue(key); }); + } + QString lockFileError(const QLockFile &lock) { switch (lock.error()) @@ -292,6 +298,7 @@ namespace BlackMisc auto missingKeys = m_cache->m_revision.keysWithNewerTimestamps().subtract(newValues.keys()); if (! missingKeys.isEmpty()) { m_cache->m_revision.writeNewRevision({}, missingKeys); } + else if (m_cache->m_revision.isNewSession()) { m_cache->m_revision.writeNewRevision({}); } msg.setCategories(this); CLogMessage::preformatted(msg); @@ -324,6 +331,21 @@ namespace BlackMisc }); } + class BLACKMISC_EXPORT CDataCacheRevision::Session + { + public: + Session(const QString &filename) : m_filename(filename) {} + void updateSession(); + bool isNewSession() const { return m_isNewSession; } + private: + const QString m_filename; + bool m_isNewSession = false; + }; + + CDataCacheRevision::CDataCacheRevision(const QString &basename) : m_basename(basename) {} + + CDataCacheRevision::~CDataCacheRevision() = default; + CDataCacheRevision::LockGuard CDataCacheRevision::beginUpdate(const QMap ×tamps, bool updateUuid, bool pinsOnly) { QMutexLocker lock(&m_mutex); @@ -341,6 +363,7 @@ namespace BlackMisc m_timestamps.clear(); m_originalTimestamps.clear(); + if (! m_session) { m_session = std::make_unique(m_basename + "/.session"); } QFile revisionFile(m_basename + "/.rev"); if (revisionFile.exists()) @@ -398,6 +421,12 @@ namespace BlackMisc { if (deferrals.contains(key) && ! m_admittedValues.contains(key)) { m_timestamps.remove(key); } } + + m_session->updateSession(); + if (isNewSession()) + { + for (const auto &key : fromJson(json.value("session").toArray())) { m_timestamps.remove(key); } + } } else if (revisionFile.size() > 0) { @@ -440,6 +469,7 @@ namespace BlackMisc json.insert("ttl", toJson(m_timesToLive)); json.insert("pins", toJson(m_pinnedValues)); json.insert("deferrals", toJson(m_deferredValues)); + json.insert("session", toJson(m_sessionValues)); revisionFile.write(QJsonDocument(json).toJson()); if (! revisionFile.checkedClose()) @@ -648,6 +678,23 @@ namespace BlackMisc m_admittedQueue.insert(key); } + void CDataCacheRevision::sessionValue(const QString &key) + { + QMutexLocker lock(&m_mutex); + + Q_ASSERT(! m_updateInProgress); + m_sessionValues.insert(key); + } + + bool CDataCacheRevision::isNewSession() const + { + QMutexLocker lock(&m_mutex); + + Q_ASSERT(m_updateInProgress); + Q_ASSERT(m_session); + return m_session->isNewSession(); + } + QJsonObject CDataCacheRevision::toJson(const QMap ×tamps) { QJsonObject result; @@ -687,6 +734,32 @@ namespace BlackMisc } return result; } + + void CDataCacheRevision::Session::updateSession() + { + CAtomicFile file(m_filename); + bool ok = file.open(QIODevice::ReadWrite | QFile::Text); + if (! ok) + { + CLogMessage(this).error("Failed to open session file %1: %2") << m_filename << file.errorString(); + m_isNewSession = true; + return; + } + CSequence session; + session.convertFromJson(file.readAll()); + session.removeIf([](const CProcessInfo &pi) { return ! pi.exists(); }); + + m_isNewSession = session.isEmpty(); + + CProcessInfo currentProcess = CProcessInfo::currentProcess(); + Q_ASSERT(currentProcess.exists()); + session.replaceOrAdd(currentProcess, currentProcess); + if (!(file.seek(0) && file.resize(0) && file.write(QJsonDocument(session.toJson()).toJson()) && file.checkedClose())) + { + CLogMessage(this).error("Failed to write to session file %1: %2") << m_filename << file.errorString(); + } + } + } //! \endcond diff --git a/src/blackmisc/datacache.h b/src/blackmisc/datacache.h index 066b59e57..f2e374220 100644 --- a/src/blackmisc/datacache.h +++ b/src/blackmisc/datacache.h @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -85,7 +86,10 @@ namespace BlackMisc { public: //! Construct the single instance of the revision metastate. - CDataCacheRevision(const QString &basename) : m_basename(basename) {} + CDataCacheRevision(const QString &basename); + + //! Destructor. + ~CDataCacheRevision(); //! Non-copyable. //! @{ @@ -155,6 +159,12 @@ namespace BlackMisc //! Set the flag which will cause a deferred-load value to be loaded. void admitValue(const QString &key); + //! Set the flag which will cause a value to be reset when starting a new session. + void sessionValue(const QString &key); + + //! True if the current update is the first of a new session. + bool isNewSession() const; + private: mutable QMutex m_mutex { QMutex::Recursive }; bool m_updateInProgress = false; @@ -170,8 +180,12 @@ namespace BlackMisc QSet m_deferredValues; QSet m_admittedValues; QSet m_admittedQueue; + QSet m_sessionValues; std::vector> m_promises; + class Session; + std::unique_ptr m_session; + static QJsonObject toJson(const QMap ×tamps); static QMap fromJson(const QJsonObject ×tamps); static QJsonArray toJson(const QSet &pins); @@ -262,6 +276,9 @@ namespace BlackMisc //! Method used for implementing deferring values. void admitValue(const QString &key, bool triggerLoad); + //! Method used for implementing session values. + void sessionValue(const QString &key); + private: CDataCache(); @@ -295,6 +312,7 @@ namespace BlackMisc if (Trait::timeToLive() >= 0) { CDataCache::instance()->setTimeToLive(this->getKey(), Trait::timeToLive()); } if (Trait::isPinned()) { CDataCache::instance()->pinValue(this->getKey()); } if (Trait::isDeferred()) { CDataCache::instance()->deferValue(this->getKey()); } + if (Trait::isSession()) { CDataCache::instance()->sessionValue(this->getKey()); } static_assert(! (Trait::isPinned() && Trait::isDeferred()), "trait can not be both pinned and deferred"); } @@ -420,6 +438,11 @@ namespace BlackMisc //! Good for large values the loading of which might depend on some other condition. static constexpr bool isDeferred() { return false; } + //! If true, then upon starting an application, value will be overwritten with the default + //! if there are no other applications currently using the cache. In effect, the value + //! is retained only while there are applications using the cache. + static constexpr bool isSession() { return false; } + //! Deleted default constructor. TDataTrait() = delete;