/* Copyright (C) 2015 * swift project Community / Contributors * * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, * including this file, may be copied, modified, propagated, or distributed except according to the terms * contained in the LICENSE file. */ //! \file #ifndef BLACKMISC_VALUECACHE_H #define BLACKMISC_VALUECACHE_H #include "blackmisc/valuecache_private.h" namespace BlackMisc { /*! * Manages a map of { QString, CVariant } pairs, which can be distributed among multiple processes. */ class BLACKMISC_EXPORT CValueCache : public QObject { Q_OBJECT public: class BatchGuard; //! Whether or not the cache can be distributed among multiple processes. enum DistributionMode { LocalOnly, //!< Not distributed. Distributed //!< Distributed among multiple processes. }; //! Constructor. //! \param mode Whether or not the cache can be distributed among multiple processes. //! \param parent The parent of the QObject. explicit CValueCache(DistributionMode mode, QObject *parent = nullptr); //! Return map containing all values in the cache. //! If prefix is provided then only those values whose keys start with that prefix. //! \threadsafe BlackMisc::CVariantMap getAllValues(const QString &keyPrefix = {}) const; //! Add some values to the cache. //! Values already in the cache will remain in the cache unless they are overwritten. //! \threadsafe void insertValues(const BlackMisc::CVariantMap &values); //! Save values in Json format. //! If prefix is provided then only those values whose keys start with that prefix. //! \threadsafe QJsonObject saveToJson(const QString &keyPrefix = {}) const; //! Load all values in Json format. //! Values already in the cache will remain in the cache unless they are overwritten. //! \threadsafe void loadFromJson(const QJsonObject &json); //! Begins a batch of changes to be made through CCached instances owned by owner. //! \details All changes made through those CCached instances will be deferred until the returned RAII object is //! destroyed. If the destruction happens during stack unwinding due to an exception being thrown, the changes are //! abandoned, otherwise they are applied in one single change signal instead of lots of individual ones. //! Can be called multiple times; the batch will be applied (or abandoned) when the last RAII object is destroyed. //! CCached::getValue will continue to return the old value until the batched changes are applied. //! \note Must be called in the thread where owner lives. BatchGuard batchChanges(QObject *owner); public slots: //! Notify this cache that values have been changed by one of the duplicate caches in the multi-process environment. //! \see BlackMisc::CValueCache::valuesChangedByLocal. //! \param values The values that were changed. //! \param originator Identifier of the process which made the change. Can be this very process, or a different one. void changeValuesFromRemote(const BlackMisc::CVariantMap &values, const BlackMisc::CIdentifier &originator); signals: //! Emitted when values in the cache are changed by an object in this very process. //! The interprocess communication (e.g. DBus) should arrange for this signal to call the slot changeValueFromRemote //! of CValueCache instances in all processes including this one. The slot will do its own round-trip detection. void valuesChangedByLocal(const BlackMisc::CVariantMap &values); private: friend class Private::CValuePage; struct Element; using ElementPtr = QSharedPointer; // QMap doesn't support move-only types QMap m_elements; mutable QMutex m_mutex { QMutex::Recursive }; Element &getElement(const QString &key); Element &getElement(const QString &key, QMap::const_iterator pos); CVariant getValue(const QString &key); signals: //! \private void valuesChanged(const BlackMisc::CVariantMap &values, QObject *changedBy); private slots: void changeValues(const BlackMisc::CVariantMap &values); }; /*! * Provides access to one of the values stored in a CValueCache. * \tparam T The expected value type of the cached value. */ template class CCached { public: //! Type of pointer to non-const member function of U taking no arguments and returning void, //! for slot parameter of CCached constructor. template using NotifySlot = void (U::*)(); //! Constructor. //! \param cache The CValueCache object which manages the value. //! \param key The key string which identifies the value. //! \param owner Will be the parent of the internal QObject used for signal/slot connections. //! \param slot A member function of owner which will be called when the value is changed by another source. template CCached(CValueCache *cache, const QString &key, U *owner, NotifySlot slot = nullptr) : CCached(cache, key, nullptr, T{}, owner, slot) {} //! Constructor. //! \param cache The CValueCache object which manages the value. //! \param key The key string which identifies the value. //! \param validator A functor which tests the validity of a value and returns true if it is valid. //! \param defaultValue A value which will be used as default if the value is invalid. //! \param owner Will be the parent of the internal QObject used for signal/slot connections. //! \param slot A member function of owner which will be called when the value is changed by another source. template CCached(CValueCache *cache, const QString &key, F validator, const T &defaultValue, U *owner, NotifySlot slot = nullptr) : m_page(Private::CValuePage::getPageFor(owner, cache)), m_element(m_page.createElement(key, qMetaTypeId(), wrap(validator), CVariant::from(defaultValue), slot_cast(slot))) {} //! Read the current value. Must be called from the thread in which the owner lives. const T &get() const { static const T empty {}; return *(isValid() ? static_cast(getVariant().data()) : &empty); } //! Write a new value. Must be called from the thread in which the owner lives. CStatusMessage set(const T &value) { return m_page.setValue(m_element, CVariant::from(value)); } //! Deleted copy constructor. CCached(const CCached &) = delete; //! Deleted copy assignment operator. CCached &operator =(const CCached &) = delete; private: template static Private::CValuePage::Validator wrap(F func) { return [func](const CVariant &value)->bool { return func(value.to()); }; } static Private::CValuePage::Validator wrap(std::nullptr_t) { return {}; } template static Private::CValuePage::NotifySlot slot_cast(F slot) { return static_cast(slot); } const QVariant &getVariant() const { return m_page.getValue(m_element).getQVariant(); } bool isValid() const { return getVariant().isValid() && getVariant().userType() == qMetaTypeId(); } Private::CValuePage &m_page; Private::CValuePage::Element &m_element; }; /*! * RAII object returned by CValueCache::batchChanges. Applies deferred changes when it is destroyed. */ class BLACKMISC_EXPORT CValueCache::BatchGuard { public: //! Destructor. Applies deferred changes. ~BatchGuard() Q_DECL_NOEXCEPT; //! Deleted copy constructor. Class is move-only. BatchGuard(const BatchGuard &) = delete; //! Deleted copy assignment operator. Class is move-only. BatchGuard &operator =(const BatchGuard &) = delete; //! Move constructor. BatchGuard(BatchGuard &&other) : m_page(other.m_page) { other.m_page = nullptr; } //! Move assignment operator. BatchGuard &operator =(BatchGuard &&other) { std::swap(m_page, other.m_page); return *this; } private: friend class CValueCache; BatchGuard(Private::CValuePage &page) : m_page(&page) {} Private::CValuePage *m_page; }; } // namespace #endif