Commit a2341479 authored by Aurel's avatar Aurel

new version of ERP5SyncML using ERP5 Objects


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@43048 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent f30cd399
This diff is collapsed.
# -*- coding: utf-8 -*-
############################################################################## ##############################################################################
# #
# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved. # Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
...@@ -29,7 +30,6 @@ ...@@ -29,7 +30,6 @@
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
from Products.ERP5SyncML.SyncCode import SyncCode
from zLOG import LOG from zLOG import LOG
...@@ -49,13 +49,15 @@ class ERP5ConduitTitleGid(ERP5Conduit): ...@@ -49,13 +49,15 @@ class ERP5ConduitTitleGid(ERP5Conduit):
""" """
return object.getTitle() return object.getTitle()
def getGidFromXML(self, xml, namespace, gid_from_xml_list): def getGidFromXML(self, xml, gid_from_xml_list):
""" """
return the Gid composed of FirstName and LastName generate with a peace of return the Gid composed of FirstName and LastName generate with a peace of
xml xml
""" """
first_name = xml.xpath('string(.//syncml:object//syncml:first_name)') first_name = xml.xpath('string(.//syncml:object//syncml:first_name)',
last_name = xml.xpath('string(.//syncml:object//syncml:last_name)') namespaces=xml.nsmap)
last_name = xml.xpath('string(.//syncml:object//syncml:last_name)',
namespaces=xml.nsmap)
gid = "%s %s" % (first_name, last_name) gid = "%s %s" % (first_name, last_name)
if gid in gid_from_xml_list or gid == ' ': if gid in gid_from_xml_list or gid == ' ':
return False return False
......
...@@ -28,7 +28,9 @@ ...@@ -28,7 +28,9 @@
############################################################################## ##############################################################################
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
from Products.ERP5Type import Permissions
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from StringIO import StringIO
# Declarative security # Declarative security
security = ClassSecurityInfo() security = ClassSecurityInfo()
...@@ -42,6 +44,8 @@ class ERP5DocumentConduit(ERP5Conduit): ...@@ -42,6 +44,8 @@ class ERP5DocumentConduit(ERP5Conduit):
# Declarative security # Declarative security
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareProtected(Permissions.AccessContentsInformation, 'getGidFromObject')
def getGidFromObject(self, object): def getGidFromObject(self, object):
""" """
return the Gid generate with the reference, object, language of the object return the Gid generate with the reference, object, language of the object
...@@ -49,4 +53,3 @@ class ERP5DocumentConduit(ERP5Conduit): ...@@ -49,4 +53,3 @@ class ERP5DocumentConduit(ERP5Conduit):
return "%s-%s-%s" %\ return "%s-%s-%s" %\
(object.getReference(), object.getVersion(), object.getLanguage()) (object.getReference(), object.getVersion(), object.getLanguage())
...@@ -46,7 +46,7 @@ from zLOG import LOG ...@@ -46,7 +46,7 @@ from zLOG import LOG
class ERP5ShopOrderConduit(ERP5Conduit): class ERP5ShopOrderConduit(ERP5Conduit):
""" """
This conduit is used in the synchronisation process of Storever and ERP5 to convert This conduit is used in the synchronization process of Storever and ERP5 to convert
a Storever Shop Order to a ERP5 Sale Order. a Storever Shop Order to a ERP5 Sale Order.
Don't forget to add this base categories in portal_category : Don't forget to add this base categories in portal_category :
'hd_size', 'memory_size', 'optical_drive', 'keyboard_layout', 'cpu_type' 'hd_size', 'memory_size', 'optical_drive', 'keyboard_layout', 'cpu_type'
...@@ -297,7 +297,7 @@ class ERP5ShopOrderConduit(ERP5Conduit): ...@@ -297,7 +297,7 @@ class ERP5ShopOrderConduit(ERP5Conduit):
security.declarePrivate('updateObjProperty') security.declarePrivate('updateObjProperty')
def updateObjProperty(self, object, property, kw, key): def updateObjProperty(self, object, property, kw, key):
""" """
This function update the property of an object with a given value stored in a dictionnary. This function help the Conduit to make decision about the synchronisation of values. This function update the property of an object with a given value stored in a dictionnary. This function help the Conduit to make decision about the synchronization of values.
Example of call : self.updateObjProperty(person_object, 'DefaultAddressStreetAddress', kw, 'address') Example of call : self.updateObjProperty(person_object, 'DefaultAddressStreetAddress', kw, 'address')
...@@ -505,7 +505,7 @@ class ERP5ShopOrderConduit(ERP5Conduit): ...@@ -505,7 +505,7 @@ class ERP5ShopOrderConduit(ERP5Conduit):
# # TODO : do the same things for each single information # # TODO : do the same things for each single information
# # TODO : before doing something working well in every case, copy the previou value in the comment field to traceback the modification and let me evaluate the solidity of my algorithm # # TODO : before doing something working well in every case, copy the previou value in the comment field to traceback the modification and let me evaluate the solidity of my algorithm
# # TODO : perhaps it's possible to factorize the code using a generic function # # TODO : perhaps it's possible to factorize the code using a generic function
# Synchronise the street address # Synchronize the street address
# Solution (d'apres seb) # Solution (d'apres seb)
# machin = getattr (object, methos) # machin = getattr (object, methos)
...@@ -706,7 +706,7 @@ class ERP5ShopOrderConduit(ERP5Conduit): ...@@ -706,7 +706,7 @@ class ERP5ShopOrderConduit(ERP5Conduit):
# Create a nice title (without discontinued) from the product title # Create a nice title (without discontinued) from the product title
product_title = self.niceTitle(kw['product_title']) product_title = self.niceTitle(kw['product_title'])
# Synchronise every data # Synchronize every data
product_object.setDescription(kw['product_description']) product_object.setDescription(kw['product_description'])
product_object.setTitle(product_title) product_object.setTitle(product_title)
# # TODO : I don't know where to put this value, # # TODO : I don't know where to put this value,
......
# -*- coding: utf-8 -*-
############################################################################## ##############################################################################
# #
# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved. # Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
...@@ -29,10 +30,9 @@ ...@@ -29,10 +30,9 @@
from Products.ERP5SyncML.Conduit.VCardConduit import VCardConduit from Products.ERP5SyncML.Conduit.VCardConduit import VCardConduit
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
from Products.ERP5SyncML.SyncCode import SyncCode from zLOG import LOG, INFO
from zLOG import LOG, INFO, DEBUG, TRACE
class SharedVCardConduit(VCardConduit, SyncCode): class SharedVCardConduit(VCardConduit):
""" """
A conduit is in charge to read data from a particular structure, A conduit is in charge to read data from a particular structure,
and then to save this data in another structure. and then to save this data in another structure.
...@@ -45,55 +45,52 @@ class SharedVCardConduit(VCardConduit, SyncCode): ...@@ -45,55 +45,52 @@ class SharedVCardConduit(VCardConduit, SyncCode):
# Declarative security # Declarative security
security = ClassSecurityInfo() security = ClassSecurityInfo()
def getGidFromObject(self, object): def getGidFromObject(self, object):
""" """
return the Gid composed of FirstName_LastName generate with the object return the Gid composed of FirstName_LastName generate with the object
""" """
gid_list = [] gid_list = []
if object.getFirstName() not in ('', None): if object.getFirstName():
gid_list.append(object.getFirstName()) gid_list.append(object.getFirstName())
gid_list.append('_') gid_list.append('_')
if object.getLastName() not in ('', None): if object.getLastName():
gid_list.append(object.getLastName()) gid_list.append(object.getLastName())
sql_kw = {} sql_kw = {}
sql_kw['portal_type'] = 'Person' sql_kw['portal_type'] = 'Person'
sql_kw['title'] = object.getTitle() sql_kw['title'] = object.getTitle()
sql_kw['id'] = '<'+object.getId() sql_kw['id'] = {'query': object.getId(), 'range': 'max'}
results = object.portal_catalog.countResults(**sql_kw)[0][0] results = object.portal_catalog.countResults(**sql_kw)[0][0]
LOG('getGidFromObject', DEBUG, 'getId:%s, getTitle:%s' % (object.getId(), object.getTitle())) #LOG('getGidFromObject', INFO, 'getId:%s, getTitle:%s' % (object.getId(), object.getTitle()))
LOG('getGidFromObject, number of results :', DEBUG, results) #LOG('getGidFromObject, number of results :', INFO, results)
if int(results) > 0: if int(results) > 0:
gid_list.append('__') gid_list.append('__')
gid_list.append(str(int(results)+1)) gid_list.append(str(int(results)+1))
gid = ''.join(gid_list) gid = ''.join(gid_list)
LOG('getGidFromObject gid :', DEBUG, gid) #LOG('getGidFromObject gid :', INFO, gid)
return gid return gid
def getGidFromXML(self, vcard, namespace, gid_from_xml_list): def getGidFromXML(self, vcard, gid_from_xml_list):
""" """
return the Gid composed of FirstName and LastName generate with a vcard return the Gid composed of FirstName and LastName generate with a vcard
""" """
vcard_dict = self.vcard2Dict(vcard) vcard_dict = self.vcard2Dict(vcard)
gid_from_vcard_list = [] gid_from_vcard_list = []
if vcard_dict.has_key('first_name') and \ if vcard_dict.get('first_name'):
vcard_dict['first_name'] not in ('', None):
gid_from_vcard_list.append(vcard_dict['first_name']) gid_from_vcard_list.append(vcard_dict['first_name'])
gid_from_vcard_list.append('_') gid_from_vcard_list.append('_')
if vcard_dict.has_key('last_name') and \ if vcard_dict.get('last_name'):
vcard_dict['last_name'] not in ('', None):
gid_from_vcard_list.append(vcard_dict['last_name']) gid_from_vcard_list.append(vcard_dict['last_name'])
gid_from_vcard = ''.join(gid_from_vcard_list) gid_from_vcard = ''.join(gid_from_vcard_list)
LOG('getGidFromXML, gid_from_vcard :', DEBUG, gid_from_vcard) #LOG('getGidFromXML, gid_from_vcard :', INFO, gid_from_vcard)
number = len([item for item in gid_from_xml_list if item.startswith(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, gid_from_xml_list :', INFO, gid_from_xml_list)
LOG('getGidFromXML, number :', DEBUG, number) #LOG('getGidFromXML, number :', INFO, number)
if number > 0: if number:
gid_from_vcard_list.append('__') gid_from_vcard_list.append('__')
gid_from_vcard_list.append(str(number+1)) gid_from_vcard_list.append(str(number+1))
#it's mean for 3 persons a a a, the gid will be #it's mean for 3 persons a a a, the gid will be
#a_, a___2 a___3 #a_, a___2 a___3
gid_from_vcard = ''.join(gid_from_vcard_list) gid_from_vcard = ''.join(gid_from_vcard_list)
LOG('getGidFromXML, returned gid_from_vcard :', DEBUG, gid_from_vcard) #LOG('getGidFromXML, returned gid_from_vcard :', INFO, gid_from_vcard)
return gid_from_vcard return gid_from_vcard
# -*- coding: utf-8 -*-
############################################################################## ##############################################################################
# #
# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved. # Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
...@@ -29,31 +30,22 @@ ...@@ -29,31 +30,22 @@
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
from Products.ERP5Type.Utils import convertToUpperCase import difflib
from Products.CMFCore.utils import getToolByName
from Products.ERP5SyncML.SyncCode import SyncCode
from Products.ERP5SyncML.Subscription import Subscription
from Acquisition import aq_base, aq_inner, aq_chain, aq_acquire
from ZODB.POSException import ConflictError
from zLOG import LOG from zLOG import LOG
class VCardConduit(ERP5Conduit, SyncCode): class VCardConduit(ERP5Conduit):
""" """
A conduit is in charge to read data from a particular structure, A conduit is in charge to read data from a particular structure,
and then to save this data in another structure. and then to save this data in another structure.
VCardConduit is a peace of code to update VCards from text stream VCardConduit is a piece of code to update VCards from text stream
""" """
# Declarative security # Declarative security
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareProtected(Permissions.AccessContentsInformation, '__init__')
def __init__(self):
self.args = {}
security.declareProtected(Permissions.ModifyPortalContent, 'addNode') security.declareProtected(Permissions.ModifyPortalContent, 'addNode')
def addNode(self, xml=None, object=None, previous_xml=None, def addNode(self, xml=None, object=None, previous_xml=None,
...@@ -69,7 +61,8 @@ class VCardConduit(ERP5Conduit, SyncCode): ...@@ -69,7 +61,8 @@ class VCardConduit(ERP5Conduit, SyncCode):
portal_type = 'Person' #the VCard can just use Person portal_type = 'Person' #the VCard can just use Person
if sub_object is None: if sub_object is None:
new_object, reset_local_roles, reset_workflow = ERP5Conduit.constructContent(self, object, object_id, portal_type) new_object, reset_local_roles, reset_workflow =\
ERP5Conduit.constructContent(self, object, object_id, portal_type)
else: #if the object exist, it juste must be update else: #if the object exist, it juste must be update
new_object = sub_object new_object = sub_object
#LOG('addNode', 0, 'new_object:%s, sub_object:%s' % (new_object, sub_object)) #LOG('addNode', 0, 'new_object:%s, sub_object:%s' % (new_object, sub_object))
...@@ -109,7 +102,7 @@ class VCardConduit(ERP5Conduit, SyncCode): ...@@ -109,7 +102,7 @@ class VCardConduit(ERP5Conduit, SyncCode):
""" """
return the a list of CTType capabilities supported return the a list of CTType capabilities supported
""" """
return self.MEDIA_TYPE.values() return ('text/xml', 'text/vcard', 'text/x-vcard',)
def getCapabilitiesVerCTList(self, capabilities_ct_type): def getCapabilitiesVerCTList(self, capabilities_ct_type):
""" """
...@@ -117,8 +110,8 @@ class VCardConduit(ERP5Conduit, SyncCode): ...@@ -117,8 +110,8 @@ class VCardConduit(ERP5Conduit, SyncCode):
""" """
#add here the other version supported #add here the other version supported
verCTTypeList = {} verCTTypeList = {}
verCTTypeList[self.MEDIA_TYPE['TEXT_VCARD']]=('3.0',) verCTTypeList['text/vcard'] = ('3.0',)
verCTTypeList[self.MEDIA_TYPE['TEXT_XVCARD']]=('2.1',) verCTTypeList['text/x-vcard'] = ('2.1',)
return verCTTypeList[capabilities_ct_type] return verCTTypeList[capabilities_ct_type]
def getPreferedCapabilitieVerCT(self): def getPreferedCapabilitieVerCT(self):
...@@ -132,7 +125,7 @@ class VCardConduit(ERP5Conduit, SyncCode): ...@@ -132,7 +125,7 @@ class VCardConduit(ERP5Conduit, SyncCode):
""" """
return the prefered capabilitie VerCT return the prefered capabilitie VerCT
""" """
prefered_type = self.MEDIA_TYPE['TEXT_XVCARD'] prefered_type = 'text/x-vcard'
return prefered_type return prefered_type
def changePropertyEncoding(self, property_parameters_list, def changePropertyEncoding(self, property_parameters_list,
...@@ -143,7 +136,7 @@ class VCardConduit(ERP5Conduit, SyncCode): ...@@ -143,7 +136,7 @@ class VCardConduit(ERP5Conduit, SyncCode):
encoding='' encoding=''
for item in property_parameters_list : for item in property_parameters_list :
if item.has_key('ENCODING'): if ENCODING in item:
encoding = item['ENCODING'] encoding = item['ENCODING']
property_value_list_well_incoded=[] property_value_list_well_incoded=[]
...@@ -218,3 +211,28 @@ class VCardConduit(ERP5Conduit, SyncCode): ...@@ -218,3 +211,28 @@ class VCardConduit(ERP5Conduit, SyncCode):
#LOG('edit_dict =',0,edit_dict) #LOG('edit_dict =',0,edit_dict)
return edit_dict return edit_dict
security.declareProtected(Permissions.ModifyPortalContent,
'replaceIdFromXML')
def replaceIdFromXML(self, xml, attribute_name, new_id, as_string=True):
"""
Return the Same vlue
"""
return xml
def getContentType(self):
"""Content-Type of binded data
"""
return 'text/vcard'
def generateDiff(self, old_data, new_data):
"""return unified diff for plain-text documents
"""
diff = '\n'.join(difflib.unified_diff(old_data.splitlines(),
new_data.splitlines()))
return diff
def applyDiff(self, original_data, diff):
"""Use difflib to patch original_data
"""
raise NotImplementedError('patch unified diff')
...@@ -27,25 +27,13 @@ ...@@ -27,25 +27,13 @@
# #
############################################################################## ##############################################################################
from Products.ERP5Type.Globals import PersistentMapping
from time import gmtime,strftime # for anchors
from SyncCode import SyncCode
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import getToolByName
from Acquisition import Implicit, aq_base
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
from Products.ERP5Type.Core.Folder import Folder
from Products.ERP5Type.Base import Base from Products.ERP5Type.Base import Base
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
from Products.ERP5Type import PropertySheet from Products.ERP5Type import PropertySheet
from DateTime import DateTime
from zLOG import LOG, DEBUG, INFO
import md5 class SyncMLConflict(Base):
from base64 import b64encode, b64decode, b16encode, b16decode
#class Conflict(SyncCode, Implicit):
class Conflict(SyncCode, Base):
""" """
object_path : the path of the obect object_path : the path of the obect
keyword : an identifier of the conflict keyword : an identifier of the conflict
...@@ -53,92 +41,28 @@ class Conflict(SyncCode, Base): ...@@ -53,92 +41,28 @@ class Conflict(SyncCode, Base):
subscriber_value : the value sent by the remote box subscriber_value : the value sent by the remote box
""" """
isIndexable = ConstantGetter('isIndexable', value=False)
# Make sure RAD generated accessors at the class level
def __init__(self, object_path=None, keyword=None, xupdate=None,
publisher_value=None, subscriber_value=None, subscriber=None):
self.object_path=object_path
self.keyword = keyword
self.setLocalValue(publisher_value)
self.setRemoteValue(subscriber_value)
self.subscriber = subscriber
self.resetXupdate()
self.copy_path = None
def getObjectPath(self):
"""
get the object path
"""
return self.object_path
def getPublisherValue(self):
"""
get the domain
"""
return self.publisher_value
def getXupdateList(self):
"""
get the xupdate wich gave an error
"""
xupdate_list = []
if len(self.xupdate)>0:
for xupdate in self.xupdate:
xupdate_list+= [xupdate]
return xupdate_list
def resetXupdate(self): meta_type = 'ERP5 Conflict'
""" portal_type = 'SyncML Conflict'
Reset the xupdate list
"""
self.xupdate = PersistentMapping()
def setXupdate(self, xupdate): # Declarative security
""" security = ClassSecurityInfo()
set the xupdate security.declareObjectProtected(Permissions.AccessContentsInformation)
"""
if xupdate == None:
self.resetXupdate()
else:
self.xupdate = self.getXupdateList() + [xupdate]
def setXupdateList(self, xupdate): property_sheets = ( PropertySheet.Base
""" , PropertySheet.XMLObject
set the xupdate , PropertySheet.CategoryCore
""" , PropertySheet.SyncMLConflict )
self.xupdate = xupdate
def setLocalValue(self, value): def _getPortalSynchronizationTool(self):
""" return getToolByName(self.getPortalObject(), 'portal_synchronizations')
get the domain
"""
try:
self.publisher_value = value
except TypeError: # It happens when we try to store StringIO
self.publisher_value = None
def getSubscriberValue(self):
"""
get the domain
"""
return self.subscriber_value
def setRemoteValue(self, value):
"""
get the domain
"""
try:
self.subscriber_value = value
except TypeError: # It happens when we try to store StringIO
self.subscriber_value = None
def applyPublisherValue(self): def applyPublisherValue(self):
""" """
after a conflict resolution, we have decided after a conflict resolution, we have decided
to keep the local version of this object to keep the local version of this object
""" """
p_sync = getToolByName(self, 'portal_synchronizations') p_sync = self._getPortalSynchronizationTool()
p_sync.applyPublisherValue(self) p_sync.applyPublisherValue(self)
def applyPublisherDocument(self): def applyPublisherDocument(self):
...@@ -146,7 +70,7 @@ class Conflict(SyncCode, Base): ...@@ -146,7 +70,7 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided after a conflict resolution, we have decided
to keep the local version of this object to keep the local version of this object
""" """
p_sync = getToolByName(self, 'portal_synchronizations') p_sync = self._getPortalSynchronizationTool()
p_sync.applyPublisherDocument(self) p_sync.applyPublisherDocument(self)
def getPublisherDocument(self): def getPublisherDocument(self):
...@@ -154,7 +78,7 @@ class Conflict(SyncCode, Base): ...@@ -154,7 +78,7 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided after a conflict resolution, we have decided
to keep the local version of this object to keep the local version of this object
""" """
p_sync = getToolByName(self, 'portal_synchronizations') p_sync = self._getPortalSynchronizationTool()
return p_sync.getPublisherDocument(self) return p_sync.getPublisherDocument(self)
def getPublisherDocumentPath(self): def getPublisherDocumentPath(self):
...@@ -162,7 +86,7 @@ class Conflict(SyncCode, Base): ...@@ -162,7 +86,7 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided after a conflict resolution, we have decided
to keep the local version of this object to keep the local version of this object
""" """
p_sync = getToolByName(self, 'portal_synchronizations') p_sync = self._getPortalSynchronizationTool()
return p_sync.getPublisherDocumentPath(self) return p_sync.getPublisherDocumentPath(self)
def getSubscriberDocument(self): def getSubscriberDocument(self):
...@@ -170,7 +94,7 @@ class Conflict(SyncCode, Base): ...@@ -170,7 +94,7 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided after a conflict resolution, we have decided
to keep the local version of this object to keep the local version of this object
""" """
p_sync = getToolByName(self, 'portal_synchronizations') p_sync = self._getPortalSynchronizationTool()
return p_sync.getSubscriberDocument(self) return p_sync.getSubscriberDocument(self)
def getSubscriberDocumentPath(self): def getSubscriberDocumentPath(self):
...@@ -178,7 +102,7 @@ class Conflict(SyncCode, Base): ...@@ -178,7 +102,7 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided after a conflict resolution, we have decided
to keep the local version of this object to keep the local version of this object
""" """
p_sync = getToolByName(self, 'portal_synchronizations') p_sync = self._getPortalSynchronizationTool()
return p_sync.getSubscriberDocumentPath(self) return p_sync.getSubscriberDocumentPath(self)
def applySubscriberDocument(self): def applySubscriberDocument(self):
...@@ -186,49 +110,18 @@ class Conflict(SyncCode, Base): ...@@ -186,49 +110,18 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided after a conflict resolution, we have decided
to keep the local version of this object to keep the local version of this object
""" """
p_sync = getToolByName(self, 'portal_synchronizations') p_sync = self._getPortalSynchronizationTool()
p_sync.applySubscriberDocument(self) p_sync.applySubscriberDocument(self)
def applySubscriberValue(self, object=None): def applySubscriberValue(self, object=None):
""" """
get the domain get the domain
""" """
p_sync = getToolByName(self, 'portal_synchronizations') p_sync = self._getPortalSynchronizationTool()
p_sync.applySubscriberValue(self, object=object) p_sync.applySubscriberValue(self, object=object)
def setSubscriber(self, subscriber):
"""
set the domain
"""
self.subscriber = subscriber
def getSubscriber(self): def getSubscriber(self):
""" """
get the domain Return the grand parent subscriber
"""
return self.subscriber
def getKeyword(self):
""" """
get the domain return self.getParentValue().getParentValue()
"""
return self.keyword
def getPropertyId(self):
"""
get the property id
"""
return self.keyword
def getCopyPath(self):
"""
Get the path of the copy, or None if none has been made
"""
copy_path = self.copy_path
return copy_path
def setCopyPath(self, path):
"""
"""
self.copy_path = path
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.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.Document.SyncMLSubscription import SyncMLSubscription
from Products.ERP5Type import Permissions
from AccessControl import ClassSecurityInfo
from Products.ERP5SyncML.SyncMLConstant import ACTIVITY_PRIORITY
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
class SyncMLPublication(SyncMLSubscription):
"""Reply to request from SyncML clients,
Serve data to be synchronized.
"""
meta_type = 'ERP5 Publication'
portal_type = 'SyncML Publication' # may be useful in the future...
# Declarative security
security = ClassSecurityInfo()
security.declareProtected(Permissions.AccessContentsInformation,
'getSubscriber')
def getSubscriber(self, subscription_url):
"""
return the subscriber corresponding the to subscription_url
"""
subscriber = None
for subscription in self.contentValues(portal_type='SyncML Subscription'):
if subscription.getSubscriptionUrlString() == subscription_url:
subscriber = subscription
break
return subscriber
security.declareProtected(Permissions.AccessContentsInformation,
'getSubscriberList')
def getSubscriberList(self):
"""
Get the list of subscribers
"""
return self.contentValues(portal_type='SyncML Subscription')
security.declareProtected(Permissions.ModifyPortalContent,
'resetSubscriberList')
def resetSubscriberList(self):
"""
Reset all subscribers
"""
id_list = []
for subscriber in self.contentValues(portal_type='SyncML Subscription'):
subscriber.resetSignatureList()
id_list.append(subscriber.getId())
self.activate(activity='SQLQueue',
tag=self.getId(),
after_tag=id_list,
priority=ACTIVITY_PRIORITY).manage_delObjects(id_list)
security.declareProtected(Permissions.AccessContentsInformation,
'getConflictList')
def getConflictList(self, *args, **kw):
"""
Return the list of conflicts from all subscribers
"""
conflict_list = []
for subscriber in self.getSubscriberList():
conflict_list.extend(subscriber.getConflictList())
return conflict_list
security.declarePrivate('createUnrestrictedSubscriber')
@UnrestrictedMethod
def createUnrestrictedSubscriber(self, **kw):
"""Create a subscriber even if user is anonymous
"""
kw['portal_type'] = 'SyncML Subscription'
return self.newContent(**kw)
This diff is collapsed.
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.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.ERP5Type.Globals import Persistent, PersistentMapping
from SyncCode import SyncCode
from Subscription import Subscription
from Products.ERP5Type import Permissions
from Products.ERP5Type.Core.Folder import Folder
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import PropertySheet
from zLOG import LOG
def addSubscriber( self, id, title='', REQUEST=None ):
"""
Add a new Category and generate UID by calling the
ZSQLCatalog
"""
o = Subscriber( id ,'')
self._setObject( id, o )
if REQUEST is not None:
return self.manage_main(self, REQUEST, update_menu=1)
return o
class Subscriber(Subscription):
"""
This is used to store a subscriber, with :
subscribtion_url
signatures -- a dictionnary which contains the signature
of documents at the time they were synchronized.
last_anchor - it defines the id of the last synchronisation
next_anchor - it defines the id of the current synchronisation
"""
def __init__(self, id, subscription_url):
"""
constructor
"""
self.subscription_url = subscription_url
self.last_anchor = '00000000T000000Z'
self.next_anchor = '00000000T000000Z'
self.session_id = 0
Folder.__init__(self, id)
def ReceiveDocuments(self):
"""
We receive documents from a subsriber
we add if document does not exist
we update if the local signature did not change
we keep as conflict to be solved by user if
local signature changed (between 2 syncs)
"""
def ConfirmReception(self):
"""
?????
Send ACK for a group of documents
"""
def SendDocuments(self):
"""
We send all the updated documents (ie. documents not marked
as conflicting)
"""
def addPublication( self, id, title='', REQUEST=None ):
"""
Add a new Category and generate UID by calling the
ZSQLCatalog
"""
o = Publication( id, '', '', '', '', '')
self._setObject( id, o )
if REQUEST is not None:
return self.manage_main(self, REQUEST, update_menu=1)
return o
class Publication(Subscription):
"""
Publication defined by
publication_url
destination_path - the place where objects are and will be stored
query
xml_mapping
Contains:
list_subscribers -- a list of subsbribtions
"""
meta_type='ERP5 Publication'
portal_type='SyncML Publication' # may be useful in the future...
icon = None
# Declarative properties
property_sheets = ( PropertySheet.Base
, PropertySheet.SimpleItem )
allowed_types = ( 'Signatures',)
# Declarative security
security = ClassSecurityInfo()
security.declareProtected(Permissions.ManagePortal,
'manage_editProperties',
'manage_changeProperties',
'manage_propertiesForm',
)
# Declarative constructors
constructors = (addPublication,)
# Constructor
def __init__(self, id, title, publication_url, destination_path,
source_uri, query, xml_mapping, conduit, gpg_key, id_generator,
media_type, authentication_format,
authentication_type, activity_enabled, synchronize_with_erp5_sites,
sync_content_type):
"""
constructor
"""
self.id = id
self.setActivityEnabled(activity_enabled)
self.publication_url = publication_url
self.destination_path = destination_path
self.setSourceURI(source_uri)
self.setQuery(query)
self.xml_mapping = xml_mapping
self.domain_type = self.PUB
self.gpg_key = gpg_key
self.setMediaType(media_type)
self.setSynchronizationIdGenerator(id_generator)
self.setConduit(conduit)
Folder.__init__(self, id)
self.title = title
self.setAuthenticationFormat(authentication_format)
self.setAuthenticationType(authentication_type)
self.setSyncContentType(sync_content_type)
self.setSynchronizeWithERP5Sites(synchronize_with_erp5_sites)
def getPublicationUrl(self):
"""
return the publication url
"""
return self.publication_url
def getLocalUrl(self):
"""
return the publication url
"""
return self.publication_url
def setPublicationUrl(self, publication_url):
"""
return the publication url
"""
self.publication_url = publication_url
def addSubscriber(self, subscriber):
"""
Add a new subscriber to the publication
"""
# 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()
if new_id is None:
new_id = str(self.generateNewId())
subscriber.id = new_id
self._setObject(new_id, subscriber)
def getSubscriber(self, subscription_url):
"""
return the subscriber corresponding the to subscription_url
"""
o = None
for sub in self.getSubscriberList():
if sub.getSubscriptionUrl() == subscription_url:
o = sub
break
return o
def getSubscriberList(self):
"""
Get the list of subscribers
"""
return self.objectValues()
def delSubscriber(self, subscription_url):
"""
Delete a subscriber for this publication
"""
for o in self.getSubscriberList():
if o.getSubscriptionUrl() == subscription_url:
self.manage_delObjects(o.id)
break
def resetAllSubscribers(self):
"""
Reset all subscribers
"""
id_list = []
for subscriber in self.getSubscriberList():
subscriber.resetAllSignatures()
id_list.append(subscriber.getId())
self.activate(activity='SQLQueue',
tag=self.getId(),
after_tag=id_list,
priority=self.PRIORITY).manage_delObjects(id_list)
def getConflictList(self):
"""
Return the list of conflicts from all subscribers
"""
conflict_list = []
for subscriber in self.getSubscriberList():
conflict_list.extend(subscriber.getConflictList())
return conflict_list
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2003 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.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.
#
##############################################################################
import smtplib # to send emails
from Subscription import Subscription
from Signature import Signature
from XMLSyncUtils import XMLSyncUtils
import commands
from Conduit.ERP5Conduit import ERP5Conduit
from AccessControl import getSecurityManager
from DateTime import DateTime
from zLOG import LOG, DEBUG, INFO
from lxml import etree
from lxml.builder import ElementMaker
from SyncCode import SYNCML_NAMESPACE
nsmap = {'syncml' : SYNCML_NAMESPACE}
E = ElementMaker(namespace=SYNCML_NAMESPACE, nsmap=nsmap)
class SubscriptionSynchronization(XMLSyncUtils):
def SubSyncInit(self, subscription):
"""
Send the first XML message from the client
"""
#LOG('SubSyncInit',0,'starting....')
cmd_id = 1 # specifies a SyncML message-unique command identifier
subscription.NewAnchor()
subscription.initLastMessageId()
#save the actual user to use it in all the session:
user = getSecurityManager().getUser()
subscription.setZopeUser(user)
subscription.setAuthenticated(True)
#create element 'SyncML'
xml = E.SyncML()
# syncml header
xml.append(self.SyncMLHeader(subscription.incrementSessionId(),
subscription.incrementMessageId(), subscription.getPublicationUrl(),
subscription.getSubscriptionUrl(), source_name=subscription.getLogin()))
# syncml body
sync_body = E.SyncBody()
xml.append(sync_body)
# We have to set every object as NOT_SYNCHRONIZED
subscription.startSynchronization()
# alert message
sync_body.append(self.SyncMLAlert(cmd_id, subscription.getSynchronizationType(),
subscription.getTargetURI(),
subscription.getSourceURI(),
subscription.getLastAnchor(),
subscription.getNextAnchor()))
cmd_id += 1
syncml_put = self.SyncMLPut(cmd_id, subscription)
if syncml_put is not None:
sync_body.append(syncml_put)
cmd_id += 1
xml_string = etree.tostring(xml, encoding='utf-8', xml_declaration=True,
pretty_print=True)
self.sendResponse(from_url=subscription.subscription_url,
to_url=subscription.publication_url,
sync_id=subscription.getTitle(),
xml=xml_string, domain=subscription,
content_type=subscription.getSyncContentType())
return {'has_response':1, 'xml':xml_string}
def SubSyncCred (self, subscription, msg=None, RESPONSE=None):
"""
This method send crendentials
"""
cmd_id = 1 # specifies a SyncML message-unique command identifier
#create element 'SyncML' with a default namespace
xml = E.SyncML()
# syncml header
data = "%s:%s" % (subscription.getLogin(), subscription.getPassword())
data = subscription.encode(subscription.getAuthenticationFormat(), data)
xml.append(self.SyncMLHeader(
subscription.incrementSessionId(),
subscription.incrementMessageId(),
subscription.getPublicationUrl(),
subscription.getSubscriptionUrl(),
source_name=subscription.getLogin(),
dataCred=data,
authentication_format=subscription.getAuthenticationFormat(),
authentication_type=subscription.getAuthenticationType()))
# syncml body
sync_body = E.SyncBody()
xml.append(sync_body)
# We have to set every object as NOT_SYNCHRONIZED
subscription.startSynchronization()
# alert message
sync_body.append(self.SyncMLAlert(cmd_id, subscription.getSynchronizationType(),
subscription.getTargetURI(),
subscription.getSourceURI(),
subscription.getLastAnchor(),
subscription.getNextAnchor()))
cmd_id += 1
syncml_put = self.SyncMLPut(cmd_id, subscription)
if syncml_put is not None:
sync_body.append(syncml_put)
cmd_id += 1
sync_body.append(E.Final())
xml_string = etree.tostring(xml, encoding='utf-8', xml_declaration=True,
pretty_print=True)
self.sendResponse(from_url=subscription.subscription_url,
to_url=subscription.publication_url,
sync_id=subscription.getTitle(),
xml=xml_string, domain=subscription,
content_type=subscription.getSyncContentType())
return {'has_response':1, 'xml':xml_string}
def SubSyncModif(self, subscription, xml_client):
"""
Send the client modification, this happens after the Synchronization
initialization
"""
return self.SyncModif(subscription, xml_client)
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.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.ERP5Type.Accessor.TypeDefinition import list_types
from Products.ERP5Type.Globals import Persistent
import re
SYNCML_NAMESPACE = 'SYNCML:SYNCML1.2'
class SyncCode(Persistent):
"""
Class giving the Synchronization's Constants
"""
# SyncML Alert Codes
TWO_WAY = 200
SLOW_SYNC = 201 # This means we get the data from the publication
ONE_WAY_FROM_SERVER = 204
CODE_LIST = ( TWO_WAY, ONE_WAY_FROM_SERVER, )
# SyncML Status Codes
SUCCESS = 200
ITEM_ADDED = 201
WAITING_DATA = 214
REFRESH_REQUIRED = 508
CHUNK_OK = 214
CONFLICT = 409 # A conflict is detected
CONFLICT_MERGE = 207 # We have merged the two versions, sending
# whatever is needed to change(replace)
CONFLICT_CLIENT_WIN = 208 # The client is the "winner", we keep
# the version of the client
UNAUTHORIZED = 401
AUTH_REQUIRED = 407
AUTH_ACCEPTED = 212
# Difference between publication and subscription
PUB = 1
SUB = 0
NULL_ANCHOR = '00000000T000000Z'
# ERP5 Sync Codes
SYNCHRONIZED = 1
SENT = 2
NOT_SENT = 3
PARTIAL = 4
NOT_SYNCHRONIZED = 5
PUB_CONFLICT_MERGE = 6
PUB_CONFLICT_CLIENT_WIN = 8
MAX_LINES = 5000
MAX_OBJECTS = 300
action_tag = 'workflow_action'
#NOT_EDITABLE_PROPERTY = ('id','object','uid','xupdate:element',action_tag,
# 'xupdate:attribute','local_role')
XUPDATE_INSERT = ('xupdate:insert-after','xupdate:insert-before')
XUPDATE_ADD = ('xupdate:append',)
XUPDATE_DEL = ('xupdate:remove',)
XUPDATE_UPDATE = ('xupdate:update',)
XUPDATE_ELEMENT = ('xupdate:element',)
XUPDATE_INSERT_OR_ADD = tuple(XUPDATE_INSERT) + tuple(XUPDATE_ADD)
XUPDATE_TAG = tuple(XUPDATE_INSERT) + tuple(XUPDATE_ADD) + \
tuple(XUPDATE_UPDATE) + tuple(XUPDATE_DEL)
text_type_list = ('text','string')
list_type_list = list_types
none_type = 'None'
boolean_type = 'boolean'
force_conflict_list = ('layout_and_schema','ModificationDate')
binary_type_list = ('image','file','document','pickle')
date_type_list = ('date',)
dict_type_list = ('dict',)
int_type_list = ('int',)
pickle_type_list = ('object',)
data_type_list = ('data', 'base_data',)
xml_object_tag = 'object'
#history_tag = 'workflow_history'
history_tag = 'workflow_action'
local_role_tag = 'local_role'
local_permission_tag = 'local_permission'
local_permission_list = (local_permission_tag,'/'+local_permission_tag)
local_group_tag = 'local_group'
local_role_list = (local_role_tag,'/'+local_role_tag,
local_group_tag,'/'+local_group_tag)
ADDABLE_PROPERTY = local_role_list + (history_tag,) + local_permission_list
NOT_EDITABLE_PROPERTY = ('id','object','uid','xupdate:attribute') \
+ XUPDATE_ELEMENT + ADDABLE_PROPERTY
attribute_type_exp = re.compile("^.*attribute::type$")
history_exp = re.compile("/%s\[@id='.*'\]" % history_tag)
bad_history_exp = re.compile("/%s\[@id='.*'\]/" % history_tag)
extract_id_from_xpath = re.compile(
"(?P<object_block>(?P<property>[^/]+)\[@"\
"(?P<id_of_id>id|gid)='(?P<object_id>[^']+)'\])")
# Those regular expression are deprecated and keept
# only for backward compatibility
object_exp = re.compile("/object\[@id='.*'\]")
sub_object_exp = re.compile("/object\[@id='.*'\]/")
sub_sub_object_exp = re.compile("/object\[@id='.*'\]/object\[@id='.*'\]/")
#media types :
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'
#Activity priority
PRIORITY = 5
#Namespace
#In SyncML Representation Protocol OMA
#we use URN as format of namespace
# List namespaces supported
URN_LIST = ('SYNCML:SYNCML1.1', 'SYNCML:SYNCML1.2')
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.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.ERP5Type.Globals import Persistent
import re
# Namespaces.
SYNCML_NAMESPACE = 'SYNCML:SYNCML1.2'
# In SyncML Representation Protocol OMA
# we use URN as format of namespace
# List namespaces supported
URN_LIST = ('SYNCML:SYNCML1.1', 'SYNCML:SYNCML1.2')
NSMAP = {'syncml': SYNCML_NAMESPACE}
## SyncML Alert Codes
#TWO_WAY = 200
#SLOW_SYNC = 201 # This means we get the data from the publication
#ONE_WAY_FROM_SERVER = 204
#CODE_LIST = (TWO_WAY, ONE_WAY_FROM_SERVER,)
# SyncML Status Codes
#SUCCESS = 200
#ITEM_ADDED = 201
#WAITING_DATA = 214
#REFRESH_REQUIRED = 508
#CHUNK_OK = 214
#CONFLICT = 409 # A conflict is detected
#CONFLICT_MERGE = 207 # We have merged the two versions, sending
## whatever is needed to change(replace)
#CONFLICT_CLIENT_WIN = 208 # The client is the "winner", we keep
## the version of the client
#UNAUTHORIZED = 401
#AUTH_REQUIRED = 407
#AUTH_ACCEPTED = 212
NULL_ANCHOR = '00000000T000000Z'
# ERP5 Sync Codes for Signatures
SYNCHRONIZED = 1
#SENT = 2
#NOT_SENT = 3
PARTIAL = 4
NOT_SYNCHRONIZED = 5
PUB_CONFLICT_MERGE = 6
PUB_CONFLICT_CLIENT_WIN = 8
#MAX_LINES = 5000
MAX_OBJECTS = 300
MAX_LEN = 1<<16
XUPDATE_INSERT_LIST = ('xupdate:insert-after', 'xupdate:insert-before')
XUPDATE_ADD = 'xupdate:append'
XUPDATE_DEL = 'xupdate:remove'
XUPDATE_UPDATE = 'xupdate:update'
XUPDATE_ELEMENT = 'xupdate:element'
XUPDATE_INSERT_OR_ADD_LIST = XUPDATE_INSERT_LIST + (XUPDATE_ADD,)
ADD_ACTION = 'Add'
REPLACE_ACTION = 'Replace'
##media types :
#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'
#Activity priority
ACTIVITY_PRIORITY = 5
This diff is collapsed.
This diff is collapsed.
# -*- coding: utf-8 -*-
#############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.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.
#
##############################################################################
# Required modules - some modules are imported later to prevent circular deadlocks
import persistent
try:
# Python 2.5 or later
from hashlib import md5 as md5_new
from hashlib import sha1 as sha_new
except ImportError:
# Python 2.4
from md5 import new as md5_new
from sha import new as sha_new
#####################################################
# Avoid importing from (possibly unpatched) Globals
#####################################################
from ZPublisher.HTTPRequest import FileUpload
from OFS.Image import Pdata
from StringIO import StringIO
import transaction
class PdataHelper(persistent.Persistent):
"""Inspired by OFS.Image, this wrapper aim to handle
long string easily to transform them into Pdata
"""
def __init__(self, persistent_object, value):
"""Constructor
- persistent_object: Object contains by storage to access it.
- value: value to wrapp into Pdata if it is a BaseString or a file.
It can be also a Pdata object
"""
self._max_len = 1 << 16
self._data, self.size = self._read_data(persistent_object, value)
self.md5sum = None
def _read_data(self, persistent_object, value):
"""Copied from OFS.Image._read_data
with some modernisation.
Returns always a Pdata and its size
- persistent_object: Object known by storage to access it.
- value: value to wrapp into Pdata
"""
n = self._max_len
if isinstance(value, (str, unicode)):
if isinstance(value, unicode):
value = value.encode('utf-8')
size=len(value)
if size < n:
return Pdata(value), size
# Big string: cut it into smaller chunks
value = StringIO(value)
if isinstance(value, FileUpload) and not value:
raise ValueError, 'File not specified'
if isinstance(value, Pdata):
size = self._read_size_from_pdata(value)
return value, size
# Clear md5sum to force refreshing
self.md5sum = None
seek=value.seek
read=value.read
seek(0,2)
size=end=value.tell()
if size <= 2*n:
seek(0)
return Pdata(read(size)), size
# Make sure we have an _p_jar, even if we are a new object, by
# doing a sub-transaction commit.
transaction.savepoint(optimistic=True)
if persistent_object._p_jar is None:
# Ugh
seek(0)
return Pdata(read(size)), size
# Now we're going to build a linked list from back
# to front to minimize the number of database updates
# and to allow us to get things out of memory as soon as
# possible.
next = None
while end > 0:
pos = end-n
if pos < n:
pos = 0 # we always want at least n bytes
seek(pos)
# Create the object and assign it a next pointer
# in the same transaction, so that there is only
# a single database update for it.
data = Pdata(read(end-pos))
persistent_object._p_jar.add(data)
data.next = next
# Save the object so that we can release its memory.
transaction.savepoint(optimistic=True)
data._p_deactivate()
# The object should be assigned an oid and be a ghost.
assert data._p_oid is not None
assert data._p_state == -1
next = data
end = pos
return next, size
def _digest_md5_hash_from_pdata(self, pdata):
"""Compute hash part by part
"""
md5_hash = md5_new()
next = pdata
while next is not None:
md5_hash.update(next.data)
next = next.next
return md5_hash.hexdigest()
def _read_size_from_pdata(self, pdata):
"""Compute size part by part
"""
size = 0
next = pdata
while next is not None:
size += len(next.data)
next = next.next
return size
def __len__(self):
"""Return size of Pdata value
"""
return self.size
def __str__(self):
"""Return string concatenation
of all Pdata parts
"""
return str(self._data)
def getContentMd5(self):
"""
"""
if self.md5sum is not None:
return self.md5sum
md5sum = self._digest_md5_hash_from_pdata(self._data)
self.md5sum = md5sum
return md5sum
def __getslice__(self, i, j):
"""XXX Could be improved to avoid loading
into memory all Pdata objects
"""
return self.__str__()[i:j]
def getLastPdata(self):
"""return the last Pdata element
of a Pdata chains
"""
pdata = self._data
next = pdata.next
while next is not None:
pdata = next
next = pdata.next
return pdata
This diff is collapsed.
# -*- coding: utf-8 -*-
############################################################################## ##############################################################################
# #
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved. # Copyright (c) 2002-2010 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com> # Jean-Paul Smets-Solanes <jp@nexedi.com>
# #
# WARNING: This program as such is intended to be used by professional # WARNING: This program as such is intended to be used by professional
...@@ -30,27 +31,34 @@ ...@@ -30,27 +31,34 @@
and extended local roles management and extended local roles management
""" """
import sys, Permissions, os
from App.Common import package_home
this_module = sys.modules[ __name__ ]
product_path = package_home( globals() )
this_module._dtmldir = os.path.join( product_path, 'dtml' )
# Update ERP5 Globals # Update ERP5 Globals
from Products.ERP5Type.Utils import initializeProduct, updateGlobals from Products.ERP5Type.Utils import initializeProduct, updateGlobals
import sys, Permissions
this_module = sys.modules[ __name__ ]
document_classes = updateGlobals( this_module, globals(), permissions_module = Permissions)
# Define object classes and tools document_classes = updateGlobals(this_module, globals(),
import SynchronizationTool, Publication, Subscription permissions_module=Permissions)
object_classes = (Subscription.Subscription, Publication.Publication,Publication.Subscriber)
portal_tools = (SynchronizationTool.SynchronizationTool,) import PropertySheet
content_classes = () import interfaces
content_constructors = ()
# Finish installation
def initialize( context ): def initialize( context ):
# Import Product Components
from Tool import SynchronizationTool
import Document import Document
# Define documents, classes, constructors and tools
object_classes = ()
content_constructors = ()
content_classes = ()
portal_tools = (SynchronizationTool.SynchronizationTool,)
# Do initialization step
initializeProduct(context, this_module, globals(), initializeProduct(context, this_module, globals(),
document_module = Document, document_module=Document,
document_classes = document_classes, document_classes=document_classes,
object_classes = object_classes, object_classes=object_classes,
portal_tools = portal_tools, portal_tools=portal_tools,
content_constructors = content_constructors, content_constructors=content_constructors,
content_classes = content_classes) content_classes=content_classes)
# -*- coding: utf-8 -*-
from conduit import IConduit
# -*- coding: utf-8 -*-
############################################################################## ##############################################################################
# #
# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved. # Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
...@@ -35,9 +36,9 @@ class IConduit(Interface): ...@@ -35,9 +36,9 @@ class IConduit(Interface):
- updating an object attributes from an XUpdate XML stream - updating an object attributes from an XUpdate XML stream
(Conduits are not in charge of creating new objects which (Conduits are not in charge of creating new objects which
are eventually missing in a synchronisation process) are eventually missing in a synchronization process)
If an object has be created during a synchronisation process, If an object has be created during a synchronization process,
the way to proceed consists in: the way to proceed consists in:
1- creating an empty instance of the appropriate class 1- creating an empty instance of the appropriate class
...@@ -45,9 +46,9 @@ class IConduit(Interface): ...@@ -45,9 +46,9 @@ class IConduit(Interface):
2- updating that empty instance with the conduit 2- updating that empty instance with the conduit
The first implementation of ERP5 synchronisation The first implementation of ERP5 synchronization
will define a default location to create new objects and will define a default location to create new objects and
a default class. This will be defined at the level of the synchronisation a default class. This will be defined at the level of the synchronization
tool tool
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx
...@@ -116,12 +117,36 @@ class IConduit(Interface): ...@@ -116,12 +117,36 @@ class IConduit(Interface):
""" """
def getGidFromObject(object): def getGidFromObject(object, configurable_gid_dictionary=None):
""" """
return the Gid composed with the object informations return the Gid composed with the object informations
- object is the document on which for we are building the gid.
It is usefull to interogate properties of this document itself.
- configurable_gid_dictionary optional argument which is supposed to be a dictionary
with parameters usefull to build the GID.
property_list = ordered_list of properties to interrogate
prefix = string to add in first place of the outputted string
safe = True or False. True means raise an error if interrogated property is
empty or doesn't exists.
if generated GID is empty an error is allways raised
""" """
def getGidFromXML(xml, namespace, gid_from_xml_list): def getGidFromXML(xml, namespace, gid_from_xml_list):
""" """
return the Gid composed with xml informations return the Gid composed with xml informations
""" """
def applyDiff(original_data, diff):
"""Patch original data with given diff
and return patched data
original_data - can be plain/text or text/xml data
diff - diff generated with difflib or xupdate
"""
def generateDiff(old_data, new_data):
"""return a diff between old_data and new_data
respecting mimetype of handled data.
text/xml => xupdate
plain/text => difflib
"""
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment