[Erp5-report] r15529 - in /erp5/trunk/products/ERP5SyncML: ./ dtml/
nobody at svn.erp5.org
nobody at svn.erp5.org
Tue Aug 7 16:16:47 CEST 2007
Author: seb
Date: Tue Aug 7 16:16:47 2007
New Revision: 15529
URL: http://svn.erp5.org?rev=15529&view=rev
Log:
- add vcard and mobile phone support
- clean some parts
Modified:
erp5/trunk/products/ERP5SyncML/Publication.py
erp5/trunk/products/ERP5SyncML/PublicationSynchronization.py
erp5/trunk/products/ERP5SyncML/Subscription.py
erp5/trunk/products/ERP5SyncML/SubscriptionSynchronization.py
erp5/trunk/products/ERP5SyncML/SyncCode.py
erp5/trunk/products/ERP5SyncML/SynchronizationTool.py
erp5/trunk/products/ERP5SyncML/XMLSyncUtils.py
erp5/trunk/products/ERP5SyncML/dtml/explainSynchronizationTool.dtml
erp5/trunk/products/ERP5SyncML/dtml/managePublications.dtml
erp5/trunk/products/ERP5SyncML/dtml/manageSubscriptions.dtml
erp5/trunk/products/ERP5SyncML/dtml/manage_addPublication.dtml
erp5/trunk/products/ERP5SyncML/dtml/manage_addSubscription.dtml
Modified: erp5/trunk/products/ERP5SyncML/Publication.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5SyncML/Publication.py?rev=15529&r1=15528&r2=15529&view=diff
==============================================================================
--- erp5/trunk/products/ERP5SyncML/Publication.py (original)
+++ erp5/trunk/products/ERP5SyncML/Publication.py Tue Aug 7 16:16:47 2007
@@ -84,16 +84,6 @@
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 @@
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 @@
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 @@
"""
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()
Modified: erp5/trunk/products/ERP5SyncML/PublicationSynchronization.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5SyncML/PublicationSynchronization.py?rev=15529&r1=15528&r2=15529&view=diff
==============================================================================
--- erp5/trunk/products/ERP5SyncML/PublicationSynchronization.py (original)
+++ erp5/trunk/products/ERP5SyncML/PublicationSynchronization.py Tue Aug 7 16:16:47 2007
@@ -52,7 +52,7 @@
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 @@
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)
-
- self.sendResponse(from_url=publication.getPublicationUrl(),
- to_url=subscriber.getSubscriptionUrl(),
- sync_id=publication.getTitle(), xml=xml_a, domain=publication)
-
- 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)
+ xml_status, cmd_id = self.SyncMLStatus(xml_client,
+ self.AUTH_REQUIRED, cmd_id, next_anchor,
+ subscription=subscriber).values()
+ xml(xml_status)
+
+ 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
+
# 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
-
- 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)
-
- self.sendResponse(from_url=publication.getPublicationUrl(),
- to_url=subscriber.getSubscriptionUrl(), sync_id=publication.getTitle(),
- xml=xml_a, domain=publication)
+ # 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(' <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())
+
return {'has_response':1,'xml':xml_a}
@@ -226,7 +218,7 @@
"""
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 @@
#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 @@
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
Modified: erp5/trunk/products/ERP5SyncML/Subscription.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5SyncML/Subscription.py?rev=15529&r1=15528&r2=15529&view=diff
==============================================================================
--- erp5/trunk/products/ERP5SyncML/Subscription.py (original)
+++ erp5/trunk/products/ERP5SyncML/Subscription.py Tue Aug 7 16:16:47 2007
@@ -575,6 +575,7 @@
"""
Add a new Subscribption
"""
+ LOG('addSubscription starting...',0,'')
o = Subscription( id ,'','','','','','')
self._setObject( id, o )
if REQUEST is not None:
@@ -648,7 +649,8 @@
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 @@
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 @@
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,10 +742,25 @@
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 @@
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 @@
def setPublicationUrl(self, publication_url):
"""
- return the publication url
+ set the publication url
"""
self.publication_url = publication_url
@@ -1058,6 +1091,30 @@
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
Modified: erp5/trunk/products/ERP5SyncML/SubscriptionSynchronization.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5SyncML/SubscriptionSynchronization.py?rev=15529&r1=15528&r2=15529&view=diff
==============================================================================
--- erp5/trunk/products/ERP5SyncML/SubscriptionSynchronization.py (original)
+++ erp5/trunk/products/ERP5SyncML/SubscriptionSynchronization.py Tue Aug 7 16:16:47 2007
@@ -75,7 +75,8 @@
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 @@
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 @@
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}
Modified: erp5/trunk/products/ERP5SyncML/SyncCode.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5SyncML/SyncCode.py?rev=15529&r1=15528&r2=15529&view=diff
==============================================================================
--- erp5/trunk/products/ERP5SyncML/SyncCode.py (original)
+++ erp5/trunk/products/ERP5SyncML/SyncCode.py Tue Aug 7 16:16:47 2007
@@ -37,8 +37,9 @@
# SyncML Alert Codes
TWO_WAY = 200
SLOW_SYNC = 201 # This means we get the data from the publication
+ ONE_WAY_FROM_SERVER = 204
WAITING_DATA = 214
- ONE_WAY_FROM_SERVER = 204
+ REFRESH_REQUIRED = 508
# SyncML Status Codes
SUCCESS = 200
@@ -121,3 +122,9 @@
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'
Modified: erp5/trunk/products/ERP5SyncML/SynchronizationTool.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5SyncML/SynchronizationTool.py?rev=15529&r1=15528&r2=15529&view=diff
==============================================================================
--- erp5/trunk/products/ERP5SyncML/SynchronizationTool.py (original)
+++ erp5/trunk/products/ERP5SyncML/SynchronizationTool.py Tue Aug 7 16:16:47 2007
@@ -176,10 +176,13 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
#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 @@
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 @@
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 @@
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 @@
#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 @@
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 @@
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 @@
# 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 @@
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 @@
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 )
Modified: erp5/trunk/products/ERP5SyncML/XMLSyncUtils.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5SyncML/XMLSyncUtils.py?rev=15529&r1=15528&r2=15529&view=diff
==============================================================================
--- erp5/trunk/products/ERP5SyncML/XMLSyncUtils.py (original)
+++ erp5/trunk/products/ERP5SyncML/XMLSyncUtils.py Tue Aug 7 16:16:47 2007
@@ -112,7 +112,7 @@
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 @@
xml_a = ''.join(xml_list)
return xml_a
- def SyncMLStatus(self, cmd_id, target_ref, source_ref, sync_code,
- next_anchor=None):
- """
- Since the Status section is always almost the same, this is the
- way to set one quickly.
- """
+ def SyncMLStatus(self, remote_xml, data_code, cmd_id, next_anchor,
+ subscription=None):
+ """
+ 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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
"""
@@ -540,6 +631,27 @@
not in ('', None, []):
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):
"""
@@ -688,7 +800,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
# 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 @@
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 @@
"""
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 @@
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 @@
# 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 @@
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 @@
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 @@
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
Modified: erp5/trunk/products/ERP5SyncML/dtml/explainSynchronizationTool.dtml
URL: http://svn.erp5.org/erp5/trunk/products/ERP5SyncML/dtml/explainSynchronizationTool.dtml?rev=15529&r1=15528&r2=15529&view=diff
==============================================================================
--- erp5/trunk/products/ERP5SyncML/dtml/explainSynchronizationTool.dtml (original)
+++ erp5/trunk/products/ERP5SyncML/dtml/explainSynchronizationTool.dtml Tue Aug 7 16:16:47 2007
@@ -55,6 +55,12 @@
<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 @@
<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
Modified: erp5/trunk/products/ERP5SyncML/dtml/managePublications.dtml
URL: http://svn.erp5.org/erp5/trunk/products/ERP5SyncML/dtml/managePublications.dtml?rev=15529&r1=15528&r2=15529&view=diff
==============================================================================
--- erp5/trunk/products/ERP5SyncML/dtml/managePublications.dtml (original)
+++ erp5/trunk/products/ERP5SyncML/dtml/managePublications.dtml Tue Aug 7 16:16:47 2007
@@ -111,6 +111,26 @@
<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">
Conduit
</label></div>
</td>
Modified: erp5/trunk/products/ERP5SyncML/dtml/manageSubscriptions.dtml
URL: http://svn.erp5.org/erp5/trunk/products/ERP5SyncML/dtml/manageSubscriptions.dtml?rev=15529&r1=15528&r2=15529&view=diff
==============================================================================
--- erp5/trunk/products/ERP5SyncML/dtml/manageSubscriptions.dtml (original)
+++ erp5/trunk/products/ERP5SyncML/dtml/manageSubscriptions.dtml Tue Aug 7 16:16:47 2007
@@ -147,6 +147,26 @@
<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">
Conduit
</label></div>
</td>
Modified: erp5/trunk/products/ERP5SyncML/dtml/manage_addPublication.dtml
URL: http://svn.erp5.org/erp5/trunk/products/ERP5SyncML/dtml/manage_addPublication.dtml?rev=15529&r1=15528&r2=15529&view=diff
==============================================================================
--- erp5/trunk/products/ERP5SyncML/dtml/manage_addPublication.dtml (original)
+++ erp5/trunk/products/ERP5SyncML/dtml/manage_addPublication.dtml Tue Aug 7 16:16:47 2007
@@ -106,6 +106,26 @@
<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">
Conduit
</label></div>
</td>
Modified: erp5/trunk/products/ERP5SyncML/dtml/manage_addSubscription.dtml
URL: http://svn.erp5.org/erp5/trunk/products/ERP5SyncML/dtml/manage_addSubscription.dtml?rev=15529&r1=15528&r2=15529&view=diff
==============================================================================
--- erp5/trunk/products/ERP5SyncML/dtml/manage_addSubscription.dtml (original)
+++ erp5/trunk/products/ERP5SyncML/dtml/manage_addSubscription.dtml Tue Aug 7 16:16:47 2007
@@ -143,6 +143,26 @@
<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">
Conduit
</label></div>
</td>
More information about the Erp5-report
mailing list