From e8a6bdf98e196f3e1acd5de56939f6b6b4ef2962 Mon Sep 17 00:00:00 2001
From: Julien Muchembled <jm@nexedi.com>
Date: Tue, 12 Jul 2011 16:26:53 +0200
Subject: [PATCH] New methods to efficiently run a script on all objects
 returned by a catalog search

Update Folder.callMethodOnObjectList so that an active proces can collect
results.
---
 product/ERP5Catalog/CatalogTool.py | 45 ++++++++++++++++++++++++++++++
 product/ERP5Type/Core/Folder.py    | 19 ++++++-------
 2 files changed, 54 insertions(+), 10 deletions(-)

diff --git a/product/ERP5Catalog/CatalogTool.py b/product/ERP5Catalog/CatalogTool.py
index 79f5f95634..617218a889 100644
--- a/product/ERP5Catalog/CatalogTool.py
+++ b/product/ERP5Catalog/CatalogTool.py
@@ -956,4 +956,49 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject):
           return aq_base_name
       return aq_base_name
 
+    def _searchAndActivate(self, method_id, method_args=(), method_kw={},
+                           activate_kw={}, min_uid=None, **kw):
+      """Search the catalog and run a script by activity on all found objects
+
+      This method is configurable (via 'packet_size' & 'activity_count'
+      parameters) so that it can work efficiently with databases of any size.
+
+      'activate_kw' may specify an active process to collect results.
+      Note however, we don't use Base_makeActiveResult so you're likely to get
+      ConflictError at the beginning. You could avoid this by making sure
+      'result_list' is already initialized on the active process.
+      """
+      catalog_kw = dict(kw)
+      packet_size = catalog_kw.pop('packet_size', 30)
+      limit = packet_size * catalog_kw.pop('activity_count', 100)
+      if min_uid:
+        catalog_kw['uid'] = {'query': min_uid, 'range': 'nlt'}
+      if catalog_kw.pop('restricted', False):
+        search = self
+      else:
+        search = self.unrestrictedSearchResults
+      r = search(sort_on=(('uid','ascending'),), limit=limit, **catalog_kw)
+      result_count = len(r)
+      if result_count:
+        if result_count == limit:
+          tag = activate_kw.get('tag')
+          if not tag:
+            activate_kw['tag'] = tag = 'searchAndActivate_%r' % time.time()
+          _tag = '%s_%s' % (tag, min_uid)
+          self.activate(tag=tag, after_tag=_tag, activity='SQLQueue') \
+              ._searchAndActivate(method_id,method_args, method_kw,
+                                  dict(activate_kw), r[-1].getUid(), **kw)
+          activate_kw['tag'] = _tag
+        r = [x.getPath() for x in r]
+        r.sort()
+        activate = self.getPortalObject().portal_activities.activate
+        for i in xrange(0, result_count, packet_size):
+          activate(activity='SQLQueue', **activate_kw).callMethodOnObjectList(
+            r[i:i+packet_size], method_id, *method_args, **method_kw)
+
+    security.declarePublic('searchAndActivate')
+    def searchAndActivate(self, *args, **kw):
+      """Restricted version of _searchAndActivate"""
+      return self._searchAndActivate(restricted=True, *args, **kw)
+
 InitializeClass(CatalogTool)
diff --git a/product/ERP5Type/Core/Folder.py b/product/ERP5Type/Core/Folder.py
index 562c02229b..803ee5c137 100644
--- a/product/ERP5Type/Core/Folder.py
+++ b/product/ERP5Type/Core/Folder.py
@@ -1524,21 +1524,20 @@ class Folder(CopyContainer, CMFBTreeFolder, CMFHBTreeFolder, Base, FolderMixIn,
     object.manage_beforeDelete(object, self)
     self._delOb(id)
 
-  security.declareProtected( Permissions.ManagePortal, 'callMethodOnObjectList' )
+  security.declareProtected(Permissions.ManagePortal, 'callMethodOnObjectList')
   def callMethodOnObjectList(self, object_path_list, method_id, *args, **kw):
     """
-    Very usefull if we want to activate the call of a method
+    Very useful if we want to activate the call of a method
     on many objects at a time. Like this we could prevent creating
-    too much acitivities at a time, and we may have only the path
-
+    too many activities at a time, and we may have only the path
     """
-    portal = self.getPortalObject()
+    result_list = []
+    traverse = self.getPortalObject().unrestrictedTraverse
     for object_path in object_path_list:
-      current_object = portal.unrestrictedTraverse(object_path)
-      method = getattr(current_object, method_id, None)
-      if method is None:
-        raise ValueError, "The method %s was not found" % method_id
-      method(*args, **kw)
+      result = getattr(traverse(object_path), method_id)(*args, **kw)
+      if type(result) in (list, tuple):
+        result_list += result
+    return result_list
 
   def _verifyObjectPaste(self, object, validate_src=1):
     # To paste in an ERP5Type folder, we need to check 'Add permission'
-- 
2.30.9