SynchronizationTool.py 44.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
## 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.
#
##############################################################################

27
"""
Jean-Paul Smets's avatar
Jean-Paul Smets committed
28 29 30 31
ERP portal_synchronizations tool.
"""

from OFS.SimpleItem import SimpleItem
Jean-Paul Smets's avatar
Jean-Paul Smets committed
32
from Products.ERP5Type.Core.Folder import Folder
33
from Products.ERP5Type.Base import Base
Jean-Paul Smets's avatar
Jean-Paul Smets committed
34 35 36 37 38
from Products.CMFCore.utils import UniqueObject
from Globals import InitializeClass, DTMLFile, PersistentMapping, Persistent
from AccessControl import ClassSecurityInfo, getSecurityManager
from Products.CMFCore import CMFCorePermissions
from Products.ERP5SyncML import _dtmldir
39
from Products.ERP5SyncML import Conduit
Jean-Paul Smets's avatar
Jean-Paul Smets committed
40
from Publication import Publication,Subscriber
41
from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2
Jean-Paul Smets's avatar
Jean-Paul Smets committed
42
from Subscription import Subscription,Signature
43 44
from XMLSyncUtils import Parse
#from Ft.Xml import Parse
Sebastien Robin's avatar
Sebastien Robin committed
45
from Products.ERP5Type import Permissions
Jean-Paul Smets's avatar
Jean-Paul Smets committed
46 47
from PublicationSynchronization import PublicationSynchronization
from SubscriptionSynchronization import SubscriptionSynchronization
48
from SyncCode import SyncCode
49
from Products.CMFCore.utils import getToolByName
50
from AccessControl.SecurityManagement import newSecurityManager
51
from AccessControl.SecurityManagement import noSecurityManager
52
from AccessControl.User import UnrestrictedUser
Sebastien Robin's avatar
Sebastien Robin committed
53
from Acquisition import aq_base
54
import urllib
55
import urllib2
56
import httplib
57
import socket
58
import os
Jean-Paul Smets's avatar
Jean-Paul Smets committed
59
import string
60 61
import commands
import random
62
from DateTime import DateTime
63
from zLOG import LOG, TRACE, DEBUG, INFO
Jean-Paul Smets's avatar
Jean-Paul Smets committed
64

65 66 67 68 69 70 71 72 73 74 75
class TimeoutHTTPConnection(httplib.HTTPConnection):
  """
  Custom Classes to set timeOut on handle sockets
  """
  def connect(self):
    httplib.HTTPConnection.connect(self)
    self.sock.settimeout(3600)

class TimeoutHTTPHandler(urllib2.HTTPHandler):
  def http_open(self, req):
    return self.do_open(TimeoutHTTPConnection, req)
76

Jean-Paul Smets's avatar
Jean-Paul Smets committed
77

78

79
class SynchronizationTool( SubscriptionSynchronization,
80
    PublicationSynchronization, UniqueObject, Folder):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
81 82
  """
    This tool implements the synchronization algorithm
Jean-Paul Smets's avatar
Jean-Paul Smets committed
83 84

    TODO: XXX-Please use BaseTool
Jean-Paul Smets's avatar
Jean-Paul Smets committed
85 86 87
  """


88
  id           = 'portal_synchronizations'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
89
  meta_type    = 'ERP5 Synchronizations'
90
  portal_type  = 'Synchronisation Tool'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
91

92 93 94 95
  # On the server, this is use to keep track of the temporary
  # copies.
  objectsToRemove = [] 
  
Jean-Paul Smets's avatar
Jean-Paul Smets committed
96 97 98 99 100 101 102 103 104 105 106 107 108
  security = ClassSecurityInfo()

  #
  #  Default values.
  #
  list_publications = PersistentMapping()
  list_subscriptions = PersistentMapping()

  # Do we want to use emails ?
  #email = None
  email = 1
  same_export = 1

109 110 111 112
  # Multiple inheritance inconsistency caused by Base must be circumvented
  def __init__( self, *args, **kwargs ):
    Folder.__init__(self, self.id, **kwargs)

Jean-Paul Smets's avatar
Jean-Paul Smets committed
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129

  #
  #  ZMI methods
  #
  manage_options = ( ( { 'label'   : 'Overview'
             , 'action'   : 'manage_overview'
             }
            , { 'label'   : 'Publications'
             , 'action'   : 'managePublications'
             }
            , { 'label'   : 'Subscriptions'
             , 'action'   : 'manageSubscriptions'
             }
            , { 'label'   : 'Conflicts'
             , 'action'   : 'manageConflicts'
             }
            )
130
           + Folder.manage_options
Jean-Paul Smets's avatar
Jean-Paul Smets committed
131 132 133 134 135 136 137 138 139 140 141
           )

  security.declareProtected( CMFCorePermissions.ManagePortal
               , 'manage_overview' )
  manage_overview = DTMLFile( 'dtml/explainSynchronizationTool', globals() )

  security.declareProtected( CMFCorePermissions.ManagePortal
               , 'managePublications' )
  managePublications = DTMLFile( 'dtml/managePublications', globals() )

  security.declareProtected( CMFCorePermissions.ManagePortal
142 143
               , 'manage_addPublicationForm' )
  manage_addPublicationForm = DTMLFile( 'dtml/manage_addPublication', globals() )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
144 145

  security.declareProtected( CMFCorePermissions.ManagePortal
Yoshinori Okuji's avatar
Yoshinori Okuji committed
146
               , 'manageSubscriptions' )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
147 148 149 150 151 152 153
  manageSubscriptions = DTMLFile( 'dtml/manageSubscriptions', globals() )

  security.declareProtected( CMFCorePermissions.ManagePortal
               , 'manageConflicts' )
  manageConflicts = DTMLFile( 'dtml/manageConflicts', globals() )

  security.declareProtected( CMFCorePermissions.ManagePortal
154 155
               , 'manage_addSubscriptionForm' )
  manage_addSubscriptionForm = DTMLFile( 'dtml/manage_addSubscription', globals() )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175

  security.declareProtected( CMFCorePermissions.ManagePortal
               , 'editProperties' )
  def editProperties( self
           , publisher=None
           , REQUEST=None
           ):
    """
      Form handler for "tool-wide" properties (including list of
      metadata elements).
    """
    if publisher is not None:
      self.publisher = publisher

    if REQUEST is not None:
      REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
                    + '/propertiesForm'
                    + '?manage_tabs_message=Tool+updated.'
                    )

176 177
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manage_addPublication')
178
  def manage_addPublication(self, title, publication_url, 
179 180
            destination_path, source_uri, query, xml_mapping, 
            conduit, gpg_key, 
181
            synchronization_id_generator=None, gid_generator=None, 
182
            media_type=None, auth_required=0, authentication_format='', 
183 184 185
            authentication_type='', RESPONSE=None, activity_enabled = False,
            sync_content_type='application/vnd.syncml+xml', 
            synchronize_with_erp5_sites=True):
186
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
187 188
      create a new publication
    """
189 190 191
    #if not('publications' in self.objectIds()):
    #  publications = Folder('publications')
    #  self._setObject(publications.id, publications)
192
    folder = self.getObjectContainer()
193
    new_id = self.getPublicationIdFromTitle(title)
194 195 196 197 198 199
    pub = Publication(new_id, title, publication_url,
                      destination_path, source_uri, query, xml_mapping,
                      conduit, gpg_key, synchronization_id_generator,
                      gid_generator, media_type, auth_required,
                      authentication_format, authentication_type,
                      activity_enabled, synchronize_with_erp5_sites,
200
                      sync_content_type)
201
    folder._setObject( new_id, pub )
202 203 204
    #if len(self.list_publications) == 0:
    #  self.list_publications = PersistentMapping()
    #self.list_publications[id] = pub
Jean-Paul Smets's avatar
Jean-Paul Smets committed
205 206 207
    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')

208
  security.declareProtected(Permissions.ModifyPortalContent,
209
      'manage_addSubscription')
210
  def manage_addSubscription(self, title, publication_url, subscription_url,
211 212 213 214
                       destination_path, source_uri, target_uri, query,
                       xml_mapping, conduit, gpg_key,
                       synchronization_id_generator=None, gid_generator=None,
                       media_type=None, login=None, password=None,
215 216 217
                       RESPONSE=None, activity_enabled=False,
                       alert_code=SyncCode.TWO_WAY,
                       synchronize_with_erp5_sites = True,
218
                       sync_content_type='application/vnd.syncml+xml'):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
219
    """
Sebastien Robin's avatar
Sebastien Robin committed
220
      XXX should be renamed as addSubscription
Jean-Paul Smets's avatar
Jean-Paul Smets committed
221 222
      create a new subscription
    """
223 224 225
    #if not('subscriptions' in self.objectIds()):
    #  subscriptions = Folder('subscriptions')
    #  self._setObject(subscriptions.id, subscriptions)
226
    folder = self.getObjectContainer()
227 228
    new_id = self.getSubscriptionIdFromTitle(title)
    sub = Subscription(new_id, title, publication_url, subscription_url,
229
                       destination_path, source_uri, target_uri, query,
230
                       xml_mapping, conduit, gpg_key,
231
                       synchronization_id_generator, gid_generator, media_type,
232 233
                       login, password, activity_enabled, alert_code, 
                       synchronize_with_erp5_sites, sync_content_type)
234
    folder._setObject( new_id, sub )
235 236 237
    #if len(self.list_subscriptions) == 0:
    #  self.list_subscriptions = PersistentMapping()
    #self.list_subscriptions[id] = sub
Jean-Paul Smets's avatar
Jean-Paul Smets committed
238 239 240
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

241
  security.declareProtected(Permissions.ModifyPortalContent,
242
      'manage_editPublication')
243 244 245 246 247
  def manage_editPublication(self, title, publication_url,
                            destination_path, source_uri, query, xml_mapping,
                            conduit, gpg_key, synchronization_id_generator,
                            gid_generator,  media_type=None, auth_required=0,
                            authentication_format='', authentication_type='',
248 249 250
                            RESPONSE=None, activity_enabled=False,
                            sync_content_type='application/vnd.syncml+xml',
                            synchronize_with_erp5_sites=False):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
251 252 253
    """
      modify a publication
    """
254
    pub = self.getPublication(title)
255
    pub.setTitle(title)
Nicolas Delaby's avatar
Nicolas Delaby committed
256
    pub.setActivityEnabled(activity_enabled)
257 258
    pub.setPublicationUrl(publication_url)
    pub.setDestinationPath(destination_path)
259
    pub.setSourceURI(source_uri)
260
    pub.setQuery(query)
261
    pub.setConduit(conduit)
262 263
    pub.setXMLMapping(xml_mapping)
    pub.setGPGKey(gpg_key)
264
    pub.setSynchronizationIdGenerator(synchronization_id_generator)
265
    pub.setGidGenerator(gid_generator)
266
    pub.setMediaType(media_type)
267 268 269
    pub.setAuthentication(auth_required)
    pub.setAuthenticationFormat(authentication_format)
    pub.setAuthenticationType(authentication_type)
270 271
    pub.setSyncContentType(sync_content_type)
    pub.setSynchronizeWithERP5Sites(synchronize_with_erp5_sites)
272

Jean-Paul Smets's avatar
Jean-Paul Smets committed
273 274 275
    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')

276 277
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manage_editSubscription')
278
  def manage_editSubscription(self, title, publication_url, subscription_url,
279 280
      destination_path, source_uri, target_uri, query, xml_mapping, conduit,
      gpg_key, synchronization_id_generator, gid_generator, media_type=None,
281 282 283
      login='', password='', RESPONSE=None, activity_enabled=False, 
      alert_code=SyncCode.TWO_WAY, synchronize_with_erp5_sites=False, 
      sync_content_type='application/vnd.syncml+xml'):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
284 285 286
    """
      modify a subscription
    """
287
    sub = self.getSubscription(title)
288
    sub.setTitle(title)
289
    sub.setActivityEnabled(activity_enabled)
290 291
    sub.setPublicationUrl(publication_url)
    sub.setDestinationPath(destination_path)
292 293
    sub.setSourceURI(source_uri)
    sub.setTargetURI(target_uri)
294
    sub.setQuery(query)
295
    sub.setConduit(conduit)
296 297 298
    sub.setXMLMapping(xml_mapping)
    sub.setGPGKey(gpg_key)
    sub.setSubscriptionUrl(subscription_url)
299
    sub.setSynchronizationIdGenerator(synchronization_id_generator)
300
    sub.setGidGenerator(gid_generator)
301
    sub.setMediaType(media_type)
302 303
    sub.setLogin(login)
    sub.setPassword(password)
304 305
    sub.setSyncContentType(sync_content_type)
    sub.setSynchronizeWithERP5Sites(synchronize_with_erp5_sites)
306 307
    sub.setAlertCode(alert_code)

Jean-Paul Smets's avatar
Jean-Paul Smets committed
308 309 310
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

311
  security.declareProtected(Permissions.ModifyPortalContent,
312
      'manage_deletePublication')
313
  def manage_deletePublication(self, title, RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
314 315 316
    """
      delete a publication
    """
317
    id = self.getPublicationIdFromTitle(title)
318 319
    folder = self.getObjectContainer()
    folder._delObject(id)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
320 321 322
    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')

323
  security.declareProtected(Permissions.ModifyPortalContent,
324
      'manage_deleteSubscription')
325
  def manage_deleteSubscription(self, title, RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
326 327 328
    """
      delete a subscription
    """
329
    id = self.getSubscriptionIdFromTitle(title)
330 331
    folder = self.getObjectContainer()
    folder._delObject(id)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
332 333 334
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

335
  security.declareProtected(Permissions.ModifyPortalContent,
336
      'manage_resetPublication')
337
  def manage_resetPublication(self, title, RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
338 339 340
    """
      reset a publication
    """
341
    pub = self.getPublication(title)
342
    pub.resetAllSubscribers()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
343 344 345
    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')

346
  security.declareProtected(Permissions.ModifyPortalContent,
347
      'manage_resetSubscription')
348
  def manage_resetSubscription(self, title, RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
349 350 351
    """
      reset a subscription
    """
352
    sub = self.getSubscription(title)
353 354
    sub.resetAllSignatures()
    sub.resetAnchors()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
355 356 357
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

358
  security.declareProtected(Permissions.ModifyPortalContent,
359
      'manage_syncSubscription')
360 361 362 363
  def manage_syncSubscription(self, title, RESPONSE=None):
    """
      reset a subscription
    """
364
    self.SubSync(self.getSubscription(title).getPath())
365 366 367
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

368 369
  security.declareProtected(Permissions.AccessContentsInformation,
      'getPublicationList')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
370 371 372 373
  def getPublicationList(self):
    """
      Return a list of publications
    """
374
    folder = self.getObjectContainer()
375 376
    object_list = [pub for pub in folder.objectValues() if pub.getDomainType() == self.PUB]
    #object_list = filter(lambda x: x.id.find('pub')==0,object_list)
377
    return object_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
378

379 380
  security.declareProtected(Permissions.AccessContentsInformation,
      'getPublication')
381
  def getPublication(self, title):
382
    """
383
      Return the  publications with this id
384
    """
385
    pub = None
386 387
    for p in self.getPublicationList():
      if p.getTitle() == title:
388 389 390
        pub = p
        break
    return pub
391

392 393
  security.declareProtected(Permissions.AccessContentsInformation,
      'getObjectContainer')
394 395 396 397 398 399 400 401 402 403 404
  def getObjectContainer(self):
    """
    this returns the external mount point if there is one
    """
    folder = self
    portal_url = getToolByName(self,'portal_url')
    root = portal_url.getPortalObject().aq_parent
    if 'external_mount_point' in root.objectIds():
      folder = root.external_mount_point
    return folder

405 406
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSubscriptionList')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
407 408 409 410
  def getSubscriptionList(self):
    """
      Return a list of publications
    """
411
    folder = self.getObjectContainer()
412 413
    object_list = [sub for sub in folder.objectValues() if sub.getDomainType() == self.SUB]
    #object_list = filter(lambda x: x.id.find('sub')==0,object_list)
414
    return object_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
415

416
  def getSubscription(self, title):
417
    """
418
      Returns the subscription with this title
419
    """
420
    sub = None
421 422
    for s in self.getSubscriptionList():
      if s.getTitle() == title:
423 424
        sub = s
    return sub
425 426


427 428
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSynchronizationList')
429
  def getSynchronizationList(self):
430 431 432 433 434
    """
      Returns the list of subscriptions and publications
    """
    return self.getSubscriptionList() + self.getPublicationList()

435 436
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSubscriberList')
437 438 439 440 441
  def getSubscriberList(self):
    """
      Returns the list of subscribers and subscriptions
    """
    s_list = []
442
    s_list.extend(self.getSubscriptionList())
443
    for publication in self.getPublicationList():
444
      s_list.extend(publication.getSubscriberList())
445 446
    return s_list

447 448
  security.declareProtected(Permissions.AccessContentsInformation,
      'getConflictList')
449
  def getConflictList(self, context=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
450 451 452 453
    """
    Retrieve the list of all conflicts
    Here the list is as follow :
    [conflict_1,conflict2,...] where conflict_1 is like:
454 455
    ['publication',publication_id,object.getPath(),property_id,
    publisher_value,subscriber_value]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
456
    """
457
    path = self.resolveContext(context)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
458 459
    conflict_list = []
    for publication in self.getPublicationList():
Sebastien Robin's avatar
Sebastien Robin committed
460 461 462
      for subscriber in publication.getSubscriberList():
        sub_conflict_list = subscriber.getConflictList()
        for conflict in sub_conflict_list:
463
          conflict.setSubscriber(subscriber)
464 465
          if path is None or conflict.getObjectPath() == path:
            conflict_list += [conflict.__of__(subscriber)]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
466 467
    for subscription in self.getSubscriptionList():
      sub_conflict_list = subscription.getConflictList()
468 469
      LOG('SynchronizationTool.getConflictList, sub_conflict_list', DEBUG,
          sub_conflict_list)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
470
      for conflict in sub_conflict_list:
471
        conflict.setSubscriber(subscription)
472 473
        if path is None or conflict.getObjectPath() == path:
          conflict_list += [conflict.__of__(subscription)]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
474 475
    return conflict_list

476 477
  security.declareProtected(Permissions.AccessContentsInformation,
      'getDocumentConflictList')
478 479 480 481 482 483 484 485
  def getDocumentConflictList(self, context=None):
    """
    Retrieve the list of all conflicts for a given document
    Well, this is the same thing as getConflictList with a path
    """
    return self.getConflictList(context)


486 487
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSynchronizationState')
488
  def getSynchronizationState(self, context):
489
    """
490
    context : the context on which we are looking for state
491

492 493 494
    This functions have to retrieve the synchronization state,
    it will first look in the conflict list, if nothing is found,
    then we have to check on a publication/subscription.
495

496
    This method returns a mapping between subscription and states
Sebastien Robin's avatar
Sebastien Robin committed
497 498 499 500 501

    JPS suggestion:
      path -> object, document, context, etc.
      type -> '/titi/toto' or ('','titi', 'toto') or <Base instance 1562567>
      object = self.resolveContext(context) (method to add)
502
    """
503
    path = self.resolveContext(context)
504 505
    conflict_list = self.getConflictList()
    state_list= []
506
    LOG('getSynchronizationState', DEBUG, 'path: %s' % str(path))
507 508
    for conflict in conflict_list:
      if conflict.getObjectPath() == path:
509
        LOG('getSynchronizationState', DEBUG, 'found a conflict: %s' % str(conflict))
510
        state_list += [[conflict.getSubscriber(),self.CONFLICT]]
511
    for domain in self.getSynchronizationList():
512
      destination = domain.getDestinationPath()
513
      LOG('getSynchronizationState', TRACE, 'destination: %s' % str(destination))
514
      j_path = '/'.join(path)
515
      LOG('getSynchronizationState', TRACE, 'j_path: %s' % str(j_path))
516 517
      if j_path.find(destination)==0:
        o_id = j_path[len(destination)+1:].split('/')[0]
518
        LOG('getSynchronizationState', TRACE, 'o_id: %s' % o_id)
519 520 521 522 523
        subscriber_list = []
        if domain.domain_type==self.PUB:
          subscriber_list = domain.getSubscriberList()
        else:
          subscriber_list = [domain]
524
        LOG('getSynchronizationState, subscriber_list:', TRACE, subscriber_list)
525
        for subscriber in subscriber_list:
526
          signature = subscriber.getSignatureFromObjectId(o_id)
527
          #XXX check if signature could be not None ...
528 529
          if signature is not None:
            state = signature.getStatus()
530 531
            LOG('getSynchronizationState:', TRACE, 'sub.dest :%s, state: %s' % \
                                   (subscriber.getSubscriptionUrl(),str(state)))
532 533 534 535 536 537 538 539
            found = None
            # Make sure there is not already a conflict giving the state
            for state_item in state_list:
              if state_item[0]==subscriber:
                found = 1
            if found is None:
              state_list += [[subscriber,state]]
    return state_list
540

541 542 543 544 545 546
  security.declareProtected(Permissions.AccessContentsInformation,
      'getAlertCodeList')
  def getAlertCodeList(self):
    return self.CODE_LIST

  security.declareProtected(Permissions.ModifyPortalContent,
547
      'applyPublisherValue')
548
  def applyPublisherValue(self, conflict):
Sebastien Robin's avatar
Sebastien Robin committed
549 550 551 552 553
    """
      after a conflict resolution, we have decided
      to keep the local version of an object
    """
    object = self.unrestrictedTraverse(conflict.getObjectPath())
554
    subscriber = conflict.getSubscriber()
Sebastien Robin's avatar
Sebastien Robin committed
555
    # get the signature:
556
    LOG('p_sync.applyPublisherValue, subscriber: ', DEBUG, subscriber)
557
    signature = subscriber.getSignatureFromObjectId(object.getId()) # XXX may be change for rid
558
    copy_path = conflict.getCopyPath()
559
    LOG('p_sync.applyPublisherValue, copy_path: ', TRACE, copy_path)
Sebastien Robin's avatar
Sebastien Robin committed
560 561
    signature.delConflict(conflict)
    if signature.getConflictList() == []:
562
      if copy_path is not None:
563
        LOG('p_sync.applyPublisherValue, conflict_list empty on : ', TRACE, signature)
564 565 566
        # Delete the copy of the object if the there is one
        directory = object.aq_parent
        copy_id = copy_path[-1]
567
        LOG('p_sync.applyPublisherValue, copy_id: ', TRACE, copy_id)
568 569
        if hasattr(directory.aq_base, 'hasObject'):
          # optimize the case of a BTree folder
570
          LOG('p_sync.applyPublisherValue, deleting...: ', TRACE, copy_id)
571 572 573 574
          if directory.hasObject(copy_id):
            directory._delObject(copy_id)
        elif copy_id in directory.objectIds():
          directory._delObject(copy_id)
Sebastien Robin's avatar
Sebastien Robin committed
575 576
      signature.setStatus(self.PUB_CONFLICT_MERGE)

577
  security.declareProtected(Permissions.ModifyPortalContent,
578
      'applyPublisherDocument')
579 580 581 582 583 584 585
  def applyPublisherDocument(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
    subscriber = conflict.getSubscriber()
    for c in self.getConflictList(conflict.getObjectPath()):
      if c.getSubscriber() == subscriber:
586
        LOG('applyPublisherDocument, applying on conflict: ', DEBUG, conflict)
587 588
        c.applyPublisherValue()

589 590
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getPublisherDocumentPath')
591 592 593 594 595 596 597
  def getPublisherDocumentPath(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
    subscriber = conflict.getSubscriber()
    return conflict.getObjectPath()

598 599
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getPublisherDocument')
600 601 602 603 604
  def getPublisherDocument(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
    publisher_object_path = self.getPublisherDocumentPath(conflict)
605
    LOG('getPublisherDocument publisher_object_path', TRACE, publisher_object_path)
606 607 608
    publisher_object = self.unrestrictedTraverse(publisher_object_path)
    return publisher_object

609 610 611 612 613 614 615 616 617 618 619 620 621

  def getSubscriberDocumentVersion(self, conflict, docid):
    """
    Given a 'conflict' and a 'docid' refering to a new version of a
    document, applies the conflicting changes to the document's new
    version. By so, two differents versions of the same document will be
    available.
    Thus, the manager will be able to open both version of the document
    before selecting which one to keep.
    """
    subscriber = conflict.getSubscriber()
    publisher_object_path = conflict.getObjectPath()
    publisher_object = self.unrestrictedTraverse(publisher_object_path)
622 623 624
    publisher_xml = self.getXMLObject(
                              object=publisher_object,
                              xml_mapping=subscriber.getXMLMapping())
625 626 627 628
    directory = publisher_object.aq_parent
    object_id = docid
    if object_id in directory.objectIds():
        directory._delObject(object_id)
629
        # Import the conduit and get it
630
        conduit_name = subscriber.getConduit()
631 632 633 634 635
        conduit = self.getConduitByName(conduit_name)
        conduit.addNode(
                    xml=publisher_xml,
                    object=directory,
                    object_id=object_id)
636 637 638 639 640 641
        subscriber_document = directory._getOb(object_id)
        for c in self.getConflictList(conflict.getObjectPath()):
            if c.getSubscriber() == subscriber:
                c.applySubscriberValue(object=subscriber_document)
        return subscriber_document

642 643
  def _getCopyId(self, object):
    directory = object.aq_inner.aq_parent
644
    if directory.getId() != 'portal_repository':
645 646 647 648 649 650 651 652 653
      object_id = object.getId() + '_conflict_copy'
      if object_id in directory.objectIds():
        directory._delObject(object_id)
    else:
      repotool = directory
      docid, rev = repotool.getDocidAndRevisionFromObjectId(object.getId())
      new_rev = repotool.getFreeRevision(docid) + 10 # make sure it's not gonna provoke conflicts
      object_id = repotool._getId(docid, new_rev)
    return object_id
654

655 656
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubscriberDocumentPath')
657 658 659 660
  def getSubscriberDocumentPath(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
661 662
    copy_path = conflict.getCopyPath()
    if copy_path is not None:
663
      return copy_path
664 665 666
    subscriber = conflict.getSubscriber()
    publisher_object_path = conflict.getObjectPath()
    publisher_object = self.unrestrictedTraverse(publisher_object_path)
Nicolas Delaby's avatar
Nicolas Delaby committed
667
    publisher_xml = subscriber.getXMLFromObject(publisher_object)
668
    directory = publisher_object.aq_inner.aq_parent
669
    object_id = self._getCopyId(publisher_object)
670
    # Import the conduit and get it
671
    conduit_name = subscriber.getConduit()
672 673 674 675 676
    conduit = self.getConduitByName(conduit_name)
    conduit.addNode(
                xml=publisher_xml,
                object=directory,
                object_id=object_id)
677
    subscriber_document = directory._getOb(object_id)
678
    subscriber_document._conflict_resolution = 1
679 680 681
    for c in self.getConflictList(conflict.getObjectPath()):
      if c.getSubscriber() == subscriber:
        c.applySubscriberValue(object=subscriber_document)
682 683 684
    copy_path = subscriber_document.getPhysicalPath()
    conflict.setCopyPath(copy_path)
    return copy_path
685

686 687
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubscriberDocument')
688 689 690 691 692 693 694 695
  def getSubscriberDocument(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
    subscriber_object_path = self.getSubscriberDocumentPath(conflict)
    subscriber_object = self.unrestrictedTraverse(subscriber_object_path)
    return subscriber_object

696 697
  security.declareProtected(Permissions.ModifyPortalContent, 
      'applySubscriberDocument')
698 699 700 701 702 703 704 705 706
  def applySubscriberDocument(self, conflict):
    """
    apply the subscriber value for all conflict of the given document
    """
    subscriber = conflict.getSubscriber()
    for c in self.getConflictList(conflict.getObjectPath()):
      if c.getSubscriber() == subscriber:
        c.applySubscriberValue()

707 708
  security.declareProtected(Permissions.ModifyPortalContent, 
      'applySubscriberValue')
709
  def applySubscriberValue(self, conflict,object=None):
Sebastien Robin's avatar
Sebastien Robin committed
710 711 712 713
    """
      after a conflict resolution, we have decided
      to keep the local version of an object
    """
714 715 716 717 718 719 720
    solve_conflict = 1
    if object is None:
      object = self.unrestrictedTraverse(conflict.getObjectPath())
    else:
      # This means an object was given, this is used in order
      # to see change on a copy, so don't solve conflict
      solve_conflict=0
721
    subscriber = conflict.getSubscriber()
Sebastien Robin's avatar
Sebastien Robin committed
722
    # get the signature:
Nicolas Delaby's avatar
Nicolas Delaby committed
723
    #LOG('p_sync.setRemoteObject, subscriber: ',0,subscriber)
724
    signature = subscriber.getSignatureFromObjectId(object.getId()) # XXX may be change for rid
725
    # Import the conduit and get it
726
    conduit_name = subscriber.getConduit()
727
    conduit = self.getConduitByName(conduit_name)
Sebastien Robin's avatar
Sebastien Robin committed
728 729
    for xupdate in conflict.getXupdateList():
      conduit.updateNode(xml=xupdate,object=object,force=1)
730
    if solve_conflict:
731
      copy_path = conflict.getCopyPath()
732 733
      signature.delConflict(conflict)
      if signature.getConflictList() == []:
734 735 736 737 738 739 740 741 742 743
        if copy_path is not None:
          # Delete the copy of the object if the there is one
          directory = object.aq_parent
          copy_id = copy_path[-1]
          if hasattr(directory.aq_base, 'hasObject'):
            # optimize the case of a BTree folder
            if directory.hasObject(id):
              directory._delObject(copy_id)
          elif copy_id in directory.objectIds():
            directory._delObject(copy_id)
744
        signature.setStatus(self.PUB_CONFLICT_MERGE)
Sebastien Robin's avatar
Sebastien Robin committed
745

746
  security.declareProtected(Permissions.ModifyPortalContent,
747
      'managePublisherValue')
748
  def managePublisherValue(self, subscription_url, property_id, object_path,
749
      RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
750 751 752
    """
    Do whatever needed in order to store the local value on
    the remote server
Sebastien Robin's avatar
Sebastien Robin committed
753 754 755

    Suggestion (API)
      add method to view document with applied xupdate
756
      of a given subscriber XX
757
      (ex. viewSubscriberDocument?path=ddd&subscriber_id=dddd)
Sebastien Robin's avatar
Sebastien Robin committed
758
      Version=Version CPS
Jean-Paul Smets's avatar
Jean-Paul Smets committed
759 760
    """
    # Retrieve the conflict object
761 762 763
    LOG('manageLocalValue', DEBUG, '%s %s %s' % (str(subscription_url),
                                          str(property_id),
                                          str(object_path)))
Sebastien Robin's avatar
Sebastien Robin committed
764
    for conflict in self.getConflictList():
765
      if conflict.getPropertyId() == property_id:
766 767
        if '/'.join(conflict.getObjectPath()) == object_path:
          if conflict.getSubscriber().getSubscriptionUrl() == subscription_url:
768
            conflict.applyPublisherValue()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
769 770 771
    if RESPONSE is not None:
      RESPONSE.redirect('manageConflicts')

772 773 774 775
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manageSubscriberValue')
  def manageSubscriberValue(self, subscription_url, property_id, object_path, 
      RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
776 777 778 779
    """
    Do whatever needed in order to store the remote value locally
    and confirmed that the remote box should keep it's value
    """
780 781 782
    LOG('manageLocalValue', DEBUG, '%s %s %s' % (str(subscription_url),
                                          str(property_id),
                                          str(object_path)))
Sebastien Robin's avatar
Sebastien Robin committed
783
    for conflict in self.getConflictList():
784
      if conflict.getPropertyId() == property_id:
785 786
        if '/'.join(conflict.getObjectPath()) == object_path:
          if conflict.getSubscriber().getSubscriptionUrl() == subscription_url:
787
            conflict.applySubscriberValue()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
788 789
    if RESPONSE is not None:
      RESPONSE.redirect('manageConflicts')
790 791

  security.declareProtected(Permissions.ModifyPortalContent,
792
      'manageSubscriberDocument')
793 794 795 796
  def manageSubscriberDocument(self, subscription_url, object_path):
    """
    """
    for conflict in self.getConflictList():
797 798
      if '/'.join(conflict.getObjectPath()) == object_path:
        if conflict.getSubscriber().getSubscriptionUrl() == subscription_url:
799 800 801
          conflict.applySubscriberDocument()
          break
    self.managePublisherDocument(object_path)
802

803 804
  security.declareProtected(Permissions.ModifyPortalContent, 
      'managePublisherDocument')
805 806 807 808 809 810 811
  def managePublisherDocument(self, object_path):
    """
    """
    retry = True
    while retry:
      retry = False
      for conflict in self.getConflictList():
812
        if '/'.join(conflict.getObjectPath()) == object_path:
813 814 815
          conflict.applyPublisherDocument()
          retry = True
          break
Jean-Paul Smets's avatar
Jean-Paul Smets committed
816

817 818 819 820 821 822 823 824 825 826
  def resolveContext(self, context):
    """
    We try to return a path (like ('','erp5','foo') from the context.
    Context can be :
      - a path
      - an object
      - a string representing a path
    """
    if context is None:
      return context
Sebastien Robin's avatar
Sebastien Robin committed
827
    elif isinstance(context, tuple):
828
      return context
Sebastien Robin's avatar
Sebastien Robin committed
829
    elif isinstance(context, tuple):
830 831 832 833
      return tuple(context.split('/'))
    else:
      return context.getPhysicalPath()

834
  security.declarePublic('sendResponse')
835
  def sendResponse(self, to_url=None, from_url=None, sync_id=None,xml=None, 
836
      domain=None, send=1, content_type='application/vnd.syncml+xml'):
837 838 839 840
    """
    We will look at the url and we will see if we need to send mail, http
    response, or just copy to a file.
    """
841 842 843 844 845
    LOG('sendResponse, self.getPhysicalPath: ', DEBUG, self.getPhysicalPath())
    LOG('sendResponse, to_url: ', DEBUG, to_url)
    LOG('sendResponse, from_url: ', DEBUG, from_url)
    LOG('sendResponse, sync_id: ', DEBUG, sync_id)
    LOG('sendResponse, xml: \n', DEBUG, xml)
846 847 848

    if content_type == self.CONTENT_TYPE['SYNCML_WBXML']:
      xml = self.xml2wbxml(xml)
849
      #LOG('sendHttpResponse, xml after wbxml: \n', DEBUG, self.hexdump(xml))
Nicolas Delaby's avatar
Nicolas Delaby committed
850 851
    if isinstance(xml, unicode):
      xml = xml.encode('utf-8')
852 853 854 855 856 857 858
    if domain is not None:
      gpg_key = domain.getGPGKey()
      if gpg_key not in ('',None):
        filename = str(random.randrange(1,2147483600)) + '.txt'
        decrypted = file('/tmp/%s' % filename,'w')
        decrypted.write(xml)
        decrypted.close()
859
        (status,output)=commands.getstatusoutput('gzip /tmp/%s' % filename)
860 861 862
        (status,output)=commands.getstatusoutput('gpg --yes --homedir \
            /var/lib/zope/Products/ERP5SyncML/gnupg_keys -r "%s" -se \
            /tmp/%s.gz' % (gpg_key,filename))
863
        #LOG('readResponse, gpg output:', DEBUG, output)
864
        encrypted = file('/tmp/%s.gz.gpg' % filename,'r')
865 866
        xml = encrypted.read()
        encrypted.close()
867 868 869
        commands.getstatusoutput('rm -f /tmp/%s.gz' % filename)
        commands.getstatusoutput('rm -f /tmp/%s.gz.gpg' % filename)
    if send:
Sebastien Robin's avatar
Sebastien Robin committed
870
      if isinstance(to_url, str):
871
        if to_url.find('http://')==0:
Sebastien Robin's avatar
Sebastien Robin committed
872
          domain = aq_base(domain)
873 874 875 876 877
          if domain.domain_type == self.PUB and not domain.getActivityEnabled():
            # not use activity
            # XXX Make sure this is not a problem
            return None
          #use activities to send send an http response
878
          LOG('sendResponse, will start sendHttpResponse, xml', DEBUG, '')
879 880
          self.activate(activity='RAMQueue').sendHttpResponse(sync_id=sync_id,
                                           to_url=to_url,
881
                                           xml=xml,
882 883
                                           domain_path=domain.getPath(),
                                           content_type=content_type)
884 885 886 887 888 889 890 891 892 893
        elif to_url.find('file://')==0:
          filename = to_url[len('file:/'):]
          stream = file(filename,'w')
          stream.write(xml)
          stream.close()
          # we have to use local files (unit testing for example
        elif to_url.find('mailto:')==0:
          # we will send an email
          to_address = to_url[len('mailto:'):]
          from_address = from_url[len('mailto:'):]
894
          self.sendMail(from_address, to_address, sync_id, xml)
895
    return xml
896 897

  security.declarePrivate('sendHttpResponse')
898
  def sendHttpResponse(self, to_url=None, sync_id=None, xml=None,
899
      domain_path=None, content_type='application/vnd.syncml+xml'):
900
    domain = self.unrestrictedTraverse(domain_path)
901
    LOG('sendHttpResponse, starting with domain:', DEBUG, domain)
902
    if domain is not None:
903 904
      if domain.domain_type == self.PUB and not domain.getActivityEnabled():
            return xml
905 906 907 908
    # Retrieve the proxy from os variables
    proxy_url = ''
    if os.environ.has_key('http_proxy'):
      proxy_url = os.environ['http_proxy']
909
    LOG('sendHttpResponse, proxy_url:', DEBUG, proxy_url)
910 911 912 913 914 915 916
    if proxy_url !='':
      proxy_handler = urllib2.ProxyHandler({"http" :proxy_url})
    else:
      proxy_handler = urllib2.ProxyHandler({})
    pass_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
    auth_handler = urllib2.HTTPBasicAuthHandler(pass_mgr)
    proxy_auth_handler = urllib2.ProxyBasicAuthHandler(pass_mgr)
917 918
    opener = urllib2.build_opener(proxy_handler, proxy_auth_handler,
        auth_handler, TimeoutHTTPHandler)
919
    urllib2.install_opener(opener)
920 921
    to_encode = {}
    head = '<?xml version="1.0" encoding="UTF-8"?>'
922 923 924 925 926 927

    if content_type == self.CONTENT_TYPE['SYNCML_WBXML']:
      #because xml2wbxml add the head to the xml
      to_encode['text'] = xml
    else:
      to_encode['text'] = head + xml
928
    to_encode['sync_id'] = sync_id
929 930
    headers = {'User-Agent':'ERP5SyncML', 'Content-Type':content_type}

931
    #XXX bad hack for synchronization with erp5
932 933 934 935 936 937 938 939 940 941
    # because at this time, when we call the readResponse method, we must
    # encode the data with urlencode if we want the readResponse method to 
    # receive the data's in parameters.
    # All this should be improved to not use urlencode in all cases.
    # to do this, perhaps use SOAP :
    #  - http://en.wikipedia.org/wiki/SOAP
    #  - http://www.contentmanagementsoftware.info/zope/SOAPSupport
    #  - http://svn.zope.org/soap/trunk/

    if domain.getSynchronizeWithERP5Sites():
942
      LOG('Synchronization with another ERP5 instance ...', DEBUG, '')
943 944 945 946 947 948 949 950 951 952
      if to_url.find('readResponse')<0:
        to_url = to_url + '/portal_synchronizations/readResponse'
      encoded = urllib.urlencode(to_encode)
      data=encoded
      request = urllib2.Request(url=to_url, data=data)
    else:
    #XXX only to synchronize with other server than erp5 (must be improved):
      data=head+xml
      request = urllib2.Request(to_url, data, headers)

953
    try:
954 955
      url_file = urllib2.urlopen(request)
      result = url_file.read()
956
    except socket.error, msg:
957 958
      self.activate(activity='RAMQueue').sendHttpResponse(to_url=to_url,
          sync_id=sync_id, xml=xml, domain_path=domain.getPath(),
959
          content_type=content_type)
960 961
      LOG('sendHttpResponse, socket ERROR:', INFO, msg)
      LOG('sendHttpResponse, url, data', INFO, (url, data))
962
      return
963
    except urllib2.URLError, msg:
964 965
      LOG("sendHttpResponse, can't open url %s :" % to_url, INFO, msg)
      LOG('sendHttpResponse, to_url, data', INFO, (to_url, data))
966 967
      return

968
    if domain is not None:
969 970 971
      if domain.domain_type == self.SUB and not domain.getActivityEnabled():
            #if we don't use activity :
            gpg_key = domain.getGPGKey()
972 973
            if result not in (None, ''):
              self.readResponse(sync_id=sync_id, text=result)
974
    return result
975 976 977 978 979 980 981 982

  security.declarePublic('sync')
  def sync(self):
    """
    This will try to synchronize every subscription
    """
    # Login as a manager to make sure we can create objects
    uf = self.acl_users
983
    user = UnrestrictedUser('syncml', 'syncml', ['Manager', 'Member'], '')
984 985
    newSecurityManager(None, user)
    message_list = self.portal_activities.getMessageList()
986
    LOG('sync, message_list:', DEBUG, message_list)
987 988
    if len(message_list) == 0:
      for subscription in self.getSubscriptionList():
989
        LOG('sync, type(subcription):', DEBUG, type(subscription))
990
        self.activate(activity='RAMQueue').SubSync(subscription.getPath())
991 992

  security.declarePublic('readResponse')
993
  def readResponse(self, text='', sync_id=None, to_url=None, from_url=None):
994 995 996 997
    """
    We will look at the url and we will see if we need to send mail, http
    response, or just copy to a file.
    """
998 999
    LOG('readResponse, text :', DEBUG, text)
    #LOG('readResponse, hexdump(text) :', DEBUG, self.hexdump(text))
1000

1001 1002
    # Login as a manager to make sure we can create objects
    uf = self.acl_users
1003
    user = uf.getUserById('syncml').__of__(uf)
1004
    user = UnrestrictedUser('syncml', 'syncml', ['Manager', 'Member'], '')
1005
    newSecurityManager(None, user)
1006
    status_code = None
1007

1008
    if text not in ('', None):
1009 1010 1011 1012 1013
      # XXX We will look everywhere for a publication/subsription with
      # the id sync_id, this is not so good, but there is no way yet
      # to know if we will call a publication or subscription XXX
      gpg_key = ''
      for publication in self.getPublicationList():
1014
        if publication.getTitle() == sync_id:
1015
          gpg_key = publication.getGPGKey()
1016
          domain = publication
1017 1018
      if gpg_key == '':
        for subscription in self.getSubscriptionList():
1019
          if subscription.getTitle() == sync_id:
1020
            gpg_key = subscription.getGPGKey()
1021
            domain = subscription
1022 1023
      # decrypt the message if needed
      if gpg_key not in (None,''):
1024
        filename = str(random.randrange(1, 2147483600)) + '.txt'
1025
        encrypted = file('/tmp/%s.gz.gpg' % filename,'w')
1026 1027
        encrypted.write(text)
        encrypted.close()
1028 1029 1030
        (status,output)=commands.getstatusoutput('gpg --homedir \
            /var/lib/zope/Products/ERP5SyncML/gnupg_keys -r "%s"  --decrypt \
            /tmp/%s.gz.gpg > /tmp/%s.gz' % (gpg_key, filename, filename))
1031
        LOG('readResponse, gpg output:', TRACE, output)
1032
        (status,output)=commands.getstatusoutput('gunzip /tmp/%s.gz' % filename)
1033 1034
        decrypted = file('/tmp/%s' % filename,'r')
        text = decrypted.read()
1035
        LOG('readResponse, text:', TRACE, text)
1036 1037
        decrypted.close()
        commands.getstatusoutput('rm -f /tmp/%s' % filename)
1038
        commands.getstatusoutput('rm -f /tmp/%s.gz.gpg' % filename)
1039 1040
      # Get the target and then find the corresponding publication or
      # Subscription
1041
      LOG('type(text) : ', TRACE, type(text))
1042 1043
      if domain.getSyncContentType() == self.CONTENT_TYPE['SYNCML_WBXML']:
        text = self.wbxml2xml(text)
1044
      LOG('readResponse, text after wbxml :\n', TRACE, text)
1045
      xml = Parse(text)
Sebastien Robin's avatar
Sebastien Robin committed
1046
      url = self.getTarget(xml)
1047
      for publication in self.getPublicationList():
1048 1049
        if publication.getPublicationUrl()==url and \
        publication.getTitle()==sync_id:
1050 1051 1052 1053 1054 1055
          if publication.getActivityEnabled():
            #use activities to send SyncML data.
            self.activate(activity='RAMQueue').PubSync(publication.getPath(),
                                                       text)
            return ' '
          else:
1056
            result = self.PubSync(publication.getPath(), xml)
1057 1058
            # Then encrypt the message
            xml = result['xml']
1059 1060
            if publication.getSyncContentType() ==\
             self.CONTENT_TYPE['SYNCML_WBXML']:
1061
              xml = self.xml2wbxml(xml)
1062
            return xml
1063
      for subscription in self.getSubscriptionList():
1064 1065
        if subscription.getSubscriptionUrl() == url and \
            subscription.getTitle() == sync_id:
1066
              subscription_path = self.getSubscription(sync_id).getPath()
1067
              self.activate(activity='RAMQueue').SubSync(subscription_path,
1068 1069
                                                         text)
              return ' '
1070 1071

    # we use from only if we have a file 
Sebastien Robin's avatar
Sebastien Robin committed
1072
    elif isinstance(from_url, str):
1073
      if from_url.find('file://') == 0:
1074 1075
        try:
          filename = from_url[len('file:/'):]
1076
          stream = file(filename, 'r')
1077
          xml = stream.read()
1078
          LOG('readResponse', DEBUG, 'file... msg: %s' % str(stream.read()))
1079
        except IOError:
1080
          LOG('readResponse, cannot read file: ', DEBUG, filename)
1081
          xml = None
1082
        if xml is not None and len(xml) == 0:
1083 1084
          xml = None
        return xml
1085

1086 1087
  security.declareProtected(Permissions.ModifyPortalContent, 
      'getPublicationIdFromTitle')
1088 1089 1090 1091 1092 1093
  def getPublicationIdFromTitle(self, title):
    """
    simply return an id from a title
    """
    return 'pub_' + title

1094 1095
  security.declareProtected(Permissions.ModifyPortalContent, 
      'getPublicationIdFromTitle')
1096 1097 1098 1099 1100 1101
  def getSubscriptionIdFromTitle(self, title):
    """
    simply return an id from a title
    """
    return 'sub_' + title

Sebastien Robin's avatar
Sebastien Robin committed
1102
  security.declareProtected(Permissions.ModifyPortalContent, 'addNode')
1103
  def addNode(self, conduit='ERP5Conduit', **kw):
Sebastien Robin's avatar
Sebastien Robin committed
1104 1105 1106
    """
    """
    # Import the conduit and get it
1107
    conduit_object = self.getConduitByName(conduit)
Sebastien Robin's avatar
Sebastien Robin committed
1108 1109
    return conduit_object.addNode(**kw)

1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121
  def hexdump(self, raw=''):
    """
    this function is used to display the raw in a readable format :
    it display raw in hexadecimal format and display too the printable 
    characters (because if not printable characters are printed, it makes 
    terminal display crash)
    """
    buf = ""
    line = ""
    start = 0
    done = False
    while not done:
1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146
      end = start + 16
      max = len(str(raw))
      if end > max:
        end = max
        done = True
      chunk = raw[start:end]
      for i in xrange(len(chunk)):
        if i > 0:
          spacing = " "
        else:
          spacing = ""
        buf += "%s%02x" % (spacing, ord(chunk[i]))
      if done:
        for i in xrange(16 - (end % 16)):
          buf += "   "
      buf += "  "
      for c in chunk:
        val = ord(c)
        if val >= 33 and val <= 126:
          buf += c
        else:
          buf += "."
      buf += "\n"
      start += 16
    return buf
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1147
InitializeClass( SynchronizationTool )