Commit a2341479 authored by Aurel's avatar Aurel
Browse files

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.
......@@ -29,7 +30,6 @@
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
......@@ -49,13 +49,15 @@ class ERP5ConduitTitleGid(ERP5Conduit):
"""
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
xml
"""
first_name = xml.xpath('string(.//syncml:object//syncml:first_name)')
last_name = xml.xpath('string(.//syncml:object//syncml:last_name)')
first_name = xml.xpath('string(.//syncml:object//syncml:first_name)',
namespaces=xml.nsmap)
last_name = xml.xpath('string(.//syncml:object//syncml:last_name)',
namespaces=xml.nsmap)
gid = "%s %s" % (first_name, last_name)
if gid in gid_from_xml_list or gid == ' ':
return False
......
......@@ -28,7 +28,9 @@
##############################################################################
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
from Products.ERP5Type import Permissions
from AccessControl import ClassSecurityInfo
from StringIO import StringIO
# Declarative security
security = ClassSecurityInfo()
......@@ -42,6 +44,8 @@ class ERP5DocumentConduit(ERP5Conduit):
# Declarative security
security = ClassSecurityInfo()
security.declareProtected(Permissions.AccessContentsInformation, 'getGidFromObject')
def getGidFromObject(self, object):
"""
return the Gid generate with the reference, object, language of the object
......@@ -49,4 +53,3 @@ class ERP5DocumentConduit(ERP5Conduit):
return "%s-%s-%s" %\
(object.getReference(), object.getVersion(), object.getLanguage())
......@@ -46,7 +46,7 @@ from zLOG import LOG
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.
Don't forget to add this base categories in portal_category :
'hd_size', 'memory_size', 'optical_drive', 'keyboard_layout', 'cpu_type'
......@@ -297,7 +297,7 @@ class ERP5ShopOrderConduit(ERP5Conduit):
security.declarePrivate('updateObjProperty')
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')
......@@ -505,7 +505,7 @@ class ERP5ShopOrderConduit(ERP5Conduit):
# # 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 : perhaps it's possible to factorize the code using a generic function
# Synchronise the street address
# Synchronize the street address
# Solution (d'apres seb)
# machin = getattr (object, methos)
......@@ -706,7 +706,7 @@ class ERP5ShopOrderConduit(ERP5Conduit):
# Create a nice title (without discontinued) from the product title
product_title = self.niceTitle(kw['product_title'])
# Synchronise every data
# Synchronize every data
product_object.setDescription(kw['product_description'])
product_object.setTitle(product_title)
# # TODO : I don't know where to put this value,
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
......@@ -29,10 +30,9 @@
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
from zLOG import LOG, INFO
class SharedVCardConduit(VCardConduit, SyncCode):
class SharedVCardConduit(VCardConduit):
"""
A conduit is in charge to read data from a particular structure,
and then to save this data in another structure.
......@@ -45,55 +45,52 @@ class SharedVCardConduit(VCardConduit, SyncCode):
# 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):
if object.getFirstName():
gid_list.append(object.getFirstName())
gid_list.append('_')
if object.getLastName() not in ('', None):
if object.getLastName():
gid_list.append(object.getLastName())
sql_kw = {}
sql_kw['portal_type'] = 'Person'
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]
LOG('getGidFromObject', DEBUG, 'getId:%s, getTitle:%s' % (object.getId(), object.getTitle()))
LOG('getGidFromObject, number of results :', DEBUG, results)
#LOG('getGidFromObject', INFO, 'getId:%s, getTitle:%s' % (object.getId(), object.getTitle()))
#LOG('getGidFromObject, number of results :', INFO, results)
if int(results) > 0:
gid_list.append('__')
gid_list.append(str(int(results)+1))
gid = ''.join(gid_list)
LOG('getGidFromObject gid :', DEBUG, gid)
#LOG('getGidFromObject gid :', INFO, 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
"""
vcard_dict = self.vcard2Dict(vcard)
gid_from_vcard_list = []
if vcard_dict.has_key('first_name') and \
vcard_dict['first_name'] not in ('', None):
if vcard_dict.get('first_name'):
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):
if vcard_dict.get('last_name'):
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)
#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)])
LOG('getGidFromXML, gid_from_xml_list :', DEBUG, gid_from_xml_list)
LOG('getGidFromXML, number :', DEBUG, number)
if number > 0:
#LOG('getGidFromXML, gid_from_xml_list :', INFO, gid_from_xml_list)
#LOG('getGidFromXML, number :', INFO, number)
if number:
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)
#LOG('getGidFromXML, returned gid_from_vcard :', INFO, gid_from_vcard)
return gid_from_vcard
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
......@@ -29,31 +30,22 @@
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions
from Products.ERP5Type.Utils import convertToUpperCase
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
import difflib
from zLOG import LOG
class VCardConduit(ERP5Conduit, SyncCode):
class VCardConduit(ERP5Conduit):
"""
A conduit is in charge to read data from a particular 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
security = ClassSecurityInfo()
security.declareProtected(Permissions.AccessContentsInformation, '__init__')
def __init__(self):
self.args = {}
security.declareProtected(Permissions.ModifyPortalContent, 'addNode')
def addNode(self, xml=None, object=None, previous_xml=None,
......@@ -69,7 +61,8 @@ class VCardConduit(ERP5Conduit, SyncCode):
portal_type = 'Person' #the VCard can just use Person
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
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):
"""
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):
"""
......@@ -117,8 +110,8 @@ class VCardConduit(ERP5Conduit, SyncCode):
"""
#add here the other version supported
verCTTypeList = {}
verCTTypeList[self.MEDIA_TYPE['TEXT_VCARD']]=('3.0',)
verCTTypeList[self.MEDIA_TYPE['TEXT_XVCARD']]=('2.1',)
verCTTypeList['text/vcard'] = ('3.0',)
verCTTypeList['text/x-vcard'] = ('2.1',)
return verCTTypeList[capabilities_ct_type]
def getPreferedCapabilitieVerCT(self):
......@@ -132,7 +125,7 @@ class VCardConduit(ERP5Conduit, SyncCode):
"""
return the prefered capabilitie VerCT
"""
prefered_type = self.MEDIA_TYPE['TEXT_XVCARD']
prefered_type = 'text/x-vcard'
return prefered_type
def changePropertyEncoding(self, property_parameters_list,
......@@ -143,7 +136,7 @@ class VCardConduit(ERP5Conduit, SyncCode):
encoding=''
for item in property_parameters_list :
if item.has_key('ENCODING'):
if ENCODING in item:
encoding = item['ENCODING']
property_value_list_well_incoded=[]
......@@ -218,3 +211,28 @@ class VCardConduit(ERP5Conduit, SyncCode):
#LOG('edit_dict =',0,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 @@
#
##############################################################################
from Products.ERP5Type.Globals import PersistentMapping
from time import gmtime,strftime # for anchors
from SyncCode import SyncCode
from AccessControl import ClassSecurityInfo
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 import Permissions
from Products.ERP5Type import PropertySheet
from DateTime import DateTime
from zLOG import LOG, DEBUG, INFO
import md5
from base64 import b64encode, b64decode, b16encode, b16decode
#class Conflict(SyncCode, Implicit):
class Conflict(SyncCode, Base):
class SyncMLConflict(Base):
"""
object_path : the path of the obect
keyword : an identifier of the conflict
......@@ -53,92 +41,28 @@ class Conflict(SyncCode, Base):
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):
"""
Reset the xupdate list
"""
self.xupdate = PersistentMapping()
meta_type = 'ERP5 Conflict'
portal_type = 'SyncML Conflict'
def setXupdate(self, xupdate):
"""
set the xupdate
"""
if xupdate == None:
self.resetXupdate()
else:
self.xupdate = self.getXupdateList() + [xupdate]
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
def setXupdateList(self, xupdate):
"""
set the xupdate
"""
self.xupdate = xupdate
property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject
, PropertySheet.CategoryCore
, PropertySheet.SyncMLConflict )
def setLocalValue(self, value):
"""
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 _getPortalSynchronizationTool(self):
return getToolByName(self.getPortalObject(), 'portal_synchronizations')
def applyPublisherValue(self):
"""
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = getToolByName(self, 'portal_synchronizations')
p_sync = self._getPortalSynchronizationTool()
p_sync.applyPublisherValue(self)
def applyPublisherDocument(self):
......@@ -146,7 +70,7 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = getToolByName(self, 'portal_synchronizations')
p_sync = self._getPortalSynchronizationTool()
p_sync.applyPublisherDocument(self)
def getPublisherDocument(self):
......@@ -154,7 +78,7 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = getToolByName(self, 'portal_synchronizations')
p_sync = self._getPortalSynchronizationTool()
return p_sync.getPublisherDocument(self)
def getPublisherDocumentPath(self):
......@@ -162,7 +86,7 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = getToolByName(self, 'portal_synchronizations')
p_sync = self._getPortalSynchronizationTool()
return p_sync.getPublisherDocumentPath(self)
def getSubscriberDocument(self):
......@@ -170,7 +94,7 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = getToolByName(self, 'portal_synchronizations')
p_sync = self._getPortalSynchronizationTool()
return p_sync.getSubscriberDocument(self)
def getSubscriberDocumentPath(self):
......@@ -178,7 +102,7 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = getToolByName(self, 'portal_synchronizations')
p_sync = self._getPortalSynchronizationTool()
return p_sync.getSubscriberDocumentPath(self)
def applySubscriberDocument(self):
......@@ -186,49 +110,18 @@ class Conflict(SyncCode, Base):
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = getToolByName(self, 'portal_synchronizations')
p_sync = self._getPortalSynchronizationTool()
p_sync.applySubscriberDocument(self)
def applySubscriberValue(self, object=None):
"""
get the domain
"""
p_sync = getToolByName(self, 'portal_synchronizations')
p_sync = self._getPortalSynchronizationTool()
p_sync.applySubscriberValue(self, object=object)
def setSubscriber(self, subscriber):
"""
set the domain
"""
self.subscriber = subscriber
def getSubscriber(self):
"""
get the domain
"""
return self.subscriber
def getKeyword(self):
Return the grand parent subscriber
"""
get the domain
"""
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
return self.getParentValue().getParentValue()
# -*- 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)
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
# Danièle Vanbaelinghem <daniele@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 AccessControl import ClassSecurityInfo
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type import Permissions
from Products.ERP5Type import PropertySheet
from zLOG import LOG, DEBUG, INFO
from Products.ERP5SyncML.Utils import PdataHelper
import md5
_MARKER = []
class SyncMLSignature(XMLObject):
"""
status -- SENT, CONFLICT...
md5_object -- An MD5 value of a given document
#uid -- The UID of the document
id -- the ID of the document
gid -- the global id of the document
rid -- the uid of the document on the remote database,
only needed on the server.
xml -- the xml of the object at the time where it was synchronized
"""
meta_type = 'ERP5 Signature'
portal_type = 'SyncML Signature'
isIndexable = ConstantGetter('isIndexable', value=False)
# Make sure RAD generated accessors at the class level
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Declarative properties
property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject
, PropertySheet.CategoryCore
, PropertySheet.DublinCore
, PropertySheet.Reference
, PropertySheet.Data
, PropertySheet.Document
, PropertySheet.SyncMLSignature )
security.declareProtected(Permissions.ModifyPortalContent, 'setData')
def setData(self, value):
"""
set the XML corresponding to the object
"""
if value:
# convert the string to Pdata
pdata_wrapper = PdataHelper(self.getPortalObject(), value)
#data, size = pdata_wrapper()
self._setData(pdata_wrapper)
self.setTemporaryData(None) # We make sure that the data will not be erased
self.setContentMd5(pdata_wrapper.getContentMd5())
else:
self._setData(None)
self.setContentMd5(None)
security.declareProtected(Permissions.AccessContentsInformation, 'getData')
def getData(self, default=_MARKER):
"""
get the XML corresponding to the object
"""
if self.hasData():
return str(self._baseGetData())
if default is _MARKER:
return self._baseGetData()
else:
return self._baseGetData(default)
security.declareProtected(Permissions.ModifyPortalContent,
'setTemporaryData')
def setTemporaryData(self, value):
"""
This is the xml temporarily saved, it will
be stored with setXML when we will receive
the confirmation of synchronization
"""
if value:
pdata_wrapper = PdataHelper(self.getPortalObject(), value)
self._setTemporaryData(pdata_wrapper)
else:
self._setTemporaryData(None)
security.declareProtected(Permissions.AccessContentsInformation,
'getTemporaryData')
def getTemporaryData(self, default=_MARKER):
"""
get the temp xml
"""
if self.hasTemporaryData():
return str(self._baseGetTemporaryData())
if default is _MARKER:
return self._baseGetTemporaryData()
else:
return self._baseGetTemporaryData(default)
security.declareProtected(Permissions.AccessContentsInformation, 'checkMD5')
def checkMD5(self, xml_string):
"""
check if the given md5_object returns the same things as
the one stored in this signature, this is very usefull
if we want to know if an objects has changed or not
Returns 1 if MD5 are equals, else it returns 0
"""
return ((md5.new(xml_string).hexdigest()) == self.getContentMd5())
security.declareProtected(Permissions.ModifyPortalContent, 'setPartialData')
def setPartialData(self, value):
"""
Set the partial string we will have to
deliver in the future
"""
if value is not None:
if not isinstance(value, PdataHelper):
value = PdataHelper(self.getPortalObject(), value)
self._setPartialData(value)
self.setLastDataPartialData(value.getLastPdata())
else:
self._setPartialData(None)
self.setLastDataPartialData(None)
security.declareProtected(Permissions.ModifyPortalContent, 'setLastData')
def setLastData(self, value):
"""
This is the xml temporarily saved, it will
be stored with setXML when we will receive
the confirmation of synchronization
"""
if value is not None:
pdata_wrapper = PdataHelper(self.getPortalObject(), value)
self._setLastData(pdata_wrapper)
else:
self._setLastData(None)
security.declareProtected(Permissions.AccessContentsInformation,
'getPartialData')
def getPartialData(self, default=_MARKER):
"""
get the patial xml
"""
if self.hasPartialData():
return str(self._baseGetPartialData())
if default is _MARKER:
return self._baseGetPartialData()
else:
return self._baseGetPartialData(default)
security.declareProtected(Permissions.ModifyPortalContent, 'appendPartialData')
def appendPartialData(self, value):
"""
Append the partial string we will have to deliver in the future
"""
if value is not None:
if not isinstance(value, PdataHelper):
value = PdataHelper(self.getPortalObject(), value)
last_data = value.getLastPdata()
if self.hasLastDataPartialData():
last_data_partial_data = self.getLastDataPartialData()
last_data_partial_data.next = value._data
self.setLastDataPartialData(last_data_partial_data)
else:
self.setPartialData(value)
self.setLastDataPartialData(last_data)
#security.declareProtected(Permissions.AccessContentsInformation,
#'getFirstChunkPdata')
#def getFirstChunkPdata(self, size_lines):
#"""
#"""
#chunk = [self.getPartialData().data]
#size = chunk[0].count('\n')
#current = self.getPartialData()
#next = current.next
#while size < size_lines and next is not None:
#current = next
#size += current.data.count('\n')
#chunk.append(current.data)
#next = current.next
#if size == size_lines:
#self.setPartialData(next)
#elif size > size_lines:
#overflow = size - size_lines
#data_list = chunk[-1].split('\n')
#chunk[-1] = '\n'.join(data_list[:-overflow])
#current.data = '\n'.join(data_list[-overflow:])
#self.setPartialData(current)
#return ''.join(chunk)
def getFirstPdataChunk(self, max_len):
"""
"""
#chunk, rest_in_queue = self._baseGetPartialData().\
#getFirstPdataChunkAndRestInQueue(max_len)
partial_data = self._baseGetPartialData()
chunk = partial_data[:max_len]
rest_in_queue = partial_data[max_len:]
if rest_in_queue is not None:
self.setPartialData(rest_in_queue)
return str(chunk)
security.declareProtected(Permissions.ModifyPortalContent,
'setSubscriberXupdate')
def setSubscriberXupdate(self, value):
"""
This is the xml temporarily saved, it will
be stored with setXML when we will receive
the confirmation of synchronization
"""
if value is not None:
pdata_wrapper = PdataHelper(self.getPortalObject(), value)
self._setSubscriberXupdate(pdata_wrapper)
else:
self._setSubscriberXupdate(None)
security.declareProtected(Permissions.AccessContentsInformation,
'getSubscriberXupdate')
def getSubscriberXupdate(self, default=_MARKER):
"""
get the patial xml
"""
if self.hasSubscriberXupdate():
return str(self._baseGetSubscriberXupdate())
if default is _MARKER:
return self._baseGetSubscriberXupdate()
else:
return self._baseGetSubscriberXupdate(default)
security.declareProtected(Permissions.ModifyPortalContent,
'setPublisherXupdate')
def setPublisherXupdate(self, value):
"""
This is the xml temporarily saved, it will
be stored with setXML when we will receive
the confirmation of synchronization
"""
if value is not None:
pdata_wrapper = PdataHelper(self.getPortalObject(), value)
self._setPublisherXupdate(pdata_wrapper)
else:
self._setPublisherXupdate(None)
security.declareProtected(Permissions.AccessContentsInformation,
'getPublisherXupdate')
def getPublisherXupdate(self, default=_MARKER):
"""
get the patial xml
"""
if self.hasPublisherXupdate():
return str(self._baseGetPublisherXupdate())
if default is _MARKER:
return self._baseGetPublisherXupdate()
else:
return self._baseGetPublisherXupdate(default)
security.declareProtected(Permissions.ModifyPortalContent,
'reset')
def reset(self):
"""Clear Signature and change validation_state to not_synchronized
"""
if self.getValidationState() != 'not_synchronized':
self.drift()
self.setPartialData(None)
self.setTemporaryData(None)
def getConflictList(self):
"""
Return the actual action for a partial synchronization
"""
return self.contentValues()
# returned_conflict_list = []
# if getattr(self, 'conflict_list', None) is None:
# return returned_conflict_list
# if len(self.conflict_list)>0:
# returned_conflict_list.extend(self.conflict_list)
# return returned_conflict_list
def setConflictList(self, conflict_list):
"""
Return the actual action for a partial synchronization
"""
return
# if conflict_list is None or conflict_list == []:
# self.resetConflictList()
# else:
# self.conflict_list = conflict_list
def resetConflictList(self):
"""
Return the actual action for a partial synchronization
"""
return
#self.conflict_list = PersistentMapping()
def delConflict(self, conflict):
"""
Delete provided conflict object
"""
self.manage_delObjects([conflict.getId(),])
# conflict_list = []
# for c in self.getConflictList():
# #LOG('delConflict, c==conflict',0,c==aq_base(conflict))
# if c != aq_base(conflict):
# conflict_list += [c]
# if conflict_list != []:
# self.setConflictList(conflict_list)
# else:
# self.resetConflictList()
# -*- 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 AccessControl import ClassSecurityInfo
from Acquisition import aq_base
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5Type.Utils import deprecated
from DateTime import DateTime
from zLOG import LOG, DEBUG, INFO, WARNING
from base64 import b16encode, b16decode
from Products.ERP5SyncML.XMLSyncUtils import getConduitByName
from Products.ERP5SyncML.SyncMLConstant import MAX_OBJECTS, ACTIVITY_PRIORITY
from warnings import warn
_MARKER = []
class SyncMLSubscription(XMLObject):
"""
Subscription hold the definition of a master ODB
from/to which a selection of objects will be synchronized
Subscription defined by::
publication_url -- a URI to a publication
subscription_url -- URL of ourselves
destination_path -- the place where objects are stored
query -- a query which defines a local set of documents which
are going to be synchronized
xml_mapping -- a PageTemplate to map documents to XML
gpg_key -- the name of a gpg key to use
Subscription also holds private data to manage
the synchronization. We choose to keep an MD5 value for
all documents which belong to the synchronization process::
signatures -- a dictionnary which contains the signature
of documents at the time they were synchronized
session_id -- it defines the id of the session
with the server.
last_anchor - it defines the id of the last synchronization
next_anchor - it defines the id of the current synchronization
Subscription inherit of File because the Signature use method _read_data
which have the need of a __r_jar not None.
During the initialization of a Signature this __p_jar is None
"""
meta_type = 'ERP5 Subscription'
portal_type = 'SyncML Subscription' # may be useful in the future...
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Declarative properties
property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject
, PropertySheet.CategoryCore
, PropertySheet.DublinCore
, PropertySheet.Reference
, PropertySheet.Login
, PropertySheet.Url
, PropertySheet.Gpg
, PropertySheet.Data
, PropertySheet.SyncMLSubscription
, PropertySheet.SyncMLSubscriptionConstraint )
security.declareProtected(Permissions.AccessContentsInformation,
'isOneWayFromServer')
def isOneWayFromServer(self):
return self.getPortalType() == 'SyncML Subscription' and \
self.getSyncmlAlertCode() == 'one_way_from_server'
security.declareProtected(Permissions.AccessContentsInformation,
'isOneWayFromClient')
def isOneWayFromClient(self):
return self.getParentValue().getPortalType() == 'SyncML Publication' and \
self.getSyncmlAlertCode() == 'one_way_from_client'
security.declareProtected(Permissions.AccessContentsInformation,
'getSynchronizationType')
def getSynchronizationType(self, default=_MARKER):
"""Deprecated alias of getSyncmlAlertCode
"""
warn('Use getSyncmlAlertCode instead', DeprecationWarning)
if default is _MARKER:
code = self.getSyncmlAlertCode()
else:
code = self.getSyncmlAlertCode(default=default)
return code
security.declarePrivate('checkCorrectRemoteSessionId')
def checkCorrectRemoteSessionId(self, session_id):
"""
We will see if the last session id was the same
wich means that the same message was sent again
return True if the session id was not seen, False if already seen
"""
if self.getLastSessionId() == session_id:
return False
self.setLastSessionId(session_id)
return True
security.declarePrivate('checkCorrectRemoteMessageId')
def checkCorrectRemoteMessageId(self, message_id):
"""
We will see if the last message id was the same
wich means that the same message was sent again
return True if the message id was not seen, False if already seen
"""
if self.getLastMessageId() == message_id:
return False
self.setLastMessageId(message_id)
return True
security.declareProtected(Permissions.ModifyPortalContent,
'initLastMessageId')
def initLastMessageId(self, last_message_id=0):
"""
set the last message id to 0
"""
self.setLastMessageId(last_message_id)
security.declareProtected(Permissions.AccessContentsInformation,
'getXmlBindingGeneratorMethodId')
def getXmlBindingGeneratorMethodId(self, default=_MARKER, force=False):
"""
return the xml mapping
"""
if self.isOneWayFromServer() and not force:
return None
if default is _MARKER:
return self._baseGetXmlBindingGeneratorMethodId()
else:
return self._baseGetXmlBindingGeneratorMethodId(default=default)
security.declareProtected(Permissions.AccessContentsInformation,
'getGidFromObject')
def getGidFromObject(self, object, encoded=True):
"""
Returns the object gid
"""
o_base = aq_base(object)
gid = None
# first try with new method
gid_generator = self.getGidGeneratorMethodId("")
if gid_generator not in ("", None) and getattr(self, gid_generator, None):
raw_gid = getattr(self, gid_generator)(object)
else:
# old way using the conduit
conduit_name = self.getConduitModuleId()
conduit = getConduitByName(conduit_name)
raw_gid = conduit.getGidFromObject(object)
if isinstance(raw_gid, unicode):
raw_gid = raw_gid.encode('ascii', 'ignore')
if encoded:
gid = b16encode(raw_gid)
else:
gid = raw_gid
return gid
security.declareProtected(Permissions.AccessContentsInformation,
'getObjectFromGid')
def getObjectFromGid(self, gid, use_list_method=True):
"""
This tries to get the object with the given gid
This uses the query if it exist and use_list_method is True
"""
if len(gid)%2 != 0:
#something encode in base 16 is always a even number of number
#if not, b16decode will failed
return None
signature = self.getSignatureFromGid(gid)
# First look if we do already have the mapping between
# the id and the gid
destination = self.getSourceValue()
if signature is not None and signature.getReference():
document_path = signature.getReference()
document = self.getPortalObject().unrestrictedTraverse(document_path, None)
if document is not None:
return document
#LOG('entering in the slow loop of getObjectFromGid !!!', WARNING,
#self.getPath())
if use_list_method:
object_list = self.getObjectList(gid=b16decode(gid))
for document in object_list:
document_gid = self.getGidFromObject(document)
if document_gid == gid:
return document
#LOG('getObjectFromGid', DEBUG, 'returning None')
return None
security.declareProtected(Permissions.AccessContentsInformation,
'getObjectFromId')
def getObjectFromId(self, id):
"""
return the object corresponding to the id
"""
object_list = self.getObjectList(id=id)
o = None
for object in object_list:
if object.getId() == id:
o = object
break
return o
security.declareProtected(Permissions.AccessContentsInformation,
'getObjectList')
def getObjectList(self, **kw):
"""
This returns the list of sub-object corresponding
to the query
"""
folder = self.getSourceValue()
list_method_id = self.getListMethodId()
if list_method_id is not None and isinstance(list_method_id, str):
result_list = []
query_method = folder.unrestrictedTraverse(list_method_id, None)
if query_method is not None:
result_list = query_method(context_document=self, **kw)
else:
raise KeyError, 'This Subscriber %s provide no list method:%r'\
% (self.getPath(), list_method_id)
else:
raise KeyError, 'This Subscriber %s provide no list method with id:%r'\
% (self.getPath(), list_method_id)
# XXX Access all objects is very costly
return [x for x in result_list
if not getattr(x, '_conflict_resolution', False)]
security.declarePrivate('generateNewIdWithGenerator')
def generateNewIdWithGenerator(self, object=None, gid=None):
"""
This tries to generate a new Id
"""
id_generator = self.getSynchronizationIdGeneratorMethodId()
if id_generator is not None:
o_base = aq_base(object)
new_id = None
if callable(id_generator):
new_id = id_generator(object, gid=gid)
elif getattr(o_base, id_generator, None) is not None:
generator = getattr(object, id_generator)
new_id = generator()
else:
# This is probably a python script
generator = getattr(object, id_generator)
new_id = generator(object=object, gid=gid)
#LOG('generateNewIdWithGenerator, new_id: ', DEBUG, new_id)
return new_id
return None
security.declareProtected(Permissions.ModifyPortalContent,
'incrementSessionId')
def incrementSessionId(self):
"""
increment and return the session id
"""
session_id = self.getSessionId()
session_id += 1
self._setSessionId(session_id)
self.resetMessageId() # for a new session, the message Id must be reset
return session_id
security.declareProtected(Permissions.ModifyPortalContent,
'incrementMessageId')
def incrementMessageId(self):
"""
return the message id
"""
message_id = self.getMessageId(0)
message_id += 1
self._setMessageId(message_id)
return message_id
security.declareProtected(Permissions.ModifyPortalContent,
'resetMessageId')
def resetMessageId(self):
"""
set the message id to 0
"""
self._setMessageId(0)
security.declareProtected(Permissions.ModifyPortalContent,
'createNewAnchor')
def createNewAnchor(self):
"""
set a new anchor
"""
self.setLastAnchor(self.getNextAnchor())
self.setNextAnchor(DateTime())
security.declareProtected(Permissions.ModifyPortalContent,
'resetAnchorList')
def resetAnchorList(self):
"""
reset both last and next anchors
"""
self.setLastAnchor(None)
self.setNextAnchor(None)
security.declareProtected(Permissions.AccessContentsInformation,
'getSignatureFromObjectId')
def getSignatureFromObjectId(self, id):
"""
return the signature corresponding to the id
### Use a reverse dictionary will be usefull
to handle changes of GIDs
"""
document = None
# XXX very slow
for signature in self.objectValues():
document = signature.getSourceValue()
if document is not None:
if id == document.getId():
document = signature
break
return document
security.declareProtected(Permissions.AccessContentsInformation,
'getSignatureFromGid')
def getSignatureFromGid(self, gid):
"""
return the signature corresponding to the gid
"""
return self._getOb(gid, None)
security.declareProtected(Permissions.AccessContentsInformation,
'getGidList')
def getGidList(self):
"""
Returns the list of gids from signature
"""
return [id for id in self.getObjectIds()]
security.declareProtected(Permissions.AccessContentsInformation,
'getSignatureList')
@deprecated
def getSignatureList(self):
"""
Returns the list of Signatures
"""
return self.contentValues(portal_type='SyncML Signature')
security.declareProtected(Permissions.AccessContentsInformation,
'hasSignature')
def hasSignature(self, gid):
"""
Check if there's a signature with this uid
"""
return self.getSignatureFromGid(gid) is not None
security.declareProtected(Permissions.ModifyPortalContent,
'resetSignatureList')
def resetSignatureList(self):
"""
Reset all signatures in activities
"""
object_id_list = [id for id in self.getObjectIds()]
object_list_len = len(object_id_list)
for i in xrange(0, object_list_len, MAX_OBJECTS):
current_id_list = object_id_list[i:i+MAX_OBJECTS]
self.activate(activity='SQLQueue',
tag=self.getId(),
priority=ACTIVITY_PRIORITY).manage_delObjects(current_id_list)
security.declareProtected(Permissions.AccessContentsInformation,
'getConflictList')
def getConflictList(self, *args, **kw):
"""
Return the list of all conflicts from all signatures
"""
conflict_list = []
for signature in self.objectValues():
conflict_list.extend(signature.getConflictList())
return conflict_list
security.declareProtected(Permissions.ModifyPortalContent,
'removeRemainingObjectPath')
def removeRemainingObjectPath(self, object_path):
"""
We should now wich objects should still
synchronize
"""
remaining_object_list = self.getProperty('remaining_object_path_list')
if remaining_object_list is None:
# it is important to let remaining_object_path_list to None
# it means it has not beeing initialised yet
return
new_list = []
new_list.extend(remaining_object_list)
while object_path in new_list:
new_list.remove(object_path)
self._edit(remaining_object_path_list=new_list)
security.declareProtected(Permissions.ModifyPortalContent,
'initialiseSynchronization')
def initialiseSynchronization(self):
"""
Set the status of every object as not_synchronized
XXX Improve method to not fetch many objects in unique transaction
"""
LOG('Subscription.initialiseSynchronization()', 0, self.getPath())
for signature in self.contentValues(portal_type='SyncML Signature'):
# Change the status only if we are not in a conflict mode
if signature.getValidationState() not in ('conflict',
'conflict_resolved_with_merge',
'conflict_resolved_with_client_command_winning'):
if self.getIsActivityEnabled():
signature.activate(tag=self.getId(), activity='SQLQueue',
priority=ACTIVITY_PRIORITY).reset()
else:
signature.reset()
self._edit(remaining_object_path_list=None)
##############################################################################
#
# 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
# -*- 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 Publication import Publication,Subscriber
from Signature import Signature
from XMLSyncUtils import XMLSyncUtils
from Conduit.ERP5Conduit import ERP5Conduit
from Products.CMFCore.utils import getToolByName
from Products.ERP5Security.ERP5UserManager import ERP5UserManager
from Products.PluggableAuthService.interfaces.plugins import\
IAuthenticationPlugin
from AccessControl.SecurityManagement import newSecurityManager
import commands
from DateTime import DateTime
from zLOG import LOG, DEBUG, INFO, WARNING
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 PublicationSynchronization(XMLSyncUtils):
"""
Receive the first XML message from the client
"""
def PubSyncInit(self, publication=None, xml_client=None, subscriber=None,
sync_type=None):
"""
Read the client xml message
Send the first XML message from the server
"""
LOG('PubSyncInit', INFO, 'Starting... publication: %s' % (publication.getPath()))
#the session id is set at the same value of those of the client
subscriber.setSessionId(self.getSessionIdFromXml(xml_client))
#same for the message id
subscriber.setMessageId(self.getMessageIdFromXml(xml_client))
#at the begining of the synchronization the subscriber is not authenticated
subscriber.setAuthenticated(False)
#the last_message_id is 1 because the message that
#we are about to send is the message 1
subscriber.initLastMessageId(1)
alert = None
# Get informations from the body
if xml_client is not None: # We have received a message
last_anchor = self.getAlertLastAnchor(xml_client)
next_anchor = self.getAlertNextAnchor(xml_client)
alert = self.checkAlert(xml_client)
alert_code = self.getAlertCodeFromXML(xml_client)
cred = self.checkCred(xml_client)
#the source and the target of the subscriber are reversed compared
# to those of the publication :
subscriber.setSourceURI(self.getTargetURI(xml_client))
subscriber.setTargetURI(self.getSourceURI(xml_client))
cmd_id = 1 # specifies a SyncML message-unique command identifier
#create element 'SyncML' with a default namespace
xml = E.SyncML()
# syncml header
xml.append(self.SyncMLHeader(subscriber.getSessionId(),
subscriber.getMessageId(),
subscriber.getSubscriptionUrl(),
publication.getPublicationUrl()))
# syncml body
sync_body = E.SyncBody()
xml.append(sync_body)
#at the begining, the code is initialised at UNAUTHORIZED
auth_code = self.UNAUTHORIZED
if not cred:
auth_code = self.AUTH_REQUIRED
# LOG("PubSyncInit there's no credential !!!", INFO,'')
# Prepare the xml message for the Sync initialization package
sync_body.append(self.SyncMLChal(cmd_id, "SyncHdr",
publication.getPublicationUrl(), subscriber.getSubscriptionUrl(),
publication.getAuthenticationFormat(),
publication.getAuthenticationType(), auth_code))
cmd_id += 1
# chal message
xml_status, cmd_id = self.SyncMLStatus(
xml_client,
auth_code,
cmd_id,
next_anchor,
subscription=subscriber)
sync_body.extend(xml_status)
else:
# If slow sync, then resend everything
if alert_code == self.SLOW_SYNC and \
subscriber.getNextAnchor() != self.NULL_ANCHOR:
LOG('Warning !!!, reseting client synchronization for subscriber:', WARNING,
subscriber.getPath())
subscriber.resetAllSignatures()
subscriber.resetAnchors()
# Check if the last time synchronization is the same as the client one
if subscriber.getNextAnchor() != last_anchor:
if not last_anchor:
LOG('PubSyncInit', INFO, 'anchor null')
else:
mess = '\nsubscriber.getNextAnchor:\t%s\nsubscriber.getLastAnchor:\t%s\
\nlast_anchor:\t\t\t%s\nnext_anchor:\t\t\t%s' % \
(subscriber.getNextAnchor(),
subscriber.getLastAnchor(),
last_anchor,
next_anchor)
LOG('PubSyncInit Anchors', INFO, mess)
else:
subscriber.setNextAnchor(next_anchor)
(authentication_format, authentication_type, data) = \
self.getCred(xml_client)
if authentication_type == publication.getAuthenticationType():
authentication_format = publication.getAuthenticationFormat()
decoded = subscriber.decode(authentication_format, data)
if decoded and ':' in decoded:
(login, password) = decoded.split(':')
uf = self.getPortalObject().acl_users
for plugin_name, plugin in uf._getOb('plugins').listPlugins(
IAuthenticationPlugin ):
if plugin.authenticateCredentials(
{'login':login, 'password':password}) is not None:
subscriber.setAuthenticated(True)
LOG("PubSyncInit Authentication Accepted", INFO, '')
auth_code = self.AUTH_ACCEPTED
#here we must log in with the user authenticated :
user = uf.getUserById(login).__of__(uf)
newSecurityManager(None, user)
subscriber.setUser(login)
break
else:
auth_code = self.UNAUTHORIZED
#in all others cases, the auth_code is set to UNAUTHORIZED
if auth_code == self.UNAUTHORIZED:
LOG("PubSyncInit Authentication Failed !! with login :", INFO, login)
# Prepare the xml message for the Sync initialization package
if auth_code == self.AUTH_ACCEPTED:
xml_status, cmd_id = self.SyncMLStatus(xml_client, auth_code,
cmd_id, next_anchor,
subscription=subscriber)
sync_body.extend(xml_status)
# alert message
sync_body.append(self.SyncMLAlert(cmd_id, sync_type,
subscriber.getTargetURI(),
subscriber.getSourceURI(),
subscriber.getLastAnchor(),
next_anchor))
cmd_id += 1
else:
# chal message
sync_body.append(self.SyncMLChal(cmd_id, "SyncHdr",
publication.getPublicationUrl(),
subscriber.getSubscriptionUrl(),
publication.getAuthenticationFormat(),
publication.getAuthenticationType(),
auth_code))
cmd_id += 1
xml_status, cmd_id = self.SyncMLStatus(xml_client,
self.AUTH_REQUIRED, cmd_id,
next_anchor,
subscription=subscriber)
sync_body.extend(xml_status)
# We have to set every object as NOT_SYNCHRONIZED
subscriber.startSynchronization()
else:
# We have started the sync from the server (may be for a conflict
# resolution)
raise ValueError, "the syncml message is None. Maybe a synchronisation \
has been started from the server (forbiden)"
# a synchronisation is always starded from a client and can't be from
# a server !
sync_body.append(E.Final())
xml_string = etree.tostring(xml, encoding='utf-8', pretty_print=True)
if publication.getSyncContentType() == self.CONTENT_TYPE['SYNCML_WBXML']:
xml_string = self.xml2wbxml(xml_string)
self.sendResponse(from_url=publication.getPublicationUrl(),
to_url=subscriber.getSubscriptionUrl(),
sync_id=publication.getTitle(),
xml=xml_string, domain=publication,
content_type=publication.getSyncContentType())
return {'has_response':1, 'xml':xml_string}
def PubSyncModif(self, publication, xml_client):
"""
The modidification message for the publication
"""
return self.SyncModif(publication, xml_client)
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
# Danièle Vanbaelinghem <daniele@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 PersistentMapping
from time import gmtime,strftime # for anchors
from SyncCode import SyncCode
from AccessControl import ClassSecurityInfo
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 import Permissions
from Products.ERP5Type import PropertySheet
from Products.ERP5.Document import Document
from DateTime import DateTime
from zLOG import LOG, DEBUG, INFO
import cStringIO
from OFS.Image import Pdata
from OFS.Image import File
import md5
from base64 import b64encode, b64decode, b16encode, b16decode
class Signature(Folder, SyncCode, File):
"""
status -- SENT, CONFLICT...
md5_object -- An MD5 value of a given document
#uid -- The UID of the document
id -- the ID of the document
gid -- the global id of the document
rid -- the uid of the document on the remote database,
only needed on the server.
xml -- the xml of the object at the time where it was synchronized
"""
isIndexable = ConstantGetter('isIndexable', value=False)
# Make sure RAD generated accessors at the class level
# Constructor
def __init__(self,
id=None,
rid=None,
status=None,
xml_string=None,
object=None):
Folder.__init__(self, id)
File.__init__(self, id, '', '')
if object is not None:
self.setPath(object.getPhysicalPath())
self.setObjectId(object.getId())
else:
self.setPath(None)
self.setId(id)
self.setRid(rid)
self.status = status
self.setXML(xml_string)
self.setPartialXML(None)
self.action = None
self.setTempXML(None)
self.resetConflictList()
self.md5_string = None
self.force = 0
self.setSubscriberXupdate(None)
self.setPublisherXupdate(None)
self.last_data_partial_xml = None
def setStatus(self, status):
"""
set the Status (see SyncCode for numbers)
"""
self.status = status
if status == self.SYNCHRONIZED:
temp_xml = self.getTempXML()
self.setForce(0)
if temp_xml is not None:
# This happens when we have sent the xml
# and we just get the confirmation
self.setXML(temp_xml)
self.setTempXML(None)
self.setPartialXML(None)
self.setSubscriberXupdate(None)
self.setPublisherXupdate(None)
if len(self.getConflictList())>0:
self.resetConflictList()
# XXX This may be a problem, if the document is changed
# during a synchronization
self.setLastSynchronizationDate(DateTime())
self.getParentValue().removeRemainingObjectPath(self.getPath())
if status == self.NOT_SYNCHRONIZED:
self.setTempXML(None)
self.setPartialXML(None)
elif status in (self.PUB_CONFLICT_MERGE, self.SENT):
# We have a solution for the conflict, don't need to keep the list
self.resetConflictList()
def getStatus(self):
"""
get the Status (see SyncCode for numbers)
"""
return self.status
def getPath(self):
"""
get the force value (if we need to force update or not)
"""
return getattr(self, 'path', None)
def setPath(self, path):
"""
set the force value (if we need to force update or not)
"""
self.path = path
def getForce(self):
"""
get the force value (if we need to force update or not)
"""
return self.force
def setForce(self, force):
"""
set the force value (if we need to force update or not)
"""
self.force = force
def getLastModificationDate(self):
"""
get the last modfication date, so that we don't always
check the xml
"""
return getattr(self, 'modification_date', None)
def setLastModificationDate(self,value):
"""
set the last modfication date, so that we don't always
check the xml
"""
setattr(self, 'modification_date', value)
def getLastSynchronizationDate(self):
"""
get the last modfication date, so that we don't always
check the xml
"""
return getattr(self, 'synchronization_date', None)
def setLastSynchronizationDate(self,value):
"""
set the last modfication date, so that we don't always
check the xml
"""
setattr(self, 'synchronization_date', value)
def hasXML(self):
"""
return True if the xml is available
"""
return bool(getattr(self, 'xml', None))
def setXML(self, xml):
"""
set the XML corresponding to the object
"""
if xml is not None:
# convert the string to Pdata if the big size
file = cStringIO.StringIO(xml)
self.xml, size = self.getParentValue()._read_data(file)
self.setTempXML(None) # We make sure that the xml will not be erased
self.setMD5(xml)
else:
self.xml = None
def getXML(self, default=None):
"""
get the XML corresponding to the object
"""
#Never return empty string
if self.hasXML():
if isinstance(self.xml, Pdata):
return str(self.xml)
elif isinstance(self.xml, str):
return self.xml
else:
raise ValueError, "the self.xml haven't good type"
else:
return default
def hasTempXML(self):
"""
Return true if the temp_xml is available
"""
return bool(getattr(self, 'temp_xml', None))
def setTempXML(self, xml):
"""
This is the xml temporarily saved, it will
be stored with setXML when we will receive
the confirmation of synchronization
"""
if xml is not None:
file = cStringIO.StringIO(xml)
self.temp_xml, size = self.getParentValue()._read_data(file)
else:
self.temp_xml = None
def getTempXML(self, default=None):
"""
get the temp xml
"""
if self.hasTempXML():
if isinstance(self.temp_xml, Pdata):
return str(self.temp_xml)
elif isinstance(self.temp_xml, str):
return self.temp_xml
else:
raise ValueError, "the self.xml haven't good type"
else:
return default
def setSubscriberXupdate(self, xupdate):
"""
set the full temp xupdate
"""
self.subscriber_xupdate = xupdate
def getSubscriberXupdate(self):
"""
get the full temp xupdate
"""
return self.subscriber_xupdate
def setPublisherXupdate(self, xupdate):
"""
set the full temp xupdate
"""
self.publisher_xupdate = xupdate
def getPublisherXupdate(self):
"""
get the full temp xupdate
"""
return self.publisher_xupdate
def setMD5(self, xml):
"""
set the MD5 object of this signature
"""
self.md5_string = md5.new(xml).digest()
def getMD5(self):
"""
get the MD5 object of this signature
"""
return self.md5_string
def checkMD5(self, xml_string):
"""
check if the given md5_object returns the same things as
the one stored in this signature, this is very usefull
if we want to know if an objects has changed or not
Returns 1 if MD5 are equals, else it returns 0
"""
return ((md5.new(xml_string).digest()) == self.getMD5())
def setRid(self, rid):
"""
set the rid
"""
if isinstance(rid, unicode):
rid = rid.encode('utf-8')
self.rid = rid
def getRid(self):
"""
get the rid
"""
return getattr(self, 'rid', None)
def setId(self, id):
"""
set the id
"""
if isinstance(id, unicode):
id = id.encode('utf-8')
self.id = id
def getId(self):
"""
get the id
"""
return self.id
def getGid(self):
"""
get the gid
"""
return self.getId()
def setObjectId(self, id):
"""
set the id of the object associated to this signature
"""
if isinstance(id, unicode):
id = id.encode('utf-8')
self.object_id = id
def getObjectId(self):
"""
get the id of the object associated to this signature
"""
return getattr(self, 'object_id', None)
def hasPartialXML(self):
"""
Return true is the partial xml is available
"""
return bool(getattr(self, 'partial_xml', None))
def setPartialXML(self, xml):
"""
Set the partial string we will have to
deliver in the future
"""
if xml is not None:
# change encoding of xml to convert in file
try:
xml = xml.encode('utf-8')
except UnicodeDecodeError:
xml = xml.decode('utf-8').encode('ascii','xmlcharrefreplace')
# convert the string to Pdata if the big size
file = cStringIO.StringIO(xml)
self.partial_xml, size = self.getParentValue()._read_data(file)
if not isinstance(self.partial_xml, Pdata):
self.partial_xml = Pdata(self.partial_xml)
self.last_data_partial_xml = self.partial_xml.getLastPdata()
else:
self.partial_xml = None
self.last_data_partial_xml = None
def appendPartialXML(self, xml):
"""
Append the partial string we will have to deliver in the future
"""
if xml is not None:
try:
xml = xml.encode('utf-8')
except UnicodeDecodeError:
xml = xml.decode('utf-8').encode('ascii','xmlcharrefreplace')
file = cStringIO.StringIO(xml)
xml_append, size = self.getParentValue()._read_data(file)
if not isinstance(xml_append, Pdata):
xml_append = Pdata(xml_append)
last_data = xml_append.getLastPdata()
if self.last_data_partial_xml is not None:
self.last_data_partial_xml.next = xml_append
else:
self.partial_xml = xml_append
self.last_data_partial_xml = last_data
def getFirstChunkPdata(self, size_lines):
"""
"""
chunk = list()
chunk.append(self.partial_xml.data)
size = chunk[0].count('\n')
current = self.partial_xml
next = current.next
while size < size_lines and next is not None:
current = next
size += current.data.count('\n')
chunk.append(current.data)
next = current.next
if size == size_lines:
self.partial_xml = next
elif size > size_lines:
overflow = size - size_lines
data_list = chunk[-1].split('\n')
chunk[-1] = '\n'.join(data_list[:-overflow])
current.data = '\n'.join(data_list[-overflow:])
self.partial_xml = current
return ''.join(chunk)
def getPartialXML(self, default=None):
"""
Set the partial string we will have to
deliver in the future
"""
if self.hasPartialXML():
if isinstance(self.partial_xml, Pdata):
return str(self.partial_xml)
else:
raise ValueError, "the self.xml haven't good type"
else:
return default
def getAction(self):
"""
Return the actual action for a partial synchronization
"""
return self.action
def setAction(self, action):
"""
Return the actual action for a partial synchronization
"""
self.action = action
def getConflictList(self):
"""
Return the actual action for a partial synchronization
"""
returned_conflict_list = []
if len(self.conflict_list)>0:
returned_conflict_list.extend(self.conflict_list)
return returned_conflict_list
def resetConflictList(self):
"""
Return the actual action for a partial synchronization
"""
self.conflict_list = PersistentMapping()
def setConflictList(self, conflict_list):
"""
Return the actual action for a partial synchronization
"""
if conflict_list is None or conflict_list == []:
self.resetConflictList()
else:
self.conflict_list = conflict_list
def delConflict(self, conflict):
"""
Return the actual action for a partial synchronization
"""
conflict_list = []
for c in self.getConflictList():
#LOG('delConflict, c==conflict',0,c==aq_base(conflict))
if c != aq_base(conflict):
conflict_list += [c]
if conflict_list != []:
self.setConflictList(conflict_list)
else:
self.resetConflictList()
def getObject(self):
"""
Returns the object corresponding to this signature
"""
return self.getParentValue().getObjectFromGid(self.getObjectId())
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.
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