Subscription.py 32.1 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
##############################################################################
#
# 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
32
from AccessControl import ClassSecurityInfo
Sebastien Robin's avatar
Sebastien Robin committed
33 34
from Products.CMFCore.utils import getToolByName
from Acquisition import Implicit, aq_base
Jean-Paul Smets's avatar
Jean-Paul Smets committed
35
from Products.ERP5Type.Core.Folder import Folder
36 37 38
from Products.ERP5Type.Base import Base
from Products.ERP5Type import Permissions
from Products.ERP5Type import PropertySheet
Sebastien Robin's avatar
Sebastien Robin committed
39
from DateTime import DateTime
Jean-Paul Smets's avatar
Jean-Paul Smets committed
40 41 42 43
from zLOG import LOG

import md5

44 45
#class Conflict(SyncCode, Implicit):
class Conflict(SyncCode, Base):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
46 47 48
  """
    object_path : the path of the obect
    keyword : an identifier of the conflict
49 50
    publisher_value : the value that we have locally
    subscriber_value : the value sent by the remote box
Jean-Paul Smets's avatar
Jean-Paul Smets committed
51 52

  """
53
  isIndexable = 0
54
  isPortalContent = 0 # Make sure RAD generated accessors at the class level
55

56 57
  def __init__(self, object_path=None, keyword=None, xupdate=None, publisher_value=None,\
               subscriber_value=None, subscriber=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
58 59
    self.object_path=object_path
    self.keyword = keyword
60 61 62
    self.setLocalValue(publisher_value)
    self.setRemoteValue(subscriber_value)
    self.subscriber = subscriber
63
    self.resetXupdate()
64
    self.copy_path = None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
65 66 67

  def getObjectPath(self):
    """
68
    get the object path
Jean-Paul Smets's avatar
Jean-Paul Smets committed
69 70 71
    """
    return self.object_path

72
  def getPublisherValue(self):
73 74 75
    """
    get the domain
    """
76
    return self.publisher_value
77

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
  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

109 110 111 112 113
  def setLocalValue(self, value):
    """
    get the domain
    """
    try:
114
      self.publisher_value = value
115
    except TypeError: # It happens when we try to store StringIO
116
      self.publisher_value = None
117

118
  def getSubscriberValue(self):
119 120 121
    """
    get the domain
    """
122
    return self.subscriber_value
123 124 125 126 127 128

  def setRemoteValue(self, value):
    """
    get the domain
    """
    try:
129
      self.subscriber_value = value
130
    except TypeError: # It happens when we try to store StringIO
131
      self.subscriber_value = None
132

133
  def applyPublisherValue(self):
Sebastien Robin's avatar
Sebastien Robin committed
134 135 136 137 138
    """
      after a conflict resolution, we have decided
      to keep the local version of this object
    """
    p_sync = getToolByName(self,'portal_synchronizations')
139
    p_sync.applyPublisherValue(self)
Sebastien Robin's avatar
Sebastien Robin committed
140

141 142 143 144 145 146 147 148
  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)

149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
  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')
179
    return p_sync.getSubscriberDocumentPath(self)
180

181
  def applySubscriberDocument(self):
182 183 184 185 186 187 188
    """
      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)

189
  def applySubscriberValue(self,object=None):
Sebastien Robin's avatar
Sebastien Robin committed
190 191 192 193
    """
    get the domain
    """
    p_sync = getToolByName(self,'portal_synchronizations')
194
    p_sync.applySubscriberValue(self,object=object)
Sebastien Robin's avatar
Sebastien Robin committed
195

196
  def setSubscriber(self, subscriber):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
197 198 199
    """
    set the domain
    """
200
    self.subscriber = subscriber
Jean-Paul Smets's avatar
Jean-Paul Smets committed
201

202
  def getSubscriber(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
203 204 205
    """
    get the domain
    """
206
    return self.subscriber
Jean-Paul Smets's avatar
Jean-Paul Smets committed
207

208 209 210 211 212 213
  def getKeyword(self):
    """
    get the domain
    """
    return self.keyword

214
  def getPropertyId(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
215
    """
216
    get the property id
Jean-Paul Smets's avatar
Jean-Paul Smets committed
217
    """
218
    return self.keyword
Jean-Paul Smets's avatar
Jean-Paul Smets committed
219

220 221 222 223 224 225 226 227 228 229 230 231
  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
    
232

233
class Signature(Folder,SyncCode):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
234 235 236 237 238
  """
    status -- SENT, CONFLICT...
    md5_object -- An MD5 value of a given document
    #uid -- The UID of the document
    id -- the ID of the document
239
    gid -- the global id of the document
Jean-Paul Smets's avatar
Jean-Paul Smets committed
240 241 242 243
    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
  """
244
  isIndexable = 0
245 246
  isPortalContent = 0 # Make sure RAD generated accessors at the class level
  
Jean-Paul Smets's avatar
Jean-Paul Smets committed
247
  # Constructor
248
  def __init__(self,gid=None, id=None, status=None, xml_string=None,object=None):
249
    self.setGid(gid)
250 251
    if object is not None:
      self.setPath(object.getPhysicalPath())
252
    self.setId(id)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
253 254 255 256 257 258
    self.status = status
    self.setXML(xml_string)
    self.partial_xml = None
    self.action = None
    self.setTempXML(None)
    self.resetConflictList()
259
    self.md5_string = None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
260
    self.force = 0
261 262
    self.setSubscriberXupdate(None)
    self.setPublisherXupdate(None)
263
    Folder.__init__(self,id)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
264 265 266 267 268 269 270 271 272 273 274 275 276 277

  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(self.getTempXML())
      self.setTempXML(None)
278
      self.setPartialXML(None)
279
      self.setSubscriberXupdate(None)
Sebastien Robin's avatar
Sebastien Robin committed
280
      self.setPublisherXupdate(None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
281 282
      if len(self.getConflictList())>0:
        self.resetConflictList()
Sebastien Robin's avatar
Sebastien Robin committed
283 284 285
      # XXX This may be a problem, if the document is changed
      # during a synchronization
      self.setLastSynchronizationDate(DateTime())
286
      self.getParentValue().removeRemainingObjectPath(self.getPath())
287 288 289
    if status == self.NOT_SYNCHRONIZED:
      self.setTempXML(None)
      self.setPartialXML(None)
290 291 292
    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()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
293 294 295 296 297 298 299

  def getStatus(self):
    """
      get the Status (see SyncCode for numbers)
    """
    return self.status

300 301 302 303 304 305 306 307 308 309 310 311
  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

Jean-Paul Smets's avatar
Jean-Paul Smets committed
312 313 314 315 316 317 318 319 320 321 322 323
  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

Sebastien Robin's avatar
Sebastien Robin committed
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
  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)

Jean-Paul Smets's avatar
Jean-Paul Smets committed
352 353 354 355 356 357 358
  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
359
      self.setMD5(xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
360 361 362 363 364

  def getXML(self):
    """
      set the XML corresponding to the object
    """
365 366 367 368
    xml =  getattr(self,'xml',None)
    if xml == '':
      xml = None
    return xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383

  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

384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407
  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

Jean-Paul Smets's avatar
Jean-Paul Smets committed
408 409 410 411 412 413 414 415 416 417
  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
    """
418
    return self.md5_string
Jean-Paul Smets's avatar
Jean-Paul Smets committed
419 420 421 422 423 424 425 426

  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
    """
427
    return ((md5.new(xml_string).digest()) == self.getMD5())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452

  def setRid(self, rid):
    """
      set the rid
    """
    self.rid = rid

  def getRid(self):
    """
      get the rid
    """
    return self.rid

  def setId(self, id):
    """
      set the id
    """
    self.id = id

  def getId(self):
    """
      get the id
    """
    return self.id

453 454 455 456 457 458 459 460 461 462 463 464
  def setGid(self, gid):
    """
      set the id
    """
    self.gid = gid

  def getGid(self):
    """
      get the id
    """
    return self.gid

Jean-Paul Smets's avatar
Jean-Paul Smets committed
465 466 467 468 469
  def setPartialXML(self, xml):
    """
    Set the partial string we will have to
    deliver in the future
    """
470 471
    if type(xml) is type(u'a'):
      xml = xml.encode('utf-8')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
472 473 474 475 476 477 478
    self.partial_xml = xml

  def getPartialXML(self):
    """
    Set the partial string we will have to
    deliver in the future
    """
479
    #LOG('Subscriber.getPartialXML',0,'partial_xml: %s' % str(self.partial_xml))
480 481
    if self.partial_xml is not None:
      self.partial_xml = self.partial_xml.replace('@-@@-@','--') # need to put back '--'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
    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
    """
    conflict_list = []
    if len(self.conflict_list)>0:
      for conflict in self.conflict_list:
        conflict_list += [conflict]
    return 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
    """
516
    # LOG('setConflictList, list',0,conflict_list)
Sebastien Robin's avatar
Sebastien Robin committed
517
    if conflict_list is None or conflict_list==[]:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
518 519
      self.resetConflictList()
    else:
Sebastien Robin's avatar
Sebastien Robin committed
520 521 522 523 524 525
      self.conflict_list = conflict_list

  def delConflict(self, conflict):
    """
    Return the actual action for a partial synchronization
    """
526
    # LOG('delConflict, conflict',0,conflict)
Sebastien Robin's avatar
Sebastien Robin committed
527 528 529 530 531 532 533 534 535
    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()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
536

537 538 539 540
  def getObject(self):
    """
    Returns the object corresponding to this signature
    """
541
    return self.getParentValue().getObjectFromGid(self.getGid())
542

543 544 545 546 547
  def checkSynchronizationNeeded(self, object):
    """
    We will look at date, if there is no changes, no need to syncrhonize
    """
    last_modification = DateTime(object.ModificationDate())
548
    # LOG('checkSynchronizationNeeded object.ModificationDate()',0,object.ModificationDate())
549 550 551 552 553 554 555 556 557 558
    last_synchronization = self.getLastSynchronizationDate()
    parent = object.aq_parent
    # XXX CPS Specific
    if parent.id == 'portal_repository': # Make sure there is no sub objects
    #if 1:
      if last_synchronization is not None and last_modification is not None \
        and self.getSubscriberXupdate() is None and self.getPublisherXupdate() is None and \
        self.getStatus()==self.NOT_SYNCHRONIZED:
        if last_synchronization > last_modification:
        #if 1:
559
          # LOG('checkSynchronizationNeeded, no modification on: ',0,object.id)
560 561 562
          self.setStatus(self.SYNCHRONIZED)
    

563

564 565
def addSubscription( self, id, title='', REQUEST=None ):
    """
566
    Add a new Subscribption
567 568 569 570 571 572 573 574
    """
    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):
575 576
#class Subscription(Folder, SyncCode, Implicit, Folder, Impli):
class Subscription(Folder, SyncCode):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
  """
    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

594 595
    gpg_key -- the name of a gpg key to use

Jean-Paul Smets's avatar
Jean-Paul Smets committed
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611
    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

  """

612
  meta_type='ERP5 Subscription'
613
  portal_type='SyncML Subscription' # may be useful in the future...
614 615 616 617
  isPortalContent = 1
  isRADContent = 1
  icon = None

618
  isIndexable = 0
619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635

  # 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',
                              )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
636 637

  # Constructor
638
  def __init__(self, id, title, publication_url, subscription_url, destination_path, query, xml_mapping, conduit, gpg_key):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
639 640 641 642 643 644 645 646 647
    """
      We need to create a dictionnary of
      signatures of documents which belong to the synchronisation
      process
    """
    self.id = id
    self.publication_url = (publication_url)
    self.subscription_url = str(subscription_url)
    self.destination_path = str(destination_path)
648
    self.setQuery(query)
649
    self.setXMLMapping(xml_mapping)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
650 651
    self.anchor = None
    self.session_id = 0
652
    #self.signatures = PersistentMapping()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
653 654 655
    self.last_anchor = '00000000T000000Z'
    self.next_anchor = '00000000T000000Z'
    self.domain_type = self.SUB
656
    self.gpg_key = gpg_key
657 658
    self.setGidGenerator(None)
    self.setIdGenerator(None)
659
    self.setConduit(conduit)
660 661
    Folder.__init__(self, id)
    self.title = title
Sebastien Robin's avatar
Sebastien Robin committed
662
    
Jean-Paul Smets's avatar
Jean-Paul Smets committed
663 664
    #self.signatures = PersitentMapping()

665 666 667 668 669 670 671 672 673 674 675 676
  def getTitle(self):
    """
    getter for title
    """
    return getattr(self,'title',None)

  def setTitle(self, value):
    """
    setter for title
    """
    self.title = value

Jean-Paul Smets's avatar
Jean-Paul Smets committed
677 678 679 680 681 682 683 684 685 686 687 688
  # Accessors
  def getRemoteId(self, id, path=None):
    """
      Returns the remote id from a know local id
      Returns None if...
      path allows to implement recursive sync
    """
    pass

  def getSynchronizationType(self, default=None):
    """
    """
689 690 691
    # XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    # XXX for debugging only, to be removed
    dict_sign = {}
692 693
    for o in self.objectValues():
      dict_sign[o.getId()] = o.getStatus()
694
    # LOG('getSignature',0,'signatures_status: %s' % str(dict_sign))
695
    # XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Jean-Paul Smets's avatar
Jean-Paul Smets committed
696
    code = self.SLOW_SYNC
697
    if len(self.objectValues()) > 0:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
698 699 700
      code = self.TWO_WAY
    if default is not None:
      code = default
701
    # LOG('Subscription',0,'getSynchronizationType: %s' % code)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
702 703
    return code

704 705 706 707 708 709 710 711
  def setXMLMapping(self, value):
    """
    this the name of the method used in order to get the xml
    """
    if value == '':
      value = None
    self.xml_mapping = value

712 713 714 715 716 717 718 719 720 721 722 723 724
  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 1 if the session id was not seen, 0 if already seen
    """
    last_session_id = getattr(self,'last_session_id',None)
    if last_session_id == session_id:
      return 0
    self.last_session_id = session_id
    return 1

Sebastien Robin's avatar
Sebastien Robin committed
725 726 727 728 729 730 731 732
  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 1 if the message id was not seen, 0 if already seen
    """
    last_message_id = getattr(self,'last_message_id',None)
733 734
    # LOG('checkCorrectRemoteMessageId  last_message_id =',0,last_message_id)
    # LOG('checkCorrectRemoteMessageId  message_id =',0,message_id)
Sebastien Robin's avatar
Sebastien Robin committed
735 736 737 738 739 740 741 742 743 744
    if last_message_id == message_id:
      return 0
    self.last_message_id = message_id
    return 1

  def initLastMessageId(self, last_message_id=None):
    """
    set the last message id to 0
    """
    self.last_message_id=last_message_id
745 746 747 748 749 750 751 752 753 754 755 756 757

  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

Jean-Paul Smets's avatar
Jean-Paul Smets committed
758 759 760 761 762 763 764 765 766 767 768 769 770
  def getLocalId(self, rid, path=None):
    """
      Returns the local id from a know remote id
      Returns None if...
    """
    pass

  def getId(self):
    """
      return the ID
    """
    return self.id

771 772 773 774 775 776
  def getDomainType(self):
    """
      return the ID
    """
    return self.domain_type

Jean-Paul Smets's avatar
Jean-Paul Smets committed
777 778 779 780 781 782
  def setId(self, id):
    """
      set the ID
    """
    self.id = id

783 784 785 786 787 788 789 790 791 792 793 794 795
  def setConduit(self, value):
    """
      set the Conduit
    """
    self.conduit = value

  def getConduit(self):
    """
      get the Conduit

    """
    return getattr(self,'conduit',None)

Jean-Paul Smets's avatar
Jean-Paul Smets committed
796 797 798 799 800 801
  def getQuery(self):
    """
      return the query
    """
    return self.query

802 803 804 805 806 807
  def getGPGKey(self):
    """
      return the gnupg key name
    """
    return getattr(self,'gpg_key','')

808 809 810 811 812 813
  def setGPGKey(self, value):
    """
      setter for the gnupg key name
    """
    self.gpg_key = value

Jean-Paul Smets's avatar
Jean-Paul Smets committed
814 815 816 817
  def setQuery(self, query):
    """
      set the query
    """
818 819
    if query == '':
      query = None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839
    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):
    """
      return the publication url
    """
    self.publication_url = publication_url

840
  def getXMLMapping(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
841 842 843
    """
      return the xml mapping
    """
844 845
    xml_mapping = getattr(self,'xml_mapping','asXML')
    return xml_mapping
Jean-Paul Smets's avatar
Jean-Paul Smets committed
846

847
  def getXMLFromObject(self,object):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
848 849 850
    """
      return the xml mapping
    """
851 852 853 854 855 856 857
    xml_mapping = self.getXMLMapping()
    xml = ''
    if xml_mapping is not None:
      func = getattr(object,xml_mapping,None)
      if func is not None:
        xml = func()
    return xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
858

859
  def setGidGenerator(self, method):
860 861 862 863
    """
    This set the method name wich allows to find a gid
    from any object
    """
864
    if method in (None,'','None'):
865 866
      method = 'getId'
    self.gid_generator = method
867 868 869 870 871 872 873 874

  def getGidGenerator(self):
    """
    This get the method name wich allows to find a gid
    from any object
    """
    return self.gid_generator

875 876 877 878 879
  def getGidFromObject(self, object):
    """
    """
    o_base = aq_base(object)
    o_gid = None
880
    # LOG('getGidFromObject',0,'gidgenerator : _%s_' % repr(self.getGidGenerator()))
881 882
    gid_gen = self.getGidGenerator()
    if callable(gid_gen):
883
      # LOG('getGidFromObject gid_generator',0,'is callable')
884
      o_gid=gid_gen(object)
885
      # LOG('getGidFromObject',0,'o_gid: %s' % repr(o_gid))
886
    elif getattr(o_base, gid_gen, None) is not None:
887
      # LOG('getGidFromObject',0,'there is the gid generator on o_base')
888
      generator = getattr(object, gid_gen)
889 890
      o_gid = generator() # XXX - used to be o_gid = generator(object=object) which is redundant
      # LOG('getGidFromObject',0,'o_gid: %s' % repr(o_gid))
891 892
    elif gid_gen is not None:
      # It might be a script python
893
      # LOG('getGidFromObject',0,'there is the gid generator')
894
      generator = getattr(object,gid_gen)
895 896
      o_gid = generator() # XXX - used to be o_gid = generator(object=object) which is redundant
      # LOG('getGidFromObject',0,'o_gid: %s' % repr(o_gid))
897 898
    return o_gid

899 900 901 902 903 904 905 906 907 908
  def getObjectFromGid(self, gid):
    """
    This tries to get the object with the given gid
    This uses the query if it exist
    """
    signature = self.getSignature(gid)
    # First look if we do already have the mapping between
    # the id and the gid
    object_list = self.getObjectList()
    destination = self.getDestination()
909 910 911
    # LOG('getObjectFromGid', 0, 'self: %s' % self)    
    # LOG('getObjectFromGid',0,'gid: %s' % repr(gid))
    # LOG('getObjectFromGid oject_list',0,object_list)
912
    if signature is not None and signature.getId() is not None:
913
      o_id = signature.getId()
914
      # LOG('getObjectFromGid o_id',0,o_id)
915 916 917
      o = None
      try:
        o = destination._getOb(o_id)
918
      except (AttributeError, KeyError, TypeError):
919 920 921 922
        pass
      if o is not None and o in object_list:
        return o
    for o in object_list:
923
      # LOG('getObjectFromGid',0,'working on : %s' % repr(o))
924 925 926
      o_gid = self.getGidFromObject(o)
      if o_gid == gid:
        return o
927
    # LOG('getObjectFromGid',0,'returning None')
928 929
    return None

930 931 932 933 934 935 936 937 938
#  def setOneWaySyncFromServer(self,value):
#    """
#    If this option is enabled, then we will not 
#    send our own modifications
#    """
#    self.one_way_sync_from_server = value
#


939 940 941 942 943 944 945 946
  def getObjectList(self):
    """
    This returns the list of sub-object corresponding
    to the query
    """
    destination = self.getDestination()
    query = self.getQuery()
    query_list = []
947 948
    if query is None:
      return query_list
Sebastien Robin's avatar
Sebastien Robin committed
949
    if isinstance(query, str):
950 951 952
      query_method = getattr(destination,query,None)
      if query_method is not None:
        query_list = query_method()
953
    elif callable(query): # XXX - used to be if callable(query)
954
      query_list = query(destination)
955 956
    return [x for x in query_list
              if not getattr(x,'_conflict_resolution',False)]
957

958
  def generateNewIdWithGenerator(self, object=None,gid=None):
959 960 961
    """
    This tries to generate a new Id
    """
962
    # LOG('generateNewId, object: ',0,object.getPhysicalPath())
963
    id_generator = self.getIdGenerator()
964 965
    # LOG('generateNewId, id_generator: ',0,id_generator)
    # LOG('generateNewId, portal_object: ',0,object.getPortalObject())
966
    if id_generator is not None:
967
      o_base = aq_base(object)
968 969
      new_id = None
      if callable(id_generator):
970
        new_id = id_generator(object,gid=gid)
971
      elif getattr(o_base, id_generator, None) is not None:
972
        generator = getattr(object, id_generator)
973
        new_id = generator()
974 975 976 977
      else: 
        # This is probably a python scrip
        generator = getattr(object, id_generator)
        new_id = generator(object=object,gid=gid)
978 979
      LOG('generateNewId, new_id: ',0,new_id)
      return new_id
980 981
    return None

982
  def setIdGenerator(self, method):
983 984 985 986
    """
    This set the method name wich allows to generate
    a new id
    """
987 988
    if method in ('','None'):
      method = None
989
    self.id_generator = method
990 991 992 993 994 995 996

  def getIdGenerator(self):
    """
    This get the method name wich allows to generate a new id
    """
    return self.id_generator

Jean-Paul Smets's avatar
Jean-Paul Smets committed
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
  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

1015 1016 1017 1018 1019 1020
  def getDestination(self):
    """
      return the destination object itself
    """
    return self.unrestrictedTraverse(self.getDestinationPath())

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031
  def setDestinationPath(self, destination_path):
    """
      set the destination path
    """
    self.destination_path = destination_path

  def getSubscription(self):
    """
      return the current subscription
    """
    return self
Sebastien Robin's avatar
Sebastien Robin committed
1032 1033 1034 1035 1036 1037
    
  def setSessionId(self, session_id):
    """
      set the session id
    """
    self.session_id = session_id
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1038 1039 1040 1041 1042

  def getSessionId(self):
    """
      return the session id
    """
Sebastien Robin's avatar
Sebastien Robin committed
1043 1044 1045 1046 1047 1048 1049
    #self.session_id += 1 #to be commented
    return self.session_id

  def incrementSessionId(self):
    """
      increment and return the session id
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1050
    self.session_id += 1
Sebastien Robin's avatar
Sebastien Robin committed
1051
    self.resetMessageId() # for a new session, the message Id must be reset
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1052 1053
    return self.session_id

Sebastien Robin's avatar
Sebastien Robin committed
1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076
  def incrementMessageId(self):
    """
      return the message id
    """
    #self.message_id += 1
    #return self.message_id
    #return 5
    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

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120
  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
    """
1121 1122
    if signature.getGid() in self.objectIds():
      self._delObject(signature.getGid())
1123
    self._setObject( signature.getGid(), aq_base(signature) )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1124

1125
  def delSignature(self, gid):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1126 1127 1128
    """
      add a Signature to the subscription
    """
1129 1130
    #del self.signatures[gid]
    self._delObject(gid)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1131

1132
  def getSignature(self, gid):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1133 1134 1135
    """
      add a Signature to the subscription
    """
1136 1137 1138
    o = None
    if gid in self.objectIds():
      o = self._getOb(gid)
1139 1140
    #if o is not None:
    #  return o.__of__(self)
1141
    return o
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1142 1143 1144 1145 1146

  def getSignatureList(self):
    """
      add a Signature to the subscription
    """
1147
    return self.objectValues()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1148

1149
  def hasSignature(self, gid):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1150 1151 1152
    """
      Check if there's a signature with this uid
    """
1153 1154
    #return self.signatures.has_key(gid)
    return gid in self.objectIds()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1155 1156 1157 1158 1159

  def resetAllSignatures(self):
    """
      Reset all signatures
    """
1160 1161 1162
    while len(self.objectIds())>0:
      for id in self.objectIds():
        self._delObject(id)
1163

1164
  def getGidList(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1165 1166 1167
    """
    Returns the list of ids from signature
    """
1168
    return self.objectIds()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1169 1170 1171 1172 1173 1174 1175 1176 1177 1178

  def getConflictList(self):
    """
    Return the list of all conflicts from all signatures
    """
    conflict_list = []
    for signature in self.getSignatureList():
      conflict_list += signature.getConflictList()
    return conflict_list

1179
  def getRemainingObjectPathList(self):
1180 1181 1182 1183
    """
    We should now wich objects should still
    synchronize
    """
1184
    return getattr(self,'remaining_object_path_list',None)
1185

1186
  def setRemainingObjectPathList(self, value):
1187 1188 1189 1190
    """
    We should now wich objects should still
    synchronize
    """
1191
    setattr(self,'remaining_object_path_list',value)
1192

1193
  def removeRemainingObjectPath(self, object_path):
1194 1195 1196 1197
    """
    We should now wich objects should still
    synchronize
    """
1198
    remaining_object_list = self.getRemainingObjectPathList()
1199 1200 1201
    if remaining_object_list is not None:
      new_list = []
      for o in remaining_object_list:
1202
        if o != object_path:
1203
          new_list.append(o)
1204
      self.setRemainingObjectPathList(new_list)
1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219

#  def getCurrentObject(self):
#    """
#    When we send some partial data, then we should
#    always synchronize the same object until it is finished
#    """
#    getattr(self,'current_object',None)
#
#  def setCurrentObject(self,object):
#    """
#    When we send some partial data, then we should
#    always synchronize the same object until it is finished
#    """
#    setattr(self,'current_object',object)

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1220 1221 1222 1223
  def startSynchronization(self):
    """
    Set the status of every object as NOT_SYNCHRONIZED
    """
1224
    for o in self.objectValues():
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1225
      # Change the status only if we are not in a conflict mode
1226
      if not(o.getStatus() in (self.CONFLICT,self.PUB_CONFLICT_MERGE,
1227
                                                        self.PUB_CONFLICT_CLIENT_WIN)):
1228 1229 1230
        o.setStatus(self.NOT_SYNCHRONIZED)
        o.setPartialXML(None)
        o.setTempXML(None)
1231
    self.setRemainingObjectPathList(None)
1232