diff --git a/product/ERP5SyncML/Conduit/ERP5Conduit.py b/product/ERP5SyncML/Conduit/ERP5Conduit.py index 92c1e594e34b49648395d72d94ba3d6a2aa63aff..952651c289c5d8516798c0938a58b5c2c23593a5 100644 --- a/product/ERP5SyncML/Conduit/ERP5Conduit.py +++ b/product/ERP5SyncML/Conduit/ERP5Conduit.py @@ -1166,3 +1166,16 @@ class ERP5Conduit(XMLSyncUtilsMixin): xml_string = buf.getvalue() buf.close() return xml_string + + def getGidFromObject(self, object): + """ + return the Gid composed with the object informations + """ + return object.getId() + + def getGidFromXML(self, xml, gid_from_xml_list): + """ + return the Gid composed with xml informations + """ + return None + diff --git a/product/ERP5SyncML/Conduit/ERP5ConduitTitleGid.py b/product/ERP5SyncML/Conduit/ERP5ConduitTitleGid.py new file mode 100644 index 0000000000000000000000000000000000000000..5e4eb589a92ab7392a81f9c82ebee6e1d497ab04 --- /dev/null +++ b/product/ERP5SyncML/Conduit/ERP5ConduitTitleGid.py @@ -0,0 +1,57 @@ +############################################################################## +# +# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved. +# Fabien Morin <fabien.morin@gmail.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit +from AccessControl import ClassSecurityInfo +from Products.ERP5Type import Permissions +from Products.ERP5SyncML.SyncCode import SyncCode + +from zLOG import LOG + +class ERP5ConduitTitleGid(ERP5Conduit): + """ + ERP5ConduitTitleGid provides two methods who permit to have the GID + The Gid is composed by the title : "FirtName LastName" + this class is made for unit test + """ + + # Declarative security + security = ClassSecurityInfo() + + def getGidFromObject(self, object): + """ + return the Gid composed of FirstName and LastName generate with the object + """ + return object.getTitle() + +# def getGidFromXML(self, xml): +# """ +# return the Gid composed of FirstName and LastName generate with a peace of +# xml +# """ +# #to be defined diff --git a/product/ERP5SyncML/Conduit/SharedVCardConduit.py b/product/ERP5SyncML/Conduit/SharedVCardConduit.py new file mode 100755 index 0000000000000000000000000000000000000000..b1d72f00f98550c065fd7ae79e304bb2a3f283fe --- /dev/null +++ b/product/ERP5SyncML/Conduit/SharedVCardConduit.py @@ -0,0 +1,99 @@ +############################################################################## +# +# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved. +# Fabien Morin <fabien.morin@gmail.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +from Products.ERP5SyncML.Conduit.VCardConduit import VCardConduit +from AccessControl import ClassSecurityInfo +from Products.ERP5Type import Permissions +from Products.ERP5SyncML.SyncCode import SyncCode +from zLOG import LOG, INFO, DEBUG, TRACE + +class SharedVCardConduit(VCardConduit, SyncCode): + """ + A conduit is in charge to read data from a particular structure, + and then to save this data in another structure. + + SharedVCardConduit is a peace of code who provide GID. + This GID are the same for all subscriber, so a same object could be updated + by all the subscriber. + """ + + + # Declarative security + security = ClassSecurityInfo() + + def getGidFromObject(self, object): + """ + return the Gid composed of FirstName_LastName generate with the object + """ + gid_list = [] + if object.getFirstName() not in ('', None): + gid_list.append(object.getFirstName()) + gid_list.append('_') + if object.getLastName() not in ('', None): + gid_list.append(object.getLastName()) + sql_kw = {} + sql_kw['portal_type'] = 'Person' + sql_kw['title'] = object.getTitle() + sql_kw['id'] = '<'+object.getId() + results = object.portal_catalog.countResults(**sql_kw)[0][0] + LOG('getGidFromObject', DEBUG, 'getId:%s, getTitle:%s' % (object.getId(), object.getTitle())) + LOG('getGidFromObject, number of results :', DEBUG, results) + if int(results) > 0: + gid_list.append('__') + gid_list.append(str(int(results)+1)) + gid = ''.join(gid_list) + LOG('getGidFromObject gid :', DEBUG, gid) + return gid + + def getGidFromXML(self, vcard, gid_from_xml_list): + """ + return the Gid composed of FirstName and LastName generate with a vcard + """ + vcard_dict = self.vcard2Dict(vcard) + gid_from_vcard_list = [] + if vcard_dict.has_key('first_name') and \ + vcard_dict['first_name'] not in ('', None): + gid_from_vcard_list.append(vcard_dict['first_name']) + gid_from_vcard_list.append('_') + if vcard_dict.has_key('last_name') and \ + vcard_dict['last_name'] not in ('', None): + gid_from_vcard_list.append(vcard_dict['last_name']) + gid_from_vcard = ''.join(gid_from_vcard_list) + LOG('getGidFromXML, gid_from_vcard :', DEBUG, gid_from_vcard) + number = len([item for item in gid_from_xml_list if item.startswith(gid_from_vcard)]) + LOG('getGidFromXML, gid_from_xml_list :', DEBUG, gid_from_xml_list) + LOG('getGidFromXML, number :', DEBUG, number) + if number > 0: + gid_from_vcard_list.append('__') + gid_from_vcard_list.append(str(number+1)) + #it's mean for 3 persons a a a, the gid will be + #a_, a___2 a___3 + gid_from_vcard = ''.join(gid_from_vcard_list) + LOG('getGidFromXML, returned gid_from_vcard :', DEBUG, gid_from_vcard) + return gid_from_vcard + diff --git a/product/ERP5SyncML/Conduit/VCardConduit.py b/product/ERP5SyncML/Conduit/VCardConduit.py index 40d42e83a5714255c6741df9f97c0b39f7dcdfc0..e698f450952f3538f7a73e3da086abbd31709d01 100755 --- a/product/ERP5SyncML/Conduit/VCardConduit.py +++ b/product/ERP5SyncML/Conduit/VCardConduit.py @@ -64,6 +64,8 @@ class VCardConduit(ERP5Conduit, SyncCode): """ LOG('VCardConduit',0,'addNode, object=%s, object_id=%s, sub_object:%s, \ xml:\n%s' % (str(object), str(object_id), str(sub_object), xml)) + if not isinstance(xml, str): + xml = self.nodeToString(xml) portal_type = 'Person' #the VCard can just use Person if sub_object is None: @@ -135,21 +137,6 @@ class VCardConduit(ERP5Conduit, SyncCode): prefered_type = self.MEDIA_TYPE['TEXT_XVCARD'] return prefered_type - def getGidFromXML(self, vcard): - """ - return the Gid composed of FirstName and LastName - """ - vcard_dict = self.vcard2Dict(vcard) - gid_from_vcard = [] - if vcard_dict.has_key('first_name'): - gid_from_vcard.append(vcard_dict['first_name']) - gid_from_vcard.append(' ') - if vcard_dict.has_key('last_name'): - gid_from_vcard.append(vcard_dict['last_name']) - gid_from_vcard = ''.join(gid_from_vcard) - LOG('gid_from_vcard', 0, gid_from_vcard) - return gid_from_vcard - def changePropertyEncoding(self, property_parameters_list, property_value_list): """ diff --git a/product/ERP5SyncML/ERP5SyncMLMobileServer.py b/product/ERP5SyncML/ERP5SyncMLMobileServer.py new file mode 100755 index 0000000000000000000000000000000000000000..cff9049554e4d0bfd2641a3ab212a5af82f1beb5 --- /dev/null +++ b/product/ERP5SyncML/ERP5SyncMLMobileServer.py @@ -0,0 +1,237 @@ +#!/usr/bin/python +# coding=UTF-8 +import httplib +import urllib,urllib2, os +from Ft.Xml import Parse +import cStringIO +import string +import socket +import time +from optparse import OptionParser +try: + from Ft.Xml.Domlette import Print, PrettyPrint +except ImportError: + LOG('ERP5Conduit',0,"Can't import Print and PrettyPrint") + class Print: + def __init__(self, *args, **kw): + raise ImportError, "Sorry, it was not possible to import Ft library" + + class PrettyPrint: + def __init__(self, *args, **kw): + raise ImportError, "Sorry, it was not possible to import Ft library" + + +class OptionParser(OptionParser): + + def check_required (self, opt): + option = self.get_option(opt) + + # Assumes the option's 'default' is set to None! + if getattr(self.values, option.dest) is None: + self.error("%s option not supplied" % option) + + +parser = OptionParser() +parser.add_option("--host", help="address of this small server (typically, it's the ip of this computer)") +parser.add_option("--publication", help="address of the publication (e.g. http://localhost:9080/erp5Serv)") +parser.add_option("-p", "--port", type="int", help="port used by this server (default is 1234)", default=1234) + +(options, args) = parser.parse_args() + +parser.check_required("--publication") +parser.check_required("--host") + + + +#CONFIGURATION SECTION + +#address of this small server : +#Host = '192.168.242.247' +Host=options.host + +#address of the publication : +#publication_url = 'http://localhost:9080/erp5Serv' +publication_url = options.publication + +#address use to transmit the message received from the external client : +to_url = publication_url+"/portal_synchronizations/readResponse" + +#port of this server : +#Port = 1234 +Port=options.port + +#address of the this server : +syncml_server_url = 'http://'+Host+':'+str(Port) + +#socket : +sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + +#END CONFIGURATION SECTION + +CRLF = "\015\012" +#in unix, it's the same as \r\n, and on windows, it's the same as \n (\r on mac) +#this octal constant just increase a little this application portability + + + + +def nodeToString(node): + """ + return an xml string corresponding to the node + """ + buf = cStringIO.StringIO() + Print(node, stream=buf, encoding='utf-8') + xml_string = buf.getvalue() + buf.close() + return xml_string + +def xml2wbxml(xml): + """ + convert xml string to wbxml using a temporary file + """ + 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(wbxml): + """ + convert wbxml string to xml using a temporary file + """ + 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 + +def hexdump(raw=''): + """ + print raw in readable format without broke the terminal output ! + """ + buf = "" + line = "" + start = 0 + done = False + while not done: + end = start + 16 + max = len(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 + +def getClientUrl(text): + """ + find the client url in the text and return it + """ + document = Parse(text) + client_url = document.xpath('string(//SyncHdr/Source/LocURI)').encode('utf-8') + return client_url + +def sendResponse(text, to_url, client_url): + """ + send the message receive from the external client to erp5 server + """ + result = None + opener = urllib2.build_opener() + urllib2.install_opener(opener) + to_encode = {} + + print '\nsendResponse...' + + text=wbxml2xml(text) + text = text.replace(syncml_server_url, publication_url) + text = text.replace(client_url, syncml_server_url) + + print "text = ",text + to_encode['text'] = text + to_encode['sync_id'] = 'Person' + headers = {'Content-type': 'application/vnd.syncml+xml'} + + encoded = urllib.urlencode(to_encode) + data=encoded + request = urllib2.Request(url=to_url, data=data) + + try: + result = urllib2.urlopen(request).read() + except socket.error, msg: + print 'error, url:%s ,data : %s'%(url, data) + except urllib2.URLError, msg: + print "sendResponse, can't open url : %s" % to_url + + return result + + +def main(): + sock.bind((Host,Port)) + # we just listen to one and unique connection + sock.listen(1) + + text = '' + # the script stop here until a client connect to him + print 'wait for a client connection...' + client, address = sock.accept() + print "the host ",address," is connected." + while 1: + print('\n\nwait for message ...') + msg = client.recv(1024) # we receive 1024 caracter max + if not msg: # if we receive nothing + break + elif not msg.startswith('POST'): + text = text + msg + if text.endswith('\x01\x01'): + client_url = getClientUrl(wbxml2xml(text)) + response = sendResponse(text=text, to_url=to_url, client_url=client_url) + if response not in ('', None): + response = response.replace(syncml_server_url, client_url) + response = response.replace(publication_url, syncml_server_url) + print "\nresponse = \n",response + response = xml2wbxml(response) + print "response send to the phone :\n", hexdump(response) + date_to_print = time.strftime("%a, %d %b %Y %H:%M:%S GMT") + head = CRLF.join(( + "HTTP/1.1 200 OK", + "Date: %s GMT" % date_to_print, + "Server: myPythonServer", + "Content-Length: %s" % len(response), + "Content-Type: application/vnd.syncml+wbxml", + )) + message = "%s%s%s%s" % (head, CRLF, CRLF, response) + #here it's necessary to have 2 CRLF, for more details + #see http://www.w3.org/Protocols/rfc2616/rfc2616.html + client.send(message) + text='' + else: + print "this message is a POST header." + sock.close() + +if __name__ == "__main__": + main() diff --git a/product/ERP5SyncML/Interface/IConduit.py b/product/ERP5SyncML/Interface/IConduit.py index 9afaa5713392ff96a8c2f892fcc8a6225d7ab86d..ff0144cee3560825123595dd656e91e64acfaf82 100644 --- a/product/ERP5SyncML/Interface/IConduit.py +++ b/product/ERP5SyncML/Interface/IConduit.py @@ -110,7 +110,7 @@ class IConduit(Interface): return the Gid composed with the object informations """ - def getGidFromXML(self, xml): + def getGidFromXML(self, xml, gid_from_xml_list): """ return the Gid composed with xml informations """ diff --git a/product/ERP5SyncML/Publication.py b/product/ERP5SyncML/Publication.py index f5ccf2b4d619dd752345a4ebbe6b3687147763b5..fb9e16aa760609098678a7d412ad03fda4292498 100644 --- a/product/ERP5SyncML/Publication.py +++ b/product/ERP5SyncML/Publication.py @@ -144,7 +144,7 @@ class Publication(Subscription): # Constructor def __init__(self, id, title, publication_url, destination_path, source_uri, query, xml_mapping, conduit, gpg_key, id_generator, - gid_generator, media_type, authentication_format, + media_type, authentication_format, authentication_type, activity_enabled, synchronize_with_erp5_sites, sync_content_type): """ @@ -159,7 +159,6 @@ class Publication(Subscription): self.xml_mapping = xml_mapping self.domain_type = self.PUB self.gpg_key = gpg_key - self.setGidGenerator(gid_generator) self.setMediaType(media_type) self.setSynchronizationIdGenerator(id_generator) self.setConduit(conduit) diff --git a/product/ERP5SyncML/PublicationSynchronization.py b/product/ERP5SyncML/PublicationSynchronization.py index be5c0486c6bc53e6e1ab1941d8d5fb8c0c21561c..d924eaec59de296b966b6522997bb0267d967667 100644 --- a/product/ERP5SyncML/PublicationSynchronization.py +++ b/product/ERP5SyncML/PublicationSynchronization.py @@ -234,7 +234,10 @@ class PublicationSynchronization(XMLSyncUtils): result = self.PubSyncInit(publication,xml_client,subscriber=subscriber, sync_type=self.SLOW_SYNC) elif self.checkAlert(xml_client) and \ - alert_code in (self.TWO_WAY, self.SLOW_SYNC, self.ONE_WAY_FROM_SERVER): + alert_code in (self.TWO_WAY, self.SLOW_SYNC, \ + self.ONE_WAY_FROM_SERVER): + subscriber.setXMLMapping(publication.getXMLMapping()) + subscriber.setConduit(publication.getConduit()) result = self.PubSyncInit(publication=publication, xml_client=xml_client, subscriber=subscriber, sync_type=alert_code) else: diff --git a/product/ERP5SyncML/Subscription.py b/product/ERP5SyncML/Subscription.py index 34e241193697a71d30c8fe24b6c25c926324ea32..24e4844028dae92e21de740c11ab39bd18c7e405 100644 --- a/product/ERP5SyncML/Subscription.py +++ b/product/ERP5SyncML/Subscription.py @@ -649,7 +649,7 @@ class Subscription(Folder, SyncCode): # Constructor 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, + conduit, gpg_key, id_generator, media_type, login, password, activity_enabled, alert_code, synchronize_with_erp5_sites, sync_content_type): """ @@ -677,7 +677,6 @@ class Subscription(Folder, SyncCode): self.password=password self.domain_type = self.SUB self.gpg_key = gpg_key - self.setGidGenerator(gid_generator) self.setSynchronizationIdGenerator(id_generator) self.setConduit(conduit) Folder.__init__(self, id) @@ -944,22 +943,6 @@ class Subscription(Folder, SyncCode): xml = func() return xml - def setGidGenerator(self, method): - """ - This set the method name wich allows to find a gid - from any object - """ - if method in (None, '', 'None'): - method = 'getId' - self.gid_generator = method - - def getGidGenerator(self): - """ - This get the method name wich allows to find a gid - from any object - """ - return self.gid_generator - def getMediaType(self): """ This method return the type of media used in this session, @@ -1046,17 +1029,25 @@ class Subscription(Folder, SyncCode): """ o_base = aq_base(object) o_gid = None - gid_gen = self.getGidGenerator() + conduit_name = self.getConduit() + conduit = self.getConduitByName(conduit_name) + gid_gen = getattr(conduit, 'getGidFromObject', None) + LOG('getGidFromObject, Conduit :', DEBUG, conduit_name) + LOG('getGidFromObject, gid_gen:', DEBUG, gid_gen) if callable(gid_gen): o_gid = gid_gen(object) - elif getattr(o_base, gid_gen, None) is not None: - generator = getattr(object, gid_gen) - o_gid = generator() # XXX - used to be o_gid = generator(object=object) which is redundant - elif gid_gen is not None: - # It might be a script python - generator = getattr(object,gid_gen) - o_gid = generator() # XXX - used to be o_gid = generator(object=object) which is redundant + else: + raise ValueError, "The conduit "+conduit_name+"seems to no have a \ + getGidFromObject method and it must" +# elif getattr(o_base, gid_gen, None) is not None: +# generator = getattr(object, gid_gen) +# o_gid = generator() # XXX - used to be o_gid = generator(object=object) which is redundant +# elif gid_gen is not None: +# # It might be a script python +# generator = getattr(object,gid_gen) +# o_gid = generator() # XXX - used to be o_gid = generator(object=object) which is redundant o_gid = b16encode(o_gid) + LOG('getGidFromObject returning', DEBUG, o_gid) return o_gid def getObjectFromGid(self, gid): @@ -1490,3 +1481,23 @@ class Subscription(Folder, SyncCode): retrun the user logged in """ return getattr(self, 'user', None) + + def getConduitByName(self, conduit_name): + """ + Get Conduit Object by given name. + The Conduit can be located in Any Products according to naming Convention + Products.<Product Name>.Conduit.<Conduit Module> ,if conduit_name equal module's name. + By default Conduit must be defined in Products.ERP5SyncML.Conduit.<Conduit Module> + """ + from Products.ERP5SyncML import Conduit + if conduit_name.startswith('Products'): + path = conduit_name + conduit_name = conduit_name.split('.')[-1] + conduit_module = __import__(path, globals(), locals(), ['']) + conduit = getattr(conduit_module, conduit_name)() + else: + conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]), + globals(), locals(), ['']) + conduit = getattr(conduit_module, conduit_name)() + return conduit + diff --git a/product/ERP5SyncML/SyncCode.py b/product/ERP5SyncML/SyncCode.py index 0667d38b4244e1a8f3212ba3f8a95a19e110ff79..dccf0b3eea432fae8ddf35168d8482ffe8ec93d0 100644 --- a/product/ERP5SyncML/SyncCode.py +++ b/product/ERP5SyncML/SyncCode.py @@ -72,7 +72,7 @@ class SyncCode(Persistent): PUB_CONFLICT_CLIENT_WIN = 8 MAX_LINES = 5000 - MAX_OBJECTS = 100 + MAX_OBJECTS = 300 action_tag = 'workflow_action' #NOT_EDITABLE_PROPERTY = ('id','object','uid','xupdate:element',action_tag, diff --git a/product/ERP5SyncML/SynchronizationTool.py b/product/ERP5SyncML/SynchronizationTool.py index 4c0516dedc84aab6957b4377a79be7b95618427c..83519f96d699ed7df295c71150c0d992de770ce5 100644 --- a/product/ERP5SyncML/SynchronizationTool.py +++ b/product/ERP5SyncML/SynchronizationTool.py @@ -178,7 +178,7 @@ class SynchronizationTool( SubscriptionSynchronization, def manage_addPublication(self, title, publication_url, destination_path, source_uri, query, xml_mapping, conduit, gpg_key, - synchronization_id_generator=None, gid_generator=None, + synchronization_id_generator=None, media_type=None, authentication_format='b64', authentication_type='syncml:auth-basic', RESPONSE=None, activity_enabled = False, @@ -195,7 +195,7 @@ class SynchronizationTool( SubscriptionSynchronization, pub = Publication(new_id, title, publication_url, destination_path, source_uri, query, xml_mapping, conduit, gpg_key, synchronization_id_generator, - gid_generator, media_type, + media_type, authentication_format, authentication_type, activity_enabled, synchronize_with_erp5_sites, @@ -212,7 +212,7 @@ class SynchronizationTool( SubscriptionSynchronization, def manage_addSubscription(self, title, publication_url, subscription_url, destination_path, source_uri, target_uri, query, xml_mapping, conduit, gpg_key, - synchronization_id_generator=None, gid_generator=None, + synchronization_id_generator=None, media_type=None, login=None, password=None, RESPONSE=None, activity_enabled=False, alert_code=SyncCode.TWO_WAY, @@ -230,7 +230,7 @@ class SynchronizationTool( SubscriptionSynchronization, sub = Subscription(new_id, title, publication_url, subscription_url, destination_path, source_uri, target_uri, query, xml_mapping, conduit, gpg_key, - synchronization_id_generator, gid_generator, media_type, + synchronization_id_generator, media_type, login, password, activity_enabled, alert_code, synchronize_with_erp5_sites, sync_content_type) folder._setObject( new_id, sub ) @@ -245,7 +245,7 @@ class SynchronizationTool( SubscriptionSynchronization, def manage_editPublication(self, title, publication_url, destination_path, source_uri, query, xml_mapping, conduit, gpg_key, synchronization_id_generator, - gid_generator, media_type=None, + media_type=None, authentication_format='b64', authentication_type='syncml:auth-basic', RESPONSE=None, activity_enabled=False, @@ -265,7 +265,6 @@ class SynchronizationTool( SubscriptionSynchronization, pub.setXMLMapping(xml_mapping) pub.setGPGKey(gpg_key) pub.setSynchronizationIdGenerator(synchronization_id_generator) - pub.setGidGenerator(gid_generator) pub.setMediaType(media_type) pub.setAuthenticationFormat(authentication_format) pub.setAuthenticationType(authentication_type) @@ -279,7 +278,7 @@ class SynchronizationTool( SubscriptionSynchronization, 'manage_editSubscription') 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, + gpg_key, synchronization_id_generator, media_type=None, login='', password='', RESPONSE=None, activity_enabled=False, alert_code=SyncCode.TWO_WAY, synchronize_with_erp5_sites=False, sync_content_type='application/vnd.syncml+xml'): @@ -299,7 +298,6 @@ class SynchronizationTool( SubscriptionSynchronization, sub.setGPGKey(gpg_key) sub.setSubscriptionUrl(subscription_url) sub.setSynchronizationIdGenerator(synchronization_id_generator) - sub.setGidGenerator(gid_generator) sub.setMediaType(media_type) sub.setLogin(login) sub.setPassword(password) diff --git a/product/ERP5SyncML/XMLSyncUtils.py b/product/ERP5SyncML/XMLSyncUtils.py index 0e057816e625ec95b938aad47399b830d6d0416a..9e44cf3ee5d102a67bfc42d9027da70299e371cd 100644 --- a/product/ERP5SyncML/XMLSyncUtils.py +++ b/product/ERP5SyncML/XMLSyncUtils.py @@ -778,10 +778,28 @@ class XMLSyncUtilsMixin(SyncCode): object_list = domain.getObjectList() object_path_list = map(lambda x: x.getPhysicalPath(),object_list) subscriber.setRemainingObjectPathList(object_path_list) + + if subscriber.getMediaType() == self.MEDIA_TYPE['TEXT_VCARD']: + #here the method getGidFromObject don't return the good gid because + #the conduit use the catalog to determine it and object are not yet + #cataloged so if there is many times the same gid, we must count it + gid_not_encoded_list = [] + for object in object_list: + LOG('getSyncMLData :', DEBUG, 'object:%s, object_list:%s, objectTitle:%s, local_gid_list:%s' % (object, object_list, object.getTitle(), local_gid_list)) + gid = b16decode(domain.getGidFromObject(object)) + if gid in gid_not_encoded_list: + number = len([item for item in gid_not_encoded_list if item.startswith(gid)]) + if number > 0: + gid = gid+'__'+str(number+1) + gid_not_encoded_list.append(gid) + local_gid_list.append(b16encode(gid)) + LOG('getSyncMLData :', DEBUG,'gid_not_encoded_list:%s, local_gid_list:%s, gid:%s' % (gid_not_encoded_list, local_gid_list, gid)) + else: + local_gid_list = map(lambda x: domain.getGidFromObject(x),object_list) - local_gid_list = map(lambda x: domain.getGidFromObject(x),object_list) # Objects to remove LOG('remove object to remove ...', DEBUG, '') + object_gid_deleted = [] for object_gid in subscriber.getGidList(): if object_gid not in local_gid_list: # This is an object to remove @@ -792,6 +810,7 @@ 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() + object_gid_deleted.append(object_gid) syncml_data += self.deleteXMLObject( xml_object=signature.getXML() or '', object_gid=object_gid, @@ -1022,6 +1041,7 @@ class XMLSyncUtilsMixin(SyncCode): """ xml_confirmation = '' has_next_action = 0 + gid_from_xml_list = [] destination = self.unrestrictedTraverse(domain.getDestinationPath()) LOG('applyActionList args', DEBUG, 'domain : %s\n subscriber : %s\n cmd_id : %s' % (domain.getPath(), subscriber.getPath(), cmd_id)) LOG('applyActionList', DEBUG, self.getSyncActionList(remote_xml)) @@ -1034,8 +1054,13 @@ class XMLSyncUtilsMixin(SyncCode): partial_data = self.getPartialData(action) rid = self.getActionId(action) if action.nodeName != 'Delete': - if hasattr(conduit, 'getGidFromXML'): - gid = b16encode(conduit.getGidFromXML(self.getDataText(action))) + if hasattr(conduit, 'getGidFromXML') and \ + conduit.getGidFromXML(self.getDataText(action), + gid_from_xml_list) not in ('', None): + gid = conduit.getGidFromXML(self.getDataText(action), + gid_from_xml_list) + gid_from_xml_list.append(gid) + gid = b16encode(gid) else: gid=rid else: diff --git a/product/ERP5SyncML/dtml/managePublications.dtml b/product/ERP5SyncML/dtml/managePublications.dtml index c5616725f71c61ef5c6f2821970d475b3e39e928..140f70422954f1bce6e0f2d647421ce03d16075f 100644 --- a/product/ERP5SyncML/dtml/managePublications.dtml +++ b/product/ERP5SyncML/dtml/managePublications.dtml @@ -158,16 +158,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <input type="text" name="synchronization_id_generator" value="<dtml-var getSynchronizationIdGenerator>" size="40" /> </td> </tr> - <tr> - <td align="left" valign="top"> - <div class="form-label"> - Gid Generator - </label></div> - </td> - <td align="left" valign="top"> - <input type="text" name="gid_generator" value="<dtml-var getGidGenerator>" 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 cb375f4be59a79d24d9d6f5c04b8b6736a4c3a49..930221af1bcf3c856472880f5e0fef1e4a5614cc 100644 --- a/product/ERP5SyncML/dtml/manageSubscriptions.dtml +++ b/product/ERP5SyncML/dtml/manageSubscriptions.dtml @@ -195,16 +195,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <input type="text" name="synchronization_id_generator" value="<dtml-var getSynchronizationIdGenerator>" size="40" /> </td> </tr> - <tr> - <td align="left" valign="top"> - <div class="form-label"> - Gid Generator - </label></div> - </td> - <td align="left" valign="top"> - <input type="text" name="gid_generator" value="<dtml-var getGidGenerator>" 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 b40cb6b6ccb74f898f4a2078b125eca09cc66510..f524de2f30c75243ccf7aa5b53d6c159f403fd0d 100644 --- a/product/ERP5SyncML/dtml/manage_addPublication.dtml +++ b/product/ERP5SyncML/dtml/manage_addPublication.dtml @@ -153,16 +153,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <input type="text" name="synchronization_id_generator" size="40" /> </td> </tr> - <tr> - <td align="left" valign="top"> - <div class="form-label"> - Gid Generator - </label></div> - </td> - <td align="left" valign="top"> - <input type="text" name="gid_generator" 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 630d40bdeda457bdd9934b521491eed845679c75..46ef11d36cac80ef0edacfb732db4cc7e14d79a0 100644 --- a/product/ERP5SyncML/dtml/manage_addSubscription.dtml +++ b/product/ERP5SyncML/dtml/manage_addSubscription.dtml @@ -190,16 +190,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <input type="text" name="synchronization_id_generator" size="40" /> </td> </tr> - <tr> - <td align="left" valign="top"> - <div class="form-label"> - Gid Generator - </label></div> - </td> - <td align="left" valign="top"> - <input type="text" name="gid_generator" size="40" /> - </td> - </tr> <tr> <td align="left" valign="top"> <div class="form-label"> diff --git a/product/ERP5SyncML/tests/testERP5SyncML.py b/product/ERP5SyncML/tests/testERP5SyncML.py index d549746adeff5aa6a5c760504cc00198500d1b1f..1c1fad12e8b8ac3af3266f8133252258e9f2bea0 100644 --- a/product/ERP5SyncML/tests/testERP5SyncML.py +++ b/product/ERP5SyncML/tests/testERP5SyncML.py @@ -346,7 +346,6 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase): xml_mapping=self.xml_mapping, conduit='ERP5Conduit', gpg_key='', - gid_generator='getId', activity_enabled=False, authentication_format='b64', authentication_type='syncml:auth-basic') @@ -370,7 +369,6 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase): xml_mapping=self.xml_mapping, conduit='ERP5Conduit', gpg_key='', - gid_generator='getId', activity_enabled=False, login='fab', password='myPassword') @@ -394,7 +392,6 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase): xml_mapping=self.xml_mapping, conduit='ERP5Conduit', gpg_key='', - gid_generator='getId', activity_enabled=False, login='fab', password='myPassword') @@ -408,15 +405,13 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase): def setupPublicationAndSubscriptionAndGid(self, quiet=0, run=run_all_test): self.setupPublicationAndSubscription(quiet=1,run=1) - def getGid(object): - return object.getTitle() portal_sync = self.getSynchronizationTool() sub1 = portal_sync.getSubscription(self.sub_id1) sub2 = portal_sync.getSubscription(self.sub_id2) pub = portal_sync.getPublication(self.pub_id) - pub.setGidGenerator(getGid) - sub1.setGidGenerator(getGid) - sub2.setGidGenerator(getGid) + sub1.setConduit('ERP5ConduitTitleGid') + sub2.setConduit('ERP5ConduitTitleGid') + pub.setConduit('ERP5ConduitTitleGid') pub.setSynchronizationIdGenerator('_generateNextId') sub1.setSynchronizationIdGenerator('_generateNextId') sub2.setSynchronizationIdGenerator('_generateNextId') @@ -1342,7 +1337,6 @@ class TestERP5SyncML(TestERP5SyncMLMixin, ERP5TypeTestCase): xml_mapping=self.xml_mapping, conduit='ERP5Conduit', gpg_key='', - gid_generator='getId', activity_enabled=False, alert_code = SyncCode.ONE_WAY_FROM_SERVER, login = 'fab', diff --git a/product/ERP5SyncML/tests/testERP5SyncMLVCard.py b/product/ERP5SyncML/tests/testERP5SyncMLVCard.py index 46608a47ec392d0fd211228d198c22a362381a5e..5c9d2f6f5170995552dfd8fb7bfba8d16bbe30dc 100755 --- a/product/ERP5SyncML/tests/testERP5SyncMLVCard.py +++ b/product/ERP5SyncML/tests/testERP5SyncMLVCard.py @@ -66,13 +66,22 @@ class TestERP5SyncMLVCard(TestERP5SyncMLMixin, ERP5TypeTestCase): if not run: return if not quiet: ZopeTestCase._print('\nTest Add a VCard Publication ') - LOG('Testing... ',0,'test_36_AddVCardPublication') + LOG('Testing... ',0,'test_01_AddVCardPublication') portal_id = self.getPortalName() portal_sync = self.getSynchronizationTool() - portal_sync.manage_addPublication(self.pub_id, self.publication_url, - '/%s/person_server' % portal_id, 'Person', 'objectValues', - 'Person_exportAsVCard', 'VCardConduit', '', 'generateNewId', - 'getId', SyncCode.MEDIA_TYPE['TEXT_VCARD']) + portal_sync.manage_addPublication(title=self.pub_id, + publication_url=self.publication_url, + destination_path='/%s/person_server' % portal_id, + source_uri='Person', + query='objectValues', + xml_mapping='Person_exportAsVCard', + conduit='SharedVCardConduit', + gpg_key='', + synchronization_id_generator='generateNewId', + media_type='text/vcard', + activity_enabled=False, + authentication_format='b64', + authentication_type='syncml:auth-basic') pub = portal_sync.getPublication(self.pub_id) self.failUnless(pub is not None) @@ -83,11 +92,21 @@ class TestERP5SyncMLVCard(TestERP5SyncMLMixin, ERP5TypeTestCase): LOG('Testing... ',0,'test_02_AddVCardSubscription1') portal_id = self.getPortalId() portal_sync = self.getSynchronizationTool() - portal_sync.manage_addSubscription(self.sub_id1, self.publication_url, - self.subscription_url1, '/%s/person_client1' % portal_id, - 'Person', 'Person', 'objectValues', 'Person_exportAsVCard', - 'VCardConduit', '', 'generateNewId', 'getId', - SyncCode.MEDIA_TYPE['TEXT_VCARD']) + portal_sync.manage_addSubscription(title=self.sub_id1, + publication_url=self.publication_url, + subscription_url=self.subscription_url1, + destination_path='/%s/person_client1' % portal_id, + source_uri='Person', + target_uri='Person', + query='objectValues', + xml_mapping='Person_exportAsVCard', + conduit='SharedVCardConduit', + gpg_key='', + synchronization_id_generator='generateNewId', + media_type='text/vcard', + activity_enabled=False, + login='fab', + password='myPassword') sub = portal_sync.getSubscription(self.sub_id1) self.failUnless(sub is not None) @@ -98,11 +117,21 @@ class TestERP5SyncMLVCard(TestERP5SyncMLMixin, ERP5TypeTestCase): LOG('Testing... ',0,'test_03_AddVCardSubscription2') portal_id = self.getPortalId() portal_sync = self.getSynchronizationTool() - portal_sync.manage_addSubscription(self.sub_id2, self.publication_url, - self.subscription_url2, '/%s/person_client2' % portal_id, - 'Person', 'Person', 'objectValues', 'Person_exportAsVCard', - 'VCardConduit', '', 'generateNewId', 'getId', - SyncCode.MEDIA_TYPE['TEXT_VCARD']) + portal_sync.manage_addSubscription(title=self.sub_id2, + publication_url=self.publication_url, + subscription_url=self.subscription_url2, + destination_path='/%s/person_client2' % portal_id, + source_uri='Person', + target_uri='Person', + query='objectValues', + xml_mapping='Person_exportAsVCard', + conduit='SharedVCardConduit', + gpg_key='', + synchronization_id_generator='generateNewId', + media_type='text/vcard', + activity_enabled=False, + login='fab', + password='myPassword') sub = portal_sync.getSubscription(self.sub_id2) self.failUnless(sub is not None) @@ -143,7 +172,7 @@ class TestERP5SyncMLVCard(TestERP5SyncMLMixin, ERP5TypeTestCase): if not run: return if not quiet: - ZopeTestCase._print('\nTest Basic VCard Synchronization') + ZopeTestCase._print('\nTest Basic VCard Synchronization ') LOG('Testing... ',0,'test_05_basicVCardSynchronization') self.test_04_FirstVCardSynchronization(quiet=True, run=True) @@ -179,7 +208,7 @@ class TestERP5SyncMLVCard(TestERP5SyncMLMixin, ERP5TypeTestCase): """ if not run: return if not quiet: - ZopeTestCase._print('\nTest No Duplicate Data When Adding') + ZopeTestCase._print('\nTest No Duplicate Data When Adding ') LOG('Testing... ',0,'test_05_verifyNoDuplicateDataWhenAdding') self.test_04_FirstVCardSynchronization(quiet=True, run=True) portal_sync = self.getSynchronizationTool()