Add rescan function and SSD retimer configuration

Add ability to rescan via a function call. Also
add retimer configuration. Using this the retimer
configuration can be added at runtime. Also automatically
rescan when a probed type gets a properties changed event.

Change-Id: I0781972c4545ad00c21da4ea94accd7f1e61d515
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/configurations/Flextronics S-1100ADU00-201 PSU.json b/configurations/Flextronics S-1100ADU00-201 PSU.json
old mode 100755
new mode 100644
diff --git a/configurations/PCIE SSD Retimer.json b/configurations/PCIE SSD Retimer.json
new file mode 100644
index 0000000..e6fca44
--- /dev/null
+++ b/configurations/PCIE SSD Retimer.json
@@ -0,0 +1,18 @@
+{
+    "exposes": [
+        {
+            "address": "$address",
+            "bus": "$bus",
+            "name": "PCIE SSD Retimer FRU",
+            "type": "IntelFruDevice"
+        },
+        {
+            "address": "0x4F",
+            "bus": "$bus",
+            "name": "PCIE SSD Retimer Temp",
+            "type": "TMP75"
+        }
+    ],
+    "name": "PCIE SSD Retimer",
+    "probe": "xyz.openbmc_project.FruDevice({'BOARD_PRODUCT_NAME': 'SP3RT040X16'})"
+}
\ No newline at end of file
diff --git a/src/EntityManager.cpp b/src/EntityManager.cpp
index b27b7f2..a07c39c 100644
--- a/src/EntityManager.cpp
+++ b/src/EntityManager.cpp
@@ -18,6 +18,7 @@
 #include <dbus/properties.hpp>
 #include <nlohmann/json.hpp>
 #include <fstream>
+#include <future>
 #include <regex>
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/algorithm/string/replace.hpp>
@@ -81,6 +82,12 @@
 
 std::regex ILLEGAL_DBUS_REGEX("[^A-Za-z0-9_]");
 
+void registerCallbacks(
+    std::vector<std::pair<std::unique_ptr<dbus::match>,
+                          std::shared_ptr<dbus::filter>>> &dbusMatches,
+    std::atomic_bool &threadRunning, nlohmann::json &systemConfiguration,
+    dbus::DbusObjectServer &objServer);
+
 // calls the mapper to find all exposed objects of an interface type
 // and creates a vector<flat_map> that contains all the key value pairs
 // getManagedObjects
@@ -461,8 +468,8 @@
 }
 
 void postToDbus(const nlohmann::json &systemConfiguration,
-                dbus::DbusObjectServer &objServer,
-                std::vector<std::shared_ptr<dbus::DbusObject>> &objects)
+                dbus::DbusObjectServer &objServer)
+
 {
     for (auto &boardPair :
          nlohmann::json::iterator_wrapper(systemConfiguration))
@@ -629,7 +636,7 @@
     }
 }
 
-int main(int argc, char **argv)
+bool findJsonFiles(std::vector<nlohmann::json> &configurations)
 {
     // find configuration files
     std::vector<fs::path> jsonPaths;
@@ -637,15 +644,8 @@
     {
         std::cerr << "Unable to find any configuration files in "
                   << CONFIGURATION_DIR << "\n";
-        return 1;
+        return false;
     }
-    // setup connection to dbus
-    boost::asio::io_service io;
-    SYSTEM_BUS = std::make_shared<dbus::connection>(io, dbus::bus::system);
-    dbus::DbusObjectServer objServer(SYSTEM_BUS);
-    SYSTEM_BUS->request_name("xyz.openbmc_project.EntityManager");
-
-    std::vector<nlohmann::json> configurations;
     for (auto &jsonPath : jsonPaths)
     {
         std::ifstream jsonStream(jsonPath.c_str());
@@ -672,11 +672,49 @@
             configurations.emplace_back(data);
         }
     }
+}
 
-    // keep looping as long as at least 1 new probe passed, removing
-    // configurations from the memory store once the probe passes
+bool rescan(nlohmann::json &systemConfiguration)
+{
+    std::vector<nlohmann::json> configurations;
+    if (!findJsonFiles(configurations))
+    {
+        false;
+    }
+    // preprocess already passed configurations and missing fields
+    if (systemConfiguration.size())
+    {
+        for (auto it = configurations.begin(); it != configurations.end();)
+        {
+            auto findName = it->find("name");
+            if (findName == it->end())
+            {
+                std::cerr << "configuration missing name field " << *it << "\n";
+                it = configurations.erase(it);
+                continue;
+            }
+            else if (findName->type() != nlohmann::json::value_t::string)
+            {
+                std::cerr << "name field must be a string " << *findName
+                          << "\n";
+                it = configurations.erase(it);
+                continue;
+            }
+            auto findAlreadyFound =
+                systemConfiguration.find(findName->get<std::string>());
+            if (findAlreadyFound != systemConfiguration.end())
+            {
+                it = configurations.erase(it);
+                continue;
+            }
+            // TODO: add in tags to determine if configuration should be
+            // refreshed on AC / DC / Always.
+            it++;
+        }
+    }
+
+    // probe until no probes pass
     bool probePassed = true;
-    nlohmann::json systemConfiguration = nlohmann::json::object();
     while (probePassed)
     {
         probePassed = false;
@@ -822,11 +860,136 @@
             }
         }
     }
+}
+
+void propertiesChangedCallback(
+    std::vector<std::pair<std::unique_ptr<dbus::match>,
+                          std::shared_ptr<dbus::filter>>> &dbusMatches,
+    std::atomic_bool &threadRunning, nlohmann::json &systemConfiguration,
+    dbus::DbusObjectServer &objServer, std::shared_ptr<dbus::filter> dbusFilter)
+{
+    static std::future<void> future;
+    bool notRunning = false;
+    if (threadRunning.compare_exchange_strong(notRunning, true))
+    {
+        future = std::async(std::launch::async, [&] {
+            std::this_thread::sleep_for(std::chrono::seconds(5));
+            auto oldConfiguration = systemConfiguration;
+            DBUS_PROBE_OBJECTS.clear();
+            rescan(systemConfiguration);
+            auto newConfiguration = systemConfiguration;
+            for (auto it = newConfiguration.begin();
+                 it != newConfiguration.end();)
+            {
+                auto findKey = oldConfiguration.find(it.key());
+                if (findKey != oldConfiguration.end())
+                {
+                    it = newConfiguration.erase(it);
+                }
+                else
+                {
+                    it++;
+                }
+            }
+            // only post new items to bus for now
+            postToDbus(newConfiguration, objServer);
+            // this line to be removed in future
+            writeJsonFiles(systemConfiguration);
+            registerCallbacks(dbusMatches, threadRunning, systemConfiguration,
+                              objServer);
+            threadRunning = false;
+        });
+    }
+    if (dbusFilter != nullptr)
+    {
+        dbusFilter->async_dispatch([&, dbusFilter](boost::system::error_code ec,
+                                                   dbus::message) {
+            if (ec)
+            {
+                std::cerr << "properties changed callback error " << ec << "\n";
+            }
+            propertiesChangedCallback(dbusMatches, threadRunning,
+                                      systemConfiguration, objServer,
+                                      dbusFilter);
+        });
+    }
+}
+
+void registerCallbacks(
+    std::vector<std::pair<std::unique_ptr<dbus::match>,
+                          std::shared_ptr<dbus::filter>>> &dbusMatches,
+    std::atomic_bool &threadRunning, nlohmann::json &systemConfiguration,
+    dbus::DbusObjectServer &objServer)
+{
+    static boost::container::flat_set<std::string> watchedObjects;
+
+    for (const auto &objectMap : DBUS_PROBE_OBJECTS)
+    {
+        auto findObject = watchedObjects.find(objectMap.first);
+        if (findObject != watchedObjects.end())
+        {
+            continue;
+        }
+        // this creates a filter for properties changed for any new probe type
+        auto propertyChange = std::make_unique<dbus::match>(
+            SYSTEM_BUS,
+            "type='signal',member='PropertiesChanged',arg0='" +
+                objectMap.first + "'");
+        auto filter =
+            std::make_shared<dbus::filter>(SYSTEM_BUS, [](dbus::message &m) {
+                auto member = m.get_member();
+                return member == "PropertiesChanged";
+            });
+
+        filter->async_dispatch([&, filter](boost::system::error_code ec,
+                                           dbus::message) {
+            if (ec)
+            {
+                std::cerr << "register callbacks callback error " << ec << "\n";
+            }
+            propertiesChangedCallback(dbusMatches, threadRunning,
+                                      systemConfiguration, objServer, filter);
+        });
+        dbusMatches.emplace_back(std::move(propertyChange), filter);
+    }
+}
+
+int main(int argc, char **argv)
+{
+    // setup connection to dbus
+    boost::asio::io_service io;
+    SYSTEM_BUS = std::make_shared<dbus::connection>(io, dbus::bus::system);
+    dbus::DbusObjectServer objServer(SYSTEM_BUS);
+    SYSTEM_BUS->request_name("xyz.openbmc_project.EntityManager");
+
+    nlohmann::json systemConfiguration = nlohmann::json::object();
+    rescan(systemConfiguration);
     // this line to be removed in future
     writeJsonFiles(systemConfiguration);
-    std::vector<std::shared_ptr<dbus::DbusObject>> busObjects;
-    postToDbus(systemConfiguration, objServer, busObjects);
+    postToDbus(systemConfiguration, objServer);
+    auto object = std::make_shared<dbus::DbusObject>(
+        SYSTEM_BUS, "/xyz/openbmc_project/EntityManager");
+    objServer.register_object(object);
+    auto iface = std::make_shared<dbus::DbusInterface>(
+        "xyz.openbmc_project.EntityManager", SYSTEM_BUS);
+    object->register_interface(iface);
+
+    std::atomic_bool threadRunning(false);
+    // to keep reference to the match / filter objects so they don't get
+    // destroyed
+    std::vector<
+        std::pair<std::unique_ptr<dbus::match>, std::shared_ptr<dbus::filter>>>
+        dbusMatches;
+    iface->register_method("ReScan", [&]() {
+        propertiesChangedCallback(dbusMatches, threadRunning,
+                                  systemConfiguration, objServer, nullptr);
+        return std::tuple<>(); // this is a bug in boost-dbus, needs some sort
+                               // of return
+    });
+    registerCallbacks(dbusMatches, threadRunning, systemConfiguration,
+                      objServer);
+
     io.run();
 
     return 0;
-}
\ No newline at end of file
+}