PEL: Use class to watch properties in DataIface

Instead of having the DataInterface class explicitly watch for all of
the PropertiesChanged and InterfacesAdded signals for the D-Bus
properties it needs the values of, create some classes to wrap that
functionality.

The PropertyWatcher class will call a user defined function, passing it
the property value for the path/interface/property specified in the
following cases:
1) On construction, by making a property read method call, if the
   property is on D-Bus then.
2) On a properties changed signal for that property.
3) On an interfaces added signal for that property's interface.

The InterfaceWatcher class will call a user defined function, passing it
the property name/value map for all properties in that interface, in the
following cases:
1) On construction, by making a GetAll property read method call, if the
   interface is on D-Bus then.
2) On a properties changed signal for that interface.
3) On an interfaces added signal for that interface.

Both of these are derived from the DBusWatcher class, and the
DataInterface will store a vector of DBusWatcher pointers after it
creates the instances of the PropertyWatcher or InterfaceWatcher classes
in its constructor.

This commit changes the current properties being watched - the system
model, the system serial number, and the operating system status to this
method.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Iade4ac89a9ce1d46bdebf353350bf161722ced9f
diff --git a/extensions/openpower-pels/dbus_watcher.hpp b/extensions/openpower-pels/dbus_watcher.hpp
new file mode 100644
index 0000000..bdd3b2c
--- /dev/null
+++ b/extensions/openpower-pels/dbus_watcher.hpp
@@ -0,0 +1,339 @@
+#pragma once
+
+#include "dbus_types.hpp"
+
+#include <sdbusplus/bus/match.hpp>
+
+namespace openpower::pels
+{
+
+using sdbusplus::exception::SdBusError;
+namespace match_rules = sdbusplus::bus::match::rules;
+
+/**
+ * @class DBusWatcher
+ *
+ * The base class for the PropertyWatcher and InterfaceWatcher classes.
+ */
+class DBusWatcher
+{
+  public:
+    DBusWatcher() = delete;
+    virtual ~DBusWatcher() = default;
+    DBusWatcher(const DBusWatcher&) = default;
+    DBusWatcher& operator=(const DBusWatcher&) = default;
+    DBusWatcher(DBusWatcher&&) = default;
+    DBusWatcher& operator=(DBusWatcher&&) = default;
+
+    /**
+     * @brief Constructor
+     *
+     * @param[in] path - The D-Bus path that will be watched
+     * @param[in] interface - The D-Bus interface that will be watched
+     */
+    DBusWatcher(const std::string& path, const std::string& interface) :
+        _path(path), _interface(interface)
+    {
+    }
+
+  protected:
+    /**
+     * @brief The D-Bus path
+     */
+    std::string _path;
+
+    /**
+     * @brief The D-Bus interface
+     */
+    std::string _interface;
+
+    /**
+     * @brief The match objects for the propertiesChanged and
+     *        interfacesAdded signals.
+     */
+    std::vector<sdbusplus::bus::match_t> _matches;
+};
+
+/**
+ * @class PropertyWatcher
+ *
+ * This class allows the user to be kept up to data with a D-Bus
+ * property's value.  It does this by calling a user specified function
+ * that is passed the variant that contains the property's value when:
+ *
+ * 1) The property is read when the class is constructed, if
+ *    the property is on D-Bus at the time.
+ * 2) The property changes (via a property changed signal).
+ * 3) An interfacesAdded signal is received with that property.
+ *
+ * The DataInterface class is used to access D-Bus, and is a template
+ * to avoid any circular include issues as that class is one of the
+ * users of this one.
+ */
+template <typename DataIface>
+class PropertyWatcher : public DBusWatcher
+{
+  public:
+    PropertyWatcher() = delete;
+    ~PropertyWatcher() = default;
+    PropertyWatcher(const PropertyWatcher&) = delete;
+    PropertyWatcher& operator=(const PropertyWatcher&) = delete;
+    PropertyWatcher(PropertyWatcher&&) = delete;
+    PropertyWatcher& operator=(PropertyWatcher&&) = delete;
+
+    using PropertySetFunc = std::function<void(const DBusValue&)>;
+
+    /**
+     * @brief Constructor
+     *
+     * Reads the property if it is on D-Bus, and sets up the match
+     * objects for the propertiesChanged and interfacesAdded signals.
+     *
+     * @param[in] bus - The sdbusplus bus object
+     * @param[in] path - The D-Bus path of the property
+     * @param[in] interface - The D-Bus interface that contains the property
+     * @param[in] propertyName - The property name
+     * @param[in] dataIface - The DataInterface object
+     * @param[in] func - The callback used any time the property is read
+     */
+    PropertyWatcher(sdbusplus::bus::bus& bus, const std::string& path,
+                    const std::string& interface,
+                    const std::string& propertyName, const DataIface& dataIface,
+                    PropertySetFunc func) :
+        DBusWatcher(path, interface),
+        _name(propertyName), _setFunc(func)
+    {
+        try
+        {
+            read(dataIface);
+        }
+        catch (const SdBusError& e)
+        {
+            // Path doesn't exist now
+        }
+
+        _matches.emplace_back(
+            bus, match_rules::propertiesChanged(_path, _interface),
+            std::bind(std::mem_fn(&PropertyWatcher::propChanged), this,
+                      std::placeholders::_1));
+
+        _matches.emplace_back(
+            bus,
+            match_rules::interfacesAdded() + match_rules::argNpath(0, _path),
+            std::bind(std::mem_fn(&PropertyWatcher::interfaceAdded), this,
+                      std::placeholders::_1));
+    }
+
+    /**
+     * @brief Reads the property on D-Bus, and calls
+     *        the user defined function with the value.
+     *
+     * @param[in] dataIface - The DataInterface object
+     */
+    void read(const DataIface& dataIface)
+    {
+        auto service = dataIface.getService(_path, _interface);
+        if (!service.empty())
+        {
+            DBusValue value;
+            dataIface.getProperty(service, _path, _interface, _name, value);
+
+            _setFunc(value);
+        }
+    }
+
+    /**
+     * @brief The propertiesChanged callback
+     *
+     * Calls the user defined function with the property value
+     *
+     * @param[in] msg - The sdbusplus message object
+     */
+    void propChanged(sdbusplus::message::message& msg)
+    {
+        DBusInterface interface;
+        DBusPropertyMap properties;
+
+        msg.read(interface, properties);
+
+        auto prop = properties.find(_name);
+        if (prop != properties.end())
+        {
+            _setFunc(prop->second);
+        }
+    }
+
+    /**
+     * @brief The interfacesAdded callback
+     *
+     * Calls the user defined function with the property value
+     *
+     * @param[in] msg - The sdbusplus message object
+     */
+    void interfaceAdded(sdbusplus::message::message& msg)
+    {
+        sdbusplus::message::object_path path;
+        DBusInterfaceMap interfaces;
+
+        msg.read(path, interfaces);
+
+        auto iface = interfaces.find(_interface);
+        if (iface != interfaces.end())
+        {
+            auto prop = iface->second.find(_name);
+            if (prop != iface->second.end())
+            {
+                _setFunc(prop->second);
+            }
+        }
+    }
+
+  private:
+    /**
+     * @brief The D-Bus property name
+     */
+    std::string _name;
+
+    /**
+     * @brief The function that will be called any time the
+     *        property is read.
+     */
+    PropertySetFunc _setFunc;
+};
+
+/**
+ * @class InterfaceWatcher
+ *
+ * This class allows the user to be kept up to data with a D-Bus
+ * interface's properties..  It does this by calling a user specified
+ * function that is passed a map of the D-Bus property names and values
+ * on that interface when:
+ *
+ * 1) The interface is read when the class is constructed, if
+ *    the interface is on D-Bus at the time.
+ * 2) The interface has a property that changes (via a property changed signal).
+ * 3) An interfacesAdded signal is received.
+ *
+ * The DataInterface class is used to access D-Bus, and is a template
+ * to avoid any circular include issues as that class is one of the
+ * users of this one.
+ */
+template <typename DataIface>
+class InterfaceWatcher : public DBusWatcher
+{
+  public:
+    InterfaceWatcher() = delete;
+    ~InterfaceWatcher() = default;
+    InterfaceWatcher(const InterfaceWatcher&) = delete;
+    InterfaceWatcher& operator=(const InterfaceWatcher&) = delete;
+    InterfaceWatcher(InterfaceWatcher&&) = delete;
+    InterfaceWatcher& operator=(InterfaceWatcher&&) = delete;
+
+    using InterfaceSetFunc = std::function<void(const DBusPropertyMap&)>;
+
+    /**
+     * @brief Constructor
+     *
+     * Reads all properties on the interface if it is on D-Bus,
+     * and sets up the match objects for the propertiesChanged
+     * and interfacesAdded signals.
+     *
+     * @param[in] bus - The sdbusplus bus object
+     * @param[in] path - The D-Bus path of the property
+     * @param[in] interface - The D-Bus interface that contains the property
+     * @param[in] dataIface - The DataInterface object
+     * @param[in] func - The callback used any time the property is read
+     */
+    InterfaceWatcher(sdbusplus::bus::bus& bus, const std::string& path,
+                     const std::string& interface, const DataIface& dataIface,
+                     InterfaceSetFunc func) :
+        DBusWatcher(path, interface),
+        _setFunc(func)
+    {
+        try
+        {
+            read(dataIface);
+        }
+        catch (const SdBusError& e)
+        {
+            // Path doesn't exist now
+        }
+
+        _matches.emplace_back(
+            bus, match_rules::propertiesChanged(_path, _interface),
+            std::bind(std::mem_fn(&InterfaceWatcher::propChanged), this,
+                      std::placeholders::_1));
+
+        _matches.emplace_back(
+            bus,
+            match_rules::interfacesAdded() + match_rules::argNpath(0, _path),
+            std::bind(std::mem_fn(&InterfaceWatcher::interfaceAdded), this,
+                      std::placeholders::_1));
+    }
+
+    /**
+     * @brief Reads the interface's properties on D-Bus, and
+     * calls the the user defined function with the property map.
+     *
+     * @param[in] dataIface - The DataInterface object
+     */
+    void read(const DataIface& dataIface)
+    {
+        auto service = dataIface.getService(_path, _interface);
+        if (!service.empty())
+        {
+            auto properties =
+                dataIface.getAllProperties(service, _path, _interface);
+
+            _setFunc(properties);
+        }
+    }
+
+    /**
+     * @brief The propertiesChanged callback
+     *
+     * Calls the user defined function with the property map.  Only the
+     * properties that changed will be in the map.
+     *
+     * @param[in] msg - The sdbusplus message object
+     */
+    void propChanged(sdbusplus::message::message& msg)
+    {
+        DBusInterface interface;
+        DBusPropertyMap properties;
+
+        msg.read(interface, properties);
+
+        _setFunc(properties);
+    }
+
+    /**
+     * @brief The interfacesAdded callback
+     *
+     * Calls the user defined function with the property map
+     *
+     * @param[in] msg - The sdbusplus message object
+     */
+    void interfaceAdded(sdbusplus::message::message& msg)
+    {
+        sdbusplus::message::object_path path;
+        DBusInterfaceMap interfaces;
+
+        msg.read(path, interfaces);
+
+        auto iface = interfaces.find(_interface);
+        if (iface != interfaces.end())
+        {
+            _setFunc(iface->second);
+        }
+    }
+
+  private:
+    /**
+     * @brief The function that will be called any time the
+     *        interface is read.
+     */
+    InterfaceSetFunc _setFunc;
+};
+
+} // namespace openpower::pels