From 80316ccd813ce7855ce0bfc8e490ddc91ddeb2e3 Mon Sep 17 00:00:00 2001
From: Julien Muchembled <jm@nexedi.com>
Date: Wed, 23 Feb 2011 15:42:35 +0000
Subject: [PATCH] Review implementation of BusinessTemplateArchive

- fix export to tarball
- new API will allow to export files directly to the working copy without
  using any temporary folder

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@43617 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 product/ERP5/Document/BusinessTemplate.py | 223 +++++++++-------------
 1 file changed, 87 insertions(+), 136 deletions(-)

diff --git a/product/ERP5/Document/BusinessTemplate.py b/product/ERP5/Document/BusinessTemplate.py
index 18b93ddd1c..3bd4a0c73a 100644
--- a/product/ERP5/Document/BusinessTemplate.py
+++ b/product/ERP5/Document/BusinessTemplate.py
@@ -27,7 +27,7 @@
 #
 ##############################################################################
 
-import fnmatch, gc, imp, os, re, shutil, sys
+import fnmatch, gc, imp, os, re, shutil, sys, time
 from Shared.DC.ZRDB import Aqueduct
 from Shared.DC.ZRDB.Connection import Connection as RDBConnection
 from Products.ERP5Type.DiffUtils import DiffFile
@@ -312,14 +312,8 @@ class BusinessTemplateArchive:
   """
     This is the base class for all Business Template archives
   """
-  def _initCreation(self, path):
+  def _initCreation(self, path, **kw):
     self.path = path
-    try:
-      os.makedirs(self.path)
-    except OSError:
-      # folder already exists, remove it
-      shutil.rmtree(self.path)
-      os.makedirs(self.path)
 
   def __init__(self, creation=0, importing=0, file=None, path=None, **kw):
     if creation:
@@ -327,46 +321,44 @@ class BusinessTemplateArchive:
     elif importing:
       self._initImport(file=file, path=path, **kw)
 
-  def addFolder(self, **kw):
-    pass
-
   def addObject(self, obj, name, path=None, ext='.xml'):
-    name = name.replace('\\', '/')
-    name = quote(name)
-    name = os.path.normpath(name)
-    if path is None:
-      object_path = os.path.join(self.path, name)
-    else:
-      if '%' not in path:
-        tail, path = os.path.splitdrive(path)
-        path = path.replace('\\', '/')
-        path = tail + quote(path)
-      path = os.path.normpath(path)
-      object_path = os.path.join(path, name)
-    f = open(object_path+ext, 'wb')
+    if path:
+      name = posixpath.join(path, name)
+    # XXX required due to overuse of os.path
+    name = name.replace('\\', '/').replace(':', '/')
+    name = quote(name + ext)
+    path = name.replace('/', os.sep)
     try:
-      f.write(str(obj))
-    finally:
-      f.close()
+      write = self._writeFile
+    except AttributeError:
+      if not isinstance(obj, str):
+        obj.seek(0)
+        obj = obj.read()
+      self._writeString(obj, path)
+    else:
+      if isinstance(obj, str):
+        obj = StringIO(obj)
+      write(obj, path)
 
-  def finishCreation(self, name=None, **kw):
+  def finishCreation(self):
     pass
 
 class BusinessTemplateFolder(BusinessTemplateArchive):
   """
     Class archiving business template into a folder tree
   """
-  def addFolder(self, name=''):
-    if name != '':
-      name = os.path.normpath(name)
-      path = os.path.join(self.path, name)
-      if not os.path.exists(path):
-        os.makedirs(path)
-      return path
-
-  def _initImport(self, file=None, path=None, **kw):
-    # Normalize the paths to eliminate the effect of double-slashes.
-    root_path_len = len(os.path.normpath(path)) + len(os.sep)
+  def _writeString(self, obj, path):
+    object_path = os.path.join(self.path, path)
+    path = os.path.dirname(object_path)
+    os.path.exists(path) or os.makedirs(path)
+    f = open(object_path, 'wb')
+    try:
+      f.write(obj)
+    finally:
+      f.close()
+
+  def _initImport(self, file, path, **kw):
+    root_path_len = len(os.path.normpath(os.path.join(path, '_'))) - 1
     self.root_path_len = root_path_len
     d = {}
     for f in file:
@@ -408,26 +400,28 @@ class BusinessTemplateTarball(BusinessTemplateArchive):
     Class archiving businnes template into a tarball file
   """
 
-  def _initCreation(self, path):
-    BusinessTemplateArchive._initCreation(self, path)
-
-    # make tmp dir, must use stringIO instead
+  def _initCreation(self, **kw):
+    BusinessTemplateArchive._initCreation(self, **kw)
     # init tarfile obj
     self.fobj = StringIO()
     self.tar = tarfile.open('', 'w:gz', self.fobj)
-
-  def addFolder(self, name=''):
-    name = os.path.normpath(name)
-    if not os.path.exists(name):
-      os.makedirs(name)
-
-  def finishCreation(self, name):
-    self.tar.add(name)
+    self.time = time.time()
+
+  def _writeFile(self, obj, path):
+    if self.path:
+      path = posixpath.join(self.path, path)
+    info = tarfile.TarInfo(path)
+    info.mtime = self.time
+    obj.seek(0, 2)
+    info.size = obj.tell()
+    obj.seek(0)
+    self.tar.addfile(info, obj)
+
+  def finishCreation(self):
     self.tar.close()
-    shutil.rmtree(name)
     return self.fobj
 
-  def _initImport(self, file=None, **kw):
+  def _initImport(self, file, **kw):
     self.tar = tarfile.TarFile(fileobj=StringIO(GzipFile(fileobj=file).read()))
     self.item_dict = {}
     setdefault = self.item_dict.setdefault
@@ -688,22 +682,12 @@ class ObjectTemplateItem(BaseTemplateItem):
     """
     if len(self._objects.keys()) == 0:
       return
-    root_path = os.path.join(bta.path, self.__class__.__name__)
+    path = self.__class__.__name__
     for key, obj in self._objects.iteritems():
-      # create folder and subfolders
-      folders, id = posixpath.split(key)
-      encode_folders = []
-      for folder in folders.split('/'):
-        if '%' not in folder:
-          encode_folders.append(quote(folder))
-        else:
-          encode_folders.append(folder)
-      path = os.path.join(root_path, (os.sep).join(encode_folders))
-      bta.addFolder(name=path)
       # export object in xml
       f = StringIO()
       XMLExportImport.exportXML(obj._p_jar, obj._p_oid, f)
-      bta.addObject(obj=f.getvalue(), name=id, path=path)
+      bta.addObject(f, key, path=path)
 
   def build_sub_objects(self, context, id_list, url, **kw):
     # XXX duplicates code from build
@@ -1669,12 +1653,10 @@ class RegisteredSkinSelectionTemplateItem(BaseTemplateItem):
   def export(self, context, bta, **kw):
     if not self._objects:
       return
-    root_path = os.path.join(bta.path, self.__class__.__name__)
-    bta.addFolder(name=root_path)
     # export workflow chain
-    bta.addObject(obj=self.generateXml(), 
-                  name='registered_skin_selection',  
-                  path=root_path)
+    bta.addObject(self.generateXml(),
+                  name='registered_skin_selection',
+                  path=self.__class__.__name__)
 
   def install(self, context, trashbin, **kw):
     update_dict = kw.get('object_to_update')
@@ -2061,11 +2043,10 @@ class PortalTypeWorkflowChainTemplateItem(BaseTemplateItem):
   def export(self, context, bta, **kw):
     if not self._objects:
       return
-    root_path = os.path.join(bta.path, self.__class__.__name__)
-    bta.addFolder(name=root_path)
     # export workflow chain
     xml_data = self.generateXml()
-    bta.addObject(obj=xml_data, name='workflow_chain_type',  path=root_path)
+    bta.addObject(xml_data, name='workflow_chain_type',
+                  path=self.__class__.__name__)
 
   def install(self, context, trashbin, **kw):
     update_dict = kw.get('object_to_update')
@@ -2268,11 +2249,9 @@ class PortalTypeAllowedContentTypeTemplateItem(BaseTemplateItem):
   def export(self, context, bta, **kw):
     if not self._objects:
       return
-    path = os.path.join(bta.path, self.__class__.__name__)
-    bta.addFolder(name=path)
-    path = os.sep.join((self.__class__.__name__, self.class_property,))
     xml_data = self.generateXml(path=None)
-    bta.addObject(obj=xml_data, name=path, path=None)
+    bta.addObject(xml_data, name=self.class_property,
+                  path=self.__class__.__name__)
 
   def preinstall(self, context, installed_item, **kw):
     modified_object_list = {}
@@ -2505,21 +2484,16 @@ class CatalogMethodTemplateItem(ObjectTemplateItem):
 
     if len(self._objects.keys()) == 0:
       return
-    root_path = os.path.join(bta.path, self.__class__.__name__)
+    path = self.__class__.__name__
     for key in self._objects.keys():
       obj = self._objects[key]
-      # create folder and subfolders
-      folders, id = posixpath.split(key)
-      path = os.path.join(root_path, folders)
-      bta.addFolder(name=path)
       # export object in xml
       f=StringIO()
       XMLExportImport.exportXML(obj._p_jar, obj._p_oid, f)
-      bta.addObject(obj=f.getvalue(), name=id, path=path)
+      bta.addObject(f, key, path=path)
       # add all datas specific to catalog inside one file
-      key_name = os.path.join(obj.id+'.catalog_keys')
       xml_data = self.generateXml(key)
-      bta.addObject(obj=xml_data, name=key_name, path=path)
+      bta.addObject(xml_data, key + '.catalog_keys', path=path)
 
   def install(self, context, trashbin, **kw):
     ObjectTemplateItem.install(self, context, trashbin, **kw)
@@ -3016,14 +2990,13 @@ class PortalTypeRolesTemplateItem(BaseTemplateItem):
   def export(self, context, bta, **kw):
     if len(self._objects.keys()) == 0:
       return
-    root_path = os.path.join(bta.path, self.__class__.__name__)
-    bta.addFolder(name=root_path)
+    path = self.__class__.__name__
     for key in self._objects.keys():
       xml_data = self.generateXml(key)
       if isinstance(xml_data, unicode):
         xml_data = xml_data.encode('utf-8')
       name = key.split('/', 1)[1]
-      bta.addObject(obj=xml_data, name=name, path=root_path)
+      bta.addObject(xml_data, name=name, path=path)
 
   def _importFile(self, file_name, file):
     if not file_name.endswith('.xml'):
@@ -3196,15 +3169,13 @@ class SitePropertyTemplateItem(BaseTemplateItem):
   def export(self, context, bta, **kw):
     if len(self._objects.keys()) == 0:
       return
-    root_path = os.path.join(bta.path, self.__class__.__name__)
-    bta.addFolder(name=root_path)
     xml_data = '<site_property>'
     keys = self._objects.keys()
     keys.sort()
     for path in keys:
       xml_data += self.generateXml(path)
     xml_data += '\n</site_property>'
-    bta.addObject(obj=xml_data, name='properties', path=root_path)
+    bta.addObject(xml_data, name='properties', path=self.__class__.__name__)
 
 class ModuleTemplateItem(BaseTemplateItem):
 
@@ -3268,14 +3239,13 @@ class ModuleTemplateItem(BaseTemplateItem):
   def export(self, context, bta, **kw):
     if len(self._objects) == 0:
       return
-    path = os.path.join(bta.path, self.__class__.__name__)
-    bta.addFolder(path)
+    path = self.__class__.__name__
     keys = self._objects.keys()
     keys.sort()
     for key in keys:
       # export modules one by one
       xml_data = self.generateXml(path=key)
-      bta.addObject(obj=xml_data, name=key, path=path)
+      bta.addObject(xml_data, name=key, path=path)
 
   def install(self, context, trashbin, **kw):
     portal = context.getPortalObject()
@@ -3479,15 +3449,13 @@ class DocumentTemplateItem(BaseTemplateItem):
   def export(self, context, bta, **kw):
     if len(self._objects.keys()) == 0:
       return
-    path = os.path.join(bta.path, self.__class__.__name__)
-    bta.addFolder(name=path)
     extra_prefix = self.__class__.__name__ + '/'
     for key in self._objects.keys():
       obj = self._objects[key]
       # BBB the prefix was put into each key in the previous implementation.
       if not key.startswith(extra_prefix):
         key = extra_prefix + key
-      bta.addObject(obj=obj, name=key, ext='.py')
+      bta.addObject(obj, name=key, ext='.py')
 
   def _importFile(self, file_name, file):
     if not file_name.endswith('.py'):
@@ -3847,8 +3815,6 @@ class RoleTemplateItem(BaseTemplateItem):
   def export(self, context, bta, **kw):
     if len(self._objects) == 0:
       return
-    path = os.path.join(bta.path, self.__class__.__name__)
-    bta.addFolder(name=path)
     # BBB it might be necessary to change the data structure.
     obsolete_key = self.__class__.__name__ + '/role_list'
     if obsolete_key in self._objects:
@@ -3857,7 +3823,7 @@ class RoleTemplateItem(BaseTemplateItem):
       del self._objects[obsolete_key]
     xml_data = self.generateXml()
     path = obsolete_key
-    bta.addObject(obj=xml_data, name=path)
+    bta.addObject(xml_data, name=path)
 
 class CatalogSearchKeyTemplateItem(BaseTemplateItem):
   key_list_attr = 'sql_catalog_search_keys'
@@ -3945,11 +3911,9 @@ class CatalogSearchKeyTemplateItem(BaseTemplateItem):
   def export(self, context, bta, **kw):
     if len(self._objects.keys()) == 0:
       return
-    path = os.path.join(bta.path, self.__class__.__name__)
-    bta.addFolder(name=path)
     for path in self._objects.keys():
       xml_data = self.generateXml(path=path)
-      bta.addObject(obj=xml_data, name=path, path=None)
+      bta.addObject(xml_data, name=path)
 
 class CatalogResultKeyTemplateItem(CatalogSearchKeyTemplateItem):
   key_list_attr = 'sql_search_result_keys'
@@ -4186,19 +4150,17 @@ class MessageTranslationTemplateItem(BaseTemplateItem):
   def export(self, context, bta, **kw):
     if len(self._objects) == 0:
       return
-    root_path = os.path.join(bta.path, self.__class__.__name__)
-    bta.addFolder(name=root_path)
+    root_path = self.__class__.__name__
     for key, obj in self._objects.iteritems():
       path = os.path.join(root_path, key)
-      bta.addFolder(name=path)
       if '/' in key:
-        bta.addObject(obj, os.path.join(path, 'translation'), ext='.po')
+        bta.addObject(obj, 'translation', ext='.po', path=path)
       else:
         xml_data = ['<language>']
         xml_data.append(' <code>%s</code>' % (escape(key), ))
         xml_data.append(' <name>%s</name>' % (escape(obj), ))
         xml_data.append('</language>')
-        bta.addObject('\n'.join(xml_data), os.path.join(path, 'language'))
+        bta.addObject('\n'.join(xml_data), 'language', path=path)
 
   def _importFile(self, file_name, file):
     name = posixpath.split(file_name)[1]
@@ -4242,23 +4204,11 @@ class LocalRolesTemplateItem(BaseTemplateItem):
     return xml_data
 
   def export(self, context, bta, **kw):
-    if len(self._objects.keys()) == 0:
-      return
-    root_path = os.path.join(bta.path, self.__class__.__name__)
-    bta.addFolder(name=root_path)
-    for key in self._objects.keys():
+    path = self.__class__.__name__
+    for key in self._objects:
       xml_data = self.generateXml(key)
-
-      folders, id = posixpath.split(key)
-      encode_folders = []
-      for folder in folders.split('/')[1:]:
-        if '%' not in folder:
-          encode_folders.append(quote(folder))
-        else:
-          encode_folders.append(folder)
-      path = os.path.join(root_path, (os.sep).join(encode_folders))
-      bta.addFolder(name=path)
-      bta.addObject(obj=xml_data, name=id, path=path)
+      assert key[:12] == 'local_roles/'
+      bta.addObject(xml_data, key[12:], path=path)
 
   def _importFile(self, file_name, file):
     if not file_name.endswith('.xml'):
@@ -5139,7 +5089,7 @@ Business Template is a set of definitions, such as skins, portal types and categ
       return False
 
     security.declareProtected(Permissions.ManagePortal, 'export')
-    def export(self, path=None, local=0, **kw):
+    def export(self, path=None, local=0, bta=None, **kw):
       """
         Export this Business Template
       """
@@ -5147,15 +5097,17 @@ Business Template is a set of definitions, such as skins, portal types and categ
         raise TemplateConditionError, \
               'Business Template must be built before export'
 
-      if local:
-        # we export into a folder tree
-        bta = BusinessTemplateFolder(creation=1, path=path)
-      else:
-        # We export BT into a tarball file
-        bta = BusinessTemplateTarball(creation=1, path=path)
+      if bta is None:
+        if local:
+          # we export into a folder tree
+          bta = BusinessTemplateFolder(creation=1, path=path)
+        else:
+          # We export BT into a tarball file
+          if path is None:
+            path = self.getTitle()
+          bta = BusinessTemplateTarball(creation=1, path=path)
 
       # export bt
-      bta.addFolder(path+os.sep+'bt')
       for prop in self.propertyMap():
         prop_type = prop['type']
         id = prop['id']
@@ -5166,16 +5118,15 @@ Business Template is a set of definitions, such as skins, portal types and categ
         if not value:
           continue
         if prop_type in ('text', 'string', 'int', 'boolean'):
-          bta.addObject(obj=value, name=id, path=path+os.sep+'bt', ext='')
+          bta.addObject(str(value), name=id, path='bt', ext='')
         elif prop_type in ('lines', 'tokens'):
-          bta.addObject(obj=str('\n').join(value), name=id,
-                        path=path+os.sep+'bt', ext='')
+          bta.addObject('\n'.join(value), name=id, path='bt', ext='')
 
       # Export each part
       for item_name in self._item_name_list:
         getattr(self, item_name).export(context=self, bta=bta)
 
-      return bta.finishCreation(self.getTitle())
+      return bta.finishCreation()
 
     security.declareProtected(Permissions.ManagePortal, 'importFile')
     def importFile(self, dir = 0, file=None, root_path=None):
-- 
2.30.9