Commit 2798e1d9 authored by Yoshinori Okuji's avatar Yoshinori Okuji

Clean up the code very much, and add new features.

Import modules globally instead of within a method.
Remove importURL and deleteBackupObjects.
Add security declarations into publish, download.
Fix file descriptor leaking and temporary file leaking.
Add manage_download, updateRepositoryBusinessTemplateList,
getRepositoryList, getRepositoryBusinessTemplateList,
getUpdatedRepositoryBusinessTemplateList, and compareVersions.


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@4674 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent c53e569c
...@@ -32,7 +32,7 @@ from Products.CMFCore.utils import UniqueObject ...@@ -32,7 +32,7 @@ from Products.CMFCore.utils import UniqueObject
from App.config import getConfiguration from App.config import getConfiguration
import os, tarfile, string, commands, OFS import os, tarfile, string, commands, OFS
from Acquisition import Implicit from Acquisition import Implicit, aq_base
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Globals import InitializeClass, DTMLFile, PersistentMapping from Globals import InitializeClass, DTMLFile, PersistentMapping
from Products.ERP5Type.Tool.BaseTool import BaseTool from Products.ERP5Type.Tool.BaseTool import BaseTool
...@@ -44,7 +44,12 @@ from OFS.Traversable import NotFound ...@@ -44,7 +44,12 @@ from OFS.Traversable import NotFound
from difflib import unified_diff from difflib import unified_diff
from cStringIO import StringIO from cStringIO import StringIO
from zLOG import LOG from zLOG import LOG
from urllib import pathname2url from urllib import pathname2url, urlopen, splittype, urlretrieve
import re
from xml.dom.minidom import parse
import struct
import cPickle
import base64
class LocalConfiguration(Implicit): class LocalConfiguration(Implicit):
""" """
...@@ -77,6 +82,9 @@ class TemplateTool (BaseTool): ...@@ -77,6 +82,9 @@ class TemplateTool (BaseTool):
meta_type = 'ERP5 Template Tool' meta_type = 'ERP5 Template Tool'
portal_type = 'Template Tool' portal_type = 'Template Tool'
allowed_types = ( 'ERP5 Business Template',) allowed_types = ( 'ERP5 Business Template',)
# This stores information on repositories.
repository_dict = {}
# Declarative Security # Declarative Security
security = ClassSecurityInfo() security = ClassSecurityInfo()
...@@ -97,14 +105,6 @@ class TemplateTool (BaseTool): ...@@ -97,14 +105,6 @@ class TemplateTool (BaseTool):
return bt return bt
return None return None
# Import a business template
def importURL(self, url):
"""
Import a business template
"""
# Copy it to import directory
# and import in self
def updateLocalConfiguration(self, template, **kw): def updateLocalConfiguration(self, template, **kw):
template_id = template.getId() template_id = template.getId()
if not hasattr(self, '_local_configuration'): self._local_configuration = PersistentMapping() if not hasattr(self, '_local_configuration'): self._local_configuration = PersistentMapping()
...@@ -143,8 +143,8 @@ class TemplateTool (BaseTool): ...@@ -143,8 +143,8 @@ class TemplateTool (BaseTool):
""" """
path = business_template.getTitle() path = business_template.getTitle()
path = pathname2url(path) path = pathname2url(path)
tmpdir_path = mkdtemp() tmpdir_path = mkdtemp() # XXXXXXXXXXXXXXX Why is it necessary to create a temporary directory?
current_directory = os.getcwd() current_directory = os.getcwd() # XXXXXXXXXXXXXXXXXX not thread safe
os.chdir(tmpdir_path) os.chdir(tmpdir_path)
export_string = business_template.export(path=path) export_string = business_template.export(path=path)
os.chdir(current_directory) os.chdir(current_directory)
...@@ -159,6 +159,7 @@ class TemplateTool (BaseTool): ...@@ -159,6 +159,7 @@ class TemplateTool (BaseTool):
finally: finally:
export_string.close() export_string.close()
security.declareProtected( 'Import/Export objects', 'publish' )
def publish(self, business_template, url, username=None, password=None): def publish(self, business_template, url, username=None, password=None):
""" """
Publish in a format or another Publish in a format or another
...@@ -185,10 +186,13 @@ class TemplateTool (BaseTool): ...@@ -185,10 +186,13 @@ class TemplateTool (BaseTool):
Import template from a temp file Import template from a temp file
""" """
file = open(path, 'r') file = open(path, 'r')
# read magic key to determine wich kind of bt we use try:
file.seek(0) # read magic key to determine wich kind of bt we use
magic = file.read(5) file.seek(0)
file.close() magic = file.read(5)
finally:
file.close()
if magic == '<?xml': # old version if magic == '<?xml': # old version
self._importObjectFromFile(path, id=id) self._importObjectFromFile(path, id=id)
bt = self[id] bt = self[id]
...@@ -196,41 +200,59 @@ class TemplateTool (BaseTool): ...@@ -196,41 +200,59 @@ class TemplateTool (BaseTool):
bt.setProperty('template_format_version', 0, type='int') bt.setProperty('template_format_version', 0, type='int')
else: # new version else: # new version
tar = tarfile.open(path, 'r:gz') tar = tarfile.open(path, 'r:gz')
# create bt object try:
self.newContent(portal_type='Business Template', id=id) # create bt object
bt = self._getOb(id) self.newContent(portal_type='Business Template', id=id)
prop_dict = {} bt = self._getOb(id)
for prop in bt.propertyMap(): prop_dict = {}
type = prop['type'] for prop in bt.propertyMap():
pid = prop['id'] type = prop['type']
prop_path = os.path.join(tar.members[0].name, 'bt', pid) pid = prop['id']
prop_path = os.path.join(tar.members[0].name, 'bt', pid)
try:
info = tar.getmember(prop_path)
except KeyError:
continue
value = tar.extractfile(info).read()
if type == 'text' or type == 'string' or type == 'int':
prop_dict[pid] = value
elif type == 'lines' or type == 'tokens':
prop_dict[pid[:-5]] = value.split(str(os.linesep))
prop_dict.pop('id', '')
bt.edit(**prop_dict)
# import all other files from bt
fobj = open(path, 'r')
try: try:
info = tar.getmember(prop_path) bt.importFile(file=fobj)
except KeyError: finally:
continue fobj.close()
value = tar.extractfile(info).read() finally:
if type == 'text' or type == 'string' or type == 'int': tar.close()
prop_dict[pid] = value
elif type == 'lines' or type == 'tokens':
prop_dict[pid[:-5]] = value.split(str(os.linesep))
prop_dict.pop('id', '')
bt.edit(**prop_dict)
# import all other files from bt
fobj = open(path, 'r')
bt.importFile(file=fobj)
fobj.close()
tar.close()
os.remove(path)
return bt return bt
def download(self, url, id=None, REQUEST=None): security.declareProtected( Permissions.ManagePortal, 'download' )
""" def manage_download(self, url, id=None, REQUEST=None):
Download Business template, can be file or local directory """The management interface for download.
""" """
if REQUEST is None: if REQUEST is None:
REQUEST = getattr(self, 'REQUEST', None) REQUEST = getattr(self, 'REQUEST', None)
from urllib import splittype, urlretrieve
self.download(url, id=id)
if REQUEST is not None:
ret_url = self.absolute_url() + '/' + REQUEST.get('form_id', 'view')
REQUEST.RESPONSE.redirect("%s?portal_status_message=Business+Templates+Downloaded+Successfully"
% ret_url)
security.declareProtected( 'Import/Export objects', 'download' )
def download(self, url, id=None, REQUEST=None):
"""
Download Business template, can be file or local directory
"""
# For backward compatibility; If REQUEST is passed, it is likely from the management interface.
if REQUEST is not None:
self.manage_download(url, id=id, REQUEST=REQUEST)
type, name = splittype(url) type, name = splittype(url)
if os.path.isdir(name): # new version of business template in plain format (folder) if os.path.isdir(name): # new version of business template in plain format (folder)
file_list = [] file_list = []
...@@ -242,8 +264,8 @@ class TemplateTool (BaseTool): ...@@ -242,8 +264,8 @@ class TemplateTool (BaseTool):
os.path.walk(name, callback, None) os.path.walk(name, callback, None)
file_list.sort() file_list.sort()
# import bt object # import bt object
self.newContent(portal_type='Business Template', id=id) bt = self.newContent(portal_type='Business Template', id=id)
bt = self._getOb(id) id = bt.getId()
bt_path = os.path.join(name, 'bt') bt_path = os.path.join(name, 'bt')
# import properties # import properties
...@@ -264,15 +286,19 @@ class TemplateTool (BaseTool): ...@@ -264,15 +286,19 @@ class TemplateTool (BaseTool):
# import all others objects # import all others objects
bt.importFile(dir=1, file=file_list, root_path=name) bt.importFile(dir=1, file=file_list, root_path=name)
else: else:
tempid, temppath = mkstemp() tempid, temppath = mkstemp()
file, headers = urlretrieve(url, temppath) try:
bt = self._importBT(temppath, id) os.close(tempid) # Close the opened fd as soon as possible.
file, headers = urlretrieve(url, temppath)
if id is None:
id = str(self.generateNewId())
bt = self._importBT(temppath, id)
finally:
os.remove(temppath)
bt.build(no_action=1) bt.build(no_action=1)
bt.reindexObject() bt.reindexObject()
if REQUEST is not None: return bt
REQUEST.RESPONSE.redirect("%s?portal_status_message=Business+Template+Downloaded+Successfully"
% self.absolute_url())
def importFile(self, import_file=None, id=None, REQUEST=None, **kw): def importFile(self, import_file=None, id=None, REQUEST=None, **kw):
""" """
...@@ -287,20 +313,27 @@ class TemplateTool (BaseTool): ...@@ -287,20 +313,27 @@ class TemplateTool (BaseTool):
% self.absolute_url()) % self.absolute_url())
return return
else : else :
raise 'Error', 'No file or an empty file was specified' raise RuntimeError, 'No file or an empty file was specified'
# copy to a temp location # copy to a temp location
import_file.seek(0) #Rewind to the beginning of file import_file.seek(0) #Rewind to the beginning of file
tempid, temppath = mkstemp() tempid, temppath = mkstemp()
tempfile = open(temppath, 'w') try:
tempfile.write(import_file.read()) os.close(tempid) # Close the opened fd as soon as possible
tempfile.close() tempfile = open(temppath, 'w')
bt = self._importBT(temppath, id) try:
tempfile.write(import_file.read())
finally:
tempfile.close()
bt = self._importBT(temppath, id)
finally:
os.remove(temppath)
bt.build(no_action=1) bt.build(no_action=1)
bt.reindexObject() bt.reindexObject()
if REQUEST is not None: if REQUEST is not None:
REQUEST.RESPONSE.redirect("%s?portal_status_message=Business+Template+Imported+Successfully" ret_url = self.absolute_url() + '/' + REQUEST.get('form_id', 'view')
% self.absolute_url()) REQUEST.RESPONSE.redirect("%s?portal_status_message=Business+Templates+Imported+Successfully"
% ret_url)
def runUnitTestList(self, test_list=[], **kwd) : def runUnitTestList(self, test_list=[], **kwd) :
""" """
...@@ -310,30 +343,6 @@ class TemplateTool (BaseTool): ...@@ -310,30 +343,6 @@ class TemplateTool (BaseTool):
from Products.ERP5Type.tests.runUnitTest import getUnitTestFile from Products.ERP5Type.tests.runUnitTest import getUnitTestFile
return os.popen('/usr/bin/python %s %s 2>&1' % (getUnitTestFile(), ' '.join(test_list))).read() return os.popen('/usr/bin/python %s %s 2>&1' % (getUnitTestFile(), ' '.join(test_list))).read()
security.declareProtected(Permissions.DeletePortalContent, 'deleteBackupObjects')
def deleteBackupObjects(self, dry_run=1) :
"""
removes 'btsave' objects from the ZODB
"""
import re
backup_re = re.compile('_btsave_[0-9]+$')
backup_list = []
portal = self.getPortalObject()
for module in portal.objectValues() :
if backup_re.search(module.getId()) :
backup_list.append((module.getId(), portal))
else :
for oid in module.objectIds() :
if backup_re.search(oid) :
backup_list.append((oid, module))
if dry_run :
return '\n'.join(['%s in %s' % e for e in backup_list])
else :
for oid, module in backup_list :
module.manage_delObjects(oid)
def diff(self, **kw): def diff(self, **kw):
""" """
Make a diff between two Business Template Make a diff between two Business Template
...@@ -437,5 +446,192 @@ class TemplateTool (BaseTool): ...@@ -437,5 +446,192 @@ class TemplateTool (BaseTool):
if compare_to_installed: if compare_to_installed:
self.manage_delObjects(ids=['installed_bt']) self.manage_delObjects(ids=['installed_bt'])
return diff_msg return diff_msg
security.declareProtected( 'Import/Export objects', 'updateRepositoryBusinessTemplateList' )
def updateRepositoryBusinessTemplateList(self, repository_list, REQUEST=None, RESPONSE=None, **kw):
"""Update the information on Business Templates in repositories.
"""
self.repository_dict = PersistentMapping()
property_list = ('title', 'version', 'description', 'license', 'dependency', 'copyright')
#LOG('updateRepositoryBusiessTemplateList', 0, 'repository_list = %r' % (repository_list,))
for repository in repository_list:
url = '/'.join([repository, 'bt5list'])
f = urlopen(url)
property_dict_list = []
try:
doc = parse(f)
try:
root = doc.documentElement
for template in root.getElementsByTagName("template"):
id = template.getAttribute('id')
if type(id) == type(u''):
id = id.encode('utf-8')
temp_property_dict = {}
for node in template.childNodes:
if node.nodeName in property_list:
value = ''
for text in node.childNodes:
if text.nodeType == text.TEXT_NODE:
value = text.data
if type(value) == type(u''):
value = value.encode('utf-8')
break
temp_property_dict.setdefault(node.nodeName, []).append(value)
property_dict = {}
property_dict['id'] = id
property_dict['title'] = temp_property_dict.get('title', [''])[0]
property_dict['version'] = temp_property_dict.get('version', [''])[0]
property_dict['description'] = temp_property_dict.get('description', [''])[0]
property_dict['license'] = temp_property_dict.get('license', [''])[0]
property_dict['dependency_list'] = temp_property_dict.get('dependency', ())
property_dict['copyright_list'] = temp_property_dict.get('copyright', ())
property_dict_list.append(property_dict)
finally:
doc.unlink()
finally:
f.close()
self.repository_dict[repository] = tuple(property_dict_list)
if REQUEST is not None:
ret_url = self.absolute_url() + '/' + REQUEST.get('form_id', 'view')
REQUEST.RESPONSE.redirect("%s?portal_status_message=Business+Templates+Updated+Successfully"
% ret_url)
security.declareProtected( Permissions.AccessContentsInformation, 'getRepositoryList' )
def getRepositoryList(self):
"""Get the list of repositories.
"""
return self.repository_dict.keys()
security.declarePublic( 'decodeRepositoryBusinessTemplateUid' )
def decodeRepositoryBusinessTemplateUid(self, uid):
"""Decode the uid of a business template in a repository. Return a repository and an id.
"""
return cPickle.loads(base64.b64decode(uid))
security.declareProtected( Permissions.AccessContentsInformation, 'getRepositoryBusinessTemplateList' )
def getRepositoryBusinessTemplateList(self, update_only=0, **kw):
"""Get the list of Business Templates in repositories.
"""
version_state_title_dict = { 'new' : 'New', 'present' : 'Present', 'old' : 'Old' }
from Products.ERP5Type.Document import newTempBusinessTemplate
template_list = []
template_item_list = []
if update_only:
# First of all, filter Business Templates in repositories.
template_item_dict = {}
for repository, property_dict_list in self.repository_dict.items():
for property_dict in property_dict_list:
title = property_dict['title']
if title not in template_item_dict:
# If this is the first time to see this business template, insert it.
template_item_dict[title] = (repository, property_dict)
else:
# If this business template has been seen before, insert it only if
# this business template is newer.
previous_repository, previous_property_dict = template_item_dict[title]
if self.compareVersions(previous_property_dict['version'], property_dict['version']) < 0:
template_item_dict[title] = (repository, property_dict)
# Next, select only updated business templates.
for repository, property_dict in template_item_dict.values():
installed_bt = self.getInstalledBusinessTemplate(property_dict['title'])
if installed_bt is not None:
if self.compareVersions(installed_bt.getVersion(), property_dict['version']) < 0:
template_item_list.append((repository, property_dict))
# FIXME: resolve dependencies
else:
for repository, property_dict_list in self.repository_dict.items():
for property_dict in property_dict_list:
template_item_list.append((repository, property_dict))
# Create temporary Business Template objects for displaying.
for repository, property_dict in template_item_list:
property_dict = property_dict.copy()
id = property_dict['id']
del property_dict['id']
version = property_dict['version']
version_state = 'new'
for bt in self.searchFolder(title = property_dict['title']):
result = self.compareVersions(version, bt.getObject().getVersion())
if result == 0:
version_state = 'present'
break
elif result < 0:
version_state = 'old'
version_state_title = version_state_title_dict[version_state]
uid = base64.b64encode(cPickle.dumps((repository, id)))
obj = newTempBusinessTemplate(self, 'temp_' + uid,
version_state = version_state,
version_state_title = version_state_title,
repository = repository, **property_dict)
obj.setUid(uid)
template_list.append(obj)
return template_list
security.declareProtected( Permissions.AccessContentsInformation, 'getUpdatedRepositoryBusinessTemplateList' )
def getUpdatedRepositoryBusinessTemplateList(self, **kw):
"""Get the list of updated Business Templates in repositories.
"""
#LOG('getUpdatedRepositoryBusinessTemplateList', 0, 'kw = %r' % (kw,))
return self.getRepositoryBusinessTemplateList(update_only=1, **kw)
def compareVersions(self, version1, version2):
"""Return negative if version1 < version2, 0 if version1 == version2, positive if version1 > version2.
Here is the algorithm:
- Non-alphanumeric characters are not significant, besides the function of delimiters.
- If a level of a version number is missing, it is assumed to be zero.
- An alphabetical character is less than any numerical value.
- Numerical values are compared as integers.
This archives the following predicates:
- 1.0 < 1.0.1
- 1.0rc1 < 1.0
- 1.0a < 1.0.1
- 1.1 < 2.0
- 1.0.0 = 1.0
"""
r = re.compile('(\d+|[a-zA-Z])')
v1 = r.findall(version1)
v2 = r.findall(version2)
def convert(v, i):
"""Convert the ith element of v to an interger for a comparison.
"""
#LOG('convert', 0, 'v = %r, i = %r' % (v, i))
try:
e = v[i]
try:
e = int(e)
except ValueError:
# ASCII code is one byte, so this produces negative.
e = struct.unpack('b', e)[0] - 0x200
except IndexError:
e = 0
return e
for i in xrange(max(len(v1), len(v2))):
e1 = convert(v1, i)
e2 = convert(v2, i)
result = cmp(e1, e2)
if result != 0:
return result
return 0
InitializeClass(TemplateTool) InitializeClass(TemplateTool)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment