Subscription.py 27 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
##############################################################################
#
# 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.
#
##############################################################################

29
from Products.ERP5Type.Globals import PersistentMapping
30
from time import gmtime, strftime # for anchors
Jean-Paul Smets's avatar
Jean-Paul Smets committed
31
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
35
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
Jean-Paul Smets's avatar
Jean-Paul Smets committed
36
from Products.ERP5Type.Core.Folder import Folder
37 38 39
from Products.ERP5Type.Base import Base
from Products.ERP5Type import Permissions
from Products.ERP5Type import PropertySheet
40
from OFS.Image import File
Sebastien Robin's avatar
Sebastien Robin committed
41
from DateTime import DateTime
42
from zLOG import LOG, DEBUG, INFO
Jean-Paul Smets's avatar
Jean-Paul Smets committed
43
import md5
44
from base64 import b64encode, b64decode, b16encode, b16decode
45

46
def addSubscription( self, id, title='', REQUEST=None ):
47 48 49 50 51 52 53 54
  """
  Add a new Subscription
  """
  o = Subscription(id, '', '', '', '', '', '')
  self._setObject(id, o)
  if REQUEST is not None:
    return self.manage_main(self, REQUEST, update_menu=1)
  return o
55 56

#class Subscription(SyncCode, Implicit):
57
#class Subscription(Folder, SyncCode, Implicit, Folder, Impli):
58 59
from XMLSyncUtils import XMLSyncUtils

60
class Subscription(Folder, XMLSyncUtils, File):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
61 62 63 64 65 66 67 68
  """
    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

69
    subscription_url -- URL of ourselves
Jean-Paul Smets's avatar
Jean-Paul Smets committed
70 71 72 73 74 75 76 77

    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

78 79
    gpg_key -- the name of a gpg key to use

Jean-Paul Smets's avatar
Jean-Paul Smets committed
80 81 82 83 84 85 86 87 88 89 90 91 92
    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
93 94 95 96 97
    
    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 
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
98

99 100
  meta_type = 'ERP5 Subscription'
  portal_type = 'SyncML Subscription' # may be useful in the future...
101
  icon = None
102
  isIndexable = ConstantGetter('isIndexable', value=False)
103
  user = None
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120

  # 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
121 122

  # Constructor
123 124
  def __init__(self, id, title, publication_url, subscription_url,
      destination_path, source_uri, target_uri, query, xml_mapping,
125
      conduit, gpg_key, id_generator, media_type, login,
126
      password, activity_enabled, alert_code, synchronize_with_erp5_sites,
127
      sync_content_type):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
128 129 130 131 132 133
    """
      We need to create a dictionnary of
      signatures of documents which belong to the synchronisation
      process
    """
    self.id = id
134
    self.setAlertCode(alert_code)
135
    self.setActivityEnabled(activity_enabled)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
136 137 138
    self.publication_url = (publication_url)
    self.subscription_url = str(subscription_url)
    self.destination_path = str(destination_path)
139 140
    self.setSourceURI(source_uri)
    self.setTargetURI(target_uri)
141
    self.setQuery(query)
142
    self.setXMLMapping(xml_mapping)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
143 144
    self.anchor = None
    self.session_id = 0
145
    #self.signatures = PersistentMapping()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
146 147
    self.last_anchor = '00000000T000000Z'
    self.next_anchor = '00000000T000000Z'
148
    self.setMediaType(media_type)
149
    self.login = login
150
    self.password = password
Jean-Paul Smets's avatar
Jean-Paul Smets committed
151
    self.domain_type = self.SUB
152
    self.gpg_key = gpg_key
153
    self.setSynchronizationIdGenerator(id_generator)
154
    self.setConduit(conduit)
155 156
    Folder.__init__(self, id)
    self.title = title
157 158
    self.setSyncContentType(sync_content_type)
    self.setSynchronizeWithERP5Sites(synchronize_with_erp5_sites)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
159

160
  def getAlertCodeList(self):
161
    return self.CODE_LIST
162 163 164 165 166 167 168 169

  def getAlertCode(self):
    return getattr(self, 'alert_code', 200)

  def setAlertCode(self, value):
    self.alert_code = int(value)

  def isOneWayFromServer(self):
170 171
    return self.getDomainType() == self.SUB and \
           self.getAlertCode() == self.ONE_WAY_FROM_SERVER
172

173 174 175 176 177 178 179 180 181 182 183 184
  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

185 186 187 188
  def getTitle(self):
    """
    getter for title
    """
189
    return getattr(self, 'title', None)
190 191 192 193 194 195

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

197 198 199 200 201 202 203 204
  def setSourceURI(self, value):
    """
    setter for source_uri
    """
    self.source_uri = value

  def getSourceURI(self):
    """
205
    getter for the source_uri (the local path of the subscription data base)
206 207 208 209 210 211 212 213
    """
    return getattr(self, 'source_uri', None)

  def setTargetURI(self, value):
    """
    setter for target_uri
    """
    self.target_uri = value
214

215
  def getTargetURI(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
216
    """
217 218
    getter for the target_uri (the distant Publication data base we want to 
    synchronize with)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
219
    """
220
    return getattr(self, 'target_uri', None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
221

222 223 224 225
  def setSyncContentType(self, sync_content_type):
    """
    content type used by the subscriber
    """
226
    self.sync_content_type = sync_content_type
227 228 229 230 231 232 233 234 235 236
    # 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')

Jean-Paul Smets's avatar
Jean-Paul Smets committed
237 238 239
  def getSynchronizationType(self, default=None):
    """
    """
240 241
    # XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    # XXX for debugging only, to be removed
Nicolas Delaby's avatar
Nicolas Delaby committed
242
    #dict_sign = {}
243
    #for o in self.getSignatureList():
Nicolas Delaby's avatar
Nicolas Delaby committed
244
      #dict_sign[o.getId()] = o.getStatus()
245
    # LOG('getSignature', DEBUG, 'signatures_status: %s' % str(dict_sign))
246
    # XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Jean-Paul Smets's avatar
Jean-Paul Smets committed
247
    code = self.SLOW_SYNC
248
    if len(self.getSignatureList()[:1]) > 0:
249
      code = self.getAlertCode()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
250 251
    if default is not None:
      code = default
252
    #LOG('Subscription', DEBUG, 'getSynchronizationType: %s' % code)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
253 254
    return code

255 256
  def setXMLMapping(self, value):
    """
257
    this the name of the method used in order to set the xml
258 259 260 261 262
    """
    if value == '':
      value = None
    self.xml_mapping = value

263 264 265 266 267 268 269 270 271 272 273 274 275
  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
    """
276
    return getattr(self, 'synchronize_with_erp5_sites', True)
277

278 279 280 281 282
  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

283
    return True if the session id was not seen, False if already seen
284
    """
285
    last_session_id = getattr(self, 'last_session_id', None)
286
    if last_session_id == session_id:
287
      return False 
288
    self.last_session_id = session_id
289
    return True
290

Sebastien Robin's avatar
Sebastien Robin committed
291 292 293 294 295
  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

296
    return True if the message id was not seen, False if already seen
Sebastien Robin's avatar
Sebastien Robin committed
297
    """
298
    last_message_id = getattr(self, 'last_message_id', None)
Sebastien Robin's avatar
Sebastien Robin committed
299
    if last_message_id == message_id:
300
      return False
Sebastien Robin's avatar
Sebastien Robin committed
301
    self.last_message_id = message_id
302
    return True
Sebastien Robin's avatar
Sebastien Robin committed
303

304
  def initLastMessageId(self, last_message_id=0):
Sebastien Robin's avatar
Sebastien Robin committed
305 306 307
    """
    set the last message id to 0
    """
308
    self.last_message_id = last_message_id
309 310 311 312 313

  def getLastSentMessage(self):
    """
    This is the getter for the last message we have sent
    """
314
    return getattr(self, 'last_sent_message', '')
315

316
  def setLastSentMessage(self, xml):
317 318 319 320 321
    """
    This is the setter for the last message we have sent
    """
    self.last_sent_message = xml

322
  def getDomainType(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
323
    """
324
      return the ID
Jean-Paul Smets's avatar
Jean-Paul Smets committed
325
    """
326
    return self.domain_type
Jean-Paul Smets's avatar
Jean-Paul Smets committed
327 328 329 330 331 332 333 334 335 336 337 338 339

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

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

340 341 342 343 344 345 346 347 348 349
  def setConduit(self, value):
    """
      set the Conduit
    """
    self.conduit = value

  def getConduit(self):
    """
      get the Conduit
    """
350
    return getattr(self, 'conduit', None)
351

Jean-Paul Smets's avatar
Jean-Paul Smets committed
352 353 354 355 356 357
  def getQuery(self):
    """
      return the query
    """
    return self.query

358 359 360 361
  def getGPGKey(self):
    """
      return the gnupg key name
    """
362
    return getattr(self, 'gpg_key', '')
363

364 365 366 367 368 369
  def setGPGKey(self, value):
    """
      setter for the gnupg key name
    """
    self.gpg_key = value

Jean-Paul Smets's avatar
Jean-Paul Smets committed
370 371 372 373
  def setQuery(self, query):
    """
      set the query
    """
374 375
    if query == '':
      query = None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
    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):
    """
392
      set the publication url
Jean-Paul Smets's avatar
Jean-Paul Smets committed
393 394 395
    """
    self.publication_url = publication_url

396
  def getXMLMapping(self, force=0):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
397 398 399
    """
      return the xml mapping
    """
400 401 402
    if self.isOneWayFromServer() and force == 0:
      return None
    xml_mapping = getattr(self, 'xml_mapping', None)
403
    return xml_mapping
Jean-Paul Smets's avatar
Jean-Paul Smets committed
404

405
  def getMediaType(self):
406
    """
407 408
    This method return the type of media used in this session,
    for example, it could be "text/vcard" or "xml/text",...
409
    """
410
    return getattr(self, 'media_type', self.MEDIA_TYPE['TEXT_XML'])
411

412
  def setMediaType(self, media_type):
413
    """
414
    set the type of media used
415
    """
416
    if media_type in (None, ''):
417 418
      media_type = self.MEDIA_TYPE['TEXT_XML']
    self.media_type = media_type
419

420 421 422 423 424 425 426 427 428 429
  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
    """
430
    self.login = new_login
431 432 433 434 435 436 437 438 439 440 441

  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
    """
442
    self.password = new_password
443

Fabien Morin's avatar
Fabien Morin committed
444 445 446 447 448 449 450 451 452 453 454 455
  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

456 457 458 459
  def getAuthenticationFormat(self):
    """
      return the format of authentication
    """
460
    return getattr(self, 'authentication_format', 'b64')
461 462 463 464 465

  def getAuthenticationType(self):
    """
      return the type of authentication
    """
466
    return getattr(self, 'authentication_type', 'syncml:auth-basic')
467 468 469 470 471

  def setAuthenticationFormat(self, authentication_format):
    """
      set the format of authentication
    """
472 473 474
    if authentication_format in (None, ''):
      self.authentication_format = 'b64'
    else:
475
      self.authentication_format = authentication_format
476 477 478 479 480

  def setAuthenticationType(self, authentication_type):
    """
      set the type of authentication
    """
481 482 483 484
    if authentication_type in (None, ''):
      self.authentication_type = 'syncml:auth-basic'
    else:
      self.authentication_type = authentication_type
485

486 487
  def getGidFromObject(self, object):
    """
488
      Returns the object gid 
489 490 491
    """
    o_base = aq_base(object)
    o_gid = None
492 493 494
    conduit_name = self.getConduit()
    conduit = self.getConduitByName(conduit_name)
    gid_gen = getattr(conduit, 'getGidFromObject', None)
495
    if callable(gid_gen):
496
      o_gid = gid_gen(object)
497
    else:
498
      raise ValueError, "The conduit "+conduit_name+"seems to not have a \
499
          getGidFromObject method and it must"
500
    o_gid = b16encode(o_gid)
501
    #LOG('getGidFromObject returning', DEBUG, o_gid)
502 503
    return o_gid

504 505 506 507 508
  def getObjectFromGid(self, gid):
    """
    This tries to get the object with the given gid
    This uses the query if it exist
    """
509
    if len(gid)%2 != 0:
Fabien Morin's avatar
Fabien Morin committed
510
    #something encode in base 16 is always a even number of number
511 512
    #if not, b16decode will failed
      return None
513
    signature = self.getSignatureFromGid(gid)
514 515 516
    # First look if we do already have the mapping between
    # the id and the gid
    destination = self.getDestination()
517
    if signature is not None and signature.getPath() is not None:
518 519
      o = None
      try:
520
        o = destination.getPortalObject().restrictedTraverse(signature.getPath())
521
      except (AttributeError, KeyError, TypeError):
522
        pass
Nicolas Delaby's avatar
Nicolas Delaby committed
523 524
      o_id = signature.getObjectId()
      #try with id param too, because gid is not catalogged
525
      object_list = self.getObjectList(gid=b16decode(gid), id=o_id)
Aurel's avatar
Aurel committed
526
      if o is not None and o.getId() in [document.getId() for document in object_list]:
527
        return o
528
    #LOG('entering in the slow loop of getObjectFromGid !!!',0,'')
529
    object_list = self.getObjectList(gid=b16decode(gid))
530
    for o in object_list:
531 532 533
      o_gid = self.getGidFromObject(o)
      if o_gid == gid:
        return o
534
    #LOG('getObjectFromGid', DEBUG, 'returning None')
535 536 537 538 539 540
    return None

  def getObjectFromId(self, id):
    """
    return the object corresponding to the id
    """
541
    object_list = self.getObjectList(id=id)
542
    o = None
543 544
    for object in object_list:
      if object.getId() == id:
545 546 547
        o = object
        break
    return o
548

549
  def getObjectList(self, **kw):
550 551 552 553
    """
    This returns the list of sub-object corresponding
    to the query
    """
554
    destination = self.getDestinationPath()
555
    query = self.getQuery()
556
    if query is not None and isinstance(query, str):
557 558 559 560 561 562 563 564 565 566 567 568
      count = query.count("/")
      if count > 0:
        # There is a query path different than destination path change
        # destination path for this query
        cut_query = query.split('/')
        if destination.endswith('/'):
          destination = "%s%s" % (destination, "/".join(cut_query[:count]))
        else:  
          destination = "%s/s" % (destination, "/".join(cut_query[:count]))
        query = cut_query[count]
      query_list = []
      destination = self.unrestrictedTraverse(destination)
569
      query_method = getattr(destination, query, None)
570
      if query_method is not None:
571
        query_list = query_method(**kw)
572
      else:
573
        raise KeyError, 'This Subscriber %s provide no Query: %s'\
574
          % (self.getTitle(), query)
575
    elif callable(query): # used in the test
576
      destination = self.unrestrictedTraverse(destination)
577
      query_list = query(destination)
578
    else:
579 580
      raise KeyError, 'This Subscriber %s provide no Query with id: %s'\
        % (self.getTitle(), query)
581
    return [x for x in query_list
582
              if not getattr(x, '_conflict_resolution', False)]
583

584
  def generateNewIdWithGenerator(self, object=None, gid=None):
585 586 587
    """
    This tries to generate a new Id
    """
588
    id_generator = self.getSynchronizationIdGenerator()
589
    if id_generator is not None:
590
      o_base = aq_base(object)
591 592
      new_id = None
      if callable(id_generator):
593
        new_id = id_generator(object, gid=gid)
594
      elif getattr(o_base, id_generator, None) is not None:
595
        generator = getattr(object, id_generator)
596
        new_id = generator()
597
      else: 
Nicolas Delaby's avatar
Nicolas Delaby committed
598
        # This is probably a python script
599
        generator = getattr(object, id_generator)
600
        new_id = generator(object=object, gid=gid)
601
      #LOG('generateNewIdWithGenerator, new_id: ', DEBUG, new_id)
602
      return new_id
603 604
    return None

605
  def setSynchronizationIdGenerator(self, method):
606 607 608 609
    """
    This set the method name wich allows to generate
    a new id
    """
610
    if method in ('', 'None'):
611
      method = None
612
    self.synchronization_id_generator = method
613

614
  def getSynchronizationIdGenerator(self):
615 616 617
    """
    This get the method name wich allows to generate a new id
    """
618
    return getattr(self, 'synchronization_id_generator', None)
619

Jean-Paul Smets's avatar
Jean-Paul Smets committed
620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
  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

638 639 640 641 642 643
  def getDestination(self):
    """
      return the destination object itself
    """
    return self.unrestrictedTraverse(self.getDestinationPath())

Jean-Paul Smets's avatar
Jean-Paul Smets committed
644 645 646 647 648 649 650 651 652 653 654
  def setDestinationPath(self, destination_path):
    """
      set the destination path
    """
    self.destination_path = destination_path

  def getSubscription(self):
    """
      return the current subscription
    """
    return self
655

Sebastien Robin's avatar
Sebastien Robin committed
656 657 658 659 660
  def setSessionId(self, session_id):
    """
      set the session id
    """
    self.session_id = session_id
Jean-Paul Smets's avatar
Jean-Paul Smets committed
661 662 663 664 665

  def getSessionId(self):
    """
      return the session id
    """
Sebastien Robin's avatar
Sebastien Robin committed
666 667 668 669 670 671 672
    #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
673
    self.session_id += 1
Sebastien Robin's avatar
Sebastien Robin committed
674
    self.resetMessageId() # for a new session, the message Id must be reset
Jean-Paul Smets's avatar
Jean-Paul Smets committed
675 676
    return self.session_id

Sebastien Robin's avatar
Sebastien Robin committed
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695
  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
696

697 698 699 700 701
  def setMessageId(self, message_id):
    """
      set the message id to message_id
    """
    self.message_id = message_id
Sebastien Robin's avatar
Sebastien Robin committed
702

Jean-Paul Smets's avatar
Jean-Paul Smets committed
703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746
  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
    """
747
    if self.getSignatureFromGid(signature.getGid()) is not None:
748 749
      self.delSignature(signature.getGid())
    self._setObject(signature.getGid(), aq_base(signature))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
750

751
  def delSignature(self, gid):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
752
    """
753
      del a Signature of the subscription
Jean-Paul Smets's avatar
Jean-Paul Smets committed
754
    """
755
    self._delObject(gid)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
756

757
  def getSignatureFromObjectId(self, id):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
758
    """
759
    return the signature corresponding to the id
760 761 762 763 764 765 766 767 768 769 770 771 772
    """
    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
    """
Nicolas Delaby's avatar
Nicolas Delaby committed
773
    return getattr(self, gid, None)
774 775 776 777

  def getSignatureFromRid(self, rid):
    """
    return the signature corresponding to the rid
Jean-Paul Smets's avatar
Jean-Paul Smets committed
778
    """
779
    o = None
780 781 782 783 784
    # XXX very slow
    for signature in self.getSignatureList():
      if rid == signature.getRid():
        o = signature
        break
785
    return o
Jean-Paul Smets's avatar
Jean-Paul Smets committed
786

787 788 789 790
  def getGidList(self):
    """
    Returns the list of gids from signature
    """
791
    return [id for id in self.getObjectIds()]
792

Jean-Paul Smets's avatar
Jean-Paul Smets committed
793 794
  def getSignatureList(self):
    """
795
      Returns the list of Signatures
Jean-Paul Smets's avatar
Jean-Paul Smets committed
796
    """
797
    return self.objectValues()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
798

799
  def hasSignature(self, gid):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
800 801 802
    """
      Check if there's a signature with this uid
    """
803
    return self.getSignatureFromGid(gid) is not None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
804 805 806

  def resetAllSignatures(self):
    """
807
      Reset all signatures in activities
Jean-Paul Smets's avatar
Jean-Paul Smets committed
808
    """
809 810
    object_id_list = [id for id in self.getObjectIds()]
    object_list_len = len(object_id_list)
Nicolas Delaby's avatar
Nicolas Delaby committed
811 812
    for i in xrange(0, object_list_len, self.MAX_OBJECTS):
      current_id_list = object_id_list[i:i+self.MAX_OBJECTS]
813
      self.activate(activity='SQLQueue',
Nicolas Delaby's avatar
Nicolas Delaby committed
814 815
                    tag=self.getId(),
                    priority=self.PRIORITY).manage_delObjects(current_id_list)
816

Jean-Paul Smets's avatar
Jean-Paul Smets committed
817 818 819 820 821 822
  def getConflictList(self):
    """
    Return the list of all conflicts from all signatures
    """
    conflict_list = []
    for signature in self.getSignatureList():
823
      conflict_list.extend(signature.getConflictList())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
824 825
    return conflict_list

826
  def getRemainingObjectPathList(self):
827 828 829 830
    """
    We should now wich objects should still
    synchronize
    """
831
    return getattr(self, 'remaining_object_path_list', None)
832

833
  def setRemainingObjectPathList(self, value):
834 835 836 837
    """
    We should now wich objects should still
    synchronize
    """
838
    setattr(self, 'remaining_object_path_list', value)
839

840
  def removeRemainingObjectPath(self, object_path):
841 842 843 844
    """
    We should now wich objects should still
    synchronize
    """
845
    remaining_object_list = self.getRemainingObjectPathList()
846 847
    if remaining_object_list is not None:
      new_list = []
Nicolas Delaby's avatar
Nicolas Delaby committed
848 849 850
      new_list.extend(remaining_object_list)
      while object_path in new_list:
        new_list.remove(object_path)
851
      self.setRemainingObjectPathList(new_list)
852

Jean-Paul Smets's avatar
Jean-Paul Smets committed
853 854 855 856
  def startSynchronization(self):
    """
    Set the status of every object as NOT_SYNCHRONIZED
    """
857
    for s in self.getSignatureList():
Jean-Paul Smets's avatar
Jean-Paul Smets committed
858
      # Change the status only if we are not in a conflict mode
859 860 861 862 863 864
      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)
865
    self.setRemainingObjectPathList(None)
866

867 868 869 870 871
  def isAuthenticated(self):
    """
    return True if the subscriber is authenticated for this session, False 
    in other case
    """
872 873
    return getattr(self, 'is_authenticated', None)

874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890
  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
891
      LOG('encode : unknown or not implemented format : ', INFO, format)
892 893
      raise ValueError, "Sorry, the server ask for the format %s but \
            it's unknow or not implemented" % format
894 895 896 897 898 899 900 901 902 903 904 905

  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
906
      LOG('decode : unknown or not implemented format :', INFO, format)
907 908
      raise ValueError, "Sorry, the format %s is unknow or \
            not implemented" % format
909 910 911 912 913 914

  def isDecodeEncodeTheSame(self, string_encoded, string_decoded, format):
    """
      return True if the string_encoded is equal to string_decoded encoded 
      in format
    """
915
    return self.encode(format, string_decoded) == string_encoded
916 917 918 919 920

  def setUser(self, user):
    """
      save the user logged in to log him on each transaction
    """
921
    self.user = user
922 923 924 925 926

  def getUser(self):
    """
      retrun the user logged in
    """
927
    return getattr(self, 'user', None)
928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947

  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

948 949 950 951
#XXX backwards compatibility
from Products.ERP5SyncML import Signature, Conflict
Signature = Signature.Signature
Conflict = Conflict.Conflict