Update PSU if version cannot be obtained

Currently this application will ignore a PSU if the PSU code version
cannot be obtained.

However, there are several scenarios where this could occur:
* A code update was previously attempted on the PSU, and the update
  failed.  The PSU is now in a bad state, and the update needs to be
  performed again.
* The PSU is running old code that does not support reading the code
  version.

In these scenarios performing a code update on the PSU is desired.

Enhance this application so that a PSU with an unknown code version will
be updated provided that it is the correct model.

Tested:
* Verified code update was still performed if version could not be
  obtained.
* Tested where PSU information is found at start of application
* Tested where PSU information is found from InterfacesAdded event
* Tested where PSU information is found from PropertiesChanged event
* For the complete test plan, see
  https://gist.github.com/smccarney/3846a97fef590eb69181edc669555a00

Change-Id: I6e60761fa20eeb4500529c00f33de16d69a8d84d
Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
diff --git a/src/item_updater.cpp b/src/item_updater.cpp
index 8c92917..a545bf3 100644
--- a/src/item_updater.cpp
+++ b/src/item_updater.cpp
@@ -266,8 +266,6 @@
 
 void ItemUpdater::removePsuObject(const std::string& psuInventoryPath)
 {
-    psuStatusMap[psuInventoryPath] = {false, ""};
-
     auto it = psuPathActivationMap.find(psuInventoryPath);
     if (it == psuPathActivationMap.end())
     {
@@ -302,6 +300,24 @@
     }
 }
 
+void ItemUpdater::addPsuToStatusMap(const std::string& psuPath)
+{
+    if (!psuStatusMap.contains(psuPath))
+    {
+        psuStatusMap[psuPath] = {false, ""};
+
+        // Add matches for PSU Inventory's property changes
+        psuMatches.emplace_back(
+            bus, MatchRules::propertiesChanged(psuPath, ITEM_IFACE),
+            std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this,
+                      std::placeholders::_1)); // For present
+        psuMatches.emplace_back(
+            bus, MatchRules::propertiesChanged(psuPath, ASSET_IFACE),
+            std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this,
+                      std::placeholders::_1)); // For model
+    }
+}
+
 std::unique_ptr<Version> ItemUpdater::createVersionObject(
     const std::string& objPath, const std::string& versionId,
     const std::string& versionString,
@@ -371,8 +387,6 @@
             {
                 createPsuObject(psuPath, version);
             }
-            // Check if there is new PSU images to update
-            syncToLatestImage();
         }
         else
         {
@@ -380,6 +394,9 @@
             log<level::ERR>("Failed to get PSU version",
                             entry("PSU=%s", psuPath.c_str()));
         }
+        // Check if there are new PSU images to update
+        processStoredImage();
+        syncToLatestImage();
     }
     else
     {
@@ -390,6 +407,8 @@
             // handled by "Present" callback.
             return;
         }
+        psuStatusMap[psuPath].model = "";
+
         // Remove object or association
         removePsuObject(psuPath);
     }
@@ -400,25 +419,18 @@
     auto paths = utils::getPSUInventoryPath(bus);
     for (const auto& p : paths)
     {
+        addPsuToStatusMap(p);
+
         auto service = utils::getService(bus, p.c_str(), ITEM_IFACE);
-        auto present = utils::getProperty<bool>(bus, service.c_str(), p.c_str(),
-                                                ITEM_IFACE, PRESENT);
+        psuStatusMap[p].present = utils::getProperty<bool>(
+            bus, service.c_str(), p.c_str(), ITEM_IFACE, PRESENT);
         psuStatusMap[p].model = utils::getProperty<std::string>(
             bus, service.c_str(), p.c_str(), ASSET_IFACE, MODEL);
         auto version = utils::getVersion(p);
         if ((psuPathActivationMap.find(p) == psuPathActivationMap.end()) &&
-            present && !version.empty())
+            psuStatusMap[p].present && !version.empty())
         {
             createPsuObject(p, version);
-            // Add matches for PSU Inventory's property changes
-            psuMatches.emplace_back(
-                bus, MatchRules::propertiesChanged(p, ITEM_IFACE),
-                std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this,
-                          std::placeholders::_1)); // For present
-            psuMatches.emplace_back(
-                bus, MatchRules::propertiesChanged(p, ASSET_IFACE),
-                std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this,
-                          std::placeholders::_1)); // For model
         }
     }
 }
@@ -599,8 +611,14 @@
     msg.read(objPath, interfaces);
     std::string path = objPath.str;
 
-    if (interfaces.find(PSU_INVENTORY_IFACE) == interfaces.end() ||
-        (psuStatusMap[path].present && !psuStatusMap[path].model.empty()))
+    if (interfaces.find(PSU_INVENTORY_IFACE) == interfaces.end())
+    {
+        return;
+    }
+
+    addPsuToStatusMap(path);
+
+    if (psuStatusMap[path].present && !psuStatusMap[path].model.empty())
     {
         return;
     }
diff --git a/src/item_updater.hpp b/src/item_updater.hpp
index e07aca8..885847a 100644
--- a/src/item_updater.hpp
+++ b/src/item_updater.hpp
@@ -163,6 +163,15 @@
      */
     void removePsuObject(const std::string& psuInventoryPath);
 
+    /** @brief Add PSU inventory path to the PSU status map
+     *  @details Also adds PropertiesChanged listeners for the inventory path so
+     *           we are notified when the present or model properties change.
+     *           Does nothing if the inventory path already exists in the map.
+     *
+     * @param[in]  psuPath - The PSU inventory path
+     */
+    void addPsuToStatusMap(const std::string& psuPath);
+
     /**
      * @brief Create and populate the active PSU Version.
      */