Commit 141a752f authored by Sebastien Robin's avatar Sebastien Robin

many bugs corrected thanks to unit test


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@370 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 2cae14a9
...@@ -43,6 +43,7 @@ import pickle ...@@ -43,6 +43,7 @@ import pickle
import string import string
from xml.dom.ext import PrettyPrint from xml.dom.ext import PrettyPrint
from cStringIO import StringIO from cStringIO import StringIO
from xml.sax.saxutils import escape, unescape
import re, copy import re, copy
from zLOG import LOG from zLOG import LOG
...@@ -101,7 +102,8 @@ class ERP5Conduit(XMLSyncUtilsMixin): ...@@ -101,7 +102,8 @@ class ERP5Conduit(XMLSyncUtilsMixin):
self.args = {} self.args = {}
security.declareProtected(Permissions.ModifyPortalContent, 'addNode') security.declareProtected(Permissions.ModifyPortalContent, 'addNode')
def addNode(self, xml=None, object=None, previous_xml=None, force=0, **kw): def addNode(self, xml=None, object=None, previous_xml=None,
object_id=None, force=0, **kw):
""" """
A node is added A node is added
...@@ -132,6 +134,7 @@ class ERP5Conduit(XMLSyncUtilsMixin): ...@@ -132,6 +134,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
conflict_list += self.addNode(xml=xml,object=object, conflict_list += self.addNode(xml=xml,object=object,
previous_xml=previous_xml, force=force, **kw) previous_xml=previous_xml, force=force, **kw)
elif xml.nodeName == 'object': elif xml.nodeName == 'object':
if object_id is None:
object_id = self.getAttribute(xml,'id') object_id = self.getAttribute(xml,'id')
docid = self.getObjectDocid(xml) docid = self.getObjectDocid(xml)
LOG('addNode',0,'object_id: %s' % object_id) LOG('addNode',0,'object_id: %s' % object_id)
...@@ -296,7 +299,6 @@ class ERP5Conduit(XMLSyncUtilsMixin): ...@@ -296,7 +299,6 @@ class ERP5Conduit(XMLSyncUtilsMixin):
new_select_list += (select_item,) 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'
data_xml = xml data_xml = xml
data = None data = None
LOG('updateNode',0,'keyword: %s' % str(keyword)) LOG('updateNode',0,'keyword: %s' % str(keyword))
...@@ -307,6 +309,8 @@ class ERP5Conduit(XMLSyncUtilsMixin): ...@@ -307,6 +309,8 @@ class ERP5Conduit(XMLSyncUtilsMixin):
if subnode1.nodeName=='name': if subnode1.nodeName=='name':
keyword = subnode1.nodeValue keyword = subnode1.nodeValue
data_xml = subnode data_xml = subnode
if keyword is None: # This is not a selection, directly the property
keyword = xml.nodeName
if len(self.getElementNodeList(data_xml))==0: if len(self.getElementNodeList(data_xml))==0:
try: try:
data = data_xml.childNodes[0].data data = data_xml.childNodes[0].data
...@@ -832,6 +836,7 @@ class ERP5Conduit(XMLSyncUtilsMixin): ...@@ -832,6 +836,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
# Encoders.encode_base64(msg) # Encoders.encode_base64(msg)
# msg.set_payload(data) # msg.set_payload(data)
# data = msg.get_payload(decode=1) # data = msg.get_payload(decode=1)
data = unescape(data)
elif data_type in self.pickle_type_list: elif data_type in self.pickle_type_list:
data = data.replace('@@@','\n') data = data.replace('@@@','\n')
msg = MIMEBase('application','octet-stream') msg = MIMEBase('application','octet-stream')
......
...@@ -76,7 +76,7 @@ class Subscriber(Subscription): ...@@ -76,7 +76,7 @@ class Subscriber(Subscription):
""" """
class Publication(SyncCode): class Publication(Subscription):
""" """
Publication defined by Publication defined by
...@@ -104,34 +104,12 @@ class Publication(SyncCode): ...@@ -104,34 +104,12 @@ class Publication(SyncCode):
self.id = id self.id = id
self.publication_url = publication_url self.publication_url = publication_url
self.destination_path = destination_path self.destination_path = destination_path
self.query = query self.setQuery(query)
self.xml_mapping = xml_mapping self.xml_mapping = xml_mapping
self.list_subscribers = PersistentMapping() self.list_subscribers = PersistentMapping()
self.domain_type = self.PUB self.domain_type = self.PUB
self.setGidGenerator(None)
def getId(self): self.setIdGenerator(None)
"""
return the id
"""
return self.id
def setId(self, id):
"""
set the id
"""
self.id = id
def getQuery(self):
"""
return the query
"""
return self.query
def setQuery(self, query):
"""
set the query
"""
self.query = query
def getPublicationUrl(self): def getPublicationUrl(self):
""" """
...@@ -152,30 +130,6 @@ class Publication(SyncCode): ...@@ -152,30 +130,6 @@ class Publication(SyncCode):
""" """
self.publication_url = publication_url self.publication_url = publication_url
def getDestinationPath(self):
"""
return the destination path
"""
return self.destination_path
def setDestinationPath(self, destination_path):
"""
set the destination path
"""
self.destination_path = destination_path
def getXML_Mapping(self):
"""
return the xml mapping
"""
return self.xml_mapping
def setXML_Mapping(self, xml_mapping):
"""
return the xml mapping
"""
self.xml_mapping = xml_mapping
def addSubscriber(self, subscriber): def addSubscriber(self, subscriber):
""" """
Add a new subscriber to the publication Add a new subscriber to the publication
...@@ -201,7 +155,7 @@ class Publication(SyncCode): ...@@ -201,7 +155,7 @@ class Publication(SyncCode):
""" """
for f in range(len(self.list_subscribers)): for f in range(len(self.list_subscribers)):
if self.list_subscribers[f].subscription_url == subscription_url: if self.list_subscribers[f].subscription_url == subscription_url:
return self.list_subscribers[f] return self.list_subscribers[f].__of__(self)
return None return None
def getSubscriberList(self): def getSubscriberList(self):
...@@ -219,7 +173,8 @@ class Publication(SyncCode): ...@@ -219,7 +173,8 @@ class Publication(SyncCode):
""" """
for f in range(len(self.list_subscribers)): for f in range(len(self.list_subscribers)):
if self.list_subscribers[f].subscription_url == subscription_url: if self.list_subscribers[f].subscription_url == subscription_url:
self.list_subscribers = self.list_subscribers[0:f] + self.list_subscribers[f+1:len(self.list_subscribers)] self.list_subscribers = self.list_subscribers[0:f] + \
self.list_subscribers[f+1:len(self.list_subscribers)]
def resetAllSubscribers(self): def resetAllSubscribers(self):
""" """
......
...@@ -134,6 +134,8 @@ class PublicationSynchronization(XMLSyncUtils): ...@@ -134,6 +134,8 @@ class PublicationSynchronization(XMLSyncUtils):
if self.email is None: if self.email is None:
file = open('/tmp/sync_client','r') file = open('/tmp/sync_client','r')
xml_client = FromXmlStream(file) xml_client = FromXmlStream(file)
file.seek(0)
LOG('PubSync',0,'Starting... msg: %s' % str(file.read()))
file.close() file.close()
elif msg is not None: elif msg is not None:
xml_client = FromXml(msg) xml_client = FromXml(msg)
...@@ -164,22 +166,25 @@ class PublicationSynchronization(XMLSyncUtils): ...@@ -164,22 +166,25 @@ class PublicationSynchronization(XMLSyncUtils):
# FIXME: Why can't we use the method addSubscriber ?? # FIXME: Why can't we use the method addSubscriber ??
self.getPublication(id).addSubscriber(subscriber) self.getPublication(id).addSubscriber(subscriber)
# first synchronization # first synchronization
self.PubSyncInit(self.list_publications[id],xml_client,subscriber=subscriber) self.PubSyncInit(self.getPublication(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.getPublication(id),
xml_client=xml_client, subscriber=subscriber) xml_client=xml_client, subscriber=subscriber)
else: else:
self.PubSyncModif(self.list_publications[id], xml_client) self.PubSyncModif(self.getPublication(id), xml_client)
elif subscriber is not None: elif subscriber is not None:
# This looks like we are starting a synchronization after # This looks like we are starting a synchronization after
# a conflict resolution by the user # a conflict resolution by the user
self.PubSyncInit(publication=self.list_publications[id], self.PubSyncInit(publication=self.getPublication(id),
xml_client=None, subscriber=subscriber) xml_client=None, subscriber=subscriber)
has_response = 1 #pubsync always replies to messages
if RESPONSE is not None: if RESPONSE is not None:
RESPONSE.redirect('managePublications') RESPONSE.redirect('managePublications')
else:
return 1
def PubSyncModif(self, publication, xml_client): def PubSyncModif(self, publication, xml_client):
""" """
......
...@@ -180,14 +180,16 @@ class Signature(SyncCode): ...@@ -180,14 +180,16 @@ class Signature(SyncCode):
md5_object -- An MD5 value of a given document md5_object -- An MD5 value of a given document
#uid -- The UID of the document #uid -- The UID of the document
id -- the ID of the document id -- the ID of the document
gid -- the global id of the document
rid -- the uid of the document on the remote database, rid -- the uid of the document on the remote database,
only needed on the server. only needed on the server.
xml -- the xml of the object at the time where it was synchronized xml -- the xml of the object at the time where it was synchronized
""" """
# Constructor # Constructor
def __init__(self,id=None, status=None, xml_string=None): def __init__(self,gid=None, status=None, xml_string=None):
self.id = id self.setGid(gid)
self.setId(None)
self.status = status self.status = status
self.setXML(xml_string) self.setXML(xml_string)
self.partial_xml = None self.partial_xml = None
...@@ -314,6 +316,18 @@ class Signature(SyncCode): ...@@ -314,6 +316,18 @@ class Signature(SyncCode):
""" """
return self.id return self.id
def setGid(self, gid):
"""
set the id
"""
self.gid = gid
def getGid(self):
"""
get the id
"""
return self.gid
def setPartialXML(self, xml): def setPartialXML(self, xml):
""" """
Set the partial string we will have to Set the partial string we will have to
...@@ -398,7 +412,7 @@ class Signature(SyncCode): ...@@ -398,7 +412,7 @@ class Signature(SyncCode):
else: else:
self.resetConflictList() self.resetConflictList()
class Subscription(SyncCode): class Subscription(SyncCode, Implicit):
""" """
Subscription hold the definition of a master ODB Subscription hold the definition of a master ODB
from/to which a selection of objects will be synchronised from/to which a selection of objects will be synchronised
...@@ -445,7 +459,7 @@ class Subscription(SyncCode): ...@@ -445,7 +459,7 @@ class Subscription(SyncCode):
self.publication_url = (publication_url) self.publication_url = (publication_url)
self.subscription_url = str(subscription_url) self.subscription_url = str(subscription_url)
self.destination_path = str(destination_path) self.destination_path = str(destination_path)
self.query = query self.setQuery(query)
self.xml_mapping = xml_mapping self.xml_mapping = xml_mapping
self.anchor = None self.anchor = None
self.session_id = 0 self.session_id = 0
...@@ -453,6 +467,9 @@ class Subscription(SyncCode): ...@@ -453,6 +467,9 @@ class Subscription(SyncCode):
self.last_anchor = '00000000T000000Z' self.last_anchor = '00000000T000000Z'
self.next_anchor = '00000000T000000Z' self.next_anchor = '00000000T000000Z'
self.domain_type = self.SUB self.domain_type = self.SUB
self.setGidGenerator(None)
self.setIdGenerator(None)
#self.signatures = PersitentMapping() #self.signatures = PersitentMapping()
# Accessors # Accessors
...@@ -496,6 +513,12 @@ class Subscription(SyncCode): ...@@ -496,6 +513,12 @@ class Subscription(SyncCode):
""" """
return self.id return self.id
def getDomainType(self):
"""
return the ID
"""
return self.domain_type
def setId(self, id): def setId(self, id):
""" """
set the ID set the ID
...@@ -512,6 +535,8 @@ class Subscription(SyncCode): ...@@ -512,6 +535,8 @@ class Subscription(SyncCode):
""" """
set the query set the query
""" """
if query in (None,''):
query = 'objectValues'
self.query = query self.query = query
def getPublicationUrl(self): def getPublicationUrl(self):
...@@ -532,18 +557,119 @@ class Subscription(SyncCode): ...@@ -532,18 +557,119 @@ class Subscription(SyncCode):
""" """
self.publication_url = publication_url self.publication_url = publication_url
def getXML_Mapping(self): def getXMLMapping(self):
""" """
return the xml mapping return the xml mapping
""" """
return self.xml_mapping return self.xml_mapping
def setXML_Mapping(self, xml_mapping): def setXMLMapping(self, xml_mapping):
""" """
return the xml mapping return the xml mapping
""" """
self.xml_mapping = xml_mapping self.xml_mapping = xml_mapping
def setGidGenerator(self, method_id):
"""
This set the method name wich allows to find a gid
from any object
"""
if method_id in (None,''):
method_id = 'getId'
self.gid_generator = method_id
def getGidGenerator(self):
"""
This get the method name wich allows to find a gid
from any object
"""
return self.gid_generator
def getObjectFromGid(self, gid):
"""
This tries to get the object with the given gid
This uses the query if it exist
"""
signature = self.getSignature(gid)
# First look if we do already have the mapping between
# the id and the gid
# query_list = []
# query = self.getQuery()
# if query is type('a'):
# query_method = getattr(object,self.getQuery(),None)
# query_list = query()
# if callable(query):
# query_list = query(self)
object_list = self.getObjectList()
destination = self.getDestination()
if signature is not None:
o_id = signature.getId()
o = None
try:
o = destination._getOb(o_id)
except (AttributeError, KeyError):
pass
if o is not None and o in object_list:
return o
for o in object_list:
LOG('getObjectFromGid',0,'working on : %s' % repr(o))
o_base = aq_base(o)
LOG('getObjectFromGid',0,'gidgenerator : %s' % repr(self.getGidGenerator()))
if hasattr(o_base, self.getGidGenerator()):
LOG('getObjectFromGid',0,'there is the gid generator')
generator = getattr(o, self.getGidGenerator())
o_gid = generator()
LOG('getObjectFromGid',0,'o_gid: %s' % repr(o_gid))
LOG('getObjectFromGid',0,'gid: %s' % repr(gid))
if o_gid == gid:
return o
LOG('getObjectFromGid',0,'returning None')
return None
def getObjectList(self):
"""
This returns the list of sub-object corresponding
to the query
"""
destination = self.getDestination()
LOG('getObjectList',0,'this is a log')
query = self.getQuery()
query_list = []
if type(query) is type('a'):
query_method = getattr(destination,query,None)
if query_method is not None:
query_list = query_method()
if callable(query):
query_list = query(destination)
# if query is not None:
# query_list = query()
return query_list
def generateNewId(self, object=None,gid=None):
"""
This tries to generate a new Id
"""
if self.getIdGenerator() is not None:
o_base = aq_base(object)
if hasattr(aq_base, self.getIdGenerator()):
generator = getattr(o, self.getIdGenerator())
new_id = generator()
return new_id
return None
def setIdGenerator(self, method_id):
"""
This set the method name wich allows to generate
a new id
"""
self.id_generator = method_id
def getIdGenerator(self):
"""
This get the method name wich allows to generate a new id
"""
return self.id_generator
def getSubscriptionUrl(self): def getSubscriptionUrl(self):
""" """
return the subscription url return the subscription url
...@@ -562,6 +688,12 @@ class Subscription(SyncCode): ...@@ -562,6 +688,12 @@ class Subscription(SyncCode):
""" """
return self.destination_path return self.destination_path
def getDestination(self):
"""
return the destination object itself
"""
return self.unrestrictedTraverse(self.getDestinationPath())
def setDestinationPath(self, destination_path): def setDestinationPath(self, destination_path):
""" """
set the destination path set the destination path
...@@ -625,15 +757,15 @@ class Subscription(SyncCode): ...@@ -625,15 +757,15 @@ class Subscription(SyncCode):
""" """
add a Signature to the subscription add a Signature to the subscription
""" """
self.signatures[signature.id] = signature self.signatures[signature.getGid()] = signature
def delSignature(self, id): def delSignature(self, gid):
""" """
add a Signature to the subscription add a Signature to the subscription
""" """
del self.signatures[id] del self.signatures[gid]
def getSignature(self, id): def getSignature(self, gid):
""" """
add a Signature to the subscription add a Signature to the subscription
""" """
...@@ -642,8 +774,8 @@ class Subscription(SyncCode): ...@@ -642,8 +774,8 @@ class Subscription(SyncCode):
#for key in self.signatures.keys(): #for key in self.signatures.keys():
# dict[key]=self.signatures[key].getPartialXML() # dict[key]=self.signatures[key].getPartialXML()
#LOG('Subscription',0,'dict: %s' % str(dict)) #LOG('Subscription',0,'dict: %s' % str(dict))
if self.signatures.has_key(id): if self.signatures.has_key(gid):
return self.signatures[id] return self.signatures[gid]
return None return None
def getSignatureList(self): def getSignatureList(self):
...@@ -655,12 +787,12 @@ class Subscription(SyncCode): ...@@ -655,12 +787,12 @@ class Subscription(SyncCode):
signature_list += [self.signatures[key]] signature_list += [self.signatures[key]]
return signature_list return signature_list
def hasSignature(self, id): def hasSignature(self, gid):
""" """
Check if there's a signature with this uid Check if there's a signature with this uid
""" """
LOG('Subscription',0,'keys: %s' % str(self.signatures.keys())) LOG('Subscription',0,'keys: %s' % str(self.signatures.keys()))
return self.signatures.has_key(id) return self.signatures.has_key(gid)
def resetAllSignatures(self): def resetAllSignatures(self):
""" """
...@@ -668,7 +800,7 @@ class Subscription(SyncCode): ...@@ -668,7 +800,7 @@ class Subscription(SyncCode):
""" """
self.signatures = PersistentMapping() self.signatures = PersistentMapping()
def getIdList(self): def getGidList(self):
""" """
Returns the list of ids from signature Returns the list of ids from signature
""" """
......
...@@ -88,37 +88,42 @@ class SubscriptionSynchronization(XMLSyncUtils): ...@@ -88,37 +88,42 @@ class SubscriptionSynchronization(XMLSyncUtils):
LOG('SubSync',0,'starting... id: %s' % str(id)) LOG('SubSync',0,'starting... id: %s' % str(id))
LOG('SubSync',0,'starting... msg: %s' % str(msg)) LOG('SubSync',0,'starting... msg: %s' % str(msg))
has_response = 0 #check if subsync replies to this messages
# first synchronization # first synchronization
if self.email is None: if self.email is None:
file = open('/tmp/sync','r') file = open('/tmp/sync','r')
if file.readlines() == []: if file.readlines() == []:
self.SubSyncInit(self.list_subscriptions[id]) self.SubSyncInit(self.getSubscription(id))
has_response = 1
else: else:
file.seek(0) file.seek(0)
xml_client = FromXmlStream(file) xml_client = FromXmlStream(file)
self.SubSyncModif(self.list_subscriptions[id],xml_client) file.seek(0)
LOG('SubSync',0,'starting... msg: %s' % str(file.read()))
has_response = self.SubSyncModif(self.getSubscription(id),xml_client)
file.close() file.close()
else: else:
if msg==None: if msg==None:
self.SubSyncInit(self.list_subscriptions[id]) self.SubSyncInit(self.getSubscription(id))
has_response = 1
else: else:
xml_client = FromXml(msg) xml_client = FromXml(msg)
self.SubSyncModif(self.list_subscriptions[id],xml_client) has_response = self.SubSyncModif(self.getSubscription(id),xml_client)
# Looks like this is not needed now
# if self.checkAlert(xml_client):
# self.SubSyncModif(self.list_subscriptions[id],xml_client)
# else:
# self.SubLastSync(self.list_subscriptions[id],xml_client)
if RESPONSE is not None: if RESPONSE is not None:
RESPONSE.redirect('manageSubscriptions') RESPONSE.redirect('manageSubscriptions')
else:
LOG('SubSync',0,'has_response: %s' % str(has_response))
return has_response
def SubSyncModif(self, subscription, xml_client): def SubSyncModif(self, subscription, xml_client):
""" """
Send the client modification, this happens after the Synchronization Send the client modification, this happens after the Synchronization
initialization initialization
""" """
self.SyncModif(subscription, xml_client) return self.SyncModif(subscription, xml_client)
def SubLastSync(self, subscription, xml_client=None, RESPONSE=None): def SubLastSync(self, subscription, xml_client=None, RESPONSE=None):
......
...@@ -243,16 +243,18 @@ class SynchronizationTool( UniqueObject, SimpleItem, ...@@ -243,16 +243,18 @@ class SynchronizationTool( UniqueObject, SimpleItem,
self.list_publications = PersistentMapping() self.list_publications = PersistentMapping()
for key in self.list_publications.keys(): for key in self.list_publications.keys():
LOG('getPublicationList',0,'key: %s, pub:%s' % (key,repr(self.list_publications[key]))) LOG('getPublicationList',0,'key: %s, pub:%s' % (key,repr(self.list_publications[key])))
return_list += [self.list_publications[key]] return_list += [self.list_publications[key].__of__(self)]
return return_list return return_list
security.declareProtected(Permissions.AccessContentsInformation,'getPublication') security.declareProtected(Permissions.AccessContentsInformation,'getPublication')
def getPublication(self, id): def getPublication(self, id):
""" """
Return a list of publications Return the publications with this id
""" """
#self.list_publications=PersistentMapping() #self.list_publications=PersistentMapping()
return self.list_publications[id] if self.list_publications.has_key(id):
return self.list_publications[id].__of__(self)
return None
security.declareProtected(Permissions.AccessContentsInformation,'getSubscriptionList') security.declareProtected(Permissions.AccessContentsInformation,'getSubscriptionList')
def getSubscriptionList(self): def getSubscriptionList(self):
...@@ -264,19 +266,38 @@ class SynchronizationTool( UniqueObject, SimpleItem, ...@@ -264,19 +266,38 @@ class SynchronizationTool( UniqueObject, SimpleItem,
# SynchronizationTool, XXX To be removed # SynchronizationTool, XXX To be removed
self.list_subscriptions = PersistentMapping() self.list_subscriptions = PersistentMapping()
for key in self.list_subscriptions.keys(): for key in self.list_subscriptions.keys():
return_list += [self.list_subscriptions[key]] return_list += [self.list_subscriptions[key].__of__(self)]
return return_list return return_list
def getSubscription(self, id):
"""
Returns the subscription with this id
"""
for subscription in self.getSubscriptionList():
if subscription.getId()==id:
return subscription
return None
security.declareProtected(Permissions.AccessContentsInformation,'') security.declareProtected(Permissions.AccessContentsInformation,'')
def getSynchronizationList(self): def getSynchronizationList(self):
""" """
Returns the list of subscriptions and publications Returns the list of subscriptions and publications
getSynchronizationList ? (mon choix)
getSubscriptionOrPublicationList ?
""" """
return self.getSubscriptionList() + self.getPublicationList() return self.getSubscriptionList() + self.getPublicationList()
security.declareProtected(Permissions.AccessContentsInformation,'')
def getSubscriberList(self):
"""
Returns the list of subscribers and subscriptions
"""
s_list = []
s_list += self.getSubscriptionList()
for publication in self.getPublicationList():
s_list += publication.getSubscriberList()
return s_list
security.declareProtected(Permissions.AccessContentsInformation,'getConflictList') security.declareProtected(Permissions.AccessContentsInformation,'getConflictList')
def getConflictList(self, context=None): def getConflictList(self, context=None):
""" """
...@@ -292,14 +313,14 @@ class SynchronizationTool( UniqueObject, SimpleItem, ...@@ -292,14 +313,14 @@ class SynchronizationTool( UniqueObject, SimpleItem,
sub_conflict_list = subscriber.getConflictList() sub_conflict_list = subscriber.getConflictList()
for conflict in sub_conflict_list: for conflict in sub_conflict_list:
#conflict.setDomain('Publication') #conflict.setDomain('Publication')
conflict.setDomain(subscriber) conflict.setSubscriber(subscriber)
#conflict.setDomainId(subscriber.getId()) #conflict.setDomainId(subscriber.getId())
conflict_list += [conflict.__of__(self)] conflict_list += [conflict.__of__(self)]
for subscription in self.getSubscriptionList(): for subscription in self.getSubscriptionList():
sub_conflict_list = subscription.getConflictList() sub_conflict_list = subscription.getConflictList()
for conflict in sub_conflict_list: for conflict in sub_conflict_list:
#conflict.setDomain('Subscription') #conflict.setDomain('Subscription')
conflict.setDomain(subscription) conflict.setSubscriber(subscription)
#conflict.setDomainId(subscription.getId()) #conflict.setDomainId(subscription.getId())
conflict_list += [conflict.__of__(self)] conflict_list += [conflict.__of__(self)]
if path is not None: # Retrieve only conflicts for a given path if path is not None: # Retrieve only conflicts for a given path
...@@ -342,7 +363,7 @@ class SynchronizationTool( UniqueObject, SimpleItem, ...@@ -342,7 +363,7 @@ class SynchronizationTool( UniqueObject, SimpleItem,
for conflict in conflict_list: for conflict in conflict_list:
if conflict.getObjectPath() == path: if conflict.getObjectPath() == path:
LOG('getSynchronizationState',0,'found a conflict: %s' % str(conflict)) LOG('getSynchronizationState',0,'found a conflict: %s' % str(conflict))
state_list += [[conflict.getDomain(),self.CONFLICT]] state_list += [[conflict.getSubscriber(),self.CONFLICT]]
for domain in self.getSynchronizationList(): for domain in self.getSynchronizationList():
destination = domain.getDestinationPath() destination = domain.getDestinationPath()
LOG('getSynchronizationState',0,'destination: %s' % str(destination)) LOG('getSynchronizationState',0,'destination: %s' % str(destination))
...@@ -356,10 +377,13 @@ class SynchronizationTool( UniqueObject, SimpleItem, ...@@ -356,10 +377,13 @@ class SynchronizationTool( UniqueObject, SimpleItem,
subscriber_list = domain.getSubscriberList() subscriber_list = domain.getSubscriberList()
else: else:
subscriber_list = [domain] subscriber_list = [domain]
LOG('getSynchronizationState, subscriber_list:',0,subscriber_list)
for subscriber in subscriber_list: for subscriber in subscriber_list:
signature = subscriber.getSignature(o_id) signature = subscriber.getSignature(o_id)
if signature is not None: if signature is not None:
state = signature.getStatus() state = signature.getStatus()
LOG('getSynchronizationState:',0,'sub.dest :%s, state: %s' % \
(subscriber.getSubscriptionUrl(),str(state)))
found = None found = None
# Make sure there is not already a conflict giving the state # Make sure there is not already a conflict giving the state
for state_item in state_list: for state_item in state_list:
...@@ -376,7 +400,7 @@ class SynchronizationTool( UniqueObject, SimpleItem, ...@@ -376,7 +400,7 @@ class SynchronizationTool( UniqueObject, SimpleItem,
to keep the local version of an object to keep the local version of an object
""" """
object = self.unrestrictedTraverse(conflict.getObjectPath()) object = self.unrestrictedTraverse(conflict.getObjectPath())
subscriber = conflict.getDomain() subscriber = conflict.getSubscriber()
# get the signature: # get the signature:
LOG('p_sync.setLocalObject, subscriber: ',0,subscriber) LOG('p_sync.setLocalObject, subscriber: ',0,subscriber)
signature = subscriber.getSignature(object.getId()) # XXX may be change for rid signature = subscriber.getSignature(object.getId()) # XXX may be change for rid
...@@ -411,7 +435,7 @@ class SynchronizationTool( UniqueObject, SimpleItem, ...@@ -411,7 +435,7 @@ class SynchronizationTool( UniqueObject, SimpleItem,
to keep the local version of an object to keep the local version of an object
""" """
object = self.unrestrictedTraverse(conflict.getObjectPath()) object = self.unrestrictedTraverse(conflict.getObjectPath())
subscriber = conflict.getDomain() subscriber = conflict.getSubscriber()
# get the signature: # get the signature:
LOG('p_sync.setRemoteObject, subscriber: ',0,subscriber) LOG('p_sync.setRemoteObject, subscriber: ',0,subscriber)
signature = subscriber.getSignature(object.getId()) # XXX may be change for rid signature = subscriber.getSignature(object.getId()) # XXX may be change for rid
...@@ -449,7 +473,7 @@ class SynchronizationTool( UniqueObject, SimpleItem, ...@@ -449,7 +473,7 @@ class SynchronizationTool( UniqueObject, SimpleItem,
if conflict.getKeyword() == keyword: if conflict.getKeyword() == keyword:
LOG('manageLocalValue',0,'found the keyword') LOG('manageLocalValue',0,'found the keyword')
if '/'.join(conflict.getObjectPath())==object_path: if '/'.join(conflict.getObjectPath())==object_path:
if conflict.getDomain().getSubscriptionUrl()==subscription_url: if conflict.getSubscriber().getSubscriptionUrl()==subscription_url:
conflict.applyPublisherValue() conflict.applyPublisherValue()
if RESPONSE is not None: if RESPONSE is not None:
RESPONSE.redirect('manageConflicts') RESPONSE.redirect('manageConflicts')
...@@ -468,7 +492,7 @@ class SynchronizationTool( UniqueObject, SimpleItem, ...@@ -468,7 +492,7 @@ class SynchronizationTool( UniqueObject, SimpleItem,
if conflict.getKeyword() == keyword: if conflict.getKeyword() == keyword:
LOG('manageLocalValue',0,'found the keyword') LOG('manageLocalValue',0,'found the keyword')
if '/'.join(conflict.getObjectPath())==object_path: if '/'.join(conflict.getObjectPath())==object_path:
if conflict.getDomain().getSubscriptionUrl()==subscription_url: if conflict.getSubscriber().getSubscriptionUrl()==subscription_url:
conflict.applySubscriberValue() conflict.applySubscriberValue()
if RESPONSE is not None: if RESPONSE is not None:
RESPONSE.redirect('manageConflicts') RESPONSE.redirect('manageConflicts')
......
...@@ -127,7 +127,8 @@ class XMLSyncUtilsMixin(SyncCode): ...@@ -127,7 +127,8 @@ class XMLSyncUtilsMixin(SyncCode):
server.sendmail(fromaddr, "seb@localhost", msg) server.sendmail(fromaddr, "seb@localhost", msg)
server.quit() server.quit()
def addXMLObject(self, cmd_id=0, object=None, xml_string=None,more_data=0): def addXMLObject(self, cmd_id=0, object=None, xml_string=None,
more_data=0,gid=None):
""" """
Add an object with the SyncML protocol Add an object with the SyncML protocol
""" """
...@@ -136,7 +137,7 @@ class XMLSyncUtilsMixin(SyncCode): ...@@ -136,7 +137,7 @@ class XMLSyncUtilsMixin(SyncCode):
xml += ' <CmdID>%s</CmdID>\n' % cmd_id xml += ' <CmdID>%s</CmdID>\n' % cmd_id
xml += ' <Meta><Type>%s</Type></Meta>\n' % object.portal_type xml += ' <Meta><Type>%s</Type></Meta>\n' % object.portal_type
xml += ' <Item>\n' xml += ' <Item>\n'
xml += ' <Source><LocURI>%s</LocURI></Source>\n' % object.id xml += ' <Source><LocURI>%s</LocURI></Source>\n' % gid
xml += ' <Data>\n' xml += ' <Data>\n'
xml += xml_string xml += xml_string
xml += ' </Data>\n' xml += ' </Data>\n'
...@@ -146,7 +147,7 @@ class XMLSyncUtilsMixin(SyncCode): ...@@ -146,7 +147,7 @@ class XMLSyncUtilsMixin(SyncCode):
xml += ' </Add>\n' xml += ' </Add>\n'
return xml return xml
def deleteXMLObject(self, cmd_id=0, object_id=None, xml_object=''): def deleteXMLObject(self, cmd_id=0, object_gid=None, xml_object=''):
""" """
Add an object with the SyncML protocol Add an object with the SyncML protocol
""" """
...@@ -155,7 +156,7 @@ class XMLSyncUtilsMixin(SyncCode): ...@@ -155,7 +156,7 @@ class XMLSyncUtilsMixin(SyncCode):
xml += ' <CmdID>%s</CmdID>\n' % cmd_id xml += ' <CmdID>%s</CmdID>\n' % cmd_id
#xml += ' <Meta><Type>%s</Type></Meta>\n' % object.portal_type #xml += ' <Meta><Type>%s</Type></Meta>\n' % object.portal_type
xml += ' <Item>\n' xml += ' <Item>\n'
xml += ' <Source><LocURI>%s</LocURI></Source>\n' % object_id xml += ' <Source><LocURI>%s</LocURI></Source>\n' % object_gid
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
...@@ -164,7 +165,8 @@ class XMLSyncUtilsMixin(SyncCode): ...@@ -164,7 +165,8 @@ class XMLSyncUtilsMixin(SyncCode):
xml += ' </Delete>\n' xml += ' </Delete>\n'
return xml return xml
def replaceXMLObject(self, cmd_id=0, object=None, xml_string=None, more_data=0): def replaceXMLObject(self, cmd_id=0, object=None, xml_string=None,
more_data=0,gid=None):
""" """
Add an object with the SyncML protocol Add an object with the SyncML protocol
""" """
...@@ -173,7 +175,7 @@ class XMLSyncUtilsMixin(SyncCode): ...@@ -173,7 +175,7 @@ class XMLSyncUtilsMixin(SyncCode):
xml += ' <CmdID>%s</CmdID>\n' % cmd_id xml += ' <CmdID>%s</CmdID>\n' % cmd_id
xml += ' <Meta><Type>%s</Type></Meta>\n' % object.portal_type xml += ' <Meta><Type>%s</Type></Meta>\n' % object.portal_type
xml += ' <Item>\n' xml += ' <Item>\n'
xml += ' <Source><LocURI>%s</LocURI></Source>\n' % object.id xml += ' <Source><LocURI>%s</LocURI></Source>\n' % str(gid)
xml += ' <Data>\n' xml += ' <Data>\n'
xml += xml_string xml += xml_string
xml += ' </Data>\n' xml += ' </Data>\n'
...@@ -548,214 +550,42 @@ class XMLSyncUtilsMixin(SyncCode): ...@@ -548,214 +550,42 @@ class XMLSyncUtilsMixin(SyncCode):
attribute_list += [subnode] attribute_list += [subnode]
return attribute_list return attribute_list
def getSyncMLData(self, domain=None,remote_xml=None,cmd_id=0,
subscriber=None,destination_path=None):
class XMLSyncUtils(XMLSyncUtilsMixin):
def Sync(self, id, msg=None, RESPONSE=None):
"""
This is the main method for synchronization
"""
pass
def SyncInit(self, domain):
"""
Initialization of a synchronization, this is
used for the first message of every synchronization
"""
pass
def SyncModif(self, domain, remote_xml):
"""
Modification Message, this is used after the first
message in order to send modifications.
""" """
This generate the syncml data message. This returns a string
with all modification made locally (ie replace, add ,delete...)
""" """
Send the server modification, this happens after the Synchronization local_gid_list = []
initialization
"""
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
cmd_id = 1 # specifies a SyncML message-unique command identifier
LOG('SyncModif',0,'Starting... domain: %s' % str(domain))
# Get the destination folder
destination_path = self.unrestrictedTraverse(domain.getDestinationPath())
first_node = remote_xml.childNodes[1]
# Get informations from the header
xml_header = first_node.childNodes[1]
if xml_header.nodeName != "SyncHdr":
LOG('PubSyncModif',0,'This is not a SyncML Header')
return
subscriber = domain # If we are the client, this is fine
if domain.domain_type == self.PUB:
for subnode in xml_header.childNodes:
if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == "Source":
subscription_url = str(subnode.childNodes[0].data)
subscriber = domain.getSubscriber(subscription_url)
next_status = self.getNextSyncBodyStatus(remote_xml, None)
next_status = self.getNextSyncBodyStatus(remote_xml, next_status) # We do not want the first one
destination_waiting_more_data = 0
while next_status != None:
object_id = self.getStatusTarget(next_status)
status_code = self.getStatusCode(next_status)
signature = subscriber.getSignature(object_id)
LOG('SyncModif',0,'next_status: %s' % str(status_code))
if status_code == self.CHUNK_OK:
destination_waiting_more_data = 1
signature.setStatus(self.PARTIAL)
elif status_code == self.CONFLICT:
signature.setStatus(self.CONFLICT)
elif status_code == self.CONFLICT_MERGE:
# We will have to apply the update, and we should not care about conflicts
# so we have to force the update
signature.setStatus(self.NOT_SYNCHRONIZED)
signature.setForce(1)
elif status_code == self.CONFLICT_CLIENT_WIN:
# The server was agree to apply our updates, nothing to do
signature.setStatus(self.SYNCHRONIZED)
elif status_code == self.SUCCESS:
signature.setStatus(self.SYNCHRONIZED)
next_status = self.getNextSyncBodyStatus(remote_xml, next_status)
alert_code = self.getAlertCode(remote_xml)
next_action = self.getNextSyncAction(remote_xml, None)
has_next_action = 0
if next_action is not None:
has_next_action = 1
LOG('SyncModif, has_next_action:',0,has_next_action)
conduit = ERP5Conduit()
xml_confirmation = ''
while next_action != None:
conflict_list = []
status_code = self.SUCCESS
# Thirst we have to check the kind of action it is
partial_data = self.getPartialData(next_action)
#LOG('XMLSyncUtils',0,'partial_data: %s' % str(partial_data))
#LOG('XMLSyncUtils',0,'checkActionMoreData: %s' % str(self.checkActionMoreData(next_action)))
object_id = self.getActionId(next_action)
signature = subscriber.getSignature(object_id)
if signature == None:
signature = Signature(id=object_id,status=self.NOT_SYNCHRONIZED)
subscriber.addSignature(signature)
force = signature.getForce()
if self.checkActionMoreData(next_action) == 0:
data_subnode = None
if partial_data != None:
data_subnode = signature.getPartialXML() + partial_data
LOG('SyncModif',0,'data_subnode: %s' % data_subnode)
data_subnode = FromXml(data_subnode)
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
else:
data_subnode = self.getDataSubNode(next_action)
if next_action.nodeName == 'Add':
conflict_list += conduit.addNode(xml=data_subnode, object=destination_path)
# Then store the xml of this new subobject
object =None
LOG('SyncModif',0,'addNode, getActionId: %s' % self.getActionId(next_action))
try:
object = destination_path._getOb(self.getActionId(next_action))
except (AttributeError, KeyError):
pass
if object is not None:
LOG('SyncModif',0,'addNode, found the object')
xml_object = object.asXML()
signature.setStatus(self.SYNCHRONIZED)
signature.setXML(xml_object)
#has_sign = subscriber.hasSignature(object.id)
#LOG('SyncModif',0,'has_sign: %i' % has_sign)
xml_confirmation += self.SyncMLConfirmation(cmd_id,object.id,self.SUCCESS,'Add')
cmd_id +=1
elif next_action.nodeName == 'Replace':
object =None
try:
object = destination_path._getOb(self.getActionId(next_action))
except (AttributeError, KeyError):
pass
LOG('SyncModif',0,'object: %s will be updated...' % str(object))
if object is not None:
LOG('SyncModif',0,'object: %s will be updated...' % object.id)
signature = subscriber.getSignature(object.id)
LOG('SyncModif',0,'previous signature: %s' % str(signature))
previous_xml = signature.getXML()
LOG('SyncModif',0,'previous signature: %i' % len(previous_xml))
conflict_list += conduit.updateNode(xml=data_subnode, object=object,
previous_xml=signature.getXML(),force=force)
xml_object = object.asXML()
signature.setTempXML(xml_object)
if conflict_list != []:
status_code = self.CONFLICT
signature.setStatus(self.CONFLICT)
signature.setConflictList(signature.getConflictList()+conflict_list)
string_io = StringIO()
PrettyPrint(data_subnode,stream=string_io)
data_subnode_string = string_io.getvalue()
signature.setPartialXML(data_subnode_string)
else:
signature.setStatus(self.SYNCHRONIZED)
xml_confirmation += self.SyncMLConfirmation(cmd_id,
object.id,status_code,'Replace')
cmd_id +=1
elif next_action.nodeName == 'Delete':
conduit.deleteNode(xml=self.getDataSubNode(next_action), object=destination_path,
object_id=self.getActionId(next_action))
subscriber.delSignature(self.getActionId(next_action))
else: # We want to retrieve more data
signature.setStatus(self.PARTIAL)
#LOG('SyncModif',0,'setPartialXML: %s' % str(previous_partial))
previous_partial = signature.getPartialXML() or ''
previous_partial += partial_data
signature.setPartialXML(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,
self.WAITING_DATA,next_action.nodeName)
if conflict_list != [] and signature is not None:
# We had a conflict
signature.setStatus(self.CONFLICT)
next_action = self.getNextSyncAction(remote_xml, next_action)
xml = ""
xml += '<SyncML>\n'
# syncml header
if domain.domain_type == self.PUB:
xml += self.SyncMLHeader(subscriber.getSessionId(), "1",
subscriber.getSubscriptionUrl(), domain.getPublicationUrl())
elif domain.domain_type == self.SUB:
xml += self.SyncMLHeader(domain.getSessionId(), "1",
domain.getPublicationUrl(), domain.getSubscriptionUrl())
cmd_id += 1
# Add or replace objects
local_id_list = []
syncml_data = '' syncml_data = ''
if has_next_action == 0: for object in domain.getObjectList():
for object in destination_path.objectValues():
status = self.SENT status = self.SENT
local_id_list += [object.id] gid_generator = getattr(object,domain.getGidGenerator(),None)
object_gid = None
if gid_generator is not None:
object_gid = gid_generator()
local_gid_list += [object_gid]
force = 0 force = 0
if syncml_data.count('\n') < self.MAX_LINES and (object.id.find('.')!=0): # 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,'xml_mapping: %s' % str(domain.xml_mapping)) LOG('getSyncMLData',0,'xml_mapping: %s' % str(domain.xml_mapping))
LOG('SyncModif',0,'code: %s' % str(self.getAlertCode(remote_xml))) LOG('getSyncMLData',0,'code: %s' % str(self.getAlertCode(remote_xml)))
LOG('XMLSyncModif',0,'id_list: %s' % str(local_id_list)) LOG('getSyncMLData',0,'gid_list: %s' % str(local_gid_list))
LOG('XMLSyncModif',0,'hasSinature: %s' % str(subscriber.hasSignature(object.id))) LOG('getSyncMLData',0,'hasSignature: %s' % str(subscriber.hasSignature(object_gid)))
LOG('XMLSyncModif',0,'alert_code == slowsync: %s' % str(self.getAlertCode(remote_xml)==self.SLOW_SYNC)) LOG('getSyncMLData',0,'alert_code == slowsync: %s' % str(self.getAlertCode(remote_xml)==self.SLOW_SYNC))
signature = subscriber.getSignature(object.id) signature = subscriber.getSignature(object_gid)
if signature is not None:
LOG('getSyncMLData',0,'signature.status: %s' % str(signature.getStatus()))
LOG('getSyncMLData',0,'signature.action: %s' % str(signature.getAction()))
status = self.SENT status = self.SENT
more_data=0 more_data=0
# For the case it was never synchronized, we have to send everything # 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 \ 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())
LOG('getSyncMLData',0,'no signature for gid: %s' % object_gid)
xml_string = xml_object xml_string = xml_object
signature = Signature(id=object.id) signature = Signature(gid=object_gid)
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
...@@ -767,26 +597,27 @@ class XMLSyncUtils(XMLSyncUtilsMixin): ...@@ -767,26 +597,27 @@ class XMLSyncUtils(XMLSyncUtilsMixin):
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)) LOG('getSyncMLData',0,'setPartialXML with: %s' % str(rest_string))
signature.setPartialXML(rest_string) signature.setPartialXML(rest_string)
status =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,gid=object_gid,
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) 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('getSyncMLData',0,'checkMD5: %s' % str(signature.checkMD5(xml_object)))
LOG('SyncModif',0,'getStatus: %s' % str(signature.getStatus())) LOG('getSyncMLData',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')
if not signature.checkMD5(xml_object): if not signature.checkMD5(xml_object):
# This object has changed on this side, we have to generate some xmldiff # This object has changed on this side, we have to generate some xmldiff
xml_string = self.getXupdateObject(object=object,xml_mapping=domain.xml_mapping, xml_string = self.getXupdateObject(object=object,
xml_mapping=domain.xml_mapping,
old_xml=signature.getXML()) old_xml=signature.getXML())
if xml_string.count('\n') > self.MAX_LINES: if xml_string.count('\n') > self.MAX_LINES:
i = 0 i = 0
...@@ -802,7 +633,7 @@ class XMLSyncUtils(XMLSyncUtilsMixin): ...@@ -802,7 +633,7 @@ class XMLSyncUtils(XMLSyncUtilsMixin):
signature.setAction('Replace') signature.setAction('Replace')
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,gid=object_gid,
xml_string=xml_string, more_data=more_data) xml_string=xml_string, more_data=more_data)
cmd_id += 1 cmd_id += 1
signature.setTempXML(xml_object) signature.setTempXML(xml_object)
...@@ -810,12 +641,13 @@ class XMLSyncUtils(XMLSyncUtilsMixin): ...@@ -810,12 +641,13 @@ class XMLSyncUtils(XMLSyncUtilsMixin):
signature.setStatus(self.SYNCHRONIZED) signature.setStatus(self.SYNCHRONIZED)
elif signature.getStatus()==self.PUB_CONFLICT_CLIENT_WIN: elif signature.getStatus()==self.PUB_CONFLICT_CLIENT_WIN:
# We have decided to apply the update # We have decided to apply the update
LOG('SyncModif',0,'signature.getTempXML(): %s' % str(signature.getTempXML())) LOG('getSyncMLData',0,'signature.getTempXML(): %s' % str(signature.getTempXML()))
# XXX previous_xml will be getXML instead of getTempXML because # XXX previous_xml will be getXML instead of getTempXML because
# some modification was already made and the update may not apply correctly # some modification was already made and the update
# may not apply correctly
conduit.updateNode(xml=signature.getPartialXML(), object=object, conduit.updateNode(xml=signature.getPartialXML(), object=object,
previous_xml=signature.getXML(),force=1) previous_xml=signature.getXML(),force=1)
xml_confirmation += self.SyncMLConfirmation(cmd_id,object.id, xml_confirmation += self.SyncMLConfirmation(cmd_id,object_gid,
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:
...@@ -835,31 +667,270 @@ class XMLSyncUtils(XMLSyncUtilsMixin): ...@@ -835,31 +667,270 @@ class XMLSyncUtils(XMLSyncUtilsMixin):
xml_string = '<!--' + xml_string + '-->' xml_string = '<!--' + xml_string + '-->'
signature.setStatus(status) signature.setStatus(status)
if signature.getAction()=='Replace': if signature.getAction()=='Replace':
syncml_data += self.replaceXMLObject(cmd_id=cmd_id,object=object, syncml_data += self.replaceXMLObject(cmd_id=cmd_id,object=object,gid=object_gid,
xml_string=xml_string, more_data=more_data) xml_string=xml_string, more_data=more_data)
elif signature.getAction()=='Add': elif signature.getAction()=='Add':
syncml_data += self.addXMLObject(cmd_id=cmd_id, object=object, syncml_data += self.addXMLObject(cmd_id=cmd_id, object=object,gid=object_gid,
xml_string=xml_string, more_data=more_data) xml_string=xml_string, more_data=more_data)
# Objects to remove # Objects to remove
#for object_id in id_list: #for object_id in id_list:
for object_id in subscriber.getIdList(): for object_gid in subscriber.getGidList():
if not (object_id in local_id_list): if not (object_gid in local_gid_list): # @@@@@
# This is an object to remove # This is an object to remove
signature = subscriber.getSignature(object_id) signature = subscriber.getSignature(object_gid)
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
xml_object = signature.getXML() xml_object = signature.getXML()
if xml_object is not None: # This prevent to delete an object that we if xml_object is not None: # This prevent to delete an object that we
# were not able to create # were not able to create
syncml_data += self.deleteXMLObject(xml_object=signature.getXML() or '', syncml_data += self.deleteXMLObject(xml_object=signature.getXML() or '',
object_id=object_id,cmd_id=cmd_id) object_gid=object_gid,cmd_id=cmd_id)
subscriber.delSignature(object_id) subscriber.delSignature(object_gid)
return (syncml_data,cmd_id)
def applyActionList(self, domain=None, subscriber=None,destination_path=None,
cmd_id=0,remote_xml=None,conduit=None):
"""
This just look to a list of action to do, then id applies
each action one by one, thanks to a conduit
"""
next_action = self.getNextSyncAction(remote_xml, None)
xml_confirmation = ''
has_next_action = 0
if next_action is not None:
has_next_action = 1
while next_action != None:
conflict_list = []
status_code = self.SUCCESS
# Thirst we have to check the kind of action it is
partial_data = self.getPartialData(next_action)
#LOG('XMLSyncUtils',0,'partial_data: %s' % str(partial_data))
#LOG('XMLSyncUtils',0,'checkActionMoreData: %s' % str(self.checkActionMoreData(next_action)))
object_gid = self.getActionId(next_action)
signature = subscriber.getSignature(object_gid)
if signature == None:
signature = Signature(gid=object_gid,status=self.NOT_SYNCHRONIZED)
subscriber.addSignature(signature)
force = signature.getForce()
object = domain.getObjectFromGid(object_gid)
LOG('applyActionList',0,'object: %s' % repr(object))
if self.checkActionMoreData(next_action) == 0:
data_subnode = None
if partial_data != None:
data_subnode = signature.getPartialXML() + partial_data
LOG('SyncModif',0,'data_subnode: %s' % data_subnode)
data_subnode = FromXml(data_subnode)
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
else:
data_subnode = self.getDataSubNode(next_action)
if next_action.nodeName == 'Add':
# Then store the xml of this new subobject
#object = domain.getObjectFromGid(object=destination_path,gid=object_gid)
if object is None:
object_id = domain.generateNewId(object=destination_path)
conflict_list += conduit.addNode(xml=data_subnode, object=destination_path,
object_id=object_id)
object = domain.getObjectFromGid(object_gid)
LOG('applyActionList',0,'object after add: %s' % repr(object)) #LOG('SyncModif',0,'addNode, getActionId: %s' % self.getActionId(next_action))
# try:
# object = destination_path._getOb(self.getActionId(next_action))
# except (AttributeError, KeyError):
# pass
if object is not None:
LOG('SyncModif',0,'addNode, found the object')
mapping = getattr(object,domain.getXMLMapping(),None)
xml_object = ''
if mapping is not None:
xml_object = mapping()
#xml_object = object.asXML()
signature.setStatus(self.SYNCHRONIZED)
signature.setId(object.getId())
signature.setXML(xml_object)
xml_confirmation +=\
self.SyncMLConfirmation(cmd_id,object_gid,self.SUCCESS,'Add')
cmd_id +=1
elif next_action.nodeName == 'Replace':
#object = domain.getObjectFromGid(object=destination_path,gid=object_gid)
# object =None
# try:
# object = destination_path._getOb(self.getActionId(next_action))
# except (AttributeError, KeyError):
# pass
LOG('SyncModif',0,'object: %s will be updated...' % str(object))
if object is not None:
LOG('SyncModif',0,'object: %s will be updated...' % object.id)
signature = subscriber.getSignature(object_gid)
LOG('SyncModif',0,'previous signature: %s' % str(signature))
previous_xml = signature.getXML()
LOG('SyncModif',0,'previous signature: %i' % len(previous_xml))
conflict_list += conduit.updateNode(xml=data_subnode, object=object,
previous_xml=signature.getXML(),force=force)
mapping = getattr(object,domain.getXMLMapping(),None)
xml_object = ''
if mapping is not None:
xml_object = mapping()
#xml_object = object.asXML()
signature.setTempXML(xml_object)
if conflict_list != []:
status_code = self.CONFLICT
signature.setStatus(self.CONFLICT)
signature.setConflictList(signature.getConflictList()+conflict_list)
string_io = StringIO()
PrettyPrint(data_subnode,stream=string_io)
data_subnode_string = string_io.getvalue()
signature.setPartialXML(data_subnode_string)
else:
signature.setStatus(self.SYNCHRONIZED)
xml_confirmation += self.SyncMLConfirmation(cmd_id,
object_gid,status_code,'Replace')
cmd_id +=1
elif next_action.nodeName == 'Delete':
object_id = object.id
conduit.deleteNode(xml=self.getDataSubNode(next_action), object=destination_path,
object_id=self.getActionId(next_action))
subscriber.delSignature(object_gid)
else: # We want to retrieve more data
signature.setStatus(self.PARTIAL)
#LOG('SyncModif',0,'setPartialXML: %s' % str(previous_partial))
previous_partial = signature.getPartialXML() or ''
previous_partial += partial_data
signature.setPartialXML(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_gid,
self.WAITING_DATA,next_action.nodeName)
if conflict_list != [] and signature is not None:
# We had a conflict
signature.setStatus(self.CONFLICT)
next_action = self.getNextSyncAction(remote_xml, next_action)
return (xml_confirmation,has_next_action,cmd_id)
def applyStatusList(self, subscriber=None,remote_xml=None):
"""
This read a list of status list (ie syncml confirmations).
This method have to change status codes on signatures
"""
next_status = self.getNextSyncBodyStatus(remote_xml, None)
# We do not want the first one
next_status = self.getNextSyncBodyStatus(remote_xml, next_status)
has_status_list = 0
if next_status is not None:
has_status_list = 1
destination_waiting_more_data = 0
while next_status != None:
object_gid = self.getStatusTarget(next_status)
status_code = self.getStatusCode(next_status)
signature = subscriber.getSignature(object_gid)
LOG('SyncModif',0,'next_status: %s' % str(status_code))
if status_code == self.CHUNK_OK:
destination_waiting_more_data = 1
signature.setStatus(self.PARTIAL)
elif status_code == self.CONFLICT:
signature.setStatus(self.CONFLICT)
elif status_code == self.CONFLICT_MERGE:
# We will have to apply the update, and we should not care about conflicts
# so we have to force the update
signature.setStatus(self.NOT_SYNCHRONIZED)
signature.setForce(1)
elif status_code == self.CONFLICT_CLIENT_WIN:
# The server was agree to apply our updates, nothing to do
signature.setStatus(self.SYNCHRONIZED)
elif status_code == self.SUCCESS:
signature.setStatus(self.SYNCHRONIZED)
next_status = self.getNextSyncBodyStatus(remote_xml, next_status)
return (destination_waiting_more_data, has_status_list)
class XMLSyncUtils(XMLSyncUtilsMixin):
def Sync(self, id, msg=None, RESPONSE=None):
"""
This is the main method for synchronization
"""
pass
def SyncInit(self, domain):
"""
Initialization of a synchronization, this is
used for the first message of every synchronization
"""
pass
def SyncModif(self, domain, remote_xml):
"""
Modification Message, this is used after the first
message in order to send modifications.
"""
"""
Send the server modification, this happens after the Synchronization
initialization
"""
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
has_response = 0 #check if submodif replies to this messages
cmd_id = 1 # specifies a SyncML message-unique command identifier
LOG('SyncModif',0,'Starting... domain: %s' % str(domain))
# Get the destination folder
destination_path = self.unrestrictedTraverse(domain.getDestinationPath())
first_node = remote_xml.childNodes[1]
# Get informations from the header
xml_header = first_node.childNodes[1]
if xml_header.nodeName != "SyncHdr":
LOG('PubSyncModif',0,'This is not a SyncML Header')
return
subscriber = domain # If we are the client, this is fine
if domain.domain_type == self.PUB:
for subnode in xml_header.childNodes:
if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == "Source":
subscription_url = str(subnode.childNodes[0].data)
subscriber = domain.getSubscriber(subscription_url)
# First apply the list of status codes
(destination_waiting_more_data,has_status_list) = self.applyStatusList(
subscriber=subscriber,
remote_xml=remote_xml)
alert_code = self.getAlertCode(remote_xml)
conduit = ERP5Conduit()
LOG('SyncModif, subscriber: ',0,subscriber)
# Then apply the list of actions
(xml_confirmation,has_next_action,cmd_id) = self.applyActionList(cmd_id=cmd_id,
domain=domain,
destination_path=destination_path,
subscriber=subscriber,
remote_xml=remote_xml,
conduit=conduit)
LOG('SyncModif, has_next_action:',0,has_next_action)
xml = ""
xml += '<SyncML>\n'
# syncml header
if domain.domain_type == self.PUB:
xml += self.SyncMLHeader(subscriber.getSessionId(), "1",
subscriber.getSubscriptionUrl(), domain.getPublicationUrl())
elif domain.domain_type == self.SUB:
xml += self.SyncMLHeader(domain.getSessionId(), "1",
domain.getPublicationUrl(), domain.getSubscriptionUrl())
cmd_id += 1
# Add or replace objects
syncml_data = ''
# Now we have to send our own modifications
if has_next_action == 0:
(syncml_data,cmd_id) = self.getSyncMLData(domain=domain,remote_xml=remote_xml,
subscriber=subscriber,
destination_path=destination_path,
cmd_id=cmd_id)
# syncml body # syncml body
xml += ' <SyncBody>\n' xml += ' <SyncBody>\n'
# For a slow sync, we have to send everything
#if alert_code == subscriber.SLOW_SYNC:
destination_url = '' destination_url = ''
if domain.domain_type == self.PUB: if domain.domain_type == self.PUB:
subscriber.NewAnchor() subscriber.NewAnchor()
...@@ -891,18 +962,32 @@ class XMLSyncUtils(XMLSyncUtilsMixin): ...@@ -891,18 +962,32 @@ class XMLSyncUtils(XMLSyncUtilsMixin):
xml += '</SyncML>\n' xml += '</SyncML>\n'
if self.email is None: if self.email is None:
# We do not want to use email # We do not want to use email
if domain.domain_type == self.PUB: # We always reply
#if (xml_confirmation,syncml_data)!=('','') or has_status_list:
if 1:
file = open('/tmp/sync','w') file = open('/tmp/sync','w')
file.write(xml) file.write(xml)
file.close() file.close()
has_response = 1
elif domain.domain_type == self.SUB :
if self.checkAlert(remote_xml) or \
(xml_confirmation,syncml_data)!=('','') or \
has_status_list:
file = open('/tmp/sync_client','w')
has_response = 1
file.write(xml)
file.close()
else: else:
# We use email # We use email
if domain.domain_type == self.PUB: if domain.domain_type == self.PUB: # We always reply
if (xml_confirmation,syncml_data)!=('',''): #if (xml_confirmation,syncml_data)!=('','') or has_status_list:
if 1:
self.sendMail(domain.publication_url, subscriber.subscription_url, self.sendMail(domain.publication_url, subscriber.subscription_url,
domain.id, xml) domain.id, xml)
elif domain.domain_type == self.SUB: elif domain.domain_type == self.SUB:
if self.checkAlert(remote_xml) or \ if self.checkAlert(remote_xml) or \
(xml_confirmation,syncml_data)!=('',''): (xml_confirmation,syncml_data)!=('','') or \
has_status_list:
self.sendMail(domain.subscription_url, domain.publication_url, self.sendMail(domain.subscription_url, domain.publication_url,
domain.id, xml) domain.id, xml)
return has_response
...@@ -84,7 +84,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ...@@ -84,7 +84,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
</label></div> </label></div>
</td> </td>
<td align="left" valign="top"> <td align="left" valign="top">
<input type="text" name="xml_mapping" value="<dtml-var getXML_Mapping>" size="40" /> <input type="text" name="xml_mapping" value="<dtml-var getXMLMapping>" size="40" />
</td> </td>
</tr> </tr>
</table> </table>
......
...@@ -94,7 +94,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ...@@ -94,7 +94,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
</label></div> </label></div>
</td> </td>
<td align="left" valign="top"> <td align="left" valign="top">
<input type="text" name="xml_mapping" value="<dtml-var getXML_Mapping>" size="40" /> <input type="text" name="xml_mapping" value="<dtml-var getXMLMapping>" size="40" />
</td> </td>
</tr> </tr>
</table> </table>
......
...@@ -39,3 +39,5 @@ Setting up Synchronization ...@@ -39,3 +39,5 @@ Setting up Synchronization
XML Mapping : asXML XML Mapping : asXML
Then you have to go on the subscription and hit 'Sync' Then you have to go on the subscription and hit 'Sync'
\ No newline at end of file
...@@ -393,6 +393,25 @@ class TestERP5SyncML(ERP5TypeTestCase): ...@@ -393,6 +393,25 @@ class TestERP5SyncML(ERP5TypeTestCase):
# self.failUnless(person1_c.getFirstName()==self.first_name3) # self.failUnless(person1_c.getFirstName()==self.first_name3)
# self.failUnless(person1_c.getDescription()==self.description3) # self.failUnless(person1_c.getDescription()==self.description3)
def testGetConflictList(self, quiet=0):
# We will try to generate a conflict and then to get it
if not quiet:
ZopeTestCase._print('\nTest Get Conflict List ')
LOG('Testing... ',0,'testGetConflictList')
self.testFirstSynchronization(quiet=1)
# First we do only modification on server
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
person1_s = person_server._getOb(self.id1)
person1_s.setDescription(self.description2)
person_client1 = self.getPersonClient1()
person1_c = person_client1._getOb(self.id1)
person1_c.setDescription(self.description3)
self.synchronize(self.sub_id1)
#self.checkSynchronizationStateIsSynchronized()
if __name__ == '__main__': if __name__ == '__main__':
framework() framework()
else: else:
......
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