Add property watches

Property watches cache DBus property values given an externally
supplied index of property names and paths, in an externally
supplied storage location.

Change-Id: I155081da88c3ab0e4f6a13b012fc9719203b1888
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/src/propertywatchimpl.hpp b/src/propertywatchimpl.hpp
new file mode 100644
index 0000000..46799ed
--- /dev/null
+++ b/src/propertywatchimpl.hpp
@@ -0,0 +1,183 @@
+#pragma once
+
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <vector>
+#include "data_types.hpp"
+#include "propertywatch.hpp"
+
+namespace phosphor
+{
+namespace dbus
+{
+namespace monitoring
+{
+
+static constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
+static constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
+static constexpr auto MAPPER_INTERFACE =
+    "xyz.openbmc_project.ObjectMapper";
+
+using MappedPropertyIndex =
+    RefKeyMap<const std::string,
+    RefKeyMap<const std::string,
+    RefVector<const std::string>>>;
+
+MappedPropertyIndex convert(const PropertyIndex& index);
+
+template <typename DBusInterfaceType>
+void PropertyWatch<DBusInterfaceType>::start()
+{
+    if (alreadyRan)
+    {
+        return;
+    }
+    else
+    {
+        alreadyRan = true;
+    }
+
+    // The index has a flat layout which is not optimal here.  Nest
+    // properties in a map of interface names in a map of object paths.
+    auto mapped = convert(index);
+
+    for (const auto& m : mapped)
+    {
+        const auto& path = m.first.get();
+        const auto& interfaces = m.second;
+
+        // Watch for new interfaces on this path.
+        DBusInterfaceType::addMatch(
+            sdbusplus::bus::match::rules::interfacesAdded(path),
+            [this](auto & msg)
+        // *INDENT-OFF*
+            {
+                this->interfacesAdded(msg);
+            });
+        // *INDENT-ON*
+
+        // Do a query to populate the cache.  Start with a mapper query.
+        // The specific services are queried below.
+        const std::vector<std::string> queryInterfaces; // all interfaces
+        auto mapperResp =
+            DBusInterfaceType::template callMethodAndRead<GetObject>(
+                MAPPER_BUSNAME,
+                MAPPER_PATH,
+                MAPPER_INTERFACE,
+                "GetObject",
+                path,
+                queryInterfaces);
+
+        for (const auto& i : interfaces)
+        {
+            const auto& interface = i.first.get();
+
+            // Watch for property changes on this interface.
+            DBusInterfaceType::addMatch(
+                sdbusplus::bus::match::rules::propertiesChanged(
+                        path, interface),
+                [this](auto & msg)
+                // *INDENT-OFF*
+                {
+                    std::string interface;
+                    msg.read(interface);
+                    auto path = msg.get_path();
+                    this->propertiesChanged(msg, path, interface);
+                });
+                // *INDENT-ON*
+
+            // The mapper response is a busname:[interfaces] map.  Look for
+            // each interface in the index and if found, query the service and
+            // populate the cache entries for the interface.
+            for (const auto& mr : mapperResp)
+            {
+                const auto& busName = mr.first;
+                const auto& mapperInterfaces = mr.second;
+                if (mapperInterfaces.end() == std::find(
+                        mapperInterfaces.begin(),
+                        mapperInterfaces.end(),
+                        interface))
+                {
+                    // This interface isn't being watched.
+                    continue;
+                }
+
+                // Delegate type specific property updates to subclasses.
+                updateProperties(busName, path, interface);
+            }
+        }
+    }
+}
+
+template <typename T, typename DBusInterfaceType>
+void PropertyWatchOfType<T, DBusInterfaceType>::updateProperties(
+    const std::string& busName,
+    const std::string& path,
+    const std::string& interface)
+{
+    auto properties =
+        DBusInterfaceType::template callMethodAndRead<PropertiesChanged<T>>(
+            busName.c_str(),
+            path.c_str(),
+            "org.freedesktop.DBus.Properties",
+            "GetAll",
+            interface);
+    propertiesChanged(path, interface, properties);
+}
+
+template <typename T, typename DBusInterfaceType>
+void PropertyWatchOfType<T, DBusInterfaceType>::propertiesChanged(
+    const std::string& path,
+    const std::string& interface,
+    const PropertiesChanged<T>& properties)
+{
+    // Update the cache for any watched properties.
+    for (const auto& p : properties)
+    {
+        auto key = std::make_tuple(path, interface, p.first);
+        auto item = this->index.find(key);
+        if (item == this->index.end())
+        {
+            // This property isn't being watched.
+            continue;
+        }
+
+        std::get<2>(item->second).get() = p.second.template get<T>();
+    }
+}
+
+template <typename T, typename DBusInterfaceType>
+void PropertyWatchOfType<T, DBusInterfaceType>::propertiesChanged(
+    sdbusplus::message::message& msg,
+    const std::string& path,
+    const std::string& interface)
+{
+    PropertiesChanged<T> properties;
+    msg.read(properties);
+    propertiesChanged(path, interface, properties);
+}
+
+template <typename T, typename DBusInterfaceType>
+void PropertyWatchOfType<T, DBusInterfaceType>::interfacesAdded(
+    const std::string& path,
+    const InterfacesAdded<T>& interfaces)
+{
+    for (const auto& i : interfaces)
+    {
+        propertiesChanged(path, i.first, i.second);
+    }
+}
+
+template <typename T, typename DBusInterfaceType>
+void PropertyWatchOfType<T, DBusInterfaceType>::interfacesAdded(
+    sdbusplus::message::message& msg)
+{
+    sdbusplus::message::object_path path;
+    InterfacesAdded<T> interfaces;
+    msg.read(path, interfaces);
+    interfacesAdded(path, interfaces);
+}
+
+} // namespace monitoring
+} // namespace dbus
+} // namespace phosphor