SynchronizationTool.py 43.2 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, 
182 183 184
            media_type=None, authentication_format='b64', 
            authentication_type='syncml:auth-basic', 
            RESPONSE=None, activity_enabled = False,
185 186
            sync_content_type='application/vnd.syncml+xml', 
            synchronize_with_erp5_sites=True):
187
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
188 189
      create a new publication
    """
190 191 192
    #if not('publications' in self.objectIds()):
    #  publications = Folder('publications')
    #  self._setObject(publications.id, publications)
193
    folder = self.getObjectContainer()
194
    new_id = self.getPublicationIdFromTitle(title)
195 196 197
    pub = Publication(new_id, title, publication_url,
                      destination_path, source_uri, query, xml_mapping,
                      conduit, gpg_key, synchronization_id_generator,
198
                      media_type, 
199 200
                      authentication_format, 
                      authentication_type,
201
                      activity_enabled, synchronize_with_erp5_sites,
202
                      sync_content_type)
203
    folder._setObject( new_id, pub )
204 205 206
    #if len(self.list_publications) == 0:
    #  self.list_publications = PersistentMapping()
    #self.list_publications[id] = pub
Jean-Paul Smets's avatar
Jean-Paul Smets committed
207 208 209
    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')

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

237
  security.declareProtected(Permissions.ModifyPortalContent,
238
      'manage_editPublication')
239 240 241
  def manage_editPublication(self, title, publication_url,
                            destination_path, source_uri, query, xml_mapping,
                            conduit, gpg_key, synchronization_id_generator,
242
                            media_type=None,
243
                            authentication_format='b64',
244
                            authentication_type='syncml:auth-basic',
245 246 247
                            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
248 249 250
    """
      modify a publication
    """
251
    pub = self.getPublication(title)
252
    pub.setTitle(title)
Nicolas Delaby's avatar
Nicolas Delaby committed
253
    pub.setActivityEnabled(activity_enabled)
254 255
    pub.setPublicationUrl(publication_url)
    pub.setDestinationPath(destination_path)
256
    pub.setSourceURI(source_uri)
257
    pub.setQuery(query)
258
    pub.setConduit(conduit)
259 260
    pub.setXMLMapping(xml_mapping)
    pub.setGPGKey(gpg_key)
261
    pub.setSynchronizationIdGenerator(synchronization_id_generator)
262
    pub.setMediaType(media_type)
263 264
    pub.setAuthenticationFormat(authentication_format)
    pub.setAuthenticationType(authentication_type)
265 266
    pub.setSyncContentType(sync_content_type)
    pub.setSynchronizeWithERP5Sites(synchronize_with_erp5_sites)
267

Jean-Paul Smets's avatar
Jean-Paul Smets committed
268 269 270
    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')

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

Jean-Paul Smets's avatar
Jean-Paul Smets committed
302 303 304
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

305
  security.declareProtected(Permissions.ModifyPortalContent,
306
      'manage_deletePublication')
307
  def manage_deletePublication(self, title, RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
308 309 310
    """
      delete a publication
    """
311
    id = self.getPublicationIdFromTitle(title)
312 313
    folder = self.getObjectContainer()
    folder._delObject(id)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
314 315 316
    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')

317
  security.declareProtected(Permissions.ModifyPortalContent,
318
      'manage_deleteSubscription')
319
  def manage_deleteSubscription(self, title, RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
320 321 322
    """
      delete a subscription
    """
323
    id = self.getSubscriptionIdFromTitle(title)
324 325
    folder = self.getObjectContainer()
    folder._delObject(id)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
326 327 328
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

329
  security.declareProtected(Permissions.ModifyPortalContent,
330
      'manage_resetPublication')
331
  def manage_resetPublication(self, title, RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
332 333 334
    """
      reset a publication
    """
335
    pub = self.getPublication(title)
336
    pub.resetAllSubscribers()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
337 338 339
    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')

340
  security.declareProtected(Permissions.ModifyPortalContent,
341
      'manage_resetSubscription')
342
  def manage_resetSubscription(self, title, RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
343 344 345
    """
      reset a subscription
    """
346
    sub = self.getSubscription(title)
347 348
    sub.resetAllSignatures()
    sub.resetAnchors()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
349 350 351
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

352
  security.declareProtected(Permissions.ModifyPortalContent,
353
      'manage_syncSubscription')
354 355 356 357
  def manage_syncSubscription(self, title, RESPONSE=None):
    """
      reset a subscription
    """
358
    self.SubSync(self.getSubscription(title).getPath())
359 360 361
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

362 363
  security.declareProtected(Permissions.AccessContentsInformation,
      'getPublicationList')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
364 365 366 367
  def getPublicationList(self):
    """
      Return a list of publications
    """
368
    folder = self.getObjectContainer()
369
    return [pub for pub in folder.objectValues() if pub.getDomainType() == self.PUB]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
370

371 372
  security.declareProtected(Permissions.AccessContentsInformation,
      'getPublication')
373
  def getPublication(self, title):
374
    """
375
      Return the  publications with this id
376
    """
377
    pub = None
378 379
    for p in self.getPublicationList():
      if p.getTitle() == title:
380 381 382
        pub = p
        break
    return pub
383

384 385
  security.declareProtected(Permissions.AccessContentsInformation,
      'getObjectContainer')
386 387 388 389 390 391 392 393 394 395 396
  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

397 398
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSubscriptionList')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
399 400 401 402
  def getSubscriptionList(self):
    """
      Return a list of publications
    """
403
    folder = self.getObjectContainer()
404
    return [sub for sub in folder.objectValues() if sub.getDomainType() == self.SUB]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
405

406
  def getSubscription(self, title):
407
    """
408
      Returns the subscription with this title
409
    """
410
    sub = None
411 412
    for s in self.getSubscriptionList():
      if s.getTitle() == title:
413 414
        sub = s
    return sub
415 416


417 418
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSynchronizationList')
419
  def getSynchronizationList(self):
420 421 422 423 424
    """
      Returns the list of subscriptions and publications
    """
    return self.getSubscriptionList() + self.getPublicationList()

425 426
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSubscriberList')
427 428 429 430 431
  def getSubscriberList(self):
    """
      Returns the list of subscribers and subscriptions
    """
    s_list = []
432
    s_list.extend(self.getSubscriptionList())
433
    for publication in self.getPublicationList():
434
      s_list.extend(publication.getSubscriberList())
435 436
    return s_list

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

466 467
  security.declareProtected(Permissions.AccessContentsInformation,
      'getDocumentConflictList')
468 469 470 471 472 473 474 475
  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)


476 477
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSynchronizationState')
478
  def getSynchronizationState(self, context):
479
    """
480
    context : the context on which we are looking for state
481

482 483 484
    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.
485

486
    This method returns a mapping between subscription and states
Sebastien Robin's avatar
Sebastien Robin committed
487 488 489 490 491

    JPS suggestion:
      path -> object, document, context, etc.
      type -> '/titi/toto' or ('','titi', 'toto') or <Base instance 1562567>
      object = self.resolveContext(context) (method to add)
492
    """
493
    path = self.resolveContext(context)
494 495
    conflict_list = self.getConflictList()
    state_list= []
496
    LOG('getSynchronizationState', DEBUG, 'path: %s' % str(path))
497 498
    for conflict in conflict_list:
      if conflict.getObjectPath() == path:
499
        LOG('getSynchronizationState', DEBUG, 'found a conflict: %s' % str(conflict))
500
        state_list += [[conflict.getSubscriber(),self.CONFLICT]]
501
    for domain in self.getSynchronizationList():
502
      destination = domain.getDestinationPath()
503
      LOG('getSynchronizationState', TRACE, 'destination: %s' % str(destination))
504
      j_path = '/'.join(path)
505
      LOG('getSynchronizationState', TRACE, 'j_path: %s' % str(j_path))
506 507
      if j_path.find(destination)==0:
        o_id = j_path[len(destination)+1:].split('/')[0]
508
        LOG('getSynchronizationState', TRACE, 'o_id: %s' % o_id)
509 510 511 512 513
        subscriber_list = []
        if domain.domain_type==self.PUB:
          subscriber_list = domain.getSubscriberList()
        else:
          subscriber_list = [domain]
514
        LOG('getSynchronizationState, subscriber_list:', TRACE, subscriber_list)
515
        for subscriber in subscriber_list:
516
          signature = subscriber.getSignatureFromObjectId(o_id)
517
          #XXX check if signature could be not None ...
518 519
          if signature is not None:
            state = signature.getStatus()
520 521
            LOG('getSynchronizationState:', TRACE, 'sub.dest :%s, state: %s' % \
                                   (subscriber.getSubscriptionUrl(),str(state)))
522 523 524 525 526 527 528 529
            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
530

531 532 533 534 535 536
  security.declareProtected(Permissions.AccessContentsInformation,
      'getAlertCodeList')
  def getAlertCodeList(self):
    return self.CODE_LIST

  security.declareProtected(Permissions.ModifyPortalContent,
537
      'applyPublisherValue')
538
  def applyPublisherValue(self, conflict):
Sebastien Robin's avatar
Sebastien Robin committed
539 540 541 542 543
    """
      after a conflict resolution, we have decided
      to keep the local version of an object
    """
    object = self.unrestrictedTraverse(conflict.getObjectPath())
544
    subscriber = conflict.getSubscriber()
Sebastien Robin's avatar
Sebastien Robin committed
545
    # get the signature:
546
    LOG('p_sync.applyPublisherValue, subscriber: ', DEBUG, subscriber)
547
    signature = subscriber.getSignatureFromObjectId(object.getId()) # XXX may be change for rid
548
    copy_path = conflict.getCopyPath()
549
    LOG('p_sync.applyPublisherValue, copy_path: ', TRACE, copy_path)
Sebastien Robin's avatar
Sebastien Robin committed
550 551
    signature.delConflict(conflict)
    if signature.getConflictList() == []:
552
      if copy_path is not None:
553
        LOG('p_sync.applyPublisherValue, conflict_list empty on : ', TRACE, signature)
554 555 556
        # Delete the copy of the object if the there is one
        directory = object.aq_parent
        copy_id = copy_path[-1]
557
        LOG('p_sync.applyPublisherValue, copy_id: ', TRACE, copy_id)
558 559
        if hasattr(directory.aq_base, 'hasObject'):
          # optimize the case of a BTree folder
560
          LOG('p_sync.applyPublisherValue, deleting...: ', TRACE, copy_id)
561 562 563 564
          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
565 566
      signature.setStatus(self.PUB_CONFLICT_MERGE)

567
  security.declareProtected(Permissions.ModifyPortalContent,
568
      'applyPublisherDocument')
569 570 571 572 573 574 575
  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:
576
        LOG('applyPublisherDocument, applying on conflict: ', DEBUG, conflict)
577 578
        c.applyPublisherValue()

579 580
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getPublisherDocumentPath')
581 582 583 584 585 586 587
  def getPublisherDocumentPath(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
    subscriber = conflict.getSubscriber()
    return conflict.getObjectPath()

588 589
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getPublisherDocument')
590 591 592 593 594
  def getPublisherDocument(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
    publisher_object_path = self.getPublisherDocumentPath(conflict)
595
    LOG('getPublisherDocument publisher_object_path', TRACE, publisher_object_path)
596 597 598
    publisher_object = self.unrestrictedTraverse(publisher_object_path)
    return publisher_object

599 600 601 602 603 604 605 606 607 608 609 610 611

  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)
612 613 614
    publisher_xml = self.getXMLObject(
                              object=publisher_object,
                              xml_mapping=subscriber.getXMLMapping())
615 616 617 618
    directory = publisher_object.aq_parent
    object_id = docid
    if object_id in directory.objectIds():
        directory._delObject(object_id)
619
        # Import the conduit and get it
620
        conduit_name = subscriber.getConduit()
621 622 623 624 625
        conduit = self.getConduitByName(conduit_name)
        conduit.addNode(
                    xml=publisher_xml,
                    object=directory,
                    object_id=object_id)
626 627 628 629 630 631
        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

632 633
  def _getCopyId(self, object):
    directory = object.aq_inner.aq_parent
634
    if directory.getId() != 'portal_repository':
635 636 637 638 639 640 641 642 643
      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
644

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

676 677
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubscriberDocument')
678 679 680 681 682 683 684 685
  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

686 687
  security.declareProtected(Permissions.ModifyPortalContent, 
      'applySubscriberDocument')
688 689 690 691 692 693 694 695 696
  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()

697 698
  security.declareProtected(Permissions.ModifyPortalContent, 
      'applySubscriberValue')
699
  def applySubscriberValue(self, conflict,object=None):
Sebastien Robin's avatar
Sebastien Robin committed
700 701 702 703
    """
      after a conflict resolution, we have decided
      to keep the local version of an object
    """
704 705 706 707 708 709 710
    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
711
    subscriber = conflict.getSubscriber()
Sebastien Robin's avatar
Sebastien Robin committed
712
    # get the signature:
Nicolas Delaby's avatar
Nicolas Delaby committed
713
    #LOG('p_sync.setRemoteObject, subscriber: ',0,subscriber)
714
    signature = subscriber.getSignatureFromObjectId(object.getId()) # XXX may be change for rid
715
    # Import the conduit and get it
716
    conduit_name = subscriber.getConduit()
717
    conduit = self.getConduitByName(conduit_name)
Sebastien Robin's avatar
Sebastien Robin committed
718 719
    for xupdate in conflict.getXupdateList():
      conduit.updateNode(xml=xupdate,object=object,force=1)
720
    if solve_conflict:
721
      copy_path = conflict.getCopyPath()
722 723
      signature.delConflict(conflict)
      if signature.getConflictList() == []:
724 725 726 727 728 729 730 731 732 733
        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)
734
        signature.setStatus(self.PUB_CONFLICT_MERGE)
Sebastien Robin's avatar
Sebastien Robin committed
735

736
  security.declareProtected(Permissions.ModifyPortalContent,
737
      'managePublisherValue')
738
  def managePublisherValue(self, subscription_url, property_id, object_path,
739
      RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
740 741 742
    """
    Do whatever needed in order to store the local value on
    the remote server
Sebastien Robin's avatar
Sebastien Robin committed
743 744 745

    Suggestion (API)
      add method to view document with applied xupdate
746
      of a given subscriber XX
747
      (ex. viewSubscriberDocument?path=ddd&subscriber_id=dddd)
Sebastien Robin's avatar
Sebastien Robin committed
748
      Version=Version CPS
Jean-Paul Smets's avatar
Jean-Paul Smets committed
749 750
    """
    # Retrieve the conflict object
751 752 753
    LOG('manageLocalValue', DEBUG, '%s %s %s' % (str(subscription_url),
                                          str(property_id),
                                          str(object_path)))
Sebastien Robin's avatar
Sebastien Robin committed
754
    for conflict in self.getConflictList():
755
      if conflict.getPropertyId() == property_id:
756 757
        if '/'.join(conflict.getObjectPath()) == object_path:
          if conflict.getSubscriber().getSubscriptionUrl() == subscription_url:
758
            conflict.applyPublisherValue()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
759 760 761
    if RESPONSE is not None:
      RESPONSE.redirect('manageConflicts')

762 763 764 765
  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
766 767 768 769
    """
    Do whatever needed in order to store the remote value locally
    and confirmed that the remote box should keep it's value
    """
770 771 772
    LOG('manageLocalValue', DEBUG, '%s %s %s' % (str(subscription_url),
                                          str(property_id),
                                          str(object_path)))
Sebastien Robin's avatar
Sebastien Robin committed
773
    for conflict in self.getConflictList():
774
      if conflict.getPropertyId() == property_id:
775 776
        if '/'.join(conflict.getObjectPath()) == object_path:
          if conflict.getSubscriber().getSubscriptionUrl() == subscription_url:
777
            conflict.applySubscriberValue()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
778 779
    if RESPONSE is not None:
      RESPONSE.redirect('manageConflicts')
780 781

  security.declareProtected(Permissions.ModifyPortalContent,
782
      'manageSubscriberDocument')
783 784 785 786
  def manageSubscriberDocument(self, subscription_url, object_path):
    """
    """
    for conflict in self.getConflictList():
787 788
      if '/'.join(conflict.getObjectPath()) == object_path:
        if conflict.getSubscriber().getSubscriptionUrl() == subscription_url:
789 790 791
          conflict.applySubscriberDocument()
          break
    self.managePublisherDocument(object_path)
792

793 794
  security.declareProtected(Permissions.ModifyPortalContent, 
      'managePublisherDocument')
795 796 797 798 799 800 801
  def managePublisherDocument(self, object_path):
    """
    """
    retry = True
    while retry:
      retry = False
      for conflict in self.getConflictList():
802
        if '/'.join(conflict.getObjectPath()) == object_path:
803 804 805
          conflict.applyPublisherDocument()
          retry = True
          break
Jean-Paul Smets's avatar
Jean-Paul Smets committed
806

807 808 809 810 811 812 813 814 815 816
  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
817
    elif isinstance(context, tuple):
818
      return context
Sebastien Robin's avatar
Sebastien Robin committed
819
    elif isinstance(context, tuple):
820 821 822 823
      return tuple(context.split('/'))
    else:
      return context.getPhysicalPath()

824
  security.declarePublic('sendResponse')
825
  def sendResponse(self, to_url=None, from_url=None, sync_id=None,xml=None, 
826
      domain=None, send=1, content_type='application/vnd.syncml+xml'):
827 828 829 830
    """
    We will look at the url and we will see if we need to send mail, http
    response, or just copy to a file.
    """
831 832 833 834 835
    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)
836 837 838

    if content_type == self.CONTENT_TYPE['SYNCML_WBXML']:
      xml = self.xml2wbxml(xml)
839
      #LOG('sendHttpResponse, xml after wbxml: \n', DEBUG, self.hexdump(xml))
Nicolas Delaby's avatar
Nicolas Delaby committed
840 841
    if isinstance(xml, unicode):
      xml = xml.encode('utf-8')
842 843 844 845 846 847 848
    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()
849
        (status,output)=commands.getstatusoutput('gzip /tmp/%s' % filename)
850 851 852
        (status,output)=commands.getstatusoutput('gpg --yes --homedir \
            /var/lib/zope/Products/ERP5SyncML/gnupg_keys -r "%s" -se \
            /tmp/%s.gz' % (gpg_key,filename))
853
        #LOG('readResponse, gpg output:', DEBUG, output)
854
        encrypted = file('/tmp/%s.gz.gpg' % filename,'r')
855 856
        xml = encrypted.read()
        encrypted.close()
857 858 859
        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
860
      if isinstance(to_url, str):
861
        if to_url.find('http://')==0:
Sebastien Robin's avatar
Sebastien Robin committed
862
          domain = aq_base(domain)
863 864 865 866 867
          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
868
          LOG('sendResponse, will start sendHttpResponse, xml', DEBUG, '')
869 870
          self.activate(activity='RAMQueue').sendHttpResponse(sync_id=sync_id,
                                           to_url=to_url,
871
                                           xml=xml,
872 873
                                           domain_path=domain.getPath(),
                                           content_type=content_type)
874 875 876 877 878 879 880 881 882 883
        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:'):]
884
          self.sendMail(from_address, to_address, sync_id, xml)
885
    return xml
886 887

  security.declarePrivate('sendHttpResponse')
888
  def sendHttpResponse(self, to_url=None, sync_id=None, xml=None,
889
      domain_path=None, content_type='application/vnd.syncml+xml'):
890
    domain = self.unrestrictedTraverse(domain_path)
891
    LOG('sendHttpResponse, starting with domain:', DEBUG, domain)
892
    if domain is not None:
893 894
      if domain.domain_type == self.PUB and not domain.getActivityEnabled():
            return xml
895 896 897 898
    # Retrieve the proxy from os variables
    proxy_url = ''
    if os.environ.has_key('http_proxy'):
      proxy_url = os.environ['http_proxy']
899
    LOG('sendHttpResponse, proxy_url:', DEBUG, proxy_url)
900 901 902 903 904 905 906
    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)
907 908
    opener = urllib2.build_opener(proxy_handler, proxy_auth_handler,
        auth_handler, TimeoutHTTPHandler)
909
    urllib2.install_opener(opener)
910 911
    to_encode = {}
    head = '<?xml version="1.0" encoding="UTF-8"?>'
912 913 914 915 916 917

    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
918
    to_encode['sync_id'] = sync_id
919 920
    headers = {'User-Agent':'ERP5SyncML', 'Content-Type':content_type}

921
    #XXX bad hack for synchronization with erp5
922 923 924 925 926 927 928 929 930 931
    # 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():
932
      LOG('Synchronization with another ERP5 instance ...', DEBUG, '')
933 934 935 936 937 938 939 940 941 942
      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)

943
    try:
944 945
      url_file = urllib2.urlopen(request)
      result = url_file.read()
946
    except socket.error, msg:
947 948
      self.activate(activity='RAMQueue').sendHttpResponse(to_url=to_url,
          sync_id=sync_id, xml=xml, domain_path=domain.getPath(),
949
          content_type=content_type)
950 951
      LOG('sendHttpResponse, socket ERROR:', INFO, msg)
      LOG('sendHttpResponse, url, data', INFO, (url, data))
952
      return
953
    except urllib2.URLError, msg:
954 955
      LOG("sendHttpResponse, can't open url %s :" % to_url, INFO, msg)
      LOG('sendHttpResponse, to_url, data', INFO, (to_url, data))
956 957
      return

958
    if domain is not None:
959 960 961
      if domain.domain_type == self.SUB and not domain.getActivityEnabled():
            #if we don't use activity :
            gpg_key = domain.getGPGKey()
962 963
            if result not in (None, ''):
              self.readResponse(sync_id=sync_id, text=result)
964
    return result
965 966 967 968 969 970 971

  security.declarePublic('sync')
  def sync(self):
    """
    This will try to synchronize every subscription
    """
    message_list = self.portal_activities.getMessageList()
972
    LOG('sync, len(message_list):', DEBUG, len(message_list))
973 974
    if len(message_list) == 0:
      for subscription in self.getSubscriptionList():
Fabien Morin's avatar
Fabien Morin committed
975 976
        user = subscription.getZopeUser()
        newSecurityManager(None, user)
977
        self.activate(activity='RAMQueue').SubSync(subscription.getPath())
978 979

  security.declarePublic('readResponse')
980
  def readResponse(self, text='', sync_id=None, to_url=None, from_url=None):
981 982 983 984
    """
    We will look at the url and we will see if we need to send mail, http
    response, or just copy to a file.
    """
985 986
    LOG('readResponse, text :', DEBUG, text)
    #LOG('readResponse, hexdump(text) :', DEBUG, self.hexdump(text))
987
    status_code = None
988
    if text not in ('', None):
989 990 991 992
      # 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 = ''
Fabien Morin's avatar
Fabien Morin committed
993
      LOG('readResponse, sync_id :', DEBUG, sync_id)
994
      for publication in self.getPublicationList():
995
        if publication.getTitle() == sync_id:
996
          gpg_key = publication.getGPGKey()
997
          domain = publication
998 999
      if gpg_key == '':
        for subscription in self.getSubscriptionList():
1000
          if subscription.getTitle() == sync_id:
1001
            gpg_key = subscription.getGPGKey()
1002
            domain = subscription
Fabien Morin's avatar
Fabien Morin committed
1003 1004 1005
            user = domain.getZopeUser()
            LOG('readResponse, user :', DEBUG, user)
            newSecurityManager(None, user)
1006 1007
      # decrypt the message if needed
      if gpg_key not in (None,''):
1008
        filename = str(random.randrange(1, 2147483600)) + '.txt'
1009
        encrypted = file('/tmp/%s.gz.gpg' % filename,'w')
1010 1011
        encrypted.write(text)
        encrypted.close()
1012 1013 1014
        (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))
1015
        LOG('readResponse, gpg output:', TRACE, output)
1016
        (status,output)=commands.getstatusoutput('gunzip /tmp/%s.gz' % filename)
1017 1018
        decrypted = file('/tmp/%s' % filename,'r')
        text = decrypted.read()
1019
        LOG('readResponse, text:', TRACE, text)
1020 1021
        decrypted.close()
        commands.getstatusoutput('rm -f /tmp/%s' % filename)
1022
        commands.getstatusoutput('rm -f /tmp/%s.gz.gpg' % filename)
1023 1024
      # Get the target and then find the corresponding publication or
      # Subscription
1025
      LOG('type(text) : ', TRACE, type(text))
1026 1027
      if domain.getSyncContentType() == self.CONTENT_TYPE['SYNCML_WBXML']:
        text = self.wbxml2xml(text)
1028
      LOG('readResponse, text after wbxml :\n', TRACE, text)
1029
      xml = Parse(text)
Sebastien Robin's avatar
Sebastien Robin committed
1030
      url = self.getTarget(xml)
1031
      for publication in self.getPublicationList():
1032 1033
        if publication.getPublicationUrl()==url and \
        publication.getTitle()==sync_id:
1034 1035 1036 1037 1038 1039
          if publication.getActivityEnabled():
            #use activities to send SyncML data.
            self.activate(activity='RAMQueue').PubSync(publication.getPath(),
                                                       text)
            return ' '
          else:
1040
            result = self.PubSync(publication.getPath(), xml)
1041 1042
            # Then encrypt the message
            xml = result['xml']
1043 1044
            if publication.getSyncContentType() ==\
             self.CONTENT_TYPE['SYNCML_WBXML']:
1045
              xml = self.xml2wbxml(xml)
1046
            return xml
1047
      for subscription in self.getSubscriptionList():
1048 1049
        if subscription.getSubscriptionUrl() == url and \
            subscription.getTitle() == sync_id:
1050
              subscription_path = self.getSubscription(sync_id).getPath()
1051
              self.activate(activity='RAMQueue').SubSync(subscription_path,
1052 1053
                                                         text)
              return ' '
1054 1055

    # we use from only if we have a file 
Sebastien Robin's avatar
Sebastien Robin committed
1056
    elif isinstance(from_url, str):
1057
      if from_url.find('file://') == 0:
1058 1059
        try:
          filename = from_url[len('file:/'):]
1060
          stream = file(filename, 'r')
1061
          xml = stream.read()
1062
          LOG('readResponse', DEBUG, 'file... msg: %s' % str(stream.read()))
1063
        except IOError:
1064
          LOG('readResponse, cannot read file: ', DEBUG, filename)
1065
          xml = None
1066
        if xml is not None and len(xml) == 0:
1067 1068
          xml = None
        return xml
1069

1070 1071
  security.declareProtected(Permissions.ModifyPortalContent, 
      'getPublicationIdFromTitle')
1072 1073 1074 1075 1076 1077
  def getPublicationIdFromTitle(self, title):
    """
    simply return an id from a title
    """
    return 'pub_' + title

1078 1079
  security.declareProtected(Permissions.ModifyPortalContent, 
      'getPublicationIdFromTitle')
1080 1081 1082 1083 1084 1085
  def getSubscriptionIdFromTitle(self, title):
    """
    simply return an id from a title
    """
    return 'sub_' + title

Sebastien Robin's avatar
Sebastien Robin committed
1086
  security.declareProtected(Permissions.ModifyPortalContent, 'addNode')
1087
  def addNode(self, conduit='ERP5Conduit', **kw):
Sebastien Robin's avatar
Sebastien Robin committed
1088 1089 1090
    """
    """
    # Import the conduit and get it
1091
    conduit_object = self.getConduitByName(conduit)
Sebastien Robin's avatar
Sebastien Robin committed
1092 1093
    return conduit_object.addNode(**kw)

1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105
  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:
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130
      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
1131
InitializeClass( SynchronizationTool )