From 08600ea3d3f5df743458e0818227d991bb291b0b Mon Sep 17 00:00:00 2001
From: Ayush Tiwari <ayush.tiwari@nexedi.com>
Date: Fri, 17 Mar 2017 08:54:26 +0000
Subject: [PATCH] bt5_config: Add functions for comparing old and new
 installation state with OFS state

---
 product/ERP5/Tool/TemplateTool.py | 119 +++++++++++++++++++++++-------
 1 file changed, 94 insertions(+), 25 deletions(-)

diff --git a/product/ERP5/Tool/TemplateTool.py b/product/ERP5/Tool/TemplateTool.py
index bd93d82c8e3..da8189e849f 100644
--- a/product/ERP5/Tool/TemplateTool.py
+++ b/product/ERP5/Tool/TemplateTool.py
@@ -61,6 +61,7 @@ from Products.ERP5Type.Message import translateString
 from zLOG import LOG, INFO, WARNING
 from base64 import decodestring
 from difflib import unified_diff
+from operator import attrgetter
 import subprocess
 import time
 
@@ -1682,16 +1683,14 @@ class TemplateTool (BaseTool):
       # Try getting a Business Manager with the title 'Combined Business Manager'
       # which we would need to compare. Also using search keeping in mind we
       # index Business Manager(s)
-      installation_state_result = self.searchFolder(
-                                          portal_type='Business Manager',
-                                          title='Installation State',
-                                          )
+      installation_state_list = [l for l
+                                 in self.objectValues(portal_type='Business Manager')
+                                 if l.title == 'Installation State']
 
       # If there is 1 or more Business Manager(s) with 'Combined Business Manager'
       # title, take the firs one, cause it is the one created currently
-      if len(installation_state_result) :
-        path = installation_state_result.dictionaries()[0]['path']
-        old_installation_state = self.unrestrictedTraverse(path)
+      if len(installation_state_list) :
+        old_installation_state = installation_state_list[-1]
       else:
         # Create a new Business Manager which we'll use for comapring installation
         # state
@@ -1720,7 +1719,15 @@ class TemplateTool (BaseTool):
       buildBM._setTemplatePathList(final_template_path_list)
       buildBM.build()
 
+      # Compare installation state with OFS state
+      self.compareInstallationStateWithOFS(buildBM,
+                                           new_installation_state,
+                                           old_installation_state)
+
+      # Install the compared installation state
       self.installBusinessManager(new_installation_state)
+
+      # Remove the path_item with negative sign
       self.cleanInstallationState(new_installation_state)
 
       # Change status of all BM installed
@@ -1738,6 +1745,22 @@ class TemplateTool (BaseTool):
       """
       new_bm_list = bm_list[:]
 
+      # Create a list of path and hashes to be used for comparison
+      path_list = []
+      sha_list = []
+      forbidden_bm_title_list = ['Installation State',]
+
+      for bm in new_bm_list:
+        forbidden_bm_title_list.append(bm.title)
+        for item in bm._path_item_list:
+          path_list.append(item._path)
+          sha_list.append(item._sha)
+
+      installed_bm_list = [l for l
+                           in self.getInstalledBusinessManagerList()
+                           if l.title not in forbidden_bm_title_list]
+      new_bm_list.extend(installed_bm_list)
+
       # Summation should also consider arithmetic on the Business Item(s)
       # having same path and layer and combine them.
       combinedBM = self.newContent(portal_type='Business Manager',
@@ -1746,32 +1769,53 @@ class TemplateTool (BaseTool):
       new_bm_list.insert(0, combinedBM)
       combinedBM = reduce(lambda x, y: x+y, new_bm_list)
 
-      # Compare combinedBM to the old_installation_state and return new
-      # installation state
-      path_list = combinedBM.getTemplatePathList()
-
-      final_path_list = []
+      final_path_list = combinedBM.getTemplatePathList()
       final_path_item_list = []
 
-      final_path_list.extend(path_list)
       final_path_item_list.extend(combinedBM._path_item_list)
 
+      removable_sha_list = []
+      removable_path_list = []
+
       for item in old_installation_state._path_item_list:
-        if item._path in path_list:
+        if item._path in path_list and item._sha in sha_list:
+          # If there is Business Item which have no change on updation, then
+          # no need to reinstall it, cause in that case we prefer the changes
+          # at ZODB
+
+          # XXX: BAD DESIGN: Should compare both path as well as hash and keep
+          # them together in a dictionary,using them separately can lead to
+          # conflict in case two paths have same hash.
+          removable_sha_list.append(item._sha)
+          removable_path_list.append(item._path)
+        else:
           # If there is update of path item, change the sign of the last
           # version of that Business Item and add it to final_path_item_list
           item._sign = -1
-        final_path_list.append(item._path)
-        final_path_item_list.append(item)
+          final_path_item_list.append(item)
+
+      final_path_list.extend(old_installation_state.getTemplatePathList())
+
+      # Remove the Business Item objects from final_path_item_list which have
+      # same sha and path
+      final_path_list = [path for path
+                         in final_path_list
+                         if path not in removable_path_list]
+
+      final_path_item_list = [item for item
+                              in final_path_item_list
+                              if item._sha not in removable_sha_list]
 
-      final_path_list = list(set(final_path_list))
       final_path_item_list.sort(key=lambda x: x._sign)
 
+      # Remove the old installation state
+      self._delObject(old_installation_state.getId())
+
       combinedBM._setTemplatePathList(final_path_list)
       combinedBM._path_item_list = final_path_item_list
 
       # Change the title of the combined BM
-      combinedBM.edit(title='Installation State')
+      combinedBM.setTitle('Installation State')
 
       # XXX: We are missing the part of creating installed_BM for all the BM
       # we have in bm_list, because this would be needed in case we build
@@ -1784,7 +1828,8 @@ class TemplateTool (BaseTool):
 
     security.declareProtected(Permissions.ManagePortal,
             'compareInstallationStateWithOFS')
-    def compareInstallationStateWithOFS(self, buildBM, installingBM):
+    def compareInstallationStateWithOFS(self, buildBM, new_installation_state,
+            old_installation_state):
       """
       Compare the buildBM and to be installed BM and show the changes in a way
       it can be notified or installed.
@@ -1799,14 +1844,38 @@ class TemplateTool (BaseTool):
       - has_confict: True , if there is a conflict between the 2 BMs
       - DiffFile: In case there is conflict between the two states
       """
-      installing_sha_list = [item._sha for item in installingBM._path_item_list]
+      # XXX: BAD DESIGN: Should compare both path as well as hash, just path
+      # can lead to conflict in case two paths have same sha.
+
+      built_item_dict = {
+                        item._path: item._sha for
+                        item in buildBM._path_item_list
+                        }
+
+      old_item_dict = {
+                      item._path: item._sha for
+                      item in old_installation_state._path_item_list
+                      }
+
+      # For creating new_item_dict, we have to take use of template_path_list
+      # property as there can be case where we have path but not path_item as
+      # the new state already gets filtered while creation
+      new_item_dict = {
+                      item._path: item._sha for
+                      item in new_installation_state._path_item_list
+                      }
+
+      build_sha_list = built_item_dict.values()
       final_item_list = []
 
-      for item in buildBM._path_item_list:
-        if not item._sha in installing_sha_list:
-          final_item_list.append(item)
+      for item in new_installation_state._path_item_list:
+        if item._sha in build_sha_list and item._sign == 1:
+          # No need to install value which we already have
+          continue
         else:
-          return final_item_list
+          final_item_list.append(item)
+
+      new_installation_state._path_item_list = final_item_list
 
     security.declareProtected(Permissions.ManagePortal,
             'compareMultipleBusinessManager')
@@ -1828,7 +1897,7 @@ class TemplateTool (BaseTool):
       **WARNING**:This should only be done after installing the Business Manager
       """
       if installation_state.getStatus() != 'installed':
-        LOG(WARNING, 0, 'Trying to clean installation state before installing')
+        LOG('WARNING', 0, 'Trying to clean installation state before installing')
         raise ValueError, "Can't clean before installing"
 
       final_path_item_list = []
-- 
2.30.9