Commit 337a2213 authored by Sebastien Robin's avatar Sebastien Robin

many updates made when the cvs was down


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@118 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent b374861f
...@@ -27,21 +27,22 @@ ...@@ -27,21 +27,22 @@
############################################################################## ##############################################################################
from Products.ERP5SyncML.SyncCode import SyncCode from Products.ERP5SyncML.SyncCode import SyncCode
from Products.ERP5SyncML.XMLSyncUtils import XMLSyncUtilsMixin
from Products.ERP5SyncML.Subscription import Conflict from Products.ERP5SyncML.Subscription import Conflict
from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import getToolByName
from Products.ERP5SyncML.XupdateUtils import XupdateUtils from Products.ERP5SyncML.XupdateUtils import XupdateUtils
from Products.ERP5Type.Utils import convertToUpperCase from Products.ERP5Type.Utils import convertToUpperCase
from Products.ERP5Type.Accessor.TypeDefinition import list_types from Products.ERP5Type.Accessor.TypeDefinition import list_types
from xml.dom.ext.reader.Sax2 import FromXml from xml.dom.ext.reader.Sax2 import FromXml
from DateTime.DateTime import DateTime
from email.MIMEBase import MIMEBase from email.MIMEBase import MIMEBase
from email import Encoders from email import Encoders
import sre import re, copy
from zLOG import LOG from zLOG import LOG
class ERP5Conduit(SyncCode): class ERP5Conduit(XMLSyncUtilsMixin):
""" """
A conduit is a piece of code in charge of A conduit is a piece of code in charge of
...@@ -63,10 +64,22 @@ class ERP5Conduit(SyncCode): ...@@ -63,10 +64,22 @@ class ERP5Conduit(SyncCode):
a default class. This will be defined at the level of the synchronisation a default class. This will be defined at the level of the synchronisation
tool tool
""" XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx
Look carefully when we are adding elements,
for example, when we do 'insert-after', with 2 xupdate:element,
so adding 2 differents objects, actually it adds only XXXX one XXX object
In this case the getSubObjectDepth(), doesn't have
too much sence
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
There is also one problem, when we synchronize a conflict, we are not waiting
the response of the client, so that we are not sure if it take into account,
we may have CONFLICT_NOT_SYNCHRONIZED AND CONFLICT_SYNCHRONIZED
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
NOT_EDITABLE_PROPERTY = ('id','object','workflow_history','security_info','uid' """
'xupdate:element','xupdate:attribute')
def getEncoding(self): def getEncoding(self):
...@@ -75,18 +88,9 @@ class ERP5Conduit(SyncCode): ...@@ -75,18 +88,9 @@ class ERP5Conduit(SyncCode):
""" """
return "iso-8859-1" return "iso-8859-1"
def __init__(self): def __init__(self):
self.args = {} self.args = {}
def applyModification(object=None):
"""
This will apply all updates
"""
args = self.args
def addNode(self, xml=None, object=None, previous_xml=None, force=0, **kw): def addNode(self, xml=None, object=None, previous_xml=None, force=0, **kw):
""" """
A node is added A node is added
...@@ -96,69 +100,76 @@ class ERP5Conduit(SyncCode): ...@@ -96,69 +100,76 @@ class ERP5Conduit(SyncCode):
[object.getPath(),keyword,local_and_actual_value,remote_value] [object.getPath(),keyword,local_and_actual_value,remote_value]
""" """
conflict_list = [] conflict_list = []
xml = self.convertToXML(xml) xml = self.convertToXml(xml)
LOG('addNode',0,'xml_reconstitued: %s' % str(xml)) LOG('addNode',0,'xml_reconstitued: %s' % str(xml))
# In the case where this new node is a object to add # In the case where this new node is a object to add
LOG('addNode',0,'object.id: %s' % object.getId()) LOG('addNode',0,'object.id: %s' % object.getId())
LOG('addNode',0,'xml.nodeName: %s' % xml.nodeName) LOG('addNode',0,'xml.nodeName: %s' % xml.nodeName)
LOG('addNode',0,'isSubObjectAdd: %i' % self.getSubObjectAddDepth(xml)) LOG('addNode',0,'isSubObjectAdd: %i' % self.getSubObjectDepth(xml))
if xml.nodeName == 'object' \ if xml.nodeName == 'object' \
or xml.nodeName == 'xupdate:insert-after' and self.getSubObjectAddDepth(xml)==1: or xml.nodeName in self.XUPDATE_INSERT_OR_ADD and self.getSubObjectDepth(xml)==1:
object_id = self.getObjectId(xml) object_id = self.getObjectId(xml)
docid = self.getObjectDocid(xml)
LOG('addNode',0,'object_id: %s' % object_id) LOG('addNode',0,'object_id: %s' % object_id)
if object_id is not None: if object_id is not None:
subobject = None
try: try:
subobject = object[object_id] subobject = object._getOb(object_id)
except KeyError: except (AttributeError, KeyError):
pass subobject = None
#subobject = None
#try:
# subobject = object[object_id]
#except KeyError:
# pass
if subobject is None: # If so it does'nt exist yes if subobject is None: # If so it does'nt exist yes
portal_type = '' portal_type = ''
if xml.nodeName == 'object': if xml.nodeName == 'object':
portal_type = self.getObjectType(xml) portal_type = self.getObjectType(xml)
elif xml.nodeName == 'xupdate:insert-after': elif xml.nodeName in self.XUPDATE_INSERT_OR_ADD:
portal_type = self.getXupdateObjectType(xml) portal_type = self.getXupdateObjectType(xml)
portal_types = getToolByName(object,'portal_types') portal_types = getToolByName(object,'portal_types')
LOG('ERP5Conduit.addNode',0,'portal_type: |%s|' % str(portal_type)) LOG('ERP5Conduit.addNode',0,'portal_type: |%s|' % str(portal_type))
portal_types.constructContent(type_name = portal_type, if docid==None: # ERP5 content
container = object, portal_types.constructContent(type_name = portal_type,
id = object_id) container = object,
subobject = object[object_id] id = object_id)
else: # CPS content
# This is specific to CPS, we will call the proxy tool
px_tool= getToolByName(object,'portal_proxies')
proxy_type = 'document'
if portal_type == 'Workspace':
proxy_type = 'folder'
proxy = px_tool.createEmptyProxy(proxy_type,
object,portal_type,object_id,docid)
#px_tool.createRevision(proxy,px_tool.getDefaultLanguage()) # Doesn't works well
# px_tool._addProxy(proxy,None) # Doesn't works well
#object.newContent(portal_type=portal_type, id=object_id) # Doesn't works with CPS
#subobject = object[object_id] # Doesn't works with CPS
subobject = object._getOb(object_id)
# Again for CPS proxy XXX May be not needed
#if docid is not None:
# subobject.proxyChanged()
self.newObject(object=subobject,xml=xml) self.newObject(object=subobject,xml=xml)
elif xml.nodeName == 'xupdate:insert-after' \ elif xml.nodeName in self.XUPDATE_INSERT_OR_ADD \
and self.getSubObjectAddDepth(xml)==2: and self.getSubObjectDepth(xml)==2:
# We should find the object corresponding to # We should find the object corresponding to
# this update, so we have to look in the previous_xml # this update, so we have to look in the previous_xml
object_number = self.getSubObjectIndex(xml) sub_object_id = self.getSubObjectId(xml)
LOG('addNode',0,'getSubObjectModification number: %i' % object_number) LOG('addNode',0,'getSubObjectModification number: %s' % sub_object_id)
#LOG('updateNode',0,'isSubObjectModification previous: %s' % str(previous_xml)) #LOG('updateNode',0,'isSubObjectModification previous: %s' % str(previous_xml))
if previous_xml is not None and object_number is not None: if previous_xml is not None and sub_object_id is not None:
LOG('addNode',0,'previous xml is not none and also object_number') LOG('addNode',0,'previous xml is not none and also sub_object_id')
if type(previous_xml) in (type('a'),type(u'a')): previous_xml = self.getSubObjectXml(sub_object_id,previous_xml)
previous_xml = FromXml(previous_xml)
previous_xml = previous_xml.childNodes[1] # Because we just created a new xml
# document, with childNodes[0] a DocumentType and childNodes[1] the Element Node
# Get the id of the previous object # Get the id of the previous object
i = 0 i = 0
sub_previous_xml = None sub_previous_xml = None
# Find the previous xml corresponding to this subobject # Find the previous xml corresponding to this subobject
for subnode in self.getElementNodeList(previous_xml): sub_previous_xml == self.getSubObjectXml(sub_object_id,previous_xml)
if subnode.nodeName=='object':
for subnode1 in self.getElementNodeList(subnode):
if subnode1.nodeName=='object':
i += 1
if i==object_number:
sub_previous_xml = subnode1
LOG('addNode',0,'isSubObjectModification sub_p_xml: %s' % str(sub_previous_xml)) LOG('addNode',0,'isSubObjectModification sub_p_xml: %s' % str(sub_previous_xml))
if sub_previous_xml is not None: if sub_previous_xml is not None:
sub_object = None sub_object = None
LOG('addNode',0,'getObjectId: %s' % self.getObjectId(sub_previous_xml) )
# XXXXXXXXXXXX
# The problem is actually that the sub_previous_xml doesn't correspong to the
# xml, we have the xupdate of a subobject but we have the previous_xml
# of the object
try: try:
sub_object = object[self.getObjectId(sub_previous_xml)] sub_object = object[sub_object_id]
except KeyError: except KeyError:
pass pass
if sub_object is not None: if sub_object is not None:
...@@ -174,20 +185,37 @@ class ERP5Conduit(SyncCode): ...@@ -174,20 +185,37 @@ class ERP5Conduit(SyncCode):
conflict_list += self.updateNode(xml=xml,object=object, force=force, **kw) conflict_list += self.updateNode(xml=xml,object=object, force=force, **kw)
return conflict_list return conflict_list
def deleteNode(self, xml=None, object=None, **kw): def deleteNode(self, xml=None, object=None, object_id=None, force=None, **kw):
""" """
A node is deleted A node is deleted
""" """
# In the case where this new node is a object to delete # In the case where this new node is a object to delete
LOG('ERP5Conduit',0,'deleteNode') LOG('ERP5Conduit',0,'deleteNode')
xml = self.convertToXML(xml) LOG('ERP5Conduit',0,'deleteNode, object.id: %s' % object.getId())
if xml.nodeName == 'object': conflict_list = []
object_id = self.getObjectId(xml) xml = self.convertToXml(xml)
if object_id is not None: if object_id is None:
LOG('ERP5Conduit',0,'deleteNode, SubObjectDepth: %i' % self.getSubObjectDepth(xml))
if xml.nodeName == self.xml_object_tag:
object_id = self.getObjectId(xml)
elif self.getSubObjectDepth(xml)==1:
object_id = self.getSubObjectId(xml)
elif self.getSubObjectDepth(xml)==2:
# we have to call delete node on a subsubobject
sub_object_id = self.getSubObjectId(xml)
try: try:
object._delObject(object_id) sub_object = object._getOb(sub_object_id)
sub_xml = self.getSubObjectXupdate(xml)
conflict_list += self.deleteNode(xml=sub_xml,object=sub_object,
force=force)
except KeyError: except KeyError:
pass pass
else: # We do have an object_id
try:
object._delObject(object_id)
except (AttributeError, KeyError):
pass
return conflict_list
def updateNode(self, xml=None, object=None, previous_xml=None, force=0, **kw): def updateNode(self, xml=None, object=None, previous_xml=None, force=0, **kw):
""" """
...@@ -198,7 +226,7 @@ class ERP5Conduit(SyncCode): ...@@ -198,7 +226,7 @@ class ERP5Conduit(SyncCode):
when we have sub objects when we have sub objects
""" """
conflict_list = [] conflict_list = []
xml = self.convertToXML(xml) xml = self.convertToXml(xml)
LOG('updateNode',0,'xml.nodeName: %s' % xml.nodeName) LOG('updateNode',0,'xml.nodeName: %s' % xml.nodeName)
# we have an xupdate xml # we have an xupdate xml
if xml.nodeName == 'xupdate:modifications': if xml.nodeName == 'xupdate:modifications':
...@@ -208,52 +236,48 @@ class ERP5Conduit(SyncCode): ...@@ -208,52 +236,48 @@ class ERP5Conduit(SyncCode):
# we may have only the part of an xupdate # we may have only the part of an xupdate
else: else:
args = {} args = {}
LOG('isSubObjectModification',0,'result: %s' % str(self.isSubObjectModification(xml)))
if self.isProperty(xml) and not(self.isSubObjectModification(xml)): if self.isProperty(xml) and not(self.isSubObjectModification(xml)):
for subnode in xml.attributes: for subnode in xml.attributes:
if subnode.nodeType == subnode.ATTRIBUTE_NODE and subnode.nodeName=='select': if subnode.nodeType == subnode.ATTRIBUTE_NODE and subnode.nodeName=='select':
select_list = subnode.nodeValue.split('/') # Something like: ('','object[1]','sid[1]') LOG('updateNode',0,'selection: %s' % str(subnode.nodeValue))
select_list = subnode.nodeValue.split('/') # Something like:
#('','object[1]','sid[1]')
new_select_list = () new_select_list = ()
for select_item in select_list: for select_item in select_list:
new_select_list += (select_item[:select_item.find('[')],) if select_item.find('[')>=0:
select_item = select_item[:select_item.find('[')]
new_select_list += (select_item,)
select_list = new_select_list # Something like : ('','object','sid') select_list = new_select_list # Something like : ('','object','sid')
keyword = select_list[len(select_list)-1] # this will be 'sid' keyword = select_list[len(select_list)-1] # this will be 'sid'
if xml.nodeName != 'xupdate:insert-after': LOG('updateNode',0,'keyword: %s' % str(keyword))
if not (xml.nodeName in self.XUPDATE_INSERT_OR_ADD):
for subnode in self.getElementNodeList(xml): for subnode in self.getElementNodeList(xml):
if subnode.nodeName=='xupdate:element': if subnode.nodeName=='xupdate:element':
for subnode1 in subnode.attributes: for subnode1 in subnode.attributes:
if subnode1.nodeName=='name': if subnode1.nodeName=='name':
keyword = subnode1.nodeValue keyword = subnode1.nodeValue
i = 1
while (keyword.find('()') > 0) and (i <= len(select_list)):
keyword = select_list[len(select_list)-i] # we want description in :
# /object[1]/description[1]/text()[1]
i += 1
if not (keyword in self.NOT_EDITABLE_PROPERTY): if not (keyword in self.NOT_EDITABLE_PROPERTY):
# We will look for the data to enter # We will look for the data to enter
if len(self.getElementNodeList(xml))==0: if len(self.getElementNodeList(xml))==0:
data = xml.childNodes[0].data try:
data = self.convertXmlValue(data) data = xml.childNodes[0].data
#data = data[data.find('\n')+1:data.rfind('\n')] except IndexError: # There is no data
#data = data.replace('@@@','\n') data = None
else: # XXX may be not needed any more
data=[] #else:
for subnode in self.getElementNodeList(xml): # data=()
element_data = subnode.childNodes[0].data # for subnode in self.getElementNodeList(xml):
#element_data = element_data[element_data.find('\n')+1:element_data.rfind('\n')] # element_data = subnode.childNodes[0].data
element_data = self.convertXmlValue(element_data) # element_data = self.convertXmlValue(element_data)
element_data = element_data.replace('@@@','\n') # data += (element_data,)
data += [element_data] # if len(data) == 1: # This is probably because this is not a list
if len(data) == 1: # This is probably because this is not a list but a string XXX may be not good # but a string XXX may be not good
data = data[0] # data = data[0]
#LOG('updateNode',0,'data: %s' % str(data)) data_type = object.getPropertyType(keyword)
if keyword.find('_list') > 0: LOG('updateNode',0,'data_type: %s' % str(data_type))
LOG('updateNode',0,'keyword is type list') data = self.convertXmlValue(data,data_type=data_type)
if type(data) == type(u'a'): # Probably deprecated
data = data.split('@@@') # XXX very bad hack, must find something better
#if type(data) == type(u'a'):
# LOG('updateNode',0,'splitting it')
# data = data.split('\n')
args[keyword] = data args[keyword] = data
args = self.getFormatedArgs(args=args) args = self.getFormatedArgs(args=args)
# This is the place where we should look for conflicts # This is the place where we should look for conflicts
...@@ -263,21 +287,26 @@ class ERP5Conduit(SyncCode): ...@@ -263,21 +287,26 @@ class ERP5Conduit(SyncCode):
# - current_data : the data actually on this box # - current_data : the data actually on this box
isConflict = 0 isConflict = 0
if previous_xml is not None: # if no previous_xml, no conflict if previous_xml is not None: # if no previous_xml, no conflict
old_data = self.getObjectProperty(keyword,previous_xml) old_data = self.getObjectProperty(keyword,previous_xml,data_type=data_type)
current_data = object.getProperty(keyword) current_data = object.getProperty(keyword)
LOG('updateNode',0,'Conflict data: %s' % str(data)) LOG('updateNode',0,'Conflict data: %s' % str(data))
LOG('updateNode',0,'Conflict old_data: %s' % str(old_data)) LOG('updateNode',0,'Conflict old_data: %s' % str(old_data))
LOG('updateNode',0,'Conflict current_data: %s' % str(current_data)) LOG('updateNode',0,'Conflict current_data: %s' % str(current_data))
if (old_data != current_data) and (data != current_data): if (old_data != current_data) and (data != current_data):
# This is a conflict LOG('updateNode',0,'Conflict on : %s' % keyword)
isConflict = 1 # Hack in order to get the synchronization working for demo
conflict_list += [Conflict(object_path=object.getPhysicalPath(),keyword=keyword,\ # XXX this have to be removed after
local_value=current_data,remote_value=data)] if not (data_type in self.binary_type_list):
#conflict_list += [[object.getPath(),keyword,current_data,data]] # This is a conflict
isConflict = 1
conflict_list += [Conflict(object_path=object.getPhysicalPath(),
keyword=keyword,
local_value=current_data,
remote_value=data)]
# We will now apply the argument with the method edit # We will now apply the argument with the method edit
if args != {} and (isConflict==0 or force): if args != {} and (isConflict==0 or force):
LOG('updateNode',0,'object.edit, args: %s' % str(args)) LOG('updateNode',0,'object._edit, args: %s' % str(args))
object.edit(**args) object._edit(**args)
if keyword == 'object': if keyword == 'object':
# This is the case where we have to call addNode # This is the case where we have to call addNode
LOG('updateNode',0,'we will add sub-object') LOG('updateNode',0,'we will add sub-object')
...@@ -285,38 +314,16 @@ class ERP5Conduit(SyncCode): ...@@ -285,38 +314,16 @@ class ERP5Conduit(SyncCode):
elif self.isSubObjectModification(xml): elif self.isSubObjectModification(xml):
# We should find the object corresponding to # We should find the object corresponding to
# this update, so we have to look in the previous_xml # this update, so we have to look in the previous_xml
object_number = self.getSubObjectIndex(xml) sub_object_id = self.getSubObjectId(xml)
LOG('updateNode',0,'getSubObjectModification number: %i' % object_number) LOG('updateNode',0,'getSubObjectModification number: %s' % sub_object_id)
#LOG('updateNode',0,'isSubObjectModification previous: %s' % str(previous_xml)) if previous_xml is not None and sub_object_id is not None:
if previous_xml is not None and object_number is not None: LOG('updateNode',0,'previous xml is not none and also sub_object_id')
LOG('updateNode',0,'previous xml is not none and also object_number') sub_previous_xml = self.getSubObjectXml(sub_object_id,previous_xml)
if type(previous_xml) in (type('a'),type(u'a')):
previous_xml = FromXml(previous_xml)
previous_xml = previous_xml.childNodes[1] # Because we just created a new xml
# document, with childNodes[0] a DocumentType and childNodes[1] the Element Node
# Get the id of the previous object
i = 0
sub_previous_xml = None
# Find the previous xml corresponding to this subobject
for subnode in self.getElementNodeList(previous_xml):
if subnode.nodeName=='object':
for subnode1 in self.getElementNodeList(subnode):
if subnode1.nodeName=='object':
i += 1
if i==object_number:
sub_previous_xml = subnode1
LOG('updateNode',0,'isSubObjectModification sub_p_xml: %s' % str(sub_previous_xml)) LOG('updateNode',0,'isSubObjectModification sub_p_xml: %s' % str(sub_previous_xml))
if sub_previous_xml is not None: if sub_previous_xml is not None:
sub_object = None sub_object = None
LOG('updateNode',0,'getObjectId: %s' % self.getObjectId(sub_previous_xml) )
# XXXXXXXXXXXX
# The problem is actually that the sub_previous_xml doesn't correspong to the
# xml, we have the xupdate of a subobject but we have the previous_xml
# of the object
# I guess it was resolved with getSubObjectXupdate... so this comment may be removed XXX
try: try:
sub_object = object[self.getObjectId(sub_previous_xml)] sub_object = object[sub_object_id]
except KeyError: except KeyError:
pass pass
if sub_object is not None: if sub_object is not None:
...@@ -346,10 +353,12 @@ class ERP5Conduit(SyncCode): ...@@ -346,10 +353,12 @@ class ERP5Conduit(SyncCode):
for item in data: for item in data:
if type(item) is type(u"a"): if type(item) is type(u"a"):
item = item.encode(self.getEncoding()) item = item.encode(self.getEncoding())
item = item.replace('@@@','\n')
new_data += [item] new_data += [item]
data = new_data data = new_data
if type(data) is type(u"a"): if type(data) is type(u"a"):
data = data.encode(self.getEncoding()) data = data.encode(self.getEncoding())
data = data.replace('@@@','\n')
if keyword == 'binary_data': if keyword == 'binary_data':
LOG('ERP5Conduit.getFormatedArgs',0,'binary_data keyword: %s' % str(keyword)) LOG('ERP5Conduit.getFormatedArgs',0,'binary_data keyword: %s' % str(keyword))
msg = MIMEBase('application','octet-stream') msg = MIMEBase('application','octet-stream')
...@@ -377,33 +386,19 @@ class ERP5Conduit(SyncCode): ...@@ -377,33 +386,19 @@ class ERP5Conduit(SyncCode):
This will change the xml in order to change the update This will change the xml in order to change the update
from the object to the subobject from the object to the subobject
""" """
for subnode in xml.attributes: xml = copy.deepcopy(xml)
if subnode.nodeType == subnode.ATTRIBUTE_NODE and subnode.nodeName=='select': for subnode in self.getAttributeNodeList(xml):
value = subnode.nodeValue if subnode.nodeName=='select':
if sre.search("/object\[[0-9]*\]/object\[[0-9]*\]/.",value) is not None: subnode.nodeValue = self.getSubObjectSelect(subnode.nodeValue)
try:
new_value = ''
new_value_list = (value.split('/')[1:2] + value.split('/')[3:])
for s in new_value_list:
new_value += '/' + s
except KeyError:
pass
subnode.nodeValue = new_value
element_node_list = self.getElementNodeList(xml) element_node_list = self.getElementNodeList(xml)
# This is when we have a sub_sub_object_add and we will want a sub_object_add # This is when we have a sub_sub_object_add and we will want a sub_object_add
LOG('getSubObjectXupdate',0,'xml.nodeName: %s' % xml.nodeName) LOG('getSubObjectXupdate',0,'xml.nodeName: %s' % xml.nodeName)
#for j in range(0,len(xml.childNodes)):
# subnode = xml.childNodes[j]
for subnode in self.getElementNodeList(xml): for subnode in self.getElementNodeList(xml):
#if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName=='xupdate:element':
if subnode.nodeName=='xupdate:element': if subnode.nodeName=='xupdate:element':
LOG('getSubObjectXupdate',0,'subnode.nodeName: %s' % subnode.nodeName) LOG('getSubObjectXupdate',0,'subnode.nodeName: %s' % subnode.nodeName)
for subnode1 in self.getElementNodeList(subnode): for subnode1 in self.getElementNodeList(subnode):
LOG('getSubObjectXupdate',0,'subnode1.nodeName: %s' % subnode1.nodeName) LOG('getSubObjectXupdate',0,'subnode1.nodeName: %s' % subnode1.nodeName)
if subnode.nodeType == subnode.ELEMENT_NODE and \ if subnode1.nodeName=='object':
subnode1.nodeName=='object':
#LOG('getSubObjectXupdate',0,'xml.childNodes[j].nodeName: %s' % xml.childNodes[j].nodeName)
#xml.childNodes[j] = subnode1
return subnode1 return subnode1
return xml return xml
...@@ -411,60 +406,105 @@ class ERP5Conduit(SyncCode): ...@@ -411,60 +406,105 @@ class ERP5Conduit(SyncCode):
""" """
Check if it is a modification from an subobject Check if it is a modification from an subobject
""" """
good_list = ('/object[1]/object',) good_list = (self.sub_object_exp,)
for subnode in xml.attributes: for subnode in xml.attributes:
if subnode.nodeType == subnode.ATTRIBUTE_NODE and subnode.nodeName=='select': if subnode.nodeType == subnode.ATTRIBUTE_NODE and subnode.nodeName=='select':
value = subnode.nodeValue value = subnode.nodeValue
LOG('isSubObjectModification',0,'value: %s' % value) LOG('isSubObjectModification',0,'value: %s' % value)
for good_string in good_list: for good_string in good_list:
if value.find(good_string)==0: if re.search(good_string,value) is not None:
return 1 return 1
return 0 return 0
def getSubObjectAddDepth(self, xml): def getSubObjectDepth(self, xml):
""" """
Check if this modification is in reality a new subobject to add Give the Depth of a subobject modification
""" 0 means, no depth
if xml.nodeName == 'xupdate:insert-after': 1 means it is a subobject
2 means it is more depth than subobject
"""
LOG('getSubObjectDepth',0,'xml.nodeName: %s' % xml.nodeName)
if xml.nodeName in self.XUPDATE_TAG:
LOG('getSubObjectDepth',0,'xml2.nodeName: %s' % xml.nodeName)
if xml.nodeName == self.xml_object_tag:
return 1
for subnode in self.getAttributeNodeList(xml):
LOG('getSubObjectDepth',0,'subnode.nodeName: %s' % subnode.nodeName)
if subnode.nodeName == 'select':
value = subnode.nodeValue
LOG('getSubObjectDepth',0,'subnode.nodeValue: %s' % subnode.nodeValue)
if re.search(self.sub_object_exp,value) is not None:
new_select = self.getSubObjectSelect(value)
if self.getSubObjectSelect(new_select) != new_select:
return 2
return 1
for subnode in self.getElementNodeList(xml): for subnode in self.getElementNodeList(xml):
# One part of this is specific to xmldiff, may be need to rewrite
if subnode.nodeName == 'xupdate:element': if subnode.nodeName == 'xupdate:element':
for attribute in subnode.attributes: for attribute in subnode.attributes:
if attribute.nodeName == 'name': if attribute.nodeName == 'name':
is_sub_add = 0 is_sub_add = 0
if attribute.nodeValue == 'object': if attribute.nodeValue == self.xml_object_tag:
is_sub_add = 1 is_sub_add = 1
if not is_sub_add: if not is_sub_add:
return 0 return 0
for subnode1 in self.getElementNodeList(subnode): for subnode1 in self.getElementNodeList(subnode):
if subnode1.nodeName == 'object': # In this particular case, this is sub_sub_add if subnode1.nodeName == self.xml_object_tag: # In this particular case, this is sub_sub_add
return 2 return 2
return 1 return 1
if subnode.nodeName == 'object':
return 1
return 0 return 0
def getSubObjectIndex(self, xml): def getSubObjectSelect(self, select):
""" """
Return the number of the subobject in an xupdate modification Return a string wich is the selection for the subobject
ex: for "/object[@id='161']/object[@id='default_address']/street_address"
if returns "/object[@id='default_address']/street_address"
""" """
selection = None if re.search(self.sub_object_exp,select) is not None:
good_string = '/object[1]/object[' s = self.xml_object_tag
for subnode in xml.attributes: new_value = '/' + select[select.find(s,select.find(s)+1):]
if subnode.nodeType == subnode.ATTRIBUTE_NODE and subnode.nodeName=='select': select = new_value
return select
def getSubObjectId(self, xml):
"""
Return the id of the subobject in an xupdate modification
"""
object_id = None
for subnode in self.getAttributeNodeList(xml):
if subnode.nodeName=='select':
value = subnode.nodeValue value = subnode.nodeValue
if value.find(good_string)==0: if re.search(self.sub_object_exp,value) is not None:
string_number = value[len(good_string):] s = self.xml_object_tag
string_number = string_number.split(']')[0] object_id = value[value.find(s,value.find(s)+1):]
selection = int(string_number) object_id = object_id[object_id.find("'")+1:]
return selection object_id = object_id[:object_id.find("'")]
return object_id
return object_id
def getSubObjectXml(self, object_id, xml):
"""
Return the xml of the subobject which as the id object_id
"""
xml = self.convertToXml(xml)
for subnode in self.getElementNodeList(xml):
if subnode.nodeName==self.xml_object_tag:
LOG('getSub0bjectXml: object_id:',0,object_id)
if object_id == self.getObjectId(subnode):
return subnode
return None
def getObjectId(self, xml): def getObjectId(self, xml):
""" """
Retrieve the id Retrieve the id
""" """
for subnode in self.getElementNodeList(xml): #for subnode in self.getElementNodeList(xml):
if subnode.nodeName == 'id': # if subnode.nodeName == 'id':
data = subnode.childNodes[0].data # data = subnode.childNodes[0].data
# return self.convertXmlValue(data)
for attribute in self.getAttributeNodeList(xml):
if attribute.nodeName == 'id':
data = attribute.childNodes[0].data
return self.convertXmlValue(data) return self.convertXmlValue(data)
# In the case where the new object is inside an xupdate # In the case where the new object is inside an xupdate
if xml.nodeName.find('xupdate')>=0: if xml.nodeName.find('xupdate')>=0:
...@@ -476,32 +516,42 @@ class ERP5Conduit(SyncCode): ...@@ -476,32 +516,42 @@ class ERP5Conduit(SyncCode):
return self.convertXmlValue(data) return self.convertXmlValue(data)
return None return None
def getObjectDocid(self, xml):
"""
Retrieve the docid
"""
for subnode in self.getElementNodeList(xml):
if subnode.nodeName == 'docid':
data = subnode.childNodes[0].data
return self.convertXmlValue(data)
return None
def getObjectProperty(self, property, xml): def getObjectProperty(self, property, xml, data_type=None):
""" """
Retrieve the given property Retrieve the given property
""" """
if type(xml) in (type('a'),type(u'a')): xml = self.convertToXml(xml)
xml = FromXml(xml)
xml = xml.childNodes[1] # Because we just created a new xml
# document, with childNodes[0] a DocumentType and childNodes[1] the Element Node # document, with childNodes[0] a DocumentType and childNodes[1] the Element Node
for subnode in self.getElementNodeList(xml): for subnode in self.getElementNodeList(xml):
LOG('getObjectProperty',0,'subnode.nodeName: %s' % subnode.nodeName)
if subnode.nodeName == property: if subnode.nodeName == property:
data = subnode.childNodes[0].data try:
data = self.convertXmlValue(data) data = subnode.childNodes[0].data
if property[-5:]=='_list': data = self.convertXmlValue(data, data_type=data_type)
data = data.split('@@@') except IndexError: # There is no data
data = None
return data return data
return None return None
def convertToXML(self,xml): def convertToXml(self,xml):
""" """
if xml is a string, convert it to a node if xml is a string, convert it to a node
""" """
if type(xml) in (type('a'),type(u'a')): if type(xml) in (type('a'),type(u'a')):
xml = FromXml(xml) xml = FromXml(xml)
xml = xml.childNodes[1] # Because we just created a new xml xml = xml.childNodes[1] # Because we just created a new xml
# If we have the xml from the node erp5, we just take the subnode
if xml.nodeName=='erp5':
xml = self.getElementNodeList(xml)[0]
return xml return xml
def getObjectType(self, xml): def getObjectType(self, xml):
...@@ -556,27 +606,20 @@ class ERP5Conduit(SyncCode): ...@@ -556,27 +606,20 @@ class ERP5Conduit(SyncCode):
keyword=str(subnode.nodeName) keyword=str(subnode.nodeName)
if len(subnode.childNodes) > 0: # We check that this tag is not empty if len(subnode.childNodes) > 0: # We check that this tag is not empty
data = subnode.childNodes[0].data data = subnode.childNodes[0].data
data = self.convertXmlValue(data)
args[keyword]=data args[keyword]=data
LOG('newObject',0,'keyword: %s' % str(keyword)) LOG('newObject',0,'keyword: %s' % str(keyword))
LOG('newObject',0,'keywordtype: %s' % str(keyword_type)) LOG('newObject',0,'keywordtype: %s' % str(keyword_type))
#if args.has_key(keyword):
# LOG('newObject',0,'data: %s' % str(args[keyword]))
if args.has_key(keyword): if args.has_key(keyword):
LOG('newObject',0,'data: %s' % str(args[keyword])) args[keyword] = self.convertXmlValue(args[keyword],keyword_type)
if keyword_type in list_types:
if args.has_key(keyword):
if type(args[keyword]) in [type(u'a'),type('a')]:
args[keyword] = args[keyword].split('@@@')
elif keyword_type in ('text',):
if args.has_key(keyword):
args[keyword] = args[keyword].replace('@@@','\n')
# We should first edit the object # We should first edit the object
args = self.getFormatedArgs(args=args) args = self.getFormatedArgs(args=args)
LOG('newObject',0,"object.getpath: %s" % str(object.getPath())) LOG('newObject',0,"object.getpath: %s" % str(object.getPath()))
LOG('newObject',0,"args: %s" % str(args)) LOG('newObject',0,"args: %s" % str(args))
# edit the object with a dictionnary of arguments, # edit the object with a dictionnary of arguments,
# like {"telephone_number":"02-5648"} # like {"telephone_number":"02-5648"}
object.edit(**args) object._edit(**args)
# Then we may create subobject # Then we may create subobject
for subnode in xml.childNodes: for subnode in xml.childNodes:
...@@ -584,37 +627,37 @@ class ERP5Conduit(SyncCode): ...@@ -584,37 +627,37 @@ class ERP5Conduit(SyncCode):
subnode.nodeName=='object': subnode.nodeName=='object':
self.addNode(object=object,xml=subnode) self.addNode(object=object,xml=subnode)
def convertXmlValue(self, data): def convertXmlValue(self, data, data_type=None):
""" """
It is possible that the xml change the value, for example It is possible that the xml change the value, for example
there is some too much '\n' and some spaces. We should correct it there is some too much '\n' and some spaces. We have to do some extra
""" things so that we convert correctly the vlalue
# Theses two lines are not needed any more """
#if data.find('\n')>=0 and data[0]==' ': # We may suppose there is two '\n' in this case if data is None:
# data = data[data.find('\n')+1:data.rfind('\n')] if data_type in self.list_type_list:
# Then we should remove every other \n data = ()
# we doesn't want any of them on our synchronization, they have return data
# to be all replaced by '@@@', this is really usefull in order to split
# very long data
data = data.replace('\n','') data = data.replace('\n','')
# Remove spaces at the beginning
while data.find(' ')==0:
data = data[1:]
# Remove spaces at the end
while data.rfind(' ')==(len(data)-1):
data = data[:-1]
if type(data) is type(u"a"): if type(data) is type(u"a"):
data = data.encode(self.getEncoding()) data = data.encode(self.getEncoding())
# We can now convert string in tuple, dict, binary...
if data_type in self.list_type_list:
if type(data) is type('a'):
data = tuple(data.split('@@@'))
elif data_type in self.text_type_list:
data = data.replace('@@@','\n')
elif data_type in self.binary_type_list:
data = data.replace('@@@','\n')
msg = MIMEBase('application','octet-stream')
Encoders.encode_base64(msg)
msg.set_payload(data)
data = msg.get_payload(decode=1)
elif data_type in self.date_type_list:
data = DateTime(data)
elif data_type in self.dict_type_list:
dict_list = map(lambda x:x.split(':'),data[1:-1].split(','))
data = map(lambda (x,y):(x.replace(' ','').replace("'",''),int(y)),dict_list)
data = dict(data)
LOG('convertXmlValue',0,'data: %s' % str(data))
return data return data
def getElementNodeList(self, node):
"""
Return childNodes that are ElementNode
"""
subnode_list = []
for subnode in node.childNodes:
if subnode.nodeType == subnode.ELEMENT_NODE:
subnode_list += [subnode]
LOG('getElementNodeList',0,'end...')
return subnode_list
...@@ -160,7 +160,7 @@ class PublicationSynchronization(XMLSyncUtils): ...@@ -160,7 +160,7 @@ class PublicationSynchronization(XMLSyncUtils):
# FIXME: Why can't we use the method addSubscriber ?? # FIXME: Why can't we use the method addSubscriber ??
self.list_publications[id].addSubscriber(subscriber) self.list_publications[id].addSubscriber(subscriber)
# first synchronization # first synchronization
self.PubSyncInit(self.list_publications[id],xml_client) self.PubSyncInit(self.list_publications[id],xml_client,subscriber=subscriber)
elif self.checkAlert(xml_client) and self.getAlertCode(xml_client) in (self.TWO_WAY,self.SLOW_SYNC): elif self.checkAlert(xml_client) and self.getAlertCode(xml_client) in (self.TWO_WAY,self.SLOW_SYNC):
self.PubSyncInit(publication=self.list_publications[id], self.PubSyncInit(publication=self.list_publications[id],
......
...@@ -45,8 +45,8 @@ class Conflict(SyncCode): ...@@ -45,8 +45,8 @@ class Conflict(SyncCode):
remote_value=None, domain=None, domain_id=None): remote_value=None, domain=None, domain_id=None):
self.object_path=object_path self.object_path=object_path
self.keyword = keyword self.keyword = keyword
self.local_value=local_value self.setLocalValue(local_value)
self.remote_value=remote_value self.setRemoteValue(remote_value)
self.domain = domain self.domain = domain
self.domain_id = domain_id self.domain_id = domain_id
...@@ -56,6 +56,36 @@ class Conflict(SyncCode): ...@@ -56,6 +56,36 @@ class Conflict(SyncCode):
""" """
return self.object_path return self.object_path
def getLocalValue(self):
"""
get the domain
"""
return self.local_value
def setLocalValue(self, value):
"""
get the domain
"""
try:
self.local_value = value
except TypeError: # It happens when we try to store StringIO
self.local_value = None
def getRemoteValue(self):
"""
get the domain
"""
return self.remote_value
def setRemoteValue(self, value):
"""
get the domain
"""
try:
self.remote_value = value
except TypeError: # It happens when we try to store StringIO
self.remote_value = None
def setDomain(self, domain): def setDomain(self, domain):
""" """
set the domain set the domain
...@@ -68,6 +98,12 @@ class Conflict(SyncCode): ...@@ -68,6 +98,12 @@ class Conflict(SyncCode):
""" """
return self.domain return self.domain
def getKeyword(self):
"""
get the domain
"""
return self.keyword
def getDomainId(self): def getDomainId(self):
""" """
get the domain id get the domain id
...@@ -80,6 +116,17 @@ class Conflict(SyncCode): ...@@ -80,6 +116,17 @@ class Conflict(SyncCode):
""" """
self.domain_id = domain_id self.domain_id = domain_id
def applyRemoteValue():
"""
We will take the remote value for this conflict
"""
pass
def applyLocalValue():
"""
We will take the local value for this conflict
"""
pass
class Signature(SyncCode): class Signature(SyncCode):
""" """
...@@ -102,6 +149,7 @@ class Signature(SyncCode): ...@@ -102,6 +149,7 @@ class Signature(SyncCode):
self.setTempXML(None) self.setTempXML(None)
self.setTempXML(None) self.setTempXML(None)
self.resetConflictList() self.resetConflictList()
self.md5_string = None
self.force = 0 self.force = 0
#def __init__(self,object=None, status=None, xml_string=None): #def __init__(self,object=None, status=None, xml_string=None):
...@@ -125,6 +173,9 @@ class Signature(SyncCode): ...@@ -125,6 +173,9 @@ class Signature(SyncCode):
self.setTempXML(None) self.setTempXML(None)
if len(self.getConflictList())>0: if len(self.getConflictList())>0:
self.resetConflictList() self.resetConflictList()
elif status in (self.PUB_CONFLICT_MERGE,self.SENT):
# We have a solution for the conflict, don't need to keep the list
self.resetConflictList()
def getStatus(self): def getStatus(self):
""" """
...@@ -151,7 +202,7 @@ class Signature(SyncCode): ...@@ -151,7 +202,7 @@ class Signature(SyncCode):
self.xml = xml self.xml = xml
if self.xml != None: if self.xml != None:
self.setTempXML(None) # We make sure that the xml will not be erased self.setTempXML(None) # We make sure that the xml will not be erased
self.md5_string = md5.new(xml).digest() self.setMD5(xml)
def getXML(self): def getXML(self):
""" """
...@@ -183,7 +234,7 @@ class Signature(SyncCode): ...@@ -183,7 +234,7 @@ class Signature(SyncCode):
""" """
get the MD5 object of this signature get the MD5 object of this signature
""" """
return self.md5_object return self.md5_string
def checkMD5(self, xml_string): def checkMD5(self, xml_string):
""" """
...@@ -192,7 +243,7 @@ class Signature(SyncCode): ...@@ -192,7 +243,7 @@ class Signature(SyncCode):
if we want to know if an objects has changed or not if we want to know if an objects has changed or not
Returns 1 if MD5 are equals, else it returns 0 Returns 1 if MD5 are equals, else it returns 0
""" """
return md5.new(xml_string).digest() == self.md5_string return ((md5.new(xml_string).digest()) == self.getMD5())
def setRid(self, rid): def setRid(self, rid):
""" """
...@@ -223,14 +274,16 @@ class Signature(SyncCode): ...@@ -223,14 +274,16 @@ class Signature(SyncCode):
Set the partial string we will have to Set the partial string we will have to
deliver in the future deliver in the future
""" """
#LOG('Subscriber.setPartialXML before',0,'partial_xml: %s' % str(self.partial_xml))
self.partial_xml = xml self.partial_xml = xml
#LOG('Subscriber.setPartialXML after',0,'partial_xml: %s' % str(self.partial_xml))
def getPartialXML(self): def getPartialXML(self):
""" """
Set the partial string we will have to Set the partial string we will have to
deliver in the future deliver in the future
""" """
LOG('Subscriber.getPartialXML',0,'partial_xml: %s' % str(self.partial_xml)) #LOG('Subscriber.getPartialXML',0,'partial_xml: %s' % str(self.partial_xml))
return self.partial_xml return self.partial_xml
def getAction(self): def getAction(self):
...@@ -339,6 +392,13 @@ class Subscription(SyncCode): ...@@ -339,6 +392,13 @@ class Subscription(SyncCode):
def getSynchronizationType(self, default=None): def getSynchronizationType(self, default=None):
""" """
""" """
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# XXX for debugging only, to be removed
dict_sign = {}
for object_id in self.signatures.keys():
dict_sign[object_id] = self.signatures[object_id].getStatus()
LOG('getSignature',0,'signatures_status: %s' % str(dict_sign))
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
code = self.SLOW_SYNC code = self.SLOW_SYNC
if len(self.signatures.keys()) > 0: if len(self.signatures.keys()) > 0:
code = self.TWO_WAY code = self.TWO_WAY
...@@ -562,7 +622,7 @@ class Subscription(SyncCode): ...@@ -562,7 +622,7 @@ class Subscription(SyncCode):
for object_id in self.signatures.keys(): for object_id in self.signatures.keys():
# Change the status only if we are not in a conflict mode # Change the status only if we are not in a conflict mode
if not(self.signatures[object_id].getStatus() in (self.CONFLICT,self.PUB_CONFLICT_MERGE, if not(self.signatures[object_id].getStatus() in (self.CONFLICT,self.PUB_CONFLICT_MERGE,
self.SUB_CONFLICT_MERGE)): self.PUB_CONFLICT_CLIENT_WIN)):
self.signatures[object_id].setStatus(self.NOT_SYNCHRONIZED) self.signatures[object_id].setStatus(self.NOT_SYNCHRONIZED)
self.signatures[object_id].setPartialXML(None) self.signatures[object_id].setPartialXML(None)
self.signatures[object_id].setTempXML(None) self.signatures[object_id].setTempXML(None)
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
# #
############################################################################## ##############################################################################
from Products.ERP5Type.Accessor.TypeDefinition import list_types
from Globals import Persistent from Globals import Persistent
class SyncCode(Persistent): class SyncCode(Persistent):
...@@ -60,10 +61,28 @@ class SyncCode(Persistent): ...@@ -60,10 +61,28 @@ class SyncCode(Persistent):
PARTIAL = 4 PARTIAL = 4
NOT_SYNCHRONIZED = 5 NOT_SYNCHRONIZED = 5
PUB_CONFLICT_MERGE = 6 PUB_CONFLICT_MERGE = 6
SUB_CONFLICT_MERGE = 7 #SUB_CONFLICT_MERGE = 7
PUB_CONFLICT_CLIENT_WIN = 8 PUB_CONFLICT_CLIENT_WIN = 8
SUB_CONFLICT_CLIENT_WIN = 9 #SUB_CONFLICT_CLIENT_WIN = 9
MAX_LINES = 1000 MAX_LINES = 1000
#ENCODING='iso-8859-1' #ENCODING='iso-8859-1'
NOT_EDITABLE_PROPERTY = ('id','object','workflow_history','security_info','uid'
'xupdate:element','xupdate:attribute')
XUPDATE_INSERT = ('xupdate:insert-after','xupdate:insert-before')
XUPDATE_ADD = ('xupdate:append',)
XUPDATE_DEL = ('xupdate:remove',)
XUPDATE_UPDATE = ('xupdate:update',)
XUPDATE_INSERT_OR_ADD = tuple(XUPDATE_INSERT) + tuple(XUPDATE_ADD)
XUPDATE_TAG = tuple(XUPDATE_INSERT) + tuple(XUPDATE_ADD) + \
tuple(XUPDATE_UPDATE) + tuple(XUPDATE_DEL)
text_type_list = ('text','string')
list_type_list = list_types
binary_type_list = ('image','file','document')
date_type_list = ('date',)
dict_type_list = ('dict',)
xml_object_tag = 'object'
sub_object_exp = "/object\[@id='.*'\]/object\[@id='.*'\]"
...@@ -28,7 +28,6 @@ ...@@ -28,7 +28,6 @@
import smtplib import smtplib
from Products.ERP5SyncML.SyncCode import SyncCode from Products.ERP5SyncML.SyncCode import SyncCode
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
from Products.ERP5SyncML.Subscription import Signature from Products.ERP5SyncML.Subscription import Signature
from xml.dom.ext.reader.Sax2 import FromXml from xml.dom.ext.reader.Sax2 import FromXml
from cStringIO import StringIO from cStringIO import StringIO
...@@ -36,7 +35,7 @@ from xml.dom.ext import PrettyPrint ...@@ -36,7 +35,7 @@ from xml.dom.ext import PrettyPrint
import commands import commands
from zLOG import LOG from zLOG import LOG
class XMLSyncUtils(SyncCode): class XMLSyncUtilsMixin(SyncCode):
def SyncMLHeader(self, session_id, msg_id, target, source): def SyncMLHeader(self, session_id, msg_id, target, source):
""" """
...@@ -158,7 +157,7 @@ class XMLSyncUtils(SyncCode): ...@@ -158,7 +157,7 @@ class XMLSyncUtils(SyncCode):
xml += ' <Item>\n' xml += ' <Item>\n'
xml += ' <Source><LocURI>%s</LocURI></Source>\n' % object_id xml += ' <Source><LocURI>%s</LocURI></Source>\n' % object_id
xml += ' <Data>\n' xml += ' <Data>\n'
xml += xml_object # We have to send the data, because it allows to #xml += xml_object # We have to send the data, because it allows to
# wich object to delete (it could be clever things # wich object to delete (it could be clever things
xml += ' </Data>\n' xml += ' </Data>\n'
xml += ' </Item>\n' xml += ' </Item>\n'
...@@ -197,12 +196,14 @@ class XMLSyncUtils(SyncCode): ...@@ -197,12 +196,14 @@ class XMLSyncUtils(SyncCode):
file2 = open('/tmp/sync_old_object','w') file2 = open('/tmp/sync_old_object','w')
file2.write(old_xml) file2.write(old_xml)
file2.close() file2.close()
xupdate = commands.getoutput('xmldiff -xg /tmp/sync_old_object /tmp/sync_new_object') #xupdate = commands.getoutput('xmldiff -xg /tmp/sync_old_object /tmp/sync_new_object')
xupdate = commands.getoutput('erp5diff /tmp/sync_old_object /tmp/sync_new_object')
xupdate = xupdate[xupdate.find('<xupdate:modifications'):] xupdate = xupdate[xupdate.find('<xupdate:modifications'):]
while xupdate.find('xupdate:move')>0: # XXX To be removed, this is only needed for xmldiff with does bad things
LOG('getXupdateObject',0,'Removing the move section') #while xupdate.find('xupdate:move')>0:
xupdate = xupdate[:xupdate.find('<xupdate:move')] + \ # LOG('getXupdateObject',0,'Removing the move section')
xupdate[xupdate.find('</xupdate:move>\n')+16:] # xupdate = xupdate[:xupdate.find('<xupdate:move')] + \
# xupdate[xupdate.find('</xupdate:move>\n')+16:]
return xupdate return xupdate
def getXMLObject(self, object=None, xml_mapping=None): def getXMLObject(self, object=None, xml_mapping=None):
...@@ -517,6 +518,30 @@ class XMLSyncUtils(SyncCode): ...@@ -517,6 +518,30 @@ class XMLSyncUtils(SyncCode):
if subnode2.nodeType == subnode2.ELEMENT_NODE and subnode2.nodeName == 'Type': if subnode2.nodeType == subnode2.ELEMENT_NODE and subnode2.nodeName == 'Type':
return str(subnode2.childNodes[0].data) return str(subnode2.childNodes[0].data)
def getElementNodeList(self, node):
"""
Return childNodes that are ElementNode
"""
subnode_list = []
for subnode in node.childNodes:
if subnode.nodeType == subnode.ELEMENT_NODE:
subnode_list += [subnode]
return subnode_list
def getAttributeNodeList(self, node):
"""
Return childNodes that are ElementNode
"""
attribute_list = []
for subnode in node.attributes:
if subnode.nodeType == subnode.ATTRIBUTE_NODE:
attribute_list += [subnode]
return attribute_list
class XMLSyncUtils(XMLSyncUtilsMixin):
def Sync(self, id, msg=None, RESPONSE=None): def Sync(self, id, msg=None, RESPONSE=None):
""" """
This is the main method for synchronization This is the main method for synchronization
...@@ -539,6 +564,7 @@ class XMLSyncUtils(SyncCode): ...@@ -539,6 +564,7 @@ class XMLSyncUtils(SyncCode):
Send the server modification, this happens after the Synchronization Send the server modification, this happens after the Synchronization
initialization initialization
""" """
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
cmd_id = 1 # specifies a SyncML message-unique command identifier cmd_id = 1 # specifies a SyncML message-unique command identifier
LOG('SyncModif',0,'Starting... domain: %s' % str(domain)) LOG('SyncModif',0,'Starting... domain: %s' % str(domain))
# Get the destination folder # Get the destination folder
...@@ -564,8 +590,9 @@ class XMLSyncUtils(SyncCode): ...@@ -564,8 +590,9 @@ class XMLSyncUtils(SyncCode):
destination_waiting_more_data = 0 destination_waiting_more_data = 0
while next_status != None: while next_status != None:
object_id = self.getStatusTarget(next_status) object_id = self.getStatusTarget(next_status)
status_code = self.SUCCESS status_code = self.getStatusCode(next_status)
signature = subscriber.getSignature(object_id) signature = subscriber.getSignature(object_id)
LOG('SyncModif',0,'next_status: %s' % str(status_code))
if status_code == self.CHUNK_OK: if status_code == self.CHUNK_OK:
destination_waiting_more_data = 1 destination_waiting_more_data = 1
signature.setStatus(self.PARTIAL) signature.setStatus(self.PARTIAL)
...@@ -579,7 +606,7 @@ class XMLSyncUtils(SyncCode): ...@@ -579,7 +606,7 @@ class XMLSyncUtils(SyncCode):
elif status_code == self.CONFLICT_CLIENT_WIN: elif status_code == self.CONFLICT_CLIENT_WIN:
# The server was agree to apply our updates, nothing to do # The server was agree to apply our updates, nothing to do
signature.setStatus(self.SYNCHRONIZED) signature.setStatus(self.SYNCHRONIZED)
else: elif status_code == self.SUCCESS:
signature.setStatus(self.SYNCHRONIZED) signature.setStatus(self.SYNCHRONIZED)
next_status = self.getNextSyncBodyStatus(remote_xml, next_status) next_status = self.getNextSyncBodyStatus(remote_xml, next_status)
...@@ -607,6 +634,7 @@ class XMLSyncUtils(SyncCode): ...@@ -607,6 +634,7 @@ class XMLSyncUtils(SyncCode):
data_subnode = None data_subnode = None
if partial_data != None: if partial_data != None:
data_subnode = signature.getPartialXML() + partial_data data_subnode = signature.getPartialXML() + partial_data
LOG('SyncModif',0,'data_subnode: %s' % data_subnode)
data_subnode = FromXml(data_subnode) data_subnode = FromXml(data_subnode)
data_subnode = data_subnode.childNodes[1] # Because we just created a new xml data_subnode = data_subnode.childNodes[1] # Because we just created a new xml
# document, with childNodes[0] a DocumentType and childNodes[1] the Element Node # document, with childNodes[0] a DocumentType and childNodes[1] the Element Node
...@@ -618,8 +646,8 @@ class XMLSyncUtils(SyncCode): ...@@ -618,8 +646,8 @@ class XMLSyncUtils(SyncCode):
object =None object =None
LOG('SyncModif',0,'addNode, getActionId: %s' % self.getActionId(next_action)) LOG('SyncModif',0,'addNode, getActionId: %s' % self.getActionId(next_action))
try: try:
object = destination_path[self.getActionId(next_action)] object = destination_path._getOb(self.getActionId(next_action))
except KeyError: except (AttributeError, KeyError):
pass pass
if object is not None: if object is not None:
LOG('SyncModif',0,'addNode, found the object') LOG('SyncModif',0,'addNode, found the object')
...@@ -633,8 +661,8 @@ class XMLSyncUtils(SyncCode): ...@@ -633,8 +661,8 @@ class XMLSyncUtils(SyncCode):
elif next_action.nodeName == 'Replace': elif next_action.nodeName == 'Replace':
object =None object =None
try: try:
object = destination_path[self.getActionId(next_action)] object = destination_path._getOb(self.getActionId(next_action))
except KeyError: except (AttributeError, KeyError):
pass pass
LOG('SyncModif',0,'object: %s will be updated...' % str(object)) LOG('SyncModif',0,'object: %s will be updated...' % str(object))
if object is not None: if object is not None:
...@@ -661,7 +689,8 @@ class XMLSyncUtils(SyncCode): ...@@ -661,7 +689,8 @@ class XMLSyncUtils(SyncCode):
object.id,status_code,'Replace') object.id,status_code,'Replace')
cmd_id +=1 cmd_id +=1
elif next_action.nodeName == 'Delete': elif next_action.nodeName == 'Delete':
conduit.deleteNode(xml=self.getDataSubNode(next_action), object=destination_path) conduit.deleteNode(xml=self.getDataSubNode(next_action), object=destination_path,
object_id=self.getActionId(next_action))
subscriber.delSignature(self.getActionId(next_action)) subscriber.delSignature(self.getActionId(next_action))
else: # We want to retrieve more data else: # We want to retrieve more data
signature.setStatus(self.PARTIAL) signature.setStatus(self.PARTIAL)
...@@ -669,7 +698,8 @@ class XMLSyncUtils(SyncCode): ...@@ -669,7 +698,8 @@ class XMLSyncUtils(SyncCode):
previous_partial = signature.getPartialXML() or '' previous_partial = signature.getPartialXML() or ''
previous_partial += partial_data previous_partial += partial_data
signature.setPartialXML(previous_partial) signature.setPartialXML(previous_partial)
LOG('SyncModif',0,'previous_partial: %s' % str(previous_partial)) #LOG('SyncModif',0,'previous_partial: %s' % str(previous_partial))
LOG('SyncModif',0,'waiting more data for :%s' % signature.getId())
xml_confirmation += self.SyncMLConfirmation(cmd_id,object_id, xml_confirmation += self.SyncMLConfirmation(cmd_id,object_id,
self.WAITING_DATA,next_action.nodeName) self.WAITING_DATA,next_action.nodeName)
if conflict_list != [] and signature is not None: if conflict_list != [] and signature is not None:
...@@ -696,9 +726,10 @@ class XMLSyncUtils(SyncCode): ...@@ -696,9 +726,10 @@ class XMLSyncUtils(SyncCode):
syncml_data = '' syncml_data = ''
if has_next_action == 0: if has_next_action == 0:
for object in destination_path.objectValues(): for object in destination_path.objectValues():
status = self.SENT
local_id_list += [object.id] local_id_list += [object.id]
force = 0 force = 0
if syncml_data.count('\n') < self.MAX_LINES: # If not we have to cut if syncml_data.count('\n') < self.MAX_LINES and (object.id.find('.')!=0): # If not we have to cut
xml_object = self.getXMLObject(object=object,xml_mapping=domain.xml_mapping) xml_object = self.getXMLObject(object=object,xml_mapping=domain.xml_mapping)
LOG('SyncModif',0,'code: %s' % str(self.getAlertCode(remote_xml))) LOG('SyncModif',0,'code: %s' % str(self.getAlertCode(remote_xml)))
LOG('XMLSyncModif',0,'id_list: %s' % str(local_id_list)) LOG('XMLSyncModif',0,'id_list: %s' % str(local_id_list))
...@@ -707,11 +738,12 @@ class XMLSyncUtils(SyncCode): ...@@ -707,11 +738,12 @@ class XMLSyncUtils(SyncCode):
signature = subscriber.getSignature(object.id) signature = subscriber.getSignature(object.id)
status = self.SENT status = self.SENT
more_data=0 more_data=0
if signature==None or signature.getXML()==None or \ # For the case it was never synchronized, we have to send everything
if signature==None or (signature.getXML()==None and signature.getStatus()!=self.PARTIAL) or \
self.getAlertCode(remote_xml)==self.SLOW_SYNC: self.getAlertCode(remote_xml)==self.SLOW_SYNC:
LOG('PubSyncModif',0,'Current object.getPath: %s' % object.getPath()) #LOG('PubSyncModif',0,'Current object.getPath: %s' % object.getPath())
xml_string = xml_object xml_string = xml_object
signature = Signature(id=object.id, status=status) signature = Signature(id=object.id)
signature.setTempXML(xml_object) signature.setTempXML(xml_object)
if xml_string.count('\n') > self.MAX_LINES: if xml_string.count('\n') > self.MAX_LINES:
more_data=1 more_data=1
...@@ -721,18 +753,22 @@ class XMLSyncUtils(SyncCode): ...@@ -721,18 +753,22 @@ class XMLSyncUtils(SyncCode):
while i < self.MAX_LINES: while i < self.MAX_LINES:
short_string += rest_string[:rest_string.find('\n')+1] short_string += rest_string[:rest_string.find('\n')+1]
rest_string = xml_string[len(short_string):] rest_string = xml_string[len(short_string):]
LOG('XMLSyncUtils',0,'rest_string: %s' % str(rest_string)) #LOG('XMLSyncUtils',0,'rest_string: %s' % str(rest_string))
i += 1 i += 1
LOG('SyncModif',0,'setPartialXML with: %s' % str(rest_string))
signature.setPartialXML(rest_string) signature.setPartialXML(rest_string)
signature.setStatus(self.PARTIAL) status =self.PARTIAL
signature.setAction('Add') signature.setAction('Add')
xml_string = '<!--' + short_string + '-->' xml_string = '<!--' + short_string + '-->'
syncml_data += self.addXMLObject(cmd_id=cmd_id, object=object, syncml_data += self.addXMLObject(cmd_id=cmd_id, object=object,
xml_string=xml_string, more_data=more_data) xml_string=xml_string, more_data=more_data)
cmd_id += 1 cmd_id += 1
signature.setStatus(status)
subscriber.addSignature(signature) subscriber.addSignature(signature)
elif signature.getStatus()==self.NOT_SYNCHRONIZED \ elif signature.getStatus()==self.NOT_SYNCHRONIZED \
or signature.getStatus()==self.PUB_CONFLICT_MERGE: # We don't have synchronized this object yet or signature.getStatus()==self.PUB_CONFLICT_MERGE: # We don't have synchronized this object yet
LOG('SyncModif',0,'checkMD5: %s' % str(signature.checkMD5(xml_object)))
LOG('SyncModif',0,'getStatus: %s' % str(signature.getStatus()))
if signature.getStatus()==self.PUB_CONFLICT_MERGE: if signature.getStatus()==self.PUB_CONFLICT_MERGE:
xml_confirmation += self.SyncMLConfirmation(cmd_id,object.id, xml_confirmation += self.SyncMLConfirmation(cmd_id,object.id,
self.CONFLICT_MERGE,'Replace') self.CONFLICT_MERGE,'Replace')
...@@ -752,7 +788,6 @@ class XMLSyncUtils(SyncCode): ...@@ -752,7 +788,6 @@ class XMLSyncUtils(SyncCode):
signature.setPartialXML(rest_string) signature.setPartialXML(rest_string)
status = self.PARTIAL status = self.PARTIAL
signature.setAction('Replace') signature.setAction('Replace')
signature.setStatus(status)
xml_string = '<!--' + short_string + '-->' xml_string = '<!--' + short_string + '-->'
signature.setStatus(status) signature.setStatus(status)
syncml_data += self.replaceXMLObject(cmd_id=cmd_id,object=object, syncml_data += self.replaceXMLObject(cmd_id=cmd_id,object=object,
...@@ -772,7 +807,7 @@ class XMLSyncUtils(SyncCode): ...@@ -772,7 +807,7 @@ class XMLSyncUtils(SyncCode):
self.CONFLICT_CLIENT_WIN,'Replace') self.CONFLICT_CLIENT_WIN,'Replace')
signature.setStatus(self.SYNCHRONIZED) signature.setStatus(self.SYNCHRONIZED)
elif signature.getStatus()==self.PARTIAL: elif signature.getStatus()==self.PARTIAL:
xml_string = signature.getPartialXML() or '' xml_string = signature.getPartialXML()
if xml_string.count('\n') > self.MAX_LINES: if xml_string.count('\n') > self.MAX_LINES:
i = 0 i = 0
more_data=1 more_data=1
...@@ -801,8 +836,11 @@ class XMLSyncUtils(SyncCode): ...@@ -801,8 +836,11 @@ class XMLSyncUtils(SyncCode):
signature = subscriber.getSignature(object_id) signature = subscriber.getSignature(object_id)
if signature.getStatus()!=self.PARTIAL: # If partial, then we have a signature if signature.getStatus()!=self.PARTIAL: # If partial, then we have a signature
# but no local object # but no local object
syncml_data += self.deleteXMLObject(xml_object=signature.getXML(), xml_object = signature.getXML()
object_id=object_id,cmd_id=cmd_id) if xml_object is not None: # This prevent to delete an object that we
# were not able to create
syncml_data += self.deleteXMLObject(xml_object=signature.getXML() or '',
object_id=object_id,cmd_id=cmd_id)
subscriber.delSignature(object_id) subscriber.delSignature(object_id)
......
...@@ -26,11 +26,12 @@ ...@@ -26,11 +26,12 @@
# #
############################################################################## ##############################################################################
from Products.ERP5SyncML.XMLSyncUtils import * from Products.ERP5SyncML.XMLSyncUtils import XMLSyncUtilsMixin
from xml.dom.ext.reader.Sax2 import FromXml from xml.dom.ext.reader.Sax2 import FromXml
from Products.ERP5SyncML.SyncCode import SyncCode
from zLOG import LOG from zLOG import LOG
class XupdateUtils: class XupdateUtils(XMLSyncUtilsMixin):
""" """
This class contains method specific to xupdate xml, This class contains method specific to xupdate xml,
this is the place where we should parse xupdate data. this is the place where we should parse xupdate data.
...@@ -41,155 +42,25 @@ class XupdateUtils: ...@@ -41,155 +42,25 @@ class XupdateUtils:
Parse the xupdate and then it will call the conduit Parse the xupdate and then it will call the conduit
""" """
conflict_list = [] conflict_list = []
if type(xupdate) is type('a'): if type(xupdate) in (type('a'),type(u'a')):
xupdate = FromXml(xupdate) xupdate = FromXml(xupdate)
# This is a list of selection with a fake tag for subnode in self.getElementNodeList(xupdate):
fake_tag_list = ()
for subnode in xupdate.childNodes:
to_continue = 0
selection_name = '' selection_name = ''
if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == 'xupdate:append': if subnode.nodeName in self.XUPDATE_ADD:
# Check if we do not have a fake tag somewhere # Check if we do not have a fake tag somewhere
for subnode1 in subnode.attributes: for subnode1 in self.getElementNodeList(subnode):
if subnode1.nodeType == subnode1.ATTRIBUTE_NODE and subnode1.nodeName=='select': if subnode1.nodeName == 'xupdate:element':
selection_name = subnode1.nodeValue conflict_list += conduit.addNode(xml=subnode, object=object, force=force, **kw)
LOG('applyXupdate',0,'selection_name: %s' % str(selection_name)) elif subnode1.nodeName == 'xupdate:text':
for subnode1 in subnode.childNodes: conflict_list += conduit.addNode(xml=subnode,object=object, force=force, **kw)
if subnode1.nodeType == subnode1.ELEMENT_NODE and subnode1.nodeName == 'xupdate:element': elif subnode.nodeName in self.XUPDATE_DEL:
for subnode2 in subnode1.attributes: conflict_list += conduit.deleteNode(xml=subnode, object=object, force=force, **kw)
if subnode2.nodeName=='name' and subnode2.nodeValue == 'LogilabXMLDIFFFAKETag': elif subnode.nodeName in self.XUPDATE_UPDATE:
fake_tag_list += (selection_name,)
to_continue = 1
if not to_continue:
conduit.addNode(xml=subnode, object=object, force=force, **kw)
if subnode1.nodeType == subnode.ELEMENT_NODE and subnode1.nodeName == 'xupdate:text':
if selection_name in fake_tag_list: # This is the case where xmldiff do the crazy thing :
# Adding a fake tag, modify and delete,
# so we should only update.
conflict_list += conduit.updateNode(xml=subnode, object=object, force=force, **kw)
else:
conflict_list += conduit.addNode(xml=subnode,object=object, force=force, **kw)
#if to_continue:
# continue
elif subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == 'xupdate:remove':
for subnode1 in subnode.attributes:
if subnode1.nodeType == subnode1.ATTRIBUTE_NODE and subnode1.nodeName=='select':
selection_name = subnode1.nodeValue
LOG('applyXupdate',0,'fake_tag_list: %s' % str(fake_tag_list))
for fake_tag in fake_tag_list:
if selection_name.find(fake_tag) == 0:
LOG('applyXupdate',0,'selection ignored for delete: %s' % str(selection_name))
to_continue = 1
if not to_continue:
conduit.deleteNode(xml=subnode, object=object)
elif subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == 'xupdate:update':
conflict_list += conduit.updateNode(xml=subnode, object=object, force=force, **kw) conflict_list += conduit.updateNode(xml=subnode, object=object, force=force, **kw)
elif subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == 'xupdate:insert-after': elif subnode.nodeName in self.XUPDATE_INSERT:
conflict_list += conduit.addNode(xml=subnode, object=object, force=force, **kw) conflict_list += conduit.addNode(xml=subnode, object=object, force=force, **kw)
return conflict_list return conflict_list
def old_applyXupdate(self, object=None, xupdate=None, conduit=None):
"""
deprecated and should not be used
"""
for subnode in xupdate.childNodes:
if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == 'xupdate:modifications':
for subnode2 in subnode.childNodes:
if subnode2.nodeType == subnode2.ELEMENT_NODE:
to_continue = 0
select_list = ()
# First check if we are not a sub-object
for subnode3 in subnode2.attributes:
if subnode3.nodeType == subnode3.ATTRIBUTE_NODE and subnode3.nodeName=='select':
nodeValue = subnode3.nodeValue
if nodeValue.find('/object[1]/object')==0:
to_continue = 1
elif nodeValue.find('/object[1]/workflow_history')==0:
to_continue = 1
elif nodeValue.find('/object[1]/security_info')==0:
to_continue = 1
else:
select_list = subnode3.nodeValue.split('/') # Something like: ('','object[1]','sid[1]')
new_select_list = ()
for select_item in select_list:
new_select_list += (select_item[:select_item.find('[')],)
select_list = new_select_list # Something like : ('','object','sid')
if to_continue:
continue
# Then we have to find the keyword, differents ways are needed
# depending if we are inserting, updating, removing...
keyword = None
data = None
if subnode2.nodeName == 'xupdate:insert-after': # We suppose the tag was empty before
# XXX this supposition could be WRONG
for subnode3 in getElementNodeList(subnode2):
if subnode3.nodeName=='xupdate:element':
for subnode4 in subnode3.attributes:
if subnode4.nodeName=='name':
keyword = subnode4.nodeValue
LOG('ApplyUpdate',0,'i-a, keyword: %s' % keyword)
if keyword=='object': # This is a subobject, we have to stop right now
to_continue = 1
elif keyword.find('element_')==0: # We are on a part of a list
keyword = select_list[len(select_list)-2]
if to_continue:
continue
if len(getElementNodeList(subnode3))==0:
data = str(subnode3.childNodes[0].data) # We assume the child is a text node
else: # We have many elements
data = []
for subnode4 in getElementNodeList(subnode3):
# In this case we should only have one childnode
LOG('ApplyUpdate',0,'subnode4: %s' % str(subnode4))
element_data = subnode4.childNodes[0].data
element_data = element_data[element_data.find('\n')+1:element_data.rfind('\n')]
data += [element_data]
elif subnode2.nodeName == 'xupdate:append':
keyword = select_list[len(select_list)-1]
if len(getElementNodeList(subnode2))==0:
data = subnode2.childNodes[0].data
data = data[data.find('\n')+1:data.rfind('\n')]
else:
data=[]
for subnode3 in getElementNodeList(subnode2):
element_data = subnode3.childNodes[0].data
element_data = element_data[element_data.find('\n')+1:element_data.rfind('\n')]
data += [element_data]
if len(data) == 1: # This is probably because this is not a list but a string XXX may be not good
data = data[0]
LOG('ERP5Conduit.ApplyUpdate',0,'args: %s' % str(args))
if keyword is not None:
if type(keyword) is type(u"a"):
LOG('ERP5Conduit.ApplyUpdate',0,'keyword before encoding: %s' % str(type(keyword)))
keyword = keyword.encode(self.getEncoding())
LOG('ERP5Conduit.ApplyUpdate',0,'keyword after encoding: %s' % str(type(keyword)))
if not(keyword in self.NOT_EDITABLE_PROPERTY):
if type(data) is type([]) or type(data) is type(()):
new_data = []
for item in data:
if type(item) is type(u"a"):
item = item.encode(self.getEncoding())
new_data += [item]
data = new_data
if type(data) is type(u"a"):
data = data.encode(self.getEncoding())
# if we have already this keyword, then we may append to a list
if args.has_key(keyword):
arg_type = type(args[keyword])
if arg_type is type(()) or arg_type is type([]):
if type(data) is not type(()) or type(data) is not type([]):
data = [data]
data = args[keyword] + data
args[keyword] = data
if len(args) > 0:
object.edit(**args)
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