diff --git a/product/ERP5SyncML/Conduit/ERP5Conduit.py b/product/ERP5SyncML/Conduit/ERP5Conduit.py index a29a82a6e675f5309351ecba893b9f29d9f34295..221d11f281679a4e7d309a471ba079438390104a 100644 --- a/product/ERP5SyncML/Conduit/ERP5Conduit.py +++ b/product/ERP5SyncML/Conduit/ERP5Conduit.py @@ -139,7 +139,6 @@ class ERP5Conduit(XMLSyncUtilsMixin): xml = self.convertToXml(xml) if xml is None: return {'conflict_list':conflict_list, 'object':sub_object} - #LOG('addNode',0,'xml_reconstitued: %s' % str(xml)) # In the case where this new node is a object to add if xml.nodeName in self.XUPDATE_INSERT_OR_ADD and \ self.getSubObjectDepth(xml)==0: @@ -153,7 +152,6 @@ class ERP5Conduit(XMLSyncUtilsMixin): if object_id is None: object_id = self.getAttribute(xml,'id') docid = self.getObjectDocid(xml) - #LOG('addNode',0,'object_id: %s' % object_id) if object_id is not None: if sub_object is None: try: @@ -181,7 +179,6 @@ class ERP5Conduit(XMLSyncUtilsMixin): elif xml.nodeName in self.XUPDATE_INSERT_OR_ADD \ and self.getSubObjectDepth(xml)>=1: sub_object_id = self.getSubObjectId(xml) - #LOG('addNode',0,'getSubObjectModification number: %s' % sub_object_id) if previous_xml is not None and sub_object_id is not None: #LOG('addNode',0,'previous xml is not none and also sub_object_id') # Find the previous xml corresponding to this subobject @@ -242,7 +239,7 @@ class ERP5Conduit(XMLSyncUtilsMixin): sub_object = object._getOb(sub_object_id) sub_xml = self.getSubObjectXupdate(xml) conflict_list += self.deleteNode(xml=sub_xml,object=sub_object, - force=force, simulate=simulate, **kw) + force=force, simulate=simulate, **kw) except (KeyError, AttributeError, TypeError): #LOG('ERP5Conduit',0,'deleteNode, Unable to delete SubObject: %s' % str(sub_object_id)) pass @@ -385,7 +382,6 @@ class ERP5Conduit(XMLSyncUtilsMixin): #subscriber_value=data)] # not needed any more # We will now apply the argument with the method edit if args != {} and (isConflict==0 or force) and (not simulate): - #LOG('updateNode',0,'object._edit, args: %s' % str(args)) #object._edit(**args) self.editDocument(object=object,**args) # It is sometimes required to do something after an edit @@ -394,12 +390,10 @@ class ERP5Conduit(XMLSyncUtilsMixin): if keyword == 'object': # This is the case where we have to call addNode - #LOG('updateNode',0,'we will add sub-object') conflict_list += self.addNode(xml=xml, object=object, force=force, simulate=simulate, **kw)['conflict_list'] elif keyword == self.history_tag and not simulate: # This is the case where we have to call addNode - #LOG('updateNode',0,'we will add history') conflict_list += self.addNode(xml=subnode,object=object,force=force, simulate=simulate,**kw)['conflict_list'] elif keyword in (self.local_role_tag,self.local_permission_tag) and not simulate: @@ -433,8 +427,9 @@ class ERP5Conduit(XMLSyncUtilsMixin): sub_xml = self.getSubObjectXupdate(xml) #LOG('updateNode',0,'sub_xml: %s' % str(sub_xml)) # Then do the udpate - conflict_list += self.updateNode(xml=sub_xml, object=sub_object, force=force, - previous_xml=sub_previous_xml, simulate=simulate, **kw) + conflict_list += self.updateNode(xml=sub_xml, object=sub_object, + force=force, previous_xml=sub_previous_xml, + simulate=simulate, **kw) elif previous_xml is None and xml is not None and sub_object_id is not None: sub_object = None try: @@ -470,7 +465,6 @@ class ERP5Conduit(XMLSyncUtilsMixin): This lookd inside the args dictionnary and then convert any unicode string to string """ - #LOG('ERP5Conduit.getFormatedArgs',0,'args: %s' % str(args)) new_args = {} for keyword in args.keys(): data = args[keyword] @@ -646,7 +640,6 @@ class ERP5Conduit(XMLSyncUtilsMixin): 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.getAttribute(subnode,'id'): return subnode return None @@ -700,7 +693,6 @@ class ERP5Conduit(XMLSyncUtilsMixin): if xml is None: return if type(xml) in (type('a'),type(u'a')): - #LOG('Conduit.convertToXml xml',0,repr(xml)) if type(xml) is type(u'a'): xml = xml.encode('utf-8') xml = Parse(xml) @@ -981,7 +973,7 @@ class ERP5Conduit(XMLSyncUtilsMixin): force=force, simulate=simulate, **kw)['conflict_list'] elif subnode.nodeName in self.XUPDATE_DEL: conflict_list += conduit.deleteNode(xml=sub_xupdate, object=object, \ - force=force, simulate=simulate, **kw) + force=force, simulate=simulate, **kw) elif subnode.nodeName in self.XUPDATE_UPDATE: conflict_list += conduit.updateNode(xml=sub_xupdate, object=object, \ force=force, simulate=simulate, **kw) @@ -1065,27 +1057,19 @@ class ERP5Conduit(XMLSyncUtilsMixin): This is really usefull if you want to write your own Conduit. """ conflict_list = [] - #LOG('addNode, workflow_history isHistoryAdd:',0,self.isHistoryAdd(xml)) # We want to add a workflow action wf_tool = getToolByName(object,'portal_workflow') wf_id = self.getAttribute(xml,'id') if wf_id is None: # History added by xupdate wf_id = self.getHistoryIdFromSelect(xml) - #LOG('addNode, workflow_history id:',0,wf_id) - #LOG('addNode, workflow_history xml:',0,xml.toxml())#toxml isn't in 4Suite - #LOG('addNode, workflow_history xml.getElmentNodeList:',0,self.getElementNodeList(xml)) xml = self.getElementNodeList(xml)[0] - #LOG('addNode, workflow_history id:',0,wf_id) - #LOG('addNode, workflow_history xml:',0,xml) #for action in self.getWorkflowActionFromXml(xml): status = self.getStatusFromXml(xml) #LOG('addNode, status:',0,status) add_action = self.isWorkflowActionAddable(object=object, status=status,wf_tool=wf_tool, wf_id=wf_id,xml=xml) - #LOG('addNode, workflow_history add_action:',0,add_action) if add_action and not simulate: - #LOG('addNode, setting status:',0,'ok') wf_tool.setStatusOf(wf_id,object,status) # Specific CPS, try to remove duplicate lines in portal_repository._histories @@ -1191,4 +1175,3 @@ class ERP5Conduit(XMLSyncUtilsMixin): xml_string = buf.getvalue() buf.close() return xml_string - diff --git a/product/ERP5SyncML/Publication.py b/product/ERP5SyncML/Publication.py index 9cb4a05b82dcd064a0c6f3f57aed2a76711bad9b..f9b9908a36bf7ce14895d202dde74826d0382ccf 100644 --- a/product/ERP5SyncML/Publication.py +++ b/product/ERP5SyncML/Publication.py @@ -152,7 +152,7 @@ class Publication(Subscription): constructors = (addPublication,) # Constructor - def __init__(self, id, title, publication_url, destination_path, + def __init__(self, id, title, publication_url, destination_path, source_uri, query, xml_mapping, conduit, gpg_key, id_generator, gid_generator, media_type, auth_required=False, authentication_format='', authentication_type=''): @@ -162,6 +162,7 @@ class Publication(Subscription): self.id = id self.publication_url = publication_url self.destination_path = destination_path + self.setSourceURI(source_uri) self.setQuery(query) self.xml_mapping = xml_mapping #self.list_subscribers = PersistentMapping() diff --git a/product/ERP5SyncML/PublicationSynchronization.py b/product/ERP5SyncML/PublicationSynchronization.py index d6bbfe401e0c1e89da0d382dfcb9fcbf940e8e6b..ec74e62a322518af465d4a7824c6e402c0efc091 100644 --- a/product/ERP5SyncML/PublicationSynchronization.py +++ b/product/ERP5SyncML/PublicationSynchronization.py @@ -110,7 +110,7 @@ class PublicationSynchronization(XMLSyncUtils): if authentication_format == publication.getAuthenticationFormat(): if authentication_type == publication.getAuthenticationType(): decoded = subscriber.decode(authentication_format, data) - if decoded not in ('', None) and decoded.__contains__(':'): + if decoded not in ('', None) and ':' in decoded: (login, password) = decoded.split(':') uf = self.getPortalObject().acl_users for plugin_name, plugin in uf._getOb('plugins').listPlugins( @@ -124,6 +124,8 @@ class PublicationSynchronization(XMLSyncUtils): newSecurityManager(None, user) subscriber.setUser(login) break + else: + auth_code=self.UNAUTHORIZED #in all others cases, the auth_code is set to UNAUTHORIZED @@ -148,6 +150,13 @@ class PublicationSynchronization(XMLSyncUtils): subscriber.getSubscriptionUrl(), publication.getPublicationUrl(), subscriber.getLastAnchor(), subscriber.getNextAnchor())) cmd_id += 1 + else: + # chal message + xml(self.SyncMLChal(cmd_id, "SyncHdr", + publication.getPublicationUrl(), subscriber.getSubscriptionUrl(), + publication.getAuthenticationFormat(), + publication.getAuthenticationType(), self.AUTH_REQUIRED)) + cmd_id += 1 xml(' </SyncBody>\n') xml('</SyncML>\n') xml_a = ''.join(xml_list) diff --git a/product/ERP5SyncML/Subscription.py b/product/ERP5SyncML/Subscription.py index c49961cbbdac3dcec16a5023263df4bc7cf6cccd..c9eafcab58271fbab9c25845d128a532ba42d216 100644 --- a/product/ERP5SyncML/Subscription.py +++ b/product/ERP5SyncML/Subscription.py @@ -42,9 +42,10 @@ from zLOG import LOG import md5 try: - from base64 import b64encode, b64decode + from base64 import b64encode, b64decode, b16encode, b16decode except ImportError: - from base64 import encodestring as b64encode, decodestring as b64decode + from base64 import encodestring as b64encode, decodestring as b64decode, \ + encodestring as b16encode, decodestring as b16decode #class Conflict(SyncCode, Implicit): class Conflict(SyncCode, Base): @@ -250,13 +251,16 @@ class Signature(Folder,SyncCode): isPortalContent = 0 # Make sure RAD generated accessors at the class level # Constructor - def __init__(self,gid=None, id=None, status=None, xml_string=None,object=None): - self.setGid(gid) + def __init__(self, id=None, rid=None, status=None, xml_string=None, + object=None): if object is not None: self.setPath(object.getPhysicalPath()) + self.setObjectId(object.getId()) else: self.setPath(None) self.setId(id) + self.setGid(id) + self.setRid(rid) self.status = status self.setXML(xml_string) self.partial_xml = None @@ -280,7 +284,7 @@ class Signature(Folder,SyncCode): if temp_xml is not None: # This happens when we have sent the xml # and we just get the confirmation - self.setXML(self.getTempXML()) + self.setXML(temp_xml) self.setTempXML(None) self.setPartialXML(None) self.setSubscriberXupdate(None) @@ -367,7 +371,7 @@ class Signature(Folder,SyncCode): def getXML(self): """ - set the XML corresponding to the object + get the XML corresponding to the object """ xml = getattr(self,'xml',None) if xml == '': @@ -437,18 +441,22 @@ class Signature(Folder,SyncCode): """ set the rid """ + if rid is type(u'a'): + rid = rid.encode('utf-8') self.rid = rid def getRid(self): """ get the rid """ - return self.rid + return getattr(self, 'rid', None) def setId(self, id): """ set the id """ + if id is type(u'a'): + id = id.encode('utf-8') self.id = id def getId(self): @@ -459,16 +467,32 @@ class Signature(Folder,SyncCode): def setGid(self, gid): """ - set the id + set the gid """ + if gid is type(u'a'): + gid = gid.encode('utf-8') self.gid = gid def getGid(self): """ - get the id + get the gid """ return self.gid + def setObjectId(self, id): + """ + set the id of the object associated to this signature + """ + if id is type(u'a'): + id = id.encode('utf-8') + self.object_id = id + + def getObjectId(self): + """ + get the id of the object associated to this signature + """ + return getattr(self, 'object_id', None) + def setPartialXML(self, xml): """ Set the partial string we will have to @@ -545,7 +569,7 @@ class Signature(Folder,SyncCode): """ Returns the object corresponding to this signature """ - return self.getParentValue().getObjectFromGid(self.getGid()) + return self.getParentValue().getObjectIdGid(self.getObjectId()) def checkSynchronizationNeeded(self, object): """ @@ -643,8 +667,9 @@ class Subscription(Folder, SyncCode): # Constructor def __init__(self, id, title, publication_url, subscription_url, - destination_path, query, xml_mapping, conduit, gpg_key, id_generator, - gid_generator, media_type, login, password): + destination_path, source_uri, target_uri, query, xml_mapping, + conduit, gpg_key, id_generator, gid_generator, media_type, login, + password): """ We need to create a dictionnary of signatures of documents which belong to the synchronisation @@ -654,6 +679,8 @@ class Subscription(Folder, SyncCode): self.publication_url = (publication_url) self.subscription_url = str(subscription_url) self.destination_path = str(destination_path) + self.setSourceURI(source_uri) + self.setTargetURI(target_uri) self.setQuery(query) self.setXMLMapping(xml_mapping) self.anchor = None @@ -685,15 +712,31 @@ class Subscription(Folder, SyncCode): setter for title """ self.title = value + + def setSourceURI(self, value): + """ + setter for source_uri + """ + self.source_uri = value + + def getSourceURI(self): + """ + getter for the source_uri (the local path of the subscription) + """ + return getattr(self, 'source_uri', None) + + def setTargetURI(self, value): + """ + setter for target_uri + """ + self.target_uri = value - # Accessors - def getRemoteId(self, id, path=None): + def getTargetURI(self): """ - Returns the remote id from a know local id - Returns None if... - path allows to implement recursive sync + getter for the target_uri (the distant Publication we want to synchronize + with) """ - pass + return getattr(self, 'target_uri', None) def getSynchronizationType(self, default=None): """ @@ -749,7 +792,7 @@ class Subscription(Folder, SyncCode): self.last_message_id = message_id return True - def initLastMessageId(self, last_message_id=None): + def initLastMessageId(self, last_message_id=0): """ set the last message id to 0 """ @@ -767,12 +810,11 @@ class Subscription(Folder, SyncCode): """ self.last_sent_message = xml - def getLocalId(self, rid, path=None): + def getDomainType(self): """ - Returns the local id from a know remote id - Returns None if... + return the ID """ - pass + return self.domain_type def getId(self): """ @@ -780,12 +822,6 @@ class Subscription(Folder, SyncCode): """ return self.id - def getDomainType(self): - """ - return the ID - """ - return self.domain_type - def setId(self, id): """ set the ID @@ -934,13 +970,13 @@ class Subscription(Folder, SyncCode): """ return the format of authentication """ - return getattr(self, 'authentication_format', '') + return getattr(self, 'authentication_format', 'b64') def getAuthenticationType(self): """ return the type of authentication """ - return getattr(self, 'authentication_type', '') + return getattr(self, 'authentication_type', 'syncml:auth-basic') def setAuthenticationFormat(self, authentication_format): """ @@ -969,7 +1005,7 @@ class Subscription(Folder, SyncCode): # It might be a script python generator = getattr(object,gid_gen) o_gid = generator() # XXX - used to be o_gid = generator(object=object) which is redundant - #LOG('getGidFromObject',0,'o_gid: %s' % repr(o_gid)) + o_gid = b16encode(o_gid) return o_gid def getObjectFromGid(self, gid): @@ -977,16 +1013,13 @@ class Subscription(Folder, SyncCode): This tries to get the object with the given gid This uses the query if it exist """ - signature = self.getSignature(gid) + signature = self.getSignatureFromGid(gid) # First look if we do already have the mapping between # the id and the gid object_list = self.getObjectList() destination = self.getDestination() - # LOG('getObjectFromGid', 0, 'self: %s' % self) - # LOG('getObjectFromGid',0,'gid: %s' % repr(gid)) - # LOG('getObjectFromGid oject_list',0,object_list) - if signature is not None and signature.getId() is not None: - o_id = signature.getId() + if signature is not None and signature.getObjectId() is not None: + o_id = signature.getObjectId() o = None try: o = destination._getOb(o_id) @@ -998,7 +1031,18 @@ class Subscription(Folder, SyncCode): o_gid = self.getGidFromObject(o) if o_gid == gid: return o - # LOG('getObjectFromGid',0,'returning None') + LOG('getObjectFromGid',0,'returning None') + return None + + def getObjectFromId(self, id): + """ + return the object corresponding to the id + """ + object_list = self.getObjectList() + #XXX very slow with lot of objects + for object in object_list: + if object.getId() == id: + return object return None # def setOneWaySyncFromServer(self,value): @@ -1195,28 +1239,83 @@ class Subscription(Folder, SyncCode): """ add a Signature to the subscription """ - if signature.getGid() in self.objectIds(): - self._delObject(signature.getGid()) - self._setObject(signature.getGid(), aq_base(signature) ) + if signature.getGid() in self.getGidList(): + self.delSignature(signature.getGid()) + self._setObject(signature.getGid(), aq_base(signature)) def delSignature(self, gid): """ - add a Signature to the subscription + del a Signature of the subscription """ #del self.signatures[gid] self._delObject(gid) - def getSignature(self, gid): + def getSignatureFromObjectId(self, id): """ - add a Signature to the subscription + return the signature corresponding to the gid + """ + o = None + # XXX very slow + for signature in self.getSignatureList(): + if id == signature.getObjectId(): + o = signature + break + return o + + def getSignatureFromGid(self, gid): + """ + return the signature corresponding to the gid + """ + o = None + # XXX very slow + for signature in self.getSignatureList(): + if gid == signature.getGid(): + o = signature + break + return o + + def getSignatureFromRid(self, rid): + """ + return the signature corresponding to the rid """ o = None - if gid in self.objectIds(): - o = self._getOb(gid) - #if o is not None: - # return o.__of__(self) + # XXX very slow + for signature in self.getSignatureList(): + if rid == signature.getRid(): + o = signature + break return o + def getObjectIdList(self): + """ + Returns the list of gids from signature + """ + object_id_list = [] + for signature in self.getSignatureList(): + if signature.getObjectId() is not None: + object_id_list.append(signature.getObjectId()) + return object_id_list + + def getGidList(self): + """ + Returns the list of gids from signature + """ + gid_list = [] + for signature in self.getSignatureList(): + if signature.getGid() is not None: + gid_list.append(signature.getGid()) + return gid_list + + def getRidList(self): + """ + Returns the list of rids from signature + """ + rid_list = [] + for signature in self.getSignatureList(): + if signature.getRid() is not None: + rid_list.append(signature.getRid()) + return rid_list + def getSignatureList(self): """ add a Signature to the subscription @@ -1238,12 +1337,6 @@ class Subscription(Folder, SyncCode): for id in self.objectIds(): self._delObject(id) - def getGidList(self): - """ - Returns the list of ids from signature - """ - return self.objectIds() - def getConflictList(self): """ Return the list of all conflicts from all signatures @@ -1280,20 +1373,6 @@ class Subscription(Folder, SyncCode): new_list.append(o) self.setRemainingObjectPathList(new_list) -# def getCurrentObject(self): -# """ -# When we send some partial data, then we should -# always synchronize the same object until it is finished -# """ -# getattr(self,'current_object',None) -# -# def setCurrentObject(self,object): -# """ -# When we send some partial data, then we should -# always synchronize the same object until it is finished -# """ -# setattr(self,'current_object',object) - def startSynchronization(self): """ Set the status of every object as NOT_SYNCHRONIZED diff --git a/product/ERP5SyncML/SubscriptionSynchronization.py b/product/ERP5SyncML/SubscriptionSynchronization.py index 62183ef6d947cc5fb9a1d137ba46177e595f6244..bf03af1579b5649929d13d5d9c7fa2594a88705b 100644 --- a/product/ERP5SyncML/SubscriptionSynchronization.py +++ b/product/ERP5SyncML/SubscriptionSynchronization.py @@ -49,7 +49,7 @@ class SubscriptionSynchronization(XMLSyncUtils): # syncml header xml(self.SyncMLHeader(subscription.incrementSessionId(), subscription.incrementMessageId(), subscription.getPublicationUrl(), - subscription.getSubscriptionUrl())) + subscription.getSubscriptionUrl(), source_name=subscription.getLogin())) # syncml body xml(' <SyncBody>\n') @@ -59,16 +59,15 @@ class SubscriptionSynchronization(XMLSyncUtils): # alert message xml(self.SyncMLAlert(cmd_id, subscription.getSynchronizationType(), - subscription.getPublicationUrl(), - subscription.getDestinationPath(), + subscription.getTargetURI(), + subscription.getSourceURI(), subscription.getLastAnchor(), subscription.getNextAnchor())) cmd_id += 1 - - xml(' <Put>\n') - xml(' <CmdID>%s</CmdID>\n' % cmd_id) - cmd_id += 1 - xml(' </Put>\n') + syncml_put = self.SyncMLPut(cmd_id, subscription) + if syncml_put not in ('', None): + xml(syncml_put) + cmd_id += 1 xml(' </SyncBody>\n') xml('</SyncML>\n') xml_a = ''.join(xml_list) @@ -79,20 +78,17 @@ class SubscriptionSynchronization(XMLSyncUtils): return {'has_response':1,'xml':xml_a} - def SubSync(self, id, msg=None, RESPONSE=None): + def SubSync(self, subscription, msg=None, RESPONSE=None): """ This is the synchronization method for the client """ - #LOG('SubSync',0,'starting... id: %s' % str(id)) - #LOG('SubSync',0,'starting... msg: %s' % str(msg)) response = None #check if subsync replies to this messages - subscription = self.getSubscription(id) if msg==None and (subscription.getSubscriptionUrl()).find('file')>=0: - msg = self.readResponse(sync_id=id, + msg = self.readResponse(sync_id=subscription.getSourceURI(), from_url=subscription.getSubscriptionUrl()) if msg==None: - response = self.SubSyncInit(self.getSubscription(id)) + response = self.SubSyncInit(subscription) else: xml_client = msg if isinstance(xml_client, str) or isinstance(xml_client, unicode): @@ -105,21 +101,24 @@ class SubscriptionSynchronization(XMLSyncUtils): if status_code == self.AUTH_REQUIRED: if self.checkChal(xml_client): authentication_format, authentication_type = self.getChal(xml_client) - subscription.setAuthenticationFormat(authentication_format) - subscription.setAuthenticationType(authentication_type) + #LOG('auth_required :',0, 'format:%s, type:%s' % (authentication_format, authentication_type)) + if authentication_format is not None and \ + authentication_type is not None: + subscription.setAuthenticationFormat(authentication_format) + subscription.setAuthenticationType(authentication_type) else: raise ValueError, "Sorry, the server chalenge for an \ authentication, but the authentication format is not find" #LOG('readResponse', 0, 'Authentication required') - response = self.SubSyncCred(id, xml_client) + response = self.SubSyncCred(subscription, xml_client) elif status_code == self.UNAUTHORIZED: - #LOG('readResponse', 0, 'Bad authentication') + LOG('readResponse', 0, 'Bad authentication') return {'has_response':0,'xml':xml_client} else: - response = self.SubSyncModif(self.getSubscription(id), xml_client) + response = self.SubSyncModif(subscription, xml_client) else: - response = self.SubSyncModif(self.getSubscription(id), xml_client) + response = self.SubSyncModif(subscription, xml_client) if RESPONSE is not None: @@ -127,43 +126,43 @@ class SubscriptionSynchronization(XMLSyncUtils): else: return response - def SubSyncCred (self, id, msg=None, RESPONSE=None): + def SubSyncCred (self, subscription, msg=None, RESPONSE=None): """ This method send crendentials """ - - #LOG('SubSyncCred',0,'starting... id: %s' % str(id)) - #LOG('SubSyncCred',0,'starting... msg: %s' % str(msg)) - cmd_id = 1 # specifies a SyncML message-unique command identifier - subscription = self.getSubscription(id) xml_list = [] xml = xml_list.append xml('<SyncML>\n') # syncml header data = "%s:%s" % (subscription.getLogin(), subscription.getPassword()) data=subscription.encode(subscription.getAuthenticationFormat(), data) - xml(self.SyncMLHeader(subscription.getSessionId(), - subscription.incrementMessageId(), subscription.getPublicationUrl(), - subscription.getSubscriptionUrl(), dataCred=data, + xml(self.SyncMLHeader( + subscription.incrementSessionId(), + subscription.incrementMessageId(), + subscription.getPublicationUrl(), + subscription.getSubscriptionUrl(), + source_name=subscription.getLogin(), + dataCred=data, authentication_format=subscription.getAuthenticationFormat(), authentication_type=subscription.getAuthenticationType())) # syncml body xml(' <SyncBody>\n') + # We have to set every object as NOT_SYNCHRONIZED + subscription.startSynchronization() + # alert message xml(self.SyncMLAlert(cmd_id, subscription.getSynchronizationType(), - subscription.getPublicationUrl(), - subscription.getDestinationPath(), + subscription.getTargetURI(), + subscription.getSourceURI(), subscription.getLastAnchor(), subscription.getNextAnchor())) cmd_id += 1 - - xml(' <Put>\n') - xml(' <CmdID>%s</CmdID>\n' % cmd_id) + xml(self.SyncMLPut(cmd_id, subscription)) cmd_id += 1 - xml(' </Put>\n') + xml(' <Final/>\n') xml(' </SyncBody>\n') xml('</SyncML>\n') xml_a = ''.join(xml_list) diff --git a/product/ERP5SyncML/SyncCode.py b/product/ERP5SyncML/SyncCode.py index 4a4f8edd70f14f344fa3440a29f18523392eef70..77d98a02c5f7e5978b6a9f4a129a90300cc7ee06 100644 --- a/product/ERP5SyncML/SyncCode.py +++ b/product/ERP5SyncML/SyncCode.py @@ -41,6 +41,8 @@ class SyncCode(Persistent): # SyncML Status Codes SUCCESS = 200 + ITEM_ADDED = 201 + CHUNK_OK = 214 CONFLICT = 409 # A conflict is detected CONFLICT_MERGE = 207 # We have merged the two versions, sending diff --git a/product/ERP5SyncML/SynchronizationTool.py b/product/ERP5SyncML/SynchronizationTool.py index fa4b22febfe22c306f5c15183f8f83ff4230af93..d87d439a1183f4434322717f66563ec4efdf558b 100644 --- a/product/ERP5SyncML/SynchronizationTool.py +++ b/product/ERP5SyncML/SynchronizationTool.py @@ -161,7 +161,7 @@ class SynchronizationTool( SubscriptionSynchronization, security.declareProtected(Permissions.ModifyPortalContent, 'manage_addPublication') def manage_addPublication(self, title, publication_url, destination_path, - query, xml_mapping, conduit, gpg_key, + source_uri, query, xml_mapping, conduit, gpg_key, synchronization_id_generator=None, gid_generator=None, media_type=None, auth_required=0, authentication_format='', authentication_type='', RESPONSE=None): @@ -174,7 +174,7 @@ class SynchronizationTool( SubscriptionSynchronization, folder = self.getObjectContainer() new_id = self.getPublicationIdFromTitle(title) pub = Publication(new_id, title, publication_url, destination_path, - query, xml_mapping, conduit, gpg_key, + source_uri, query, xml_mapping, conduit, gpg_key, synchronization_id_generator, gid_generator, media_type, auth_required, authentication_format, authentication_type) folder._setObject( new_id, pub ) @@ -187,7 +187,8 @@ class SynchronizationTool( SubscriptionSynchronization, security.declareProtected(Permissions.ModifyPortalContent, 'manage_addSubscription') def manage_addSubscription(self, title, publication_url, subscription_url, - destination_path, query, xml_mapping, conduit, gpg_key, + destination_path, source_uri, target_uri, query, + xml_mapping, conduit, gpg_key, synchronization_id_generator=None, gid_generator=None, media_type=None, login=None, password=None, RESPONSE=None): @@ -201,7 +202,8 @@ class SynchronizationTool( SubscriptionSynchronization, folder = self.getObjectContainer() new_id = self.getSubscriptionIdFromTitle(title) sub = Subscription(new_id, title, publication_url, subscription_url, - destination_path, query, xml_mapping, conduit, gpg_key, + destination_path, source_uri, target_uri, query, + xml_mapping, conduit, gpg_key, synchronization_id_generator, gid_generator, media_type, login, password) folder._setObject( new_id, sub ) @@ -214,7 +216,7 @@ class SynchronizationTool( SubscriptionSynchronization, security.declareProtected(Permissions.ModifyPortalContent, 'manage_editPublication') def manage_editPublication(self, title, publication_url, destination_path, - query, xml_mapping, conduit, gpg_key, + source_uri, query, xml_mapping, conduit, gpg_key, synchronization_id_generator, gid_generator, media_type=None, auth_required=0, authentication_format='', authentication_type='', @@ -226,6 +228,7 @@ class SynchronizationTool( SubscriptionSynchronization, pub.setTitle(title) pub.setPublicationUrl(publication_url) pub.setDestinationPath(destination_path) + pub.setSourceURI(source_uri) pub.setQuery(query) pub.setConduit(conduit) pub.setXMLMapping(xml_mapping) @@ -243,9 +246,9 @@ class SynchronizationTool( SubscriptionSynchronization, security.declareProtected(Permissions.ModifyPortalContent, 'manage_editSubscription') def manage_editSubscription(self, title, publication_url, subscription_url, - destination_path, query, xml_mapping, conduit, gpg_key, - synchronization_id_generator, gid_generator, media_type=None,login='', - password='', RESPONSE=None): + destination_path, source_uri, target_uri, query, xml_mapping, conduit, + gpg_key, synchronization_id_generator, gid_generator, media_type=None, + login='', password='', RESPONSE=None): """ modify a subscription """ @@ -253,6 +256,8 @@ class SynchronizationTool( SubscriptionSynchronization, sub.setTitle(title) sub.setPublicationUrl(publication_url) sub.setDestinationPath(destination_path) + sub.setSourceURI(source_uri) + sub.setTargetURI(target_uri) sub.setQuery(query) sub.setConduit(conduit) sub.setXMLMapping(xml_mapping) @@ -319,7 +324,7 @@ class SynchronizationTool( SubscriptionSynchronization, """ reset a subscription """ - self.SubSync(title) + self.SubSync(self.getSubscription(title)) if RESPONSE is not None: RESPONSE.redirect('manageSubscriptions') @@ -371,7 +376,7 @@ class SynchronizationTool( SubscriptionSynchronization, def getSubscription(self, title): """ - Returns the subscription with this id + Returns the subscription with this title """ for s in self.getSubscriptionList(): if s.getTitle() == title: @@ -489,7 +494,7 @@ class SynchronizationTool( SubscriptionSynchronization, subscriber_list = [domain] #LOG('getSynchronizationState, subscriber_list:',0,subscriber_list) for subscriber in subscriber_list: - signature = subscriber.getSignature(o_id) + signature = subscriber.getSignatureFromObjectId(o_id) if signature is not None: state = signature.getStatus() #LOG('getSynchronizationState:',0,'sub.dest :%s, state: %s' % \ @@ -514,7 +519,7 @@ class SynchronizationTool( SubscriptionSynchronization, subscriber = conflict.getSubscriber() # get the signature: #LOG('p_sync.applyPublisherValue, subscriber: ',0,subscriber) - signature = subscriber.getSignature(object.getId()) # XXX may be change for rid + signature = subscriber.getSignatureFromObjectId(object.getId()) # XXX may be change for rid copy_path = conflict.getCopyPath() #LOG('p_sync.applyPublisherValue, copy_path: ',0,copy_path) signature.delConflict(conflict) @@ -690,7 +695,7 @@ class SynchronizationTool( SubscriptionSynchronization, subscriber = conflict.getSubscriber() # get the signature: #LOG('p_sync.setRemoteObject, subscriber: ',0,subscriber) - signature = subscriber.getSignature(object.getId()) # XXX may be change for rid + signature = subscriber.getSignatureFromObjectId(object.getId()) # XXX may be change for rid # Import the conduit and get it conduit_name = subscriber.getConduit() conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]), @@ -885,12 +890,22 @@ class SynchronizationTool( SubscriptionSynchronization, opener = urllib2.build_opener(proxy_handler, proxy_auth_handler, auth_handler, urllib2.HTTPHandler) urllib2.install_opener(opener) - to_encode = {'text':xml,'sync_id':sync_id} - encoded = urllib.urlencode(to_encode) + to_encode = {} + head = '<?xml version="1.0" encoding="UTF-8"?>' + to_encode['text'] = head + xml + to_encode['sync_id'] = sync_id + headers = {'Content-type': 'application/vnd.syncml+xml'} + + #XXX bad hack for synchronization with erp5 if to_url.find('readResponse')<0: to_url = to_url + '/portal_synchronizations/readResponse' - request = urllib2.Request(url=to_url,data=encoded) - #result = urllib2.urlopen(request).read() + encoded = urllib.urlencode(to_encode) + data=encoded + request = urllib2.Request(url=to_url, data=data) + +#XXX only to synchronize with other server than erp5 (must be improved): +# data=head+xml +# request = urllib2.Request(to_url, data, headers) try: result = urllib2.urlopen(request).read() except socket.error, msg: @@ -898,10 +913,12 @@ class SynchronizationTool( SubscriptionSynchronization, sync_id=sync_id, xml=xml, domain=domain) LOG('sendHttpResponse, socket ERROR:',0,msg) return + except urllib2.URLError, msg: + LOG("sendHttpResponse, can't open url %s :" % to_url, 0, msg) + return + - #LOG('sendHttpResponse, before result, domain:',0,domain) - #LOG('sendHttpResponse, result:',0,result) if domain is not None: if domain.domain_type == self.SUB: gpg_key = domain.getGPGKey() @@ -929,7 +946,7 @@ class SynchronizationTool( SubscriptionSynchronization, if len(message_list) == 0: for subscription in self.getSubscriptionList(): #LOG('sync, subcription:',0,subscription) - self.activate(activity='RAMQueue').SubSync(subscription.getTitle()) + self.activate(activity='RAMQueue').SubSync(subscription) security.declarePublic('readResponse') def readResponse(self, text=None, sync_id=None, to_url=None, from_url=None): @@ -937,9 +954,6 @@ class SynchronizationTool( SubscriptionSynchronization, We will look at the url and we will see if we need to send mail, http response, or just copy to a file. """ - #LOG('readResponse, ',0,'starting') - #LOG('readResponse, self.getPhysicalPath: ',0,self.getPhysicalPath()) - #LOG('readResponse, sync_id: ',0,sync_id) # Login as a manager to make sure we can create objects uf = self.acl_users user = uf.getUserById('syncml').__of__(uf) @@ -994,9 +1008,9 @@ class SynchronizationTool( SubscriptionSynchronization, for subscription in self.getSubscriptionList(): if subscription.getSubscriptionUrl()==url and \ subscription.getTitle()==sync_id: - result = self.activate(activity='RAMQueue').SubSync(sync_id, - text) - #result = self.SubSync(sync_id,xml) + result = self.activate(activity='RAMQueue').SubSync(\ + self.getSubscription(sync_id), text) + #result = self.SubSync(self.getSubscription(sync_id),xml) # we use from only if we have a file elif isinstance(from_url, str): @@ -1041,24 +1055,4 @@ class SynchronizationTool( SubscriptionSynchronization, conduit_object = getattr(conduit_module, conduit)() return conduit_object.addNode(**kw) -# security.declarePrivate('notify_sync') -# def notify_sync(self, event_type, object, infos): -# """Notification from the event service. -# -# # XXX very specific to cps -# -# Called when an object is added/deleted/modified. -# Update the date of sync -# """ -# from Products.CPSCore.utils import _isinstance -# from Products.CPSCore.ProxyBase import ProxyBase -# -# if event_type in ('sys_modify_object', -# 'modify_object'): -# if not(_isinstance(object, ProxyBase)): -# repotool = getToolByName(self, 'portal_repository') -# if repotool.isObjectInRepository(object): -# object_id = object.getId() - - InitializeClass( SynchronizationTool ) diff --git a/product/ERP5SyncML/XMLSyncUtils.py b/product/ERP5SyncML/XMLSyncUtils.py index 48ecb5c8682b98e6516bdc18e12258567803aa95..913c08115c6d0248165a23a6d14a9d84af3cabe1 100644 --- a/product/ERP5SyncML/XMLSyncUtils.py +++ b/product/ERP5SyncML/XMLSyncUtils.py @@ -30,7 +30,7 @@ import smtplib from Products.ERP5SyncML.SyncCode import SyncCode from Products.ERP5SyncML.Subscription import Signature from DateTime import DateTime -from cStringIO import StringIO +from StringIO import StringIO from xml.dom.ext import PrettyPrint import random from zLOG import LOG @@ -50,6 +50,11 @@ except ImportError: def __init__(self, *args, **kw): raise ImportError, "Sorry, it was not possible to import Ft library" +try: + from base64 import b16encode, b16decode +except ImportError: + from base64 import encodestring as b16encode, decodestring as b16decode + class XMLSyncUtilsMixin(SyncCode): def SyncMLHeader(self, session_id, msg_id, target, source, target_name=None, @@ -79,8 +84,8 @@ class XMLSyncUtilsMixin(SyncCode): if dataCred not in (None, ''): xml(' <Cred>\n') xml(' <Meta>\n') - xml(' <Format>%s</Format>\n' % authentication_format) - xml(' <Type>%s</Type>\n' % authentication_type) + xml(" <Format xmlns='syncml:metinf'>%s</Format>\n" % authentication_format) + xml(" <Type xmlns='syncml:metinf'>%s</Type>\n" % authentication_type) xml(' </Meta>\n') xml(' <Data>%s</Data>\n' % dataCred) xml(' </Cred>\n') @@ -142,18 +147,37 @@ class XMLSyncUtilsMixin(SyncCode): xml_a = ''.join(xml_list) return xml_a - def SyncMLConfirmation(self, cmd_id, target_ref, sync_code, cmd): + def SyncMLConfirmation(self, cmd_id=None, target_ref=None, cmd=None, + sync_code=None, msg_ref=None, cmd_ref=None, source_ref=None, + remote_xml=None): """ This is used in order to confirm that an object was correctly synchronized """ + if remote_xml is not None : + msg_ref=remote_xml.xpath("string(//MsgID)").encode('utf-8') + cmd_ref=remote_xml.xpath("string(.//CmdID)").encode('utf-8') + target_ref=remote_xml.xpath("string(.//Target/LocURI)").encode('utf-8') + source_ref=remote_xml.xpath("string(.//Source/LocURI)").encode('utf-8') + xml_list = [] xml = xml_list.append xml(' <Status>\n') - xml(' <CmdID>%s</CmdID>\n' % cmd_id) - xml(' <TargetRef>%s</TargetRef>\n' % target_ref) - xml(' <Cmd>%s</Cmd>\n' % cmd) - xml(' <Data>%s</Data>\n' % sync_code) + #here there is a lot of test to keep compatibility with older call + if cmd_id not in (None,'') : + xml(' <CmdID>%s</CmdID>\n' % cmd_id) + if msg_ref not in (None,''): + xml(' <MsgRef>%s</MsgRef>\n' % msg_ref) + if cmd_ref not in (None,''): + xml(' <CmdRef>%s</CmdRef>\n' %cmd_ref) + if cmd not in (None,''): + xml(' <Cmd>%s</Cmd>\n' % cmd) + if target_ref not in (None,''): + xml(' <TargetRef>%s</TargetRef>\n' % target_ref) + if source_ref not in (None,''): + xml(' <SourceRef>%s</SourceRef>\n' % source_ref) + if sync_code not in (None,''): + xml(' <Data>%s</Data>\n' % sync_code) xml(' </Status>\n') xml_a = ''.join(xml_list) return xml_a @@ -172,8 +196,8 @@ class XMLSyncUtilsMixin(SyncCode): xml(' <SourceRef>%s</SourceRef>\n' % source_ref) xml(' <Chal>\n') xml(' <Meta>\n') - xml(' <Format>%s</Format>\n' % auth_format) - xml(' <Type>%s</Type>\n' % auth_type) + xml(" <Format xmlns='syncml:metinf'>%s</Format>\n" % auth_format) + xml(" <Type xmlns='syncml:metinf'>%s</Type>\n" % auth_type) xml(' </Meta>\n') xml(' </Chal>\n') xml(' <Data>%s</Data>\n' % str(data_code)) @@ -181,6 +205,87 @@ class XMLSyncUtilsMixin(SyncCode): xml_a = ''.join(xml_list) return xml_a + def SyncMLPut(self, cmd_id, subscription): + """ + this is used to inform the server of the CTType version supported + """ + from Products.ERP5SyncML import Conduit + # Import the conduit and get it + conduit_name = subscription.getConduit() + if conduit_name.startswith('Products'): + path = conduit_name + conduit_name = conduit_name.split('.')[-1] + conduit_module = __import__(path, globals(), locals(), ['']) + conduit = getattr(conduit_module, conduit_name)() + else: + conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]), + globals(), locals(), ['']) + conduit = getattr(conduit_module, conduit_name)() + #if the conduit support the SyncMLPut : + if hasattr(conduit, 'getCapabilitiesCTType') and \ + hasattr(conduit, 'getCapabilitiesVerCTList') and \ + hasattr(conduit, 'getPreferedCapabilitieVerCT'): + xml_list = [] + xml = xml_list.append + if conduit.getCapabilitiesVerCTList() not in ([], None): + xml(' <Put>\n') + xml(' <CmdID>%s</CmdID>\n' % cmd_id) + xml(' <Meta>\n') + xml(' <Type>application/vnd.syncml-devinf+xml</Type>\n'); + xml(' </Meta>\n') + xml(' <Item>\n') + xml(' <Source>\n') + xml(' <LocURI>./devinf11</LocURI>\n') + xml(' </Source>\n') + xml(' <Data>\n') + xml(' <DevInf>\n') + xml(' <VerDTD>1.1</VerDTD>\n') + xml(' <Man>Fabien MORIN</Man>\n') + xml(' <Mod>ERP5SyncML</Mod>\n') + xml(' <OEM>Open Source</OEM>\n') + xml(' <SwV>0.1</SwV>\n') + xml(' <DevID>%s</DevID>\n' % subscription.getSubscriptionUrl()) + xml(' <DevTyp>workstation</DevTyp>\n') + xml(' <UTC/>\n') + xml(' <DataStore>\n') + xml(' <SourceRef>%s</SourceRef>\n' % subscription.getSourceURI()) + xml(' <Rx-Pref>\n') + xml(' <CTType>%s</CTType>\n' % conduit.getCapabilitiesCTType()) + xml(' <VerCT>%s</VerCT>\n' % conduit.getPreferedCapabilitieVerCT()) + xml(' </Rx-Pref>\n') + for rx_version in conduit.getCapabilitiesVerCTList(): + xml(' <Rx>\n') + xml(' <CTType>%s</CTType>\n' % conduit.getCapabilitiesCTType()) + xml(' <VerCT>%s</VerCT>\n' % rx_version) + xml(' </Rx>\n') + + xml(' <Tx-Pref>\n') + xml(' <CTType>%s</CTType>\n' % conduit.getCapabilitiesCTType()) + xml(' <VerCT>%s</VerCT>\n' % conduit.getPreferedCapabilitieVerCT()) + xml(' </Tx-Pref>\n') + for tx_version in conduit.getCapabilitiesVerCTList(): + xml(' <Tx>\n') + xml(' <CTType>%s</CTType>\n' % conduit.getCapabilitiesCTType()) + xml(' <VerCT>%s</VerCT>\n' % rx_version) + xml(' </Tx>\n') + + xml(' <SyncCap>\n') + xml(' <SyncType>2</SyncType>\n') + xml(' <SyncType>1</SyncType>\n') + xml(' <SyncType>4</SyncType>\n') + xml(' <SyncType>6</SyncType>\n') + xml(' </SyncCap>\n') + + xml(' </DataStore>\n') + xml(' </DevInf>\n') + xml(' </Data>\n') + xml(' </Item>\n') + xml(' </Put>\n') + xml_a = ''.join(xml_list) + return xml_a + return '' + + def sendMail(self, fromaddr, toaddr, id_sync, msg): """ Send a message via email @@ -213,9 +318,14 @@ class XMLSyncUtilsMixin(SyncCode): xml(' <Source>\n') xml(' <LocURI>%s</LocURI>\n' % gid) xml(' </Source>\n') - xml(' <Data>\n') - xml(xml_string) - xml(' </Data>\n') + if media_type == self.MEDIA_TYPE['TEXT_XML']: + xml(' <Data>') + xml(xml_string) + xml('</Data>\n') + else: + xml(' <Data><![CDATA[') + xml(xml_string) + xml('\n]]></Data>\n') if more_data == 1: xml(' <MoreData/>\n') xml(' </Item>\n') @@ -258,7 +368,7 @@ class XMLSyncUtilsMixin(SyncCode): xml(' <Source>\n') xml(' <LocURI>%s</LocURI>\n' % str(gid)) xml(' </Source>\n') - xml(' <Data>\n') + xml(' <Data>') xml(xml_string) xml(' </Data>\n') if more_data == 1: @@ -308,7 +418,6 @@ class XMLSyncUtilsMixin(SyncCode): xml = xml_method() else: raise ValueError, "Sorry the script or method was not found" - #LOG('getXMLObject', 0, 'xml_mapping:%s, xml:%s, object:%s xml_method:%s' % (xml_mapping, xml, object, xml_method)) return xml def getSessionId(self, xml): @@ -370,7 +479,10 @@ class XMLSyncUtilsMixin(SyncCode): """ Return the value of the alert code inside the xml_stream """ - return xml.xpath('string(TargetRef)') + status = xml.xpath('string(TargetRef)') + if isinstance(status, unicode): + status = status.encode('utf-8') + return status def getStatusCode(self, xml): """ @@ -386,7 +498,10 @@ class XMLSyncUtilsMixin(SyncCode): Return the value of the command inside the xml_stream """ if xml.nodeName=='Status': - return xml.xpath('string(//Status/Cmd)') + cmd = xml.xpath('string(//Status/Cmd)') + if isinstance(cmd, unicode): + cmd = cmd.encode('utf-8') + return cmd else: return None @@ -399,8 +514,8 @@ class XMLSyncUtilsMixin(SyncCode): data='' first_node = xml.childNodes[0] - format = first_node.xpath('string(/SyncML/SyncHdr/Cred/Meta/Format)') - type = first_node.xpath('string(/SyncML/SyncHdr/Cred/Meta/Type)') + format = first_node.xpath("string(/SyncML/SyncHdr/Cred/Meta/*[local-name() = 'Format'])") + type = first_node.xpath("string(/SyncML/SyncHdr/Cred/Meta/*[local-name() = 'Type'])") data = first_node.xpath('string(/SyncML/SyncHdr/Cred/Data)') format = format.encode('utf-8') @@ -420,12 +535,12 @@ class XMLSyncUtilsMixin(SyncCode): """ return the chalenge information : format and type """ - format='' - type='' + format=None + type=None first_node = xml.childNodes[0] - format = first_node.xpath('string(/SyncML/SyncBody/Status/Chal/Meta/Format)') - type = first_node.xpath('string(/SyncML/SyncBody/Status/Chal/Meta/Type)') + format = first_node.xpath("string(//*[local-name() = 'Format'])") + type = first_node.xpath("string(//*[local-name() = 'Type'])") format = format.encode('utf-8') type = type.encode('utf-8') @@ -468,7 +583,7 @@ class XMLSyncUtilsMixin(SyncCode): sync = True return sync - def CheckStatus(self, xml_stream): + def checkStatus(self, xml_stream): """ Check if there's a Status section in the xml_xtream """ @@ -477,43 +592,34 @@ class XMLSyncUtilsMixin(SyncCode): status = True return status - def getNextSyncAction(self, xml_stream, last_action): + def getSyncActionList(self, xml_stream): """ - It goes throw actions in the Sync section of the SyncML file, - then it returns the next action (could be "add", "replace", - "delete"). + return the list of the action (could be "add", "replace", "delete"). """ - first_node = xml_stream.childNodes[0] - client_body = first_node.childNodes[3] - if client_body.nodeName != "SyncBody": - print "This is not a SyncML Body" - next_action = None - for subnode in client_body.childNodes: - if subnode.nodeType == subnode.ELEMENT_NODE and \ - subnode.nodeName == "Sync": - # if we didn't use this method before - if last_action == None and len(subnode.childNodes) > 1: - next_action = subnode.childNodes[1] - else: - found = None - for subnode2 in subnode.childNodes: - if subnode2.nodeType == subnode.ELEMENT_NODE and \ - subnode2 != last_action and found is None: - pass - elif subnode2.nodeType == subnode.ELEMENT_NODE and \ - subnode2 == last_action and found is None: - found = 1 - elif subnode2.nodeType == subnode.ELEMENT_NODE and \ - found is not None: - next_action = subnode2 - break - return next_action + return xml_stream.xpath('//Add|//Delete|//Replace') + + def getSyncBodyStatusList(self, xml_stream): + """ + return the list of dictionary corredponding to the data of each status bloc + the data are : cmd, code and source + """ + status_list = [] + xml = xml_stream.xpath('//Status') + for status in xml: + tmp_dict = {} + tmp_dict['cmd'] = status.xpath('string(./Cmd)').encode('utf-8') + tmp_dict['code'] = status.xpath('string(./Data)').encode('utf-8') + tmp_dict['source'] = status.xpath('string(./SourceRef)').encode('utf-8') + tmp_dict['target'] = status.xpath('string(./TargetRef)').encode('utf-8') + status_list.append(tmp_dict) + + return status_list def getNextSyncBodyStatus(self, xml_stream, last_status): """ - It goes throw actions in the Sync section of the SyncML file, - then it returns the next action (could be "add", "replace", - "delete"). + It goes throw actions in the Sync section of the SyncML file, + then it returns the next action (could be "add", "replace", + "delete"). """ first_node = xml_stream.childNodes[0] client_body = first_node.childNodes[3] @@ -539,16 +645,19 @@ class XMLSyncUtilsMixin(SyncCode): """ return the section data in text form, it's usefull for the VCardConduit """ - return action.xpath('string(Item/Data)') + data = action.xpath('string(Item/Data)') + if isinstance(data, unicode): + data = data.encode('utf-8') + return data def getDataSubNode(self, action): """ Return the node starting with <object....> of the action """ - if action.xpath('Item/Data') not in ([], None): - data_node = action.xpath('Item/Data')[0] - if data_node.hasChildNodes() and data_node.childNodes.__len__()>1: - return data_node.childNodes[1] + if action.xpath('.//Item/Data') not in ([], None): + data_node = action.xpath('.//Item/Data')[0] + if data_node.hasChildNodes(): + return data_node.childNodes[0] return None def getPartialData(self, action): @@ -575,20 +684,16 @@ class XMLSyncUtilsMixin(SyncCode): return None - def getActionId(self, action): + def getActionId(self, action, action_name): """ Return the rid of the object described by the action """ - for subnode in action.childNodes: - if subnode.nodeType == subnode.ELEMENT_NODE and \ - subnode.nodeName == 'Item': - for subnode2 in subnode.childNodes: - if subnode2.nodeType == subnode2.ELEMENT_NODE and \ - subnode2.nodeName == 'Source': - for subnode3 in subnode2.childNodes: - if subnode3.nodeType == subnode3.ELEMENT_NODE and \ - subnode3.nodeName == 'LocURI': - return str(subnode3.childNodes[0].data) + id = action.xpath('string(.//Item/Source/LocURI)') + if id in (None, ''): + id = action.xpath('string(.//Item/Target/LocURI)') + if isinstance(id, unicode): + id = id.encode('utf-8') + return id def checkActionMoreData(self, action): """ @@ -602,7 +707,10 @@ class XMLSyncUtilsMixin(SyncCode): """ Return the type of the object described by the action """ - return action.xpath('string(Meta/Type)') + action_type = action.xpath('string(Meta/Type)') + if isinstance(action_type, unicode): + action_type = action_type.encode('utf-8') + return action_type def getElementNodeList(self, node): """ @@ -647,18 +755,21 @@ class XMLSyncUtilsMixin(SyncCode): #object_gid = domain.getGidFromObject(object) local_gid_list = map(lambda x: domain.getGidFromObject(x),object_list) # Objects to remove - #for object_id in id_list: for object_gid in subscriber.getGidList(): if not (object_gid in local_gid_list): # This is an object to remove - signature = subscriber.getSignature(object_gid) + signature = subscriber.getSignatureFromGid(object_gid) if signature.getStatus()!=self.PARTIAL: # If partial, then we have a signature # but no local object xml_object = signature.getXML() if xml_object is not None: # This prevent to delete an object that # we were not able to create + rid = signature.getRid() + if rid != None: + object_gid=rid #to use the remote id if it exist syncml_data += self.deleteXMLObject(xml_object=signature.getXML()\ or '', object_gid=object_gid,cmd_id=cmd_id) + cmd_id += 1 local_gid_list = [] #for object in domain.getObjectList(): @@ -683,9 +794,10 @@ class XMLSyncUtilsMixin(SyncCode): #LOG('getSyncMLData',0,'xml_mapping: %s' % str(domain.xml_mapping)) #LOG('getSyncMLData',0,'code: %s' % str(self.getAlertCode(remote_xml))) #LOG('getSyncMLData',0,'gid_list: %s' % str(local_gid_list)) + #LOG('getSyncMLData',0,'subscriber.getGidList: %s' % subscriber.getGidList()) #LOG('getSyncMLData',0,'hasSignature: %s' % str(subscriber.hasSignature(object_gid))) #LOG('getSyncMLData',0,'alert_code == slowsync: %s' % str(self.getAlertCode(remote_xml)==self.SLOW_SYNC)) - signature = subscriber.getSignature(object_gid) + signature = subscriber.getSignatureFromGid(object_gid) # Here we first check if the object was modified or not by looking at dates if signature is not None: @@ -702,7 +814,10 @@ class XMLSyncUtilsMixin(SyncCode): xml_object = self.getXMLObject(object=object, xml_mapping=domain.xml_mapping) xml_string = xml_object - signature = Signature(gid=object_gid,id=object.getId(),object=object) + if isinstance(xml_string, unicode): + xml_string = xml_object.encode('utf-8') + gid = subscriber.getGidFromObject(object) + signature = Signature(id=gid,object=object) signature.setTempXML(xml_object) if xml_string.count('\n') > self.MAX_LINES: if xml_string.find('--') >= 0: # This make comment fails, so we need to replace @@ -714,16 +829,18 @@ class XMLSyncUtilsMixin(SyncCode): while i < self.MAX_LINES: short_string += rest_string[:rest_string.find('\n')+1] rest_string = xml_string[len(short_string):] - #LOG('XMLSyncUtils',0,'rest_string: %s' % str(rest_string)) i += 1 #LOG('getSyncMLData',0,'setPartialXML with: %s' % str(rest_string)) signature.setPartialXML(rest_string) - status =self.PARTIAL + status = self.PARTIAL signature.setAction('Add') xml_string = '<!--' + short_string + '-->' + gid = signature.getRid()#in fisrt, we try with rid if there is one + if gid == None: + gid = signature.getGid() syncml_data += self.addXMLObject(cmd_id=cmd_id, object=object, - gid=object_gid, xml_string=xml_string, more_data=more_data, - media_type=subscriber.getMediaType()) + gid=gid, xml_string=xml_string, + more_data=more_data, media_type=subscriber.getMediaType()) cmd_id += 1 signature.setStatus(status) subscriber.addSignature(signature) @@ -734,8 +851,9 @@ class XMLSyncUtilsMixin(SyncCode): #LOG('getSyncMLData',0,'checkMD5: %s' % str(signature.checkMD5(xml_object))) #LOG('getSyncMLData',0,'getStatus: %s' % str(signature.getStatus())) if signature.getStatus()==self.PUB_CONFLICT_MERGE: - xml_confirmation += self.SyncMLConfirmation(cmd_id,object.id, - self.CONFLICT_MERGE,'Replace') + xml_confirmation += self.SyncMLConfirmation(cmd_id=cmd_id, + source_ref=signature.getGid(), sync_code=self.CONFLICT_MERGE, + cmd='Replace') set_synchronized = 1 if not signature.checkMD5(xml_object): set_synchronized = 0 @@ -762,9 +880,12 @@ class XMLSyncUtilsMixin(SyncCode): if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']: xml_string = self.getXMLObject(object=object, xml_mapping=domain.xml_mapping) + gid = signature.getRid()#in fisrt, we try with rid if there is one + if gid == None: + gid = signature.getGid() syncml_data += self.replaceXMLObject(cmd_id=cmd_id, object=object, - gid=object_gid, xml_string=xml_string, more_data=more_data, - media_type=subscriber.getMediaType()) + gid=gid, xml_string=xml_string, + more_data=more_data, media_type=subscriber.getMediaType()) cmd_id += 1 signature.setTempXML(xml_object) # Now we can apply the xupdate from the subscriber @@ -780,6 +901,7 @@ class XMLSyncUtilsMixin(SyncCode): signature.setTempXML(xml_object) if set_synchronized: # We have to do that after this previous update # We should not have this case when we are in CONFLICT_MERGE + signature.setStatus(self.SYNCHRONIZED) elif signature.getStatus()==self.PUB_CONFLICT_CLIENT_WIN: # We have decided to apply the update @@ -789,8 +911,9 @@ class XMLSyncUtilsMixin(SyncCode): xml_update = signature.getPartialXML() conduit.updateNode(xml=signature.getPartialXML(), object=object, previous_xml=signature.getXML(),force=1) - xml_confirmation += self.SyncMLConfirmation(cmd_id,object_gid, - self.CONFLICT_CLIENT_WIN,'Replace') + xml_confirmation += self.SyncMLConfirmation(cmd_id=cmd_id, + target_ref=object_gid, sync_code=self.CONFLICT_CLIENT_WIN, + cmd='Replace') signature.setStatus(self.SYNCHRONIZED) elif signature.getStatus()==self.PARTIAL: xml_string = signature.getPartialXML() @@ -815,13 +938,19 @@ class XMLSyncUtilsMixin(SyncCode): xml_mapping=domain.xml_mapping) #LOG('xml_string =', 0, xml_string) if signature.getAction()=='Replace': + gid = signature.getRid()#in fisrt, we try with rid if there is one + if gid == None: + gid = signature.getGid() syncml_data += self.replaceXMLObject(cmd_id=cmd_id, object=object, - gid=object_gid, xml_string=xml_string, more_data=more_data, + gid=gid, xml_string=xml_string, more_data=more_data, media_type=subscriber.getMediaType()) elif signature.getAction()=='Add': + gid = signature.getRid()#in fisrt, we try with rid if there is one + if gid == None: + gid = signature.getGid() syncml_data += self.addXMLObject(cmd_id=cmd_id, object=object, - gid=object_gid, xml_string=xml_string, more_data=more_data, - media_type=subscriber.getMediaType()) + gid=gid, xml_string=xml_string, + more_data=more_data, media_type=subscriber.getMediaType()) return (syncml_data,xml_confirmation,cmd_id) def applyActionList(self, domain=None, subscriber=None,destination_path=None, @@ -830,27 +959,42 @@ class XMLSyncUtilsMixin(SyncCode): 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: + for action in self.getSyncActionList(remote_xml): conflict_list = [] status_code = self.SUCCESS # Thirst we have to check the kind of action it is - partial_data = self.getPartialData(next_action) - object_gid = self.getActionId(next_action) - signature = subscriber.getSignature(object_gid) - object = domain.getObjectFromGid(object_gid) + partial_data = self.getPartialData(action) + rid = self.getActionId(action, action.nodeName) + if action.nodeName != 'Delete': + if hasattr(conduit, 'getGidFromXML'): + gid = b16encode(conduit.getGidFromXML(self.getDataText(action))) + else: + gid=rid + else: + gid=rid + object_id = domain.generateNewIdWithGenerator(object=destination_path,gid=gid) + signature = subscriber.getSignatureFromGid(gid) + if signature != None and rid != gid: + #in this case, the object was created on another subscriber than erp5 + # and we should save it's remote id + signature.setRid(rid) + LOG('gid == rid ?', 0, 'gid=%s, rid=%s' % (gid, rid)) + object = subscriber.getObjectFromGid(gid) if signature == None: #LOG('applyActionList, signature is None',0,signature) - signature = Signature(gid=object_gid, status=self.NOT_SYNCHRONIZED, - object=object).__of__(subscriber) + if gid == rid: + signature = Signature(id=gid, status=self.NOT_SYNCHRONIZED, + object=object).__of__(subscriber) + else: + signature = Signature(rid=rid, id=gid, status=self.NOT_SYNCHRONIZED, + object=object).__of__(subscriber) + signature.setObjectId(object_id) subscriber.addSignature(signature) force = signature.getForce() #LOG('applyActionList',0,'object: %s' % repr(object)) - if self.checkActionMoreData(next_action) == 0: + if self.checkActionMoreData(action) == 0: data_subnode = None if partial_data != None: signature_partial_xml = signature.getPartialXML() @@ -859,20 +1003,19 @@ class XMLSyncUtilsMixin(SyncCode): else: data_subnode = partial_data #LOG('SyncModif',0,'data_subnode: %s' % data_subnode) - #data_subnode = FromXml(data_subnode) if subscriber.getMediaType() == self.MEDIA_TYPE['TEXT_XML']: data_subnode = Parse(data_subnode) data_subnode = data_subnode.childNodes[0] # Because we just created a new xml # document, with childNodes[0] a DocumentType and childNodes[1] the Element Node else: if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']: - data_subnode = self.getDataText(next_action) + data_subnode = self.getDataText(action) else: - data_subnode = self.getDataSubNode(next_action) - if next_action.nodeName == 'Add': + data_subnode = self.getDataSubNode(action) + if action.nodeName == 'Add': # Then store the xml of this new subobject if object is None: - object_id = domain.generateNewIdWithGenerator(object=destination_path,gid=object_gid) + object_id = domain.generateNewIdWithGenerator(object=destination_path,gid=gid) #if object_id is not None: add_data = conduit.addNode(xml=data_subnode, object=destination_path, object_id=object_id) @@ -880,14 +1023,14 @@ class XMLSyncUtilsMixin(SyncCode): conflict_list += add_data['conflict_list'] # Retrieve directly the object from addNode object = add_data['object'] - #LOG('XMLSyncUtils, in ADD add_data',0,add_data) + LOG('XMLSyncUtils, in ADD add_data',0,add_data) if object is not None: signature.setPath(object.getPhysicalPath()) - #LOG('applyActionList',0,'object after add: %s' % repr(object)) + signature.setObjectId(object.getId()) else: #Object was retrieve but need to be updated without recreated #usefull when an object is only deleted by workflow. - object_id = domain.generateNewIdWithGenerator(object=destination_path,gid=object_gid) + object_id = domain.generateNewIdWithGenerator(object=destination_path,gid=gid) add_data = conduit.addNode(xml=data_subnode, object=destination_path, object_id=object_id, @@ -901,17 +1044,20 @@ class XMLSyncUtilsMixin(SyncCode): #if mapping is not None: # xml_object = mapping() signature.setStatus(self.SYNCHRONIZED) - signature.setId(object.getId()) + #signature.setId(object.getId()) signature.setPath(object.getPhysicalPath()) signature.setXML(xml_object) - xml_confirmation +=\ - self.SyncMLConfirmation(cmd_id,object_gid,self.SUCCESS,'Add') + xml_confirmation += self.SyncMLConfirmation( + cmd_id=cmd_id, + cmd='Add', + sync_code=self.ITEM_ADDED, + remote_xml=action) cmd_id +=1 - elif next_action.nodeName == 'Replace': + elif action.nodeName == 'Replace': #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) + signature = subscriber.getSignatureFromGid(gid) #LOG('SyncModif',0,'previous signature: %s' % str(signature)) previous_xml = signature.getXML() #LOG('SyncModif',0,'previous signature: %i' % len(previous_xml)) @@ -934,8 +1080,11 @@ class XMLSyncUtilsMixin(SyncCode): signature.setPartialXML(data_subnode_string) elif not simulate: signature.setStatus(self.SYNCHRONIZED) - xml_confirmation += self.SyncMLConfirmation(cmd_id, - object_gid,status_code,'Replace') + xml_confirmation += self.SyncMLConfirmation(\ + cmd_id=cmd_id, + cmd='Replace', + sync_code=status_code, + remote_xml=action) cmd_id +=1 if simulate: # This means we are on the publiher side and we want to store @@ -947,17 +1096,22 @@ class XMLSyncUtilsMixin(SyncCode): #LOG('applyActionList, subscriber_xupdate:',0,data_subnode_string) signature.setSubscriberXupdate(data_subnode_string) - elif next_action.nodeName == 'Delete': + elif action.nodeName == 'Delete': object_id = signature.getId() if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']: - data_subnode = self.getDataText(next_action) + data_subnode = self.getDataText(action) else: - data_subnode = self.getDataSubNode(next_action) - conduit.deleteNode(xml=data_subnode, object=destination_path, - object_id=object_id) - subscriber.delSignature(object_gid) - xml_confirmation += self.SyncMLConfirmation(cmd_id, - object_gid,status_code,'Delete') + data_subnode = self.getDataSubNode(action) + if subscriber.getObjectFromGid(object_id) not in (None, ''): + #if the object exist: + conduit.deleteNode(xml=data_subnode, object=destination_path, + object_id=subscriber.getObjectFromGid(object_id).getId()) + subscriber.delSignature(gid) + xml_confirmation += self.SyncMLConfirmation( + cmd_id=cmd_id, + cmd='Delete', + sync_code=status_code, + remote_xml=action) else: # We want to retrieve more data signature.setStatus(self.PARTIAL) #LOG('SyncModif',0,'setPartialXML: %s' % str(previous_partial)) @@ -967,13 +1121,17 @@ class XMLSyncUtilsMixin(SyncCode): 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) + #xml_confirmation += self.SyncMLConfirmation(cmd_id, object_gid, + # self.WAITING_DATA, action.nodeName) + xml_confirmation += self.SyncMLConfirmation(\ + cmd_id=cmd_id, + cmd=action.nodeName, + sync_code=self.WAITING_DATA, + remote_xml=action) 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): @@ -981,44 +1139,46 @@ class XMLSyncUtilsMixin(SyncCode): 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) - #LOG('applyStatusList, next_status',0,next_status) - # We do not want the first one - next_status = self.getNextSyncBodyStatus(remote_xml, next_status) + status_list = self.getSyncBodyStatusList(remote_xml) 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) - status_cmd = self.getStatusCommand(next_status) - signature = subscriber.getSignature(object_gid) - #LOG('SyncModif',0,'next_status: %s' % str(status_code)) - if status_cmd in ('Add','Replace'): - 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) - elif status_cmd == 'Delete': - if status_code == self.SUCCESS: - subscriber.delSignature(object_gid) - next_status = self.getNextSyncBodyStatus(remote_xml, next_status) + if status_list != []: + for status in status_list: + status_cmd = status['cmd'] + #if status_cmd in ('Delete'): + # object_gid = status['target'] + #else: + object_gid = status['source'] + status_code = int(status['code']) + if status_cmd in ('Add','Replace'): + has_status_list = 1 + signature = subscriber.getSignatureFromGid(object_gid) + if signature == None: + signature = subscriber.getSignatureFromRid(object_gid) + 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 in (self.SUCCESS, self.ITEM_ADDED): + signature.setStatus(self.SYNCHRONIZED) + elif status_cmd == 'Delete': + if status_code == self.SUCCESS: + signature = subscriber.getSignatureFromGid(object_gid) + if signature == None: + signature = subscriber.getSignatureFromRid(object_gid) + subscriber.delSignature(signature.getGid()) return (destination_waiting_more_data, has_status_list) - class XMLSyncUtils(XMLSyncUtilsMixin): def Sync(self, id, msg=None, RESPONSE=None): @@ -1121,7 +1281,7 @@ class XMLSyncUtils(XMLSyncUtilsMixin): xml_list = [] xml = xml_list.append xml('<SyncML>\n') - + # syncml header if domain.domain_type == self.PUB: xml(self.SyncMLHeader(subscriber.getSessionId(), @@ -1130,54 +1290,114 @@ class XMLSyncUtils(XMLSyncUtilsMixin): elif domain.domain_type == self.SUB: xml(self.SyncMLHeader(domain.getSessionId(), domain.incrementMessageId(), 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 and not \ - (domain.domain_type==self.SUB and alert_code==self.SLOW_SYNC): - (syncml_data,xml_confirmation,cmd_id) = self.getSyncMLData(domain=domain, - remote_xml=remote_xml, - subscriber=subscriber, - destination_path=destination_path, - cmd_id=cmd_id,xml_confirmation=xml_confirmation, - conduit=conduit) # syncml body xml(' <SyncBody>\n') + + # status for SyncHdr + message_id = self.getMessageId(remote_xml) + xml(' <Status>\n') + xml(' <CmdID>%s</CmdID>\n' % cmd_id) + cmd_id += 1 + xml(' <MsgRef>%s</MsgRef>\n' % message_id) + xml(' <CmdRef>0</CmdRef>\n') #to make reference to the SyncHdr, it's 0 + xml(' <Cmd>SyncHdr</Cmd>\n') + xml(' <TargetRef>%s</TargetRef>\n' \ + % remote_xml.xpath('string(//SyncHdr/Target/LocURI)').encode('utf-8')) + xml(' <SourceRef>%s</SourceRef>\n' \ + % remote_xml.xpath('string(//SyncHdr/Source/LocURI)').encode('utf-8')) + xml(' <Data>200</Data>\n') + xml(' </Status>\n') + + #list of element in the SyncBody bloc + syncbody_element_list = remote_xml.xpath('//SyncBody/*') + + #add the status bloc corresponding to the receive command + for syncbody_element in syncbody_element_list: + if str(syncbody_element.nodeName) not in ('Status', 'Final', 'Replace'): + xml(' <Status>\n') + xml(' <CmdID>%s</CmdID>\n' % cmd_id) + cmd_id += 1 + xml(' <MsgRef>%s</MsgRef>\n' % message_id) + xml(' <CmdRef>%s</CmdRef>\n' \ + % syncbody_element.xpath('string(.//CmdID)').encode('utf-8')) + xml(' <Cmd>%s</Cmd>\n' % syncbody_element.nodeName.encode('utf-8')) + + target_ref = syncbody_element.xpath('string(.//Target/LocURI)').encode('utf-8') + if target_ref not in (None, ''): + xml(' <TargetRef>%s</TargetRef>\n' % target_ref ) + source_ref = syncbody_element.xpath('string(.//Source/LocURI)').encode('utf-8') + if source_ref not in (None, ''): + xml(' <SourceRef>%s</SourceRef>\n' % source_ref ) + + #xml(' <Data>%s</Data>\n' % subscriber.getSynchronizationType()) + if syncbody_element.nodeName.encode('utf-8') == 'Add': + xml(' <Data>%s</Data>\n' % '201') + else: + xml(' <Data>%s</Data>\n' % '200') + + # if syncbody_element.xpath('.//Item') not in ([], None, '') and\ + # syncbody_element.xpath('.//Item.....'): #contient une ancre Next... + + xml(' <Item>\n') + xml(' <Data>\n') + xml(" <Anchor xmlns='syncml:metinf'>\n") + xml(' <Next>%s</Next>\n' % subscriber.getNextAnchor()) + xml(' </Anchor>\n') + xml(' </Data>\n') + xml(' </Item>\n') + + xml(' </Status>\n') + + destination_url = '' - if domain.domain_type == self.PUB: - subscriber.NewAnchor() - destination_url = domain.getPublicationUrl() - xml(self.SyncMLStatus(cmd_id, subscriber.getSubscriptionUrl(), - domain.getDestinationPath(), - subscriber.getSynchronizationType(), - subscriber.getNextAnchor())) - elif domain.domain_type == self.SUB: - destination_url = domain.getPublicationUrl() - xml(self.SyncMLStatus(cmd_id, domain.getPublicationUrl(), - subscriber.getDestinationPath(), - subscriber.getSynchronizationType(), - subscriber.getNextAnchor())) # alert message if we want more data if destination_waiting_more_data == 1: xml(self.SyncMLAlert(cmd_id, self.WAITING_DATA, - destination_url, - domain.getDestinationPath(), + subscriber.getTargetURI(), + subscriber.getSourceURI(), subscriber.getLastAnchor(), subscriber.getNextAnchor())) # Now we should send confirmations - xml(xml_confirmation) + cmd_id_before_getsyncmldata = cmd_id + (syncml_data,xml_confirmation,cmd_id) = self.getSyncMLData(domain=domain, + remote_xml=remote_xml, + subscriber=subscriber, + destination_path=destination_path, + cmd_id=cmd_id+1,xml_confirmation=xml_confirmation, + conduit=conduit) if syncml_data != '': xml(' <Sync>\n') + xml(' <CmdID>%s</CmdID>\n' % cmd_id_before_getsyncmldata) + if domain.domain_type == self.SUB: + if subscriber.getTargetURI() not in ('', None): + xml(' <Target>\n') + xml(' <LocURI>%s</LocURI>\n' % subscriber.getTargetURI()) + xml(' </Target>\n') + if subscriber.getSourceURI() not in ('', None): + xml(' <Source>\n') + xml(' <LocURI>%s</LocURI>\n' % subscriber.getSourceURI()) + xml(' </Source>\n') + elif domain.domain_type == self.PUB: + if domain.getTargetURI() not in ('', None): + xml(' <Target>\n') + xml(' <LocURI>%s</LocURI>\n' % domain.getTargetURI()) + xml(' </Target>\n') + if domain.getSourceURI() not in ('', None): + xml(' <Source>\n') + xml(' <LocURI>%s</LocURI>\n' % domain.getSourceURI()) + xml(' </Source>\n') xml(syncml_data) xml(' </Sync>\n') + xml(xml_confirmation) xml(' <Final/>\n') xml(' </SyncBody>\n') xml('</SyncML>\n') xml_a = ''.join(xml_list) + if domain.domain_type == self.PUB: # We always reply subscriber.setLastSentMessage(xml_a) self.sendResponse(from_url=domain.publication_url, @@ -1186,7 +1406,7 @@ class XMLSyncUtils(XMLSyncUtilsMixin): has_response = 1 elif domain.domain_type == self.SUB: if self.checkAlert(remote_xml) or \ - (xml_confirmation,syncml_data)!=('','') or \ + (xml_confirmation,syncml_data)!=('','') or \ has_status_list: subscriber.setLastSentMessage(xml_a) self.sendResponse(from_url=domain.subscription_url, diff --git a/product/ERP5SyncML/dtml/managePublications.dtml b/product/ERP5SyncML/dtml/managePublications.dtml index 727e25b8c95bcf66a30f4e7cb20fd755683aca14..1e7fbdfbad068b75aec7f4e39c530e4e3b6c1508 100644 --- a/product/ERP5SyncML/dtml/managePublications.dtml +++ b/product/ERP5SyncML/dtml/managePublications.dtml @@ -67,6 +67,16 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <input type="text" name="destination_path" value="<dtml-var getDestinationPath>" size="40" /> </td> </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Source URI + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="source_uri" size="40" value="<dtml-var getSourceURI>" /> + </td> + </tr> <tr> <td align="left" valign="top"> <div class="form-label"> diff --git a/product/ERP5SyncML/dtml/manageSubscriptions.dtml b/product/ERP5SyncML/dtml/manageSubscriptions.dtml index 1b9f14f53e0f68cad1853758068c1d7c04f21dc3..7f1f7558c6eb9b8b3bc241a3db0c1128c886cd4f 100644 --- a/product/ERP5SyncML/dtml/manageSubscriptions.dtml +++ b/product/ERP5SyncML/dtml/manageSubscriptions.dtml @@ -77,6 +77,26 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <input type="text" name="destination_path" value="<dtml-var getDestinationPath>" size="40" /> </td> </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Source URI + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="source_uri" size="40" value="<dtml-var getSourceURI>" /> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Target URI + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="target_uri" size="40" value="<dtml-var getTargetURI>" /> + </td> + </tr> <tr> <td align="left" valign="top"> <div class="form-label"> diff --git a/product/ERP5SyncML/dtml/manage_addPublication.dtml b/product/ERP5SyncML/dtml/manage_addPublication.dtml index faca84f7b7ba764174e134267b9e505113a9145a..59b71d43369a0023debf69eebb60bffda6cef36e 100644 --- a/product/ERP5SyncML/dtml/manage_addPublication.dtml +++ b/product/ERP5SyncML/dtml/manage_addPublication.dtml @@ -63,6 +63,16 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <input type="text" name="destination_path" size="40" /> </td> </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Source URI + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="source_uri" size="40" /> + </td> + </tr> <tr> <td align="left" valign="top"> <div class="form-label"> diff --git a/product/ERP5SyncML/dtml/manage_addSubscription.dtml b/product/ERP5SyncML/dtml/manage_addSubscription.dtml index 65217244bf841fbe29cc80eaeef17243b380f672..ed9251a2b0830dfc5d043ef49c80960f6db431b2 100644 --- a/product/ERP5SyncML/dtml/manage_addSubscription.dtml +++ b/product/ERP5SyncML/dtml/manage_addSubscription.dtml @@ -73,6 +73,26 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <input type="text" name="destination_path" size="40" /> </td> </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Source URI + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="source_uri" size="40" /> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Target URI + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="target_uri" size="40" /> + </td> + </tr> <tr> <td align="left" valign="top"> <div class="form-label"> diff --git a/product/ERP5SyncML/tests/testERP5SyncML.py b/product/ERP5SyncML/tests/testERP5SyncML.py index d4caa28dde3c35d31479245d70ffc3b4377f387c..41b803059725b94e85e1e46ded6f2412f1214e6d 100644 --- a/product/ERP5SyncML/tests/testERP5SyncML.py +++ b/product/ERP5SyncML/tests/testERP5SyncML.py @@ -43,9 +43,10 @@ from Products.ERP5SyncML.SyncCode import SyncCode from zLOG import LOG try: - from base64 import b64encode, b64decode + from base64 import b64encode, b64decode, b16encode, b16decode except ImportError: - from base64 import encodestring as b64encode, decodestring as b64decode + from base64 import encodestring as b64encode, decodestring as b64decode, \ + encodestring as b16encode, decodestring as b16decode class TestERP5SyncMLMixin: @@ -195,10 +196,10 @@ class TestERP5SyncMLMixin: file.write('') file.close() nb_message = 1 - result = portal_sync.SubSync(subscription.getTitle()) + result = portal_sync.SubSync(subscription) while result['has_response']==1: portal_sync.PubSync(publication.getTitle()) - result = portal_sync.SubSync(subscription.getTitle()) + result = portal_sync.SubSync(subscription) nb_message += 1 + result['has_response'] return nb_message @@ -226,16 +227,16 @@ class TestERP5SyncMLMixin: file.write('') file.close() nb_message = 1 - result = portal_sync.SubSync(subscription.getTitle()) + result = portal_sync.SubSync(subscription) while result['has_response']==1: # We do thing three times, so that we will test # if we manage well duplicate messages portal_sync.PubSync(publication.getTitle()) portal_sync.PubSync(publication.getTitle()) portal_sync.PubSync(publication.getTitle()) - result = portal_sync.SubSync(subscription.getTitle()) - result = portal_sync.SubSync(subscription.getTitle()) - result = portal_sync.SubSync(subscription.getTitle()) + result = portal_sync.SubSync(subscription) + result = portal_sync.SubSync(subscription) + result = portal_sync.SubSync(subscription) nb_message += 1 + result['has_response'] return nb_message @@ -337,9 +338,15 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase): LOG('Testing... ',0,'test_02_AddPublication') portal_id = self.getPortalName() portal_sync = self.getSynchronizationTool() - portal_sync.manage_addPublication(self.pub_id,self.publication_url, - '/%s/person_server' % portal_id,'objectValues', self.xml_mapping, - 'ERP5Conduit','') + portal_sync.manage_addPublication(title=self.pub_id, + publication_url=self.publication_url, + destination_path='/%s/person_server' % portal_id, + source_uri='Person', + query='objectValues', + xml_mapping=self.xml_mapping, + conduit='ERP5Conduit', + gpg_key='', + gid_generator='getId') pub = portal_sync.getPublication(self.pub_id) self.failUnless(pub is not None) @@ -350,9 +357,17 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase): LOG('Testing... ',0,'test_03_AddSubscription1') portal_id = self.getPortalId() portal_sync = self.getSynchronizationTool() - portal_sync.manage_addSubscription(self.sub_id1, self.publication_url, - self.subscription_url1,'/%s/person_client1' % portal_id,'objectValues', - self.xml_mapping,'ERP5Conduit','') + portal_sync.manage_addSubscription(title=self.sub_id1, + publication_url=self.publication_url, + subscription_url=self.subscription_url1, + destination_path='/%s/person_client1' % portal_id, + source_uri='Person', + target_uri='Person', + query='objectValues', + xml_mapping=self.xml_mapping, + conduit='ERP5Conduit', + gpg_key='', + gid_generator='getId') sub = portal_sync.getSubscription(self.sub_id1) self.failUnless(sub is not None) @@ -363,9 +378,17 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase): LOG('Testing... ',0,'test_04_AddSubscription2') portal_id = self.getPortalId() portal_sync = self.getSynchronizationTool() - portal_sync.manage_addSubscription(self.sub_id2,self.publication_url, - self.subscription_url2,'/%s/person_client2' % portal_id,'objectValues', - self.xml_mapping,'ERP5Conduit','') + portal_sync.manage_addSubscription(title=self.sub_id2, + publication_url=self.publication_url, + subscription_url=self.subscription_url2, + destination_path='/%s/person_client2' % portal_id, + source_uri='Person', + target_uri='Person', + query='objectValues', + xml_mapping=self.xml_mapping, + conduit='ERP5Conduit', + gpg_key='', + gid_generator='getId') sub = portal_sync.getSubscription(self.sub_id2) self.failUnless(sub is not None) @@ -530,7 +553,7 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase): # By default we can just give the id portal_sync = self.getSynchronizationTool() publication = portal_sync.getPublication(self.pub_id) - object = publication.getObjectFromGid(self.id1) + object = publication.getObjectFromId(self.id1) self.failUnless(object is not None) self.failUnless(object.getId()==self.id1) @@ -994,10 +1017,11 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase): publication = portal_sync.getPublication(self.pub_id) self.failUnless(len(publication.getObjectList())==nb_person) gid = self.first_name1 + ' ' + self.last_name1 # ie the title 'Sebastien Robin' + gid = b16encode(gid) person_c1 = subscription1.getObjectFromGid(gid) id_c1 = person_c1.getId() self.failUnless(id_c1 in ('1','2')) # id given by the default generateNewId - person_s = publication.getObjectFromGid(gid) + person_s = publication.getSubscriber(self.subscription_url1).getObjectFromGid(gid) id_s = person_s.getId() self.failUnless(id_s==self.id1) # This will test updating object @@ -1014,7 +1038,7 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase): self.checkSynchronizationStateIsSynchronized() self.failUnless(len(subscription1.getObjectList())==(nb_person-1)) self.failUnless(len(publication.getObjectList())==(nb_person-1)) - person_s = publication.getObjectFromGid(gid) + person_s = publication.getSubscriber(self.subscription_url1).getObjectFromGid(gid) person_c1 = subscription1.getObjectFromGid(gid) self.failUnless(person_s.getDescription()==self.description3) self.failUnless(person_c1.getDescription()==self.description3) @@ -1299,9 +1323,17 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase): LOG('Testing... ',0,'test_32_AddOneWaySubscription') portal_id = self.getPortalId() portal_sync = self.getSynchronizationTool() - portal_sync.manage_addSubscription(self.sub_id1, self.publication_url, - self.subscription_url1, '/%s/person_client1' % portal_id, - 'objectValues', '', 'ERP5Conduit', '') + portal_sync.manage_addSubscription(title=self.sub_id1, + publication_url=self.publication_url, + subscription_url=self.subscription_url1, + destination_path='/%s/person_client1' % portal_id, + source_uri='Person', + target_uri='Person', + query='objectValues', + xml_mapping='', + conduit='ERP5Conduit', + gpg_key='', + gid_generator='getId') sub = portal_sync.getSubscription(self.sub_id1) self.failUnless(sub is not None)