############################################################################## # # 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 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.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 try: from base64 import b64encode, b64decode, b16encode, b16decode except ImportError: from base64 import encodestring as b64encode, decodestring as b64decode, \ encodestring as b16encode, decodestring as b16decode #class Conflict(SyncCode, Implicit): class Conflict(SyncCode, Base): """ object_path : the path of the obect keyword : an identifier of the conflict publisher_value : the value that we have locally subscriber_value : the value sent by the remote box """ isIndexable = 0 isPortalContent = 0 # 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() def setXupdate(self, xupdate): """ set the xupdate """ if xupdate == None: self.resetXupdate() else: self.xupdate = self.getXupdateList() + [xupdate] def setXupdateList(self, xupdate): """ set the xupdate """ self.xupdate = xupdate 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 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.applyPublisherValue(self) def applyPublisherDocument(self): """ after a conflict resolution, we have decided to keep the local version of this object """ p_sync = getToolByName(self, 'portal_synchronizations') p_sync.applyPublisherDocument(self) def getPublisherDocument(self): """ after a conflict resolution, we have decided to keep the local version of this object """ p_sync = getToolByName(self, 'portal_synchronizations') return p_sync.getPublisherDocument(self) def getPublisherDocumentPath(self): """ after a conflict resolution, we have decided to keep the local version of this object """ p_sync = getToolByName(self, 'portal_synchronizations') return p_sync.getPublisherDocumentPath(self) def getSubscriberDocument(self): """ after a conflict resolution, we have decided to keep the local version of this object """ p_sync = getToolByName(self, 'portal_synchronizations') return p_sync.getSubscriberDocument(self) def getSubscriberDocumentPath(self): """ after a conflict resolution, we have decided to keep the local version of this object """ p_sync = getToolByName(self, 'portal_synchronizations') return p_sync.getSubscriberDocumentPath(self) def applySubscriberDocument(self): """ after a conflict resolution, we have decided to keep the local version of this object """ p_sync = getToolByName(self, 'portal_synchronizations') p_sync.applySubscriberDocument(self) def applySubscriberValue(self, object=None): """ get the domain """ p_sync = getToolByName(self, 'portal_synchronizations') 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): """ 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 class Signature(Folder, SyncCode): """ 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 = 0 isPortalContent = 0 # Make sure RAD generated accessors at the class level # Constructor def __init__(self, id=None, rid=None, status=None, xml_string=None, object=None): 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.partial_xml = None self.action = None self.setTempXML(None) self.resetConflictList() self.md5_string = None self.force = 0 self.setSubscriberXupdate(None) self.setPublisherXupdate(None) Folder.__init__(self,id) 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 setXML(self, xml): """ set the XML corresponding to the object """ self.xml = xml if self.xml != None: self.setTempXML(None) # We make sure that the xml will not be erased self.setMD5(xml) def getXML(self): """ get the XML corresponding to the object """ xml = getattr(self, 'xml', None) if xml == '': xml = None return xml def setTempXML(self, xml): """ This is the xml temporarily saved, it will be stored with setXML when we will receive the confirmation of synchronization """ self.temp_xml = xml def getTempXML(self): """ get the temp xml """ return self.temp_xml 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 rid is type(u'a'): 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 id is type(u'a'): 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 id is type(u'a'): 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 setPartialXML(self, xml): """ Set the partial string we will have to deliver in the future """ if type(xml) is type(u'a'): xml = xml.encode('utf-8') if xml is not None: xml = xml.replace('@-@@-@','--') # need to put back '--' self.partial_xml = xml def getPartialXML(self): """ Set the partial string we will have to deliver in the future """ return self.partial_xml 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()) def addSubscription( self, id, title='', REQUEST=None ): """ Add a new Subscribption """ o = Subscription( id ,'','','','','','') self._setObject( id, o ) if REQUEST is not None: return self.manage_main(self, REQUEST, update_menu=1) return o #class Subscription(SyncCode, Implicit): #class Subscription(Folder, SyncCode, Implicit, Folder, Impli): from XMLSyncUtils import XMLSyncUtils class Subscription(Folder, XMLSyncUtils): """ Subscription hold the definition of a master ODB from/to which a selection of objects will be synchronised Subscription defined by:: publication_url -- a URI to a publication subsribtion_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 synchronised 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 synchronisation. We choose to keep an MD5 value for all documents which belong to the synchronisation 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 synchronisation next_anchor - it defines the id of the current synchronisation """ meta_type='ERP5 Subscription' portal_type='SyncML Subscription' # may be useful in the future... isPortalContent = 1 isRADContent = 1 icon = None isIndexable = 0 user = None # Declarative properties property_sheets = ( PropertySheet.Base , PropertySheet.SimpleItem ) allowed_types = ( 'Signatures',) # Declarative constructors constructors = (addSubscription,) # Declarative security security = ClassSecurityInfo() security.declareProtected(Permissions.ManagePortal, 'manage_editProperties', 'manage_changeProperties', 'manage_propertiesForm', ) # Constructor def __init__(self, id, title, publication_url, subscription_url, destination_path, source_uri, target_uri, query, xml_mapping, conduit, gpg_key, id_generator, media_type, login, password, activity_enabled, alert_code, synchronize_with_erp5_sites, sync_content_type): """ We need to create a dictionnary of signatures of documents which belong to the synchronisation process """ self.id = id self.setAlertCode(alert_code) self.setActivityEnabled(activity_enabled) self.publication_url = (publication_url) self.subscription_url = str(subscription_url) self.destination_path = str(destination_path) self.setSourceURI(source_uri) self.setTargetURI(target_uri) self.setQuery(query) self.setXMLMapping(xml_mapping) self.anchor = None self.session_id = 0 #self.signatures = PersistentMapping() self.last_anchor = '00000000T000000Z' self.next_anchor = '00000000T000000Z' self.setMediaType(media_type) self.login = login self.password=password self.domain_type = self.SUB self.gpg_key = gpg_key self.setSynchronizationIdGenerator(id_generator) self.setConduit(conduit) Folder.__init__(self, id) self.title = title self.setSyncContentType(sync_content_type) self.setSynchronizeWithERP5Sites(synchronize_with_erp5_sites) #self.signatures = PersitentMapping() def getAlertCodeList(self): return self.CODE_LIST def getAlertCode(self): return getattr(self, 'alert_code', 200) def setAlertCode(self, value): self.alert_code = int(value) def isOneWayFromServer(self): return self.getDomainType() == self.SUB and self.getAlertCode() == self.ONE_WAY_FROM_SERVER def getActivityEnabled(self): """ return true if we are using activity, false otherwise """ return getattr(self, 'activity_enabled', None) def setActivityEnabled(self, activity_enabled): """ set if we are using activity or not """ self.activity_enabled = activity_enabled def getTitle(self): """ getter for title """ return getattr(self,'title',None) def setTitle(self, value): """ setter for title """ self.title = value def setSourceURI(self, value): """ setter for source_uri """ self.source_uri = value def getSourceURI(self): """ getter for the source_uri (the local path of the subscription data base) """ return getattr(self, 'source_uri', None) def setTargetURI(self, value): """ setter for target_uri """ self.target_uri = value def getTargetURI(self): """ getter for the target_uri (the distant Publication data base we want to synchronize with) """ return getattr(self, 'target_uri', None) def setSyncContentType(self, sync_content_type): """ content type used by the subscriber """ self.sync_content_type = sync_content_type # the varible name is sync_content_type instead of content_type because # content_type seems to be a function name already used def getSyncContentType(self): """ getter of the subscriber sync_content_type """ return getattr(self, 'sync_content_type', 'application/vnd.syncml+xml') def getSynchronizationType(self, default=None): """ """ # XXXXXXXXXXXXXXXXXXXXXXXXXXXXX # XXX for debugging only, to be removed #dict_sign = {} #for o in self.getSignatureList(): #dict_sign[o.getId()] = o.getStatus() # LOG('getSignature', DEBUG, 'signatures_status: %s' % str(dict_sign)) # XXXXXXXXXXXXXXXXXXXXXXXXXXXXX code = self.SLOW_SYNC if len(self.getSignatureList()[:1]) > 0: code = self.getAlertCode() if default is not None: code = default #LOG('Subscription', DEBUG, 'getSynchronizationType: %s' % code) return code def setXMLMapping(self, value): """ this the name of the method used in order to set the xml """ if value == '': value = None self.xml_mapping = value def setSynchronizeWithERP5Sites(self, synchronize_with_erp5_sites): """ if the synchronisation is made with another ERP5 site, synchronize_with_erp5_sites is True, False in other case XXX in the future, the method used to sendHttpResponse will be the same in all cases, so this method will be useless """ self.synchronize_with_erp5_sites = synchronize_with_erp5_sites def getSynchronizeWithERP5Sites(self): """ return True if the synchronisation is between two erp5 sites """ return getattr(self, 'synchronize_with_erp5_sites', True) 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 """ last_session_id = getattr(self, 'last_session_id', None) if last_session_id == session_id: return False self.last_session_id = session_id return True 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 """ last_message_id = getattr(self,'last_message_id',None) #LOG('checkCorrectRemoteMessageId last_message_id = ', DEBUG, last_message_id) #LOG('checkCorrectRemoteMessageId message_id = ', DEBUG, message_id) if last_message_id == message_id: return False self.last_message_id = message_id return True def initLastMessageId(self, last_message_id=0): """ set the last message id to 0 """ self.last_message_id = last_message_id def getLastSentMessage(self): """ This is the getter for the last message we have sent """ return getattr(self, 'last_sent_message', '') def setLastSentMessage(self,xml): """ This is the setter for the last message we have sent """ self.last_sent_message = xml def getDomainType(self): """ return the ID """ return self.domain_type def getId(self): """ return the ID """ return self.id def setId(self, id): """ set the ID """ self.id = id def setConduit(self, value): """ set the Conduit """ self.conduit = value def getConduit(self): """ get the Conduit """ return getattr(self, 'conduit', None) def getQuery(self): """ return the query """ return self.query def getGPGKey(self): """ return the gnupg key name """ return getattr(self, 'gpg_key', '') def setGPGKey(self, value): """ setter for the gnupg key name """ self.gpg_key = value def setQuery(self, query): """ set the query """ if query == '': query = None self.query = query 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): """ set the publication url """ self.publication_url = publication_url def getXMLMapping(self, force=0): """ return the xml mapping """ if self.isOneWayFromServer() and force == 0: return None xml_mapping = getattr(self, 'xml_mapping', None) return xml_mapping def getXMLFromObject(self, object, force=0): """ return the xml mapping """ xml_mapping = self.getXMLMapping(force=force) xml = '' if xml_mapping is not None: func = getattr(object, xml_mapping, None) if func is not None: xml = func() return xml def getMediaType(self): """ This method return the type of media used in this session, for example, it could be "text/vcard" or "xml/text",... """ return getattr(self, 'media_type', self.MEDIA_TYPE['TEXT_XML']) def setMediaType(self, media_type): """ set the type of media used """ if media_type in (None, ''): media_type = self.MEDIA_TYPE['TEXT_XML'] self.media_type = media_type def getLogin(self): """ This method return the login of this subscription """ return getattr(self, 'login', '') def setLogin(self, new_login): """ set the login at new_login """ self.login = new_login def getPassword(self): """ This method return the password of this subscription """ return getattr(self, 'password', '') def setPassword(self, new_password): """ set the password at new_password """ self.password = new_password def getZopeUser(self): """ This method return the zope user who begin the synchronization session """ return getattr(self, 'zope_user_name', None) def setZopeUser(self, user_name): """ This method set the zope user_name """ self.zope_user_name = user_name def getAuthenticationFormat(self): """ return the format of authentication """ return getattr(self, 'authentication_format', 'b64') def getAuthenticationType(self): """ return the type of authentication """ return getattr(self, 'authentication_type', 'syncml:auth-basic') def setAuthenticationFormat(self, authentication_format): """ set the format of authentication """ if authentication_format in (None, ''): self.authentication_format = 'b64' else: self.authentication_format = authentication_format def setAuthenticationType(self, authentication_type): """ set the type of authentication """ if authentication_type in (None, ''): self.authentication_type = 'syncml:auth-basic' else: self.authentication_type = authentication_type def getGidFromObject(self, object): """ """ o_base = aq_base(object) o_gid = None conduit_name = self.getConduit() conduit = self.getConduitByName(conduit_name) gid_gen = getattr(conduit, 'getGidFromObject', None) if callable(gid_gen): o_gid = gid_gen(object) else: raise ValueError, "The conduit "+conduit_name+"seems to not have a \ getGidFromObject method and it must" o_gid = b16encode(o_gid) #LOG('getGidFromObject returning', DEBUG, o_gid) return o_gid def getObjectFromGid(self, gid): """ This tries to get the object with the given gid This uses the query if it exist """ 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.getDestination() if signature is not None and signature.getPath() is not None: o = None try: o = destination.getPortalObject().restrictedTraverse(signature.getPath()) except (AttributeError, KeyError, TypeError): pass o_id = signature.getObjectId() #try with id param too, because gid is not catalogged object_list = self.getObjectList(gid = b16decode(gid), id = o_id) #LOG('getObjectFromGid :', DEBUG, 'object_list=%s, gid=%s, o_id=%s' % (object_list, gid, o_id)) if o is not None and o in object_list: return o #LOG('entering in the slow loop of getObjectFromGid !!!',0,'') object_list = self.getObjectList(gid = b16decode(gid)) #LOG('getObjectFromGid :', DEBUG, 'object_list slow loop=%s, gid=%s' % (object_list, gid)) for o in object_list: o_gid = self.getGidFromObject(o) if o_gid == gid: return o #LOG('getObjectFromGid', DEBUG, 'returning None') return None 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 def getObjectFromRid(self, rid): """ return the object corresponding to the id """ signature = self.getSignatureFromRid(rid) destination = self.getDestination() o = None if signature is not None and signature.getPath() is not None: try: o = destination.getPortalObject().restrictedTraverse(signature.getPath()) except: pass return o def getObjectList(self, **kw): """ This returns the list of sub-object corresponding to the query """ destination = self.getDestination() query = self.getQuery() query_list = [] if query is not None and isinstance(query, str): query_method = getattr(destination, query, None) if query_method is not None: query_list = query_method(**kw) elif callable(query): # used in the test query_list = query(destination) else: LOG('This Subscriber %s provide no Query with id :' % (self.getTitle()), INFO, query) return [x for x in query_list if not getattr(x,'_conflict_resolution',False)] def generateNewIdWithGenerator(self, object=None, gid=None): """ This tries to generate a new Id """ id_generator = self.getSynchronizationIdGenerator() 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('generateNewId, new_id: ', DEBUG, new_id) return new_id return None def setSynchronizationIdGenerator(self, method): """ This set the method name wich allows to generate a new id """ if method in ('', 'None'): method = None self.synchronization_id_generator = method def getSynchronizationIdGenerator(self): """ This get the method name wich allows to generate a new id """ return getattr(self, 'synchronization_id_generator', None) def getSubscriptionUrl(self): """ return the subscription url """ return self.subscription_url def setSubscriptionUrl(self, subscription_url): """ set the subscription url """ self.subscription_url = subscription_url def getDestinationPath(self): """ return the destination path """ return self.destination_path def getDestination(self): """ return the destination object itself """ return self.unrestrictedTraverse(self.getDestinationPath()) def setDestinationPath(self, destination_path): """ set the destination path """ self.destination_path = destination_path def getSubscription(self): """ return the current subscription """ return self def setSessionId(self, session_id): """ set the session id """ self.session_id = session_id def getSessionId(self): """ return the session id """ #self.session_id += 1 #to be commented return self.session_id def incrementSessionId(self): """ increment and return the session id """ self.session_id += 1 self.resetMessageId() # for a new session, the message Id must be reset return self.session_id def incrementMessageId(self): """ return the message id """ value = getattr(self, 'message_id', 0) self.message_id = value +1 return self.message_id def getMessageId(self): """ increment and return the message id """ return self.message_id def resetMessageId(self): """ set the message id to 0 """ self.message_id = 0 def setMessageId(self, message_id): """ set the message id to message_id """ self.message_id = message_id def getLastAnchor(self): """ return the id of the last synchronisation """ return self.last_anchor def getNextAnchor(self): """ return the id of the current synchronisation """ return self.next_anchor def setLastAnchor(self, last_anchor): """ set the value last anchor """ self.last_anchor = last_anchor def setNextAnchor(self, next_anchor): """ set the value next anchor """ # We store the old next anchor as the new last one self.last_anchor = self.next_anchor self.next_anchor = next_anchor def NewAnchor(self): """ set a new anchor """ self.last_anchor = self.next_anchor self.next_anchor = strftime("%Y%m%dT%H%M%SZ", gmtime()) def resetAnchors(self): """ reset both last and next anchors """ self.last_anchor = self.NULL_ANCHOR self.next_anchor = self.NULL_ANCHOR def addSignature(self, signature): """ add a Signature to the subscription """ if self.getSignatureFromGid(signature.getGid()) is not None: self.delSignature(signature.getGid()) self._setObject(signature.getGid(), aq_base(signature)) def delSignature(self, gid): """ del a Signature of the subscription """ self._delObject(gid) def getSignatureFromObjectId(self, id): """ return the signature corresponding to the id """ o = None # XXX very slow for signature in self.getSignatureList(): if id == signature.getObjectId(): o = signature break return o def getSignatureFromGid(self, gid): """ return the signature corresponding to the gid """ return getattr(self, gid, None) def getSignatureFromRid(self, rid): """ return the signature corresponding to the rid """ o = None # XXX very slow for signature in self.getSignatureList(): if rid == signature.getRid(): o = signature break return o def getGidList(self): """ Returns the list of gids from signature """ return [id for id in self.getObjectIds()] def getSignatureList(self): """ Returns the list of Signatures """ return self.objectValues() def hasSignature(self, gid): """ Check if there's a signature with this uid """ return self.getSignatureFromGid(gid) is not None def resetAllSignatures(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, 100): current_id_list = object_id_list[i:i+100] self.activate(activity='SQLQueue').manage_delObjects(current_id_list) def getConflictList(self): """ Return the list of all conflicts from all signatures """ conflict_list = [] for signature in self.getSignatureList(): conflict_list.extend(signature.getConflictList()) return conflict_list def getRemainingObjectPathList(self): """ We should now wich objects should still synchronize """ return getattr(self, 'remaining_object_path_list', None) def setRemainingObjectPathList(self, value): """ We should now wich objects should still synchronize """ setattr(self, 'remaining_object_path_list', value) def removeRemainingObjectPath(self, object_path): """ We should now wich objects should still synchronize """ remaining_object_list = self.getRemainingObjectPathList() if remaining_object_list is not None: new_list = [] new_list.extend(remaining_object_list) while object_path in new_list: new_list.remove(object_path) self.setRemainingObjectPathList(new_list) def startSynchronization(self): """ Set the status of every object as NOT_SYNCHRONIZED """ for s in self.getSignatureList(): # Change the status only if we are not in a conflict mode if s.getStatus() not in (self.CONFLICT, self.PUB_CONFLICT_MERGE, self.PUB_CONFLICT_CLIENT_WIN): s.setStatus(self.NOT_SYNCHRONIZED) s.setPartialXML(None) s.setTempXML(None) self.setRemainingObjectPathList(None) def isAuthenticated(self): """ return True if the subscriber is authenticated for this session, False in other case """ return getattr(self, 'is_authenticated', None) def setAuthenticated(self, value): """ set at True or False the value of is_authenticated is the subscriber is authenticated for this session or not """ self.is_authenticated = value def encode(self, format, string_to_encode): """ return the string_to_encode encoded with format format """ if format in ('', None): return string_to_encode if format == 'b64': return b64encode(string_to_encode) #elif format is .... put here the other formats else:#if there is no format corresponding with format, raise an error LOG('encode : unknown or not implemented format : ', INFO, format) raise ValueError, "Sorry, the server ask for the format %s but it's unknow or not implemented" % format def decode(self, format, string_to_decode): """ return the string_to_decode decoded with format format """ string_to_decode = string_to_decode.encode('utf-8') if format in ('', None): return string_to_decode if format == 'b64': return b64decode(string_to_decode) #elif format is .... put here the other formats else:#if there is no format corresponding with format, raise an error LOG('decode : unknown or not implemented format :', INFO, format) raise ValueError, "Sorry, the format %s is unknow or not implemented" % format def isDecodeEncodeTheSame(self, string_encoded, string_decoded, format): """ return True if the string_encoded is equal to string_decoded encoded in format """ return self.encode(format, string_decoded) == string_encoded def setUser(self, user): """ save the user logged in to log him on each transaction """ self.user = user def getUser(self): """ retrun the user logged in """ return getattr(self, 'user', None) def getConduitByName(self, conduit_name): """ Get Conduit Object by given name. The Conduit can be located in Any Products according to naming Convention Products.<Product Name>.Conduit.<Conduit Module> ,if conduit_name equal module's name. By default Conduit must be defined in Products.ERP5SyncML.Conduit.<Conduit Module> """ from Products.ERP5SyncML import Conduit if conduit_name.startswith('Products'): path = conduit_name conduit_name = conduit_name.split('.')[-1] conduit_module = __import__(path, globals(), locals(), ['']) conduit = getattr(conduit_module, conduit_name)() else: conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]), globals(), locals(), ['']) conduit = getattr(conduit_module, conduit_name)() return conduit