diff --git a/product/ERP5SyncML/Publication.py b/product/ERP5SyncML/Publication.py index da6a447bbaff1bb992918f5532050442b26b969d..c46d8023db7a74926ab2ca1f02b0711c6dc85dfd 100644 --- a/product/ERP5SyncML/Publication.py +++ b/product/ERP5SyncML/Publication.py @@ -84,16 +84,6 @@ class Subscriber(Subscription): Send ACK for a group of documents """ - def getConduit(self): - """ - Return the conduit of the publication - """ - #LOG('Subscriber.getConduit, self.getPhysicalPath()',0,self.getPhysicalPath()) - #LOG('Subscriber.getConduit, self.getParent().getPhysicalPath()',0,self.aq_parent.getPhysicalPath()) - #LOG('Subscriber.getConduit, self.getParent()',0,self.getParent()) - return self.aq_parent.getConduit() - #return self.conduit - def SendDocuments(self): """ We send all the updated documents (ie. documents not marked @@ -155,7 +145,8 @@ class Publication(Subscription): 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, authentication_format, - authentication_type, activity_enabled): + authentication_type, activity_enabled, synchronize_with_erp5_sites, + sync_content_type): """ constructor """ @@ -178,6 +169,8 @@ class Publication(Subscription): self.auth_required = auth_required self.authentication_format = authentication_format self.authentication_type = authentication_type + self.setSyncContentType(sync_content_type) + self.setSynchronizeWithERP5Sites(synchronize_with_erp5_sites) def getPublicationUrl(self): """ @@ -238,6 +231,7 @@ class Publication(Subscription): """ Add a new subscriber to the publication """ + LOG('addSubscriber starting ...',0,'') # We have to remove the subscriber if it already exist (there were probably a reset on the client) self.delSubscriber(subscriber.getSubscriptionUrl()) new_id = subscriber.getId() diff --git a/product/ERP5SyncML/PublicationSynchronization.py b/product/ERP5SyncML/PublicationSynchronization.py index 1898dfd81e92d08895a7ccf987a5f4b9ae6897ce..b0d9046c250167e3d3c21ebc259432d2634271fb 100644 --- a/product/ERP5SyncML/PublicationSynchronization.py +++ b/product/ERP5SyncML/PublicationSynchronization.py @@ -52,7 +52,7 @@ class PublicationSynchronization(XMLSyncUtils): Read the client xml message Send the first XML message from the server """ - #LOG('PubSyncInit',0,'Starting... publication: %s' % str(publication)) + LOG('PubSyncInit',0,'Starting... publication: %s' % str(publication)) #the session id is set at the same value of those of the client subscriber.setSessionId(self.getSessionId(xml_client)) @@ -72,153 +72,145 @@ class PublicationSynchronization(XMLSyncUtils): alert = self.checkAlert(xml_client) alert_code = self.getAlertCode(xml_client) cred = self.checkCred(xml_client) - #XXX this is in developement, it's just for tests + + #the source and the target of the subscriber are reversed compared + # to those of the publication : + subscriber.setSourceURI(self.getTargetURI(xml_client)) + subscriber.setTargetURI(self.getSourceURI(xml_client)) + + # If slow sync, then resend everything + if alert_code == self.SLOW_SYNC: + LOG('Warning !!!, reseting client synchronization for subscriber:',0, + subscriber) + subscriber.resetAllSignatures() + + # Check if the last time synchronization is the same as the client one + mess='\nsubscriber.getNextAnchor:\t%s\nsubscriber.getLastAnchor:\t%s\ + \nlast_anchor:\t\t\t%s\nnext_anchor:\t\t\t%s' % \ + (subscriber.getNextAnchor(), subscriber.getLastAnchor(), last_anchor, \ + next_anchor) + #LOG('PubSyncInit',0,mess) + + if subscriber.getNextAnchor() != last_anchor: + if last_anchor in (None, ''): + LOG('PubSyncInit',0,'anchor null') + #raise ValueError, "Sorry, the anchor was null" + else: + message = "bad anchors in PubSyncInit! " + \ + subscriber.getNextAnchor() + " and " + last_anchor + LOG('PubSyncInit',0,message) + else: + subscriber.setNextAnchor(next_anchor) + + xml_list = [] + xml = xml_list.append + cmd_id = 1 # specifies a SyncML message-unique command identifier + xml('<SyncML>\n') + # syncml header + xml(self.SyncMLHeader(subscriber.getSessionId(), + subscriber.getMessageId(), + subscriber.getSubscriptionUrl(), + publication.getPublicationUrl())) + # syncml body + xml(' <SyncBody>\n') + + if publication.isAuthenticationRequired(): + #at the begining, the code is initialised at UNAUTHORIZED + auth_code=self.UNAUTHORIZED + LOG('PubSyncInit',0,'authentication required') if not cred: - #LOG('PubSyncInit',0,'authentication required') + auth_code=self.AUTH_REQUIRED + LOG("there's no credential !!!",0,'') # Prepare the xml message for the Sync initialization package - cmd_id = 1 # specifies a SyncML message-unique command identifier - xml_list = [] - xml = xml_list.append - xml('<SyncML>\n') - # syncml header - xml(self.SyncMLHeader(subscriber.getSessionId(), - subscriber.getMessageId(), subscriber.getSubscriptionUrl(), - publication.getPublicationUrl())) - # syncml body - xml(' <SyncBody>\n') - # chal message xml(self.SyncMLChal(cmd_id, "SyncHdr", publication.getPublicationUrl(), subscriber.getSubscriptionUrl(), publication.getAuthenticationFormat(), - publication.getAuthenticationType(), self.AUTH_REQUIRED)) + publication.getAuthenticationType(), auth_code)) cmd_id += 1 - - xml(' </SyncBody>\n') - - xml('</SyncML>\n') - xml_a = ''.join(xml_list) - - self.sendResponse(from_url=publication.getPublicationUrl(), - to_url=subscriber.getSubscriptionUrl(), - sync_id=publication.getTitle(), xml=xml_a, domain=publication) - - else:#(if the subscriber begin the session with a cred) -> to be tested - (authentication_format, authentication_type, data) = self.getCred(xml_client) - #at the begining, the code is initialised at UNAUTHORIZED - auth_code=self.UNAUTHORIZED - - if authentication_format == publication.getAuthenticationFormat(): - if authentication_type == publication.getAuthenticationType(): - decoded = subscriber.decode(authentication_format, data) - 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( - IAuthenticationPlugin ): - if plugin.authenticateCredentials( - {'login':login, 'password':password}) is not None: - subscriber.setAuthenticated(True) - auth_code=self.AUTH_ACCEPTED - #here we must log in with the user authenticated : - user = uf.getUserById(login).__of__(uf) - newSecurityManager(None, user) - subscriber.setUser(login) - break - else: - auth_code=self.UNAUTHORIZED + # chal message + xml_status, cmd_id = self.SyncMLStatus(xml_client, auth_code, + cmd_id, next_anchor, subscription=subscriber).values() + xml(xml_status) + else: + (authentication_format, authentication_type, data) = \ + self.getCred(xml_client) + if authentication_type == publication.getAuthenticationType(): + authentication_format = publication.getAuthenticationFormat() + decoded = subscriber.decode(authentication_format, data) + 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( + IAuthenticationPlugin ): + if plugin.authenticateCredentials( + {'login':login, 'password':password}) is not None: + subscriber.setAuthenticated(True) + auth_code=self.AUTH_ACCEPTED + #here we must log in with the user authenticated : + user = uf.getUserById(login).__of__(uf) + newSecurityManager(None, user) + subscriber.setUser(login) + break + else: + auth_code=self.UNAUTHORIZED #in all others cases, the auth_code is set to UNAUTHORIZED # Prepare the xml message for the Sync initialization package - cmd_id = 1 # specifies a SyncML message-unique command identifier - xml_list = [] - xml = xml_list.append - xml('<SyncML>\n') - # syncml header - xml(self.SyncMLHeader(subscriber.getSessionId(), - subscriber.getMessageId(), - subscriber.getSubscriptionUrl(), - publication.getPublicationUrl())) - # syncml body - xml(' <SyncBody>\n') - xml(self.SyncMLStatus(cmd_id, subscriber.getSubscriptionUrl(), - publication.getPublicationUrl(), auth_code)) - cmd_id += 1 if auth_code == self.AUTH_ACCEPTED: + xml_status, cmd_id = self.SyncMLStatus(xml_client, auth_code, + cmd_id, next_anchor, subscription=subscriber).values() + xml(xml_status) # alert message - xml(self.SyncMLAlert(cmd_id, sync_type, - subscriber.getSubscriptionUrl(), publication.getPublicationUrl(), - subscriber.getLastAnchor(), subscriber.getNextAnchor())) + xml(self.SyncMLAlert(cmd_id, sync_type, subscriber.getTargetURI(), + subscriber.getSourceURI(), subscriber.getLastAnchor(), + next_anchor)) cmd_id += 1 else: # chal message xml(self.SyncMLChal(cmd_id, "SyncHdr", publication.getPublicationUrl(), subscriber.getSubscriptionUrl(), publication.getAuthenticationFormat(), - publication.getAuthenticationType(), self.AUTH_REQUIRED)) + publication.getAuthenticationType(), auth_code)) cmd_id += 1 - xml(' </SyncBody>\n') - xml('</SyncML>\n') - xml_a = ''.join(xml_list) + xml_status, cmd_id = self.SyncMLStatus(xml_client, + self.AUTH_REQUIRED, cmd_id, next_anchor, + subscription=subscriber).values() + xml(xml_status) - self.sendResponse(from_url=publication.getPublicationUrl(), - to_url=subscriber.getSubscriptionUrl(), - sync_id=publication.getTitle(), xml=xml_a, domain=publication) + elif alert is not None: #if no identification is required : + # syncml header + xml_status, cmd_id = self.SyncMLStatus(xml_client, self.AUTH_ACCEPTED, + cmd_id, next_anchor, subscription=subscriber).values() + xml(xml_status) + # alert message + xml(self.SyncMLAlert(cmd_id, sync_type, subscriber.getTargetURI(), + subscriber.getSourceURI(), subscriber.getLastAnchor(), next_anchor)) + cmd_id += 1 - else : - # If slow sync, then resend everything - if alert_code == self.SLOW_SYNC: - LOG('Warning !!!, reseting client synchronization for subscriber:',0, - subscriber) - subscriber.resetAllSignatures() - - # Check if the last time synchronization is the same as the client one - mess='\nsubscriber.getNextAnchor:\t%s\nsubscriber.getLastAnchor:\t%s\ - \nlast_anchor:\t\t\t%s\nnext_anchor:\t\t\t%s' % (subscriber.getNextAnchor(), subscriber.getLastAnchor(), last_anchor, next_anchor) - #LOG('PubSyncInit',0,mess) - - if subscriber.getNextAnchor() != last_anchor: - if last_anchor == None: - #LOG('PubSyncInit',0,'anchor null') - raise ValueError, "Sorry, the anchor was null" - else: - message = "bad anchors in PubSyncInit! " + \ - subscriber.getNextAnchor() + " and " + last_anchor - #LOG('PubSyncInit',0,message) - else: - subscriber.setNextAnchor(next_anchor) # We have to set every object as NOT_SYNCHRONIZED subscriber.startSynchronization() else: - # We have started the sync from the server (may be for a conflict resolution) - pass - - if alert is not None and not publication.isAuthenticationRequired(): - # Prepare the xml message for the Sync initialization package - cmd_id = 1 # specifies a SyncML message-unique command identifier - xml_list = [] - xml = xml_list.append + # We have started the sync from the server (may be for a conflict + # resolution) + raise ValueError, "the syncml message is None. Maybe a synchronisation \ + has been started from the server (forbiden)" + # a synchronisation is always starded from a client and can't be from + # a server ! - xml('<SyncML>\n') - # syncml header - xml(self.SyncMLHeader(subscriber.getSessionId(), - subscriber.incrementMessageId(), subscriber.getSubscriptionUrl(), - publication.getPublicationUrl())) - # syncml body - xml(' <SyncBody>\n') - # alert message - xml(self.SyncMLAlert(cmd_id, sync_type, subscriber.getSubscriptionUrl(), - publication.getPublicationUrl(), subscriber.getLastAnchor(), - subscriber.getNextAnchor())) - cmd_id += 1 - xml(' </SyncBody>\n') - xml('</SyncML>\n') - xml_a = ''.join(xml_list) + xml(' <Final/>\n') + xml(' </SyncBody>\n') + xml('</SyncML>\n') + xml_a = ''.join(xml_list) + + if publication.getSyncContentType() == self.CONTENT_TYPE['SYNCML_WBXML']: + xml_a = self.xml2wbxml(xml_a) + self.sendResponse(from_url=publication.getPublicationUrl(), + to_url=subscriber.getSubscriptionUrl(), sync_id=publication.getTitle(), + xml=xml_a, domain=publication, + content_type=publication.getSyncContentType()) - self.sendResponse(from_url=publication.getPublicationUrl(), - to_url=subscriber.getSubscriptionUrl(), sync_id=publication.getTitle(), - xml=xml_a, domain=publication) return {'has_response':1,'xml':xml_a} @@ -226,7 +218,7 @@ class PublicationSynchronization(XMLSyncUtils): """ This is the synchronization method for the server """ - #LOG('PubSync',0,'Starting... publication: %s' % str(publication_path)) + LOG('PubSync',0,'Starting... publication: %s' % str(publication_path)) # Read the request from the client publication = self.unrestrictedTraverse(publication_path) xml_client = msg @@ -244,18 +236,19 @@ class PublicationSynchronization(XMLSyncUtils): #LOG('PubSync',0,'This is not a SyncML Message') raise ValueError, "Sorry, This is not a SyncML Message" alert_code = self.getAlertCode(xml_client) - + # Get informations from the header client_header = first_node.childNodes[1] if client_header.nodeName != "SyncHdr": #LOG('PubSync',0,'This is not a SyncML Header') raise ValueError, "Sorry, This is not a SyncML Header" - subscription_url = self.getSourceURI(client_header) + subscription_url = self.getSubscriptionUrl(client_header) # Get the subscriber or create it if not already in the list subscriber = publication.getSubscriber(subscription_url) if subscriber == None: subscriber = Subscriber(publication.generateNewId(),subscription_url) subscriber.setXMLMapping(publication.getXMLMapping()) + subscriber.setConduit(publication.getConduit()) publication.addSubscriber(subscriber) # first synchronization result = self.PubSyncInit(publication,xml_client,subscriber=subscriber, @@ -266,6 +259,8 @@ class PublicationSynchronization(XMLSyncUtils): xml_client=xml_client, subscriber=subscriber, sync_type=alert_code) else: #we log the user authenticated to do the synchronization with him + if self.checkMap(xml_client) : + self.setRidWithMap(xml_client, subscriber) if publication.isAuthenticationRequired(): if subscriber.isAuthenticated(): uf = self.getPortalObject().acl_users diff --git a/product/ERP5SyncML/Subscription.py b/product/ERP5SyncML/Subscription.py index e5a8d0c98d218d519a8ffa28ed1f0a0b583d8680..97615d86a870d86ff3c962423e1e0eda42b176d3 100644 --- a/product/ERP5SyncML/Subscription.py +++ b/product/ERP5SyncML/Subscription.py @@ -575,6 +575,7 @@ def addSubscription( self, id, title='', REQUEST=None ): """ Add a new Subscribption """ + LOG('addSubscription starting...',0,'') o = Subscription( id ,'','','','','','') self._setObject( id, o ) if REQUEST is not None: @@ -648,7 +649,8 @@ class Subscription(Folder, SyncCode): def __init__(self, id, title, publication_url, subscription_url, destination_path, source_uri, target_uri, query, xml_mapping, conduit, gpg_key, id_generator, gid_generator, media_type, login, - password, activity_enabled, alert_code): + password, activity_enabled, alert_code, synchronize_with_erp5_sites, + sync_content_type): """ We need to create a dictionnary of signatures of documents which belong to the synchronisation @@ -679,7 +681,8 @@ class Subscription(Folder, SyncCode): self.setConduit(conduit) Folder.__init__(self, id) self.title = title - + self.setSyncContentType(sync_content_type) + self.setSynchronizeWithERP5Sites(synchronize_with_erp5_sites) #self.signatures = PersitentMapping() def getAlertCodeList(self): @@ -727,7 +730,7 @@ class Subscription(Folder, SyncCode): def getSourceURI(self): """ - getter for the source_uri (the local path of the subscription) + getter for the source_uri (the local path of the subscription data base) """ return getattr(self, 'source_uri', None) @@ -739,11 +742,26 @@ class Subscription(Folder, SyncCode): def getTargetURI(self): """ - getter for the target_uri (the distant Publication we want to synchronize - with) + getter for the target_uri (the distant Publication data base we want to + synchronize with) """ return getattr(self, 'target_uri', None) + def setSyncContentType(self, sync_content_type): + """ + content type used by the subscriber + """ + self.sync_content_type=sync_content_type + # the varible name is sync_content_type instead of content_type because + # content_type seems to be a function name already used + + + def getSyncContentType(self): + """ + getter of the subscriber sync_content_type + """ + return getattr(self, 'sync_content_type', 'application/vnd.syncml+xml') + def getSynchronizationType(self, default=None): """ """ @@ -770,6 +788,21 @@ class Subscription(Folder, SyncCode): value = None self.xml_mapping = value + def setSynchronizeWithERP5Sites(self, synchronize_with_erp5_sites): + """ + if the synchronisation is made with another ERP5 site, + synchronize_with_erp5_sites is True, False in other case + XXX in the future, the method used to sendHttpResponse will be the same + in all cases, so this method will be useless + """ + self.synchronize_with_erp5_sites = synchronize_with_erp5_sites + + def getSynchronizeWithERP5Sites(self): + """ + return True if the synchronisation is between two erp5 sites + """ + return getattr(self, 'synchronize_with_erp5_sites', None) + def checkCorrectRemoteSessionId(self, session_id): """ We will see if the last session id was the same @@ -886,7 +919,7 @@ class Subscription(Folder, SyncCode): def setPublicationUrl(self, publication_url): """ - return the publication url + set the publication url """ self.publication_url = publication_url @@ -1058,6 +1091,30 @@ class Subscription(Folder, SyncCode): break return o + def getObjectFromRid(self, rid): + """ + return the object corresponding to the id + """ + signature = self.getSignatureFromRid(rid) + destination = self.getDestination() + o = None + if signature is not None and signature.getPath() is not None: + try: + o = destination.getPortalObject().restrictedTraverse(signature.getPath()) + except: + pass + return o + + + +# def setOneWaySyncFromServer(self,value): +# """ +# If this option is enabled, then we will not +# send our own modifications +# """ +# self.one_way_sync_from_server = value +# + def getObjectList(self, **kw): """ This returns the list of sub-object corresponding diff --git a/product/ERP5SyncML/SubscriptionSynchronization.py b/product/ERP5SyncML/SubscriptionSynchronization.py index 5ab65dc9c4ea06e79de6db5f7e6f12c8814402e2..086d7041b44dd5fd0d061702cc14563316ddcb42 100644 --- a/product/ERP5SyncML/SubscriptionSynchronization.py +++ b/product/ERP5SyncML/SubscriptionSynchronization.py @@ -75,7 +75,8 @@ class SubscriptionSynchronization(XMLSyncUtils): self.sendResponse(from_url=subscription.subscription_url, to_url=subscription.publication_url, sync_id=subscription.getTitle(), - xml=xml_a,domain=subscription) + xml=xml_a,domain=subscription, + content_type=subscription.getSyncContentType()) return {'has_response':1,'xml':xml_a} @@ -86,7 +87,7 @@ class SubscriptionSynchronization(XMLSyncUtils): response = None #check if subsync replies to this messages subscription = self.unrestrictedTraverse(subscription_path) if msg==None and (subscription.getSubscriptionUrl()).find('file')>=0: - msg = self.readResponse(sync_id=subscription.getSourceURI(), + msg = self.readResponse(sync_id=subscription.getSubscriptionUrl(), from_url=subscription.getSubscriptionUrl()) if msg==None: response = self.SubSyncInit(subscription) @@ -170,7 +171,8 @@ class SubscriptionSynchronization(XMLSyncUtils): self.sendResponse(from_url=subscription.subscription_url, to_url=subscription.publication_url, sync_id=subscription.getTitle(), - xml=xml_a,domain=subscription) + xml=xml_a,domain=subscription, + content_type=subscription.getSyncContentType()) return {'has_response':1,'xml':xml_a} diff --git a/product/ERP5SyncML/SyncCode.py b/product/ERP5SyncML/SyncCode.py index 2b86df1bff48550e15d5fa9b2ef9bea51a05c735..e4883ee8fb9cf477bcfb517f530cc00c46e0fbc9 100644 --- a/product/ERP5SyncML/SyncCode.py +++ b/product/ERP5SyncML/SyncCode.py @@ -37,8 +37,9 @@ class SyncCode(Persistent): # SyncML Alert Codes TWO_WAY = 200 SLOW_SYNC = 201 # This means we get the data from the publication - WAITING_DATA = 214 ONE_WAY_FROM_SERVER = 204 + WAITING_DATA = 214 + REFRESH_REQUIRED = 508 # SyncML Status Codes SUCCESS = 200 @@ -121,3 +122,9 @@ class SyncCode(Persistent): MEDIA_TYPE = {} MEDIA_TYPE['TEXT_XML'] = 'text/xml' MEDIA_TYPE['TEXT_VCARD'] = 'text/vcard' + MEDIA_TYPE['TEXT_XVCARD'] = 'text/x-vcard' + + #content types : + CONTENT_TYPE = {} + CONTENT_TYPE['SYNCML_XML'] = 'application/vnd.syncml+xml' + CONTENT_TYPE['SYNCML_WBXML'] = 'application/vnd.syncml+wbxml' diff --git a/product/ERP5SyncML/SynchronizationTool.py b/product/ERP5SyncML/SynchronizationTool.py index c8a64a06822edcbb7f8fc8336af55b0a6c85850c..072596b79e461c2de8360df570049624539090f9 100644 --- a/product/ERP5SyncML/SynchronizationTool.py +++ b/product/ERP5SyncML/SynchronizationTool.py @@ -176,10 +176,13 @@ class SynchronizationTool( SubscriptionSynchronization, security.declareProtected(Permissions.ModifyPortalContent, 'manage_addPublication') def manage_addPublication(self, title, publication_url, - destination_path, source_uri, query, xml_mapping, conduit, gpg_key, + destination_path, 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, activity_enabled = False): + authentication_type='', RESPONSE=None, activity_enabled = False, + sync_content_type='application/vnd.syncml+xml', + synchronize_with_erp5_sites=True): """ create a new publication """ @@ -192,7 +195,9 @@ class SynchronizationTool( SubscriptionSynchronization, destination_path, source_uri, query, xml_mapping, conduit, gpg_key, synchronization_id_generator, gid_generator, media_type, auth_required, - authentication_format, authentication_type, activity_enabled) + authentication_format, authentication_type, + activity_enabled, synchronize_with_erp5_sites, + sync_content_type) folder._setObject( new_id, pub ) #if len(self.list_publications) == 0: # self.list_publications = PersistentMapping() @@ -207,7 +212,10 @@ class SynchronizationTool( SubscriptionSynchronization, xml_mapping, conduit, gpg_key, synchronization_id_generator=None, gid_generator=None, media_type=None, login=None, password=None, - RESPONSE=None, activity_enabled=False, alert_code=SyncCode.TWO_WAY): + RESPONSE=None, activity_enabled=False, + alert_code=SyncCode.TWO_WAY, + synchronize_with_erp5_sites = True, + sync_content_type='application/vnd.syncml+xml'): """ XXX should be renamed as addSubscription create a new subscription @@ -221,7 +229,8 @@ class SynchronizationTool( SubscriptionSynchronization, destination_path, source_uri, target_uri, query, xml_mapping, conduit, gpg_key, synchronization_id_generator, gid_generator, media_type, - login, password, activity_enabled, alert_code) + login, password, activity_enabled, alert_code, + synchronize_with_erp5_sites, sync_content_type) folder._setObject( new_id, sub ) #if len(self.list_subscriptions) == 0: # self.list_subscriptions = PersistentMapping() @@ -236,7 +245,9 @@ class SynchronizationTool( SubscriptionSynchronization, conduit, gpg_key, synchronization_id_generator, gid_generator, media_type=None, auth_required=0, authentication_format='', authentication_type='', - RESPONSE=None, activity_enabled=False): + RESPONSE=None, activity_enabled=False, + sync_content_type='application/vnd.syncml+xml', + synchronize_with_erp5_sites=False): """ modify a publication """ @@ -256,6 +267,8 @@ class SynchronizationTool( SubscriptionSynchronization, pub.setAuthentication(auth_required) pub.setAuthenticationFormat(authentication_format) pub.setAuthenticationType(authentication_type) + pub.setSyncContentType(sync_content_type) + pub.setSynchronizeWithERP5Sites(synchronize_with_erp5_sites) if RESPONSE is not None: RESPONSE.redirect('managePublications') @@ -265,7 +278,9 @@ class SynchronizationTool( SubscriptionSynchronization, def manage_editSubscription(self, title, publication_url, subscription_url, destination_path, source_uri, target_uri, query, xml_mapping, conduit, gpg_key, synchronization_id_generator, gid_generator, media_type=None, - login='', password='', RESPONSE=None, activity_enabled=False, alert_code=SyncCode.TWO_WAY): + login='', password='', RESPONSE=None, activity_enabled=False, + alert_code=SyncCode.TWO_WAY, synchronize_with_erp5_sites=False, + sync_content_type='application/vnd.syncml+xml'): """ modify a subscription """ @@ -286,6 +301,8 @@ class SynchronizationTool( SubscriptionSynchronization, sub.setMediaType(media_type) sub.setLogin(login) sub.setPassword(password) + sub.setSyncContentType(sync_content_type) + sub.setSynchronizeWithERP5Sites(synchronize_with_erp5_sites) sub.setAlertCode(alert_code) if RESPONSE is not None: @@ -833,7 +850,7 @@ class SynchronizationTool( SubscriptionSynchronization, security.declarePublic('sendResponse') def sendResponse(self, to_url=None, from_url=None, sync_id=None,xml=None, - domain=None, send=1): + domain=None, send=1, content_type='application/vnd.syncml+xml'): """ We will look at the url and we will see if we need to send mail, http response, or just copy to a file. @@ -843,6 +860,12 @@ class SynchronizationTool( SubscriptionSynchronization, #LOG('sendResponse, from_url: ',0,from_url) #LOG('sendResponse, sync_id: ',0,sync_id) #LOG('sendResponse, xml: \n',0,xml) + + if content_type == self.CONTENT_TYPE['SYNCML_WBXML']: + xml = self.xml2wbxml(xml) + #LOG('sendHttpResponse, xml after wbxml: \n',0,self.hexdump(xml)) + + if isinstance(xml, unicode): xml = xml.encode('utf-8') if domain is not None: @@ -878,7 +901,8 @@ class SynchronizationTool( SubscriptionSynchronization, self.activate(activity='RAMQueue').sendHttpResponse(sync_id=sync_id, to_url=to_url, xml=xml, - domain_path=domain.getPath()) + domain_path=domain.getPath(), + content_type=content_type) elif to_url.find('file://')==0: filename = to_url[len('file:/'):] stream = file(filename,'w') @@ -895,10 +919,11 @@ class SynchronizationTool( SubscriptionSynchronization, security.declarePrivate('sendHttpResponse') def sendHttpResponse(self, to_url=None, sync_id=None, xml=None, - domain_path=None ): + domain_path=None, content_type='application/vnd.syncml+xml'): domain = self.unrestrictedTraverse(domain_path) #LOG('sendHttpResponse, self.getPhysicalPath: ',0,self.getPhysicalPath()) #LOG('sendHttpResponse, starting with domain:',0,domain) + #LOG('sendHttpResponse, xml:',0,xml) if domain is not None: if domain.domain_type == self.PUB and not domain.getActivityEnabled(): @@ -920,27 +945,46 @@ class SynchronizationTool( SubscriptionSynchronization, urllib2.install_opener(opener) to_encode = {} head = '<?xml version="1.0" encoding="UTF-8"?>' - to_encode['text'] = head + xml + + if content_type == self.CONTENT_TYPE['SYNCML_WBXML']: + #because xml2wbxml add the head to the xml + to_encode['text'] = xml + else: + to_encode['text'] = head + xml to_encode['sync_id'] = sync_id - headers = {'Content-type': 'application/vnd.syncml+xml'} - + headers = {'User-Agent':'ERP5SyncML', 'Content-Type':content_type} + #XXX bad hack for synchronization with erp5 - if to_url.find('readResponse')<0: - to_url = to_url + '/portal_synchronizations/readResponse' - 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) + # because at this time, when we call the readResponse method, we must + # encode the data with urlencode if we want the readResponse method to + # receive the data's in parameters. + # All this should be improved to not use urlencode in all cases. + # to do this, perhaps use SOAP : + # - http://en.wikipedia.org/wiki/SOAP + # - http://www.contentmanagementsoftware.info/zope/SOAPSupport + # - http://svn.zope.org/soap/trunk/ + + if domain.getSynchronizeWithERP5Sites(): + LOG('Synchronization with another ERP5 instance ...',0,'') + if to_url.find('readResponse')<0: + to_url = to_url + '/portal_synchronizations/readResponse' + encoded = urllib.urlencode(to_encode) + data=encoded + request = urllib2.Request(url=to_url, data=data) + else: + #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() + url_file = urllib2.urlopen(request) + result = url_file.read() except socket.error, msg: self.activate(activity='RAMQueue').sendHttpResponse(to_url=to_url, - sync_id=sync_id, xml=xml, domain_path=domain.getPath()) + sync_id=sync_id, xml=xml, domain_path=domain.getPath(), + content_type=content_type) LOG('sendHttpResponse, socket ERROR:',0,msg) - LOG('sendHttpResponse, url,data',0,(url, data)) + #LOG('sendHttpResponse, url,data',0,(url, data)) return except urllib2.URLError, msg: LOG("sendHttpResponse, can't open url %s :" % to_url, 0, msg) @@ -961,7 +1005,9 @@ class SynchronizationTool( SubscriptionSynchronization, #user = uf.getUserById('syncml').__of__(uf) #newSecurityManager(None, user) #self.activate(activity='RAMQueue').readResponse(sync_id=sync_id,text=result) + self.readResponse(sync_id=sync_id,text=result) + return result security.declarePublic('sync') def sync(self): @@ -986,6 +1032,8 @@ class SynchronizationTool( SubscriptionSynchronization, response, or just copy to a file. """ #LOG('readResponse, text :', 0, text) + #LOG('readResponse, text :', 0, self.hexdump(text)) + # Login as a manager to make sure we can create objects uf = self.acl_users user = uf.getUserById('syncml').__of__(uf) @@ -1001,10 +1049,12 @@ class SynchronizationTool( SubscriptionSynchronization, for publication in self.getPublicationList(): if publication.getTitle()==sync_id: gpg_key = publication.getGPGKey() + domain = publication if gpg_key == '': for subscription in self.getSubscriptionList(): if subscription.getTitle()==sync_id: gpg_key = subscription.getGPGKey() + domain = subscription # decrypt the message if needed if gpg_key not in (None,''): filename = str(random.randrange(1,2147483600)) + '.txt' @@ -1025,8 +1075,10 @@ class SynchronizationTool( SubscriptionSynchronization, # Get the target and then find the corresponding publication or # Subscription #LOG('type(text) : ',0,type(text)) + if domain.getSyncContentType() == self.CONTENT_TYPE['SYNCML_WBXML']: + text = self.wbxml2xml(text) + #LOG('readResponse, text after wbxml :\n', 0, text) xml = Parse(text) - #XXX this function is not very optimized and should be improved url = self.getTarget(xml) for publication in self.getPublicationList(): if publication.getPublicationUrl()==url and \ @@ -1042,6 +1094,8 @@ class SynchronizationTool( SubscriptionSynchronization, xml = result['xml'] #must be commented because this method is alredy called #xml = self.sendResponse(xml=xml,domain=publication,send=0) + if publication.getSyncContentType() == self.CONTENT_TYPE['SYNCML_WBXML']: + xml = self.xml2wbxml(xml) return xml for subscription in self.getSubscriptionList(): @@ -1096,4 +1150,41 @@ class SynchronizationTool( SubscriptionSynchronization, conduit_object = getattr(conduit_module, conduit)() return conduit_object.addNode(**kw) + def hexdump(self, raw=''): + """ + this function is used to display the raw in a readable format : + it display raw in hexadecimal format and display too the printable + characters (because if not printable characters are printed, it makes + terminal display crash) + """ + buf = "" + line = "" + start = 0 + done = False + while not done: + end = start + 16 + max = len(str(raw)) + if end > max: + end = max + done = True + chunk = raw[start:end] + for i in xrange(len(chunk)): + if i > 0: + spacing = " " + else: + spacing = "" + buf += "%s%02x" % (spacing, ord(chunk[i])) + if done: + for i in xrange(16 - (end % 16)): + buf += " " + buf += " " + for c in chunk: + val = ord(c) + if val >= 33 and val <= 126: + buf += c + else: + buf += "." + buf += "\n" + start += 16 + return buf InitializeClass( SynchronizationTool ) diff --git a/product/ERP5SyncML/XMLSyncUtils.py b/product/ERP5SyncML/XMLSyncUtils.py index eeada2d58e97402ebf437bb4beb6b558a59ac79f..56077b11f3b165105ee3e69c04a5e9e3ac71a08f 100644 --- a/product/ERP5SyncML/XMLSyncUtils.py +++ b/product/ERP5SyncML/XMLSyncUtils.py @@ -112,7 +112,7 @@ class XMLSyncUtilsMixin(SyncCode): xml(' <LocURI>%s</LocURI>\n' % source) xml(' </Source>\n') xml(' <Meta>\n') - xml(' <Anchor xmlns=\'syncml:metinf\'>\n') + xml(' <Anchor>\n') xml(' <Last>%s</Last>\n' % last_anchor) xml(' <Next>%s</Next>\n' % next_anchor) xml(' </Anchor>\n') @@ -122,30 +122,87 @@ class XMLSyncUtilsMixin(SyncCode): xml_a = ''.join(xml_list) return xml_a - def SyncMLStatus(self, cmd_id, target_ref, source_ref, sync_code, - next_anchor=None): + def SyncMLStatus(self, remote_xml, data_code, cmd_id, next_anchor, + subscription=None): """ - Since the Status section is always almost the same, this is the - way to set one quickly. + return a status bloc with all status corresponding to the syncml + commands in remote_xml """ + + #list of element in the SyncBody bloc + syncbody_element_list = remote_xml.xpath('//SyncBody/*') + + message_id = self.getMessageId(remote_xml) xml_list = [] xml = xml_list.append - xml(' <Status>\n') - xml(' <CmdID>%s</CmdID>\n' % cmd_id) - xml(' <TargetRef>%s</TargetRef>\n' % target_ref) - xml(' <SourceRef>%s</SourceRef>\n' % source_ref) - xml(' <Data>%s</Data>\n' % sync_code) - if next_anchor is not None: - xml(' <Item>\n') - xml(' <Data>\n') - xml(' <Anchor xmlns=\'syncml:metinf\'>\n') - xml(' <Next>%s</Next>\n' % next_anchor) - xml(' </Anchor>\n') - xml(' </Data>\n') - xml(' </Item>\n') - xml(' </Status>\n') + + if data_code != self.AUTH_REQUIRED:#because for AUTH_REQUIRED, SyncMLChal is #called + # 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' % self.getMessageId(remote_xml)) + 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')) + if isinstance(data_code, int): + data_code = str(data_code) + xml(' <Data>%s</Data>\n' % data_code) + xml(' </Status>\n') + #add the status bloc corresponding to the receive command + for syncbody_element in syncbody_element_list: + + #LOG('SyncMLStatus : ',0,"command:%s, subscription:%s" % (str(syncbody_element.nodeName), subscription)) + if str(syncbody_element.nodeName) not in ('Status', 'Final', 'Get'): + 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' % str(self.ITEM_ADDED)) + elif syncbody_element.nodeName.encode('utf-8') == 'Alert' and \ + syncbody_element.xpath('string(.//Data)').encode('utf-8') == \ + str(self.SLOW_SYNC): + xml(' <Data>%s</Data>\n' % str(self.REFRESH_REQUIRED)) + else: + xml(' <Data>%s</Data>\n' % str(self.SUCCESS)) + + # if syncbody_element.xpath('.//Item') not in ([], None, '') and\ + # syncbody_element.xpath('.//Item.....'): #contient une ancre Next... + if str(syncbody_element.nodeName) == 'Alert': + xml(' <Item>\n') + xml(' <Data>\n') + xml(' <Anchor>\n') + xml(' <Next>%s</Next>\n' % next_anchor) + xml(' </Anchor>\n') + xml(' </Data>\n') + xml(' </Item>\n') + xml(' </Status>\n') + + if str(syncbody_element.nodeName) == 'Get' and subscription != None: + cmd_ref = syncbody_element.xpath('string(.//CmdID)').encode('utf-8') + syncml_result = self.SyncMLPut(cmd_id, subscription, markup='Results', + cmd_ref=cmd_ref, message_id=self.getMessageId(remote_xml)) + xml(syncml_result) + cmd_id += 1 xml_a = ''.join(xml_list) - return xml_a + return {'xml':xml_a, 'cmd_id':cmd_id} def SyncMLConfirmation(self, cmd_id=None, target_ref=None, cmd=None, sync_code=None, msg_ref=None, cmd_ref=None, source_ref=None, @@ -191,6 +248,8 @@ class XMLSyncUtilsMixin(SyncCode): xml = xml_list.append xml(' <Status>\n') xml(' <CmdID>%s</CmdID>\n' % cmd_id) + xml(' <MsgRef>1</MsgRef>\n') + xml(' <CmdRef>0</CmdRef>\n') xml(' <Cmd>%s</Cmd>\n' % cmd) xml(' <TargetRef>%s</TargetRef>\n' % target_ref) xml(' <SourceRef>%s</SourceRef>\n' % source_ref) @@ -205,9 +264,12 @@ class XMLSyncUtilsMixin(SyncCode): xml_a = ''.join(xml_list) return xml_a - def SyncMLPut(self, cmd_id, subscription): + def SyncMLPut(self, cmd_id, subscription, markup='Put', cmd_ref=None, + message_id=None): """ this is used to inform the server of the CTType version supported + but if the server use it to respond to a Get request, it's a <Result> markup + instead of <Put> """ from Products.ERP5SyncML import Conduit # Import the conduit and get it @@ -222,65 +284,76 @@ class XMLSyncUtilsMixin(SyncCode): globals(), locals(), ['']) conduit = getattr(conduit_module, conduit_name)() #if the conduit support the SyncMLPut : - if hasattr(conduit, 'getCapabilitiesCTType') and \ + if hasattr(conduit, 'getCapabilitiesCTTypeList') 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(' <%s>\n' % markup) + xml(' <CmdID>%s</CmdID>\n' % cmd_id) + if message_id not in (None, ''): + xml(' <MsgRef>%s</MsgRef>\n' % message_id) + if cmd_ref not in (None, '') : + xml(' <CmdRef>%s</CmdRef>\n' % cmd_ref) + 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>Nexedi</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.getPreferedCapabilitieCTType()) + xml(' <VerCT>%s</VerCT>\n' % \ + conduit.getPreferedCapabilitieVerCT()) + xml(' </Rx-Pref>\n') + for type in conduit.getCapabilitiesCTTypeList(): + if type != self.MEDIA_TYPE['TEXT_XML']: + for rx_version in conduit.getCapabilitiesVerCTList(type): + xml(' <Rx>\n') + xml(' <CTType>%s</CTType>\n' % type) + xml(' <VerCT>%s</VerCT>\n' % rx_version) + xml(' </Rx>\n') + + xml(' <Tx-Pref>\n') + xml(' <CTType>%s</CTType>\n' % \ + conduit.getPreferedCapabilitieCTType()) + xml(' <VerCT>%s</VerCT>\n' % \ + conduit.getPreferedCapabilitieVerCT()) + xml(' </Tx-Pref>\n') + for type in conduit.getCapabilitiesCTTypeList(): + if type != self.MEDIA_TYPE['TEXT_XML']: + for tx_version in conduit.getCapabilitiesVerCTList(type): + xml(' <Tx>\n') + xml(' <CTType>%s</CTType>\n' % type) + xml(' <VerCT>%s</VerCT>\n' % tx_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(' </%s>\n' % markup) xml_a = ''.join(xml_list) return xml_a return '' @@ -333,7 +406,7 @@ class XMLSyncUtilsMixin(SyncCode): xml_a = ''.join(xml_list) return xml_a - def deleteXMLObject(self, cmd_id=0, object_gid=None, xml_object=''): + def deleteXMLObject(self, cmd_id=0, object_gid=None, rid=None, xml_object=''): """ Delete an object with the SyncML protocol """ @@ -342,9 +415,14 @@ class XMLSyncUtilsMixin(SyncCode): xml(' <Delete>\n') xml(' <CmdID>%s</CmdID>\n' % cmd_id) xml(' <Item>\n') - xml(' <Source>\n') - xml(' <LocURI>%s</LocURI>\n' % object_gid) - xml(' </Source>\n') + if rid not in (None, ''): + xml(' <Target>\n') + xml(' <LocURI>%s</LocURI>\n' % rid) + xml(' </Target>\n') + else: + xml(' <Source>\n') + xml(' <LocURI>%s</LocURI>\n' % object_gid) + xml(' </Source>\n') #xml(' <Data>\n') #this 2 lines seems to be useless #xml(' </Data>\n') xml(' </Item>\n') @@ -353,7 +431,7 @@ class XMLSyncUtilsMixin(SyncCode): return xml_a def replaceXMLObject(self, cmd_id=0, object=None, xml_string=None, - more_data=0, gid=None, media_type=None): + more_data=0, gid=None, rid=None, media_type=None): """ Replace an object with the SyncML protocol """ @@ -365,9 +443,14 @@ class XMLSyncUtilsMixin(SyncCode): xml(' <Type>%s</Type>\n' % media_type) xml(' </Meta>\n') xml(' <Item>\n') - xml(' <Source>\n') - xml(' <LocURI>%s</LocURI>\n' % str(gid)) - xml(' </Source>\n') + if rid is not None: + xml(' <Target>\n') + xml(' <LocURI>%s</LocURI>\n' % str(rid)) + xml(' </Target>\n') + else: + xml(' <Source>\n') + xml(' <LocURI>%s</LocURI>\n' % str(gid)) + xml(' </Source>\n') xml(' <Data>') xml(xml_string) xml(' </Data>\n') @@ -429,11 +512,7 @@ class XMLSyncUtilsMixin(SyncCode): Return the value of the last anchor, in the alert section of the xml_stream """ - first_node = xml_stream.childNodes[0] - - # Get informations from the body - client_body = first_node.childNodes[3] - last_anchor = client_body.xpath('string(/Alert/Item/Meta/Anchor/Last)') + last_anchor = xml_stream.xpath('string(.//Alert/Item/Meta/Anchor/Last)') last_anchor = last_anchor.encode('utf-8') return last_anchor @@ -442,17 +521,29 @@ class XMLSyncUtilsMixin(SyncCode): Return the value of the next anchor, in the alert section of the xml_stream """ - first_node = xml_stream.childNodes[0] - if first_node.nodeName != "SyncML": - print "This is not a SyncML message" - - # Get informations from the body - client_body = first_node.childNodes[3] - next_anchor = client_body.xpath('string(/Alert/Item/Meta/Anchor/Next)') + next_anchor = xml_stream.xpath('string(.//Alert/Item/Meta/Anchor/Next)') next_anchor = next_anchor.encode('utf-8') return next_anchor def getSourceURI(self, xml): + """ + return the source uri of the data base + """ + source_uri = xml.xpath('string(//SyncBody/Alert/Item/Source/LocURI)') + if isinstance(source_uri, unicode): + source_uri = source_uri.encode('utf-8') + return source_uri + + def getTargetURI(self, xml): + """ + return the target uri of the data base + """ + target_uri = xml.xpath('string(//SyncBody/Alert/Item/Target/LocURI)') + if isinstance(target_uri, unicode): + target_uri = target_uri.encode('utf-8') + return target_uri + + def getSubscriptionUrl(self, xml): """ return the source URI of the syncml header """ @@ -541,6 +632,27 @@ class XMLSyncUtilsMixin(SyncCode): return True return False + def checkMap(self, xml_stream): + """ + Check if there's a Map section in the xml_stream + """ + if xml_stream.xpath('string(SyncML/SyncBody/Map)') \ + not in ('', None, []): + return True + return False + + def setRidWithMap(self, xml_stream, subscriber): + """ + get all the local objects of the given target id and set them the rid with + the given source id (in the Map section) + """ + item_list = xml_stream.xpath('SyncML/SyncBody/Map/MapItem') + for map_item in item_list: + gid = map_item.xpath('string(.//Target/LocURI)').encode('utf-8') + signature = subscriber.getSignatureFromGid(gid) + rid = map_item.xpath('string(.//Source/LocURI)').encode('utf-8') + signature.setRid(rid) + def getAlertCode(self, xml_stream): """ Return the value of the alert code inside the full syncml message @@ -688,7 +800,7 @@ class XMLSyncUtilsMixin(SyncCode): if object is not None, this usually means we want to set the actual xupdate on the signature. """ - #LOG('\ngetSyncMLData starting...',0, domain.getId()) + LOG('getSyncMLData starting...',0,'') if isinstance(conduit, str): conduit = self.getConduitByName(conduit) local_gid_list = [] @@ -714,10 +826,10 @@ class XMLSyncUtilsMixin(SyncCode): 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 + #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) + or '', object_gid=object_gid, rid=rid,cmd_id=cmd_id) cmd_id += 1 #delete Signature if object does not exist anymore for known_gid in subscriber.getGidList(): @@ -726,7 +838,6 @@ class XMLSyncUtilsMixin(SyncCode): local_gid_list = [] loop = 0 for object_path in subscriber.getRemainingObjectPathList(): - #LOG('getRemainingObject :',0,[[subscriber.getRemainingObjectPathList()[i][3] for i in range(5)],[subscriber.getRemainingObjectPathList()[-i][3] for i in range(5)]]) if max is not None and loop >= max: result['finished'] = 0 break @@ -806,9 +917,12 @@ class XMLSyncUtilsMixin(SyncCode): if not signature.checkMD5(xml_object): set_synchronized = 0 # This object has changed on this side, we have to generate some xmldiff - xml_string = self.getXupdateObject( + if subscriber.getMediaType() == self.MEDIA_TYPE['TEXT_XML']: + xml_string = self.getXupdateObject( domain.getXMLFromObject(object), signature.getXML()) + else: #if there is no xml, we re-send all the object + xml_string=domain.getXMLFromObject(object) if xml_string.count('\n') > self.MAX_LINES: # This make comment fails, so we need to replace if xml_string.find('--') >= 0: @@ -828,11 +942,11 @@ class XMLSyncUtilsMixin(SyncCode): signature.setStatus(status) if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']: xml_string = domain.getXMLFromObject(object) - gid = signature.getRid()#in fisrt, we try with rid if there is one - if gid == None: - gid = signature.getGid() + rid = signature.getRid()#in fisrt, we try with rid if there is one + gid = signature.getGid() syncml_data += self.replaceXMLObject(cmd_id=cmd_id, object=object, - gid=gid, xml_string=xml_string, + gid=gid, rid=rid, + xml_string=xml_string, more_data=more_data, media_type=subscriber.getMediaType()) cmd_id += 1 @@ -884,11 +998,10 @@ class XMLSyncUtilsMixin(SyncCode): xml_string = domain.getXMLFromObject(object) #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() + rid = signature.getRid()#in fisrt, we try with rid if there is one + gid = signature.getGid() syncml_data += self.replaceXMLObject(cmd_id=cmd_id, object=object, - gid=gid, xml_string=xml_string, more_data=more_data, + gid=gid, rid=rid, 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 @@ -918,7 +1031,10 @@ class XMLSyncUtilsMixin(SyncCode): has_next_action = 0 destination = self.unrestrictedTraverse(domain.getDestinationPath()) #LOG('applyActionList args',0,'domain : %s\n subscriber : %s\n cmd_id : %s' % (domain, subscriber, cmd_id)) + LOG('applyActionList', 0, self.getSyncActionList(remote_xml)) for action in self.getSyncActionList(remote_xml): + if isinstance(action, unicode): + action = action.encode('utf-8') conflict_list = [] status_code = self.SUCCESS # Thirst we have to check the kind of action it is @@ -939,6 +1055,8 @@ class XMLSyncUtilsMixin(SyncCode): signature.setRid(rid) #LOG('gid == rid ?', 0, 'gid=%s, rid=%s' % (gid, rid)) object = subscriber.getObjectFromGid(gid) + if object == None: + object = subscriber.getObjectFromRid(rid) #LOG('applyActionList subscriber.getObjectFromGid %s' % gid,0,object) if signature is None: #LOG('applyActionList, signature is None',0,signature) @@ -1014,6 +1132,8 @@ class XMLSyncUtilsMixin(SyncCode): if object is not None: #LOG('SyncModif',0,'object: %s will be updated...' % object.id) signature = subscriber.getSignatureFromGid(gid) + if signature is None: + signature = subscriber.getSignatureFromRid(gid) #LOG('SyncModif',0,'previous signature: %s' % str(signature)) previous_xml = signature.getXML() #LOG('SyncModif',0,'previous signature: %i' % len(previous_xml)) @@ -1103,6 +1223,8 @@ class XMLSyncUtilsMixin(SyncCode): # object_gid = status['target'] #else: object_gid = status['source'] + if object_gid in ('', None, []): + object_gid = status['target'] status_code = int(status['code']) if status_cmd in ('Add','Replace'): has_status_list = 1 @@ -1126,7 +1248,8 @@ class XMLSyncUtilsMixin(SyncCode): signature.setStatus(self.SYNCHRONIZED) elif status_cmd == 'Delete': if status_code == self.SUCCESS: - signature = subscriber.getSignatureFromGid(object_gid) or subscriber.getSignatureFromRid(object_gid) + signature = subscriber.getSignatureFromGid(object_gid) or \ + subscriber.getSignatureFromRid(object_gid) if signature is not None: subscriber.delSignature(signature.getGid()) return (destination_waiting_more_data, has_status_list) @@ -1177,20 +1300,20 @@ class XMLSyncUtils(XMLSyncUtilsMixin): """ has_response = 0 #check if syncmodif replies to this messages cmd_id = 1 # specifies a SyncML message-unique command identifier - #LOG('SyncModif',0,'Starting... domain: %s' % str(domain)) + LOG('SyncModif',0,'Starting... domain: %s' % str(domain)) first_node = remote_xml.childNodes[0] # 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') + LOG('PubSyncModif',0,'This is not a SyncML Header') raise ValueError, "Sorry, This is not a SyncML Header" subscriber = domain # If we are the client, this is fine simulate = 0 # used by applyActionList, should be 0 for client if domain.domain_type == self.PUB: simulate = 1 - subscription_url = self.getSourceURI(xml_header) + subscription_url = self.getSubscriptionUrl(xml_header) subscriber = domain.getSubscriber(subscription_url) # We have to check if this message was not already, this can be dangerous @@ -1198,19 +1321,21 @@ class XMLSyncUtils(XMLSyncUtilsMixin): message_id = self.getMessageId(remote_xml) correct_message = subscriber.checkCorrectRemoteMessageId(message_id) if not correct_message: # We need to send again the message - #LOG('SyncModif, no correct message:',0,"sending again...") + LOG('SyncModif, no correct message:',0,"sending again...") last_xml = subscriber.getLastSentMessage() - #LOG("last_xml :", 0, last_xml) + LOG("last_xml :", 0, last_xml) if last_xml != '': has_response = 1 if domain.domain_type == self.PUB: # We always reply self.sendResponse(from_url=domain.publication_url, to_url=subscriber.subscription_url, sync_id=domain.getTitle(), - xml=last_xml,domain=domain) + xml=last_xml,domain=domain, + content_type=domain.getSyncContentType()) elif domain.domain_type == self.SUB: self.sendResponse(from_url=domain.subscription_url, to_url=domain.publication_url, sync_id=domain.getTitle(), - xml=last_xml, domain=domain) + xml=last_xml, domain=domain, + content_type=domain.getSyncContentType()) return {'has_response':has_response,'xml':last_xml} subscriber.setLastSentMessage('') @@ -1250,60 +1375,9 @@ class XMLSyncUtils(XMLSyncUtilsMixin): # 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') + xml_status, cmd_id = self.SyncMLStatus(remote_xml, self.SUCCESS, cmd_id, + subscriber.getNextAnchor(), subscription=subscriber).values() + xml(xml_status) destination_url = '' # alert message if we want more data @@ -1382,24 +1456,14 @@ class XMLSyncUtils(XMLSyncUtilsMixin): 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') + 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') xml(syncml_data) xml(' </Sync>\n') xml(xml_confirmation) @@ -1412,7 +1476,8 @@ class XMLSyncUtils(XMLSyncUtilsMixin): subscriber.setLastSentMessage(xml_a) self.sendResponse(from_url=domain.publication_url, to_url=subscriber.subscription_url, sync_id=domain.getTitle(), - xml=xml_a,domain=domain) + xml=xml_a,domain=domain, + content_type=domain.getSyncContentType()) has_response = 1 elif domain.domain_type == self.SUB: if self.checkAlert(remote_xml) or \ @@ -1421,6 +1486,37 @@ class XMLSyncUtils(XMLSyncUtilsMixin): subscriber.setLastSentMessage(xml_a) self.sendResponse(from_url=domain.subscription_url, to_url=domain.publication_url, sync_id=domain.getTitle(), - xml=xml_a,domain=domain) + xml=xml_a,domain=domain, + content_type=domain.getSyncContentType()) has_response = 1 return {'has_response':has_response,'xml':xml_a} + + def xml2wbxml(self, xml): + """ + convert xml string to wbxml using a temporary file + """ + LOG('xml2wbxml starting ...',0,'') + import os + f = open('/tmp/xml2wbxml', 'w') + f.write(xml) + f.close() + os.system('/usr/bin/xml2wbxml -o /tmp/xml2wbxml /tmp/xml2wbxml') + f = open('/tmp/xml2wbxml', 'r') + wbxml = f.read() + f.close() + return wbxml + + def wbxml2xml(self, wbxml): + """ + convert wbxml string to xml using a temporary file + """ + LOG('wbxml2xml starting ...',0,'') + import os + f = open('/tmp/wbxml2xml', 'w') + f.write(wbxml) + f.close() + os.system('/usr/bin/wbxml2xml -o /tmp/wbxml2xml /tmp/wbxml2xml') + f = open('/tmp/wbxml2xml', 'r') + xml = f.read() + f.close() + return xml diff --git a/product/ERP5SyncML/dtml/explainSynchronizationTool.dtml b/product/ERP5SyncML/dtml/explainSynchronizationTool.dtml index b7fb33a7f839459cdb86646fedd4c4d225dcfa3b..4d75aa2b2a9d232222322774f78e6d0cdc820ea6 100644 --- a/product/ERP5SyncML/dtml/explainSynchronizationTool.dtml +++ b/product/ERP5SyncML/dtml/explainSynchronizationTool.dtml @@ -55,6 +55,12 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <b>Query :</b> the type of objects you want to synchronize</br> <b>XML Mapping :</b> the page template used on each object before an export</br> + <b>Synchronize with other ERP5 sites :</b> The method currently used + to send data to erp5 and external sites is not the same (it will be + improved soon)</br> + <b>Content Type :</b> the type of content use to exchange data + (could be 'application/vnd.syncml+wbxml' or + 'application/vnd.syncml+xml' for example)</br> <b>Conduit :</b> the conduit used to synchronize</br> <b>GPG key name :</b>a name of gpg key to use</br> <b>Id Generator :</b> This set the method name wich allows to @@ -80,6 +86,12 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <b>Query :</b> the type of objects you want to synchronize</br> <b>XML Mapping :</b> the page template used on each object before an export</br> + <b>Synchronize with other ERP5 sites :</b> The method currently used + to send data to erp5 and external sites is not the same (it will be + improved soon)</br> + <b>Content Type :</b> the type of content use to exchange data + (could be 'application/vnd.syncml+wbxml' or + 'application/vnd.syncml+xml' for example)</br> <b>Conduit :</b> the conduit used to synchronize</br> <b>GPG key name :</b>a name of gpg key to use</br> <b>Id Generator :</b> This set the method name wich allows to diff --git a/product/ERP5SyncML/dtml/managePublications.dtml b/product/ERP5SyncML/dtml/managePublications.dtml index 104693b0cae3dd9b81ab7073523cb6637b478c96..124b9990908a5c861ac7a4c44887b73a146cbf89 100644 --- a/product/ERP5SyncML/dtml/managePublications.dtml +++ b/product/ERP5SyncML/dtml/managePublications.dtml @@ -108,6 +108,26 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <input type="text" name="xml_mapping" value="<dtml-var getXMLMapping>" size="40" /> </td> </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Synchronize with other ERP5 sites + </label></div> + </td> + <td align="left" valign="top"> + <input type="checkbox" name="synchronize_with_erp5_sites" value="1" <dtml-if expr="getSynchronizeWithERP5Sites()">CHECKED</dtml-if>> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Content Type + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="sync_content_type" value="<dtml-var getSyncContentType>" size="40" /> + </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 81c2a08937826f63feced97af42a4843502ed742..c9080c922562f036896fd7e5e0a46df104b99104 100644 --- a/product/ERP5SyncML/dtml/manageSubscriptions.dtml +++ b/product/ERP5SyncML/dtml/manageSubscriptions.dtml @@ -144,6 +144,26 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <input type="text" name="xml_mapping" value="<dtml-var getXMLMapping>" size="40" /> </td> </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Synchronize with other ERP5 sites + </label></div> + </td> + <td align="left" valign="top"> + <input type="checkbox" name="synchronize_with_erp5_sites" value="1" <dtml-if expr="getSynchronizeWithERP5Sites()">CHECKED</dtml-if>> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Content Type + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="sync_content_type" value="<dtml-var getSyncContentType>" size="40" /> + </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 57fb9729300503465f063ac02d88ff608f109e5f..cf30f0ca4decffef87a2dc6a80378f555972d48b 100644 --- a/product/ERP5SyncML/dtml/manage_addPublication.dtml +++ b/product/ERP5SyncML/dtml/manage_addPublication.dtml @@ -103,6 +103,26 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <input type="text" name="xml_mapping" size="40" /> </td> </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Synchronize with other ERP5 sites + </label></div> + </td> + <td align="left" valign="top"> + <input type="checkbox" name="synchronize_with_erp5_sites" value="1"> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Content Type + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="sync_content_type" 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 18ffe51981b517636303a1e03512549eeb14ebe5..7af2e44a255c850c919240a03e02e7f7427abb1f 100644 --- a/product/ERP5SyncML/dtml/manage_addSubscription.dtml +++ b/product/ERP5SyncML/dtml/manage_addSubscription.dtml @@ -140,6 +140,26 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <input type="text" name="xml_mapping" size="40" /> </td> </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Synchronize with other ERP5 sites + </label></div> + </td> + <td align="left" valign="top"> + <input type="checkbox" name="synchronize_with_erp5_sites" value="1"> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Content Type + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="sync_content_type" size="40" /> + </td> + </tr> <tr> <td align="left" valign="top"> <div class="form-label">